create-lexy 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/assets/r/accordion.json +1 -1
  2. package/assets/r/alert-dialog.json +1 -1
  3. package/assets/r/app-accordion.json +1 -1
  4. package/assets/r/app-dialog.json +1 -1
  5. package/assets/r/app-header-bar.json +1 -1
  6. package/assets/r/app-sidebar.json +1 -1
  7. package/assets/r/avatar.json +1 -1
  8. package/assets/r/badge.json +1 -1
  9. package/assets/r/breadcrumb.json +1 -1
  10. package/assets/r/button-group.json +1 -1
  11. package/assets/r/button.json +1 -1
  12. package/assets/r/calendar.json +1 -1
  13. package/assets/r/card.json +1 -1
  14. package/assets/r/chart.json +1 -1
  15. package/assets/r/checkbox.json +1 -1
  16. package/assets/r/combobox.json +1 -1
  17. package/assets/r/command.json +1 -1
  18. package/assets/r/confirmacion.json +1 -1
  19. package/assets/r/counter-badge.json +1 -1
  20. package/assets/r/crm-app-layout.json +1 -1
  21. package/assets/r/crm-desk.json +1 -1
  22. package/assets/r/crm-detalle-caso.json +1 -1
  23. package/assets/r/date-picker.json +1 -1
  24. package/assets/r/dialog.json +1 -1
  25. package/assets/r/dropdown-menu.json +1 -1
  26. package/assets/r/empty.json +1 -1
  27. package/assets/r/feature-card.json +1 -1
  28. package/assets/r/form.json +1 -1
  29. package/assets/r/header-bar.json +1 -1
  30. package/assets/r/intake-wizard.json +1 -1
  31. package/assets/r/label.json +1 -1
  32. package/assets/r/login.json +1 -1
  33. package/assets/r/logo.json +1 -1
  34. package/assets/r/menubar.json +1 -1
  35. package/assets/r/navigation-menu.json +1 -1
  36. package/assets/r/pagination.json +1 -1
  37. package/assets/r/popover.json +1 -1
  38. package/assets/r/profile-card.json +1 -1
  39. package/assets/r/progress.json +1 -1
  40. package/assets/r/radio-group.json +1 -1
  41. package/assets/r/scroll-area.json +1 -1
  42. package/assets/r/searchbox.json +1 -1
  43. package/assets/r/select.json +1 -1
  44. package/assets/r/separator.json +1 -1
  45. package/assets/r/sheet.json +1 -1
  46. package/assets/r/sidebar.json +1 -1
  47. package/assets/r/skeleton.json +1 -1
  48. package/assets/r/slider.json +1 -1
  49. package/assets/r/snippet.json +1 -1
  50. package/assets/r/spinner.json +1 -1
  51. package/assets/r/status-dot.json +1 -1
  52. package/assets/r/switch.json +1 -1
  53. package/assets/r/table.json +1 -1
  54. package/assets/r/tabs.json +1 -1
  55. package/assets/r/tag.json +1 -1
  56. package/assets/r/textarea.json +1 -1
  57. package/assets/r/toaster.json +1 -1
  58. package/assets/r/tooltip.json +1 -1
  59. package/assets/r/tree.json +1 -1
  60. package/dist/index.js +219 -53
  61. package/package.json +1 -1
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/Accordion.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Accordion.tsx",
16
- "content": "import * as React from \"react\";\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Accordion = AccordionPrimitive.Root;\n\nconst AccordionItem = React.forwardRef<\n React.ElementRef<typeof AccordionPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <AccordionPrimitive.Item\n ref={ref}\n className={cn(\"border-b border-border\", className)}\n {...props}\n />\n));\nAccordionItem.displayName = \"AccordionItem\";\n\nconst AccordionTrigger = React.forwardRef<\n React.ElementRef<typeof AccordionPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n <AccordionPrimitive.Header className=\"flex w-full\">\n <AccordionPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex flex-1 cursor-pointer items-center justify-between py-4 text-left text-sm font-medium text-foreground transition-all [&[data-state=open]>svg]:rotate-180\",\n className,\n )}\n {...props}>\n {children}\n <svg\n className='h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'>\n <path d='m6 9 6 6 6-6' />\n </svg>\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n));\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;\n\nconst AccordionContent = React.forwardRef<\n React.ElementRef<typeof AccordionPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <AccordionPrimitive.Content\n ref={ref}\n className='overflow-hidden text-sm text-muted-foreground data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down'\n {...props}>\n <div className={cn(\"pb-4 pt-0\", className)}>{children}</div>\n </AccordionPrimitive.Content>\n));\nAccordionContent.displayName = AccordionPrimitive.Content.displayName;\n\nexport { Accordion, AccordionItem, AccordionTrigger, AccordionContent };\n"
16
+ "content": "import * as AccordionPrimitive from \"@radix-ui/react-accordion\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Accordion = AccordionPrimitive.Root;\n\nconst AccordionItem = React.forwardRef<\n React.ElementRef<typeof AccordionPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <AccordionPrimitive.Item\n ref={ref}\n className={cn(\"border-b border-border\", className)}\n {...props}\n />\n));\nAccordionItem.displayName = \"AccordionItem\";\n\nconst AccordionTrigger = React.forwardRef<\n React.ElementRef<typeof AccordionPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n <AccordionPrimitive.Header className=\"flex w-full\">\n <AccordionPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex flex-1 cursor-pointer items-center justify-between py-4 text-left text-sm font-medium text-foreground transition-all [&[data-state=open]>svg]:rotate-180\",\n className,\n )}\n {...props}\n >\n {children}\n <svg\n className=\"h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n));\nAccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;\n\nconst AccordionContent = React.forwardRef<\n React.ElementRef<typeof AccordionPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <AccordionPrimitive.Content\n ref={ref}\n className=\"overflow-hidden text-sm text-muted-foreground data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down\"\n {...props}\n >\n <div className={cn(\"pb-4 pt-0\", className)}>{children}</div>\n </AccordionPrimitive.Content>\n));\nAccordionContent.displayName = AccordionPrimitive.Content.displayName;\n\nexport { Accordion, AccordionContent, AccordionItem, AccordionTrigger };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Accordion.md",
@@ -16,7 +16,7 @@
16
16
  "path": "components/base/AlertDialog.tsx",
17
17
  "type": "registry:ui",
18
18
  "target": "components/base/AlertDialog.tsx",
19
- "content": "import * as React from \"react\";\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\nimport type { VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { buttonVariants } from \"./Button\";\n\n// Confirmación modal que interrumpe (rol alertdialog): sin X, no se cierra\n// con click en el overlay ni Escape implícito — la persona debe elegir.\n// Para diálogos de trabajo (formularios, contenido) usa Dialog/AppDialog.\n\nconst AlertDialog = AlertDialogPrimitive.Root;\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger;\nconst AlertDialogPortal = AlertDialogPrimitive.Portal;\n\nconst AlertDialogOverlay = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n />\n));\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\n\nconst AlertDialogContent = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg\",\n className,\n )}\n {...props}\n />\n </AlertDialogPortal>\n));\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\n\nconst AlertDialogHeader = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n \"flex flex-col space-y-2 text-center sm:text-left\",\n className,\n )}\n {...props}\n />\n);\nAlertDialogHeader.displayName = \"AlertDialogHeader\";\n\nconst AlertDialogFooter = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\n \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n className,\n )}\n {...props}\n />\n);\nAlertDialogFooter.displayName = \"AlertDialogFooter\";\n\nconst AlertDialogTitle = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Title\n ref={ref}\n className={cn(\n \"text-lg font-semibold leading-none tracking-tight text-foreground\",\n className,\n )}\n {...props}\n />\n));\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\n\nconst AlertDialogDescription = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nAlertDialogDescription.displayName =\n AlertDialogPrimitive.Description.displayName;\n\nexport interface AlertDialogActionProps\n extends React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>,\n VariantProps<typeof buttonVariants> {}\n\n/** Acción que confirma. Para acciones irreversibles usa `variant=\"destructive\"`. */\nconst AlertDialogAction = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Action>,\n AlertDialogActionProps\n>(({ className, variant, size, ...props }, ref) => (\n <AlertDialogPrimitive.Action\n ref={ref}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n));\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\n\nconst AlertDialogCancel = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Cancel\n ref={ref}\n className={cn(buttonVariants({ variant: \"ghost\" }), className)}\n {...props}\n />\n));\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\n\nexport {\n AlertDialog,\n AlertDialogPortal,\n AlertDialogOverlay,\n AlertDialogTrigger,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogFooter,\n AlertDialogTitle,\n AlertDialogDescription,\n AlertDialogAction,\n AlertDialogCancel,\n};\n"
19
+ "content": "import * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\nimport type { VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { buttonVariants } from \"./Button\";\n\n// Confirmación modal que interrumpe (rol alertdialog): sin X, no se cierra\n// con click en el overlay ni Escape implícito — la persona debe elegir.\n// Para diálogos de trabajo (formularios, contenido) usa Dialog/AppDialog.\n\nconst AlertDialog = AlertDialogPrimitive.Root;\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger;\nconst AlertDialogPortal = AlertDialogPrimitive.Portal;\n\nconst AlertDialogOverlay = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n />\n));\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\n\nconst AlertDialogContent = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg\",\n className,\n )}\n {...props}\n />\n </AlertDialogPortal>\n));\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\n\nconst AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex flex-col space-y-2 text-center sm:text-left\", className)} {...props} />\n);\nAlertDialogHeader.displayName = \"AlertDialogHeader\";\n\nconst AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", className)}\n {...props}\n />\n);\nAlertDialogFooter.displayName = \"AlertDialogFooter\";\n\nconst AlertDialogTitle = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Title\n ref={ref}\n className={cn(\"text-lg font-semibold leading-none tracking-tight text-foreground\", className)}\n {...props}\n />\n));\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\n\nconst AlertDialogDescription = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nAlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;\n\nexport interface AlertDialogActionProps\n extends\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>,\n VariantProps<typeof buttonVariants> {}\n\n/** Acción que confirma. Para acciones irreversibles usa `variant=\"destructive\"`. */\nconst AlertDialogAction = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Action>,\n AlertDialogActionProps\n>(({ className, variant, size, ...props }, ref) => (\n <AlertDialogPrimitive.Action\n ref={ref}\n className={cn(buttonVariants({ variant, size }), className)}\n {...props}\n />\n));\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\n\nconst AlertDialogCancel = React.forwardRef<\n React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n <AlertDialogPrimitive.Cancel\n ref={ref}\n className={cn(buttonVariants({ variant: \"ghost\" }), className)}\n {...props}\n />\n));\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\n\nexport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogOverlay,\n AlertDialogPortal,\n AlertDialogTitle,\n AlertDialogTrigger,\n};\n"
20
20
  },
21
21
  {
22
22
  "path": "components/base/AlertDialog.md",
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/AppAccordion.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/AppAccordion.tsx",
16
- "content": "import * as React from \"react\";\nimport {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from \"./Accordion\";\nimport { cn } from \"@/lib/utils/cn\";\n\nexport interface AppAccordionItem {\n /** Identificador único del item. */\n value: string;\n /** Título visible del item. */\n title: string;\n /** Icono decorativo antes del título. */\n icon?: React.ReactNode;\n /** Contenido del item (puede ser texto o ReactNode). */\n content: React.ReactNode;\n /** Deshabilita el item. */\n disabled?: boolean;\n}\n\nexport interface AppAccordionProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"dir\"> {\n /** Items del acordeón. */\n items: AppAccordionItem[];\n /** Tipo de acordeón: siempre un item abierto o múltiples. */\n type?: \"single\" | \"multiple\";\n /** Valor del item abierto por defecto (en modo \"single\"). */\n defaultValue?: string;\n /** Valores abiertos por defecto (en modo \"multiple\"). */\n defaultValues?: string[];\n /** Callback cuando cambia el item abierto. */\n onValueChange?: (value: string) => void;\n /** Envuelve el acordeón en un contenedor con borde, fondo y bordes redondeados. */\n containered?: boolean;\n /** Clase adicional para cada item. */\n itemClassName?: string;\n /** Si es `true`, permite colapsar el item abierto haciendo click (modo \"single\"). */\n collapsible?: boolean;\n}\n\n/**\n * Acordeón de aplicación data-driven.\n *\n * Wrapper ergonómico sobre el `Accordion` compuesto: describe los items como\n * datos (`items`) y compone las primitivas internamente. Las props extra\n * (`aria-*`, `id`…) pasan al nodo raíz y el `ref` apunta a ese nodo. Si\n * necesitas variar la estructura (triggers custom, contenido anidado), usa\n * Accordion/AccordionItem/AccordionTrigger/AccordionContent directamente.\n */\nconst AppAccordion = React.forwardRef<HTMLDivElement, AppAccordionProps>(\n function AppAccordion(\n {\n items,\n type = \"single\",\n defaultValue,\n defaultValues,\n onValueChange,\n containered = false,\n className,\n itemClassName,\n collapsible = true,\n ...props\n },\n ref,\n ) {\n const rootClass = containered\n ? cn(\"rounded-lg border border-border bg-background overflow-hidden\", className)\n : className;\n\n const resolvedItemClass = containered\n ? cn(\"px-4 last:border-b-0\", itemClassName)\n : itemClassName;\n\n const renderItems = () =>\n items.map((item) => (\n <AccordionItem\n key={item.value}\n value={item.value}\n className={resolvedItemClass}\n disabled={item.disabled}\n >\n <AccordionTrigger>\n <span className=\"flex items-center gap-2 text-left\">\n {item.icon && (\n <span className=\"shrink-0\">{item.icon}</span>\n )}\n {item.title}\n </span>\n </AccordionTrigger>\n <AccordionContent>{item.content}</AccordionContent>\n </AccordionItem>\n ));\n\n if (type === \"single\") {\n return (\n <Accordion\n ref={ref}\n type=\"single\"\n collapsible={collapsible}\n defaultValue={defaultValue}\n onValueChange={(value) => onValueChange?.(value)}\n className={rootClass}\n {...props}\n >\n {renderItems()}\n </Accordion>\n );\n }\n\n return (\n <Accordion\n ref={ref}\n type=\"multiple\"\n defaultValue={defaultValues}\n className={rootClass}\n {...props}\n >\n {renderItems()}\n </Accordion>\n );\n },\n);\nAppAccordion.displayName = \"AppAccordion\";\n\nexport { AppAccordion };\n"
16
+ "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from \"./Accordion\";\n\nexport interface AppAccordionItem {\n /** Identificador único del item. */\n value: string;\n /** Título visible del item. */\n title: string;\n /** Icono decorativo antes del título. */\n icon?: React.ReactNode;\n /** Contenido del item (puede ser texto o ReactNode). */\n content: React.ReactNode;\n /** Deshabilita el item. */\n disabled?: boolean;\n}\n\nexport interface AppAccordionProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n \"defaultValue\" | \"dir\"\n> {\n /** Items del acordeón. */\n items: AppAccordionItem[];\n /** Tipo de acordeón: siempre un item abierto o múltiples. */\n type?: \"single\" | \"multiple\";\n /** Valor del item abierto por defecto (en modo \"single\"). */\n defaultValue?: string;\n /** Valores abiertos por defecto (en modo \"multiple\"). */\n defaultValues?: string[];\n /** Callback cuando cambia el item abierto. */\n onValueChange?: (value: string) => void;\n /** Envuelve el acordeón en un contenedor con borde, fondo y bordes redondeados. */\n containered?: boolean;\n /** Clase adicional para cada item. */\n itemClassName?: string;\n /** Si es `true`, permite colapsar el item abierto haciendo click (modo \"single\"). */\n collapsible?: boolean;\n}\n\n/**\n * Acordeón de aplicación data-driven.\n *\n * Wrapper ergonómico sobre el `Accordion` compuesto: describe los items como\n * datos (`items`) y compone las primitivas internamente. Las props extra\n * (`aria-*`, `id`…) pasan al nodo raíz y el `ref` apunta a ese nodo. Si\n * necesitas variar la estructura (triggers custom, contenido anidado), usa\n * Accordion/AccordionItem/AccordionTrigger/AccordionContent directamente.\n */\nconst AppAccordion = React.forwardRef<HTMLDivElement, AppAccordionProps>(function AppAccordion(\n {\n items,\n type = \"single\",\n defaultValue,\n defaultValues,\n onValueChange,\n containered = false,\n className,\n itemClassName,\n collapsible = true,\n ...props\n },\n ref,\n) {\n const rootClass = containered\n ? cn(\"rounded-lg border border-border bg-background overflow-hidden\", className)\n : className;\n\n const resolvedItemClass = containered ? cn(\"px-4 last:border-b-0\", itemClassName) : itemClassName;\n\n const renderItems = () =>\n items.map((item) => (\n <AccordionItem\n key={item.value}\n value={item.value}\n className={resolvedItemClass}\n disabled={item.disabled}\n >\n <AccordionTrigger>\n <span className=\"flex items-center gap-2 text-left\">\n {item.icon && <span className=\"shrink-0\">{item.icon}</span>}\n {item.title}\n </span>\n </AccordionTrigger>\n <AccordionContent>{item.content}</AccordionContent>\n </AccordionItem>\n ));\n\n if (type === \"single\") {\n return (\n <Accordion\n ref={ref}\n type=\"single\"\n collapsible={collapsible}\n defaultValue={defaultValue}\n onValueChange={(value) => onValueChange?.(value)}\n className={rootClass}\n {...props}\n >\n {renderItems()}\n </Accordion>\n );\n }\n\n return (\n <Accordion\n ref={ref}\n type=\"multiple\"\n defaultValue={defaultValues}\n className={rootClass}\n {...props}\n >\n {renderItems()}\n </Accordion>\n );\n});\nAppAccordion.displayName = \"AppAccordion\";\n\nexport { AppAccordion };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/AppAccordion.md",
@@ -14,7 +14,7 @@
14
14
  "path": "components/base/AppDialog.tsx",
15
15
  "type": "registry:ui",
16
16
  "target": "components/base/AppDialog.tsx",
17
- "content": "import * as React from \"react\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogTitle,\n DialogTrigger,\n} from \"./Dialog\";\nimport { Button } from \"./Button\";\nimport { cn } from \"@/lib/utils/cn\";\n\nexport interface AppDialogProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof DialogContent>,\n \"title\" | \"children\"\n > {\n /** Elemento que dispara el diálogo (botón, enlace, etc.). */\n trigger: React.ReactNode;\n /** Icono opcional arriba del título (sin fondo, centrado). */\n icon?: React.ReactNode;\n /** Título del diálogo (headline). */\n title: string;\n /** Descripción o texto de soporte (supporting text). Usa `text-sm` igual que los botones. */\n description?: string;\n /** Contenido principal del diálogo (formularios, texto, etc.). */\n children?: React.ReactNode;\n /** Texto del botón de acción principal (derecha). */\n confirmLabel?: string;\n /** Texto del botón de cancelar (izquierda). */\n cancelLabel?: string;\n /** Variante del botón de confirmar. */\n confirmVariant?:\n | \"default\"\n | \"destructive\"\n | \"outline\"\n | \"secondary\"\n | \"ghost\"\n | \"link\";\n /** Deshabilita el botón de confirmar. */\n confirmDisabled?: boolean;\n /** Callback al confirmar. Si devuelve `false`, el diálogo no se cierra. */\n onConfirm?: () => boolean | void | Promise<boolean | void>;\n /** Callback al cancelar o cerrar. */\n onCancel?: () => void;\n /** Alineación de los botones de acción. */\n actionsAlignment?: \"right\" | \"center\" | \"left\" | \"space-between\";\n /** Controla el estado abierto/cerrado (modo controlado). */\n open?: boolean;\n /** Callback cuando cambia el estado abierto. */\n onOpenChange?: (open: boolean) => void;\n /** Clase adicional para el contenido del diálogo. */\n className?: string;\n /** Si es `true`, oculta el footer con los botones de acción. */\n hideFooter?: boolean;\n}\n\n/**\n * Diálogo de aplicación al estilo Material 3.\n *\n * Estructura canónica (sin dividers):\n * 1. Icono opcional (sin fondo, centrado)\n * 2. Headline (título)\n * 3. Supporting text (text-sm, igual que botones)\n * 4. Content area (scrollable si es necesario)\n * 5. Action area (botones, sin divider, alineación configurable)\n *\n * Si hay `icon`, el headline se centra. Si no hay icono, el headline se alinea\n * a la izquierda. El supporting text siempre usa `text-sm` para igualar la\n * tipografía del action area.\n *\n * Wrapper ergonómico sobre el Dialog compuesto: las props extra (`aria-*`,\n * `onEscapeKeyDown`…) pasan al DialogContent y el `ref` apunta a ese nodo.\n * Para estructuras distintas, usa Dialog/DialogContent/DialogFooter directo.\n */\nconst AppDialog = React.forwardRef<\n React.ElementRef<typeof DialogContent>,\n AppDialogProps\n>(function AppDialog(\n {\n trigger,\n icon,\n title,\n description,\n children,\n confirmLabel = \"Confirmar\",\n cancelLabel = \"Cancelar\",\n confirmVariant = \"default\",\n confirmDisabled,\n onConfirm,\n onCancel,\n actionsAlignment = \"right\",\n open,\n onOpenChange,\n className,\n hideFooter,\n ...props\n },\n ref,\n) {\n const [internalOpen, setInternalOpen] = React.useState(false);\n const isControlled = open !== undefined;\n const isOpen = isControlled ? open : internalOpen;\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n onCancel?.();\n }\n if (!isControlled) {\n setInternalOpen(next);\n }\n onOpenChange?.(next);\n };\n\n const handleConfirm = async () => {\n const result = onConfirm?.();\n const shouldClose = result instanceof Promise ? await result : result;\n if (shouldClose !== false) {\n handleOpenChange(false);\n }\n };\n\n const alignmentClasses = {\n right: \"justify-end\",\n center: \"justify-center\",\n left: \"justify-start\",\n \"space-between\": \"justify-between\",\n };\n\n return (\n <Dialog open={isOpen} onOpenChange={handleOpenChange}>\n <DialogTrigger asChild>{trigger}</DialogTrigger>\n <DialogContent ref={ref} className={cn(\"sm:max-w-md\", className)} {...props}>\n {/* Icono opcional (sin fondo, centrado) */}\n {icon && (\n <div className=\"flex justify-center\">\n <div className=\"text-foreground\">{icon}</div>\n </div>\n )}\n\n {/* Headline + Supporting text */}\n <div className={cn(\n \"flex flex-col\",\n icon ? \"text-center\" : \"text-left\"\n )}>\n <DialogTitle\n className={cn(\n \"text-lg font-semibold leading-none tracking-tight text-foreground\",\n icon && \"text-center\"\n )}\n >\n {title}\n </DialogTitle>\n {description && (\n <DialogDescription\n className={cn(\n \"text-sm text-muted-foreground mt-2\",\n icon && \"text-center\"\n )}\n >\n {description}\n </DialogDescription>\n )}\n </div>\n\n {/* Content area */}\n {children && (\n <div className=\"py-4 overflow-y-auto max-h-[60vh]\">\n {children}\n </div>\n )}\n\n {/* Action area (sin dividers). Sin padding propio: el gap-4 del\n DialogContent ya separa, y la base queda en p-6 = 24px = tope. */}\n {!hideFooter && (\n <div className={cn(\n \"flex flex-col-reverse sm:flex-row gap-2\",\n alignmentClasses[actionsAlignment]\n )}>\n <Button\n variant=\"ghost\"\n onClick={() => handleOpenChange(false)}\n >\n {cancelLabel}\n </Button>\n <Button\n variant={confirmVariant}\n disabled={confirmDisabled}\n onClick={handleConfirm}\n >\n {confirmLabel}\n </Button>\n </div>\n )}\n </DialogContent>\n </Dialog>\n );\n});\nAppDialog.displayName = \"AppDialog\";\n\nexport { AppDialog };\n"
17
+ "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from \"./Dialog\";\n\nexport interface AppDialogProps extends Omit<\n React.ComponentPropsWithoutRef<typeof DialogContent>,\n \"title\" | \"children\"\n> {\n /** Elemento que dispara el diálogo (botón, enlace, etc.). */\n trigger: React.ReactNode;\n /** Icono opcional arriba del título (sin fondo, centrado). */\n icon?: React.ReactNode;\n /** Título del diálogo (headline). */\n title: string;\n /** Descripción o texto de soporte (supporting text). Usa `text-sm` igual que los botones. */\n description?: string;\n /** Contenido principal del diálogo (formularios, texto, etc.). */\n children?: React.ReactNode;\n /** Texto del botón de acción principal (derecha). */\n confirmLabel?: string;\n /** Texto del botón de cancelar (izquierda). */\n cancelLabel?: string;\n /** Variante del botón de confirmar. */\n confirmVariant?: \"default\" | \"destructive\" | \"outline\" | \"secondary\" | \"ghost\" | \"link\";\n /** Deshabilita el botón de confirmar. */\n confirmDisabled?: boolean;\n /** Callback al confirmar. Si devuelve `false`, el diálogo no se cierra. */\n onConfirm?: () => boolean | void | Promise<boolean | void>;\n /** Callback al cancelar o cerrar. */\n onCancel?: () => void;\n /** Alineación de los botones de acción. */\n actionsAlignment?: \"right\" | \"center\" | \"left\" | \"space-between\";\n /** Controla el estado abierto/cerrado (modo controlado). */\n open?: boolean;\n /** Callback cuando cambia el estado abierto. */\n onOpenChange?: (open: boolean) => void;\n /** Clase adicional para el contenido del diálogo. */\n className?: string;\n /** Si es `true`, oculta el footer con los botones de acción. */\n hideFooter?: boolean;\n}\n\n/**\n * Diálogo de aplicación al estilo Material 3.\n *\n * Estructura canónica (sin dividers):\n * 1. Icono opcional (sin fondo, centrado)\n * 2. Headline (título)\n * 3. Supporting text (text-sm, igual que botones)\n * 4. Content area (scrollable si es necesario)\n * 5. Action area (botones, sin divider, alineación configurable)\n *\n * Si hay `icon`, el headline se centra. Si no hay icono, el headline se alinea\n * a la izquierda. El supporting text siempre usa `text-sm` para igualar la\n * tipografía del action area.\n *\n * Wrapper ergonómico sobre el Dialog compuesto: las props extra (`aria-*`,\n * `onEscapeKeyDown`…) pasan al DialogContent y el `ref` apunta a ese nodo.\n * Para estructuras distintas, usa Dialog/DialogContent/DialogFooter directo.\n */\nconst AppDialog = React.forwardRef<React.ElementRef<typeof DialogContent>, AppDialogProps>(\n function AppDialog(\n {\n trigger,\n icon,\n title,\n description,\n children,\n confirmLabel = \"Confirmar\",\n cancelLabel = \"Cancelar\",\n confirmVariant = \"default\",\n confirmDisabled,\n onConfirm,\n onCancel,\n actionsAlignment = \"right\",\n open,\n onOpenChange,\n className,\n hideFooter,\n ...props\n },\n ref,\n ) {\n const [internalOpen, setInternalOpen] = React.useState(false);\n const isControlled = open !== undefined;\n const isOpen = isControlled ? open : internalOpen;\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n onCancel?.();\n }\n if (!isControlled) {\n setInternalOpen(next);\n }\n onOpenChange?.(next);\n };\n\n const handleConfirm = async () => {\n const result = onConfirm?.();\n const shouldClose = result instanceof Promise ? await result : result;\n if (shouldClose !== false) {\n handleOpenChange(false);\n }\n };\n\n const alignmentClasses = {\n right: \"justify-end\",\n center: \"justify-center\",\n left: \"justify-start\",\n \"space-between\": \"justify-between\",\n };\n\n return (\n <Dialog open={isOpen} onOpenChange={handleOpenChange}>\n <DialogTrigger asChild>{trigger}</DialogTrigger>\n <DialogContent ref={ref} className={cn(\"sm:max-w-md\", className)} {...props}>\n {/* Icono opcional (sin fondo, centrado) */}\n {icon && (\n <div className=\"flex justify-center\">\n <div className=\"text-foreground\">{icon}</div>\n </div>\n )}\n\n {/* Headline + Supporting text */}\n <div className={cn(\"flex flex-col\", icon ? \"text-center\" : \"text-left\")}>\n <DialogTitle\n className={cn(\n \"text-lg font-semibold leading-none tracking-tight text-foreground\",\n icon && \"text-center\",\n )}\n >\n {title}\n </DialogTitle>\n {description && (\n <DialogDescription\n className={cn(\"text-sm text-muted-foreground mt-2\", icon && \"text-center\")}\n >\n {description}\n </DialogDescription>\n )}\n </div>\n\n {/* Content area */}\n {children && <div className=\"py-4 overflow-y-auto max-h-[60vh]\">{children}</div>}\n\n {/* Action area (sin dividers). Sin padding propio: el gap-4 del\n DialogContent ya separa, y la base queda en p-6 = 24px = tope. */}\n {!hideFooter && (\n <div\n className={cn(\n \"flex flex-col-reverse sm:flex-row gap-2\",\n alignmentClasses[actionsAlignment],\n )}\n >\n <Button variant=\"ghost\" onClick={() => handleOpenChange(false)}>\n {cancelLabel}\n </Button>\n <Button variant={confirmVariant} disabled={confirmDisabled} onClick={handleConfirm}>\n {confirmLabel}\n </Button>\n </div>\n )}\n </DialogContent>\n </Dialog>\n );\n },\n);\nAppDialog.displayName = \"AppDialog\";\n\nexport { AppDialog };\n"
18
18
  },
19
19
  {
20
20
  "path": "components/base/AppDialog.md",
@@ -17,7 +17,7 @@
17
17
  "path": "components/base/AppHeaderBar.tsx",
18
18
  "type": "registry:ui",
19
19
  "target": "components/base/AppHeaderBar.tsx",
20
- "content": "import * as React from \"react\";\nimport type { VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Button } from \"./Button\";\nimport { HeaderBar, headerVariants } from \"./HeaderBar\";\nimport { Logo } from \"./Logo\";\n\nexport interface AppHeaderBarItem {\n label: string;\n href?: string;\n active?: boolean;\n disabled?: boolean;\n onClick?: () => void;\n}\n\nexport interface AppHeaderBarAction {\n label: string;\n icon?: React.ComponentType<{ className?: string }>;\n onClick?: () => void;\n variant?: \"default\" | \"ghost\" | \"outline\";\n disabled?: boolean;\n}\n\nexport interface AppHeaderBarProps\n extends React.HTMLAttributes<HTMLElement>,\n VariantProps<typeof headerVariants> {\n /** Qué mostrar como marca. `false` oculta el área de marca. ReactNode para custom. */\n brand?: React.ReactNode | false;\n /** Items de navegación principales (centro/izquierda). */\n items?: AppHeaderBarItem[];\n /** Acciones alineadas a la derecha (botones, avatar, menús). */\n actions?: React.ReactNode;\n /** Acciones como datos (alternativa a `actions` ReactNode). */\n actionItems?: AppHeaderBarAction[];\n /** Callback cuando cambia el item activo. */\n onItemClick?: (item: AppHeaderBarItem) => void;\n}\n\n/**\n * Barra de navegación superior data-driven.\n *\n * Wrapper ergonómico sobre `HeaderBar` + `Button`: describe la navegación como\n * datos (`items`, `actionItems`) y compone las piezas en el orden correcto.\n * Para estructuras distintas (mega-menús, buscador central), usa `HeaderBar`\n * directamente con `brand`/`actions` como ReactNode.\n */\nexport const AppHeaderBar = React.forwardRef<HTMLElement, AppHeaderBarProps>(\n function AppHeaderBar(\n {\n surface = \"default\",\n bordered,\n padding,\n sticky,\n brand,\n items,\n actions,\n actionItems,\n onItemClick,\n className,\n ...props\n },\n ref\n ) {\n const logoSurface = surface === \"navy\" ? \"dark\" : \"light\";\n\n const brandArea = (\n <>\n {brand !== false && (brand ?? <Logo surface={logoSurface} />)}\n {items && items.length > 0 && (\n <nav className=\"hidden md:flex items-center gap-1\">\n {items.map((item, index) => (\n <a\n key={index}\n href={item.href}\n onClick={(e) => {\n if (item.onClick || onItemClick) {\n e.preventDefault();\n item.onClick?.();\n onItemClick?.(item);\n }\n }}\n className={cn(\n \"px-3 py-2 text-sm font-medium rounded-md transition-colors\",\n item.active\n ? surface === \"navy\"\n ? \"bg-white/15 text-white\"\n : \"bg-accent text-accent-foreground\"\n : surface === \"navy\"\n ? \"text-white/70 hover:text-white hover:bg-white/10\"\n : \"text-muted-foreground hover:text-foreground hover:bg-secondary\",\n item.disabled && \"opacity-50 pointer-events-none\"\n )}\n >\n {item.label}\n </a>\n ))}\n </nav>\n )}\n </>\n );\n\n const actionArea =\n actionItems || actions ? (\n <>\n {actionItems?.map((action, index) => {\n const Icon = action.icon;\n return (\n <Button\n key={index}\n variant={action.variant ?? \"default\"}\n onClick={action.onClick}\n disabled={action.disabled}\n >\n {Icon && <Icon className=\"size-4\" />}\n {action.label}\n </Button>\n );\n })}\n {actions}\n </>\n ) : undefined;\n\n return (\n <HeaderBar\n ref={ref}\n surface={surface}\n bordered={bordered}\n padding={padding}\n sticky={sticky}\n brand={brandArea}\n actions={actionArea}\n className={className}\n {...props}\n />\n );\n }\n);\n\nAppHeaderBar.displayName = \"AppHeaderBar\";\n"
20
+ "content": "import type { VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport { HeaderBar, headerVariants } from \"./HeaderBar\";\nimport { Logo } from \"./Logo\";\n\nexport interface AppHeaderBarItem {\n label: string;\n href?: string;\n active?: boolean;\n disabled?: boolean;\n onClick?: () => void;\n}\n\nexport interface AppHeaderBarAction {\n label: string;\n icon?: React.ComponentType<{ className?: string }>;\n onClick?: () => void;\n variant?: \"default\" | \"ghost\" | \"outline\";\n disabled?: boolean;\n}\n\nexport interface AppHeaderBarProps\n extends React.HTMLAttributes<HTMLElement>, VariantProps<typeof headerVariants> {\n /** Qué mostrar como marca. `false` oculta el área de marca. ReactNode para custom. */\n brand?: React.ReactNode | false;\n /** Items de navegación principales (centro/izquierda). */\n items?: AppHeaderBarItem[];\n /** Acciones alineadas a la derecha (botones, avatar, menús). */\n actions?: React.ReactNode;\n /** Acciones como datos (alternativa a `actions` ReactNode). */\n actionItems?: AppHeaderBarAction[];\n /** Callback cuando cambia el item activo. */\n onItemClick?: (item: AppHeaderBarItem) => void;\n}\n\n/**\n * Barra de navegación superior data-driven.\n *\n * Wrapper ergonómico sobre `HeaderBar` + `Button`: describe la navegación como\n * datos (`items`, `actionItems`) y compone las piezas en el orden correcto.\n * Para estructuras distintas (mega-menús, buscador central), usa `HeaderBar`\n * directamente con `brand`/`actions` como ReactNode.\n */\nexport const AppHeaderBar = React.forwardRef<HTMLElement, AppHeaderBarProps>(function AppHeaderBar(\n {\n surface = \"default\",\n bordered,\n padding,\n sticky,\n brand,\n items,\n actions,\n actionItems,\n onItemClick,\n className,\n ...props\n },\n ref,\n) {\n const logoSurface = surface === \"navy\" ? \"dark\" : \"light\";\n\n const brandArea = (\n <>\n {brand !== false && (brand ?? <Logo surface={logoSurface} />)}\n {items && items.length > 0 && (\n <nav className=\"hidden md:flex items-center gap-1\">\n {items.map((item, index) => (\n <a\n key={index}\n href={item.href}\n onClick={(e) => {\n if (item.onClick || onItemClick) {\n e.preventDefault();\n item.onClick?.();\n onItemClick?.(item);\n }\n }}\n className={cn(\n \"px-3 py-2 text-sm font-medium rounded-md transition-colors\",\n item.active\n ? surface === \"navy\"\n ? \"bg-white/15 text-white\"\n : \"bg-accent text-accent-foreground\"\n : surface === \"navy\"\n ? \"text-white/70 hover:text-white hover:bg-white/10\"\n : \"text-muted-foreground hover:text-foreground hover:bg-secondary\",\n item.disabled && \"opacity-50 pointer-events-none\",\n )}\n >\n {item.label}\n </a>\n ))}\n </nav>\n )}\n </>\n );\n\n const actionArea =\n actionItems || actions ? (\n <>\n {actionItems?.map((action, index) => {\n const Icon = action.icon;\n return (\n <Button\n key={index}\n variant={action.variant ?? \"default\"}\n onClick={action.onClick}\n disabled={action.disabled}\n >\n {Icon && <Icon className=\"size-4\" />}\n {action.label}\n </Button>\n );\n })}\n {actions}\n </>\n ) : undefined;\n\n return (\n <HeaderBar\n ref={ref}\n surface={surface}\n bordered={bordered}\n padding={padding}\n sticky={sticky}\n brand={brandArea}\n actions={actionArea}\n className={className}\n {...props}\n />\n );\n});\n\nAppHeaderBar.displayName = \"AppHeaderBar\";\n"
21
21
  },
22
22
  {
23
23
  "path": "components/base/AppHeaderBar.md",
@@ -16,7 +16,7 @@
16
16
  "path": "components/base/AppSidebar.tsx",
17
17
  "type": "registry:ui",
18
18
  "target": "components/base/AppSidebar.tsx",
19
- "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronRight } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils/cn\"\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarTrigger,\n} from \"./Sidebar\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./Avatar\"\n\n// AppSidebar — envoltorio *data-driven* sobre las primitivas de `Sidebar`.\n//\n// En lugar de componer 20+ subcomponentes a mano, describes la navegación como\n// datos (`groups`) y el componente arma la composición correcta, replicando la\n// estética canónica de `Sidebar` (paddings px-8, grupos colapsables con chevron,\n// sub-ítems indentados con `SidebarMenuSub` a pl-9, pie de usuario con borde).\n//\n// Para casos a medida, las primitivas de `./Sidebar` siguen disponibles.\n//\n// Se usa dentro de un <SidebarProvider> (junto a <SidebarInset> para el\n// contenido). Ver la story para el layout completo.\n\ntype IconType = React.ComponentType<{ className?: string }>\n\nexport interface AppSidebarItem {\n /** Texto visible del ítem. */\n title: string\n /** Si se define, el ítem se renderiza como enlace `<a href>`. */\n url?: string\n /** Icono (p. ej. de lucide-react). Se renderiza a la izquierda. */\n icon?: IconType\n /** Marca el ítem como activo (página actual). */\n isActive?: boolean\n /** Deshabilita el ítem (no navegable). */\n disabled?: boolean\n /** Contador/etiqueta a la derecha (p. ej. nº de notificaciones). */\n badge?: string | number\n /** Subítems anidados → convierte el ítem en colapsable. */\n items?: AppSidebarItem[]\n /** Si el ítem tiene subítems, si arranca expandido. */\n defaultOpen?: boolean\n}\n\nexport interface AppSidebarGroup {\n /** Encabezado del grupo. Si se omite, los ítems van sin título. */\n label?: string\n /** Si el grupo se puede colapsar con un chevron en su encabezado. */\n collapsible?: boolean\n /** Si es colapsable, si arranca expandido (default: true). */\n defaultOpen?: boolean\n items: AppSidebarItem[]\n}\n\nexport interface AppSidebarUser {\n name: string\n email?: string\n avatar?: string\n}\n\nexport interface AppSidebarProps\n extends React.ComponentProps<typeof Sidebar> {\n /** Marca en la cabecera cuando está expandida (normalmente el `Logo`). */\n logo?: React.ReactNode\n /** Marca compacta para el modo colapsado a iconos (p. ej. el isotipo). */\n logoIcon?: React.ReactNode\n /** Estructura de navegación. */\n groups: AppSidebarGroup[]\n /** Usuario en el pie. Ignorado si se pasa `footer`. */\n user?: AppSidebarUser\n /** Pie a medida; sobrescribe el render de `user`. */\n footer?: React.ReactNode\n /**\n * Borde lateral divisorio. Independiente del modo de colapso.\n * Default: `true`.\n */\n bordered?: boolean\n}\n\n// ─── Ítem de primer nivel (botón de menú con icono) ───────────────────────────\n\nfunction LeafItem({ item }: { item: AppSidebarItem }) {\n const Icon = item.icon\n const inner = (\n <>\n {Icon && <Icon className=\"size-4 shrink-0\" />}\n <span className=\"truncate\">{item.title}</span>\n </>\n )\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n asChild={!!item.url && !item.disabled}\n isActive={item.isActive}\n tooltip={item.title}\n aria-disabled={item.disabled || undefined}\n aria-current={item.isActive ? \"page\" : undefined}\n className={cn(\n \"h-8 rounded-md px-2 text-sm\",\n item.disabled && \"pointer-events-none opacity-50\",\n )}\n >\n {item.url && !item.disabled ? <a href={item.url}>{inner}</a> : inner}\n </SidebarMenuButton>\n {item.badge != null && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}\n </SidebarMenuItem>\n )\n}\n\n// ─── Sub-ítem indentado (compartido por grupos colapsables y branch items) ────\n\nfunction SubButton({ item }: { item: AppSidebarItem }) {\n const Icon = item.icon\n const inner = (\n <>\n {Icon && <Icon className=\"size-4 shrink-0\" />}\n <span className=\"truncate\">{item.title}</span>\n {item.badge != null && (\n <span className=\"ml-auto text-xs font-medium tabular-nums text-muted-foreground\">\n {item.badge}\n </span>\n )}\n </>\n )\n\n return (\n <SidebarMenuSubItem>\n <SidebarMenuSubButton\n asChild={!!item.url && !item.disabled}\n isActive={item.isActive}\n aria-disabled={item.disabled || undefined}\n aria-current={item.isActive ? \"page\" : undefined}\n className={cn(\n \"h-8 rounded-md py-1.5 pl-9 pr-2 text-sm\",\n item.disabled && \"cursor-not-allowed text-muted-foreground\",\n )}\n >\n {item.url && !item.disabled ? <a href={item.url}>{inner}</a> : inner}\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n )\n}\n\n// ─── Ítem de primer nivel con subítems (colapsable, chevron a la derecha) ─────\n\nfunction BranchItem({ item }: { item: AppSidebarItem }) {\n const [open, setOpen] = React.useState(item.defaultOpen ?? false)\n const Icon = item.icon\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => setOpen((v) => !v)}\n aria-expanded={open}\n isActive={item.isActive}\n tooltip={item.title}\n className=\"h-8 rounded-md px-2 text-sm\"\n >\n {Icon && <Icon className=\"size-4 shrink-0\" />}\n <span className=\"truncate\">{item.title}</span>\n <ChevronRight\n className={cn(\n \"ml-auto size-4 shrink-0 text-muted-foreground transition-transform duration-200\",\n open && \"rotate-90\",\n )}\n />\n </SidebarMenuButton>\n\n {open && (\n <SidebarMenuSub className=\"mx-0 mt-1 gap-1 border-l-0 px-0 py-0\">\n {item.items!.map((sub) => (\n <SubButton key={sub.title} item={sub} />\n ))}\n </SidebarMenuSub>\n )}\n </SidebarMenuItem>\n )\n}\n\n// ─── Grupo ────────────────────────────────────────────────────────────────────\n\nfunction Group({ group }: { group: AppSidebarGroup }) {\n const [open, setOpen] = React.useState(group.defaultOpen ?? true)\n\n // Grupo colapsable: encabezado bold con chevron a la izquierda + sub-ítems\n // indentados (mismo patrón que las stories de Sidebar).\n if (group.collapsible && group.label) {\n return (\n <SidebarGroup className=\"mb-1 p-0\">\n <SidebarGroupLabel\n asChild\n className=\"h-8 px-2 text-sm font-semibold text-sidebar-foreground hover:bg-secondary hover:text-sidebar-foreground\"\n >\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n >\n <ChevronRight\n className={cn(\n \"mr-3 size-4 shrink-0 text-muted-foreground transition-transform duration-200\",\n open && \"rotate-90\",\n )}\n />\n <span className=\"truncate\">{group.label}</span>\n </button>\n </SidebarGroupLabel>\n\n {open && (\n <SidebarGroupContent>\n <SidebarMenuSub className=\"mx-0 mt-1 gap-1 border-l-0 px-0 py-0\">\n {group.items.map((item) => (\n <SubButton key={item.title} item={item} />\n ))}\n </SidebarMenuSub>\n </SidebarGroupContent>\n )}\n </SidebarGroup>\n )\n }\n\n // Grupo plano: etiqueta estática opcional + ítems de primer nivel.\n return (\n <SidebarGroup className=\"mb-1 p-0\">\n {group.label && (\n <SidebarGroupLabel className=\"px-2 text-xs font-medium text-muted-foreground\">\n {group.label}\n </SidebarGroupLabel>\n )}\n <SidebarGroupContent>\n <SidebarMenu>\n {group.items.map((item) =>\n item.items && item.items.length > 0 ? (\n <BranchItem key={item.title} item={item} />\n ) : (\n <LeafItem key={item.title} item={item} />\n ),\n )}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n )\n}\n\n// ─── Pie de usuario ───────────────────────────────────────────────────────────\n\nfunction UserFooter({ user }: { user: AppSidebarUser }) {\n const initials = user.name\n .split(\" \")\n .map((p) => p[0])\n .slice(0, 2)\n .join(\"\")\n .toUpperCase()\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton\n tooltip={user.name}\n className=\"h-auto gap-3 rounded-md px-2 py-1.5 group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-0\"\n >\n <Avatar className=\"size-8 shrink-0\">\n {user.avatar && <AvatarImage src={user.avatar} alt={user.name} />}\n <AvatarFallback>{initials}</AvatarFallback>\n </Avatar>\n <div className=\"flex min-w-0 flex-col text-left leading-tight group-data-[collapsible=icon]:hidden\">\n <span className=\"truncate text-sm font-medium\">{user.name}</span>\n {user.email && (\n <span className=\"truncate text-xs text-muted-foreground\">\n {user.email}\n </span>\n )}\n </div>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n\n// ─── AppSidebar ───────────────────────────────────────────────────────────────\n\nexport const AppSidebar = React.forwardRef<HTMLDivElement, AppSidebarProps>(\n function AppSidebar(\n {\n logo,\n logoIcon,\n groups,\n user,\n footer,\n bordered = true,\n className,\n ...sidebarProps\n },\n ref,\n ) {\n const collapsible = sidebarProps.collapsible ?? \"offcanvas\"\n const canToggle = collapsible !== \"none\"\n\n // Borde desacoplado del modo de colapso: lo decide `bordered`, no `collapsible`.\n // El Sidebar trae el borde por defecto (variant sidebar) vía una clase con\n // modificador de `side`, así que para quitarlo hace falta `!important`.\n const borderClass = bordered ? \"\" : \"!border-l-0 !border-r-0\"\n\n return (\n <Sidebar ref={ref} className={cn(borderClass, className)} {...sidebarProps}>\n {(logo != null || logoIcon != null) && (\n <SidebarHeader className=\"px-0 py-6\">\n {/* Las dos marcas se superponen y hacen cross-fade. El isotipo se ancla\n a los primeros 64px (la franja de iconos) para aparecer ya en su\n posición final, sin desplazarse con la animación de ancho. */}\n <div className=\"relative h-8\">\n {/* Expandida: logo + trigger (sale con fade) */}\n <div className=\"absolute inset-0 flex items-center gap-3 pl-8 pr-2 transition-opacity duration-200 group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:opacity-0\">\n <div className=\"flex min-w-0 flex-1 items-center\">{logo}</div>\n {canToggle && (\n <SidebarTrigger\n aria-label=\"Colapsar barra lateral\"\n className=\"size-8 shrink-0 bg-transparent shadow-none\"\n />\n )}\n </div>\n {/* Colapsada a iconos: isotipo en posición final (entra con fade) */}\n {logoIcon != null && (\n <div className=\"pointer-events-none absolute inset-y-0 left-0 flex w-(--sidebar-width-icon) items-center justify-center opacity-0 transition-opacity duration-200 group-data-[collapsible=icon]:opacity-100\">\n {logoIcon}\n </div>\n )}\n </div>\n </SidebarHeader>\n )}\n\n <SidebarContent className=\"px-8 pb-6 group-data-[collapsible=icon]:px-4\">\n {groups.map((group, i) => (\n <Group key={group.label ?? `group-${i}`} group={group} />\n ))}\n </SidebarContent>\n\n {(footer != null || user != null) && (\n <SidebarFooter className=\"border-t border-sidebar-border px-8 py-3 group-data-[collapsible=icon]:px-4\">\n {footer != null ? footer : user != null ? <UserFooter user={user} /> : null}\n </SidebarFooter>\n )}\n </Sidebar>\n )\n },\n)\n\nAppSidebar.displayName = \"AppSidebar\"\n"
19
+ "content": "\"use client\";\n\nimport { ChevronRight } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"./Avatar\";\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarTrigger,\n} from \"./Sidebar\";\n\n// AppSidebar — envoltorio *data-driven* sobre las primitivas de `Sidebar`.\n//\n// En lugar de componer 20+ subcomponentes a mano, describes la navegación como\n// datos (`groups`) y el componente arma la composición correcta, replicando la\n// estética canónica de `Sidebar` (paddings px-8, grupos colapsables con chevron,\n// sub-ítems indentados con `SidebarMenuSub` a pl-9, pie de usuario con borde).\n//\n// Para casos a medida, las primitivas de `./Sidebar` siguen disponibles.\n//\n// Se usa dentro de un <SidebarProvider> (junto a <SidebarInset> para el\n// contenido). Ver la story para el layout completo.\n\ntype IconType = React.ComponentType<{ className?: string }>;\n\nexport interface AppSidebarItem {\n /** Texto visible del ítem. */\n title: string;\n /** Si se define, el ítem se renderiza como enlace `<a href>`. */\n url?: string;\n /** Icono (p. ej. de lucide-react). Se renderiza a la izquierda. */\n icon?: IconType;\n /** Marca el ítem como activo (página actual). */\n isActive?: boolean;\n /** Deshabilita el ítem (no navegable). */\n disabled?: boolean;\n /** Contador/etiqueta a la derecha (p. ej. nº de notificaciones). */\n badge?: string | number;\n /** Subítems anidados → convierte el ítem en colapsable. */\n items?: AppSidebarItem[];\n /** Si el ítem tiene subítems, si arranca expandido. */\n defaultOpen?: boolean;\n}\n\nexport interface AppSidebarGroup {\n /** Encabezado del grupo. Si se omite, los ítems van sin título. */\n label?: string;\n /** Si el grupo se puede colapsar con un chevron en su encabezado. */\n collapsible?: boolean;\n /** Si es colapsable, si arranca expandido (default: true). */\n defaultOpen?: boolean;\n items: AppSidebarItem[];\n}\n\nexport interface AppSidebarUser {\n name: string;\n email?: string;\n avatar?: string;\n}\n\nexport interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {\n /** Marca en la cabecera cuando está expandida (normalmente el `Logo`). */\n logo?: React.ReactNode;\n /** Marca compacta para el modo colapsado a iconos (p. ej. el isotipo). */\n logoIcon?: React.ReactNode;\n /** Estructura de navegación. */\n groups: AppSidebarGroup[];\n /** Usuario en el pie. Ignorado si se pasa `footer`. */\n user?: AppSidebarUser;\n /** Pie a medida; sobrescribe el render de `user`. */\n footer?: React.ReactNode;\n /**\n * Borde lateral divisorio. Independiente del modo de colapso.\n * Default: `true`.\n */\n bordered?: boolean;\n}\n\n// ─── Ítem de primer nivel (botón de menú con icono) ───────────────────────────\n\nfunction LeafItem({ item }: { item: AppSidebarItem }) {\n const Icon = item.icon;\n const inner = (\n <>\n {Icon && <Icon className=\"size-4 shrink-0\" />}\n <span className=\"truncate\">{item.title}</span>\n </>\n );\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n asChild={!!item.url && !item.disabled}\n isActive={item.isActive}\n tooltip={item.title}\n aria-disabled={item.disabled || undefined}\n aria-current={item.isActive ? \"page\" : undefined}\n className={cn(\n \"h-8 rounded-md px-2 text-sm\",\n item.disabled && \"pointer-events-none opacity-50\",\n )}\n >\n {item.url && !item.disabled ? <a href={item.url}>{inner}</a> : inner}\n </SidebarMenuButton>\n {item.badge != null && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}\n </SidebarMenuItem>\n );\n}\n\n// ─── Sub-ítem indentado (compartido por grupos colapsables y branch items) ────\n\nfunction SubButton({ item }: { item: AppSidebarItem }) {\n const Icon = item.icon;\n const inner = (\n <>\n {Icon && <Icon className=\"size-4 shrink-0\" />}\n <span className=\"truncate\">{item.title}</span>\n {item.badge != null && (\n <span className=\"ml-auto text-xs font-medium tabular-nums text-muted-foreground\">\n {item.badge}\n </span>\n )}\n </>\n );\n\n return (\n <SidebarMenuSubItem>\n <SidebarMenuSubButton\n asChild={!!item.url && !item.disabled}\n isActive={item.isActive}\n aria-disabled={item.disabled || undefined}\n aria-current={item.isActive ? \"page\" : undefined}\n className={cn(\n \"h-8 rounded-md py-1.5 pl-9 pr-2 text-sm\",\n item.disabled && \"cursor-not-allowed text-muted-foreground\",\n )}\n >\n {item.url && !item.disabled ? <a href={item.url}>{inner}</a> : inner}\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n );\n}\n\n// ─── Ítem de primer nivel con subítems (colapsable, chevron a la derecha) ─────\n\nfunction BranchItem({ item }: { item: AppSidebarItem }) {\n const [open, setOpen] = React.useState(item.defaultOpen ?? false);\n const Icon = item.icon;\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n onClick={() => setOpen((v) => !v)}\n aria-expanded={open}\n isActive={item.isActive}\n tooltip={item.title}\n className=\"h-8 rounded-md px-2 text-sm\"\n >\n {Icon && <Icon className=\"size-4 shrink-0\" />}\n <span className=\"truncate\">{item.title}</span>\n <ChevronRight\n className={cn(\n \"ml-auto size-4 shrink-0 text-muted-foreground transition-transform duration-200\",\n open && \"rotate-90\",\n )}\n />\n </SidebarMenuButton>\n\n {open && (\n <SidebarMenuSub className=\"mx-0 mt-1 gap-1 border-l-0 px-0 py-0\">\n {item.items!.map((sub) => (\n <SubButton key={sub.title} item={sub} />\n ))}\n </SidebarMenuSub>\n )}\n </SidebarMenuItem>\n );\n}\n\n// ─── Grupo ────────────────────────────────────────────────────────────────────\n\nfunction Group({ group }: { group: AppSidebarGroup }) {\n const [open, setOpen] = React.useState(group.defaultOpen ?? true);\n\n // Grupo colapsable: encabezado bold con chevron a la izquierda + sub-ítems\n // indentados (mismo patrón que las stories de Sidebar).\n if (group.collapsible && group.label) {\n return (\n <SidebarGroup className=\"mb-1 p-0\">\n <SidebarGroupLabel\n asChild\n className=\"h-8 px-2 text-sm font-semibold text-sidebar-foreground hover:bg-secondary hover:text-sidebar-foreground\"\n >\n <button type=\"button\" aria-expanded={open} onClick={() => setOpen((v) => !v)}>\n <ChevronRight\n className={cn(\n \"mr-3 size-4 shrink-0 text-muted-foreground transition-transform duration-200\",\n open && \"rotate-90\",\n )}\n />\n <span className=\"truncate\">{group.label}</span>\n </button>\n </SidebarGroupLabel>\n\n {open && (\n <SidebarGroupContent>\n <SidebarMenuSub className=\"mx-0 mt-1 gap-1 border-l-0 px-0 py-0\">\n {group.items.map((item) => (\n <SubButton key={item.title} item={item} />\n ))}\n </SidebarMenuSub>\n </SidebarGroupContent>\n )}\n </SidebarGroup>\n );\n }\n\n // Grupo plano: etiqueta estática opcional + ítems de primer nivel.\n return (\n <SidebarGroup className=\"mb-1 p-0\">\n {group.label && (\n <SidebarGroupLabel className=\"px-2 text-xs font-medium text-muted-foreground\">\n {group.label}\n </SidebarGroupLabel>\n )}\n <SidebarGroupContent>\n <SidebarMenu>\n {group.items.map((item) =>\n item.items && item.items.length > 0 ? (\n <BranchItem key={item.title} item={item} />\n ) : (\n <LeafItem key={item.title} item={item} />\n ),\n )}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n );\n}\n\n// ─── Pie de usuario ───────────────────────────────────────────────────────────\n\nfunction UserFooter({ user }: { user: AppSidebarUser }) {\n const initials = user.name\n .split(\" \")\n .map((p) => p[0])\n .slice(0, 2)\n .join(\"\")\n .toUpperCase();\n\n return (\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton\n tooltip={user.name}\n className=\"h-auto gap-3 rounded-md px-2 py-1.5 group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-0\"\n >\n <Avatar className=\"size-8 shrink-0\">\n {user.avatar && <AvatarImage src={user.avatar} alt={user.name} />}\n <AvatarFallback>{initials}</AvatarFallback>\n </Avatar>\n <div className=\"flex min-w-0 flex-col text-left leading-tight group-data-[collapsible=icon]:hidden\">\n <span className=\"truncate text-sm font-medium\">{user.name}</span>\n {user.email && (\n <span className=\"truncate text-xs text-muted-foreground\">{user.email}</span>\n )}\n </div>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n );\n}\n\n// ─── AppSidebar ───────────────────────────────────────────────────────────────\n\nexport const AppSidebar = React.forwardRef<HTMLDivElement, AppSidebarProps>(function AppSidebar(\n { logo, logoIcon, groups, user, footer, bordered = true, className, ...sidebarProps },\n ref,\n) {\n const collapsible = sidebarProps.collapsible ?? \"offcanvas\";\n const canToggle = collapsible !== \"none\";\n\n // Borde desacoplado del modo de colapso: lo decide `bordered`, no `collapsible`.\n // El Sidebar trae el borde por defecto (variant sidebar) vía una clase con\n // modificador de `side`, así que para quitarlo hace falta `!important`.\n const borderClass = bordered ? \"\" : \"!border-l-0 !border-r-0\";\n\n return (\n <Sidebar ref={ref} className={cn(borderClass, className)} {...sidebarProps}>\n {(logo != null || logoIcon != null) && (\n <SidebarHeader className=\"px-0 py-6\">\n {/* Las dos marcas se superponen y hacen cross-fade. El isotipo se ancla\n a los primeros 64px (la franja de iconos) para aparecer ya en su\n posición final, sin desplazarse con la animación de ancho. */}\n <div className=\"relative h-8\">\n {/* Expandida: logo + trigger (sale con fade) */}\n <div className=\"absolute inset-0 flex items-center gap-3 pl-8 pr-2 transition-opacity duration-200 group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:opacity-0\">\n <div className=\"flex min-w-0 flex-1 items-center\">{logo}</div>\n {canToggle && (\n <SidebarTrigger\n aria-label=\"Colapsar barra lateral\"\n className=\"size-8 shrink-0 bg-transparent shadow-none\"\n />\n )}\n </div>\n {/* Colapsada a iconos: isotipo en posición final (entra con fade) */}\n {logoIcon != null && (\n <div className=\"pointer-events-none absolute inset-y-0 left-0 flex w-(--sidebar-width-icon) items-center justify-center opacity-0 transition-opacity duration-200 group-data-[collapsible=icon]:opacity-100\">\n {logoIcon}\n </div>\n )}\n </div>\n </SidebarHeader>\n )}\n\n <SidebarContent className=\"px-8 pb-6 group-data-[collapsible=icon]:px-4\">\n {groups.map((group, i) => (\n <Group key={group.label ?? `group-${i}`} group={group} />\n ))}\n </SidebarContent>\n\n {(footer != null || user != null) && (\n <SidebarFooter className=\"border-t border-sidebar-border px-8 py-3 group-data-[collapsible=icon]:px-4\">\n {footer != null ? footer : user != null ? <UserFooter user={user} /> : null}\n </SidebarFooter>\n )}\n </Sidebar>\n );\n});\n\nAppSidebar.displayName = \"AppSidebar\";\n"
20
20
  },
21
21
  {
22
22
  "path": "components/base/AppSidebar.md",
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/Avatar.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Avatar.tsx",
16
- "content": "import * as React from \"react\";\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Avatar = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <AvatarPrimitive.Root\n ref={ref}\n className={cn(\n \"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full\",\n className,\n )}\n {...props}\n />\n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Image>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n <AvatarPrimitive.Image\n ref={ref}\n className={cn(\"aspect-square h-full w-full object-cover\", className)}\n {...props}\n />\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Fallback>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n <AvatarPrimitive.Fallback\n ref={ref}\n className={cn(\n \"flex h-full w-full items-center justify-center rounded-full bg-secondary text-sm font-medium text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarImage, AvatarFallback };\n"
16
+ "content": "import * as AvatarPrimitive from \"@radix-ui/react-avatar\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Avatar = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <AvatarPrimitive.Root\n ref={ref}\n className={cn(\"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full\", className)}\n {...props}\n />\n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Image>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n <AvatarPrimitive.Image\n ref={ref}\n className={cn(\"aspect-square h-full w-full object-cover\", className)}\n {...props}\n />\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n React.ElementRef<typeof AvatarPrimitive.Fallback>,\n React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n <AvatarPrimitive.Fallback\n ref={ref}\n className={cn(\n \"flex h-full w-full items-center justify-center rounded-full bg-secondary text-sm font-medium text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarFallback, AvatarImage };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Avatar.md",
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/Badge.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Badge.tsx",
16
- "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst badgeVariants = cva(\n \"inline-flex h-5 items-center justify-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium leading-none whitespace-nowrap select-none\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground\",\n secondary: \"bg-secondary text-secondary-foreground\",\n destructive: \"bg-destructive text-destructive-foreground\",\n outline: \"border border-border bg-background text-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nexport interface BadgeProps\n extends React.HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof badgeVariants> {}\n\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n ({ className, variant, ...props }, ref) => (\n <span\n ref={ref}\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n />\n ),\n);\nBadge.displayName = \"Badge\";\n\nexport { Badge, badgeVariants };\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst badgeVariants = cva(\n \"inline-flex h-5 items-center justify-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium leading-none whitespace-nowrap select-none\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground\",\n secondary: \"bg-secondary text-secondary-foreground\",\n destructive: \"bg-destructive text-destructive-foreground\",\n outline: \"border border-border bg-background text-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nexport interface BadgeProps\n extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof badgeVariants> {}\n\nconst Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(\n ({ className, variant, ...props }, ref) => (\n <span ref={ref} className={cn(badgeVariants({ variant }), className)} {...props} />\n ),\n);\nBadge.displayName = \"Badge\";\n\nexport { Badge, badgeVariants };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Badge.md",
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/Breadcrumb.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Breadcrumb.tsx",
16
- "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Breadcrumb = React.forwardRef<\n HTMLElement,\n React.ComponentPropsWithoutRef<\"nav\">\n>((props, ref) => <nav ref={ref} aria-label='breadcrumb' {...props} />);\nBreadcrumb.displayName = \"Breadcrumb\";\n\nconst BreadcrumbList = React.forwardRef<\n HTMLOListElement,\n React.ComponentPropsWithoutRef<\"ol\">\n>(({ className, ...props }, ref) => (\n <ol\n ref={ref}\n className={cn(\n \"flex flex-wrap items-center gap-2 break-words text-sm text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nBreadcrumbList.displayName = \"BreadcrumbList\";\n\nconst BreadcrumbItem = React.forwardRef<\n HTMLLIElement,\n React.ComponentPropsWithoutRef<\"li\">\n>(({ className, ...props }, ref) => (\n <li\n ref={ref}\n className={cn(\"inline-flex items-center gap-2\", className)}\n {...props}\n />\n));\nBreadcrumbItem.displayName = \"BreadcrumbItem\";\n\nconst BreadcrumbLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentPropsWithoutRef<\"a\"> & {\n /** Renderiza el hijo como el enlace (patrón Slot): `<BreadcrumbLink asChild><Link …/></BreadcrumbLink>`. */\n asChild?: boolean;\n }\n>(({ asChild = false, className, ...props }, ref) => {\n const Comp = asChild ? Slot : \"a\";\n return (\n <Comp\n ref={ref}\n className={cn(\n \"rounded text-primary transition-colors hover:text-primary/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n className,\n )}\n {...props}\n />\n );\n});\nBreadcrumbLink.displayName = \"BreadcrumbLink\";\n\nconst BreadcrumbPage = React.forwardRef<\n HTMLSpanElement,\n React.ComponentPropsWithoutRef<\"span\">\n>(({ className, ...props }, ref) => (\n <span\n ref={ref}\n role='link'\n aria-disabled='true'\n aria-current='page'\n className={cn(\"font-medium text-foreground\", className)}\n {...props}\n />\n));\nBreadcrumbPage.displayName = \"BreadcrumbPage\";\n\nconst BreadcrumbSeparator = ({\n children,\n className,\n ...props\n}: React.ComponentProps<\"li\">) => (\n <li\n role='presentation'\n aria-hidden='true'\n className={cn(\"flex items-center text-muted-foreground [&>svg]:size-4\", className)}\n {...props}>\n {children ?? (\n <svg\n xmlns='http://www.w3.org/2000/svg'\n width='24'\n height='24'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'>\n <path d='m9 18 6-6-6-6' />\n </svg>\n )}\n </li>\n);\nBreadcrumbSeparator.displayName = \"BreadcrumbSeparator\";\n\nconst BreadcrumbEllipsis = ({\n className,\n ...props\n}: React.ComponentProps<\"span\">) => (\n <span\n role='presentation'\n aria-hidden='true'\n className={cn(\"flex items-center text-muted-foreground\", className)}\n {...props}>\n <svg\n xmlns='http://www.w3.org/2000/svg'\n width='24'\n height='24'\n viewBox='0 0 24 24'\n fill='currentColor'\n className='size-4'>\n <circle cx='12' cy='12' r='1' />\n <circle cx='19' cy='12' r='1' />\n <circle cx='5' cy='12' r='1' />\n </svg>\n <span className='sr-only'>More</span>\n </span>\n);\nBreadcrumbEllipsis.displayName = \"BreadcrumbEllipsis\";\n\nexport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n};\n"
16
+ "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Breadcrumb = React.forwardRef<HTMLElement, React.ComponentPropsWithoutRef<\"nav\">>(\n (props, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />,\n);\nBreadcrumb.displayName = \"Breadcrumb\";\n\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<\"ol\">>(\n ({ className, ...props }, ref) => (\n <ol\n ref={ref}\n className={cn(\n \"flex flex-wrap items-center gap-2 break-words text-sm text-muted-foreground\",\n className,\n )}\n {...props}\n />\n ),\n);\nBreadcrumbList.displayName = \"BreadcrumbList\";\n\nconst BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<\"li\">>(\n ({ className, ...props }, ref) => (\n <li ref={ref} className={cn(\"inline-flex items-center gap-2\", className)} {...props} />\n ),\n);\nBreadcrumbItem.displayName = \"BreadcrumbItem\";\n\nconst BreadcrumbLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentPropsWithoutRef<\"a\"> & {\n /** Renderiza el hijo como el enlace (patrón Slot): `<BreadcrumbLink asChild><Link …/></BreadcrumbLink>`. */\n asChild?: boolean;\n }\n>(({ asChild = false, className, ...props }, ref) => {\n const Comp = asChild ? Slot : \"a\";\n return (\n <Comp\n ref={ref}\n className={cn(\n \"rounded text-primary transition-colors hover:text-primary/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n className,\n )}\n {...props}\n />\n );\n});\nBreadcrumbLink.displayName = \"BreadcrumbLink\";\n\nconst BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<\"span\">>(\n ({ className, ...props }, ref) => (\n <span\n ref={ref}\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn(\"font-medium text-foreground\", className)}\n {...props}\n />\n ),\n);\nBreadcrumbPage.displayName = \"BreadcrumbPage\";\n\nconst BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<\"li\">) => (\n <li\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\"flex items-center text-muted-foreground [&>svg]:size-4\", className)}\n {...props}\n >\n {children ?? (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n )}\n </li>\n);\nBreadcrumbSeparator.displayName = \"BreadcrumbSeparator\";\n\nconst BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<\"span\">) => (\n <span\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\"flex items-center text-muted-foreground\", className)}\n {...props}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n className=\"size-4\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"1\" />\n <circle cx=\"19\" cy=\"12\" r=\"1\" />\n <circle cx=\"5\" cy=\"12\" r=\"1\" />\n </svg>\n <span className=\"sr-only\">More</span>\n </span>\n);\nBreadcrumbEllipsis.displayName = \"BreadcrumbEllipsis\";\n\nexport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n};\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Breadcrumb.md",
@@ -16,7 +16,7 @@
16
16
  "path": "components/base/ButtonGroup.tsx",
17
17
  "type": "registry:ui",
18
18
  "target": "components/base/ButtonGroup.tsx",
19
- "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Separator } from \"./Separator\";\n\nconst buttonGroupVariants = cva(\n \"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:relative [&>*]:hover:z-10 [&>*]:focus-visible:z-10 [&>input]:flex-1\",\n {\n variants: {\n orientation: {\n horizontal:\n \"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px [&>[data-slot=button-group-separator]]:!ml-0 [&>[data-slot=button-group-separator]+*]:!ml-0 [&>[data-slot=button-group-separator]+*]:border-l-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-r-0\",\n vertical:\n \"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none [&>*:not(:first-child)]:-mt-px [&>[data-slot=button-group-separator]]:!mt-0 [&>[data-slot=button-group-separator]+*]:!mt-0 [&>[data-slot=button-group-separator]+*]:border-t-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-b-0\",\n },\n },\n defaultVariants: {\n orientation: \"horizontal\",\n },\n },\n);\n\nexport interface ButtonGroupProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof buttonGroupVariants> {}\n\nconst ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(\n ({ className, orientation = \"horizontal\", ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n data-slot=\"button-group\"\n data-orientation={orientation}\n className={cn(buttonGroupVariants({ orientation }), className)}\n {...props}\n />\n ),\n);\nButtonGroup.displayName = \"ButtonGroup\";\n\nexport interface ButtonGroupSeparatorProps\n extends React.ComponentPropsWithoutRef<typeof Separator> {}\n\nconst ButtonGroupSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n ButtonGroupSeparatorProps\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n <Separator\n ref={ref}\n decorative\n orientation={orientation}\n data-slot=\"button-group-separator\"\n className={cn(\n orientation === \"vertical\"\n ? \"mx-0 h-auto self-stretch\"\n : \"my-0 w-auto self-stretch\",\n className,\n )}\n {...props}\n />\n));\nButtonGroupSeparator.displayName = \"ButtonGroupSeparator\";\n\nexport interface ButtonGroupTextProps\n extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n}\n\nconst ButtonGroupText = React.forwardRef<HTMLDivElement, ButtonGroupTextProps>(\n ({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-slot=\"button-group-text\"\n className={cn(\n \"flex h-10 items-center whitespace-nowrap rounded border border-border bg-background px-3 text-sm font-medium text-muted-foreground\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nButtonGroupText.displayName = \"ButtonGroupText\";\n\nexport {\n ButtonGroup,\n ButtonGroupSeparator,\n ButtonGroupText,\n buttonGroupVariants,\n};\n"
19
+ "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Separator } from \"./Separator\";\n\nconst buttonGroupVariants = cva(\n \"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:relative [&>*]:hover:z-10 [&>*]:focus-visible:z-10 [&>input]:flex-1\",\n {\n variants: {\n orientation: {\n horizontal:\n \"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px [&>[data-slot=button-group-separator]]:!ml-0 [&>[data-slot=button-group-separator]+*]:!ml-0 [&>[data-slot=button-group-separator]+*]:border-l-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-r-0\",\n vertical:\n \"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none [&>*:not(:first-child)]:-mt-px [&>[data-slot=button-group-separator]]:!mt-0 [&>[data-slot=button-group-separator]+*]:!mt-0 [&>[data-slot=button-group-separator]+*]:border-t-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-b-0\",\n },\n },\n defaultVariants: {\n orientation: \"horizontal\",\n },\n },\n);\n\nexport interface ButtonGroupProps\n extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof buttonGroupVariants> {}\n\nconst ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(\n ({ className, orientation = \"horizontal\", ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n data-slot=\"button-group\"\n data-orientation={orientation}\n className={cn(buttonGroupVariants({ orientation }), className)}\n {...props}\n />\n ),\n);\nButtonGroup.displayName = \"ButtonGroup\";\n\nexport type ButtonGroupSeparatorProps = React.ComponentPropsWithoutRef<typeof Separator>;\n\nconst ButtonGroupSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n ButtonGroupSeparatorProps\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n <Separator\n ref={ref}\n decorative\n orientation={orientation}\n data-slot=\"button-group-separator\"\n className={cn(\n orientation === \"vertical\" ? \"mx-0 h-auto self-stretch\" : \"my-0 w-auto self-stretch\",\n className,\n )}\n {...props}\n />\n));\nButtonGroupSeparator.displayName = \"ButtonGroupSeparator\";\n\nexport interface ButtonGroupTextProps extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n}\n\nconst ButtonGroupText = React.forwardRef<HTMLDivElement, ButtonGroupTextProps>(\n ({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-slot=\"button-group-text\"\n className={cn(\n \"flex h-10 items-center whitespace-nowrap rounded border border-border bg-background px-3 text-sm font-medium text-muted-foreground\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nButtonGroupText.displayName = \"ButtonGroupText\";\n\nexport { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants };\n"
20
20
  },
21
21
  {
22
22
  "path": "components/base/ButtonGroup.md",
@@ -14,7 +14,7 @@
14
14
  "path": "components/base/Button.tsx",
15
15
  "type": "registry:ui",
16
16
  "target": "components/base/Button.tsx",
17
- "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap select-none rounded text-sm font-medium transition-colors duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80\",\n outline:\n \"border border-border bg-background text-foreground hover:border-primary hover:text-primary active:bg-accent active:text-accent-foreground\",\n secondary:\n \"bg-background text-secondary-foreground hover:bg-muted active:bg-accent active:text-accent-foreground\",\n ghost: \"text-secondary-foreground hover:bg-muted active:bg-muted\",\n link: \"text-primary underline-offset-4 hover:underline active:text-primary/80\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-8 px-3\",\n lg: \"h-12 px-5\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n /** Renderiza el hijo como el elemento raíz (patrón Slot de Radix): `<Button asChild><Link …/></Button>`. */\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
17
+ "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap select-none rounded text-sm font-medium transition-colors duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80\",\n outline:\n \"border border-border bg-background text-foreground hover:border-primary hover:text-primary active:bg-accent active:text-accent-foreground\",\n secondary:\n \"bg-background text-secondary-foreground hover:bg-muted active:bg-accent active:text-accent-foreground\",\n ghost: \"text-secondary-foreground hover:bg-muted active:bg-muted\",\n link: \"text-primary underline-offset-4 hover:underline active:text-primary/80\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-8 px-3\",\n lg: \"h-12 px-5\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Renderiza el hijo como el elemento raíz (patrón Slot de Radix): `<Button asChild><Link …/></Button>`. */\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
18
18
  },
19
19
  {
20
20
  "path": "components/base/Button.md",
@@ -16,7 +16,7 @@
16
16
  "path": "components/base/Calendar.tsx",
17
17
  "type": "registry:ui",
18
18
  "target": "components/base/Calendar.tsx",
19
- "content": "import * as React from \"react\";\nimport {\n DayPicker,\n getDefaultClassNames,\n type DayButton,\n type Locale,\n} from \"react-day-picker\";\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n ChevronDownIcon,\n} from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Button, buttonVariants } from \"./Button\";\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = \"label\",\n buttonVariant = \"ghost\",\n locale,\n formatters,\n components,\n ...props\n}: React.ComponentProps<typeof DayPicker> & {\n buttonVariant?: React.ComponentProps<typeof Button>[\"variant\"];\n}) {\n const defaultClassNames = getDefaultClassNames();\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\n \"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent\",\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className,\n )}\n captionLayout={captionLayout}\n locale={locale}\n formatters={{\n formatMonthDropdown: (date) =>\n date.toLocaleString(locale?.code, { month: \"short\" }),\n ...formatters,\n }}\n classNames={{\n root: cn(\"w-fit\", defaultClassNames.root),\n months: cn(\n \"relative flex flex-col gap-4 md:flex-row\",\n defaultClassNames.months,\n ),\n month: cn(\"flex w-full flex-col gap-4\", defaultClassNames.month),\n nav: cn(\n \"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1\",\n defaultClassNames.nav,\n ),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_previous,\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_next,\n ),\n month_caption: cn(\n \"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)\",\n defaultClassNames.month_caption,\n ),\n dropdowns: cn(\n \"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium\",\n defaultClassNames.dropdowns,\n ),\n dropdown_root: cn(\n \"cn-calendar-dropdown-root relative rounded-(--cell-radius)\",\n defaultClassNames.dropdown_root,\n ),\n dropdown: cn(\n \"absolute inset-0 bg-popover opacity-0\",\n defaultClassNames.dropdown,\n ),\n caption_label: cn(\n \"font-medium select-none\",\n captionLayout === \"label\"\n ? \"cn-calendar-caption text-sm\"\n : \"cn-calendar-caption-label flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground\",\n defaultClassNames.caption_label,\n ),\n table: \"w-full border-collapse\",\n weekdays: cn(\"flex\", defaultClassNames.weekdays),\n weekday: cn(\n \"flex-1 rounded-(--cell-radius) text-xs font-normal text-muted-foreground select-none\",\n defaultClassNames.weekday,\n ),\n week: cn(\"mt-2 flex w-full\", defaultClassNames.week),\n week_number_header: cn(\n \"w-(--cell-size) select-none\",\n defaultClassNames.week_number_header,\n ),\n week_number: cn(\n \"text-xs text-muted-foreground select-none\",\n defaultClassNames.week_number,\n ),\n day: cn(\n \"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)\",\n props.showWeekNumber\n ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)\"\n : \"[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)\",\n defaultClassNames.day,\n ),\n range_start: cn(\n \"relative isolate z-0 rounded-l-(--cell-radius) bg-background after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-background\",\n defaultClassNames.range_start,\n ),\n range_middle: cn(\n \"rounded-none bg-background\",\n defaultClassNames.range_middle,\n ),\n range_end: cn(\n \"relative isolate z-0 rounded-r-(--cell-radius) bg-background after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-background\",\n defaultClassNames.range_end,\n ),\n today: cn(\n \"rounded-(--cell-radius) bg-background text-foreground data-[selected=true]:rounded-none\",\n defaultClassNames.today,\n ),\n outside: cn(\n \"text-muted-foreground aria-selected:text-muted-foreground\",\n defaultClassNames.outside,\n ),\n disabled: cn(\n \"text-muted-foreground opacity-50\",\n defaultClassNames.disabled,\n ),\n hidden: cn(\"invisible\", defaultClassNames.hidden),\n ...classNames,\n }}\n components={{\n Root: ({ className, rootRef, ...props }) => {\n return (\n <div\n data-slot=\"calendar\"\n ref={rootRef}\n className={cn(className)}\n {...props}\n />\n );\n },\n Chevron: ({ className, orientation, ...props }) => {\n if (orientation === \"left\") {\n return (\n <ChevronLeftIcon\n className={cn(\"cn-rtl-flip size-4\", className)}\n {...props}\n />\n );\n }\n\n if (orientation === \"right\") {\n return (\n <ChevronRightIcon\n className={cn(\"cn-rtl-flip size-4\", className)}\n {...props}\n />\n );\n }\n\n return (\n <ChevronDownIcon className={cn(\"size-4\", className)} {...props} />\n );\n },\n DayButton: ({ ...props }) => (\n <CalendarDayButton locale={locale} {...props} />\n ),\n WeekNumber: ({ children, ...props }) => {\n return (\n <td {...props}>\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">\n {children}\n </div>\n </td>\n );\n },\n ...components,\n }}\n {...props}\n />\n );\n}\n\nfunction CalendarDayButton({\n className,\n day,\n modifiers,\n locale,\n ...props\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {\n const defaultClassNames = getDefaultClassNames();\n\n const ref = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n <Button\n ref={ref}\n variant=\"ghost\"\n size=\"icon\"\n data-day={day.date.toLocaleDateString(locale?.code)}\n data-selected-single={\n modifiers.selected &&\n !modifiers.range_start &&\n !modifiers.range_end &&\n !modifiers.range_middle\n }\n data-range-start={modifiers.range_start}\n data-range-end={modifiers.range_end}\n data-range-middle={modifiers.range_middle}\n className={cn(\n // Sin ring por data-focused: ese estado se marca al abrir aunque sea con\n // mouse. El foco de teclado lo cubre el focus-visible:ring del Button.\n \"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal focus-visible:z-10 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70\",\n defaultClassNames.day,\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Calendar, CalendarDayButton };\n"
19
+ "content": "import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { type DayButton, DayPicker, getDefaultClassNames, type Locale } from \"react-day-picker\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button, buttonVariants } from \"./Button\";\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = \"label\",\n buttonVariant = \"ghost\",\n locale,\n formatters,\n components,\n ...props\n}: React.ComponentProps<typeof DayPicker> & {\n buttonVariant?: React.ComponentProps<typeof Button>[\"variant\"];\n}) {\n const defaultClassNames = getDefaultClassNames();\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\n \"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent\",\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className,\n )}\n captionLayout={captionLayout}\n locale={locale}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: \"short\" }),\n ...formatters,\n }}\n classNames={{\n root: cn(\"w-fit\", defaultClassNames.root),\n months: cn(\"relative flex flex-col gap-4 md:flex-row\", defaultClassNames.months),\n month: cn(\"flex w-full flex-col gap-4\", defaultClassNames.month),\n nav: cn(\n \"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1\",\n defaultClassNames.nav,\n ),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_previous,\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_next,\n ),\n month_caption: cn(\n \"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)\",\n defaultClassNames.month_caption,\n ),\n dropdowns: cn(\n \"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium\",\n defaultClassNames.dropdowns,\n ),\n dropdown_root: cn(\n \"cn-calendar-dropdown-root relative rounded-(--cell-radius)\",\n defaultClassNames.dropdown_root,\n ),\n dropdown: cn(\"absolute inset-0 bg-popover opacity-0\", defaultClassNames.dropdown),\n caption_label: cn(\n \"font-medium select-none\",\n captionLayout === \"label\"\n ? \"cn-calendar-caption text-sm\"\n : \"cn-calendar-caption-label flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground\",\n defaultClassNames.caption_label,\n ),\n table: \"w-full border-collapse\",\n weekdays: cn(\"flex\", defaultClassNames.weekdays),\n weekday: cn(\n \"flex-1 rounded-(--cell-radius) text-xs font-normal text-muted-foreground select-none\",\n defaultClassNames.weekday,\n ),\n week: cn(\"mt-2 flex w-full\", defaultClassNames.week),\n week_number_header: cn(\"w-(--cell-size) select-none\", defaultClassNames.week_number_header),\n week_number: cn(\"text-xs text-muted-foreground select-none\", defaultClassNames.week_number),\n day: cn(\n \"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)\",\n props.showWeekNumber\n ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)\"\n : \"[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)\",\n defaultClassNames.day,\n ),\n range_start: cn(\n \"relative isolate z-0 rounded-l-(--cell-radius) bg-background after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-background\",\n defaultClassNames.range_start,\n ),\n range_middle: cn(\"rounded-none bg-background\", defaultClassNames.range_middle),\n range_end: cn(\n \"relative isolate z-0 rounded-r-(--cell-radius) bg-background after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-background\",\n defaultClassNames.range_end,\n ),\n today: cn(\n \"rounded-(--cell-radius) bg-background text-foreground data-[selected=true]:rounded-none\",\n defaultClassNames.today,\n ),\n outside: cn(\n \"text-muted-foreground aria-selected:text-muted-foreground\",\n defaultClassNames.outside,\n ),\n disabled: cn(\"text-muted-foreground opacity-50\", defaultClassNames.disabled),\n hidden: cn(\"invisible\", defaultClassNames.hidden),\n ...classNames,\n }}\n components={{\n Root: ({ className, rootRef, ...props }) => {\n return <div data-slot=\"calendar\" ref={rootRef} className={cn(className)} {...props} />;\n },\n Chevron: ({ className, orientation, ...props }) => {\n if (orientation === \"left\") {\n return <ChevronLeftIcon className={cn(\"cn-rtl-flip size-4\", className)} {...props} />;\n }\n\n if (orientation === \"right\") {\n return <ChevronRightIcon className={cn(\"cn-rtl-flip size-4\", className)} {...props} />;\n }\n\n return <ChevronDownIcon className={cn(\"size-4\", className)} {...props} />;\n },\n DayButton: ({ ...props }) => <CalendarDayButton locale={locale} {...props} />,\n WeekNumber: ({ children, ...props }) => {\n return (\n <td {...props}>\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">\n {children}\n </div>\n </td>\n );\n },\n ...components,\n }}\n {...props}\n />\n );\n}\n\nfunction CalendarDayButton({\n className,\n day,\n modifiers,\n locale,\n ...props\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {\n const defaultClassNames = getDefaultClassNames();\n\n const ref = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n <Button\n ref={ref}\n variant=\"ghost\"\n size=\"icon\"\n data-day={day.date.toLocaleDateString(locale?.code)}\n data-selected-single={\n modifiers.selected &&\n !modifiers.range_start &&\n !modifiers.range_end &&\n !modifiers.range_middle\n }\n data-range-start={modifiers.range_start}\n data-range-end={modifiers.range_end}\n data-range-middle={modifiers.range_middle}\n className={cn(\n // Sin ring por data-focused: ese estado se marca al abrir aunque sea con\n // mouse. El foco de teclado lo cubre el focus-visible:ring del Button.\n \"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal focus-visible:z-10 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70\",\n defaultClassNames.day,\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Calendar, CalendarDayButton };\n"
20
20
  },
21
21
  {
22
22
  "path": "components/base/Calendar.md",
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/Card.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Card.tsx",
16
- "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst cardVariants = cva(\n \"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-lg bg-background py-(--card-spacing) text-sm text-card-foreground shadow-sm ring-1 ring-border has-[>img:first-child]:pt-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg\",\n {\n variants: {\n size: {\n default: \"[--card-spacing:--spacing(6)]\",\n sm: \"[--card-spacing:--spacing(4)]\",\n },\n },\n defaultVariants: { size: \"default\" },\n },\n);\n\nexport interface CardProps\n extends React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof cardVariants> {}\n\nconst Card = React.forwardRef<HTMLDivElement, CardProps>(\n ({ className, size, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card\"\n data-size={size ?? \"default\"}\n className={cn(cardVariants({ size }), className)}\n {...props}\n />\n ),\n);\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-header\"\n className={cn(\n \"@container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)\",\n className,\n )}\n {...props}\n />\n));\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-title\"\n className={cn(\n \"text-base font-medium leading-normal text-foreground group-data-[size=sm]/card:text-sm\",\n className,\n )}\n {...props}\n />\n));\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nCardDescription.displayName = \"CardDescription\";\n\nconst CardAction = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-action\"\n className={cn(\n \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n className,\n )}\n {...props}\n />\n));\nCardAction.displayName = \"CardAction\";\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-content\"\n className={cn(\"px-(--card-spacing)\", className)}\n {...props}\n />\n));\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-footer\"\n className={cn(\n \"flex items-center rounded-b-lg px-(--card-spacing) [.border-t]:pt-(--card-spacing)\",\n className,\n )}\n {...props}\n />\n));\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardAction,\n CardDescription,\n CardContent,\n cardVariants,\n};\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst cardVariants = cva(\n \"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-lg bg-background py-(--card-spacing) text-sm text-card-foreground shadow-sm ring-1 ring-border has-[>img:first-child]:pt-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg\",\n {\n variants: {\n size: {\n default: \"[--card-spacing:--spacing(6)]\",\n sm: \"[--card-spacing:--spacing(4)]\",\n },\n },\n defaultVariants: { size: \"default\" },\n },\n);\n\nexport interface CardProps\n extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof cardVariants> {}\n\nconst Card = React.forwardRef<HTMLDivElement, CardProps>(({ className, size, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card\"\n data-size={size ?? \"default\"}\n className={cn(cardVariants({ size }), className)}\n {...props}\n />\n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-header\"\n className={cn(\n \"@container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-title\"\n className={cn(\n \"text-base font-medium leading-normal text-foreground group-data-[size=sm]/card:text-sm\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n ),\n);\nCardDescription.displayName = \"CardDescription\";\n\nconst CardAction = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-action\"\n className={cn(\"col-start-2 row-span-2 row-start-1 self-start justify-self-end\", className)}\n {...props}\n />\n ),\n);\nCardAction.displayName = \"CardAction\";\n\nconst CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-content\"\n className={cn(\"px-(--card-spacing)\", className)}\n {...props}\n />\n ),\n);\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-footer\"\n className={cn(\n \"flex items-center rounded-b-lg px-(--card-spacing) [.border-t]:pt-(--card-spacing)\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n Card,\n CardAction,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n cardVariants,\n};\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Card.md",
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/Chart.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Chart.tsx",
16
- "content": "import * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\n// Equivalen a los tipos internos de recharts (ValueType/NameType), que en la\n// 2.15.x no se re-exportan desde la entrada pública del paquete.\ntype TooltipValueType = number | string | Array<number | string>;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n string,\n {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<keyof typeof THEMES, string> }\n )\n>;\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error(\"useChart must be used within a <ChartContainer />\");\n }\n\n return context;\n}\n\nfunction ChartContainer({\n id,\n className,\n children,\n config,\n initialDimension = INITIAL_DIMENSION,\n ...props\n}: React.ComponentProps<\"div\"> & {\n config: ChartConfig;\n children: React.ComponentProps<\n typeof RechartsPrimitive.ResponsiveContainer\n >[\"children\"];\n initialDimension?: {\n width: number;\n height: number;\n };\n}) {\n const uniqueId = React.useId();\n const chartId = `chart-${id ?? uniqueId.replace(/:/g, \"\")}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer\n initialDimension={initialDimension}\n >\n {children}\n </RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n const colorConfig = Object.entries(config).filter(\n ([, config]) => config.theme ?? config.color,\n );\n\n if (!colorConfig.length) {\n return null;\n }\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(\n ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n .map(([key, itemConfig]) => {\n const color =\n itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??\n itemConfig.color;\n return color ? ` --color-${key}: ${color};` : null;\n })\n .join(\"\\n\")}\n}\n`,\n )\n .join(\"\\n\"),\n }}\n />\n );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n active,\n payload,\n className,\n indicator = \"dot\",\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n React.ComponentProps<\"div\"> & {\n hideLabel?: boolean;\n hideIndicator?: boolean;\n indicator?: \"line\" | \"dot\" | \"dashed\";\n nameKey?: string;\n labelKey?: string;\n } & Omit<\n RechartsPrimitive.DefaultTooltipContentProps<\n TooltipValueType,\n TooltipNameType\n >,\n \"accessibilityLayer\"\n >) {\n const { config } = useChart();\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) {\n return null;\n }\n\n const [item] = payload;\n const key = `${labelKey ?? item?.dataKey ?? item?.name ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const value =\n !labelKey && typeof label === \"string\"\n ? (config[label]?.label ?? label)\n : itemConfig?.label;\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\", labelClassName)}>\n {labelFormatter(value, payload)}\n </div>\n );\n }\n\n if (!value) {\n return null;\n }\n\n return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n }, [\n label,\n labelFormatter,\n payload,\n hideLabel,\n labelClassName,\n config,\n labelKey,\n ]);\n\n if (!active || !payload?.length) {\n return null;\n }\n\n const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n return (\n <div\n className={cn(\n \"grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-md\",\n className,\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.name ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n return (\n <div\n key={index}\n className={cn(\n \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n indicator === \"dot\" && \"items-center\",\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, item.payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn(\n \"shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)\",\n {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border border-dashed bg-transparent\":\n indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\",\n },\n )}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor,\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\n \"flex flex-1 justify-between leading-none\",\n nestLabel ? \"items-end\" : \"items-center\",\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-muted-foreground\">\n {itemConfig?.label ?? item.name}\n </span>\n </div>\n {item.value != null && (\n <span className=\"font-mono font-medium text-foreground tabular-nums\">\n {typeof item.value === \"number\"\n ? item.value.toLocaleString()\n : String(item.value)}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n className,\n hideIcon = false,\n payload,\n verticalAlign = \"bottom\",\n nameKey,\n}: React.ComponentProps<\"div\"> & {\n hideIcon?: boolean;\n nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div\n className={cn(\n \"flex items-center justify-center gap-4\",\n verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n className,\n )}\n >\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n return (\n <div\n key={index}\n className={cn(\n \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\",\n )}\n >\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div\n className=\"h-2 w-2 shrink-0 rounded-xs\"\n style={{\n backgroundColor: item.color,\n }}\n />\n )}\n {itemConfig?.label}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction getPayloadConfigFromPayload(\n config: ChartConfig,\n payload: unknown,\n key: string,\n) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined;\n }\n\n const payloadPayload =\n \"payload\" in payload &&\n typeof payload.payload === \"object\" &&\n payload.payload !== null\n ? payload.payload\n : undefined;\n\n let configLabelKey: string = key;\n\n if (\n key in payload &&\n typeof payload[key as keyof typeof payload] === \"string\"\n ) {\n configLabelKey = payload[key as keyof typeof payload] as string;\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[\n key as keyof typeof payloadPayload\n ] as string;\n }\n\n return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n};\n"
16
+ "content": "import * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\n// Equivalen a los tipos internos de recharts (ValueType/NameType), que en la\n// 2.15.x no se re-exportan desde la entrada pública del paquete.\ntype TooltipValueType = number | string | Array<number | string>;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n string,\n {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<keyof typeof THEMES, string> }\n )\n>;\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error(\"useChart must be used within a <ChartContainer />\");\n }\n\n return context;\n}\n\nfunction ChartContainer({\n id,\n className,\n children,\n config,\n initialDimension = INITIAL_DIMENSION,\n ...props\n}: React.ComponentProps<\"div\"> & {\n config: ChartConfig;\n children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>[\"children\"];\n initialDimension?: {\n width: number;\n height: number;\n };\n}) {\n const uniqueId = React.useId();\n const chartId = `chart-${id ?? uniqueId.replace(/:/g, \"\")}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer initialDimension={initialDimension}>\n {children}\n </RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n const colorConfig = Object.entries(config).filter(([, config]) => config.theme ?? config.color);\n\n if (!colorConfig.length) {\n return null;\n }\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(\n ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n .map(([key, itemConfig]) => {\n const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ?? itemConfig.color;\n return color ? ` --color-${key}: ${color};` : null;\n })\n .join(\"\\n\")}\n}\n`,\n )\n .join(\"\\n\"),\n }}\n />\n );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n active,\n payload,\n className,\n indicator = \"dot\",\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n React.ComponentProps<\"div\"> & {\n hideLabel?: boolean;\n hideIndicator?: boolean;\n indicator?: \"line\" | \"dot\" | \"dashed\";\n nameKey?: string;\n labelKey?: string;\n } & Omit<\n RechartsPrimitive.DefaultTooltipContentProps<TooltipValueType, TooltipNameType>,\n \"accessibilityLayer\"\n >) {\n const { config } = useChart();\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) {\n return null;\n }\n\n const [item] = payload;\n const key = `${labelKey ?? item?.dataKey ?? item?.name ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const value =\n !labelKey && typeof label === \"string\" ? (config[label]?.label ?? label) : itemConfig?.label;\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>\n );\n }\n\n if (!value) {\n return null;\n }\n\n return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n if (!active || !payload?.length) {\n return null;\n }\n\n const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n return (\n <div\n className={cn(\n \"grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-md\",\n className,\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.name ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n return (\n <div\n key={index}\n className={cn(\n \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n indicator === \"dot\" && \"items-center\",\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, item.payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn(\n \"shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)\",\n {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border border-dashed bg-transparent\": indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\",\n },\n )}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor,\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\n \"flex flex-1 justify-between leading-none\",\n nestLabel ? \"items-end\" : \"items-center\",\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-muted-foreground\">\n {itemConfig?.label ?? item.name}\n </span>\n </div>\n {item.value != null && (\n <span className=\"font-mono font-medium text-foreground tabular-nums\">\n {typeof item.value === \"number\"\n ? item.value.toLocaleString()\n : String(item.value)}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n className,\n hideIcon = false,\n payload,\n verticalAlign = \"bottom\",\n nameKey,\n}: React.ComponentProps<\"div\"> & {\n hideIcon?: boolean;\n nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div\n className={cn(\n \"flex items-center justify-center gap-4\",\n verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n className,\n )}\n >\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n return (\n <div\n key={index}\n className={cn(\n \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\",\n )}\n >\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div\n className=\"h-2 w-2 shrink-0 rounded-xs\"\n style={{\n backgroundColor: item.color,\n }}\n />\n )}\n {itemConfig?.label}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined;\n }\n\n const payloadPayload =\n \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n ? payload.payload\n : undefined;\n\n let configLabelKey: string = key;\n\n if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n configLabelKey = payload[key as keyof typeof payload] as string;\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n }\n\n return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n ChartTooltip,\n ChartTooltipContent,\n};\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Chart.md",
@@ -13,7 +13,7 @@
13
13
  "path": "components/base/Checkbox.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Checkbox.tsx",
16
- "content": "import * as React from \"react\";\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"peer h-4 w-4 shrink-0 cursor-pointer rounded border border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n className,\n )}\n {...props}>\n <CheckboxPrimitive.Indicator\n className={cn(\"flex items-center justify-center text-current\")}>\n <svg\n className='h-4 w-4'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='3'\n strokeLinecap='round'\n strokeLinejoin='round'>\n <path d='M20 6 9 17l-5-5' />\n </svg>\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n));\nCheckbox.displayName = CheckboxPrimitive.Root.displayName;\n\nexport { Checkbox };\n"
16
+ "content": "import * as CheckboxPrimitive from \"@radix-ui/react-checkbox\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"peer h-4 w-4 shrink-0 cursor-pointer rounded border border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground\",\n className,\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator className={cn(\"flex items-center justify-center text-current\")}>\n <svg\n className=\"h-4 w-4\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n));\nCheckbox.displayName = CheckboxPrimitive.Root.displayName;\n\nexport { Checkbox };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Checkbox.md",
@@ -15,7 +15,7 @@
15
15
  "path": "components/base/Combobox.tsx",
16
16
  "type": "registry:ui",
17
17
  "target": "components/base/Combobox.tsx",
18
- "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Button } from \"./Button\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./Command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./Popover\";\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n}\n\n/**\n * Wrapper ergonómico sobre la receta compuesta Popover + Command. Si necesitas\n * variar la estructura (grupos, ítems con iconos, multi-selección, creación de\n * opciones), compón las piezas directamente — ver \"Receta compuesta\" en\n * Combobox.md.\n */\nexport interface ComboboxProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof Button>,\n \"value\" | \"defaultValue\" | \"onChange\"\n > {\n options: ComboboxOption[];\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyMessage?: string;\n}\n\nconst Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(\n function Combobox(\n {\n options,\n value,\n defaultValue,\n onValueChange,\n placeholder = \"Seleccionar…\",\n searchPlaceholder = \"Buscar…\",\n emptyMessage = \"Sin resultados\",\n className,\n ...props\n },\n ref,\n ) {\n const [open, setOpen] = React.useState(false);\n const [internal, setInternal] = React.useState(defaultValue ?? \"\");\n const current = value ?? internal;\n const selected = options.find((option) => option.value === current);\n\n const handleSelect = (next: string) => {\n const resolved = next === current ? \"\" : next;\n setInternal(resolved);\n onValueChange?.(resolved);\n setOpen(false);\n };\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n ref={ref}\n variant='outline'\n role='combobox'\n aria-expanded={open}\n className={cn(\"w-full justify-between font-normal\", className)}\n {...props}>\n {selected ? (\n selected.label\n ) : (\n <span className='text-muted-foreground'>{placeholder}</span>\n )}\n <svg\n className='ml-2 h-4 w-4 shrink-0 opacity-50'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'>\n <path d='m7 15 5 5 5-5M7 9l5-5 5 5' />\n </svg>\n </Button>\n </PopoverTrigger>\n <PopoverContent\n align='start'\n className='w-[var(--radix-popover-trigger-width)] p-0'>\n <Command>\n <CommandInput placeholder={searchPlaceholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => (\n <CommandItem\n key={option.value}\n value={option.label}\n onSelect={() => handleSelect(option.value)}>\n <svg\n className={cn(\n \"mr-2 h-4 w-4\",\n current === option.value ? \"opacity-100\" : \"opacity-0\",\n )}\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'>\n <path d='M20 6 9 17l-5-5' />\n </svg>\n {option.label}\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n },\n);\nCombobox.displayName = \"Combobox\";\n\nexport { Combobox };\n"
18
+ "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./Command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./Popover\";\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n}\n\n/**\n * Wrapper ergonómico sobre la receta compuesta Popover + Command. Si necesitas\n * variar la estructura (grupos, ítems con iconos, multi-selección, creación de\n * opciones), compón las piezas directamente — ver \"Receta compuesta\" en\n * Combobox.md.\n */\nexport interface ComboboxProps extends Omit<\n React.ComponentPropsWithoutRef<typeof Button>,\n \"value\" | \"defaultValue\" | \"onChange\"\n> {\n options: ComboboxOption[];\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyMessage?: string;\n}\n\nconst Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(function Combobox(\n {\n options,\n value,\n defaultValue,\n onValueChange,\n placeholder = \"Seleccionar…\",\n searchPlaceholder = \"Buscar…\",\n emptyMessage = \"Sin resultados\",\n className,\n ...props\n },\n ref,\n) {\n const [open, setOpen] = React.useState(false);\n const [internal, setInternal] = React.useState(defaultValue ?? \"\");\n const current = value ?? internal;\n const selected = options.find((option) => option.value === current);\n\n const handleSelect = (next: string) => {\n const resolved = next === current ? \"\" : next;\n setInternal(resolved);\n onValueChange?.(resolved);\n setOpen(false);\n };\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n ref={ref}\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n className={cn(\"w-full justify-between font-normal\", className)}\n {...props}\n >\n {selected ? selected.label : <span className=\"text-muted-foreground\">{placeholder}</span>}\n <svg\n className=\"ml-2 h-4 w-4 shrink-0 opacity-50\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m7 15 5 5 5-5M7 9l5-5 5 5\" />\n </svg>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-[var(--radix-popover-trigger-width)] p-0\">\n <Command>\n <CommandInput placeholder={searchPlaceholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => (\n <CommandItem\n key={option.value}\n value={option.label}\n onSelect={() => handleSelect(option.value)}\n >\n <svg\n className={cn(\n \"mr-2 h-4 w-4\",\n current === option.value ? \"opacity-100\" : \"opacity-0\",\n )}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n {option.label}\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n});\nCombobox.displayName = \"Combobox\";\n\nexport { Combobox };\n"
19
19
  },
20
20
  {
21
21
  "path": "components/base/Combobox.md",