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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -113,7 +113,7 @@ export function DataTableToolbar<TData>({
{!readOnly && meta?.bulkOps?.moveSelectedUp && ( {!readOnly && meta?.bulkOps?.moveSelectedUp && (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger>
<Button <Button
aria-label={t("components.datatable.actions.move_up")} aria-label={t("components.datatable.actions.move_up")}
onClick={handleMoveSelectedUp} onClick={handleMoveSelectedUp}
@ -130,7 +130,7 @@ export function DataTableToolbar<TData>({
{!readOnly && meta?.bulkOps?.moveSelectedDown && ( {!readOnly && meta?.bulkOps?.moveSelectedDown && (
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger>
<Button <Button
aria-label={t("components.datatable.actions.move_down")} aria-label={t("components.datatable.actions.move_down")}
onClick={handleMoveSelectedDown} onClick={handleMoveSelectedDown}
@ -164,7 +164,7 @@ export function DataTableToolbar<TData>({
<Separator className="h-6 mx-1 bg-muted/50" orientation="vertical" /> <Separator className="h-6 mx-1 bg-muted/50" orientation="vertical" />
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger>
<Button onClick={handleClearSelection} size="sm" type="button" variant="outline"> <Button onClick={handleClearSelection} size="sm" type="button" variant="outline">
<ScanIcon aria-hidden="true" className="size-4 mr-1" /> <ScanIcon aria-hidden="true" className="size-4 mr-1" />
<span>{t("components.datatable.actions.clear_selection")}</span> <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(); const { t } = useTranslation();
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger>
<Button <Button
className="ml-auto hidden h-8 lg:flex gap-2 items" className="ml-auto hidden h-8 lg:flex gap-2 items"
size="sm" size="sm"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,5 @@
"use client"; "use client";
import { ChevronsUpDown, Plus } from "lucide-react";
import * as React from "react";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -16,31 +13,33 @@ import {
SidebarMenuItem, SidebarMenuItem,
useSidebar, useSidebar,
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { ChevronsUpDown, Plus } from "lucide-react";
import * as React from "react";
export function TeamSwitcher({ export function TeamSwitcher({
teams, teams,
}: { }: {
teams: { teams: {
name: string name: string;
logo: React.ElementType logo: React.ElementType;
plan: string plan: string;
}[] }[];
}) { }) {
const { isMobile } = useSidebar() const { isMobile } = useSidebar();
const [activeTeam, setActiveTeam] = React.useState(teams[0]) const [activeTeam, setActiveTeam] = React.useState(teams[0]);
if (!activeTeam) { if (!activeTeam) {
return null return null;
} }
return ( return (
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger>
<SidebarMenuButton <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"
> >
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-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" /> <activeTeam.logo className="size-4" />
@ -53,19 +52,17 @@ export function TeamSwitcher({
</SidebarMenuButton> </SidebarMenuButton>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
align="start" align="start"
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"} side={isMobile ? "bottom" : "right"}
sideOffset={4} sideOffset={4}
> >
<DropdownMenuLabel className="text-muted-foreground text-xs"> <DropdownMenuLabel className="text-muted-foreground text-xs">Teams</DropdownMenuLabel>
Teams
</DropdownMenuLabel>
{teams.map((team, index) => ( {teams.map((team, index) => (
<DropdownMenuItem <DropdownMenuItem
className="gap-2 p-2"
key={team.name} key={team.name}
onClick={() => setActiveTeam(team)} onClick={() => setActiveTeam(team)}
className="gap-2 p-2"
> >
<div className="flex size-6 items-center justify-center rounded-md border"> <div className="flex size-6 items-center justify-center rounded-md border">
<team.logo className="size-3.5 shrink-0" /> <team.logo className="size-3.5 shrink-0" />
@ -85,5 +82,5 @@ export function TeamSwitcher({
</DropdownMenu> </DropdownMenu>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </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 { import {
Badge, Badge,
Button, Button,
@ -18,6 +14,10 @@ import {
Separator, Separator,
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils"; 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"; import { useTranslation } from "../locales/i18n.ts";
/** /**
@ -65,7 +65,7 @@ export type MultiSelectOptionType = {
*/ */
export interface MultiSelectProps export interface MultiSelectProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof multiSelectVariants> { VariantProps<typeof multiSelectVariants> {
/** /**
* An array of option objects to be displayed in the multi-select component. * An array of option objects to be displayed in the multi-select component.
* Each option object has a label, value, and an optional icon. * Each option object has a label, value, and an optional icon.
@ -130,9 +130,8 @@ export interface MultiSelectProps
*/ */
selectAllVisible?: boolean; selectAllVisible?: boolean;
/** /**
* Filtra los items seleccionados * Filtra los items seleccionados
*/ */
filterSelected?: (selectedValues: string[]) => string[]; filterSelected?: (selectedValues: string[]) => string[];
@ -181,7 +180,6 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
if (autoFilter) applySelectedFilter(); if (autoFilter) applySelectedFilter();
}, [autoFilter, selectedValues, applySelectedFilter]); }, [autoFilter, selectedValues, applySelectedFilter]);
const grouped = options.reduce<Record<string, MultiSelectOptionType[]>>((acc, item) => { const grouped = options.reduce<Record<string, MultiSelectOptionType[]>>((acc, item) => {
if (!acc[item.group || ""]) acc[item.group || ""] = []; if (!acc[item.group || ""]) acc[item.group || ""] = [];
acc[item.group || ""].push(item); acc[item.group || ""].push(item);
@ -238,33 +236,33 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
}; };
return ( return (
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen} modal={modalPopover}> <Popover modal={modalPopover} onOpenChange={setIsPopoverOpen} open={isPopoverOpen}>
<PopoverTrigger asChild> <PopoverTrigger>
<Button <Button
ref={ref} ref={ref}
{...props} {...props}
onClick={handleTogglePopover}
className={cn( 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", "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 className
)} )}
onClick={handleTogglePopover}
> >
{selectedValues.length > 0 ? ( {selectedValues.length > 0 ? (
<div className='flex justify-between items-center w-full'> <div className="flex justify-between items-center w-full">
<div className='flex flex-wrap items-center'> <div className="flex flex-wrap items-center">
{selectedValues.slice(0, maxCount).map((value) => { {selectedValues.slice(0, maxCount).map((value) => {
const option = options.find((o) => o.value === value); const option = options.find((o) => o.value === value);
const IconComponent = option?.icon; const IconComponent = option?.icon;
return ( return (
<Badge <Badge
key={value}
className={cn( className={cn(
isAnimating ? "animate-bounce" : "", isAnimating ? "animate-bounce" : "",
multiSelectVariants({ variant }) multiSelectVariants({ variant })
)} )}
key={value}
style={{ animationDuration: `${animation}s` }} style={{ animationDuration: `${animation}s` }}
> >
{IconComponent && <IconComponent className='h-4 w-4 mr-2' />} {IconComponent && <IconComponent className="h-4 w-4 mr-2" />}
{option?.label} {option?.label}
{/*<XCircle {/*<XCircle
className='ml-2 h-4 w-4 cursor-pointer' className='ml-2 h-4 w-4 cursor-pointer'
@ -287,7 +285,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
> >
{`+ ${selectedValues.length - maxCount} more`} {`+ ${selectedValues.length - maxCount} more`}
<XCircleIcon <XCircleIcon
className='ml-2 h-4 w-4 cursor-pointer' className="ml-2 h-4 w-4 cursor-pointer"
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
clearExtraOptions(); clearExtraOptions();
@ -296,33 +294,33 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
</Badge> </Badge>
)} )}
</div> </div>
<div className='flex items-center justify-between'> <div className="flex items-center justify-between">
<Separator orientation='vertical' className='flex min-h-6 h-full' /> <Separator className="flex min-h-6 h-full" orientation="vertical" />
<ChevronDown className='h-4 mx-2 cursor-pointer text-muted-foreground' /> <ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" />
</div> </div>
</div> </div>
) : ( ) : (
<div className='flex items-center justify-between w-full mx-auto'> <div className="flex items-center justify-between w-full mx-auto">
<span className='text-sm text-muted-foreground mx-3'> <span className="text-sm text-muted-foreground mx-3">
{placeholder || t("components.multi_select.select_options")} {placeholder || t("components.multi_select.select_options")}
</span> </span>
<ChevronDown className='h-4 cursor-pointer text-muted-foreground mx-2' /> <ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" />
</div> </div>
)} )}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent <PopoverContent
className='w-auto p-0' align="start"
align='start' className="w-auto p-0"
onEscapeKeyDown={() => setIsPopoverOpen(false)} onEscapeKeyDown={() => setIsPopoverOpen(false)}
> >
<Command> <Command>
<CommandInput placeholder={t("common.search")} onKeyDown={handleInputKeyDown} /> <CommandInput onKeyDown={handleInputKeyDown} placeholder={t("common.search")} />
<CommandList> <CommandList>
<CommandEmpty>{t("components.multi_select.no_results")}</CommandEmpty> <CommandEmpty>{t("components.multi_select.no_results")}</CommandEmpty>
{selectAllVisible && ( {selectAllVisible && (
<CommandItem key='all' onSelect={toggleAll} className='cursor-pointer'> <CommandItem className="cursor-pointer" key="all" onSelect={toggleAll}>
<div <div
className={cn( className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary", "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" : "opacity-50 [&_svg]:invisible"
)} )}
> >
<CheckIcon className='h-4 w-4' /> <CheckIcon className="h-4 w-4" />
</div> </div>
<span>(Select All)</span> <span>(Select All)</span>
</CommandItem> </CommandItem>
)} )}
{Object.keys(grouped).map((group) => { {Object.keys(grouped).map((group) => {
return ( return (
<CommandGroup key={`group-${group || "ungrouped"}`} heading={group}> <CommandGroup heading={group} key={`group-${group || "ungrouped"}`}>
{grouped[group].map((option) => { {grouped[group].map((option) => {
const isSelected = selectedValues.includes(option.value); const isSelected = selectedValues.includes(option.value);
return ( return (
<CommandItem <CommandItem
className="cursor-pointer"
key={option.value} key={option.value}
onSelect={() => toggleOption(option.value)} onSelect={() => toggleOption(option.value)}
className='cursor-pointer'
> >
<div <div
className={cn( className={cn(
@ -360,7 +358,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
/> />
</div> </div>
{option.icon && ( {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> <span>{option.label}</span>
</CommandItem> </CommandItem>
@ -372,21 +370,21 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
<CommandSeparator /> <CommandSeparator />
<CommandGroup> <CommandGroup>
<div className='flex items-center justify-between'> <div className="flex items-center justify-between">
{selectedValues.length > 0 && ( {selectedValues.length > 0 && (
<> <>
<CommandItem <CommandItem
className="flex-1 justify-center cursor-pointer"
onSelect={handleClear} onSelect={handleClear}
className='flex-1 justify-center cursor-pointer'
> >
{t("components.multi_select.clear_selection")} {t("components.multi_select.clear_selection")}
</CommandItem> </CommandItem>
<Separator orientation='vertical' className='flex min-h-6 h-full' /> <Separator className="flex min-h-6 h-full" orientation="vertical" />
</> </>
)} )}
<CommandItem <CommandItem
className="flex-1 justify-center cursor-pointer max-w-full"
onSelect={() => setIsPopoverOpen(false)} onSelect={() => setIsPopoverOpen(false)}
className='flex-1 justify-center cursor-pointer max-w-full'
> >
{t("components.multi_select.close")} {t("components.multi_select.close")}
</CommandItem> </CommandItem>