Migración a Base UI: Quitar atributo asChild

This commit is contained in:
David Arranz 2026-04-12 12:09:20 +02:00
parent 85556056fd
commit 909a576f08
27 changed files with 433 additions and 471 deletions

View File

@ -233,7 +233,7 @@ export default function ShadcnShowcasePage() {
</div>
<div className="flex items-center gap-4">
<AlertDialog>
<AlertDialogTrigger asChild>
<AlertDialogTrigger>
<Button variant="outline">
<span className="hidden md:block">Alert Dialog</span>
<span className="block md:hidden">Dialog</span>
@ -256,7 +256,7 @@ export default function ShadcnShowcasePage() {
<ButtonGroup>
<Button variant="outline">Button Group</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<Button size="icon" variant="outline">
<ChevronUpIcon />
</Button>

View File

@ -100,7 +100,7 @@ export const UpdateCommitButtonGroup = ({
{/* Menú de acciones adicionales */}
{hasSecondaryActions && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<Button className="px-2" disabled={computedDisabled} size="sm" variant="ghost">
<MoreHorizontalIcon className="h-4 w-4" />
<span className="sr-only">Más acciones</span>

View File

@ -343,7 +343,7 @@ export function useIssuedInvoicesGridColumns(
{/* Descargar en PDF */}
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
className={"size-8"}
disabled={isPDFLoading || !isCompleted}
@ -369,7 +369,7 @@ export function useIssuedInvoicesGridColumns(
{/** biome-ignore lint/suspicious/noSelfCompare: <Desactivado por ahora> */}
{false !== false && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<Button
aria-label={t("common.more_actions")}
className="cursor-pointer text-muted-foreground hover:text-primary"

View File

@ -21,7 +21,7 @@ export const VerifactuStatusBadge = ({ status, className }: VerifactuStatusBadge
return (
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Badge
className={cn(
getVerifactuRecordStatusColor(normalizedStatus),

View File

@ -725,7 +725,7 @@ export const CreateCustomerInvoiceEditForm = ({
<div className="space-y-2">
<Label>Fecha de Emisión</Label>
<Popover>
<PopoverTrigger asChild>
<PopoverTrigger>
<Button className="w-full justify-start text-left font-normal" variant="outline">
<CalendarIcon className="mr-2 h-4 w-4" />
{format(invoiceDate, "PPP", { locale: es })}
@ -745,7 +745,7 @@ export const CreateCustomerInvoiceEditForm = ({
<div className="space-y-2">
<Label>Fecha de Operación</Label>
<Popover>
<PopoverTrigger asChild>
<PopoverTrigger>
<Button className="w-full justify-start text-left font-normal" variant="outline">
<CalendarIcon className="mr-2 h-4 w-4" />
{format(operationDate, "PPP", { locale: es })}

View File

@ -101,7 +101,7 @@ export function useProformasGridColumns(
{isIssued && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
asChild
className="size-6 text-foreground hover:text-primary"
@ -240,7 +240,7 @@ export function useProformasGridColumns(
{!isIssued && actionHandlers.onEditClick && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
className="size-8 cursor-pointer"
onClick={() => actionHandlers.onEditClick?.(proforma)}
@ -260,7 +260,7 @@ export function useProformasGridColumns(
{!isIssued && availableTransitions.length && actionHandlers.onChangeStatusClick && (
<TooltipProvider key={availableTransitions[0]}>
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
className="size-8 cursor-pointer"
onClick={() =>
@ -284,7 +284,7 @@ export function useProformasGridColumns(
{!isIssued && isApproved && actionHandlers.onIssueClick && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
className="size-8 cursor-pointer"
onClick={() => actionHandlers.onIssueClick?.(proforma)}
@ -303,7 +303,7 @@ export function useProformasGridColumns(
{!isIssued && actionHandlers.onDeleteClick && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
className="size-8 text-destructive hover:text-destructive cursor-pointer"
onClick={(e) => {

View File

@ -21,7 +21,7 @@ export const ProformaStatusBadge = ({ status, className }: ProformaStatusBadgePr
return (
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Badge
className={cn(getProformaStatusColor(normalizedStatus), "font-semibold", className)}
variant={getProformaStatusButtonVariant(normalizedStatus)}

View File

@ -73,7 +73,7 @@ export const CustomerSummaryPanel = ({
return (
<RightPanel
className={cn("bg-transparent", className)}
className={cn("bg-transparent shadow", className)}
headerActions={
<>
{customer ? (

View File

@ -164,7 +164,7 @@ export function useCustomersGridColumns(
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<Button aria-label={t("pages.list.actions.more")} size="icon" variant="ghost">
<MoreHorizontalIcon className="size-4" />
</Button>

View File

@ -30,7 +30,7 @@ export const CustomerStatusBadge = ({ status }: { status: string }) => {
return (
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<div className={cn("flex-none rounded-full p-1", statusClass)}>
<div className="size-2 rounded-full bg-current" />
</div>

View File

@ -31,7 +31,7 @@ export const HelpButton = ({
return (
<div className={`flex items-baseline justify-center mr-4 font-medium ${className}`}>
<Dialog>
<DialogTrigger asChild>
<DialogTrigger>
<Button className="inline-flex items-center font-medium group" variant="link">
<span className="underline-offset-4 group-hover:underline">{buttonText}</span>
<HelpCircleIcon className="w-4 h-4 ml-1 text-muted-foreground" />
@ -44,7 +44,7 @@ export const HelpButton = ({
<ScrollArea className="grid gap-4 py-2">
{content}
<DialogFooter>
<DialogClose asChild>
<DialogClose>
<Button type="button">{t("common.close")}</Button>
</DialogClose>
</DialogFooter>

View File

@ -31,7 +31,7 @@ export function DataTableColumnHeader<TData, TValue>({
return (
<div className={cn("flex items-center gap-2 ", className)}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<Button
//className="data-[state=open]:bg-accent -ml-4 h-8 text-xs text-muted-foreground font-semibold text-nowrap cursor-pointer"
className="-ml-3 h-8 data-[state=open]:bg-accent cursor-pointer text-foreground"

View File

@ -1,28 +1,31 @@
import { Column } from "@tanstack/react-table"
import { Check, PlusCircleIcon } from "lucide-react"
import * as React from "react"
import {
Badge, Button, Command,
Badge,
Button,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator, Popover,
CommandSeparator,
Popover,
PopoverContent,
PopoverTrigger, Separator
} from '@repo/shadcn-ui/components'
import { cn } from '@repo/shadcn-ui/lib/utils'
PopoverTrigger,
Separator,
} from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import type { Column } from "@tanstack/react-table";
import { Check, PlusCircleIcon } from "lucide-react";
import type * as React from "react";
interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>
title?: string
column?: Column<TData, TValue>;
title?: string;
options: {
label: string
value: string
icon?: React.ComponentType<{ className?: string }>
}[]
label: string;
value: string;
icon?: React.ComponentType<{ className?: string }>;
}[];
}
export function DataTableFacetedFilter<TData, TValue>({
@ -30,30 +33,24 @@ export function DataTableFacetedFilter<TData, TValue>({
title,
options,
}: DataTableFacetedFilterProps<TData, TValue>) {
const facets = column?.getFacetedUniqueValues()
const selectedValues = new Set(column?.getFilterValue() as string[])
const facets = column?.getFacetedUniqueValues();
const selectedValues = new Set(column?.getFilterValue() as string[]);
return (
<Popover>
<PopoverTrigger asChild>
<Button type="button" variant="outline" size="sm" className="h-8 border-dashed">
<PopoverTrigger>
<Button className="h-8 border-dashed" size="sm" type="button" variant="outline">
<PlusCircleIcon />
{title}
{selectedValues?.size > 0 && (
<>
<Separator orientation="vertical" className="mx-2 h-4" />
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal lg:hidden"
>
<Separator className="mx-2 h-4" orientation="vertical" />
<Badge className="rounded-sm px-1 font-normal lg:hidden" variant="secondary">
{selectedValues.size}
</Badge>
<div className="hidden gap-1 lg:flex">
{selectedValues.size > 2 ? (
<Badge
variant="secondary"
className="rounded-sm px-1 font-normal"
>
<Badge className="rounded-sm px-1 font-normal" variant="secondary">
{selectedValues.size} selected
</Badge>
) : (
@ -61,9 +58,9 @@ export function DataTableFacetedFilter<TData, TValue>({
.filter((option) => selectedValues.has(option.value))
.map((option) => (
<Badge
variant="secondary"
key={option.value}
className="rounded-sm px-1 font-normal"
key={option.value}
variant="secondary"
>
{option.label}
</Badge>
@ -74,27 +71,25 @@ export function DataTableFacetedFilter<TData, TValue>({
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0" align="start">
<PopoverContent align="start" className="w-[200px] p-0">
<Command>
<CommandInput placeholder={title} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
{options.map((option) => {
const isSelected = selectedValues.has(option.value)
const isSelected = selectedValues.has(option.value);
return (
<CommandItem
key={option.value}
onSelect={() => {
if (isSelected) {
selectedValues.delete(option.value)
selectedValues.delete(option.value);
} else {
selectedValues.add(option.value)
selectedValues.add(option.value);
}
const filterValues = Array.from(selectedValues)
column?.setFilterValue(
filterValues.length ? filterValues : undefined
)
const filterValues = Array.from(selectedValues);
column?.setFilterValue(filterValues.length ? filterValues : undefined);
}}
>
<div
@ -107,9 +102,7 @@ export function DataTableFacetedFilter<TData, TValue>({
>
<Check className="text-primary-foreground size-3.5" />
</div>
{option.icon && (
<option.icon className="text-muted-foreground size-4" />
)}
{option.icon && <option.icon className="text-muted-foreground size-4" />}
<span>{option.label}</span>
{facets?.get(option.value) && (
<span className="text-muted-foreground ml-auto flex size-4 items-center justify-center font-mono text-xs">
@ -117,7 +110,7 @@ export function DataTableFacetedFilter<TData, TValue>({
</span>
)}
</CommandItem>
)
);
})}
</CommandGroup>
{selectedValues.size > 0 && (
@ -125,8 +118,8 @@ export function DataTableFacetedFilter<TData, TValue>({
<CommandSeparator />
<CommandGroup>
<CommandItem
onSelect={() => column?.setFilterValue(undefined)}
className="justify-center text-center"
onSelect={() => column?.setFilterValue(undefined)}
>
Clear filters
</CommandItem>
@ -137,5 +130,5 @@ export function DataTableFacetedFilter<TData, TValue>({
</Command>
</PopoverContent>
</Popover>
)
);
}

View File

@ -113,7 +113,7 @@ export function DataTableToolbar<TData>({
{!readOnly && meta?.bulkOps?.moveSelectedUp && (
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
aria-label={t("components.datatable.actions.move_up")}
onClick={handleMoveSelectedUp}
@ -130,7 +130,7 @@ export function DataTableToolbar<TData>({
{!readOnly && meta?.bulkOps?.moveSelectedDown && (
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button
aria-label={t("components.datatable.actions.move_down")}
onClick={handleMoveSelectedDown}
@ -164,7 +164,7 @@ export function DataTableToolbar<TData>({
<Separator className="h-6 mx-1 bg-muted/50" orientation="vertical" />
<Tooltip>
<TooltipTrigger asChild>
<TooltipTrigger>
<Button onClick={handleClearSelection} size="sm" type="button" variant="outline">
<ScanIcon aria-hidden="true" className="size-4 mr-1" />
<span>{t("components.datatable.actions.clear_selection")}</span>

View File

@ -18,7 +18,7 @@ export function DataTableViewOptions<TData>({ table }: { table: Table<TData> })
const { t } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<Button
className="ml-auto hidden h-8 lg:flex gap-2 items"
size="sm"

View File

@ -1,9 +1,5 @@
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/registry/new-york-v4/ui/avatar"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Avatar, AvatarFallback, AvatarImage } from "@/registry/new-york-v4/ui/avatar";
import { Button } from "@/registry/new-york-v4/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
@ -13,26 +9,24 @@ import {
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/registry/new-york-v4/ui/dropdown-menu"
} from "@/registry/new-york-v4/ui/dropdown-menu";
export function UserNav() {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="button" variant="ghost" className="relative h-8 w-8 rounded-full">
<DropdownMenuTrigger>
<Button className="relative h-8 w-8 rounded-full" type="button" variant="ghost">
<Avatar className="h-9 w-9">
<AvatarImage src="/avatars/03.png" alt="@shadcn" />
<AvatarImage alt="@shadcn" src="/avatars/03.png" />
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuContent align="end" className="w-56" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm leading-none font-medium">shadcn</p>
<p className="text-muted-foreground text-xs leading-none">
m@example.com
</p>
<p className="text-muted-foreground text-xs leading-none">m@example.com</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
@ -58,5 +52,5 @@ export function UserNav() {
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
);
}

View File

@ -177,7 +177,7 @@ export function DatePickerInputComp<TFormValues extends FieldValues = FieldValue
triggerButton={
disabled || readOnly ? null : (
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild>
<PopoverTrigger>
<Button
aria-controls={popoverId}
aria-expanded={open}

View File

@ -151,7 +151,7 @@ export const MultiSelectField = <T extends FieldValues>({
<FormControl>
<Popover modal={modalPopover} onOpenChange={setIsPopoverOpen} open={isPopoverOpen}>
<PopoverTrigger asChild>
<PopoverTrigger>
<Button
aria-expanded={isPopoverOpen}
aria-invalid={fieldState.invalid}

View File

@ -1,23 +1,12 @@
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@repo/shadcn-ui/components";
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader } from "@repo/shadcn-ui/components";
import {
CameraIcon,
CircleIcon,
FileBoxIcon,
HomeIcon,
LogInIcon,
Package2Icon,
PackageIcon,
SettingsIcon,
FileCheckIcon,
FileTextIcon,
GalleryVerticalEndIcon,
TextCursorIcon,
UserPlusIcon,
UsersIcon,
} from "lucide-react";
import type * as React from "react";
@ -25,48 +14,66 @@ import type * as React from "react";
import { NavMain } from "./nav-main.tsx";
import { NavSecondary } from "./nav-secondary.tsx";
import { NavUser } from "./nav-user.tsx";
import { TeamSwitcher } from "./team-switcher.tsx";
const data = {
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEndIcon,
plan: "Enterprise",
},
],
user: {
name: "Toby Belhome",
email: "m@example.com",
avatar: "https://www.tobybelhome.com/toby-belhome.png",
},
navMain: [
{
title: "Dashboard",
url: "/dashboard",
/*{
title: "Inicio",
url: "/",
icon: HomeIcon,
},
isActive: true,
},*/
{
title: "Users",
url: "/dashboard/users",
title: "Clientes",
icon: UsersIcon,
isActive: true,
items: [
{
title: "Listado de clientes",
url: "/customers",
},
/*{
title: "Añadir un cliente",
url: "/customers/create",
},*/
],
},
{
title: "Settings",
url: "/dashboard/settings",
icon: SettingsIcon,
title: "Facturas proforma",
icon: FileTextIcon,
items: [
{
title: "Listado de proformas",
url: "/proformas",
},
/*{
title: "Enviar a Veri*Factu",
url: "#",
},*/
],
},
{
title: "Login",
url: "/login",
icon: LogInIcon,
},
{
title: "Register",
url: "/register",
icon: UserPlusIcon,
},
{
title: "404 Page",
url: "/404-page",
icon: PackageIcon,
},
{
title: "500 Page",
url: "/500-page",
icon: Package2Icon,
title: "Facturas de cliente",
icon: FileCheckIcon,
items: [
{
title: "Listado de facturas",
url: "/customer-invoices",
},
],
},
],
navClouds: [
@ -118,19 +125,14 @@ const data = {
},
],
navSecondary: [
{
title: "Get Pro",
url: "https://shadcnuikit.com/pricing",
icon: CircleIcon,
},
{
title: "Shadcn UI Kit",
url: "https://shadcnuikit.com/",
url: "http://192.168.0.104:5173/shadcnui",
icon: CircleIcon,
},
{
title: "Bundui Component",
url: "https://bundui.io",
title: "TailwindCSS v4",
url: "http://192.168.0.104:5173/tailwindcss4",
icon: CircleIcon,
},
],
@ -139,21 +141,8 @@ const data = {
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton asChild className="data-[slot=sidebar-menu-button]:!p-1.5">
<a href="#">
<img
alt="shadcn ui kit svg logo"
className="size-6 rounded-sm group-data-[collapsible=icon]:size-5"
src="https://shadcnuikit.com/logo.png"
/>
<span className="text-base font-medium">Shadcn UI Kit</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
<SidebarHeader className="mb-3">
<TeamSwitcher teams={data.teams} />
</SidebarHeader>
<SidebarContent>
<NavMain items={data.navMain} />

View File

@ -19,44 +19,10 @@ import {
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
ColumnDef,
ColumnFiltersState,
Row,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
CheckCircle2Icon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronsLeftIcon,
ChevronsRightIcon,
ColumnsIcon,
GripVerticalIcon,
LoaderIcon,
MoreVerticalIcon,
PlusIcon,
TrendingUpIcon,
} from "lucide-react";
import * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { toast } from "sonner";
import { z } from "zod/v4";
import { Badge } from "@repo/shadcn-ui/components/badge";
import { Button } from "@repo/shadcn-ui/components/button";
import {
ChartConfig,
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
@ -100,6 +66,39 @@ import {
} from "@repo/shadcn-ui/components/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@repo/shadcn-ui/components/tabs";
import { useIsMobile } from "@repo/shadcn-ui/hooks";
import {
type ColumnDef,
type ColumnFiltersState,
type Row,
type SortingState,
type VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
CheckCircle2Icon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronsLeftIcon,
ChevronsRightIcon,
ColumnsIcon,
GripVerticalIcon,
LoaderIcon,
MoreVerticalIcon,
PlusIcon,
TrendingUpIcon,
} from "lucide-react";
import * as React from "react";
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
import { toast } from "sonner";
import { z } from "zod/v4";
export const schema = z.object({
id: z.number(),
@ -121,12 +120,12 @@ function DragHandle({ id }: { id: number }) {
<Button
{...attributes}
{...listeners}
variant='ghost'
size='icon'
className='size-7 text-muted-foreground hover:bg-transparent'
className="size-7 text-muted-foreground hover:bg-transparent"
size="icon"
variant="ghost"
>
<GripVerticalIcon className='size-3 text-muted-foreground' />
<span className='sr-only'>Drag to reorder</span>
<GripVerticalIcon className="size-3 text-muted-foreground" />
<span className="sr-only">Drag to reorder</span>
</Button>
);
}
@ -140,23 +139,23 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
{
id: "select",
header: ({ table }) => (
<div className='flex items-center justify-center'>
<div className="flex items-center justify-center">
<Checkbox
aria-label="Select all"
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label='Select all'
/>
</div>
),
cell: ({ row }) => (
<div className='flex items-center justify-center'>
<div className="flex items-center justify-center">
<Checkbox
aria-label="Select row"
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label='Select row'
/>
</div>
),
@ -175,8 +174,8 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
accessorKey: "type",
header: "Section Type",
cell: ({ row }) => (
<div className='w-32'>
<Badge variant='outline' className='px-1.5 text-muted-foreground'>
<div className="w-32">
<Badge className="px-1.5 text-muted-foreground" variant="outline">
{row.original.type}
</Badge>
</div>
@ -186,9 +185,9 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
accessorKey: "status",
header: "Status",
cell: ({ row }) => (
<Badge variant='outline' className='flex gap-1 px-1.5 text-muted-foreground [&_svg]:size-3'>
<Badge className="flex gap-1 px-1.5 text-muted-foreground [&_svg]:size-3" variant="outline">
{row.original.status === "Done" ? (
<CheckCircle2Icon className='text-green-500 dark:text-green-400' />
<CheckCircle2Icon className="text-green-500 dark:text-green-400" />
) : (
<LoaderIcon />
)}
@ -198,7 +197,7 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
},
{
accessorKey: "target",
header: () => <div className='w-full text-right'>Target</div>,
header: () => <div className="w-full text-right">Target</div>,
cell: ({ row }) => (
<form
onSubmit={(e) => {
@ -210,11 +209,11 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
});
}}
>
<Label htmlFor={`${row.original.id}-target`} className='sr-only'>
<Label className="sr-only" htmlFor={`${row.original.id}-target`}>
Target
</Label>
<Input
className='h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background'
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background"
defaultValue={row.original.target}
id={`${row.original.id}-target`}
/>
@ -223,7 +222,7 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
},
{
accessorKey: "limit",
header: () => <div className='w-full text-right'>Limit</div>,
header: () => <div className="w-full text-right">Limit</div>,
cell: ({ row }) => (
<form
onSubmit={(e) => {
@ -235,11 +234,11 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
});
}}
>
<Label htmlFor={`${row.original.id}-limit`} className='sr-only'>
<Label className="sr-only" htmlFor={`${row.original.id}-limit`}>
Limit
</Label>
<Input
className='h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background'
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background"
defaultValue={row.original.limit}
id={`${row.original.id}-limit`}
/>
@ -258,16 +257,16 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
return (
<>
<Label htmlFor={`${row.original.id}-reviewer`} className='sr-only'>
<Label className="sr-only" htmlFor={`${row.original.id}-reviewer`}>
Reviewer
</Label>
<Select>
<SelectTrigger className='h-8 w-40' id={`${row.original.id}-reviewer`}>
<SelectValue placeholder='Assign reviewer' />
<SelectTrigger className="h-8 w-40" id={`${row.original.id}-reviewer`}>
<SelectValue placeholder="Assign reviewer" />
</SelectTrigger>
<SelectContent align='end'>
<SelectItem value='Eddie Lake'>Eddie Lake</SelectItem>
<SelectItem value='Jamik Tashpulatov'>Jamik Tashpulatov</SelectItem>
<SelectContent align="end">
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
<SelectItem value="Jamik Tashpulatov">Jamik Tashpulatov</SelectItem>
</SelectContent>
</Select>
</>
@ -278,17 +277,17 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
id: "actions",
cell: () => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<Button
variant='ghost'
className='flex size-8 text-muted-foreground data-[state=open]:bg-muted'
size='icon'
className="flex size-8 text-muted-foreground data-[state=open]:bg-muted"
size="icon"
variant="ghost"
>
<MoreVerticalIcon />
<span className='sr-only'>Open menu</span>
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end' className='w-32'>
<DropdownMenuContent align="end" className="w-32">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Make a copy</DropdownMenuItem>
<DropdownMenuItem>Favorite</DropdownMenuItem>
@ -307,10 +306,10 @@ function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
return (
<TableRow
data-state={row.getIsSelected() && "selected"}
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
data-dragging={isDragging}
data-state={row.getIsSelected() && "selected"}
ref={setNodeRef}
className='relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80'
style={{
transform: CSS.Transform.toString(transform),
transition: transition,
@ -325,11 +324,7 @@ function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
);
}
export function DataTable({
data: initialData,
}: {
data: z.infer<typeof schema>[];
}) {
export function DataTable({ data: initialData }: { data: z.infer<typeof schema>[] }) {
const [data, setData] = React.useState(() => initialData);
const [rowSelection, setRowSelection] = React.useState({});
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
@ -385,64 +380,64 @@ export function DataTable({
}
return (
<Tabs defaultValue='outline' className='flex w-full flex-col justify-start gap-6'>
<div className='flex items-center justify-between px-4 lg:px-6'>
<Label htmlFor='view-selector' className='sr-only'>
<Tabs className="flex w-full flex-col justify-start gap-6" defaultValue="outline">
<div className="flex items-center justify-between px-4 lg:px-6">
<Label className="sr-only" htmlFor="view-selector">
View
</Label>
<Select defaultValue='outline'>
<SelectTrigger className='@4xl/main:hidden flex w-fit' id='view-selector'>
<SelectValue placeholder='Select a view' />
<Select defaultValue="outline">
<SelectTrigger className="@4xl/main:hidden flex w-fit" id="view-selector">
<SelectValue placeholder="Select a view" />
</SelectTrigger>
<SelectContent>
<SelectItem value='outline'>Outline</SelectItem>
<SelectItem value='past-performance'>Past Performance</SelectItem>
<SelectItem value='key-personnel'>Key Personnel</SelectItem>
<SelectItem value='focus-documents'>Focus Documents</SelectItem>
<SelectItem value="outline">Outline</SelectItem>
<SelectItem value="past-performance">Past Performance</SelectItem>
<SelectItem value="key-personnel">Key Personnel</SelectItem>
<SelectItem value="focus-documents">Focus Documents</SelectItem>
</SelectContent>
</Select>
<TabsList className='@4xl/main:flex hidden'>
<TabsTrigger value='outline'>Outline</TabsTrigger>
<TabsTrigger value='past-performance' className='gap-1'>
<TabsList className="@4xl/main:flex hidden">
<TabsTrigger value="outline">Outline</TabsTrigger>
<TabsTrigger className="gap-1" value="past-performance">
Past Performance{" "}
<Badge
variant='secondary'
className='flex size-5 items-center justify-center rounded-full bg-muted-foreground/30'
className="flex size-5 items-center justify-center rounded-full bg-muted-foreground/30"
variant="secondary"
>
3
</Badge>
</TabsTrigger>
<TabsTrigger value='key-personnel' className='gap-1'>
<TabsTrigger className="gap-1" value="key-personnel">
Key Personnel{" "}
<Badge
variant='secondary'
className='flex size-5 items-center justify-center rounded-full bg-muted-foreground/30'
className="flex size-5 items-center justify-center rounded-full bg-muted-foreground/30"
variant="secondary"
>
2
</Badge>
</TabsTrigger>
<TabsTrigger value='focus-documents'>Focus Documents</TabsTrigger>
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
</TabsList>
<div className='flex items-center gap-2'>
<div className="flex items-center gap-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant='outline' size='sm'>
<DropdownMenuTrigger>
<Button size="sm" variant="outline">
<ColumnsIcon />
<span className='hidden lg:inline'>Customize Columns</span>
<span className='lg:hidden'>Columns</span>
<span className="hidden lg:inline">Customize Columns</span>
<span className="lg:hidden">Columns</span>
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end' className='w-56'>
<DropdownMenuContent align="end" className="w-56">
{table
.getAllColumns()
.filter((column) => typeof column.accessorFn !== "undefined" && column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className='capitalize'
checked={column.getIsVisible()}
className="capitalize"
key={column.id}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
@ -451,31 +446,31 @@ export function DataTable({
})}
</DropdownMenuContent>
</DropdownMenu>
<Button variant='outline' size='sm'>
<Button size="sm" variant="outline">
<PlusIcon />
<span className='hidden lg:inline'>Add Section</span>
<span className="hidden lg:inline">Add Section</span>
</Button>
</div>
</div>
<TabsContent
value='outline'
className='relative flex flex-col gap-4 overflow-auto px-4 lg:px-6'
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
value="outline"
>
<div className='overflow-hidden rounded-lg border'>
<div className="overflow-hidden rounded-lg border">
<DndContext
collisionDetection={closestCenter}
id={sortableId}
modifiers={[restrictToVerticalAxis]}
onDragEnd={handleDragEnd}
sensors={sensors}
id={sortableId}
>
<Table>
<TableHeader className='sticky top-0 z-10 bg-muted'>
<TableHeader className="sticky top-0 z-10 bg-muted">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} colSpan={header.colSpan}>
<TableHead colSpan={header.colSpan} key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
@ -485,7 +480,7 @@ export function DataTable({
</TableRow>
))}
</TableHeader>
<TableBody className='**:data-[slot=table-cell]:first:w-8'>
<TableBody className="**:data-[slot=table-cell]:first:w-8">
{table.getRowModel().rows?.length ? (
<SortableContext items={dataIds} strategy={verticalListSortingStrategy}>
{table.getRowModel().rows.map((row) => (
@ -494,7 +489,7 @@ export function DataTable({
</SortableContext>
) : (
<TableRow>
<TableCell colSpan={columns.length} className='h-24 text-center'>
<TableCell className="h-24 text-center" colSpan={columns.length}>
No results.
</TableCell>
</TableRow>
@ -503,26 +498,26 @@ export function DataTable({
</Table>
</DndContext>
</div>
<div className='flex items-center justify-between px-4'>
<div className='hidden flex-1 text-sm text-muted-foreground lg:flex'>
<div className="flex items-center justify-between px-4">
<div className="hidden flex-1 text-sm text-muted-foreground lg:flex">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className='flex w-full items-center gap-8 lg:w-fit'>
<div className='hidden items-center gap-2 lg:flex'>
<Label htmlFor='rows-per-page' className='text-sm font-medium'>
<div className="flex w-full items-center gap-8 lg:w-fit">
<div className="hidden items-center gap-2 lg:flex">
<Label className="text-sm font-medium" htmlFor="rows-per-page">
Rows per page
</Label>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
value={`${table.getState().pagination.pageSize}`}
>
<SelectTrigger className='w-20' id='rows-per-page'>
<SelectTrigger className="w-20" id="rows-per-page">
<SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger>
<SelectContent side='top'>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
@ -531,61 +526,61 @@ export function DataTable({
</SelectContent>
</Select>
</div>
<div className='flex w-fit items-center justify-center text-sm font-medium'>
<div className="flex w-fit items-center justify-center text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</div>
<div className='ml-auto flex items-center gap-2 lg:ml-0'>
<div className="ml-auto flex items-center gap-2 lg:ml-0">
<Button
variant='outline'
className='hidden size-8 p-0 lg:flex'
onClick={() => table.setPageIndex(0)}
className="hidden size-8 p-0 lg:flex"
disabled={!table.getCanPreviousPage()}
onClick={() => table.setPageIndex(0)}
variant="outline"
>
<span className='sr-only'>Go to first page</span>
<span className="sr-only">Go to first page</span>
<ChevronsLeftIcon />
</Button>
<Button
variant='outline'
className='size-8'
size='icon'
onClick={() => table.previousPage()}
className="size-8"
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
size="icon"
variant="outline"
>
<span className='sr-only'>Go to previous page</span>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon />
</Button>
<Button
variant='outline'
className='size-8'
size='icon'
onClick={() => table.nextPage()}
className="size-8"
disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
size="icon"
variant="outline"
>
<span className='sr-only'>Go to next page</span>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon />
</Button>
<Button
variant='outline'
className='hidden size-8 lg:flex'
size='icon'
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
className="hidden size-8 lg:flex"
disabled={!table.getCanNextPage()}
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
size="icon"
variant="outline"
>
<span className='sr-only'>Go to last page</span>
<span className="sr-only">Go to last page</span>
<ChevronsRightIcon />
</Button>
</div>
</div>
</div>
</TabsContent>
<TabsContent value='past-performance' className='flex flex-col px-4 lg:px-6'>
<div className='aspect-video w-full flex-1 rounded-lg border border-dashed' />
<TabsContent className="flex flex-col px-4 lg:px-6" value="past-performance">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
</TabsContent>
<TabsContent value='key-personnel' className='flex flex-col px-4 lg:px-6'>
<div className='aspect-video w-full flex-1 rounded-lg border border-dashed' />
<TabsContent className="flex flex-col px-4 lg:px-6" value="key-personnel">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
</TabsContent>
<TabsContent value='focus-documents' className='flex flex-col px-4 lg:px-6'>
<div className='aspect-video w-full flex-1 rounded-lg border border-dashed' />
<TabsContent className="flex flex-col px-4 lg:px-6" value="focus-documents">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
</TabsContent>
</Tabs>
);
@ -616,17 +611,17 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant='link' className='w-fit px-0 text-left text-foreground'>
<SheetTrigger>
<Button className="w-fit px-0 text-left text-foreground" variant="link">
{item.header}
</Button>
</SheetTrigger>
<SheetContent side='right' className='flex flex-col'>
<SheetHeader className='gap-1'>
<SheetContent className="flex flex-col" side="right">
<SheetHeader className="gap-1">
<SheetTitle>{item.header}</SheetTitle>
<SheetDescription>Showing total visitors for the last 6 months</SheetDescription>
</SheetHeader>
<div className='flex flex-1 flex-col gap-4 overflow-y-auto py-4 text-sm'>
<div className="flex flex-1 flex-col gap-4 overflow-y-auto py-4 text-sm">
{!isMobile && (
<>
<ChartContainer config={chartConfig}>
@ -640,38 +635,38 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
>
<CartesianGrid vertical={false} />
<XAxis
dataKey='month'
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={(value) => value.slice(0, 3)}
dataKey="month"
hide
tickFormatter={(value) => value.slice(0, 3)}
tickLine={false}
tickMargin={8}
/>
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator='dot' />} />
<ChartTooltip content={<ChartTooltipContent indicator="dot" />} cursor={false} />
<Area
dataKey='mobile'
type='natural'
fill='var(--color-mobile)'
dataKey="mobile"
fill="var(--color-mobile)"
fillOpacity={0.6}
stroke='var(--color-mobile)'
stackId='a'
stackId="a"
stroke="var(--color-mobile)"
type="natural"
/>
<Area
dataKey='desktop'
type='natural'
fill='var(--color-desktop)'
dataKey="desktop"
fill="var(--color-desktop)"
fillOpacity={0.4}
stroke='var(--color-desktop)'
stackId='a'
stackId="a"
stroke="var(--color-desktop)"
type="natural"
/>
</AreaChart>
</ChartContainer>
<Separator />
<div className='grid gap-2'>
<div className='flex gap-2 font-medium leading-none'>
Trending up by 5.2% this month <TrendingUpIcon className='size-4' />
<div className="grid gap-2">
<div className="flex gap-2 font-medium leading-none">
Trending up by 5.2% this month <TrendingUpIcon className="size-4" />
</div>
<div className='text-muted-foreground'>
<div className="text-muted-foreground">
Showing total visitors for the last 6 months. This is just some random text to
test the layout. It spans multiple lines and should wrap around.
</div>
@ -679,73 +674,73 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
<Separator />
</>
)}
<form className='flex flex-col gap-4'>
<div className='flex flex-col gap-3'>
<Label htmlFor='header'>Header</Label>
<Input id='header' defaultValue={item.header} />
<form className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="header">Header</Label>
<Input defaultValue={item.header} id="header" />
</div>
<div className='grid grid-cols-2 gap-4'>
<div className='flex flex-col gap-3'>
<Label htmlFor='type'>Type</Label>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="type">Type</Label>
<Select defaultValue={item.type}>
<SelectTrigger id='type' className='w-full'>
<SelectValue placeholder='Select a type' />
<SelectTrigger className="w-full" id="type">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
<SelectItem value='Table of Contents'>Table of Contents</SelectItem>
<SelectItem value='Executive Summary'>Executive Summary</SelectItem>
<SelectItem value='Technical Approach'>Technical Approach</SelectItem>
<SelectItem value='Design'>Design</SelectItem>
<SelectItem value='Capabilities'>Capabilities</SelectItem>
<SelectItem value='Focus Documents'>Focus Documents</SelectItem>
<SelectItem value='Narrative'>Narrative</SelectItem>
<SelectItem value='Cover Page'>Cover Page</SelectItem>
<SelectItem value="Table of Contents">Table of Contents</SelectItem>
<SelectItem value="Executive Summary">Executive Summary</SelectItem>
<SelectItem value="Technical Approach">Technical Approach</SelectItem>
<SelectItem value="Design">Design</SelectItem>
<SelectItem value="Capabilities">Capabilities</SelectItem>
<SelectItem value="Focus Documents">Focus Documents</SelectItem>
<SelectItem value="Narrative">Narrative</SelectItem>
<SelectItem value="Cover Page">Cover Page</SelectItem>
</SelectContent>
</Select>
</div>
<div className='flex flex-col gap-3'>
<Label htmlFor='status'>Status</Label>
<div className="flex flex-col gap-3">
<Label htmlFor="status">Status</Label>
<Select defaultValue={item.status}>
<SelectTrigger id='status' className='w-full'>
<SelectValue placeholder='Select a status' />
<SelectTrigger className="w-full" id="status">
<SelectValue placeholder="Select a status" />
</SelectTrigger>
<SelectContent>
<SelectItem value='Done'>Done</SelectItem>
<SelectItem value='In Progress'>In Progress</SelectItem>
<SelectItem value='Not Started'>Not Started</SelectItem>
<SelectItem value="Done">Done</SelectItem>
<SelectItem value="In Progress">In Progress</SelectItem>
<SelectItem value="Not Started">Not Started</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className='grid grid-cols-2 gap-4'>
<div className='flex flex-col gap-3'>
<Label htmlFor='target'>Target</Label>
<Input id='target' defaultValue={item.target} />
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="target">Target</Label>
<Input defaultValue={item.target} id="target" />
</div>
<div className='flex flex-col gap-3'>
<Label htmlFor='limit'>Limit</Label>
<Input id='limit' defaultValue={item.limit} />
<div className="flex flex-col gap-3">
<Label htmlFor="limit">Limit</Label>
<Input defaultValue={item.limit} id="limit" />
</div>
</div>
<div className='flex flex-col gap-3'>
<Label htmlFor='reviewer'>Reviewer</Label>
<div className="flex flex-col gap-3">
<Label htmlFor="reviewer">Reviewer</Label>
<Select defaultValue={item.reviewer}>
<SelectTrigger id='reviewer' className='w-full'>
<SelectValue placeholder='Select a reviewer' />
<SelectTrigger className="w-full" id="reviewer">
<SelectValue placeholder="Select a reviewer" />
</SelectTrigger>
<SelectContent>
<SelectItem value='Eddie Lake'>Eddie Lake</SelectItem>
<SelectItem value='Jamik Tashpulatov'>Jamik Tashpulatov</SelectItem>
<SelectItem value='Emily Whalen'>Emily Whalen</SelectItem>
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
<SelectItem value="Jamik Tashpulatov">Jamik Tashpulatov</SelectItem>
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
</SelectContent>
</Select>
</div>
</form>
</div>
<SheetFooter className='mt-auto flex gap-2 sm:flex-col sm:space-x-0'>
<Button className='w-full'>Submit</Button>
<SheetClose asChild>
<Button variant='outline' className='w-full'>
<SheetFooter className="mt-auto flex gap-2 sm:flex-col sm:space-x-0">
<Button className="w-full">Submit</Button>
<SheetClose>
<Button className="w-full" variant="outline">
Done
</Button>
</SheetClose>

View File

@ -1,7 +1,5 @@
"use client";
import { FolderIcon, type LucideIcon, MoreHorizontalIcon, ShareIcon } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@ -17,6 +15,7 @@ import {
SidebarMenuItem,
useSidebar,
} from "@repo/shadcn-ui/components/sidebar";
import { FolderIcon, type LucideIcon, MoreHorizontalIcon, ShareIcon } from "lucide-react";
export function NavDocuments({
items,
@ -30,28 +29,28 @@ export function NavDocuments({
const { isMobile } = useSidebar();
return (
<SidebarGroup className='group-data-[collapsible=icon]:hidden'>
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Documents</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<SidebarMenuButton>
<a href={item.url}>
<item.icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction showOnHover className='rounded-sm data-[state=open]:bg-accent'>
<DropdownMenuTrigger>
<SidebarMenuAction className="rounded-sm data-[state=open]:bg-accent" showOnHover>
<MoreHorizontalIcon />
<span className='sr-only'>More</span>
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className='w-24 rounded-lg'
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
className="w-24 rounded-lg"
side={isMobile ? "bottom" : "right"}
>
<DropdownMenuItem>
<FolderIcon />
@ -66,8 +65,8 @@ export function NavDocuments({
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className='text-sidebar-foreground/70'>
<MoreHorizontalIcon className='text-sidebar-foreground/70' />
<SidebarMenuButton className="text-sidebar-foreground/70">
<MoreHorizontalIcon className="text-sidebar-foreground/70" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>

View File

@ -46,7 +46,7 @@ export function NavMain({ items }: { items: NavMainItem[] }) {
{items.map((item) => (
<Collapsible asChild className="group/collapsible" defaultOpen={true} key={item.title}>
<SidebarMenuItem className="mb-6">
<CollapsibleTrigger asChild>
<CollapsibleTrigger>
<SidebarMenuButton tooltip={item.title}>
{item.icon && <item.icon />}
<span className="font-semibold">{item.title}</span>
@ -57,7 +57,7 @@ export function NavMain({ items }: { items: NavMainItem[] }) {
<SidebarMenuSub>
{item.items?.map((subItem) => (
<SidebarMenuSubItem key={subItem.title}>
<SidebarMenuSubButton asChild>
<SidebarMenuSubButton>
<a href={subItem.url}>
<span>{subItem.title}</span>
</a>

View File

@ -1,7 +1,5 @@
"use client";
import { Folder, Forward, type LucideIcon, MoreHorizontal, Trash2 } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@ -18,6 +16,7 @@ import {
SidebarMenuItem,
useSidebar,
} from "@repo/shadcn-ui/components/sidebar";
import { Folder, Forward, type LucideIcon, MoreHorizontal, Trash2 } from "lucide-react";
export function NavProjects({
projects,
@ -31,40 +30,40 @@ export function NavProjects({
const { isMobile } = useSidebar();
return (
<SidebarGroup className='group-data-[collapsible=icon]:hidden'>
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Projects</SidebarGroupLabel>
<SidebarMenu>
{projects.map((item) => (
<SidebarMenuItem key={item.name}>
<SidebarMenuButton asChild>
<SidebarMenuButton>
<a href={item.url}>
<item.icon />
<span>{item.name}</span>
</a>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<SidebarMenuAction showOnHover>
<MoreHorizontal />
<span className='sr-only'>More</span>
<span className="sr-only">More</span>
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent
className='w-48 rounded-lg'
side={isMobile ? "bottom" : "right"}
align={isMobile ? "end" : "start"}
className="w-48 rounded-lg"
side={isMobile ? "bottom" : "right"}
>
<DropdownMenuItem>
<Folder className='text-muted-foreground' />
<Folder className="text-muted-foreground" />
<span>View Project</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Forward className='text-muted-foreground' />
<Forward className="text-muted-foreground" />
<span>Share Project</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 className='text-muted-foreground' />
<Trash2 className="text-muted-foreground" />
<span>Delete Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
@ -72,8 +71,8 @@ export function NavProjects({
</SidebarMenuItem>
))}
<SidebarMenuItem>
<SidebarMenuButton className='text-sidebar-foreground/70'>
<MoreHorizontal className='text-sidebar-foreground/70' />
<SidebarMenuButton className="text-sidebar-foreground/70">
<MoreHorizontal className="text-sidebar-foreground/70" />
<span>More</span>
</SidebarMenuButton>
</SidebarMenuItem>

View File

@ -1,6 +1,3 @@
import { LucideIcon } from "lucide-react";
import * as React from "react";
import {
SidebarGroup,
SidebarGroupContent,
@ -8,6 +5,8 @@ import {
SidebarMenuButton,
SidebarMenuItem,
} from "@repo/shadcn-ui/components/sidebar";
import type { LucideIcon } from "lucide-react";
import type * as React from "react";
export function NavSecondary({
items,
@ -25,7 +24,7 @@ export function NavSecondary({
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<SidebarMenuButton>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>

View File

@ -1,13 +1,5 @@
"use client";
import {
BellIcon,
CreditCardIcon,
LogOutIcon,
MoreVerticalIcon,
UserCircleIcon,
} from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@repo/shadcn-ui/components/avatar";
import {
DropdownMenu,
@ -24,6 +16,13 @@ import {
SidebarMenuItem,
useSidebar,
} from "@repo/shadcn-ui/components/sidebar";
import {
BellIcon,
CreditCardIcon,
LogOutIcon,
MoreVerticalIcon,
UserCircleIcon,
} from "lucide-react";
export function NavUser({
user,
@ -40,37 +39,37 @@ export function NavUser({
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<SidebarMenuButton
size='lg'
className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground'
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
size="lg"
>
<Avatar className='size-8 rounded-lg grayscale'>
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className='rounded-lg'>CN</AvatarFallback>
<Avatar className="size-8 rounded-lg grayscale">
<AvatarImage alt={user.name} src={user.avatar} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className='grid flex-1 text-left text-sm leading-tight'>
<span className='truncate font-medium'>{user.name}</span>
<span className='truncate text-xs text-muted-foreground'>{user.email}</span>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs text-muted-foreground">{user.email}</span>
</div>
<MoreVerticalIcon className='ml-auto size-4' />
<MoreVerticalIcon className="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className='w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg'
align="end"
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align='end'
sideOffset={4}
>
<DropdownMenuLabel className='p-0 font-normal'>
<div className='flex items-center gap-2 px-1 py-1.5 text-left text-sm'>
<Avatar className='size-8 rounded-lg'>
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className='rounded-lg'>CN</AvatarFallback>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="size-8 rounded-lg">
<AvatarImage alt={user.name} src={user.avatar} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className='grid flex-1 text-left text-sm leading-tight'>
<span className='truncate font-medium'>{user.name}</span>
<span className='truncate text-xs text-muted-foreground'>{user.email}</span>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span>
<span className="truncate text-xs text-muted-foreground">{user.email}</span>
</div>
</div>
</DropdownMenuLabel>

View File

@ -1,8 +1,5 @@
"use client";
import { ChevronsUpDown, Plus } from "lucide-react";
import * as React from "react";
import {
DropdownMenu,
DropdownMenuContent,
@ -16,31 +13,33 @@ import {
SidebarMenuItem,
useSidebar,
} from "@repo/shadcn-ui/components";
import { ChevronsUpDown, Plus } from "lucide-react";
import * as React from "react";
export function TeamSwitcher({
teams,
}: {
teams: {
name: string
logo: React.ElementType
plan: string
}[]
name: string;
logo: React.ElementType;
plan: string;
}[];
}) {
const { isMobile } = useSidebar()
const [activeTeam, setActiveTeam] = React.useState(teams[0])
const { isMobile } = useSidebar();
const [activeTeam, setActiveTeam] = React.useState(teams[0]);
if (!activeTeam) {
return null
return null;
}
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<DropdownMenuTrigger>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
size="lg"
>
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<activeTeam.logo className="size-4" />
@ -53,19 +52,17 @@ export function TeamSwitcher({
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
align="start"
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
sideOffset={4}
>
<DropdownMenuLabel className="text-muted-foreground text-xs">
Teams
</DropdownMenuLabel>
<DropdownMenuLabel className="text-muted-foreground text-xs">Teams</DropdownMenuLabel>
{teams.map((team, index) => (
<DropdownMenuItem
className="gap-2 p-2"
key={team.name}
onClick={() => setActiveTeam(team)}
className="gap-2 p-2"
>
<div className="flex size-6 items-center justify-center rounded-md border">
<team.logo className="size-3.5 shrink-0" />
@ -85,5 +82,5 @@ export function TeamSwitcher({
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
);
}

View File

@ -1,7 +1,3 @@
import { type VariantProps, cva } from "class-variance-authority";
import { CheckIcon, ChevronDown, WandSparkles, XCircleIcon } from "lucide-react";
import * as React from "react";
import {
Badge,
Button,
@ -18,6 +14,10 @@ import {
Separator,
} from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { type VariantProps, cva } from "class-variance-authority";
import { CheckIcon, ChevronDown, WandSparkles, XCircleIcon } from "lucide-react";
import * as React from "react";
import { useTranslation } from "../locales/i18n.ts";
/**
@ -65,7 +65,7 @@ export type MultiSelectOptionType = {
*/
export interface MultiSelectProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof multiSelectVariants> {
VariantProps<typeof multiSelectVariants> {
/**
* An array of option objects to be displayed in the multi-select component.
* Each option object has a label, value, and an optional icon.
@ -130,7 +130,6 @@ export interface MultiSelectProps
*/
selectAllVisible?: boolean;
/**
* Filtra los items seleccionados
*/
@ -181,7 +180,6 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
if (autoFilter) applySelectedFilter();
}, [autoFilter, selectedValues, applySelectedFilter]);
const grouped = options.reduce<Record<string, MultiSelectOptionType[]>>((acc, item) => {
if (!acc[item.group || ""]) acc[item.group || ""] = [];
acc[item.group || ""].push(item);
@ -238,33 +236,33 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
};
return (
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen} modal={modalPopover}>
<PopoverTrigger asChild>
<Popover modal={modalPopover} onOpenChange={setIsPopoverOpen} open={isPopoverOpen}>
<PopoverTrigger>
<Button
ref={ref}
{...props}
onClick={handleTogglePopover}
className={cn(
"flex w-full -mt-0.5 px-1 py-0.5 rounded-md border min-h-8 h-auto items-center justify-between bg-background hover:bg-inherit [&_svg]:pointer-events-auto",
className
)}
onClick={handleTogglePopover}
>
{selectedValues.length > 0 ? (
<div className='flex justify-between items-center w-full'>
<div className='flex flex-wrap items-center'>
<div className="flex justify-between items-center w-full">
<div className="flex flex-wrap items-center">
{selectedValues.slice(0, maxCount).map((value) => {
const option = options.find((o) => o.value === value);
const IconComponent = option?.icon;
return (
<Badge
key={value}
className={cn(
isAnimating ? "animate-bounce" : "",
multiSelectVariants({ variant })
)}
key={value}
style={{ animationDuration: `${animation}s` }}
>
{IconComponent && <IconComponent className='h-4 w-4 mr-2' />}
{IconComponent && <IconComponent className="h-4 w-4 mr-2" />}
{option?.label}
{/*<XCircle
className='ml-2 h-4 w-4 cursor-pointer'
@ -287,7 +285,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
>
{`+ ${selectedValues.length - maxCount} more`}
<XCircleIcon
className='ml-2 h-4 w-4 cursor-pointer'
className="ml-2 h-4 w-4 cursor-pointer"
onClick={(event) => {
event.stopPropagation();
clearExtraOptions();
@ -296,33 +294,33 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
</Badge>
)}
</div>
<div className='flex items-center justify-between'>
<Separator orientation='vertical' className='flex min-h-6 h-full' />
<ChevronDown className='h-4 mx-2 cursor-pointer text-muted-foreground' />
<div className="flex items-center justify-between">
<Separator className="flex min-h-6 h-full" orientation="vertical" />
<ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" />
</div>
</div>
) : (
<div className='flex items-center justify-between w-full mx-auto'>
<span className='text-sm text-muted-foreground mx-3'>
<div className="flex items-center justify-between w-full mx-auto">
<span className="text-sm text-muted-foreground mx-3">
{placeholder || t("components.multi_select.select_options")}
</span>
<ChevronDown className='h-4 cursor-pointer text-muted-foreground mx-2' />
<ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" />
</div>
)}
</Button>
</PopoverTrigger>
<PopoverContent
className='w-auto p-0'
align='start'
align="start"
className="w-auto p-0"
onEscapeKeyDown={() => setIsPopoverOpen(false)}
>
<Command>
<CommandInput placeholder={t("common.search")} onKeyDown={handleInputKeyDown} />
<CommandInput onKeyDown={handleInputKeyDown} placeholder={t("common.search")} />
<CommandList>
<CommandEmpty>{t("components.multi_select.no_results")}</CommandEmpty>
{selectAllVisible && (
<CommandItem key='all' onSelect={toggleAll} className='cursor-pointer'>
<CommandItem className="cursor-pointer" key="all" onSelect={toggleAll}>
<div
className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
@ -331,21 +329,21 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
: "opacity-50 [&_svg]:invisible"
)}
>
<CheckIcon className='h-4 w-4' />
<CheckIcon className="h-4 w-4" />
</div>
<span>(Select All)</span>
</CommandItem>
)}
{Object.keys(grouped).map((group) => {
return (
<CommandGroup key={`group-${group || "ungrouped"}`} heading={group}>
<CommandGroup heading={group} key={`group-${group || "ungrouped"}`}>
{grouped[group].map((option) => {
const isSelected = selectedValues.includes(option.value);
return (
<CommandItem
className="cursor-pointer"
key={option.value}
onSelect={() => toggleOption(option.value)}
className='cursor-pointer'
>
<div
className={cn(
@ -360,7 +358,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
/>
</div>
{option.icon && (
<option.icon className='mr-2 h-4 w-4 text-muted-foreground' />
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{option.label}</span>
</CommandItem>
@ -372,21 +370,21 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
<CommandSeparator />
<CommandGroup>
<div className='flex items-center justify-between'>
<div className="flex items-center justify-between">
{selectedValues.length > 0 && (
<>
<CommandItem
className="flex-1 justify-center cursor-pointer"
onSelect={handleClear}
className='flex-1 justify-center cursor-pointer'
>
{t("components.multi_select.clear_selection")}
</CommandItem>
<Separator orientation='vertical' className='flex min-h-6 h-full' />
<Separator className="flex min-h-6 h-full" orientation="vertical" />
</>
)}
<CommandItem
className="flex-1 justify-center cursor-pointer max-w-full"
onSelect={() => setIsPopoverOpen(false)}
className='flex-1 justify-center cursor-pointer max-w-full'
>
{t("components.multi_select.close")}
</CommandItem>