create-lexy 0.6.1 → 0.6.3

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 (93) hide show
  1. package/README.md +7 -2
  2. package/assets/fonts/{OFL-NotoSans.txt → LICENSE-Geist.txt} +4 -5
  3. package/assets/fonts/geist-mono-variable-italic.woff2 +0 -0
  4. package/assets/fonts/geist-mono-variable.woff2 +0 -0
  5. package/assets/fonts/geist-sans-variable-italic.woff2 +0 -0
  6. package/assets/fonts/geist-sans-variable.woff2 +0 -0
  7. package/assets/r/accordion.json +3 -3
  8. package/assets/r/alert-dialog.json +3 -3
  9. package/assets/r/app-accordion.json +1 -1
  10. package/assets/r/app-dialog.json +3 -3
  11. package/assets/r/app-header-bar.json +2 -2
  12. package/assets/r/app-sidebar.json +2 -2
  13. package/assets/r/avatar.json +3 -3
  14. package/assets/r/badge.json +3 -3
  15. package/assets/r/brand-background.json +1 -1
  16. package/assets/r/breadcrumb.json +3 -3
  17. package/assets/r/button-group.json +3 -3
  18. package/assets/r/button.json +3 -3
  19. package/assets/r/calendar.json +2 -2
  20. package/assets/r/card.json +3 -3
  21. package/assets/r/chart.json +2 -2
  22. package/assets/r/checkbox.json +2 -2
  23. package/assets/r/combobox.json +3 -3
  24. package/assets/r/command.json +3 -3
  25. package/assets/r/confirmacion.json +1 -1
  26. package/assets/r/counter-badge.json +3 -3
  27. package/assets/r/crm-desk.json +1 -1
  28. package/assets/r/crm-detalle-caso.json +5 -5
  29. package/assets/r/date-picker.json +1 -1
  30. package/assets/r/dialog.json +3 -3
  31. package/assets/r/dropdown-menu.json +3 -3
  32. package/assets/r/empty.json +2 -2
  33. package/assets/r/feature-card.json +3 -3
  34. package/assets/r/form.json +3 -3
  35. package/assets/r/header-bar.json +2 -2
  36. package/assets/r/input.json +3 -3
  37. package/assets/r/intake-wizard.json +1 -1
  38. package/assets/r/label.json +3 -3
  39. package/assets/r/logo.json +2 -2
  40. package/assets/r/menubar.json +3 -3
  41. package/assets/r/navigation-menu.json +3 -3
  42. package/assets/r/pagination.json +2 -2
  43. package/assets/r/popover.json +3 -3
  44. package/assets/r/profile-card.json +3 -3
  45. package/assets/r/progress.json +2 -2
  46. package/assets/r/radio-group.json +2 -2
  47. package/assets/r/registry.json +47 -47
  48. package/assets/r/scroll-area.json +2 -2
  49. package/assets/r/searchbox.json +3 -3
  50. package/assets/r/select.json +3 -3
  51. package/assets/r/separator.json +2 -2
  52. package/assets/r/sheet.json +2 -2
  53. package/assets/r/sidebar.json +3 -3
  54. package/assets/r/skeleton.json +2 -2
  55. package/assets/r/slider.json +3 -3
  56. package/assets/r/snippet.json +3 -3
  57. package/assets/r/spinner.json +2 -2
  58. package/assets/r/status-dot.json +3 -3
  59. package/assets/r/switch.json +3 -3
  60. package/assets/r/table.json +3 -3
  61. package/assets/r/tabs.json +3 -3
  62. package/assets/r/tag.json +3 -3
  63. package/assets/r/textarea.json +3 -3
  64. package/assets/r/toaster.json +3 -3
  65. package/assets/r/tooltip.json +3 -3
  66. package/assets/r/tree.json +3 -3
  67. package/assets/registry-version +1 -1
  68. package/assets/theme/lexy-theme.css +562 -112
  69. package/dist/index.js +590 -276
  70. package/package.json +4 -2
  71. package/templates/.claude/skills/lexy-design/SKILL.md +189 -0
  72. package/templates/.claude/skills/lexy-dev/SKILL.md +168 -0
  73. package/templates/.claude/skills/lexy-mock-data/SKILL.md +50 -0
  74. package/templates/.github/copilot-instructions.md +50 -0
  75. package/templates/.mcp.json +8 -0
  76. package/templates/AGENTS.md +243 -0
  77. package/templates/CLAUDE.md +61 -0
  78. package/templates/ai/IMPLEMENTATION-PROTOCOL.md +148 -0
  79. package/templates/ai/PRODUCTION-CLEANUP.md +17 -0
  80. package/templates/ai/PROJECT-CONTEXT.md +65 -0
  81. package/templates/ai/README.md +19 -0
  82. package/templates/ai/TECHNICAL-USAGE.md +220 -0
  83. package/templates/ai/pautas/arquitectura-informacion-ux.md +243 -0
  84. package/templates/ai/pautas/buenas-practicas.md +236 -0
  85. package/templates/ai/pautas/calidad-industria.md +109 -0
  86. package/templates/ai/pautas/diseno-cliente.md +136 -0
  87. package/templates/ai/pautas/diseno-crm-lexy.md +109 -0
  88. package/templates/ai/pautas/patrones-de-codigo.md +234 -0
  89. package/templates/ai/pautas/recetas-layout.md +419 -0
  90. package/templates/ai/pautas/sistema-visual.md +197 -0
  91. package/templates/ai/pautas/ux-writing.md +214 -0
  92. package/templates/scripts/check-geometry.mjs +139 -0
  93. package/assets/fonts/noto-sans-latin.woff2 +0 -0
@@ -14,13 +14,13 @@
14
14
  "path": "components/base/Sheet.tsx",
15
15
  "type": "registry:ui",
16
16
  "target": "components/base/Sheet.tsx",
17
- "content": "import * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Sheet = SheetPrimitive.Root;\nconst SheetTrigger = SheetPrimitive.Trigger;\nconst SheetClose = SheetPrimitive.Close;\nconst SheetPortal = SheetPrimitive.Portal;\n\nconst SheetOverlay = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.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));\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n \"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n {\n variants: {\n side: {\n top: \"inset-x-0 top-0 border-b border-border data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n left: \"inset-y-0 left-0 h-full w-3/4 border-r border-border data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n right:\n \"inset-y-0 right-0 h-full w-3/4 border-l border-border data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n },\n },\n defaultVariants: { side: \"right\" },\n },\n);\n\ninterface SheetContentProps\n extends\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Content>,\n SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n <SheetPortal>\n <SheetOverlay />\n <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>\n {children}\n <SheetPrimitive.Close className=\"absolute right-4 top-4 cursor-pointer rounded opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none\">\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 >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n <span className=\"sr-only\">Cerrar</span>\n </SheetPrimitive.Close>\n </SheetPrimitive.Content>\n </SheetPortal>\n));\nSheetContent.displayName = SheetPrimitive.Content.displayName;\n\nconst SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex flex-col space-y-2 text-center sm:text-left\", className)} {...props} />\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", className)}\n {...props}\n />\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.Title\n ref={ref}\n className={cn(\"text-lg font-semibold text-foreground\", className)}\n {...props}\n />\n));\nSheetTitle.displayName = SheetPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nSheetDescription.displayName = SheetPrimitive.Description.displayName;\n\nexport {\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetOverlay,\n SheetPortal,\n SheetTitle,\n SheetTrigger,\n};\n"
17
+ "content": "import * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Sheet = SheetPrimitive.Root;\nconst SheetTrigger = SheetPrimitive.Trigger;\nconst SheetClose = SheetPrimitive.Close;\nconst SheetPortal = SheetPrimitive.Portal;\n\nconst SheetOverlay = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.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));\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n \"fixed z-50 gap-grouped bg-background p-6 shadow-overlay transition duration-[var(--duration-overlay)] ease-[var(--ease-standard)] motion-reduce:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out\",\n {\n variants: {\n side: {\n top: \"inset-x-0 top-0 border-b border-border data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n bottom:\n \"inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n left: \"inset-y-0 left-0 h-full w-3/4 border-r border-border data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n right:\n \"inset-y-0 right-0 h-full w-3/4 border-l border-border data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n },\n },\n defaultVariants: { side: \"right\" },\n },\n);\n\ninterface SheetContentProps\n extends\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Content>,\n SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n <SheetPortal>\n <SheetOverlay />\n <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>\n {children}\n <SheetPrimitive.Close className=\"absolute right-4 top-4 cursor-pointer rounded-button opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none\">\n <svg\n className=\"size-(--icon-size-control)\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n <span className=\"sr-only\">Cerrar</span>\n </SheetPrimitive.Close>\n </SheetPrimitive.Content>\n </SheetPortal>\n));\nSheetContent.displayName = SheetPrimitive.Content.displayName;\n\nconst SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex flex-col gap-related text-center sm:text-left\", className)} {...props} />\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div\n className={cn(\"flex flex-col-reverse gap-related sm:flex-row sm:justify-end\", className)}\n {...props}\n />\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.Title\n ref={ref}\n className={cn(\"type-dialog-title text-foreground\", className)}\n {...props}\n />\n));\nSheetTitle.displayName = SheetPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n React.ElementRef<typeof SheetPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <SheetPrimitive.Description\n ref={ref}\n className={cn(\"type-supporting text-muted-foreground\", className)}\n {...props}\n />\n));\nSheetDescription.displayName = SheetPrimitive.Description.displayName;\n\nexport {\n Sheet,\n SheetClose,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetOverlay,\n SheetPortal,\n SheetTitle,\n SheetTrigger,\n};\n"
18
18
  },
19
19
  {
20
20
  "path": "components/base/Sheet.md",
21
21
  "type": "registry:file",
22
22
  "target": "components/base/Sheet.md",
23
- "content": "# Sheet — guía de uso\n\nPanel lateral deslizante. Alternativa a `Dialog` para contenido secundario que\nrequiere más espacio o contexto persistente. El componente vive en tu proyecto: instálalo con `create-lexy add sheet` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara tareas secundarias que no ameritan salir de la página: un detalle rápido, un formulario de creación, filtros avanzados. Se desliza desde un borde y mantiene visible el contexto de fondo.\n\n## Composición\n\nComposición Radix: `SheetTrigger` abre `SheetContent` (lado configurable), con `SheetHeader` (`SheetTitle`, `SheetDescription`), cuerpo libre y `SheetFooter`; `SheetClose` cierra.\n\n## Uso básico\n\n```tsx\n<Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\">Abrir panel</Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>Configuración</SheetTitle>\n <SheetDescription>Ajusta las preferencias de tu cuenta.</SheetDescription>\n </SheetHeader>\n <div className=\"py-4\">{/* formulario */}</div>\n <SheetFooter>\n <SheetClose asChild>\n <Button variant=\"ghost\">Cerrar</Button>\n </SheetClose>\n </SheetFooter>\n </SheetContent>\n</Sheet>\n```\n\n## Reglas\n\n- Usa `Sheet` para contenido secundario que necesita más espacio que un `Dialog`.\n- No uses `Sheet` para confirmaciones simples; usa `Dialog` o `AppDialog`.\n- El panel tiene ancho máximo `sm:max-w-sm` en lados (responsive).\n- Incluye `SheetHeader` + `SheetTitle` para accesibilidad.\n- Usa `side=\"bottom\"` para layouts mobile.\n\n## Import\n\n```tsx\nimport {\n Sheet,\n SheetTrigger,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetFooter,\n SheetClose,\n} from \"@/components/base/Sheet\";\n```\n\n## Props\n\n### `SheetContent`\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `side` | `\"top\" \\| \"bottom\" \\| \"left\" \\| \"right\"` | `\"right\"` | Desde qué borde aparece el panel. |\n| `className` | `string?` | — | Clase adicional. |\n\n### `Sheet` (Root)\n\n| Prop | Tipo | Descripción |\n|---|---|---|\n| `open` | `boolean?` | Estado controlado. |\n| `onOpenChange` | `(boolean) => void` | Callback de cambio. |\n\n## Para IA\n\n1. Identifica si el contenido es secundario y necesita más espacio que un `Dialog`.\n2. Si es secundario/formulario largo → usa `Sheet`.\n3. Elige el `side` apropiado (`right` para desktop, `bottom` para mobile).\n4. Incluye `SheetHeader` + `SheetTitle` para accesibilidad.\n5. Usa `SheetFooter` para botones de acción al final.\n6. Solo usa `Dialog` para confirmaciones simples o alertas.\n"
23
+ "content": "# Sheet\nPanel lateral deslizante. Alternativa a `Dialog` para contenido secundario que\nrequiere más espacio o contexto persistente.\n\n## Cuándo usarlo\n\nPara tareas secundarias que no ameritan salir de la página: un detalle rápido, un formulario de creación, filtros avanzados. Se desliza desde un borde y mantiene visible el contexto de fondo.\n\n## Composición\n\nComposición Radix: `SheetTrigger` abre `SheetContent` (lado configurable), con `SheetHeader` (`SheetTitle`, `SheetDescription`), cuerpo libre y `SheetFooter`; `SheetClose` cierra.\n\n## Uso básico\n\n```tsx\n<Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\">Abrir panel</Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>Configuración</SheetTitle>\n <SheetDescription>Ajusta las preferencias de tu cuenta.</SheetDescription>\n </SheetHeader>\n <div className=\"py-4\">{/* formulario */}</div>\n <SheetFooter>\n <SheetClose asChild>\n <Button variant=\"ghost\">Cerrar</Button>\n </SheetClose>\n </SheetFooter>\n </SheetContent>\n</Sheet>\n```\n\n## Reglas\n\n- Usa `Sheet` para contenido secundario que necesita más espacio que un `Dialog`.\n- No uses `Sheet` para confirmaciones simples; usa `Dialog` o `AppDialog`.\n- El panel tiene ancho máximo `sm:max-w-sm` en lados (responsive).\n- Incluye `SheetHeader` + `SheetTitle` para accesibilidad.\n- Usa `side=\"bottom\"` para layouts mobile.\n\n## Import\n\n```tsx\nimport {\n Sheet,\n SheetTrigger,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetFooter,\n SheetClose,\n} from \"@/components/base/Sheet\";\n```\n\n## Props\n\n### `SheetContent`\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `side` | `\"top\" \\| \"bottom\" \\| \"left\" \\| \"right\"` | `\"right\"` | Desde qué borde aparece el panel. |\n| `className` | `string?` | — | Clase adicional. |\n\n### `Sheet` (Root)\n\n| Prop | Tipo | Descripción |\n|---|---|---|\n| `open` | `boolean?` | Estado controlado. |\n| `onOpenChange` | `(boolean) => void` | Callback de cambio. |\n\n## Para IA\n\n1. Identifica si el contenido es secundario y necesita más espacio que un `Dialog`.\n2. Si es secundario/formulario largo → usa `Sheet`.\n3. Elige el `side` apropiado (`right` para desktop, `bottom` para mobile).\n4. Incluye `SheetHeader` + `SheetTitle` para accesibilidad.\n5. Usa `SheetFooter` para botones de acción al final.\n6. Solo usa `Dialog` para confirmaciones simples o alertas.\n"
24
24
  }
25
25
  ]
26
26
  }
@@ -3,7 +3,7 @@
3
3
  "name": "sidebar",
4
4
  "type": "registry:ui",
5
5
  "title": "Sidebar",
6
- "description": "Primitivas internas (estilo shadcn) que usa `AppSidebar` por debajo. **No las compongas a mano para una barra de navegación: usa `AppSidebar`** (data-driven). Recurre a estas primitivas solo para layouts a medida. El componente vive en tu proyecto: instálalo con `create-lexy add sidebar` y edítalo con libertad.",
6
+ "description": "Primitivas internas (estilo shadcn) que usa `AppSidebar` por debajo. **No las compongas a mano para una barra de navegación: usa `AppSidebar`** (data-driven). Recurre a estas primitivas solo para layouts a medida.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-slot",
9
9
  "class-variance-authority",
@@ -22,13 +22,13 @@
22
22
  "path": "components/base/Sidebar.tsx",
23
23
  "type": "registry:ui",
24
24
  "target": "components/base/Sidebar.tsx",
25
- "content": "\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport { Input } from \"./Input\";\nimport { Separator } from \"./Separator\";\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from \"./Sheet\";\nimport { Skeleton } from \"./Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"./Tooltip\";\nimport { useIsMobile } from \"./useIsMobile\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"20rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"20rem\";\nconst SIDEBAR_WIDTH_ICON = \"4rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],\n );\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={0}>\n <div\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\n \"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n );\n },\n);\nSidebarProvider.displayName = \"SidebarProvider\";\n\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n }\n>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n <div\n className={cn(\n \"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n );\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n <SheetContent\n data-sidebar=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex h-full w-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n );\n }\n\n return (\n <div\n ref={ref}\n className=\"group peer hidden text-sidebar-foreground md:block\"\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n className={cn(\n \"relative h-svh w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n )}\n />\n <div\n className={cn(\n \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex\",\n side === \"left\"\n ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n // Adjust the padding for floating and inset variants.\n variant === \"floating\" || variant === \"inset\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]\"\n : \"bg-sidebar group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l border-sidebar-border\",\n className,\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n className=\"flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow\"\n >\n {children}\n </div>\n </div>\n </div>\n );\n },\n);\nSidebar.displayName = \"Sidebar\";\n\nconst SidebarTrigger = React.forwardRef<\n React.ElementRef<typeof Button>,\n React.ComponentProps<typeof Button>\n>(({ className, onClick, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <Button\n ref={ref}\n data-sidebar=\"trigger\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\"h-8 w-8 rounded-md hover:bg-secondary\", className)}\n onClick={(event) => {\n onClick?.(event);\n toggleSidebar();\n }}\n {...props}\n >\n <PanelLeft />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </Button>\n );\n});\nSidebarTrigger.displayName = \"SidebarTrigger\";\n\nconst SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <button\n ref={ref}\n data-sidebar=\"rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(\n \"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex\",\n \"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize\",\n \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n \"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarRail.displayName = \"SidebarRail\";\n\nconst SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<\"main\">>(\n ({ className, ...props }, ref) => {\n return (\n <main\n ref={ref}\n className={cn(\n \"relative flex w-full flex-1 flex-col bg-background\",\n \"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-lg md:peer-data-[variant=inset]:shadow\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarInset.displayName = \"SidebarInset\";\n\nconst SidebarInput = React.forwardRef<\n React.ElementRef<typeof Input>,\n React.ComponentProps<typeof Input>\n>(({ className, ...props }, ref) => {\n return (\n <Input\n ref={ref}\n data-sidebar=\"input\"\n className={cn(\n \"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarInput.displayName = \"SidebarInput\";\n\nconst SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarHeader.displayName = \"SidebarHeader\";\n\nconst SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"footer\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarFooter.displayName = \"SidebarFooter\";\n\nconst SidebarSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n React.ComponentProps<typeof Separator>\n>(({ className, ...props }, ref) => {\n return (\n <Separator\n ref={ref}\n data-sidebar=\"separator\"\n className={cn(\"mx-2 w-auto bg-sidebar-border\", className)}\n {...props}\n />\n );\n});\nSidebarSeparator.displayName = \"SidebarSeparator\";\n\nconst SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"content\"\n className={cn(\n \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/50\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarContent.displayName = \"SidebarContent\";\n\nconst SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarGroup.displayName = \"SidebarGroup\";\n\nconst SidebarGroupLabel = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-label\"\n className={cn(\n \"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\";\n\nconst SidebarGroupAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-action\"\n className={cn(\n \"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupAction.displayName = \"SidebarGroupAction\";\n\nconst SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"group-content\"\n className={cn(\"w-full text-sm\", className)}\n {...props}\n />\n ),\n);\nSidebarGroupContent.displayName = \"SidebarGroupContent\";\n\nconst SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n {...props}\n />\n ),\n);\nSidebarMenu.displayName = \"SidebarMenu\";\n\nconst SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n ),\n);\nSidebarMenuItem.displayName = \"SidebarMenuItem\";\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[color,background-color,width,height,padding] duration-200 hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-secondary data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"hover:bg-secondary hover:text-sidebar-foreground\",\n outline:\n \"bg-background shadow-[0_0_0_1px_var(--color-sidebar-border)] hover:bg-secondary hover:text-sidebar-foreground hover:shadow-[0_0_0_1px_var(--color-sidebar-border)]\",\n },\n size: {\n default: \"h-8 text-sm\",\n sm: \"h-7 text-xs\",\n lg: \"h-12 text-sm group-data-[collapsible=icon]:!p-0\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nconst SidebarMenuButton = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n isActive?: boolean;\n tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n } & VariantProps<typeof sidebarMenuButtonVariants>\n>(\n (\n {\n asChild = false,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const { isMobile, state } = useSidebar();\n\n const button = (\n <Comp\n ref={ref}\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n />\n );\n\n if (!tooltip) {\n return button;\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent\n side=\"right\"\n align=\"center\"\n hidden={state !== \"collapsed\" || isMobile}\n {...tooltip}\n />\n </Tooltip>\n );\n },\n);\nSidebarMenuButton.displayName = \"SidebarMenuButton\";\n\nconst SidebarMenuAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n showOnHover?: boolean;\n }\n>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-action\"\n className={cn(\n \"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-colors hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-foreground [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n showOnHover &&\n \"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuAction.displayName = \"SidebarMenuAction\";\n\nconst SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"menu-badge\"\n className={cn(\n \"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground\",\n \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\";\n\nconst SidebarMenuSkeleton = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n showIcon?: boolean;\n }\n>(({ className, showIcon = false, ...props }, ref) => {\n // Ancho aleatorio (50–90%) deliberado para variar el skeleton; estable\n // entre renders gracias al useMemo.\n const width = React.useMemo(() => {\n // eslint-disable-next-line react-hooks/purity -- aleatoriedad intencional, memoizada\n return `${Math.floor(Math.random() * 40) + 50}%`;\n }, []);\n\n return (\n <div\n ref={ref}\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && <Skeleton className=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />}\n <Skeleton\n className=\"h-4 max-w-(--skeleton-width) flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n );\n});\nSidebarMenuSkeleton.displayName = \"SidebarMenuSkeleton\";\n\nconst SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu-sub\"\n className={cn(\n \"mx-0 flex min-w-0 flex-col gap-1 px-0 py-0.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuSub.displayName = \"SidebarMenuSub\";\n\nconst SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ ...props }, ref) => <li ref={ref} {...props} />,\n);\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\";\n\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<\"a\"> & {\n asChild?: boolean;\n size?: \"sm\" | \"md\";\n isActive?: boolean;\n }\n>(({ asChild = false, size = \"md\", isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive}\n className={cn(\n \"flex h-8 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground/80 outline-none ring-sidebar-ring transition-colors duration-200 hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-70 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\",\n \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n size === \"sm\" && \"text-xs\",\n size === \"md\" && \"text-sm\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\";\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n"
25
+ "content": "\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport { Input } from \"./Input\";\nimport { Separator } from \"./Separator\";\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from \"./Sheet\";\nimport { Skeleton } from \"./Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"./Tooltip\";\nimport { useIsMobile } from \"./useIsMobile\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\n\n// Ancho del panel expandido (desktop). Default: Estándar (16rem / 256px) — el\n// punto resonante del sistema (4× el rail de 64px, 4:1 de contenido a 1280px) y\n// el estándar de la industria. Para cambiarlo en una instancia, pasa\n// `--sidebar-width` por `style` en <SidebarProvider>. Presets recomendados:\n// 14rem (224px) · Compacta — nav densa, máxima área de contenido\n// 16rem (256px) · Estándar — nav con contadores (default) ← recomendado\n// 17.5rem (280px) · Cómoda — labels largos o texto secundario por ítem\n// 20rem (320px) · Workspace — panel con switcher, búsqueda o árbol\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"20rem\"; // Sheet a pantalla casi completa en móvil\nconst SIDEBAR_WIDTH_ICON = \"4rem\"; // rail colapsado (64px) en collapsible=\"icon\"\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],\n );\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={0}>\n <div\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\n \"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n );\n },\n);\nSidebarProvider.displayName = \"SidebarProvider\";\n\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n }\n>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n <div\n className={cn(\n \"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n );\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n <SheetContent\n data-sidebar=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex h-full w-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n );\n }\n\n return (\n <div\n ref={ref}\n className=\"group peer hidden text-sidebar-foreground md:block\"\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n className={cn(\n \"relative h-svh w-(--sidebar-width) bg-transparent transition-[width] duration-[var(--duration-fluid)] ease-[var(--ease-fluid-glide)] motion-reduce:transition-none\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n )}\n />\n <div\n className={cn(\n \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-[var(--duration-fluid)] ease-[var(--ease-fluid-glide)] motion-reduce:transition-none md:flex\",\n side === \"left\"\n ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n // Adjust the padding for floating and inset variants.\n variant === \"floating\" || variant === \"inset\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]\"\n : \"bg-sidebar group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l border-sidebar-border\",\n className,\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n className=\"flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow\"\n >\n {children}\n </div>\n </div>\n </div>\n );\n },\n);\nSidebar.displayName = \"Sidebar\";\n\nconst SidebarTrigger = React.forwardRef<\n React.ElementRef<typeof Button>,\n React.ComponentProps<typeof Button>\n>(({ className, onClick, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <Button\n ref={ref}\n data-sidebar=\"trigger\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\"h-8 w-8 hover:bg-sidebar-hover\", className)}\n onClick={(event) => {\n onClick?.(event);\n toggleSidebar();\n }}\n {...props}\n >\n <PanelLeft />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </Button>\n );\n});\nSidebarTrigger.displayName = \"SidebarTrigger\";\n\nconst SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <button\n ref={ref}\n data-sidebar=\"rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(\n \"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all duration-[var(--duration-fast)] ease-[var(--ease-standard)] motion-reduce:transition-none after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex\",\n \"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize\",\n \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n \"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarRail.displayName = \"SidebarRail\";\n\nconst SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<\"main\">>(\n ({ className, ...props }, ref) => {\n return (\n <main\n ref={ref}\n className={cn(\n \"relative flex w-full flex-1 flex-col bg-background\",\n \"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-lg md:peer-data-[variant=inset]:shadow\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarInset.displayName = \"SidebarInset\";\n\nconst SidebarInput = React.forwardRef<\n React.ElementRef<typeof Input>,\n React.ComponentProps<typeof Input>\n>(({ className, ...props }, ref) => {\n return (\n <Input\n ref={ref}\n data-sidebar=\"input\"\n className={cn(\n \"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarInput.displayName = \"SidebarInput\";\n\nconst SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarHeader.displayName = \"SidebarHeader\";\n\nconst SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"footer\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarFooter.displayName = \"SidebarFooter\";\n\nconst SidebarSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n React.ComponentProps<typeof Separator>\n>(({ className, ...props }, ref) => {\n return (\n <Separator\n ref={ref}\n data-sidebar=\"separator\"\n className={cn(\"mx-2 w-auto bg-sidebar-border\", className)}\n {...props}\n />\n );\n});\nSidebarSeparator.displayName = \"SidebarSeparator\";\n\nconst SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"content\"\n className={cn(\n // Scrollbar fino: estándar (Firefox + Chrome moderno) vía\n // scrollbar-width/-color, y ::-webkit-* para Safari/Chrome antiguo.\n // Sin las estándar, Firefox mostraba su scrollbar por defecto (ancho).\n \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto [scrollbar-width:thin] [scrollbar-color:color-mix(in_oklab,var(--color-border)_50%,transparent)_transparent] group-data-[collapsible=icon]:overflow-hidden [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/50\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarContent.displayName = \"SidebarContent\";\n\nconst SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarGroup.displayName = \"SidebarGroup\";\n\nconst SidebarGroupLabel = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-label\"\n className={cn(\n \"flex h-8 shrink-0 items-center rounded-md px-2 type-meta font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-[var(--duration-normal)] ease-[var(--ease-standard)] motion-reduce:transition-none focus-visible:ring-2 [&>svg]:size-(--icon-size-inline) [&>svg]:shrink-0\",\n \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\";\n\nconst SidebarGroupAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-action\"\n className={cn(\n \"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-button p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-hover hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-(--icon-size-control) [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupAction.displayName = \"SidebarGroupAction\";\n\nconst SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"group-content\"\n className={cn(\"w-full type-supporting\", className)}\n {...props}\n />\n ),\n);\nSidebarGroupContent.displayName = \"SidebarGroupContent\";\n\nconst SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n {...props}\n />\n ),\n);\nSidebarMenu.displayName = \"SidebarMenu\";\n\nconst SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n ),\n);\nSidebarMenuItem.displayName = \"SidebarMenuItem\";\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-nav-item p-2 text-left type-supporting outline-none ring-sidebar-ring transition-[color,background-color,width,height,padding] duration-[var(--duration-fast)] ease-[var(--ease-standard)] motion-reduce:transition-none hover:bg-sidebar-hover hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[active=true]:[&_svg]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-hover data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-(--icon-size-inline) [&>svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"hover:bg-sidebar-hover hover:text-sidebar-foreground\",\n outline:\n \"bg-background shadow-[0_0_0_1px_var(--color-sidebar-border)] hover:bg-sidebar-hover hover:text-sidebar-foreground hover:shadow-[0_0_0_1px_var(--color-sidebar-border)]\",\n },\n size: {\n default: \"h-8\",\n sm: \"h-7 type-meta\",\n lg: \"h-12 group-data-[collapsible=icon]:!p-0\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nconst SidebarMenuButton = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n isActive?: boolean;\n tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n } & VariantProps<typeof sidebarMenuButtonVariants>\n>(\n (\n {\n asChild = false,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const { isMobile, state } = useSidebar();\n\n const button = (\n <Comp\n ref={ref}\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n />\n );\n\n if (!tooltip) {\n return button;\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent\n side=\"right\"\n align=\"center\"\n hidden={state !== \"collapsed\" || isMobile}\n {...tooltip}\n />\n </Tooltip>\n );\n },\n);\nSidebarMenuButton.displayName = \"SidebarMenuButton\";\n\nconst SidebarMenuAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n showOnHover?: boolean;\n }\n>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-action\"\n className={cn(\n \"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-button p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-colors hover:bg-sidebar-hover hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-foreground [&>svg]:size-(--icon-size-control) [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n showOnHover &&\n \"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuAction.displayName = \"SidebarMenuAction\";\n\nconst SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"menu-badge\"\n className={cn(\n \"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 type-meta font-medium tabular-nums text-sidebar-foreground\",\n \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\";\n\nconst SidebarMenuSkeleton = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n showIcon?: boolean;\n }\n>(({ className, showIcon = false, ...props }, ref) => {\n // Ancho aleatorio (50–90%) deliberado para variar el skeleton; estable\n // entre renders gracias al useMemo.\n const width = React.useMemo(() => {\n // eslint-disable-next-line react-hooks/purity -- aleatoriedad intencional, memoizada\n return `${Math.floor(Math.random() * 40) + 50}%`;\n }, []);\n\n return (\n <div\n ref={ref}\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && <Skeleton className=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />}\n <Skeleton\n className=\"h-4 max-w-(--skeleton-width) flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n );\n});\nSidebarMenuSkeleton.displayName = \"SidebarMenuSkeleton\";\n\nconst SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu-sub\"\n className={cn(\n \"mx-0 flex min-w-0 flex-col gap-1 px-0 py-0.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuSub.displayName = \"SidebarMenuSub\";\n\nconst SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ ...props }, ref) => <li ref={ref} {...props} />,\n);\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\";\n\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<\"a\"> & {\n asChild?: boolean;\n size?: \"sm\" | \"md\";\n isActive?: boolean;\n }\n>(({ asChild = false, size = \"md\", isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive}\n className={cn(\n \"flex h-8 min-w-0 items-center gap-2 overflow-hidden rounded-nav-item px-2 text-sidebar-foreground/80 outline-none ring-sidebar-ring transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)] motion-reduce:transition-none hover:bg-sidebar-hover hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-70 [&>span:last-child]:truncate [&>svg]:size-(--icon-size-inline) [&>svg]:shrink-0 [&>svg]:text-muted-foreground\",\n \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[active=true]:[&_svg]:text-sidebar-accent-foreground\",\n size === \"sm\" && \"type-meta\",\n size === \"md\" && \"type-supporting\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\";\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n"
26
26
  },
27
27
  {
28
28
  "path": "components/base/Sidebar.md",
29
29
  "type": "registry:file",
30
30
  "target": "components/base/Sidebar.md",
31
- "content": "# Sidebar — guía de uso\n\nPrimitivas internas (estilo shadcn) que usa `AppSidebar` por debajo. **No las compongas a mano para una barra de navegación: usa `AppSidebar`** (data-driven). Recurre a estas primitivas solo para layouts a medida. El componente vive en tu proyecto: instálalo con `create-lexy add sidebar` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPrimitivas internas para layouts laterales a medida (como la navegación de este showcase). Para la barra de navegación estándar de una app usa `AppSidebar` — estas piezas son el motor que corre debajo.\n\n## Composición\n\nKit completo estilo shadcn: `SidebarProvider` (estado), `Sidebar`, `SidebarHeader`/`SidebarContent`/`SidebarFooter`, grupos (`SidebarGroup*`), menús (`SidebarMenu*`), `SidebarTrigger` para colapsar y `SidebarInset` para el área de contenido.\n\n## Vía recomendada: AppSidebar\n\nPara una barra de navegación, **no** uses estas primitivas directamente. Describe la navegación como datos y deja que `AppSidebar` arme la composición correcta:\n\n```tsx\n<SidebarProvider>\n <AppSidebar groups={groups} user={user} logo={<Logo />} />\n <SidebarInset>{/* contenido */}</SidebarInset>\n</SidebarProvider>\n```\n\nLee `AppSidebar.md` para el modelo de datos (`groups`, `items`, `user`).\n\n## Cuándo SÍ usar las primitivas\n\nSolo para shells o composiciones a medida que no encajan en el modelo `groups` de `AppSidebar`:\n`SidebarProvider`, `SidebarInset`, `SidebarTrigger` para el armazón; y `SidebarGroup`, `SidebarMenu`, `SidebarMenuButton`, etc. si necesitas una estructura totalmente custom.\n\n## Reglas\n\n- Para navegación estándar → `AppSidebar`. No mezcles primitivas sueltas con `AppSidebar` en la misma barra.\n- No insertes piezas sueltas como `SidebarSeparator` dentro de una barra hecha con `AppSidebar`.\n- Si el caso es a medida, mantén un solo enfoque (todo primitivas) y documenta por qué no usaste `AppSidebar`.\n\n## Cuándo NO usar (directamente)\n\n- **Barra de navegación normal** → `AppSidebar`.\n- **Layout de app con header + sidebar** → `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n\n## Import\n\n```tsx\n// Para el armazón/contenedor (caso a medida):\nimport {\n SidebarProvider, SidebarInset, SidebarTrigger,\n} from \"@/components/base/Sidebar\";\n```\n\n## Exports\n\nContenedor: `Sidebar`, `SidebarProvider`, `SidebarInset`, `SidebarTrigger`, `SidebarRail`, `useSidebar`.\nEstructura: `SidebarHeader`, `SidebarContent`, `SidebarFooter`, `SidebarGroup`, `SidebarGroupLabel`, `SidebarGroupAction`, `SidebarGroupContent`, `SidebarSeparator`, `SidebarInput`.\nMenú: `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton`, `SidebarMenuAction`, `SidebarMenuBadge`, `SidebarMenuSkeleton`, `SidebarMenuSub`, `SidebarMenuSubItem`, `SidebarMenuSubButton`.\n\n## Para IA\n\n1. Por defecto, NO uses estas primitivas: usa `AppSidebar` y su prop `groups`.\n2. Usa `SidebarProvider`/`SidebarInset`/`SidebarTrigger` solo como armazón cuando un layout a medida lo exija.\n3. Si compones a mano, no mezcles con `AppSidebar`; documenta la decisión.\n"
31
+ "content": "# Sidebar\nPrimitivas internas (estilo shadcn) que usa `AppSidebar` por debajo. **No las compongas a mano para una barra de navegación: usa `AppSidebar`** (data-driven). Recurre a estas primitivas solo para layouts a medida.\n\n## Cuándo usarlo\n\nPrimitivas internas para layouts laterales a medida (como la navegación de este showcase). Para la barra de navegación estándar de una app usa `AppSidebar` — estas piezas son el motor que corre debajo.\n\n## Composición\n\nKit completo estilo shadcn: `SidebarProvider` (estado), `Sidebar`, `SidebarHeader`/`SidebarContent`/`SidebarFooter`, grupos (`SidebarGroup*`), menús (`SidebarMenu*`), `SidebarTrigger` para colapsar y `SidebarInset` para el área de contenido.\n\n## Vía recomendada: AppSidebar\n\nPara una barra de navegación, **no** uses estas primitivas directamente. Describe la navegación como datos y deja que `AppSidebar` arme la composición correcta:\n\n```tsx\n<SidebarProvider>\n <AppSidebar groups={groups} user={user} logo={<Logo />} />\n <SidebarInset>{/* contenido */}</SidebarInset>\n</SidebarProvider>\n```\n\nLee `AppSidebar.md` para el modelo de datos (`groups`, `items`, `user`).\n\n## Cuándo SÍ usar las primitivas\n\nSolo para shells o composiciones a medida que no encajan en el modelo `groups` de `AppSidebar`:\n`SidebarProvider`, `SidebarInset`, `SidebarTrigger` para el armazón; y `SidebarGroup`, `SidebarMenu`, `SidebarMenuButton`, etc. si necesitas una estructura totalmente custom.\n\n## Reglas\n\n- Para navegación estándar → `AppSidebar`. No mezcles primitivas sueltas con `AppSidebar` en la misma barra.\n- No insertes piezas sueltas como `SidebarSeparator` dentro de una barra hecha con `AppSidebar`.\n- Si el caso es a medida, mantén un solo enfoque (todo primitivas) y documenta por qué no usaste `AppSidebar`.\n\n## Cuándo NO usar (directamente)\n\n- **Barra de navegación normal** → `AppSidebar`.\n- **Layout de app con header + sidebar** → `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n\n## Import\n\n```tsx\n// Para el armazón/contenedor (caso a medida):\nimport {\n SidebarProvider, SidebarInset, SidebarTrigger,\n} from \"@/components/base/Sidebar\";\n```\n\n## Exports\n\nContenedor: `Sidebar`, `SidebarProvider`, `SidebarInset`, `SidebarTrigger`, `SidebarRail`, `useSidebar`.\nEstructura: `SidebarHeader`, `SidebarContent`, `SidebarFooter`, `SidebarGroup`, `SidebarGroupLabel`, `SidebarGroupAction`, `SidebarGroupContent`, `SidebarSeparator`, `SidebarInput`.\nMenú: `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton`, `SidebarMenuAction`, `SidebarMenuBadge`, `SidebarMenuSkeleton`, `SidebarMenuSub`, `SidebarMenuSubItem`, `SidebarMenuSubButton`.\n\n## Para IA\n\n1. Por defecto, NO uses estas primitivas: usa `AppSidebar` y su prop `groups`.\n2. Usa `SidebarProvider`/`SidebarInset`/`SidebarTrigger` solo como armazón cuando un layout a medida lo exija.\n3. Si compones a mano, no mezcles con `AppSidebar`; documenta la decisión.\n"
32
32
  }
33
33
  ]
34
34
  }
@@ -3,7 +3,7 @@
3
3
  "name": "skeleton",
4
4
  "type": "registry:ui",
5
5
  "title": "Skeleton",
6
- "description": "Esqueleto de carga. Para estados de espera. El componente vive en tu proyecto: instálalo con `create-lexy add skeleton` y edítalo con libertad.",
6
+ "description": "Esqueleto de carga. Para estados de espera.",
7
7
  "dependencies": [
8
8
  "class-variance-authority"
9
9
  ],
@@ -19,7 +19,7 @@
19
19
  "path": "components/base/Skeleton.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Skeleton.md",
22
- "content": "# Skeleton — guía de uso\n\nEsqueleto de carga. Para estados de espera. El componente vive en tu proyecto: instálalo con `create-lexy add skeleton` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara esperas donde conoces la forma del contenido que viene: filas de tabla, cards, un detalle. Mantiene el layout estable y hace la espera sentirse más corta. Para acciones puntuales (guardar, enviar), el `Spinner` en el botón comunica mejor.\n\n## Composición\n\nUna sola pieza (`Skeleton`) que dibujas con las dimensiones del contenido real: unos cuantos rectángulos bastan para sugerir la estructura.\n\n## Uso básico\n\n```tsx\n<Skeleton />\n```\n\n## Reglas\n\n- Usa Skeleton según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Skeleton } from \"@/components/base/Skeleton\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Skeleton desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
22
+ "content": "# Skeleton\nEsqueleto de carga. Para estados de espera.\n\n## Cuándo usarlo\n\nPara esperas donde conoces la forma del contenido que viene: filas de tabla, cards, un detalle. Mantiene el layout estable y hace la espera sentirse más corta. Para acciones puntuales (guardar, enviar), el `Spinner` en el botón comunica mejor.\n\n## Composición\n\nUna sola pieza (`Skeleton`) que dibujas con las dimensiones del contenido real: unos cuantos rectángulos bastan para sugerir la estructura.\n\n## Uso básico\n\n```tsx\n<Skeleton />\n```\n\n## Reglas\n\n- Usa Skeleton según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Skeleton } from \"@/components/base/Skeleton\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Skeleton desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "slider",
4
4
  "type": "registry:ui",
5
5
  "title": "Slider",
6
- "description": "Control deslizante. Para selección de rango numérico. El componente vive en tu proyecto: instálalo con `create-lexy add slider` y edítalo con libertad.",
6
+ "description": "Control deslizante. Para selección de rango numérico.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-slider"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Slider.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Slider.tsx",
16
- "content": "import * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Slider = React.forwardRef<\n React.ElementRef<typeof SliderPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <SliderPrimitive.Root\n ref={ref}\n className={cn(\"relative flex w-full touch-none select-none items-center\", className)}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-1.5 w-full grow overflow-hidden rounded-full bg-secondary\">\n <SliderPrimitive.Range className=\"absolute h-full bg-primary\" />\n </SliderPrimitive.Track>\n <SliderPrimitive.Thumb className=\"block h-4 w-4 cursor-pointer rounded-full border border-primary/50 bg-card shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\" />\n </SliderPrimitive.Root>\n));\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n"
16
+ "content": "import * as SliderPrimitive from \"@radix-ui/react-slider\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Slider = React.forwardRef<\n React.ElementRef<typeof SliderPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <SliderPrimitive.Root\n ref={ref}\n className={cn(\"relative flex w-full touch-none select-none items-center\", className)}\n {...props}\n >\n <SliderPrimitive.Track className=\"relative h-1.5 w-full grow overflow-hidden rounded-full bg-secondary\">\n <SliderPrimitive.Range className=\"absolute h-full bg-primary\" />\n </SliderPrimitive.Track>\n <SliderPrimitive.Thumb className=\"block h-4 w-4 cursor-pointer rounded-full border border-primary/50 bg-card shadow-raised transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\" />\n </SliderPrimitive.Root>\n));\nSlider.displayName = SliderPrimitive.Root.displayName;\n\nexport { Slider };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Slider.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Slider.md",
22
- "content": "# Slider — guía de uso\n\nControl deslizante. Para selección de rango numérico. El componente vive en tu proyecto: instálalo con `create-lexy add slider` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara elegir un valor o rango dentro de un continuo donde lo aproximado basta: montos estimados, porcentajes. Si el valor exacto importa (una cifra en pesos), un `Input` numérico es más honesto.\n\n## Composición\n\nUna sola pieza (`Slider`, Radix) de uno o dos pulgares (valor o rango). Acompáñalo del valor visible en texto.\n\n## Uso básico\n\n```tsx\n<Slider />\n```\n\n## Reglas\n\n- Usa Slider según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Slider } from \"@/components/base/Slider\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Slider desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
22
+ "content": "# Slider\nControl deslizante. Para selección de rango numérico.\n\n## Cuándo usarlo\n\nPara elegir un valor o rango dentro de un continuo donde lo aproximado basta: montos estimados, porcentajes. Si el valor exacto importa (una cifra en pesos), un `Input` numérico es más honesto.\n\n## Composición\n\nUna sola pieza (`Slider`, Radix) de uno o dos pulgares (valor o rango). Acompáñalo del valor visible en texto.\n\n## Uso básico\n\n```tsx\n<Slider />\n```\n\n## Reglas\n\n- Usa Slider según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Slider } from \"@/components/base/Slider\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Slider desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "snippet",
4
4
  "type": "registry:ui",
5
5
  "title": "Snippet",
6
- "description": "Fragmento de código. Para mostrar código con copia. El componente vive en tu proyecto: instálalo con `create-lexy add snippet` y edítalo con libertad.",
6
+ "description": "Fragmento de código. Para mostrar código con copia.",
7
7
  "dependencies": [
8
8
  "class-variance-authority"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Snippet.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Snippet.tsx",
16
- "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst snippetVariants = cva(\n // py-2 + py-0.5 del pre: el texto queda a la misma distancia del borde\n // superior e inferior que el botón de copiar (caja simétrica, 40px de alto).\n \"relative flex w-full min-w-0 items-start gap-3 rounded-md border bg-background px-3 py-2 text-sm text-foreground\",\n {\n variants: {\n variant: {\n default: \"border-border\",\n success: \"border-success/30\",\n warning: \"border-warning/30\",\n error: \"border-destructive/30\",\n },\n font: {\n mono: \"font-mono\",\n sans: \"\",\n },\n },\n defaultVariants: { variant: \"default\", font: \"mono\" },\n },\n);\n\nexport interface SnippetProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, \"children\">,\n VariantProps<typeof snippetVariants> {\n /** Texto exacto que se copia al portapapeles. */\n children: string;\n lines?: \"single\" | \"multi\";\n onCopy?: () => void;\n copiedDuration?: number;\n}\n\nexport function Snippet({\n variant,\n font,\n lines = \"single\",\n children,\n onCopy,\n copiedDuration = 1500,\n className,\n ...props\n}: SnippetProps) {\n const [copied, setCopied] = React.useState(false);\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(children);\n setCopied(true);\n onCopy?.();\n window.setTimeout(() => setCopied(false), copiedDuration);\n } catch {\n /* portapapeles no disponible */\n }\n };\n\n return (\n <div className={cn(snippetVariants({ variant, font }), className)} {...props}>\n <pre\n className={cn(\n \"min-w-0 flex-1 overflow-x-auto py-0.5\",\n lines === \"single\" ? \"whitespace-nowrap\" : \"max-h-48 whitespace-pre-wrap\",\n )}\n >\n <code>{children}</code>\n </pre>\n <button\n type=\"button\"\n onClick={copy}\n aria-label={copied ? \"Copiado\" : \"Copiar al portapapeles\"}\n className=\"m-0 flex shrink-0 cursor-pointer rounded-md p-1 text-muted-foreground transition-colors duration-150 hover:bg-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {copied ? (\n <svg\n className=\"h-4 w-4 text-success\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n ) : (\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 >\n <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n )}\n </button>\n </div>\n );\n}\n\nexport { snippetVariants };\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst snippetVariants = cva(\n // py-2 + py-0.5 del pre: el texto queda a la misma distancia del borde\n // superior e inferior que el botón de copiar (caja simétrica, 40px de alto).\n \"relative flex w-full min-w-0 items-start gap-3 rounded-md border bg-background px-3 py-2 type-supporting text-foreground\",\n {\n variants: {\n variant: {\n default: \"border-border\",\n success: \"border-success/30\",\n warning: \"border-warning/30\",\n error: \"border-destructive/30\",\n },\n font: {\n mono: \"font-mono\",\n sans: \"\",\n },\n },\n defaultVariants: { variant: \"default\", font: \"mono\" },\n },\n);\n\nexport interface SnippetProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, \"children\">,\n VariantProps<typeof snippetVariants> {\n /** Texto exacto que se copia al portapapeles. */\n children: string;\n lines?: \"single\" | \"multi\";\n onCopy?: () => void;\n copiedDuration?: number;\n}\n\nexport function Snippet({\n variant,\n font,\n lines = \"single\",\n children,\n onCopy,\n copiedDuration = 1500,\n className,\n ...props\n}: SnippetProps) {\n const [copied, setCopied] = React.useState(false);\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(children);\n setCopied(true);\n onCopy?.();\n window.setTimeout(() => setCopied(false), copiedDuration);\n } catch {\n /* portapapeles no disponible */\n }\n };\n\n return (\n <div className={cn(snippetVariants({ variant, font }), className)} {...props}>\n <pre\n className={cn(\n \"min-w-0 flex-1 overflow-x-auto py-0.5\",\n lines === \"single\" ? \"whitespace-nowrap\" : \"max-h-48 whitespace-pre-wrap\",\n )}\n >\n <code>{children}</code>\n </pre>\n <button\n type=\"button\"\n onClick={copy}\n aria-label={copied ? \"Copiado\" : \"Copiar al portapapeles\"}\n className=\"m-0 flex shrink-0 cursor-pointer rounded-button p-1 text-muted-foreground transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)] hover:bg-secondary hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none\"\n >\n {copied ? (\n <svg\n className=\"size-(--icon-size-control) text-success\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n ) : (\n <svg\n className=\"size-(--icon-size-control)\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n )}\n </button>\n </div>\n );\n}\n\nexport { snippetVariants };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Snippet.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Snippet.md",
22
- "content": "# Snippet — guía de uso\n\nFragmento de código. Para mostrar código con copia. El componente vive en tu proyecto: instálalo con `create-lexy add snippet` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara mostrar un comando o fragmento corto que la persona va a copiar: una instalación, un token, una URL. Para bloques largos de código con sintaxis, usa el bloque de código del documento, no un Snippet.\n\n## Composición\n\nUna sola pieza (`Snippet`): texto en mono con botón de copiado incorporado.\n\n## Uso básico\n\n```tsx\n<Snippet />\n```\n\n## Reglas\n\n- Usa Snippet según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Snippet } from \"@/components/base/Snippet\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Snippet desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
22
+ "content": "# Snippet\nFragmento de código. Para mostrar código con copia.\n\n## Cuándo usarlo\n\nPara mostrar un comando o fragmento corto que la persona va a copiar: una instalación, un token, una URL. Para bloques largos de código con sintaxis, usa el bloque de código del documento, no un Snippet.\n\n## Composición\n\nUna sola pieza (`Snippet`): texto en mono con botón de copiado incorporado.\n\n## Uso básico\n\n```tsx\n<Snippet />\n```\n\n## Reglas\n\n- Usa Snippet según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Snippet } from \"@/components/base/Snippet\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Snippet desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "spinner",
4
4
  "type": "registry:ui",
5
5
  "title": "Spinner",
6
- "description": "Indicador de carga visual. Usa el icono `Loader2` de Lucide con animación de rotación. El componente vive en tu proyecto: instálalo con `create-lexy add spinner` y edítalo con libertad.",
6
+ "description": "Indicador de carga visual. Usa el icono `Loader2` de Lucide con animación de rotación.",
7
7
  "dependencies": [
8
8
  "class-variance-authority",
9
9
  "lucide-react"
@@ -20,7 +20,7 @@
20
20
  "path": "components/base/Spinner.md",
21
21
  "type": "registry:file",
22
22
  "target": "components/base/Spinner.md",
23
- "content": "# Spinner — guía de uso\n\nIndicador de carga visual. Usa el icono `Loader2` de Lucide con animación de rotación. El componente vive en tu proyecto: instálalo con `create-lexy add spinner` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara esperas breves e indeterminadas, sobre todo dentro del control que las causó: el botón que guarda, el campo que busca. Si la espera tiene forma conocida (una lista que carga), prefiere `Skeleton`.\n\n## Composición\n\nUna sola pieza (`Spinner`: el icono `Loader2` de Lucide girando) con tamaños. Dentro de un `Button` deshabilitado comunica “trabajando”.\n\n## Uso básico\n\n```tsx\n<Spinner size=\"md\" />\n```\n\n## Tamaños\n\n```tsx\n<div className=\"flex gap-4\">\n <Spinner size=\"sm\" />\n <Spinner size=\"md\" />\n <Spinner size=\"lg\" />\n <Spinner size=\"xl\" />\n</div>\n```\n\n## Colores personalizados\n\n```tsx\n<Spinner className=\"text-destructive\" />\n<Spinner className=\"text-success\" />\n```\n\n## En botón cargando\n\n```tsx\n<button disabled className=\"inline-flex items-center gap-2 opacity-50\">\n <Spinner size=\"sm\" className=\"text-primary-foreground\" />\n Guardando...\n</button>\n```\n\n## Reglas\n\n- Usa `Spinner` para operaciones de corta duración (menos de 2-3 segundos).\n- Combina con texto descriptivo cuando el contexto no sea obvio.\n- Usa `size=\"sm\"` dentro de botones.\n- Usa `size=\"lg\"` o `\"xl\"` para estados de carga de página completa.\n- No uses `Spinner` si la operación dura más de 5 segundos → considera `Progress`.\n- No uses `Spinner` sin texto si hay múltiples operaciones simultáneas.\n\n## Import\n\n```tsx\nimport { Spinner } from \"@/shared/components/base/Spinner\"; // o @/components/base/Spinner\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `size` | `\"sm\" \\| \"md\" \\| \"lg\" \\| \"xl\"` | `\"md\"` | Tamaño del spinner. |\n| `className` | `string?` | — | Clase adicional. |\n\n## Para IA\n\n1. Identifica si hay una operación en progreso o carga de datos.\n2. Si es una operación corta → usa `Spinner`.\n3. Elige el tamaño según el contexto.\n4. Agrega texto descriptivo si el contexto no es obvio.\n5. Considera `Skeleton` si conoces la estructura del contenido que cargará.\n6. Considera `Progress` si la operación tiene progreso medible.\n"
23
+ "content": "# Spinner\nIndicador de carga visual. Usa el icono `Loader2` de Lucide con animación de rotación.\n\n## Cuándo usarlo\n\nPara esperas breves e indeterminadas, sobre todo dentro del control que las causó: el botón que guarda, el campo que busca. Si la espera tiene forma conocida (una lista que carga), prefiere `Skeleton`.\n\n## Composición\n\nUna sola pieza (`Spinner`: el icono `Loader2` de Lucide girando) con tamaños. Dentro de un `Button` deshabilitado comunica “trabajando”.\n\n## Uso básico\n\n```tsx\n<Spinner size=\"md\" />\n```\n\n## Tamaños\n\n```tsx\n<div className=\"flex gap-4\">\n <Spinner size=\"sm\" />\n <Spinner size=\"md\" />\n <Spinner size=\"lg\" />\n <Spinner size=\"xl\" />\n</div>\n```\n\n## Colores personalizados\n\n```tsx\n<Spinner className=\"text-destructive\" />\n<Spinner className=\"text-success\" />\n```\n\n## En botón cargando\n\n```tsx\n<button disabled className=\"inline-flex items-center gap-2 opacity-50\">\n <Spinner size=\"sm\" className=\"text-primary-foreground\" />\n Guardando...\n</button>\n```\n\n## Reglas\n\n- Usa `Spinner` para operaciones de corta duración (menos de 2-3 segundos).\n- Combina con texto descriptivo cuando el contexto no sea obvio.\n- Usa `size=\"sm\"` dentro de botones.\n- Usa `size=\"lg\"` o `\"xl\"` para estados de carga de página completa.\n- No uses `Spinner` si la operación dura más de 5 segundos → considera `Progress`.\n- No uses `Spinner` sin texto si hay múltiples operaciones simultáneas.\n\n## Import\n\n```tsx\nimport { Spinner } from \"@/shared/components/base/Spinner\"; // o @/components/base/Spinner\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `size` | `\"sm\" \\| \"md\" \\| \"lg\" \\| \"xl\"` | `\"md\"` | Tamaño del spinner. |\n| `className` | `string?` | — | Clase adicional. |\n\n## Para IA\n\n1. Identifica si hay una operación en progreso o carga de datos.\n2. Si es una operación corta → usa `Spinner`.\n3. Elige el tamaño según el contexto.\n4. Agrega texto descriptivo si el contexto no es obvio.\n5. Considera `Skeleton` si conoces la estructura del contenido que cargará.\n6. Considera `Progress` si la operación tiene progreso medible.\n"
24
24
  }
25
25
  ]
26
26
  }
@@ -3,7 +3,7 @@
3
3
  "name": "status-dot",
4
4
  "type": "registry:ui",
5
5
  "title": "StatusDot",
6
- "description": "Punto de estado con color, pensado para acompañar texto (estado de una fila, disponibilidad, conexión). El color **nunca** es la única señal: va con texto o `aria-label`. El componente vive en tu proyecto: instálalo con `create-lexy add status-dot` y edítalo con libertad.",
6
+ "description": "Punto de estado con color, pensado para acompañar texto (estado de una fila, disponibilidad, conexión). El color **nunca** es la única señal: va con texto o `aria-label`.",
7
7
  "dependencies": [
8
8
  "class-variance-authority"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/StatusDot.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/StatusDot.tsx",
16
- "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst statusDotVariants = cva(\"inline-block shrink-0 rounded-full\", {\n variants: {\n tone: {\n gray: \"bg-muted-foreground\",\n brand: \"bg-primary\",\n success: \"bg-success\",\n warning: \"bg-warning\",\n danger: \"bg-destructive\",\n info: \"bg-info\",\n },\n size: {\n sm: \"h-1.5 w-1.5\",\n md: \"h-2 w-2\",\n lg: \"h-2.5 w-2.5\",\n },\n },\n defaultVariants: { tone: \"gray\", size: \"md\" },\n});\n\nexport interface StatusDotProps\n extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof statusDotVariants> {\n /** Requerido cuando no hay texto (punto solo). */\n \"aria-label\"?: string;\n}\n\nconst StatusDot = React.forwardRef<HTMLSpanElement, StatusDotProps>(function StatusDot(\n { tone, size, children, \"aria-label\": ariaLabel, className, ...props },\n ref,\n) {\n return (\n <span ref={ref} className={cn(\"inline-flex min-w-0 items-center gap-2\", className)} {...props}>\n <span className={statusDotVariants({ tone, size })} aria-hidden />\n {children ? (\n <span className=\"text-sm text-foreground\">{children}</span>\n ) : (\n <span className=\"sr-only\">{ariaLabel ?? \"Estado\"}</span>\n )}\n </span>\n );\n});\nStatusDot.displayName = \"StatusDot\";\n\nexport { StatusDot, statusDotVariants };\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst statusDotVariants = cva(\"inline-block shrink-0 rounded-full\", {\n variants: {\n tone: {\n gray: \"bg-muted-foreground\",\n brand: \"bg-primary\",\n success: \"bg-success\",\n warning: \"bg-warning\",\n danger: \"bg-destructive\",\n info: \"bg-info\",\n },\n size: {\n sm: \"h-1.5 w-1.5\",\n md: \"h-2 w-2\",\n lg: \"h-2.5 w-2.5\",\n },\n },\n defaultVariants: { tone: \"gray\", size: \"md\" },\n});\n\nexport interface StatusDotProps\n extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof statusDotVariants> {\n /** Requerido cuando no hay texto (punto solo). */\n \"aria-label\"?: string;\n}\n\nconst StatusDot = React.forwardRef<HTMLSpanElement, StatusDotProps>(function StatusDot(\n { tone, size, children, \"aria-label\": ariaLabel, className, ...props },\n ref,\n) {\n return (\n <span ref={ref} className={cn(\"inline-flex min-w-0 items-center gap-2\", className)} {...props}>\n <span className={statusDotVariants({ tone, size })} aria-hidden />\n {children ? (\n <span className=\"type-supporting text-foreground\">{children}</span>\n ) : (\n <span className=\"sr-only\">{ariaLabel ?? \"Estado\"}</span>\n )}\n </span>\n );\n});\nStatusDot.displayName = \"StatusDot\";\n\nexport { StatusDot, statusDotVariants };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/StatusDot.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/StatusDot.md",
22
- "content": "# StatusDot — guía de uso\n\nPunto de estado con color, pensado para acompañar texto (estado de una fila, disponibilidad, conexión). El color **nunca** es la única señal: va con texto o `aria-label`. El componente vive en tu proyecto: instálalo con `create-lexy add status-dot` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara estado compacto en contexto denso: la columna de estado de una tabla, disponibilidad, conexión. El color nunca va solo — siempre con texto al lado o `aria-label`.\n\n## Composición\n\nUna sola pieza (`StatusDot`) con variantes de color semántico; convive con el texto del estado en la misma celda o línea.\n\n## Uso básico\n\n```tsx\n<StatusDot tone=\"success\">Activo</StatusDot>\n<StatusDot tone=\"warning\">Pendiente</StatusDot>\n<StatusDot tone=\"danger\">Vencido</StatusDot>\n```\n\nSi va solo (sin texto visible), agrega `aria-label`:\n\n```tsx\n<StatusDot tone=\"success\" aria-label=\"En línea\" />\n```\n\n## Reglas\n\n- Acompaña el punto con texto (`children`) o, si va solo, con `aria-label`. El color por sí solo no comunica estado (accesibilidad).\n- Usa el `tone` por significado: `success`/`warning`/`danger` para estados; `gray`/`info`/`brand` para neutros.\n- Mantén el texto de estado corto.\n\n## Cuándo NO usar\n\n- **Etiqueta de estado con fondo** (chip) → `Badge`.\n- **Conteo numérico** → `CounterBadge`.\n- **Chip removible / filtro** → `Tag`.\n\n## Import\n\n```tsx\nimport { StatusDot } from \"@/components/base/StatusDot\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `tone` | `\"gray\" \\| \"brand\" \\| \"success\" \\| \"warning\" \\| \"danger\" \\| \"info\"` | `\"gray\"` | Color del estado. |\n| `size` | `\"sm\" \\| \"md\" \\| \"lg\"` | `\"md\"` | Tamaño del punto. |\n| `children` | `ReactNode` | — | Texto del estado junto al punto. |\n\n## Para IA\n\n1. Úsalo para señalar el estado de un ítem o fila, casi siempre dentro de una `Table` o lista.\n2. Empareja el color con texto visible; si no hay texto, agrega `aria-label`.\n3. Elige `tone` por significado, no por estética.\n"
22
+ "content": "# StatusDot\nPunto de estado con color, pensado para acompañar texto (estado de una fila, disponibilidad, conexión). El color **nunca** es la única señal: va con texto o `aria-label`.\n\n## Cuándo usarlo\n\nPara estado compacto en contexto denso: la columna de estado de una tabla, disponibilidad, conexión. El color nunca va solo — siempre con texto al lado o `aria-label`.\n\n## Composición\n\nUna sola pieza (`StatusDot`) con variantes de color semántico; convive con el texto del estado en la misma celda o línea.\n\n## Uso básico\n\n```tsx\n<StatusDot tone=\"success\">Activo</StatusDot>\n<StatusDot tone=\"warning\">Pendiente</StatusDot>\n<StatusDot tone=\"danger\">Vencido</StatusDot>\n```\n\nSi va solo (sin texto visible), agrega `aria-label`:\n\n```tsx\n<StatusDot tone=\"success\" aria-label=\"En línea\" />\n```\n\n## Reglas\n\n- Acompaña el punto con texto (`children`) o, si va solo, con `aria-label`. El color por sí solo no comunica estado (accesibilidad).\n- Usa el `tone` por significado: `success`/`warning`/`danger` para estados; `gray`/`info`/`brand` para neutros.\n- Mantén el texto de estado corto.\n\n## Cuándo NO usar\n\n- **Etiqueta de estado con fondo** (chip) → `Badge`.\n- **Conteo numérico** → `CounterBadge`.\n- **Chip removible / filtro** → `Tag`.\n\n## Import\n\n```tsx\nimport { StatusDot } from \"@/components/base/StatusDot\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `tone` | `\"gray\" \\| \"brand\" \\| \"success\" \\| \"warning\" \\| \"danger\" \\| \"info\"` | `\"gray\"` | Color del estado. |\n| `size` | `\"sm\" \\| \"md\" \\| \"lg\"` | `\"md\"` | Tamaño del punto. |\n| `children` | `ReactNode` | — | Texto del estado junto al punto. |\n\n## Para IA\n\n1. Úsalo para señalar el estado de un ítem o fila, casi siempre dentro de una `Table` o lista.\n2. Empareja el color con texto visible; si no hay texto, agrega `aria-label`.\n3. Elige `tone` por significado, no por estética.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "switch",
4
4
  "type": "registry:ui",
5
5
  "title": "Switch",
6
- "description": "Interruptor de palanca. Para activar/desactivar estados. El componente vive en tu proyecto: instálalo con `create-lexy add switch` y edítalo con libertad.",
6
+ "description": "Interruptor de palanca. Para activar/desactivar estados.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-switch"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Switch.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Switch.tsx",
16
- "content": "import * as SwitchPrimitives from \"@radix-ui/react-switch\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n <SwitchPrimitives.Root\n className={cn(\n \"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n className,\n )}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb\n className={cn(\n \"pointer-events-none block h-5 w-5 rounded-full bg-card shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\",\n )}\n />\n </SwitchPrimitives.Root>\n));\nSwitch.displayName = SwitchPrimitives.Root.displayName;\n\nexport { Switch };\n"
16
+ "content": "import * as SwitchPrimitives from \"@radix-ui/react-switch\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n <SwitchPrimitives.Root\n className={cn(\n \"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n className,\n )}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb\n className={cn(\n \"pointer-events-none block h-5 w-5 rounded-full bg-card shadow-raised ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\",\n )}\n />\n </SwitchPrimitives.Root>\n));\nSwitch.displayName = SwitchPrimitives.Root.displayName;\n\nexport { Switch };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Switch.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Switch.md",
22
- "content": "# Switch — guía de uso\n\nInterruptor de palanca. Para activar/desactivar estados. El componente vive en tu proyecto: instálalo con `create-lexy add switch` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara activar o desactivar algo con efecto inmediato: notificaciones, un modo. Es un interruptor de luz — sin botón Guardar. Si el cambio se confirma después con el formulario, usa `Checkbox`.\n\n## Composición\n\nUna sola pieza (`Switch`) con su `Label` al lado. El estado se lee del propio control, no de un texto que cambia.\n\n## Uso básico\n\n```tsx\n<Switch />\n```\n\n## Reglas\n\n- Usa Switch según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Switch } from \"@/components/base/Switch\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Switch desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
22
+ "content": "# Switch\nInterruptor de palanca. Para activar/desactivar estados.\n\n## Cuándo usarlo\n\nPara activar o desactivar algo con efecto inmediato: notificaciones, un modo. Es un interruptor de luz — sin botón Guardar. Si el cambio se confirma después con el formulario, usa `Checkbox`.\n\n## Composición\n\nUna sola pieza (`Switch`) con su `Label` al lado. El estado se lee del propio control, no de un texto que cambia.\n\n## Uso básico\n\n```tsx\n<Switch />\n```\n\n## Reglas\n\n- Usa Switch según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Switch } from \"@/components/base/Switch\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Switch desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "table",
4
4
  "type": "registry:ui",
5
5
  "title": "Table",
6
- "description": "Tabla de datos para comparar/escanear filas y columnas. Componente compuesto (`TableHeader`, `TableContent`, `TableRow`, `TableCell` — también disponibles como alias `Table.Header`, `Table.Content`, `Table.Row`, `Table.Cell`) basado en CSS grid. El componente vive en tu proyecto: instálalo con `create-lexy add table` y edítalo con libertad.",
6
+ "description": "Tabla de datos para comparar/escanear filas y columnas. Componente compuesto (`TableHeader`, `TableContent`, `TableRow`, `TableCell` — también disponibles como alias `Table.Header`, `Table.Content`, `Table.Row`, `Table.Cell`) basado en CSS grid.",
7
7
  "dependencies": [
8
8
  "class-variance-authority"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Table.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Table.tsx",
16
- "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\n// Tabla en grid de divs (no <table>): el ancho de columnas se controla con\n// `columns` (grid-template-columns), lo que permite truncado y alineación\n// fiables. Compound: Table + TableHeader/TableContent/TableRow/TableCell;\n// los alias estáticos (Table.Header, …) se conservan por compatibilidad.\n\ntype TableVariant = \"normal\" | \"basic\" | \"striped\";\n\ninterface TableContextProps {\n columns?: string;\n variant: TableVariant;\n stickyHeader?: boolean;\n}\n\nconst TableContext = React.createContext<TableContextProps>({\n variant: \"normal\",\n stickyHeader: false,\n});\n\nconst tableVariants = cva(\n \"flex w-full flex-col overflow-hidden rounded-md border border-border bg-card text-sm text-foreground shadow-sm\",\n {\n variants: {\n variant: {\n normal: \"\",\n basic: \"border-none shadow-none\",\n striped: \"\",\n },\n },\n defaultVariants: { variant: \"normal\" },\n },\n);\n\nexport interface TableProps\n extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof tableVariants> {\n /** grid-template-columns (p. ej. \"2fr 1fr 1fr\"). Sin definir: columnas iguales. */\n columns?: string;\n /** Mantiene el header visible al hacer scroll vertical. */\n stickyHeader?: boolean;\n}\n\nconst TableRoot = React.forwardRef<HTMLDivElement, TableProps>(function Table(\n { children, className, columns, variant, stickyHeader = false, ...props },\n ref,\n) {\n const resolvedVariant = variant ?? \"normal\";\n return (\n <TableContext.Provider value={{ columns, variant: resolvedVariant, stickyHeader }}>\n <div ref={ref} role=\"table\" className={cn(tableVariants({ variant }), className)} {...props}>\n <div\n className={cn(\"w-full overflow-x-auto\", {\n \"max-h-125 overflow-y-auto\": stickyHeader,\n })}\n >\n {children}\n </div>\n </div>\n </TableContext.Provider>\n );\n});\n\nconst getGridStyles = (columns?: string) => {\n if (columns) return { gridTemplateColumns: columns };\n return { gridAutoFlow: \"column\" as const, gridAutoColumns: \"1fr\" };\n};\n\nconst TableHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableHeader({ children, className, style, ...props }, ref) {\n const { columns, variant, stickyHeader } = React.useContext(TableContext);\n\n return (\n <div\n ref={ref}\n role=\"rowgroup\"\n style={{ ...getGridStyles(columns), ...style }}\n className={cn(\n \"grid items-center px-4 py-3 font-semibold text-foreground\",\n {\n \"border-b border-border bg-background\": variant === \"basic\" || variant === \"normal\",\n \"bg-muted\": variant === \"striped\",\n \"sticky top-0 z-10 shadow-sm\": stickyHeader,\n },\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n },\n);\n\nconst TableContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableContent({ className, ...props }, ref) {\n return <div ref={ref} className={cn(\"flex flex-col\", className)} {...props} />;\n },\n);\n\nconst TableRow = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableRow({ children, className, style, onClick, onKeyDown, tabIndex, ...props }, ref) {\n const { columns, variant } = React.useContext(TableContext);\n const isInteractive = onClick != null;\n\n // Fila clickable: también operable por teclado (Enter/Espacio) y focusable.\n const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {\n onKeyDown?.(event);\n if (!isInteractive || event.defaultPrevented) return;\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n event.currentTarget.click();\n }\n };\n\n return (\n <div\n ref={ref}\n role=\"row\"\n onClick={onClick}\n onKeyDown={handleKeyDown}\n tabIndex={tabIndex ?? (isInteractive ? 0 : undefined)}\n style={{ ...getGridStyles(columns), ...style }}\n className={cn(\n \"grid items-center px-4 py-3 transition-colors hover:bg-muted/60\",\n {\n \"border-b border-border last:border-0\": variant === \"normal\",\n \"border-none\": variant === \"basic\",\n \"border-none odd:bg-muted/40 even:bg-card\": variant === \"striped\",\n \"cursor-pointer\": onClick,\n },\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n },\n);\n\nconst TableCell = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableCell({ className, ...props }, ref) {\n return (\n <div\n ref={ref}\n role=\"cell\"\n className={cn(\"truncate px-1 leading-relaxed text-muted-foreground\", className)}\n {...props}\n />\n );\n },\n);\n\n// Alias estáticos por compatibilidad con el uso Table.Header / Table.Row / …\nconst Table = Object.assign(TableRoot, {\n Header: TableHeader,\n Content: TableContent,\n Row: TableRow,\n Cell: TableCell,\n});\n\nexport { Table, TableCell, TableContent, TableHeader, TableRow, tableVariants };\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\n// Tabla en grid de divs (no <table>): el ancho de columnas se controla con\n// `columns` (grid-template-columns), lo que permite truncado y alineación\n// fiables. Compound: Table + TableHeader/TableContent/TableRow/TableCell;\n// los alias estáticos (Table.Header, …) se conservan por compatibilidad.\n\ntype TableVariant = \"normal\" | \"basic\" | \"striped\";\n\ninterface TableContextProps {\n columns?: string;\n variant: TableVariant;\n stickyHeader?: boolean;\n}\n\nconst TableContext = React.createContext<TableContextProps>({\n variant: \"normal\",\n stickyHeader: false,\n});\n\nconst tableVariants = cva(\n \"flex w-full flex-col overflow-hidden rounded-md border border-border bg-card type-supporting text-foreground\",\n {\n variants: {\n variant: {\n normal: \"\",\n basic: \"border-none shadow-none\",\n striped: \"\",\n },\n },\n defaultVariants: { variant: \"normal\" },\n },\n);\n\nexport interface TableProps\n extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof tableVariants> {\n /** grid-template-columns (p. ej. \"2fr 1fr 1fr\"). Sin definir: columnas iguales. */\n columns?: string;\n /** Mantiene el header visible al hacer scroll vertical. */\n stickyHeader?: boolean;\n}\n\nconst TableRoot = React.forwardRef<HTMLDivElement, TableProps>(function Table(\n { children, className, columns, variant, stickyHeader = false, ...props },\n ref,\n) {\n const resolvedVariant = variant ?? \"normal\";\n return (\n <TableContext.Provider value={{ columns, variant: resolvedVariant, stickyHeader }}>\n <div ref={ref} role=\"table\" className={cn(tableVariants({ variant }), className)} {...props}>\n <div\n className={cn(\"w-full overflow-x-auto\", {\n \"max-h-125 overflow-y-auto\": stickyHeader,\n })}\n >\n {children}\n </div>\n </div>\n </TableContext.Provider>\n );\n});\n\nconst getGridStyles = (columns?: string) => {\n if (columns) return { gridTemplateColumns: columns };\n return { gridAutoFlow: \"column\" as const, gridAutoColumns: \"1fr\" };\n};\n\nconst TableHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableHeader({ children, className, style, ...props }, ref) {\n const { columns, variant, stickyHeader } = React.useContext(TableContext);\n\n return (\n <div\n ref={ref}\n role=\"rowgroup\"\n style={{ ...getGridStyles(columns), ...style }}\n className={cn(\n \"grid items-center px-4 py-3 font-semibold text-foreground\",\n {\n \"border-b border-border bg-background\": variant === \"basic\" || variant === \"normal\",\n \"bg-muted\": variant === \"striped\",\n \"sticky top-0 z-10 shadow-raised\": stickyHeader,\n },\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n },\n);\n\nconst TableContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableContent({ className, ...props }, ref) {\n return <div ref={ref} className={cn(\"flex flex-col\", className)} {...props} />;\n },\n);\n\nconst TableRow = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableRow({ children, className, style, onClick, onKeyDown, tabIndex, ...props }, ref) {\n const { columns, variant } = React.useContext(TableContext);\n const isInteractive = onClick != null;\n\n // Fila clickable: también operable por teclado (Enter/Espacio) y focusable.\n const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (event) => {\n onKeyDown?.(event);\n if (!isInteractive || event.defaultPrevented) return;\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n event.currentTarget.click();\n }\n };\n\n return (\n <div\n ref={ref}\n role=\"row\"\n onClick={onClick}\n onKeyDown={handleKeyDown}\n tabIndex={tabIndex ?? (isInteractive ? 0 : undefined)}\n style={{ ...getGridStyles(columns), ...style }}\n className={cn(\n \"grid items-center px-4 py-3 transition-colors hover:bg-muted/60\",\n {\n \"border-b border-border last:border-0\": variant === \"normal\",\n \"border-none\": variant === \"basic\",\n \"border-none odd:bg-muted/40 even:bg-card\": variant === \"striped\",\n \"cursor-pointer\": onClick,\n },\n className,\n )}\n {...props}\n >\n {children}\n </div>\n );\n },\n);\n\nconst TableCell = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n function TableCell({ className, ...props }, ref) {\n return (\n <div\n ref={ref}\n role=\"cell\"\n className={cn(\"truncate px-1 leading-relaxed text-muted-foreground\", className)}\n {...props}\n />\n );\n },\n);\n\n// Alias estáticos por compatibilidad con el uso Table.Header / Table.Row / …\nconst Table = Object.assign(TableRoot, {\n Header: TableHeader,\n Content: TableContent,\n Row: TableRow,\n Cell: TableCell,\n});\n\nexport { Table, TableCell, TableContent, TableHeader, TableRow, tableVariants };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Table.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Table.md",
22
- "content": "# Table — guía de uso\n\nTabla de datos para comparar/escanear filas y columnas. Componente compuesto (`TableHeader`, `TableContent`, `TableRow`, `TableCell` — también disponibles como alias `Table.Header`, `Table.Content`, `Table.Row`, `Table.Cell`) basado en CSS grid. El componente vive en tu proyecto: instálalo con `create-lexy add table` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara comparar y escanear filas homogéneas: casos, facturas, personas. Es la pieza central del CRM — densidad cómoda, estado por fila y acciones cerca. Si cada elemento pide narrativa propia (foto, párrafos), una lista de cards cuenta mejor.\n\n## Composición\n\nCompuesto basado en CSS grid: `Table` define las columnas, `TableHeader` la fila de encabezados, `TableContent` el cuerpo y `TableRow`/`TableCell` cada fila y celda (también como alias `Table.Header`, `Table.Row`…). El estado por fila se acompaña con `StatusDot` o `Tag`.\n\n## Uso básico\n\n```tsx\n<Table columns=\"2fr 1fr 1fr\">\n <Table.Header>\n <Table.Cell>Cliente</Table.Cell>\n <Table.Cell>Estado</Table.Cell>\n <Table.Cell>Monto</Table.Cell>\n </Table.Header>\n <Table.Content>\n {filas.map((f) => (\n <Table.Row key={f.id} onClick={() => abrir(f.id)}>\n <Table.Cell>{f.cliente}</Table.Cell>\n <Table.Cell><StatusDot tone={f.tone}>{f.estado}</StatusDot></Table.Cell>\n <Table.Cell>{f.monto}</Table.Cell>\n </Table.Row>\n ))}\n </Table.Content>\n</Table>\n```\n\n## Reglas\n\n- Las celdas de `Table.Header` y de cada `Table.Row` deben tener el **mismo número** de columnas que `columns`.\n- Usa `columns` con `fr`/anchos para alinear; no metas anchos a mano por celda.\n- Si las filas son clicables (`onClick`), que toda la fila sea el objetivo y el cursor lo indique.\n- Para estado por fila combina con `StatusDot`/`Badge` (texto, no solo color).\n- `stickyHeader` para listas largas; mantén el header escaneable.\n\n## Cuándo NO usar\n\n- **Lista de un solo dato por ítem** → una lista simple, no una tabla.\n- **Tarjetas con jerarquía visual rica** → `Card`/`FeatureCard`.\n- **Pocos pares clave-valor de un registro** → lista de definición (`dl`) o `Card`, no tabla.\n\n## Import\n\n```tsx\nimport { Table } from \"@/components/base/Table\";\n```\n\n## Props\n\n### `Table`\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `columns` | `string` | — | `grid-template-columns` (p. ej. `\"2fr 1fr 1fr\"`). Si se omite, columnas iguales automáticas. |\n| `variant` | `\"normal\" \\| \"basic\" \\| \"striped\"` | `\"normal\"` | Con borde/relleno, sin bordes, o filas alternadas. |\n| `stickyHeader` | `boolean` | `false` | Header fijo con scroll vertical (alto máx. ~500px). |\n\n### Subcomponentes\n`TableHeader`, `TableContent`, `TableRow` (acepta `onClick`), `TableCell` — alias: `Table.Header`, `Table.Content`, `Table.Row`, `Table.Cell`. Todos aceptan `className`, esparcen `...props` al nodo raíz y reenvían `ref`. `tableVariants` (CVA) se exporta para extender estilos.\n\n## Para IA\n\n1. Usa tabla solo cuando hay varias columnas que se comparan entre filas.\n2. Define `columns` y respeta ese número de `Table.Cell` en header y filas.\n3. Elige `variant` (`striped` ayuda a escanear filas largas; `basic` para tablas embebidas).\n4. Para estados, añade `StatusDot`/`Badge` con texto, no solo color.\n5. Si las filas navegan, usa `onClick` en `Table.Row` y deja claro que es clicable.\n"
22
+ "content": "# Table\nTabla de datos para comparar/escanear filas y columnas. Componente compuesto (`TableHeader`, `TableContent`, `TableRow`, `TableCell` — también disponibles como alias `Table.Header`, `Table.Content`, `Table.Row`, `Table.Cell`) basado en CSS grid.\n\n## Cuándo usarlo\n\nPara comparar y escanear filas homogéneas: casos, facturas, personas. Es la pieza central del CRM — densidad cómoda, estado por fila y acciones cerca. Si cada elemento pide narrativa propia (foto, párrafos), una lista de cards cuenta mejor.\n\n## Composición\n\nCompuesto basado en CSS grid: `Table` define las columnas, `TableHeader` la fila de encabezados, `TableContent` el cuerpo y `TableRow`/`TableCell` cada fila y celda (también como alias `Table.Header`, `Table.Row`…). El estado por fila se acompaña con `StatusDot` o `Tag`.\n\n## Uso básico\n\n```tsx\n<Table columns=\"2fr 1fr 1fr\">\n <Table.Header>\n <Table.Cell>Cliente</Table.Cell>\n <Table.Cell>Estado</Table.Cell>\n <Table.Cell>Monto</Table.Cell>\n </Table.Header>\n <Table.Content>\n {filas.map((f) => (\n <Table.Row key={f.id} onClick={() => abrir(f.id)}>\n <Table.Cell>{f.cliente}</Table.Cell>\n <Table.Cell><StatusDot tone={f.tone}>{f.estado}</StatusDot></Table.Cell>\n <Table.Cell>{f.monto}</Table.Cell>\n </Table.Row>\n ))}\n </Table.Content>\n</Table>\n```\n\n## Reglas\n\n- Las celdas de `Table.Header` y de cada `Table.Row` deben tener el **mismo número** de columnas que `columns`.\n- Usa `columns` con `fr`/anchos para alinear; no metas anchos a mano por celda.\n- Si las filas son clicables (`onClick`), que toda la fila sea el objetivo y el cursor lo indique.\n- Para estado por fila combina con `StatusDot`/`Badge` (texto, no solo color).\n- `stickyHeader` para listas largas; mantén el header escaneable.\n\n## Cuándo NO usar\n\n- **Lista de un solo dato por ítem** → una lista simple, no una tabla.\n- **Tarjetas con jerarquía visual rica** → `Card`/`FeatureCard`.\n- **Pocos pares clave-valor de un registro** → lista de definición (`dl`) o `Card`, no tabla.\n\n## Import\n\n```tsx\nimport { Table } from \"@/components/base/Table\";\n```\n\n## Props\n\n### `Table`\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `columns` | `string` | — | `grid-template-columns` (p. ej. `\"2fr 1fr 1fr\"`). Si se omite, columnas iguales automáticas. |\n| `variant` | `\"normal\" \\| \"basic\" \\| \"striped\"` | `\"normal\"` | Con borde/relleno, sin bordes, o filas alternadas. |\n| `stickyHeader` | `boolean` | `false` | Header fijo con scroll vertical (alto máx. ~500px). |\n\n### Subcomponentes\n`TableHeader`, `TableContent`, `TableRow` (acepta `onClick`), `TableCell` — alias: `Table.Header`, `Table.Content`, `Table.Row`, `Table.Cell`. Todos aceptan `className`, esparcen `...props` al nodo raíz y reenvían `ref`. `tableVariants` (CVA) se exporta para extender estilos.\n\n## Para IA\n\n1. Usa tabla solo cuando hay varias columnas que se comparan entre filas.\n2. Define `columns` y respeta ese número de `Table.Cell` en header y filas.\n3. Elige `variant` (`striped` ayuda a escanear filas largas; `basic` para tablas embebidas).\n4. Para estados, añade `StatusDot`/`Badge` con texto, no solo color.\n5. Si las filas navegan, usa `onClick` en `Table.Row` y deja claro que es clicable.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "tabs",
4
4
  "type": "registry:ui",
5
5
  "title": "Tabs",
6
- "description": "Pestañas para alternar entre vistas **del mismo nivel** sin salir de la página (Radix). Progressive disclosure: muestra una categoría a la vez. El componente vive en tu proyecto: instálalo con `create-lexy add tabs` y edítalo con libertad.",
6
+ "description": "Pestañas para alternar entre vistas **del mismo nivel** sin salir de la página (Radix). Progressive disclosure: muestra una categoría a la vez.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-tabs"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Tabs.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Tabs.tsx",
16
- "content": "import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.List\n ref={ref}\n className={cn(\n \"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex cursor-pointer items-center justify-center whitespace-nowrap rounded px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm\",\n className,\n )}\n {...props}\n />\n));\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n \"mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n className,\n )}\n {...props}\n />\n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger };\n"
16
+ "content": "import * as TabsPrimitive from \"@radix-ui/react-tabs\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.List\n ref={ref}\n className={cn(\n \"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(\n \"inline-flex cursor-pointer items-center justify-center whitespace-nowrap rounded-nav-item px-3 py-1.5 type-action-label transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground\",\n className,\n )}\n {...props}\n />\n));\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n \"mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n className,\n )}\n {...props}\n />\n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsContent, TabsList, TabsTrigger };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Tabs.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Tabs.md",
22
- "content": "# Tabs — guía de uso\n\nPestañas para alternar entre vistas **del mismo nivel** sin salir de la página (Radix). Progressive disclosure: muestra una categoría a la vez. El componente vive en tu proyecto: instálalo con `create-lexy add tabs` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara alternar entre vistas del mismo nivel sin salir de la página: las pestañas de un detalle (Actividad, Documentos, Notas). Una categoría a la vez, el resto a un click. Si los contenidos son pasos secuenciales, eso es un wizard, no tabs.\n\n## Composición\n\n`Tabs` (estado) contiene `TabsList` con un `TabsTrigger` por pestaña, y un `TabsContent` por panel. Trigger y panel se emparejan por `value`.\n\n## Uso básico\n\n```tsx\n<Tabs defaultValue=\"resumen\">\n <TabsList>\n <TabsTrigger value=\"resumen\">Resumen</TabsTrigger>\n <TabsTrigger value=\"documentos\">Documentos</TabsTrigger>\n <TabsTrigger value=\"actividad\">Actividad</TabsTrigger>\n </TabsList>\n <TabsContent value=\"resumen\">…</TabsContent>\n <TabsContent value=\"documentos\">…</TabsContent>\n <TabsContent value=\"actividad\">…</TabsContent>\n</Tabs>\n```\n\n## Reglas\n\n- Cada `TabsTrigger.value` debe tener su `TabsContent.value` correspondiente.\n- Las pestañas son **pares**: contenido del mismo rango que no necesitas ver junto.\n- Pocas pestañas (idealmente ≤5) con etiquetas cortas y escaneables.\n- No escondas detrás de tabs información crítica o de error que el usuario debe ver siempre.\n\n## Cuándo NO usar\n\n- **Pasos secuenciales de un flujo** → usa un stepper/wizard; las tabs no implican orden.\n- **Navegación entre páginas/rutas** → `NavigationMenu` o enlaces.\n- **Mostrar/ocultar detalle puntual** → `Accordion` o `Dialog`.\n\n## Import\n\n```tsx\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from \"@/components/base/Tabs\";\n```\n\n## Props (esenciales)\n\n| Parte | Prop | Descripción |\n|---|---|---|\n| `Tabs` | `value` / `defaultValue` | Pestaña activa (controlada / inicial). |\n| `Tabs` | `onValueChange` | Cambio de pestaña. |\n| `TabsTrigger` | `value` | **Requerido.** Debe coincidir con un `TabsContent`. |\n| `TabsContent` | `value` | **Requerido.** Empareja con su trigger. |\n\n## Para IA\n\n1. Confirma que son categorías pares del mismo contexto (no pasos, no rutas).\n2. Mantén ≤5 pestañas con labels en sentence case.\n3. Empareja cada `value` trigger↔content.\n4. No ocultes errores, costos o próximos pasos dentro de una pestaña inactiva.\n"
22
+ "content": "# Tabs\nPestañas para alternar entre vistas **del mismo nivel** sin salir de la página (Radix). Progressive disclosure: muestra una categoría a la vez.\n\n## Cuándo usarlo\n\nPara alternar entre vistas del mismo nivel sin salir de la página: las pestañas de un detalle (Actividad, Documentos, Notas). Una categoría a la vez, el resto a un click. Si los contenidos son pasos secuenciales, eso es un wizard, no tabs.\n\n## Composición\n\n`Tabs` (estado) contiene `TabsList` con un `TabsTrigger` por pestaña, y un `TabsContent` por panel. Trigger y panel se emparejan por `value`.\n\n## Uso básico\n\n```tsx\n<Tabs defaultValue=\"resumen\">\n <TabsList>\n <TabsTrigger value=\"resumen\">Resumen</TabsTrigger>\n <TabsTrigger value=\"documentos\">Documentos</TabsTrigger>\n <TabsTrigger value=\"actividad\">Actividad</TabsTrigger>\n </TabsList>\n <TabsContent value=\"resumen\">…</TabsContent>\n <TabsContent value=\"documentos\">…</TabsContent>\n <TabsContent value=\"actividad\">…</TabsContent>\n</Tabs>\n```\n\n## Reglas\n\n- Cada `TabsTrigger.value` debe tener su `TabsContent.value` correspondiente.\n- Las pestañas son **pares**: contenido del mismo rango que no necesitas ver junto.\n- Pocas pestañas (idealmente ≤5) con etiquetas cortas y escaneables.\n- No escondas detrás de tabs información crítica o de error que el usuario debe ver siempre.\n\n## Cuándo NO usar\n\n- **Pasos secuenciales de un flujo** → usa un stepper/wizard; las tabs no implican orden.\n- **Navegación entre páginas/rutas** → `NavigationMenu` o enlaces.\n- **Mostrar/ocultar detalle puntual** → `Accordion` o `Dialog`.\n\n## Import\n\n```tsx\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from \"@/components/base/Tabs\";\n```\n\n## Props (esenciales)\n\n| Parte | Prop | Descripción |\n|---|---|---|\n| `Tabs` | `value` / `defaultValue` | Pestaña activa (controlada / inicial). |\n| `Tabs` | `onValueChange` | Cambio de pestaña. |\n| `TabsTrigger` | `value` | **Requerido.** Debe coincidir con un `TabsContent`. |\n| `TabsContent` | `value` | **Requerido.** Empareja con su trigger. |\n\n## Para IA\n\n1. Confirma que son categorías pares del mismo contexto (no pasos, no rutas).\n2. Mantén ≤5 pestañas con labels en sentence case.\n3. Empareja cada `value` trigger↔content.\n4. No ocultes errores, costos o próximos pasos dentro de una pestaña inactiva.\n"
23
23
  }
24
24
  ]
25
25
  }
package/assets/r/tag.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "tag",
4
4
  "type": "registry:ui",
5
5
  "title": "Tag",
6
- "description": "Etiqueta de categoría. Para clasificación. El componente vive en tu proyecto: instálalo con `create-lexy add tag` y edítalo con libertad.",
6
+ "description": "Etiqueta de categoría. Para clasificación.",
7
7
  "dependencies": [
8
8
  "class-variance-authority"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Tag.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Tag.tsx",
16
- "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst tagVariants = cva(\n \"inline-flex select-none items-center gap-1 border text-xs font-medium leading-none\",\n {\n variants: {\n tone: {\n gray: \"border-border bg-secondary text-secondary-foreground\",\n brand: \"border-primary/20 bg-primary/10 text-primary\",\n success: \"border-success/20 bg-success/10 text-success\",\n warning: \"border-warning/20 bg-warning/10 text-warning\",\n danger: \"border-destructive/20 bg-destructive/10 text-destructive\",\n },\n shape: {\n square: \"rounded\",\n rounded: \"rounded-full\",\n },\n size: {\n sm: \"h-6 px-2 py-0.5\",\n md: \"h-7 px-2.5 py-1\",\n lg: \"h-8 px-3 py-1\",\n },\n },\n defaultVariants: { tone: \"gray\", shape: \"square\", size: \"sm\" },\n },\n);\n\nexport interface TagProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"color\">, VariantProps<typeof tagVariants> {\n removable?: boolean;\n onRemove?: () => void;\n}\n\nconst Tag = React.forwardRef<HTMLSpanElement, TagProps>(function Tag(\n { tone, shape, size, removable, onRemove, children, className, ...props },\n ref,\n) {\n return (\n <span ref={ref} className={cn(tagVariants({ tone, shape, size }), className)} {...props}>\n {children}\n {removable && (\n <button\n type=\"button\"\n onClick={onRemove}\n aria-label=\"Quitar\"\n className=\"-mr-0.5 inline-flex cursor-pointer items-center justify-center rounded-full opacity-60 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-current\"\n >\n <svg\n className=\"h-3 w-3\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n )}\n </span>\n );\n});\nTag.displayName = \"Tag\";\n\nexport { Tag, tagVariants };\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst tagVariants = cva(\n \"inline-flex select-none items-center gap-1 border font-medium\",\n {\n variants: {\n tone: {\n gray: \"border-border bg-secondary text-secondary-foreground\",\n brand: \"border-primary/20 bg-primary/10 text-primary\",\n success: \"border-success/30 bg-success/10 text-success-strong\",\n warning: \"border-warning/30 bg-warning/10 text-warning-strong\",\n danger: \"border-destructive/20 bg-destructive/10 text-destructive\",\n },\n shape: {\n square: \"rounded\",\n rounded: \"rounded-full\",\n },\n size: {\n sm: \"h-6 px-2 type-meta\",\n md: \"h-7 px-2.5 type-supporting\",\n lg: \"h-8 px-3 type-body\",\n },\n },\n defaultVariants: { tone: \"gray\", shape: \"square\", size: \"sm\" },\n },\n);\n\nexport interface TagProps\n extends Omit<React.HTMLAttributes<HTMLSpanElement>, \"color\">, VariantProps<typeof tagVariants> {\n removable?: boolean;\n onRemove?: () => void;\n}\n\nconst Tag = React.forwardRef<HTMLSpanElement, TagProps>(function Tag(\n { tone, shape, size, removable, onRemove, children, className, ...props },\n ref,\n) {\n return (\n <span ref={ref} className={cn(tagVariants({ tone, shape, size }), className)} {...props}>\n {children}\n {removable && (\n <button\n type=\"button\"\n onClick={onRemove}\n aria-label=\"Quitar\"\n className=\"-mr-0.5 inline-flex cursor-pointer items-center justify-center rounded-button opacity-60 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-current\"\n >\n <svg\n className=\"h-3 w-3\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n )}\n </span>\n );\n});\nTag.displayName = \"Tag\";\n\nexport { Tag, tagVariants };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Tag.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Tag.md",
22
- "content": "# Tag — guía de uso\n\nEtiqueta de categoría. Para clasificación. El componente vive en tu proyecto: instálalo con `create-lexy add tag` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara clasificar: materia, categoría, etiqueta de un caso. Es taxonomía — si lo que comunicas es un estado del sistema (activo, pendiente), eso es un `Badge`.\n\n## Composición\n\nUna sola pieza (`Tag`) con variantes de color. Convive en filas de tabla y headers de detalle junto a `Badge` y `StatusDot`, cada uno con su papel.\n\n## Uso básico\n\n```tsx\n<Tag />\n```\n\n## Reglas\n\n- Usa Tag según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Tag } from \"@/components/base/Tag\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Tag desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
22
+ "content": "# Tag\nEtiqueta de categoría. Para clasificación.\n\n## Cuándo usarlo\n\nPara clasificar: materia, categoría, etiqueta de un caso. Es taxonomía — si lo que comunicas es un estado del sistema (activo, pendiente), eso es un `Badge`.\n\n## Composición\n\nUna sola pieza (`Tag`) con variantes de color. Convive en filas de tabla y headers de detalle junto a `Badge` y `StatusDot`, cada uno con su papel.\n\n## Uso básico\n\n```tsx\n<Tag>Laboral</Tag>\n<Tag size=\"md\">Familia</Tag>\n<Tag size=\"lg\">Contrato</Tag>\n```\n\n## Reglas\n\n- Usa Tag según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Tag } from \"@/components/base/Tag\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `tone` | `\"gray\" \\| \"brand\" \\| \"success\" \\| \"warning\" \\| \"danger\"` | `\"gray\"` | Tono visual. |\n| `shape` | `\"square\" \\| \"rounded\"` | `\"square\"` | Radio de la etiqueta. |\n| `size` | `\"sm\" \\| \"md\" \\| \"lg\"` | `\"sm\"` | Escala altura y tipografía: 12/16, 14/20 o 16/24. |\n| `removable` | `boolean` | `false` | Muestra el control para quitar la etiqueta. |\n| `onRemove` | `() => void` | — | Acción del control para quitar. |\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Tag desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
23
23
  }
24
24
  ]
25
25
  }