create-lexy 0.2.0 → 0.3.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 (63) hide show
  1. package/assets/r/accordion.json +1 -0
  2. package/assets/r/alert-dialog.json +28 -0
  3. package/assets/r/app-accordion.json +1 -0
  4. package/assets/r/app-dialog.json +1 -0
  5. package/assets/r/app-header-bar.json +1 -0
  6. package/assets/r/app-sidebar.json +1 -0
  7. package/assets/r/avatar.json +1 -0
  8. package/assets/r/badge.json +1 -0
  9. package/assets/r/breadcrumb.json +1 -0
  10. package/assets/r/button-group.json +1 -0
  11. package/assets/r/button.json +1 -0
  12. package/assets/r/calendar.json +1 -0
  13. package/assets/r/card.json +1 -0
  14. package/assets/r/chart.json +1 -0
  15. package/assets/r/checkbox.json +1 -0
  16. package/assets/r/combobox.json +1 -0
  17. package/assets/r/command.json +1 -0
  18. package/assets/r/confirmacion.json +27 -0
  19. package/assets/r/counter-badge.json +1 -0
  20. package/assets/r/crm-app-layout.json +29 -0
  21. package/assets/r/crm-desk.json +31 -0
  22. package/assets/r/crm-detalle-caso.json +28 -0
  23. package/assets/r/date-picker.json +1 -0
  24. package/assets/r/dialog.json +1 -0
  25. package/assets/r/dropdown-menu.json +1 -0
  26. package/assets/r/empty.json +1 -0
  27. package/assets/r/feature-card.json +1 -0
  28. package/assets/r/form.json +30 -0
  29. package/assets/r/header-bar.json +1 -0
  30. package/assets/r/input.json +1 -0
  31. package/assets/r/intake-wizard.json +28 -0
  32. package/assets/r/label.json +1 -0
  33. package/assets/r/login.json +28 -0
  34. package/assets/r/logo.json +1 -0
  35. package/assets/r/menubar.json +1 -0
  36. package/assets/r/navigation-menu.json +1 -0
  37. package/assets/r/pagination.json +1 -0
  38. package/assets/r/popover.json +1 -0
  39. package/assets/r/profile-card.json +1 -0
  40. package/assets/r/progress.json +1 -0
  41. package/assets/r/radio-group.json +1 -0
  42. package/assets/r/registry.json +329 -51
  43. package/assets/r/scroll-area.json +1 -0
  44. package/assets/r/searchbox.json +1 -0
  45. package/assets/r/select.json +1 -0
  46. package/assets/r/separator.json +1 -0
  47. package/assets/r/sheet.json +1 -0
  48. package/assets/r/sidebar.json +1 -0
  49. package/assets/r/skeleton.json +1 -0
  50. package/assets/r/slider.json +1 -0
  51. package/assets/r/snippet.json +1 -0
  52. package/assets/r/spinner.json +1 -0
  53. package/assets/r/status-dot.json +1 -0
  54. package/assets/r/switch.json +1 -0
  55. package/assets/r/table.json +1 -0
  56. package/assets/r/tabs.json +1 -0
  57. package/assets/r/tag.json +1 -0
  58. package/assets/r/textarea.json +1 -0
  59. package/assets/r/toaster.json +25 -0
  60. package/assets/r/tooltip.json +1 -0
  61. package/assets/r/tree.json +1 -0
  62. package/dist/index.js +26 -25
  63. package/package.json +1 -1
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Accordion.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Accordion.tsx",
15
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
17
  },
17
18
  {
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "alert-dialog",
4
+ "type": "registry:ui",
5
+ "title": "AlertDialog",
6
+ "description": "Confirmación modal que **interrumpe** (rol `alertdialog`): la persona debe elegir entre cancelar o confirmar — no hay X, ni cierre con click afuera. Para destruir, descartar cambios o salir de un flujo con costo. El componente vive en tu proyecto: instálalo con `create-lexy add alert-dialog` y edítalo con libertad.",
7
+ "dependencies": [
8
+ "@radix-ui/react-alert-dialog",
9
+ "class-variance-authority"
10
+ ],
11
+ "registryDependencies": [
12
+ "button"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "components/base/AlertDialog.tsx",
17
+ "type": "registry:ui",
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"
20
+ },
21
+ {
22
+ "path": "components/base/AlertDialog.md",
23
+ "type": "registry:file",
24
+ "target": "components/base/AlertDialog.md",
25
+ "content": "# AlertDialog — guía de uso\n\nConfirmación modal que **interrumpe** (rol `alertdialog`): la persona debe elegir entre cancelar o confirmar — no hay X, ni cierre con click afuera. Para destruir, descartar cambios o salir de un flujo con costo. El componente vive en tu proyecto: instálalo con `create-lexy add alert-dialog` y edítalo con libertad.\n\n## Import\n\n```tsx\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"@/components/base/AlertDialog\";\n```\n\n## Uso básico (acción destructiva)\n\n```tsx\n<AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"destructive\">Eliminar contrato</Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>¿Eliminar este contrato?</AlertDialogTitle>\n <AlertDialogDescription>\n Se eliminará de forma permanente, junto con sus documentos adjuntos.\n Esta acción no se puede deshacer.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancelar</AlertDialogCancel>\n <AlertDialogAction variant=\"destructive\" onClick={eliminar}>\n Eliminar contrato\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n</AlertDialog>\n```\n\n## Confirmación no destructiva\n\n```tsx\n<AlertDialogAction onClick={confirmar}>Enviar ahora</AlertDialogAction>\n```\n\n`AlertDialogAction` acepta las variantes de `Button` (`variant`/`size`); por defecto es la acción primaria. `AlertDialogCancel` siempre es `ghost`.\n\n## Props\n\n| Pieza | Qué es |\n|---|---|\n| `AlertDialog` / `AlertDialogTrigger` | Primitivas de Radix (`asChild` disponible en el trigger). |\n| `AlertDialogContent` | Panel modal, mismo estilo que `DialogContent`. `className`, `...props`, `ref`. |\n| `AlertDialogHeader` / `AlertDialogFooter` | Layout de título/descripcion y acciones. |\n| `AlertDialogTitle` / `AlertDialogDescription` | Texto accesible del diálogo (obligatorios). |\n| `AlertDialogAction` | Botón que confirma y cierra. `variant`/`size` de `buttonVariants`. |\n| `AlertDialogCancel` | Botón que cancela y cierra (ghost). |\n\n## AlertDialog vs Dialog\n\n- **AlertDialog**: decisión bloqueante con consecuencias (eliminar, descartar, salir sin guardar). Escape/click afuera **no** cierran; siempre dos salidas explícitas.\n- **Dialog / AppDialog**: trabajo dentro de un modal (formularios, detalle, selección). Se puede cerrar con Escape o click en el overlay sin perder nada importante.\n- Si el \"confirmar\" requiere ingresar datos (motivo, contraseña), ya no es un AlertDialog: usa `Dialog` con formulario.\n\n## Reglas\n\n- El título es la pregunta concreta (*\"¿Eliminar este contrato?\"*), la descripción explica la consecuencia (*\"…no se puede deshacer\"*).\n- La acción repite el verbo del título (*\"Eliminar contrato\"*), nunca \"Aceptar\"/\"OK\".\n- Acción irreversible → `variant=\"destructive\"`.\n- No abuses: reserva la interrupción para decisiones con costo real; el éxito posterior se comunica con `Toaster`.\n\n## Para IA\n\n1. Úsalo solo para confirmar acciones con consecuencias; el resto va en `Dialog`/`AppDialog`.\n2. Título = pregunta con el verbo de la acción; descripción = consecuencia concreta; botón = mismo verbo.\n3. `AlertDialogTrigger asChild` sobre un `Button` existente.\n4. Tras confirmar, comunica el resultado (toast de éxito o error).\n"
26
+ }
27
+ ]
28
+ }
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/AppAccordion.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/AppAccordion.tsx",
15
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
17
  },
17
18
  {
@@ -13,6 +13,7 @@
13
13
  {
14
14
  "path": "components/base/AppDialog.tsx",
15
15
  "type": "registry:ui",
16
+ "target": "components/base/AppDialog.tsx",
16
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
18
  },
18
19
  {
@@ -16,6 +16,7 @@
16
16
  {
17
17
  "path": "components/base/AppHeaderBar.tsx",
18
18
  "type": "registry:ui",
19
+ "target": "components/base/AppHeaderBar.tsx",
19
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
21
  },
21
22
  {
@@ -15,6 +15,7 @@
15
15
  {
16
16
  "path": "components/base/AppSidebar.tsx",
17
17
  "type": "registry:ui",
18
+ "target": "components/base/AppSidebar.tsx",
18
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
20
  },
20
21
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Avatar.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Avatar.tsx",
15
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
17
  },
17
18
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Badge.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Badge.tsx",
15
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
17
  },
17
18
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Breadcrumb.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Breadcrumb.tsx",
15
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
17
  },
17
18
  {
@@ -15,6 +15,7 @@
15
15
  {
16
16
  "path": "components/base/ButtonGroup.tsx",
17
17
  "type": "registry:ui",
18
+ "target": "components/base/ButtonGroup.tsx",
18
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
20
  },
20
21
  {
@@ -13,6 +13,7 @@
13
13
  {
14
14
  "path": "components/base/Button.tsx",
15
15
  "type": "registry:ui",
16
+ "target": "components/base/Button.tsx",
16
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
18
  },
18
19
  {
@@ -15,6 +15,7 @@
15
15
  {
16
16
  "path": "components/base/Calendar.tsx",
17
17
  "type": "registry:ui",
18
+ "target": "components/base/Calendar.tsx",
18
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
20
  },
20
21
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Card.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Card.tsx",
15
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
17
  },
17
18
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Chart.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Chart.tsx",
15
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
17
  },
17
18
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Checkbox.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Checkbox.tsx",
15
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
17
  },
17
18
  {
@@ -14,6 +14,7 @@
14
14
  {
15
15
  "path": "components/base/Combobox.tsx",
16
16
  "type": "registry:ui",
17
+ "target": "components/base/Combobox.tsx",
17
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
19
  },
19
20
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Command.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Command.tsx",
15
16
  "content": "import * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Command = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive\n ref={ref}\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded bg-popover text-popover-foreground\",\n className,\n )}\n {...props}\n />\n));\nCommand.displayName = CommandPrimitive.displayName;\n\nconst CommandInput = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Input>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>\n>(({ className, ...props }, ref) => (\n <div className='flex items-center border-b border-border px-3' cmdk-input-wrapper=''>\n <svg\n className='mr-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 <circle cx='11' cy='11' r='8' />\n <path d='m21 21-4.3-4.3' />\n </svg>\n <CommandPrimitive.Input\n ref={ref}\n className={cn(\n \"flex h-10 w-full rounded bg-transparent py-3 text-sm text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}\n />\n </div>\n));\nCommandInput.displayName = CommandPrimitive.Input.displayName;\n\nconst CommandList = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.List\n ref={ref}\n className={cn(\"max-h-75 overflow-y-auto overflow-x-hidden\", className)}\n {...props}\n />\n));\nCommandList.displayName = CommandPrimitive.List.displayName;\n\nconst CommandEmpty = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Empty>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>\n>((props, ref) => (\n <CommandPrimitive.Empty\n ref={ref}\n className='py-6 text-center text-sm text-muted-foreground'\n {...props}\n />\n));\nCommandEmpty.displayName = CommandPrimitive.Empty.displayName;\n\nconst CommandGroup = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Group>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.Group\n ref={ref}\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nCommandGroup.displayName = CommandPrimitive.Group.displayName;\n\nconst CommandSeparator = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 h-px bg-border\", className)}\n {...props}\n />\n));\nCommandSeparator.displayName = CommandPrimitive.Separator.displayName;\n\nconst CommandItem = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center gap-2 rounded px-2 py-1.5 text-sm text-foreground outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-primary/15 data-[selected=true]:text-primary data-[disabled=true]:opacity-50\",\n className,\n )}\n {...props}\n />\n));\nCommandItem.displayName = CommandPrimitive.Item.displayName;\n\nconst CommandShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn(\n \"ml-auto text-xs tracking-widest text-muted-foreground\",\n className,\n )}\n {...props}\n />\n);\nCommandShortcut.displayName = \"CommandShortcut\";\n\nexport {\n Command,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n"
16
17
  },
17
18
  {
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "confirmacion",
4
+ "type": "registry:block",
5
+ "title": "confirmacion",
6
+ "description": "Pantalla de confirmación / éxito para **mundo cliente**: celebra breve, dice qué pasa ahora y dónde seguir. Instálalo con `create-lexy add confirmacion` — trae `Button` (el icono es de lucide). La vista queda en tu proyecto (`views/EnvioConfirmado.tsx`): edítala con libertad.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "button"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "views/EnvioConfirmado.tsx",
16
+ "type": "registry:file",
17
+ "target": "views/EnvioConfirmado.tsx",
18
+ "content": "import { CheckCircle2 } from \"lucide-react\";\nimport { Button } from \"@/components/base/Button\";\n\n// Receta cliente — confirmación / éxito: celebra breve, di qué pasa ahora y\n// dónde seguir. Ver confirmacion.md.\n\nexport function EnvioConfirmado() {\n return (\n <main className='mx-auto flex min-h-[60vh] w-full max-w-md flex-col items-center justify-center px-4 text-center'>\n <span className='mb-4 flex size-12 items-center justify-center rounded-full bg-success/10'>\n <CheckCircle2 className='size-6 text-success' aria-hidden />\n </span>\n <h1 className='text-2xl font-bold text-foreground'>\n ¡Enviamos tus documentos!\n </h1>\n <p className='mt-2 text-base text-muted-foreground'>\n El equipo legal los está revisando. Podrás seguir su estado en la pestaña\n «Enviados» y te avisaremos si falta algo.\n </p>\n <Button className='mt-6'>Volver al inicio</Button>\n </main>\n );\n}\n"
19
+ },
20
+ {
21
+ "path": "views/confirmacion.md",
22
+ "type": "registry:file",
23
+ "target": "views/confirmacion.md",
24
+ "content": "# confirmacion — block\n\nPantalla de confirmación / éxito para **mundo cliente**: celebra breve, dice qué pasa ahora y dónde seguir. Instálalo con `create-lexy add confirmacion` — trae `Button` (el icono es de lucide). La vista queda en tu proyecto (`views/EnvioConfirmado.tsx`): edítala con libertad.\n\n## Adaptación\n\n- Título en pasado y concreto (*\"¡Enviamos tus documentos!\"*); el párrafo dice qué pasa ahora y dónde seguir el estado.\n- El CTA lleva a la siguiente acción natural (inicio, estado del trámite), no a un dead-end.\n- Para éxito *dentro* de una vista (guardado puntual) usa `Toaster`, no esta pantalla.\n\n## Evita\n\nConfeti/celebración larga, varios CTAs compitiendo, y texto legal en este momento del flujo.\n"
25
+ }
26
+ ]
27
+ }
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/CounterBadge.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/CounterBadge.tsx",
15
16
  "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nfunction formatCount(children: React.ReactNode, max?: number): React.ReactNode {\n if (max == null) return children;\n const n =\n typeof children === \"number\"\n ? children\n : typeof children === \"string\" && /^\\d+$/.test(children.trim())\n ? Number.parseInt(children, 10)\n : null;\n if (n == null || n <= max) return children;\n return `${max}+`;\n}\n\nconst counterBadgeVariants = cva(\n \"inline-flex select-none items-center justify-center rounded-full bg-primary font-semibold tabular-nums text-primary-foreground\",\n {\n variants: {\n size: {\n sm: \"h-4 min-w-4 px-1 text-xs leading-none\",\n md: \"h-5 min-w-5 px-1.5 text-xs leading-none\",\n lg: \"h-6 min-w-6 px-1.5 text-xs/[1]\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport interface CounterBadgeProps\n extends React.HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof counterBadgeVariants> {\n /** Tope numérico (ej. 99 → \"99+\"). */\n max?: number;\n}\n\nconst CounterBadge = React.forwardRef<HTMLSpanElement, CounterBadgeProps>(\n ({ size, max, className, children, ...props }, ref) => (\n <span\n ref={ref}\n className={cn(counterBadgeVariants({ size }), className)}\n {...props}>\n {formatCount(children, max)}\n </span>\n ),\n);\nCounterBadge.displayName = \"CounterBadge\";\n\nexport { CounterBadge, counterBadgeVariants };\n"
16
17
  },
17
18
  {
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "crm-app-layout",
4
+ "type": "registry:block",
5
+ "title": "crm-app-layout",
6
+ "description": "Estructura persistente de la app interna (**CRM**): navegación lateral colapsable data-driven (`AppSidebar`) y área de trabajo (`SidebarInset`). Instálalo con `create-lexy add crm-app-layout` — trae `AppSidebar`, `Sidebar` y `Logo`. La vista queda en tu proyecto (`views/AppLayout.tsx`): edítala con libertad.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "app-sidebar",
12
+ "logo",
13
+ "sidebar"
14
+ ],
15
+ "files": [
16
+ {
17
+ "path": "views/AppLayout.tsx",
18
+ "type": "registry:file",
19
+ "target": "views/AppLayout.tsx",
20
+ "content": "import * as React from \"react\";\nimport { FolderKanban, Inbox, Settings } from \"lucide-react\";\nimport { AppSidebar } from \"@/components/base/AppSidebar\";\nimport { Logo } from \"@/components/base/Logo\";\nimport { SidebarInset, SidebarProvider } from \"@/components/base/Sidebar\";\n\n// Receta CRM — layout de aplicación: navegación lateral colapsable data-driven\n// y área de trabajo. Las vistas del desk y el detalle viven dentro de\n// SidebarInset. Ver crm-app-layout.md.\n\nexport function AppLayout({ children }: { children: React.ReactNode }) {\n return (\n <SidebarProvider>\n <AppSidebar\n logo={<Logo layout='horizontal' />}\n groups={[\n {\n label: \"Trabajo\",\n items: [\n { title: \"Casos\", icon: FolderKanban, url: \"/casos\", isActive: true },\n { title: \"Bandeja\", icon: Inbox, url: \"/bandeja\" },\n ],\n },\n {\n label: \"Sistema\",\n items: [{ title: \"Configuración\", icon: Settings, url: \"/config\" }],\n },\n ]}\n />\n <SidebarInset>{children}</SidebarInset>\n </SidebarProvider>\n );\n}\n"
21
+ },
22
+ {
23
+ "path": "views/crm-app-layout.md",
24
+ "type": "registry:file",
25
+ "target": "views/crm-app-layout.md",
26
+ "content": "# crm-app-layout — block\n\nEstructura persistente de la app interna (**CRM**): navegación lateral colapsable data-driven (`AppSidebar`) y área de trabajo (`SidebarInset`). Instálalo con `create-lexy add crm-app-layout` — trae `AppSidebar`, `Sidebar` y `Logo`. La vista queda en tu proyecto (`views/AppLayout.tsx`): edítala con libertad.\n\n## Montaje\n\nEnvuelve tus rutas internas; el contenido (desk, detalle) se renderiza como `children`:\n\n```tsx\n<AppLayout>\n <CasosDesk />\n</AppLayout>\n```\n\n## Adaptación\n\n- La navegación se describe como datos en `groups` (lee `AppSidebar.md` antes de tocarla; no compongas a mano las primitivas de `Sidebar`).\n- Marca el ítem activo con `isActive` desde tu router.\n- Pie de usuario: pasa `user={{ name, email, avatar }}` a `AppSidebar`.\n\n## Evita\n\nUsar este layout en pantallas de cliente sin panel lateral (ahí va `AppHeaderBar` + secciones) y duplicar el logo dentro del contenido.\n"
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "crm-desk",
4
+ "type": "registry:block",
5
+ "title": "crm-desk",
6
+ "description": "Desk de trabajo para **CRM**: ancho completo, toolbar con búsqueda y acción primaria, tabla densa y escaneable con estado por fila, y paginación al pie. Instálalo con `create-lexy add crm-desk` — trae `Table`, `StatusDot`, `Pagination`, `Input` y `Button`. La vista queda en tu proyecto (`views/CasosDesk.tsx`): edítala con libertad.",
7
+ "dependencies": [
8
+ "lucide-react"
9
+ ],
10
+ "registryDependencies": [
11
+ "button",
12
+ "input",
13
+ "pagination",
14
+ "status-dot",
15
+ "table"
16
+ ],
17
+ "files": [
18
+ {
19
+ "path": "views/CasosDesk.tsx",
20
+ "type": "registry:file",
21
+ "target": "views/CasosDesk.tsx",
22
+ "content": "import { Plus } from \"lucide-react\";\nimport { Button } from \"@/components/base/Button\";\nimport { Input } from \"@/components/base/Input\";\nimport { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from \"@/components/base/Pagination\";\nimport { StatusDot } from \"@/components/base/StatusDot\";\nimport { Table } from \"@/components/base/Table\";\n\n// Receta CRM — desk con tabla y acciones: ancho completo, toolbar con búsqueda\n// y acción primaria, tabla densa con estado por fila, paginación al pie.\n// Ver crm-desk.md.\n\nexport function CasosDesk() {\n return (\n <main className='w-full p-4'>\n <header className='mb-4 flex items-center justify-between gap-4'>\n <div>\n <h1 className='text-xl font-semibold text-foreground'>Casos</h1>\n <p className='text-sm text-muted-foreground'>24 activos · 3 vencen hoy</p>\n </div>\n <div className='flex items-center gap-2'>\n <Input className='h-9 w-64' placeholder='Buscar por cliente o RUT' aria-label='Buscar casos' />\n <Button size='sm'>\n <Plus /> Nuevo caso\n </Button>\n </div>\n </header>\n\n <Table columns='2fr 1.5fr 1fr 1fr' stickyHeader>\n <Table.Header>\n <Table.Cell>Cliente</Table.Cell>\n <Table.Cell>Materia</Table.Cell>\n <Table.Cell>Estado</Table.Cell>\n <Table.Cell>Plazo</Table.Cell>\n </Table.Header>\n <Table.Content>\n <Table.Row onClick={() => {}}>\n <Table.Cell className='font-medium'>María Pérez</Table.Cell>\n <Table.Cell>Despido</Table.Cell>\n <Table.Cell><StatusDot tone='warning'>En revisión</StatusDot></Table.Cell>\n <Table.Cell className='text-warning'>Vence hoy</Table.Cell>\n </Table.Row>\n <Table.Row onClick={() => {}}>\n <Table.Cell className='font-medium'>Juan Soto</Table.Cell>\n <Table.Cell>Deuda</Table.Cell>\n <Table.Cell><StatusDot tone='success'>Al día</StatusDot></Table.Cell>\n <Table.Cell className='text-muted-foreground'>12 días</Table.Cell>\n </Table.Row>\n </Table.Content>\n </Table>\n\n <footer className='mt-4 flex items-center justify-between'>\n <p className='text-sm text-muted-foreground'>Mostrando 1–20 de 64 casos</p>\n <Pagination className='mx-0 w-auto justify-end'>\n <PaginationContent>\n <PaginationItem><PaginationPrevious href='#' /></PaginationItem>\n <PaginationItem><PaginationLink href='#' isActive>1</PaginationLink></PaginationItem>\n <PaginationItem><PaginationLink href='#'>2</PaginationLink></PaginationItem>\n <PaginationItem><PaginationLink href='#'>3</PaginationLink></PaginationItem>\n <PaginationItem><PaginationNext href='#' /></PaginationItem>\n </PaginationContent>\n </Pagination>\n </footer>\n </main>\n );\n}\n"
23
+ },
24
+ {
25
+ "path": "views/crm-desk.md",
26
+ "type": "registry:file",
27
+ "target": "views/crm-desk.md",
28
+ "content": "# crm-desk — block\n\nDesk de trabajo para **CRM**: ancho completo, toolbar con búsqueda y acción primaria, tabla densa y escaneable con estado por fila, y paginación al pie. Instálalo con `create-lexy add crm-desk` — trae `Table`, `StatusDot`, `Pagination`, `Input` y `Button`. La vista queda en tu proyecto (`views/CasosDesk.tsx`): edítala con libertad.\n\n## Montaje\n\nVive dentro del layout de aplicación (`crm-app-layout`), en el `SidebarInset`. Reemplaza las filas de ejemplo por tus datos y conecta `onClick` de cada fila a la navegación al detalle (`crm-detalle-caso`).\n\n## Adaptación\n\n- El subtítulo del header resume el estado del desk (*\"24 activos · 3 vencen hoy\"*): cámbialo por tus métricas reales.\n- Los cuatro estados de datos son obligatorios: loading (`Skeleton` con la forma de la tabla), vacío y error (`Empty` con salida), contenido.\n- Columnas vía `columns` (grid-template-columns); el estado por fila siempre con `StatusDot` + texto.\n\n## Evita\n\nEnvolver cada fila en una card, espaciado de cliente, color como único signo de estado, y listas de cientos de filas sin `Pagination`.\n"
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "crm-detalle-caso",
4
+ "type": "registry:block",
5
+ "title": "crm-detalle-caso",
6
+ "description": "Detalle master-detail para **CRM**: contexto del caso a la izquierda (panel fijo de 288px), trabajo al centro en tabs, acción primaria a la mano. La información que se consulta junta vive junta. Instálalo con `create-lexy add crm-detalle-caso` — trae `Tabs`, `Tag`, `Separator` y `Button`. La vista queda en tu proyecto (`views/CasoDetalle.tsx`): edítala con libertad.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "button",
10
+ "separator",
11
+ "tabs",
12
+ "tag"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "views/CasoDetalle.tsx",
17
+ "type": "registry:file",
18
+ "target": "views/CasoDetalle.tsx",
19
+ "content": "import { Button } from \"@/components/base/Button\";\nimport { Separator } from \"@/components/base/Separator\";\nimport { Tag } from \"@/components/base/Tag\";\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from \"@/components/base/Tabs\";\n\n// Receta CRM — detalle de caso (master-detail): contexto a la izquierda,\n// trabajo al centro, acciones a la mano. Ver crm-detalle-caso.md.\n\nexport function CasoDetalle() {\n return (\n <main className='flex w-full gap-4 p-4'>\n <aside className='w-72 shrink-0 space-y-4'>\n <div>\n <h1 className='text-lg font-semibold text-foreground'>María Pérez</h1>\n <p className='text-sm text-muted-foreground'>Caso #1042 · Despido</p>\n </div>\n <Tag tone='warning'>En revisión</Tag>\n <Separator />\n <dl className='space-y-2 text-sm'>\n <div className='flex justify-between'>\n <dt className='text-muted-foreground'>Abogado</dt>\n <dd className='text-foreground'>C. Rivera</dd>\n </div>\n <div className='flex justify-between'>\n <dt className='text-muted-foreground'>Plazo</dt>\n <dd className='text-warning'>Vence hoy</dd>\n </div>\n </dl>\n </aside>\n\n <section className='min-w-0 flex-1'>\n <div className='mb-3 flex items-center justify-between'>\n <h2 className='text-base font-semibold text-foreground'>Gestiones</h2>\n <Button size='sm'>Nueva gestión</Button>\n </div>\n <Tabs defaultValue='actividad'>\n <TabsList>\n <TabsTrigger value='actividad'>Actividad</TabsTrigger>\n <TabsTrigger value='documentos'>Documentos</TabsTrigger>\n <TabsTrigger value='notas'>Notas</TabsTrigger>\n </TabsList>\n <TabsContent value='actividad'>{/* timeline */}</TabsContent>\n <TabsContent value='documentos'>{/* lista */}</TabsContent>\n <TabsContent value='notas'>{/* notas */}</TabsContent>\n </Tabs>\n </section>\n </main>\n );\n}\n"
20
+ },
21
+ {
22
+ "path": "views/crm-detalle-caso.md",
23
+ "type": "registry:file",
24
+ "target": "views/crm-detalle-caso.md",
25
+ "content": "# crm-detalle-caso — block\n\nDetalle master-detail para **CRM**: contexto del caso a la izquierda (panel fijo de 288px), trabajo al centro en tabs, acción primaria a la mano. La información que se consulta junta vive junta. Instálalo con `create-lexy add crm-detalle-caso` — trae `Tabs`, `Tag`, `Separator` y `Button`. La vista queda en tu proyecto (`views/CasoDetalle.tsx`): edítala con libertad.\n\n## Montaje\n\nVive dentro del layout de aplicación (`crm-app-layout`); se llega desde una fila del desk (`crm-desk`).\n\n## Adaptación\n\n- El `aside` lleva la identidad (quién, qué caso) + estado (`Tag` con texto) + datos clave en `dl`; agrega pares `dt`/`dd` según tu dominio.\n- Cada `TabsContent` resuelve sus cuatro estados de datos (loading/vacío/error/contenido).\n- Acciones secundarias del caso → `DropdownMenu` junto al botón primario, no una fila de botones.\n\n## Evita\n\nRepetir la identidad del caso dentro de cada tab, panel izquierdo scrolleable independiente sin necesidad, y tabs con una sola pestaña.\n"
26
+ }
27
+ ]
28
+ }
@@ -17,6 +17,7 @@
17
17
  {
18
18
  "path": "components/base/DatePicker.tsx",
19
19
  "type": "registry:ui",
20
+ "target": "components/base/DatePicker.tsx",
20
21
  "content": "import * as React from \"react\";\nimport { CalendarIcon } from \"lucide-react\";\nimport { es } from \"react-day-picker/locale\";\nimport type { Locale } from \"react-day-picker\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Button } from \"./Button\";\nimport { Calendar } from \"./Calendar\";\nimport { Popover, PopoverTrigger, PopoverContent } from \"./Popover\";\n\nfunction formatDate(date: Date, localeCode?: string) {\n return date.toLocaleDateString(localeCode, {\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n });\n}\n\n/**\n * Wrapper ergonómico sobre la receta compuesta Popover + Button + Calendar.\n * Si necesitas variar la estructura (rango, dos meses, footer, otro trigger),\n * compón las piezas directamente — ver \"Receta compuesta\" en DatePicker.md.\n */\nexport interface DatePickerProps\n extends Omit<\n React.ComponentPropsWithoutRef<typeof Button>,\n \"value\" | \"defaultValue\" | \"onChange\"\n > {\n /** Fecha seleccionada (modo controlado: pásala junto a `onChange`). */\n value?: Date;\n /** Fecha inicial en modo no controlado. */\n defaultValue?: Date;\n /** Callback de selección (activa el modo controlado). */\n onChange?: (date: Date | undefined) => void;\n placeholder?: string;\n captionLayout?: React.ComponentProps<typeof Calendar>[\"captionLayout\"];\n locale?: Partial<Locale>;\n align?: React.ComponentProps<typeof PopoverContent>[\"align\"];\n}\n\nconst DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(\n function DatePicker(\n {\n value,\n defaultValue,\n onChange,\n placeholder = \"Selecciona una fecha\",\n captionLayout = \"label\",\n locale = es,\n align = \"start\",\n className,\n ...props\n },\n ref,\n ) {\n const [open, setOpen] = React.useState(false);\n const [internal, setInternal] = React.useState<Date | undefined>(\n defaultValue,\n );\n\n // Controlado si se provee `onChange`; si no, mantenemos estado interno.\n const selected = onChange ? value : internal;\n\n const handleSelect = (date: Date | undefined) => {\n if (onChange) onChange(date);\n else setInternal(date);\n setOpen(false);\n };\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n ref={ref}\n type=\"button\"\n variant=\"outline\"\n data-empty={!selected}\n className={cn(\n \"w-65 justify-start text-left font-normal data-[empty=true]:text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <CalendarIcon />\n {selected ? formatDate(selected, locale?.code) : placeholder}\n </Button>\n </PopoverTrigger>\n {/* El panel toma el ancho del trigger (igual que Combobox); el calendario\n (248px natural) se centra dentro. */}\n <PopoverContent\n className=\"flex w-[var(--radix-popover-trigger-width)] justify-center p-0\"\n align={align}\n >\n <Calendar\n mode=\"single\"\n selected={selected}\n onSelect={handleSelect}\n captionLayout={captionLayout}\n locale={locale}\n autoFocus\n />\n </PopoverContent>\n </Popover>\n );\n },\n);\nDatePicker.displayName = \"DatePicker\";\n\nexport { DatePicker };\n"
21
22
  },
22
23
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/Dialog.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/Dialog.tsx",
15
16
  "content": "import * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Dialog = DialogPrimitive.Root;\nconst DialogTrigger = DialogPrimitive.Trigger;\nconst DialogPortal = DialogPrimitive.Portal;\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.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));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.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 {children}\n {/* Sin X de cierre: se cierra con Escape, click en el overlay o una\n acción explícita (DialogClose / botón Cancelar de AppDialog). */}\n </DialogPrimitive.Content>\n </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nconst DialogHeader = ({\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);\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogFooter = ({\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);\nDialogFooter.displayName = \"DialogFooter\";\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.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));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogClose,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n};\n"
16
17
  },
17
18
  {
@@ -12,6 +12,7 @@
12
12
  {
13
13
  "path": "components/base/DropdownMenu.tsx",
14
14
  "type": "registry:ui",
15
+ "target": "components/base/DropdownMenu.tsx",
15
16
  "content": "import * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst ChevronRightIcon = () => (\n <svg\n className='ml-auto h-4 w-4'\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\nconst CheckIcon = () => (\n <svg\n className='h-4 w-4'\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);\n\nconst DotIcon = () => (\n <svg className='h-2 w-2 fill-current' viewBox='0 0 24 24'>\n <circle cx='12' cy='12' r='12' />\n </svg>\n);\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n \"flex cursor-pointer select-none items-center rounded px-2 py-1.5 text-sm outline-none focus:bg-primary/15 focus:text-primary data-[state=open]:bg-primary/15 data-[state=open]:text-primary\",\n inset && \"pl-8\",\n className,\n )}\n {...props}>\n {children}\n <ChevronRightIcon />\n </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName =\n DropdownMenuPrimitive.SubTrigger.displayName;\n\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n className={cn(\n \"z-50 min-w-32 overflow-hidden rounded border border-border bg-popover p-1 text-popover-foreground shadow-md 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\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuSubContent.displayName =\n DropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"z-50 min-w-32 overflow-hidden rounded border border-border bg-popover p-1 text-popover-foreground shadow-md 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\",\n className,\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n));\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center gap-2 rounded px-2 py-1.5 text-sm outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n checked={checked}\n {...props}>\n <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuCheckboxItem.displayName =\n DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}>\n <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>\n <DropdownMenuPrimitive.ItemIndicator>\n <DotIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\n \"px-2 py-1.5 text-sm font-semibold text-foreground\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn(\n \"ml-auto text-xs tracking-widest text-muted-foreground\",\n className,\n )}\n {...props}\n />\n);\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\";\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuGroup,\n DropdownMenuPortal,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuRadioGroup,\n};\n"
16
17
  },
17
18
  {