create-lexy 0.6.2 → 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 +97 -91
  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
@@ -3,7 +3,7 @@
3
3
  "name": "counter-badge",
4
4
  "type": "registry:ui",
5
5
  "title": "CounterBadge",
6
- "description": "Badge con contador numérico. Para notificaciones. El componente vive en tu proyecto: instálalo con `create-lexy add counter-badge` y edítalo con libertad.",
6
+ "description": "Badge con contador numérico. Para notificaciones.",
7
7
  "dependencies": [
8
8
  "class-variance-authority"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/CounterBadge.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/CounterBadge.tsx",
16
- "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nfunction formatCount(children: React.ReactNode, max?: number): React.ReactNode {\n if (max == null) return children;\n const n =\n typeof children === \"number\"\n ? children\n : typeof children === \"string\" && /^\\d+$/.test(children.trim())\n ? Number.parseInt(children, 10)\n : null;\n if (n == null || n <= max) return children;\n return `${max}+`;\n}\n\nconst counterBadgeVariants = cva(\n \"inline-flex select-none items-center justify-center rounded-full bg-primary font-semibold tabular-nums text-primary-foreground\",\n {\n variants: {\n size: {\n sm: \"h-4 min-w-4 px-1 text-xs leading-none\",\n md: \"h-5 min-w-5 px-1.5 text-xs leading-none\",\n lg: \"h-6 min-w-6 px-1.5 text-xs/[1]\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport interface CounterBadgeProps\n extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof counterBadgeVariants> {\n /** Tope numérico (ej. 99 → \"99+\"). */\n max?: number;\n}\n\nconst CounterBadge = React.forwardRef<HTMLSpanElement, CounterBadgeProps>(\n ({ size, max, className, children, ...props }, ref) => (\n <span ref={ref} className={cn(counterBadgeVariants({ size }), className)} {...props}>\n {formatCount(children, max)}\n </span>\n ),\n);\nCounterBadge.displayName = \"CounterBadge\";\n\nexport { CounterBadge, counterBadgeVariants };\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nfunction formatCount(children: React.ReactNode, max?: number): React.ReactNode {\n if (max == null) return children;\n const n =\n typeof children === \"number\"\n ? children\n : typeof children === \"string\" && /^\\d+$/.test(children.trim())\n ? Number.parseInt(children, 10)\n : null;\n if (n == null || n <= max) return children;\n return `${max}+`;\n}\n\nconst counterBadgeVariants = cva(\n \"inline-flex select-none items-center justify-center rounded-full bg-primary font-semibold tabular-nums text-primary-foreground\",\n {\n variants: {\n size: {\n sm: \"h-4 min-w-4 px-1 type-meta leading-none\",\n md: \"h-5 min-w-5 px-1.5 type-meta leading-none\",\n lg: \"h-6 min-w-6 px-1.5 type-meta leading-none\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport interface CounterBadgeProps\n extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof counterBadgeVariants> {\n /** Tope numérico (ej. 99 → \"99+\"). */\n max?: number;\n}\n\nconst CounterBadge = React.forwardRef<HTMLSpanElement, CounterBadgeProps>(\n ({ size, max, className, children, ...props }, ref) => (\n <span ref={ref} className={cn(counterBadgeVariants({ size }), className)} {...props}>\n {formatCount(children, max)}\n </span>\n ),\n);\nCounterBadge.displayName = \"CounterBadge\";\n\nexport { CounterBadge, counterBadgeVariants };\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/CounterBadge.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/CounterBadge.md",
22
- "content": "# CounterBadge — guía de uso\n\nBadge con contador numérico. Para notificaciones. El componente vive en tu proyecto: instálalo con `create-lexy add counter-badge` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara conteos que piden atención: mensajes sin leer, tareas pendientes. Vive junto al ítem que cuantifica (icono de campana, item de menú). Si el número no cambia ni pide acción, probablemente es un dato de tabla, no un contador.\n\n## Composición\n\nUna sola pieza (`CounterBadge`) que formatea el número (99+) y se ancla al elemento que acompaña.\n\n## Uso básico\n\n```tsx\n<CounterBadge />\n```\n\n## Reglas\n\n- Usa CounterBadge según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { CounterBadge } from \"@/components/base/CounterBadge\";\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 CounterBadge 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": "# CounterBadge\nBadge con contador numérico. Para notificaciones.\n\n## Cuándo usarlo\n\nPara conteos que piden atención: mensajes sin leer, tareas pendientes. Vive junto al ítem que cuantifica (icono de campana, item de menú). Si el número no cambia ni pide acción, probablemente es un dato de tabla, no un contador.\n\n## Composición\n\nUna sola pieza (`CounterBadge`) que formatea el número (99+) y se ancla al elemento que acompaña.\n\n## Uso básico\n\n```tsx\n<CounterBadge />\n```\n\n## Reglas\n\n- Usa CounterBadge según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { CounterBadge } from \"@/components/base/CounterBadge\";\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 CounterBadge 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
  }
@@ -19,7 +19,7 @@
19
19
  "path": "views/CasosDesk.tsx",
20
20
  "type": "registry:file",
21
21
  "target": "views/CasosDesk.tsx",
22
- "content": "import { Plus } from \"lucide-react\";\n\nimport { Button } from \"@/components/base/Button\";\nimport { Input } from \"@/components/base/Input\";\nimport {\n Pagination,\n PaginationContent,\n PaginationItem,\n PaginationLink,\n PaginationNext,\n PaginationPrevious,\n} from \"@/components/base/Pagination\";\nimport { StatusDot } from \"@/components/base/StatusDot\";\nimport { Table } from \"@/components/base/Table\";\n\n// Receta CRM — desk con tabla y acciones: ancho completo, toolbar con búsqueda\n// y acción primaria, tabla densa con estado por fila, paginación al pie.\n// Bajo md: la tabla densa pasa a lista y la toolbar apila (pauta §5).\n// Ver crm-desk.md.\n\nexport function CasosDesk() {\n return (\n <main className=\"w-full p-4\">\n <header className=\"mb-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-xl font-semibold text-foreground\">Casos</h1>\n <p className=\"text-sm text-muted-foreground\">24 activos · 3 vencen hoy</p>\n </div>\n <div className=\"flex w-full items-center gap-2 sm:w-auto\">\n <Input\n className=\"h-9 w-full sm:w-64\"\n placeholder=\"Buscar por cliente o RUT\"\n aria-label=\"Buscar casos\"\n />\n <Button size=\"sm\">\n <Plus /> Nuevo caso\n </Button>\n </div>\n </header>\n\n {/* Patrón §5: la tabla densa pasa a lista bajo md — misma información,\n detalle secundario colapsado, sin scroll horizontal a ciegas. */}\n <ul className=\"space-y-2 md:hidden\" aria-label=\"Casos\">\n <li className=\"rounded-md border border-border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"font-medium text-foreground\">María Pérez</p>\n <StatusDot tone=\"warning\">En revisión</StatusDot>\n </div>\n <p className=\"mt-1 text-sm text-muted-foreground\">Despido</p>\n <p className=\"mt-1 text-sm font-medium text-warning\">Vence hoy</p>\n </li>\n <li className=\"rounded-md border border-border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"font-medium text-foreground\">Juan Soto</p>\n <StatusDot tone=\"success\">Al día</StatusDot>\n </div>\n <p className=\"mt-1 text-sm text-muted-foreground\">Deuda</p>\n <p className=\"mt-1 text-sm text-muted-foreground\">12 días</p>\n </li>\n </ul>\n\n <div className=\"hidden md:block\">\n <Table columns=\"2fr 1.5fr 1fr 1fr\" stickyHeader>\n <Table.Header>\n <Table.Cell>Cliente</Table.Cell>\n <Table.Cell>Materia</Table.Cell>\n <Table.Cell>Estado</Table.Cell>\n <Table.Cell>Plazo</Table.Cell>\n </Table.Header>\n <Table.Content>\n <Table.Row onClick={() => {}}>\n <Table.Cell className=\"font-medium\">María Pérez</Table.Cell>\n <Table.Cell>Despido</Table.Cell>\n <Table.Cell>\n <StatusDot tone=\"warning\">En revisión</StatusDot>\n </Table.Cell>\n <Table.Cell className=\"text-warning\">Vence hoy</Table.Cell>\n </Table.Row>\n <Table.Row onClick={() => {}}>\n <Table.Cell className=\"font-medium\">Juan Soto</Table.Cell>\n <Table.Cell>Deuda</Table.Cell>\n <Table.Cell>\n <StatusDot tone=\"success\">Al día</StatusDot>\n </Table.Cell>\n <Table.Cell className=\"text-muted-foreground\">12 días</Table.Cell>\n </Table.Row>\n </Table.Content>\n </Table>\n </div>\n\n <footer className=\"mt-4 flex flex-col items-center gap-3 sm:flex-row sm:justify-between\">\n <p className=\"text-sm text-muted-foreground\">Mostrando 1–20 de 64 casos</p>\n <Pagination className=\"mx-0 w-auto justify-end\">\n <PaginationContent>\n <PaginationItem>\n <PaginationPrevious href=\"#\" />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\" isActive>\n 1\n </PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">2</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">3</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationNext href=\"#\" />\n </PaginationItem>\n </PaginationContent>\n </Pagination>\n </footer>\n </main>\n );\n}\n"
22
+ "content": "import { Plus } from \"lucide-react\";\n\nimport { Button } from \"@/components/base/Button\";\nimport { Input } from \"@/components/base/Input\";\nimport {\n Pagination,\n PaginationContent,\n PaginationItem,\n PaginationLink,\n PaginationNext,\n PaginationPrevious,\n} from \"@/components/base/Pagination\";\nimport { StatusDot } from \"@/components/base/StatusDot\";\nimport { Table } from \"@/components/base/Table\";\n\n// Receta CRM — desk con tabla y acciones: ancho completo, toolbar con búsqueda\n// y acción primaria, tabla densa con estado por fila, paginación al pie.\n// Bajo md: la tabla densa pasa a lista y la toolbar apila (pauta §5).\n// Ver crm-desk.md.\n\nexport function CasosDesk() {\n return (\n <main className=\"w-full p-4\">\n <header className=\"mb-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"type-subsection-title text-foreground\">Casos</h1>\n <p className=\"type-supporting text-muted-foreground\">24 activos · 3 vencen hoy</p>\n </div>\n <div className=\"flex w-full items-center gap-2 sm:w-auto\">\n <Input\n className=\"h-9 w-full sm:w-64\"\n placeholder=\"Buscar por cliente o RUT\"\n aria-label=\"Buscar casos\"\n />\n <Button size=\"sm\">\n <Plus /> Nuevo caso\n </Button>\n </div>\n </header>\n\n {/* Patrón §5: la tabla densa pasa a lista bajo md — misma información,\n detalle secundario colapsado, sin scroll horizontal a ciegas. */}\n <ul className=\"space-y-2 md:hidden\" aria-label=\"Casos\">\n <li className=\"rounded-md border border-border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"type-item-title text-foreground\">María Pérez</p>\n <StatusDot tone=\"warning\">En revisión</StatusDot>\n </div>\n <p className=\"mt-1 type-supporting text-muted-foreground\">Despido</p>\n <p className=\"mt-1 type-data text-warning-strong\">Vence hoy</p>\n </li>\n <li className=\"rounded-md border border-border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"type-item-title text-foreground\">Juan Soto</p>\n <StatusDot tone=\"success\">Al día</StatusDot>\n </div>\n <p className=\"mt-1 type-supporting text-muted-foreground\">Deuda</p>\n <p className=\"mt-1 type-data text-muted-foreground\">12 días</p>\n </li>\n </ul>\n\n <div className=\"hidden md:block\">\n <Table columns=\"2fr 1.5fr 1fr 1fr\" stickyHeader>\n <Table.Header>\n <Table.Cell>Cliente</Table.Cell>\n <Table.Cell>Materia</Table.Cell>\n <Table.Cell>Estado</Table.Cell>\n <Table.Cell>Plazo</Table.Cell>\n </Table.Header>\n <Table.Content>\n <Table.Row onClick={() => {}}>\n <Table.Cell className=\"type-item-title text-foreground\">María Pérez</Table.Cell>\n <Table.Cell>Despido</Table.Cell>\n <Table.Cell>\n <StatusDot tone=\"warning\">En revisión</StatusDot>\n </Table.Cell>\n <Table.Cell className=\"type-data text-warning-strong\">Vence hoy</Table.Cell>\n </Table.Row>\n <Table.Row onClick={() => {}}>\n <Table.Cell className=\"type-item-title text-foreground\">Juan Soto</Table.Cell>\n <Table.Cell>Deuda</Table.Cell>\n <Table.Cell>\n <StatusDot tone=\"success\">Al día</StatusDot>\n </Table.Cell>\n <Table.Cell className=\"type-data text-muted-foreground\">12 días</Table.Cell>\n </Table.Row>\n </Table.Content>\n </Table>\n </div>\n\n <footer className=\"mt-4 flex flex-col items-center gap-3 sm:flex-row sm:justify-between\">\n <p className=\"type-supporting text-muted-foreground\">Mostrando 1–20 de 64 casos</p>\n <Pagination className=\"mx-0 w-auto justify-end\">\n <PaginationContent>\n <PaginationItem>\n <PaginationPrevious href=\"#\" />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\" isActive>\n 1\n </PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">2</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">3</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationNext href=\"#\" />\n </PaginationItem>\n </PaginationContent>\n </Pagination>\n </footer>\n </main>\n );\n}\n"
23
23
  },
24
24
  {
25
25
  "path": "views/crm-desk.md",
@@ -3,26 +3,26 @@
3
3
  "name": "crm-detalle-caso",
4
4
  "type": "registry:block",
5
5
  "title": "crm-detalle-caso",
6
- "description": "Detalle master-detail para **CRM**: contexto del caso a la izquierda (panel fijo de 288px), trabajo al centro en tabs, acción primaria a la mano. La información que se consulta junta vive junta. Instálalo con `create-lexy add crm-detalle-caso` — trae `Tabs`, `Tag`, `Separator` y `Button`. La vista queda en tu proyecto (`views/CasoDetalle.tsx`): edítala con libertad.",
6
+ "description": "Detalle master-detail para **CRM**: contexto del caso a la izquierda (panel fijo de 288px), trabajo al centro en tabs, acción primaria a la mano. La información que se consulta junta vive junta. Instálalo con `create-lexy add crm-detalle-caso` — trae `Tabs`, `Badge`, `Separator` y `Button`. La vista queda en tu proyecto (`views/CasoDetalle.tsx`): edítala con libertad.",
7
7
  "dependencies": [],
8
8
  "registryDependencies": [
9
+ "@lexy/badge",
9
10
  "@lexy/button",
10
11
  "@lexy/separator",
11
- "@lexy/tabs",
12
- "@lexy/tag"
12
+ "@lexy/tabs"
13
13
  ],
14
14
  "files": [
15
15
  {
16
16
  "path": "views/CasoDetalle.tsx",
17
17
  "type": "registry:file",
18
18
  "target": "views/CasoDetalle.tsx",
19
- "content": "import { Button } from \"@/components/base/Button\";\nimport { Separator } from \"@/components/base/Separator\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/base/Tabs\";\nimport { Tag } from \"@/components/base/Tag\";\n\n// Receta CRM — detalle de caso (master-detail): contexto a la izquierda,\n// trabajo al centro, acciones a la mano. Bajo lg las columnas apilan:\n// contexto arriba, trabajo debajo (pauta §5). Ver crm-detalle-caso.md.\n\nexport function CasoDetalle() {\n return (\n <main className=\"flex w-full flex-col gap-4 p-4 lg:flex-row\">\n <aside className=\"w-full space-y-4 lg:w-72 lg:shrink-0\">\n <div>\n <h1 className=\"text-lg font-semibold text-foreground\">María Pérez</h1>\n <p className=\"text-sm text-muted-foreground\">Caso #1042 · Despido</p>\n </div>\n <Tag tone=\"warning\">En revisión</Tag>\n <Separator />\n <dl className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">Abogado</dt>\n <dd className=\"text-foreground\">C. Rivera</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">Plazo</dt>\n <dd className=\"text-warning\">Vence hoy</dd>\n </div>\n </dl>\n </aside>\n\n <section className=\"min-w-0 flex-1\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"text-base font-semibold text-foreground\">Gestiones</h2>\n <Button size=\"sm\">Nueva gestión</Button>\n </div>\n <Tabs defaultValue=\"actividad\">\n <TabsList>\n <TabsTrigger value=\"actividad\">Actividad</TabsTrigger>\n <TabsTrigger value=\"documentos\">Documentos</TabsTrigger>\n <TabsTrigger value=\"notas\">Notas</TabsTrigger>\n </TabsList>\n <TabsContent value=\"actividad\">{/* timeline */}</TabsContent>\n <TabsContent value=\"documentos\">{/* lista */}</TabsContent>\n <TabsContent value=\"notas\">{/* notas */}</TabsContent>\n </Tabs>\n </section>\n </main>\n );\n}\n"
19
+ "content": "import { Button } from \"@/components/base/Button\";\nimport { Badge } from \"@/components/base/Badge\";\nimport { Separator } from \"@/components/base/Separator\";\nimport {\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from \"@/components/base/Tabs\";\n\n// Receta CRM — detalle de caso (master-detail): contexto a la izquierda,\n// trabajo al centro, acciones a la mano. Bajo lg las columnas apilan:\n// contexto arriba, trabajo debajo (pauta §5). Ver crm-detalle-caso.md.\n\nexport function CasoDetalle() {\n return (\n <main className=\"flex w-full flex-col gap-4 p-4 lg:flex-row\">\n <aside className=\"w-full space-y-4 lg:w-72 lg:shrink-0\">\n <div>\n <h1 className=\"type-item-title text-foreground\">María Pérez</h1>\n <p className=\"type-supporting text-muted-foreground\">Caso #1042 · Despido</p>\n </div>\n <Badge variant=\"secondary\">En revisión</Badge>\n <Separator />\n <dl className=\"space-y-2 type-supporting\">\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">Abogado</dt>\n <dd className=\"text-foreground\">C. Rivera</dd>\n </div>\n <div className=\"flex justify-between\">\n <dt className=\"text-muted-foreground\">Plazo</dt>\n <dd className=\"text-warning\">Vence hoy</dd>\n </div>\n </dl>\n </aside>\n\n <section className=\"min-w-0 flex-1\">\n <div className=\"mb-3 flex items-center justify-between\">\n <h2 className=\"type-section-title text-foreground\">Gestiones</h2>\n <Button size=\"sm\">Nueva gestión</Button>\n </div>\n <Tabs defaultValue=\"actividad\">\n <TabsList>\n <TabsTrigger value=\"actividad\">Actividad</TabsTrigger>\n <TabsTrigger value=\"documentos\">Documentos</TabsTrigger>\n <TabsTrigger value=\"notas\">Notas</TabsTrigger>\n </TabsList>\n <TabsContent value=\"actividad\">{/* timeline */}</TabsContent>\n <TabsContent value=\"documentos\">{/* lista */}</TabsContent>\n <TabsContent value=\"notas\">{/* notas */}</TabsContent>\n </Tabs>\n </section>\n </main>\n );\n}\n"
20
20
  },
21
21
  {
22
22
  "path": "views/crm-detalle-caso.md",
23
23
  "type": "registry:file",
24
24
  "target": "views/crm-detalle-caso.md",
25
- "content": "# crm-detalle-caso — block\n\nDetalle master-detail para **CRM**: contexto del caso a la izquierda (panel fijo de 288px), trabajo al centro en tabs, acción primaria a la mano. La información que se consulta junta vive junta. Instálalo con `create-lexy add crm-detalle-caso` — trae `Tabs`, `Tag`, `Separator` y `Button`. La vista queda en tu proyecto (`views/CasoDetalle.tsx`): edítala con libertad.\n\n## Montaje\n\nVive dentro del layout de aplicación (`crm-app-layout`); se llega desde una fila del desk (`crm-desk`).\n\n## Adaptación\n\n- El `aside` lleva la identidad (quién, qué caso) + estado (`Tag` con texto) + datos clave en `dl`; agrega pares `dt`/`dd` según tu dominio.\n- Cada `TabsContent` resuelve sus cuatro estados de datos (loading/vacío/error/contenido).\n- Acciones secundarias del caso → `DropdownMenu` junto al botón primario, no una fila de botones.\n\n## Evita\n\nRepetir la identidad del caso dentro de cada tab, panel izquierdo scrolleable independiente sin necesidad, y tabs con una sola pestaña.\n\n## Degradación móvil\n\nBajo `lg` las columnas apilan: contexto arriba, trabajo debajo. Las acciones del área de trabajo siguen visibles sin scroll; si el contexto crece, colapsa su detalle secundario en un `Accordion`.\n"
25
+ "content": "# crm-detalle-caso — block\n\nDetalle master-detail para **CRM**: contexto del caso a la izquierda (panel fijo de 288px), trabajo al centro en tabs, acción primaria a la mano. La información que se consulta junta vive junta. Instálalo con `create-lexy add crm-detalle-caso` — trae `Tabs`, `Badge`, `Separator` y `Button`. La vista queda en tu proyecto (`views/CasoDetalle.tsx`): edítala con libertad.\n\n## Montaje\n\nVive dentro del layout de aplicación (`crm-app-layout`); se llega desde una fila del desk (`crm-desk`).\n\n## Adaptación\n\n- El `aside` lleva la identidad (quién, qué caso) + estado (`Badge` con texto) + datos clave en `dl`; agrega pares `dt`/`dd` según tu dominio.\n- Cada `TabsContent` resuelve sus cuatro estados de datos (loading/vacío/error/contenido).\n- Acciones secundarias del caso → `DropdownMenu` junto al botón primario, no una fila de botones.\n\n## Evita\n\nRepetir la identidad del caso dentro de cada tab, panel izquierdo scrolleable independiente sin necesidad, y tabs con una sola pestaña.\n\n## Degradación móvil\n\nBajo `lg` las columnas apilan: contexto arriba, trabajo debajo. Las acciones del área de trabajo siguen visibles sin scroll; si el contexto crece, colapsa su detalle secundario en un `Accordion`.\n"
26
26
  }
27
27
  ]
28
28
  }
@@ -24,7 +24,7 @@
24
24
  "path": "components/base/DatePicker.md",
25
25
  "type": "registry:file",
26
26
  "target": "components/base/DatePicker.md",
27
- "content": "# DatePicker — guía de uso\n\nSelector de fecha en un campo: un `Button` que abre un `Popover` con un\n`Calendar` adentro. Es la composición estándar para elegir una fecha en\nformularios. Muestra la fecha elegida formateada (en español por defecto). El componente vive en tu proyecto: instálalo con `create-lexy add date-picker` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara capturar una fecha dentro de un formulario sin ocupar pantalla: el campo muestra la fecha elegida y el calendario aparece solo al abrirlo. Para fecha de nacimiento activa los dropdowns de mes/año — nadie navega trescientos meses con flechas.\n\n## Composición\n\nReceta empaquetada: un `Button` con apariencia de campo que abre un `Popover` con un `Calendar` dentro. Emparéjalo con `Label`, como a cualquier campo de formulario.\n\n## Uso básico (no controlado)\n\n```tsx\n<DatePicker />\n```\n\n## Controlado\n\n```tsx\nconst [date, setDate] = React.useState<Date | undefined>();\n\n<DatePicker value={date} onChange={setDate} />;\n```\n\n## Fecha de nacimiento (dropdowns de mes/año)\n\n```tsx\n<DatePicker captionLayout=\"dropdown\" placeholder=\"Fecha de nacimiento\" />\n```\n\n## Receta compuesta\n\n`DatePicker` es un wrapper delgado sobre `Popover` + `Button` + `Calendar`. Sus props extra (`variant`, `disabled`, `id`, `aria-*`…) pasan al botón trigger y el `ref` apunta a ese botón. Si necesitas variar la estructura (rango, dos meses, footer con acciones, otro trigger), compón las piezas directamente:\n\n```tsx\nimport { Button } from \"@/components/base/Button\";\nimport { Calendar } from \"@/components/base/Calendar\";\nimport { Popover, PopoverTrigger, PopoverContent } from \"@/components/base/Popover\";\n\n<Popover>\n <PopoverTrigger asChild>\n <Button variant=\"outline\">{fecha ? formatear(fecha) : \"Selecciona una fecha\"}</Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-0\" align=\"start\">\n <Calendar mode=\"range\" numberOfMonths={2} selected={rango} onSelect={setRango} />\n </PopoverContent>\n</Popover>\n```\n\n## Reglas\n\n- Para una sola fecha en un formulario, usa `DatePicker`; para elegir un período usa `Calendar` con `mode=\"range\"` dentro de un `Popover`.\n- En modo controlado pasa `value` **y** `onChange`; sin `onChange` el componente maneja su propio estado.\n- El popover se cierra al seleccionar una fecha.\n- Ajusta el ancho del campo con `className` (por defecto `w-65`).\n\n## Cuándo NO usar\n\n- **Elegir un rango** → `Calendar` con `mode=\"range\"` (date range picker).\n- **Mostrar un calendario siempre visible** → `Calendar` directo, sin `Popover`.\n- **Solo hora** → un input de tiempo.\n\n## Import\n\n```tsx\nimport { DatePicker } from \"@/components/base/DatePicker\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `value` | `Date` | — | Fecha seleccionada (modo controlado, junto a `onChange`). |\n| `defaultValue` | `Date` | — | Fecha inicial en modo no controlado. |\n| `onChange` | `(date?: Date) => void` | — | Callback de selección. Activa el modo controlado. |\n| `placeholder` | `string` | `\"Selecciona una fecha\"` | Texto cuando no hay fecha. |\n| `disabled` | `boolean` | `false` | Deshabilita el disparador. |\n| `captionLayout` | `\"label\" \\| \"dropdown\" \\| …` | `\"label\"` | Encabezado del calendario. |\n| `locale` | `Locale` | `es` | Localización (de `react-day-picker/locale`). |\n| `align` | `\"start\" \\| \"center\" \\| \"end\"` | `\"start\"` | Alineación del popover. |\n| `className` | `string` | — | Clases del botón disparador (p. ej. ancho). |\n\n## Para IA\n\n1. Para una fecha en formulario, usa `DatePicker` (no reconstruyas Popover+Calendar a mano salvo que necesites rango).\n2. Modo controlado: `value` + `onChange` con estado propio; no controlado: solo `<DatePicker />`.\n3. Usa `captionLayout=\"dropdown\"` para fechas lejanas (nacimiento, vencimientos).\n4. Para rango, compón `Popover` + `Button` + `Calendar mode=\"range\"`.\n"
27
+ "content": "# DatePicker\nSelector de fecha en un campo: un `Button` que abre un `Popover` con un\n`Calendar` adentro. Es la composición estándar para elegir una fecha en\nformularios. Muestra la fecha elegida formateada (en español por defecto).\n\n## Cuándo usarlo\n\nPara capturar una fecha dentro de un formulario sin ocupar pantalla: el campo muestra la fecha elegida y el calendario aparece solo al abrirlo. Para fecha de nacimiento activa los dropdowns de mes/año — nadie navega trescientos meses con flechas.\n\n## Composición\n\nReceta empaquetada: un `Button` con apariencia de campo que abre un `Popover` con un `Calendar` dentro. Emparéjalo con `Label`, como a cualquier campo de formulario.\n\n## Uso básico (no controlado)\n\n```tsx\n<DatePicker />\n```\n\n## Controlado\n\n```tsx\nconst [date, setDate] = React.useState<Date | undefined>();\n\n<DatePicker value={date} onChange={setDate} />;\n```\n\n## Fecha de nacimiento (dropdowns de mes/año)\n\n```tsx\n<DatePicker captionLayout=\"dropdown\" placeholder=\"Fecha de nacimiento\" />\n```\n\n## Receta compuesta\n\n`DatePicker` es un wrapper delgado sobre `Popover` + `Button` + `Calendar`. Sus props extra (`variant`, `disabled`, `id`, `aria-*`…) pasan al botón trigger y el `ref` apunta a ese botón. Si necesitas variar la estructura (rango, dos meses, footer con acciones, otro trigger), compón las piezas directamente:\n\n```tsx\nimport { Button } from \"@/components/base/Button\";\nimport { Calendar } from \"@/components/base/Calendar\";\nimport { Popover, PopoverTrigger, PopoverContent } from \"@/components/base/Popover\";\n\n<Popover>\n <PopoverTrigger asChild>\n <Button variant=\"outline\">{fecha ? formatear(fecha) : \"Selecciona una fecha\"}</Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-0\" align=\"start\">\n <Calendar mode=\"range\" numberOfMonths={2} selected={rango} onSelect={setRango} />\n </PopoverContent>\n</Popover>\n```\n\n## Reglas\n\n- Para una sola fecha en un formulario, usa `DatePicker`; para elegir un período usa `Calendar` con `mode=\"range\"` dentro de un `Popover`.\n- En modo controlado pasa `value` **y** `onChange`; sin `onChange` el componente maneja su propio estado.\n- El popover se cierra al seleccionar una fecha.\n- Ajusta el ancho del campo con `className` (por defecto `w-65`).\n\n## Cuándo NO usar\n\n- **Elegir un rango** → `Calendar` con `mode=\"range\"` (date range picker).\n- **Mostrar un calendario siempre visible** → `Calendar` directo, sin `Popover`.\n- **Solo hora** → un input de tiempo.\n\n## Import\n\n```tsx\nimport { DatePicker } from \"@/components/base/DatePicker\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `value` | `Date` | — | Fecha seleccionada (modo controlado, junto a `onChange`). |\n| `defaultValue` | `Date` | — | Fecha inicial en modo no controlado. |\n| `onChange` | `(date?: Date) => void` | — | Callback de selección. Activa el modo controlado. |\n| `placeholder` | `string` | `\"Selecciona una fecha\"` | Texto cuando no hay fecha. |\n| `disabled` | `boolean` | `false` | Deshabilita el disparador. |\n| `captionLayout` | `\"label\" \\| \"dropdown\" \\| …` | `\"label\"` | Encabezado del calendario. |\n| `locale` | `Locale` | `es` | Localización (de `react-day-picker/locale`). |\n| `align` | `\"start\" \\| \"center\" \\| \"end\"` | `\"start\"` | Alineación del popover. |\n| `className` | `string` | — | Clases del botón disparador (p. ej. ancho). |\n\n## Para IA\n\n1. Para una fecha en formulario, usa `DatePicker` (no reconstruyas Popover+Calendar a mano salvo que necesites rango).\n2. Modo controlado: `value` + `onChange` con estado propio; no controlado: solo `<DatePicker />`.\n3. Usa `captionLayout=\"dropdown\"` para fechas lejanas (nacimiento, vencimientos).\n4. Para rango, compón `Popover` + `Button` + `Calendar mode=\"range\"`.\n"
28
28
  }
29
29
  ]
30
30
  }
@@ -3,7 +3,7 @@
3
3
  "name": "dialog",
4
4
  "type": "registry:ui",
5
5
  "title": "Dialog",
6
- "description": "Diálogo modal primitivo (Radix). **Para la mayoría de los casos usa `AppDialog`**, que ya arma la composición Material 3 (icono, título, descripción, acciones). Usa `Dialog` directo solo para layouts modales a medida sin esa estructura. El componente vive en tu proyecto: instálalo con `create-lexy add dialog` y edítalo con libertad.",
6
+ "description": "Diálogo modal primitivo (Radix). **Para la mayoría de los casos usa `AppDialog`**, que ya arma la composición Material 3 (icono, título, descripción, acciones). Usa `Dialog` directo solo para layouts modales a medida sin esa estructura.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-dialog"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Dialog.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Dialog.tsx",
16
- "content": "import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Dialog = DialogPrimitive.Root;\nconst DialogTrigger = DialogPrimitive.Trigger;\nconst DialogPortal = DialogPrimitive.Portal;\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n />\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg\",\n className,\n )}\n {...props}\n >\n {children}\n {/* Sin X de cierre: se cierra con Escape, click en el overlay o una\n acción explícita (DialogClose / botón Cancelar de AppDialog). */}\n </DialogPrimitive.Content>\n </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nconst DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex flex-col space-y-2 text-center sm:text-left\", className)} {...props} />\n);\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogFooter = ({ 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);\nDialogFooter.displayName = \"DialogFooter\";\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"text-lg font-semibold leading-none tracking-tight text-foreground\", className)}\n {...props}\n />\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n"
16
+ "content": "import * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Dialog = DialogPrimitive.Root;\nconst DialogTrigger = DialogPrimitive.Trigger;\nconst DialogPortal = DialogPrimitive.Portal;\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n className,\n )}\n {...props}\n />\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-grouped border border-border bg-background p-6 shadow-overlay duration-[var(--duration-overlay)] ease-[var(--ease-standard)] motion-reduce:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-xl\",\n className,\n )}\n {...props}\n >\n {children}\n {/* Sin X de cierre: se cierra con Escape, click en el overlay o una\n acción explícita (DialogClose / botón Cancelar de AppDialog). */}\n </DialogPrimitive.Content>\n </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nconst DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn(\"flex flex-col gap-related text-center sm:text-left\", className)} {...props} />\n);\nDialogHeader.displayName = \"DialogHeader\";\n\nconst DialogFooter = ({ 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);\nDialogFooter.displayName = \"DialogFooter\";\n\nexport interface DialogTitleProps\n extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> {\n /** Escala visual del título. `compact` usa 22/28 para composiciones más densas. */\n scale?: \"default\" | \"compact\";\n}\n\nconst DialogTitle = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Title>,\n DialogTitleProps\n>(({ className, scale = \"default\", ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\n scale === \"compact\" ? \"type-subsection-title\" : \"type-dialog-title\",\n \"text-foreground\",\n className,\n )}\n {...props}\n />\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n React.ElementRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"type-supporting text-muted-foreground\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Dialog.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Dialog.md",
22
- "content": "# Dialog — guía de uso\n\nDiálogo modal primitivo (Radix). **Para la mayoría de los casos usa `AppDialog`**, que ya arma la composición Material 3 (icono, título, descripción, acciones). Usa `Dialog` directo solo para layouts modales a medida sin esa estructura. El componente vive en tu proyecto: instálalo con `create-lexy add dialog` y edítalo con libertad.\n\n## Cuándo usarlo\n\nEl modal primitivo para layouts a medida: formularios en modal, contenido especial. Para la mayoría de los casos usa `AppDialog`, que ya trae la estructura canónica; `Dialog` es el plan B cuando esa estructura no alcanza.\n\n## Composición\n\nComposición Radix: `DialogTrigger` abre `DialogContent` (overlay y portal ya resueltos); dentro van `DialogHeader` (`DialogTitle`, `DialogDescription`), el cuerpo libre y `DialogFooter`; `DialogClose` cierra.\n\n## Uso básico\n\n```tsx\n<Dialog>\n <DialogTrigger asChild>\n <Button variant=\"outline\">Ver detalle</Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Detalle del documento</DialogTitle>\n <DialogDescription>Resumen generado automáticamente.</DialogDescription>\n </DialogHeader>\n {/* contenido a medida */}\n <DialogFooter>\n <DialogClose asChild><Button variant=\"ghost\">Cerrar</Button></DialogClose>\n </DialogFooter>\n </DialogContent>\n</Dialog>\n```\n\n## Reglas\n\n- Incluye **siempre** un `DialogTitle` (accesibilidad: nombra el diálogo). Si lo ocultas visualmente, mantenlo para lectores de pantalla.\n- Usa `DialogDescription` para el contexto; no dejes el cuerpo sin explicación.\n- Usa `DialogTrigger asChild` / `DialogClose asChild` sobre tus propios botones.\n- No anides diálogos; un modal a la vez.\n- El diálogo **no trae X de cierre**: se cierra con Escape, click en el overlay o un botón explícito con `DialogClose` (el patrón Lexy es nombrar la acción, p. ej. «Cancelar»).\n- Explica la consecuencia de las acciones (ver pauta de UX writing), sin \"¿estás seguro?\".\n\n## Cuándo NO usar\n\n- **Confirmación o formulario modal estándar** → `AppDialog` (menos código, patrón correcto).\n- **Panel lateral** (filtros, detalle largo) → `Sheet`.\n- **Ayuda breve anclada a un control** → `Popover`/`Tooltip`.\n- **Mensaje efímero de resultado** → `Toast`.\n\n## Import\n\n```tsx\nimport {\n Dialog, DialogTrigger, DialogContent, DialogHeader,\n DialogTitle, DialogDescription, DialogFooter, DialogClose,\n} from \"@/components/base/Dialog\";\n```\n\n## Exports\n\n`Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose`, `DialogOverlay`, `DialogPortal`.\n\n## Para IA\n\n1. Pregúntate primero si `AppDialog` cubre el caso (confirmación/formulario): si sí, úsalo.\n2. Usa `Dialog` directo solo para un layout modal a medida.\n3. Garantiza `DialogTitle` (visible o sr-only) y descripción.\n4. Conecta trigger/close con `asChild` a tus botones.\n5. Redacta acciones por su consecuencia; usa `Sheet`/`Popover`/`Toast` si encajan mejor.\n"
22
+ "content": "# Dialog\nDiálogo modal primitivo (Radix). **Para la mayoría de los casos usa `AppDialog`**, que ya arma la composición Material 3 (icono, título, descripción, acciones). Usa `Dialog` directo solo para layouts modales a medida sin esa estructura.\n\n## Cuándo usarlo\n\nEl modal primitivo para layouts a medida: formularios en modal, contenido especial. Para la mayoría de los casos usa `AppDialog`, que ya trae la estructura canónica; `Dialog` es el plan B cuando esa estructura no alcanza.\n\n## Composición\n\nComposición Radix: `DialogTrigger` abre `DialogContent` (overlay y portal ya resueltos); dentro van `DialogHeader` (`DialogTitle`, `DialogDescription`), el cuerpo libre y `DialogFooter`; `DialogClose` cierra.\n\n## Uso básico\n\n```tsx\n<Dialog>\n <DialogTrigger asChild>\n <Button variant=\"outline\">Ver detalle</Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Detalle del documento</DialogTitle>\n <DialogDescription>Resumen generado automáticamente.</DialogDescription>\n </DialogHeader>\n {/* contenido a medida */}\n <DialogFooter>\n <DialogClose asChild><Button variant=\"ghost\">Cerrar</Button></DialogClose>\n </DialogFooter>\n </DialogContent>\n</Dialog>\n```\n\n## Reglas\n\n- Incluye **siempre** un `DialogTitle` (accesibilidad: nombra el diálogo). Si lo ocultas visualmente, mantenlo para lectores de pantalla.\n- `DialogTitle` usa 24/32 por defecto; usa `scale=\"compact\"` para una composición 22/28 como la de `AppDialog`.\n- Usa `DialogDescription` para el contexto; no dejes el cuerpo sin explicación.\n- Usa `DialogTrigger asChild` / `DialogClose asChild` sobre tus propios botones.\n- No anides diálogos; un modal a la vez.\n- El diálogo **no trae X de cierre**: se cierra con Escape, click en el overlay o un botón explícito con `DialogClose` (el patrón Lexy es nombrar la acción, p. ej. «Cancelar»).\n- Explica la consecuencia de las acciones (ver pauta de UX writing), sin \"¿estás seguro?\".\n\n## Cuándo NO usar\n\n- **Confirmación o formulario modal estándar** → `AppDialog` (menos código, patrón correcto).\n- **Panel lateral** (filtros, detalle largo) → `Sheet`.\n- **Ayuda breve anclada a un control** → `Popover`/`Tooltip`.\n- **Mensaje efímero de resultado** → `Toast`.\n\n## Import\n\n```tsx\nimport {\n Dialog, DialogTrigger, DialogContent, DialogHeader,\n DialogTitle, DialogDescription, DialogFooter, DialogClose,\n} from \"@/components/base/Dialog\";\n```\n\n## Exports\n\n`Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose`, `DialogOverlay`, `DialogPortal`.\n\n## Para IA\n\n1. Pregúntate primero si `AppDialog` cubre el caso (confirmación/formulario): si sí, úsalo.\n2. Usa `Dialog` directo solo para un layout modal a medida.\n3. Garantiza `DialogTitle` (visible o sr-only) y descripción.\n4. Conecta trigger/close con `asChild` a tus botones.\n5. Redacta acciones por su consecuencia; usa `Sheet`/`Popover`/`Toast` si encajan mejor.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "dropdown-menu",
4
4
  "type": "registry:ui",
5
5
  "title": "DropdownMenu",
6
- "description": "Menú de **acciones** contextuales que se abre desde un disparador (Radix). Para agrupar acciones secundarias (\"⋯\", \"Opciones\") sin llenar la pantalla de botones. El componente vive en tu proyecto: instálalo con `create-lexy add dropdown-menu` y edítalo con libertad.",
6
+ "description": "Menú de **acciones** contextuales que se abre desde un disparador (Radix). Para agrupar acciones secundarias (\"⋯\", \"Opciones\") sin llenar la pantalla de botones.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-dropdown-menu"
9
9
  ],
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/DropdownMenu.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/DropdownMenu.tsx",
16
- "content": "import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst ChevronRightIcon = () => (\n <svg\n className=\"ml-auto h-4 w-4\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n);\n\nconst CheckIcon = () => (\n <svg\n className=\"h-4 w-4\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n);\n\nconst DotIcon = () => (\n <svg className=\"h-2 w-2 fill-current\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"12\" />\n </svg>\n);\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n \"flex cursor-pointer select-none items-center rounded px-2 py-1.5 text-sm outline-none focus:bg-primary/15 focus:text-primary data-[state=open]:bg-primary/15 data-[state=open]:text-primary\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon />\n </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;\n\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n className={cn(\n \"z-50 min-w-32 overflow-hidden rounded border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"z-50 min-w-32 overflow-hidden rounded border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n className,\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n));\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center gap-2 rounded px-2 py-1.5 text-sm outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <DotIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\"px-2 py-1.5 text-sm font-semibold text-foreground\", inset && \"pl-8\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n {...props}\n />\n);\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\";\n\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n};\n"
16
+ "content": "import * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst ChevronRightIcon = () => (\n <svg\n className=\"ml-auto size-(--icon-size-inline)\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n);\n\nconst CheckIcon = () => (\n <svg\n className=\"size-(--icon-size-inline)\"\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\nconst DotIcon = () => (\n <svg className=\"h-2 w-2 fill-current\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"12\" r=\"12\" />\n </svg>\n);\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n \"flex cursor-pointer select-none items-center rounded-menu-item px-2 py-1.5 type-supporting outline-none focus:bg-primary/15 focus:text-primary data-[state=open]:bg-primary/15 data-[state=open]:text-primary\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon />\n </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;\n\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n className={cn(\n \"z-50 min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n \"z-50 min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n className,\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n));\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-menu-item px-2 py-1.5 type-supporting outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n));\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded-menu-item py-1.5 pl-8 pr-2 type-supporting outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded-menu-item py-1.5 pl-8 pr-2 type-supporting outline-none transition-colors focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <DotIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\"px-2 py-1.5 type-supporting font-semibold text-foreground\", inset && \"pl-8\", className)}\n {...props}\n />\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn(\"ml-auto type-meta tracking-widest text-muted-foreground\", className)}\n {...props}\n />\n);\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\";\n\nexport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n};\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/DropdownMenu.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/DropdownMenu.md",
22
- "content": "# DropdownMenu — guía de uso\n\nMenú de **acciones** contextuales que se abre desde un disparador (Radix). Para agrupar acciones secundarias (\"⋯\", \"Opciones\") sin llenar la pantalla de botones. El componente vive en tu proyecto: instálalo con `create-lexy add dropdown-menu` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara acciones contextuales agrupadas tras un disparador (“⋯”, “Opciones”): editar, duplicar, archivar. Saca de la vista lo secundario sin perderlo. Si se trata de elegir un valor de formulario, eso es un `Select`.\n\n## Composición\n\n`DropdownMenuTrigger` abre `DropdownMenuContent` con `DropdownMenuItem`s, organizables con `DropdownMenuGroup`, `DropdownMenuLabel` y `DropdownMenuSeparator`, más variantes de check/radio y submenús.\n\n## Uso básico\n\n```tsx\n<DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" aria-label=\"Más acciones\"><MoreHorizontal /></Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>Acciones</DropdownMenuLabel>\n <DropdownMenuItem onSelect={editar}>Editar</DropdownMenuItem>\n <DropdownMenuItem onSelect={duplicar}>Duplicar</DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem className=\"text-destructive\" onSelect={eliminar}>\n Eliminar\n </DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>\n```\n\n## Reglas\n\n- Es para **acciones**, no para seleccionar el valor de un formulario.\n- Da `aria-label` al trigger solo-icono.\n- Usa `DropdownMenuSeparator` para aislar acciones destructivas; márcalas con color **y** texto claro.\n- Agrupa con `DropdownMenuLabel`; no metas formularios largos dentro.\n- Submenús (`Sub`) solo para jerarquía real; evita anidar de más.\n\n## Cuándo NO usar\n\n- **Seleccionar un valor en un formulario** → `Select`.\n- **Buscar/filtrar opciones** → `Combobox` o `Command`.\n- **Contenido o formulario interactivo** → `Popover`.\n- **Navegación principal del sitio** → `NavigationMenu`.\n\n## Import\n\n```tsx\nimport {\n DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,\n DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel,\n} from \"@/components/base/DropdownMenu\";\n```\n\n## Exports\n\nRoot y trigger: `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`.\nÍtems: `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioItem`, `DropdownMenuRadioGroup`.\nEstructura: `DropdownMenuLabel`, `DropdownMenuSeparator`, `DropdownMenuGroup`, `DropdownMenuShortcut`.\nSubmenús: `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent`, `DropdownMenuPortal`.\n\n## Props (esenciales)\n\n| Parte | Prop | Descripción |\n|---|---|---|\n| `DropdownMenuTrigger` | `asChild` | Usa tu botón como disparador. |\n| `DropdownMenuContent` | `align` | `\"start\" \\| \"center\" \\| \"end\"`. |\n| `DropdownMenuItem` | `onSelect` | Acción al elegir. |\n| `DropdownMenuItem` | `disabled` | Deshabilita la acción. |\n| `DropdownMenuCheckboxItem` | `checked` / `onCheckedChange` | Alternar opción. |\n\n## Para IA\n\n1. Confirma que son acciones (verbos), no selección de datos.\n2. Usa `DropdownMenuTrigger asChild` sobre un `Button` (con `aria-label` si es icono).\n3. Ordena por frecuencia; separa y marca las destructivas.\n4. Si en realidad es selección de valor o búsqueda, cambia a `Select`/`Combobox`.\n"
22
+ "content": "# DropdownMenu\nMenú de **acciones** contextuales que se abre desde un disparador (Radix). Para agrupar acciones secundarias (\"⋯\", \"Opciones\") sin llenar la pantalla de botones.\n\n## Cuándo usarlo\n\nPara acciones contextuales agrupadas tras un disparador (“⋯”, “Opciones”): editar, duplicar, archivar. Saca de la vista lo secundario sin perderlo. Si se trata de elegir un valor de formulario, eso es un `Select`.\n\n## Composición\n\n`DropdownMenuTrigger` abre `DropdownMenuContent` con `DropdownMenuItem`s, organizables con `DropdownMenuGroup`, `DropdownMenuLabel` y `DropdownMenuSeparator`, más variantes de check/radio y submenús.\n\n## Uso básico\n\n```tsx\n<DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" aria-label=\"Más acciones\"><MoreHorizontal /></Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuLabel>Acciones</DropdownMenuLabel>\n <DropdownMenuItem onSelect={editar}>Editar</DropdownMenuItem>\n <DropdownMenuItem onSelect={duplicar}>Duplicar</DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem className=\"text-destructive\" onSelect={eliminar}>\n Eliminar\n </DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>\n```\n\n## Reglas\n\n- Es para **acciones**, no para seleccionar el valor de un formulario.\n- Da `aria-label` al trigger solo-icono.\n- Usa `DropdownMenuSeparator` para aislar acciones destructivas; márcalas con color **y** texto claro.\n- Agrupa con `DropdownMenuLabel`; no metas formularios largos dentro.\n- Submenús (`Sub`) solo para jerarquía real; evita anidar de más.\n\n## Cuándo NO usar\n\n- **Seleccionar un valor en un formulario** → `Select`.\n- **Buscar/filtrar opciones** → `Combobox` o `Command`.\n- **Contenido o formulario interactivo** → `Popover`.\n- **Navegación principal del sitio** → `NavigationMenu`.\n\n## Import\n\n```tsx\nimport {\n DropdownMenu, DropdownMenuTrigger, DropdownMenuContent,\n DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel,\n} from \"@/components/base/DropdownMenu\";\n```\n\n## Exports\n\nRoot y trigger: `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`.\nÍtems: `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioItem`, `DropdownMenuRadioGroup`.\nEstructura: `DropdownMenuLabel`, `DropdownMenuSeparator`, `DropdownMenuGroup`, `DropdownMenuShortcut`.\nSubmenús: `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent`, `DropdownMenuPortal`.\n\n## Props (esenciales)\n\n| Parte | Prop | Descripción |\n|---|---|---|\n| `DropdownMenuTrigger` | `asChild` | Usa tu botón como disparador. |\n| `DropdownMenuContent` | `align` | `\"start\" \\| \"center\" \\| \"end\"`. |\n| `DropdownMenuItem` | `onSelect` | Acción al elegir. |\n| `DropdownMenuItem` | `disabled` | Deshabilita la acción. |\n| `DropdownMenuCheckboxItem` | `checked` / `onCheckedChange` | Alternar opción. |\n\n## Para IA\n\n1. Confirma que son acciones (verbos), no selección de datos.\n2. Usa `DropdownMenuTrigger asChild` sobre un `Button` (con `aria-label` si es icono).\n3. Ordena por frecuencia; separa y marca las destructivas.\n4. Si en realidad es selección de valor o búsqueda, cambia a `Select`/`Combobox`.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -13,13 +13,13 @@
13
13
  "path": "components/base/Empty.tsx",
14
14
  "type": "registry:ui",
15
15
  "target": "components/base/Empty.tsx",
16
- "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nfunction Empty({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty\"\n className={cn(\n \"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-lg border-dashed p-12 text-center text-balance\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-header\"\n className={cn(\"flex max-w-sm flex-col items-center gap-2\", className)}\n {...props}\n />\n );\n}\n\nconst emptyMediaVariants = cva(\n \"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n icon: \"flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nfunction EmptyMedia({\n className,\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyMediaVariants>) {\n return (\n <div\n data-slot=\"empty-icon\"\n data-variant={variant}\n className={cn(emptyMediaVariants({ variant, className }))}\n {...props}\n />\n );\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-title\"\n className={cn(\"font-sans text-lg font-medium tracking-tight\", className)}\n {...props}\n />\n );\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <div\n data-slot=\"empty-description\"\n className={cn(\n \"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-content\"\n className={cn(\n \"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n emptyMediaVariants,\n EmptyTitle,\n};\n"
16
+ "content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nfunction Empty({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty\"\n className={cn(\n \"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-lg border-dashed p-12 text-center text-balance\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-header\"\n className={cn(\"flex max-w-sm flex-col items-center gap-2\", className)}\n {...props}\n />\n );\n}\n\nconst emptyMediaVariants = cva(\n \"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n icon: \"flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-(--icon-size-feature)\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nfunction EmptyMedia({\n className,\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyMediaVariants>) {\n return (\n <div\n data-slot=\"empty-icon\"\n data-variant={variant}\n className={cn(emptyMediaVariants({ variant, className }))}\n {...props}\n />\n );\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-title\"\n className={cn(\"type-subsection-title\", className)}\n {...props}\n />\n );\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <div\n data-slot=\"empty-description\"\n className={cn(\n \"type-supporting leading-relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-content\"\n className={cn(\n \"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 type-supporting text-balance\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n emptyMediaVariants,\n EmptyTitle,\n};\n"
17
17
  },
18
18
  {
19
19
  "path": "components/base/Empty.md",
20
20
  "type": "registry:file",
21
21
  "target": "components/base/Empty.md",
22
- "content": "# Empty — guía de uso\n\nEstado vacío para cuando no hay datos, resultados o contenido que mostrar.\nCompuesto por sub-componentes que permiten construir distintas variantes. El componente vive en tu proyecto: instálalo con `create-lexy add empty` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara el momento en que no hay nada que mostrar: sin resultados, sin datos aún, primer uso. Un buen estado vacío dice qué pasa y qué hacer después — nunca dejes una tabla en blanco sin explicación.\n\n## Composición\n\nSlots: `EmptyMedia` (icono o ilustración), `EmptyHeader` con `EmptyTitle` y `EmptyDescription`, y `EmptyContent` para la acción de salida (normalmente un `Button`).\n\n## Uso básico\n\n```tsx\n<Empty>\n <EmptyHeader>\n <EmptyTitle>Sin resultados</EmptyTitle>\n <EmptyDescription>\n No se encontraron elementos que coincidan con tu búsqueda.\n </EmptyDescription>\n </EmptyHeader>\n</Empty>\n```\n\n## Con icono\n\n```tsx\n<Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <Inbox className=\"h-6 w-6\" />\n </EmptyMedia>\n <EmptyTitle>Bandeja vacía</EmptyTitle>\n <EmptyDescription>\n No tienes mensajes pendientes.\n </EmptyDescription>\n </EmptyHeader>\n</Empty>\n```\n\n## Con acción\n\n```tsx\n<Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <Upload className=\"h-6 w-6\" />\n </EmptyMedia>\n <EmptyTitle>Sube tu primer archivo</EmptyTitle>\n <EmptyDescription>\n Arrastra archivos aquí o haz click para seleccionarlos.\n </EmptyDescription>\n </EmptyHeader>\n <EmptyContent>\n <Button>Seleccionar archivos</Button>\n </EmptyContent>\n</Empty>\n```\n\n## Reglas\n\n- Usa `Empty` cuando no hay datos que mostrar.\n- No uses `Empty` para estados de carga → usa `Skeleton` o `Spinner`.\n- No uses `Empty` en espacios muy pequeños → usa un mensaje inline.\n- Ofrece una acción clara cuando sea posible (crear, buscar, filtrar).\n- Usa `variant=\"icon\"` en `EmptyMedia` para dar contexto visual.\n\n## Import\n\n```tsx\nimport { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from \"@/shared/components/base/Empty\"; // o @/components/base/Empty\nimport { Inbox } from \"lucide-react\";\n```\n\n## Props\n\n### Sub-componentes\n\n| Componente | Props | Descripción |\n|---|---|---|\n| `Empty` | `className?` | Contenedor principal. |\n| `EmptyHeader` | `className?` | Agrupa título, descripción e icono. |\n| `EmptyMedia` | `variant?: \"default\" \\| \"icon\"`, `className?` | Icono o media decorativa. |\n| `EmptyTitle` | `className?` | Título principal. |\n| `EmptyDescription` | `className?` | Texto explicativo. Soporta enlaces. |\n| `EmptyContent` | `className?` | Área para botones o acciones. |\n\n## Para IA\n\n1. Identifica si la pantalla o sección está vacía por falta de datos.\n2. Si es un estado vacío → usa `Empty`.\n3. Crea `EmptyHeader` con `EmptyTitle` y `EmptyDescription`.\n4. Agrega `EmptyMedia` con `variant=\"icon\"` si un icono aporta contexto.\n5. Agrega `EmptyContent` con botones o links cuando haya una acción posible.\n6. No uses `Empty` para estados de carga.\n7. Asegúrate de que el mensaje sea útil y no genérico.\n"
22
+ "content": "# Empty\nEstado vacío para cuando no hay datos, resultados o contenido que mostrar.\nCompuesto por sub-componentes que permiten construir distintas variantes.\n\n## Cuándo usarlo\n\nPara el momento en que no hay nada que mostrar: sin resultados, sin datos aún, primer uso. Un buen estado vacío dice qué pasa y qué hacer después — nunca dejes una tabla en blanco sin explicación.\n\n## Composición\n\nSlots: `EmptyMedia` (icono o ilustración), `EmptyHeader` con `EmptyTitle` y `EmptyDescription`, y `EmptyContent` para la acción de salida (normalmente un `Button`).\n\n## Uso básico\n\n```tsx\n<Empty>\n <EmptyHeader>\n <EmptyTitle>Sin resultados</EmptyTitle>\n <EmptyDescription>\n No se encontraron elementos que coincidan con tu búsqueda.\n </EmptyDescription>\n </EmptyHeader>\n</Empty>\n```\n\n## Con icono\n\n```tsx\n<Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <Inbox className=\"h-6 w-6\" />\n </EmptyMedia>\n <EmptyTitle>Bandeja vacía</EmptyTitle>\n <EmptyDescription>\n No tienes mensajes pendientes.\n </EmptyDescription>\n </EmptyHeader>\n</Empty>\n```\n\n## Con acción\n\n```tsx\n<Empty>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <Upload className=\"h-6 w-6\" />\n </EmptyMedia>\n <EmptyTitle>Sube tu primer archivo</EmptyTitle>\n <EmptyDescription>\n Arrastra archivos aquí o haz click para seleccionarlos.\n </EmptyDescription>\n </EmptyHeader>\n <EmptyContent>\n <Button>Seleccionar archivos</Button>\n </EmptyContent>\n</Empty>\n```\n\n## Reglas\n\n- Usa `Empty` cuando no hay datos que mostrar.\n- No uses `Empty` para estados de carga → usa `Skeleton` o `Spinner`.\n- No uses `Empty` en espacios muy pequeños → usa un mensaje inline.\n- Ofrece una acción clara cuando sea posible (crear, buscar, filtrar).\n- Usa `variant=\"icon\"` en `EmptyMedia` para dar contexto visual.\n\n## Import\n\n```tsx\nimport { Empty, EmptyHeader, EmptyTitle, EmptyDescription, EmptyContent, EmptyMedia } from \"@/shared/components/base/Empty\"; // o @/components/base/Empty\nimport { Inbox } from \"lucide-react\";\n```\n\n## Props\n\n### Sub-componentes\n\n| Componente | Props | Descripción |\n|---|---|---|\n| `Empty` | `className?` | Contenedor principal. |\n| `EmptyHeader` | `className?` | Agrupa título, descripción e icono. |\n| `EmptyMedia` | `variant?: \"default\" \\| \"icon\"`, `className?` | Icono o media decorativa. |\n| `EmptyTitle` | `className?` | Título principal. |\n| `EmptyDescription` | `className?` | Texto explicativo. Soporta enlaces. |\n| `EmptyContent` | `className?` | Área para botones o acciones. |\n\n## Para IA\n\n1. Identifica si la pantalla o sección está vacía por falta de datos.\n2. Si es un estado vacío → usa `Empty`.\n3. Crea `EmptyHeader` con `EmptyTitle` y `EmptyDescription`.\n4. Agrega `EmptyMedia` con `variant=\"icon\"` si un icono aporta contexto.\n5. Agrega `EmptyContent` con botones o links cuando haya una acción posible.\n6. No uses `Empty` para estados de carga.\n7. Asegúrate de que el mensaje sea útil y no genérico.\n"
23
23
  }
24
24
  ]
25
25
  }
@@ -3,7 +3,7 @@
3
3
  "name": "feature-card",
4
4
  "type": "registry:ui",
5
5
  "title": "FeatureCard",
6
- "description": "Tarjeta con icono y descripción. Para destacar características. El componente vive en tu proyecto: instálalo con `create-lexy add feature-card` y edítalo con libertad.",
6
+ "description": "Tarjeta con icono y descripción. Para destacar características.",
7
7
  "dependencies": [],
8
8
  "registryDependencies": [],
9
9
  "files": [
@@ -11,13 +11,13 @@
11
11
  "path": "components/base/FeatureCard.tsx",
12
12
  "type": "registry:ui",
13
13
  "target": "components/base/FeatureCard.tsx",
14
- "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nexport interface FeatureCardProps extends Omit<React.HTMLAttributes<HTMLElement>, \"title\"> {\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Icono (cualquier nodo: SVG, lucide, etc.). */\n icon?: React.ReactNode;\n /** Si se define, renderiza como <a> con afordancia de hover/foco. */\n href?: string;\n target?: string;\n rel?: string;\n}\n\nconst cardClassName = (interactive: boolean) =>\n cn(\n \"group flex w-full flex-col gap-4 rounded-lg border border-border bg-background p-6 transition-[border-color,box-shadow] duration-150\",\n interactive &&\n \"cursor-pointer hover:border-primary/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n );\n\nfunction FeatureCardContent({\n title,\n description,\n icon,\n interactive,\n}: {\n title: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n interactive: boolean;\n}) {\n return (\n <>\n {(icon || interactive) && (\n <div className=\"flex items-start justify-between gap-3\">\n {icon ? (\n <span\n className=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-accent text-primary [&>svg]:h-4 [&>svg]:w-4\"\n aria-hidden\n >\n {icon}\n </span>\n ) : (\n <span className=\"min-w-0 flex-1\" />\n )}\n {interactive && (\n <svg\n className=\"h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-foreground\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n >\n <path d=\"M7 7h10v10M7 17 17 7\" />\n </svg>\n )}\n </div>\n )}\n <div className=\"flex flex-col gap-1\">\n <h3 className=\"text-sm font-medium text-foreground\">{title}</h3>\n {description != null && description !== \"\" && (\n <p className=\"text-xs leading-relaxed text-muted-foreground\">{description}</p>\n )}\n </div>\n </>\n );\n}\n\nexport const FeatureCard = React.forwardRef<HTMLElement, FeatureCardProps>(function FeatureCard(\n { title, description, icon, href, target, rel, className, ...props },\n ref,\n) {\n const interactive = href != null && href !== \"\";\n const classes = cn(cardClassName(interactive), className);\n\n if (interactive) {\n const resolvedRel = rel ?? (target === \"_blank\" ? \"noopener noreferrer\" : undefined);\n return (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolvedRel}\n className={classes}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n <FeatureCardContent title={title} description={description} icon={icon} interactive />\n </a>\n );\n }\n\n return (\n <article\n ref={ref as React.Ref<HTMLElement>}\n className={classes}\n {...(props as React.HTMLAttributes<HTMLElement>)}\n >\n <FeatureCardContent title={title} description={description} icon={icon} interactive={false} />\n </article>\n );\n});\n"
14
+ "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nexport interface FeatureCardProps extends Omit<React.HTMLAttributes<HTMLElement>, \"title\"> {\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Icono (cualquier nodo: SVG, lucide, etc.). */\n icon?: React.ReactNode;\n /** Si se define, renderiza como <a> con afordancia de hover/foco. */\n href?: string;\n target?: string;\n rel?: string;\n}\n\nconst cardClassName = (interactive: boolean) =>\n cn(\n \"group flex w-full flex-col gap-4 rounded-lg border border-border bg-background p-6 transition-[border-color,box-shadow] duration-[var(--duration-fast)] ease-[var(--ease-standard)] motion-reduce:transition-none\",\n interactive &&\n \"cursor-pointer hover:border-primary/40 hover:shadow-raised focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n );\n\nfunction FeatureCardContent({\n title,\n description,\n icon,\n interactive,\n}: {\n title: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n interactive: boolean;\n}) {\n return (\n <>\n {(icon || interactive) && (\n <div className=\"flex items-start justify-between gap-3\">\n {icon ? (\n <span\n className=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-accent text-primary [&>svg]:h-4 [&>svg]:w-4\"\n aria-hidden\n >\n {icon}\n </span>\n ) : (\n <span className=\"min-w-0 flex-1\" />\n )}\n {interactive && (\n <svg\n className=\"size-(--icon-size-inline) shrink-0 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-foreground\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n >\n <path d=\"M7 7h10v10M7 17 17 7\" />\n </svg>\n )}\n </div>\n )}\n <div className=\"flex flex-col gap-1\">\n <h3 className=\"type-supporting font-medium text-foreground\">{title}</h3>\n {description != null && description !== \"\" && (\n <p className=\"type-meta leading-relaxed text-muted-foreground\">{description}</p>\n )}\n </div>\n </>\n );\n}\n\nexport const FeatureCard = React.forwardRef<HTMLElement, FeatureCardProps>(function FeatureCard(\n { title, description, icon, href, target, rel, className, ...props },\n ref,\n) {\n const interactive = href != null && href !== \"\";\n const classes = cn(cardClassName(interactive), className);\n\n if (interactive) {\n const resolvedRel = rel ?? (target === \"_blank\" ? \"noopener noreferrer\" : undefined);\n return (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolvedRel}\n className={classes}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n <FeatureCardContent title={title} description={description} icon={icon} interactive />\n </a>\n );\n }\n\n return (\n <article\n ref={ref as React.Ref<HTMLElement>}\n className={classes}\n {...(props as React.HTMLAttributes<HTMLElement>)}\n >\n <FeatureCardContent title={title} description={description} icon={icon} interactive={false} />\n </article>\n );\n});\n"
15
15
  },
16
16
  {
17
17
  "path": "components/base/FeatureCard.md",
18
18
  "type": "registry:file",
19
19
  "target": "components/base/FeatureCard.md",
20
- "content": "# FeatureCard — guía de uso\n\nTarjeta con icono y descripción. Para destacar características. El componente vive en tu proyecto: instálalo con `create-lexy add feature-card` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara destacar una capacidad o beneficio con icono y descripción: grillas de “qué incluye”, onboarding, páginas de marketing. Si el contenido es operable (datos, acciones), es un `Card` normal.\n\n## Composición\n\nUna sola pieza (`FeatureCard`) con icono, título y descripción. Se usa en grillas de dos o tres columnas.\n\n## Uso básico\n\n```tsx\n<FeatureCard />\n```\n\n## Reglas\n\n- Usa FeatureCard según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { FeatureCard } from \"@/components/base/FeatureCard\";\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 FeatureCard 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"
20
+ "content": "# FeatureCard\nTarjeta con icono y descripción. Para destacar características.\n\n## Cuándo usarlo\n\nPara destacar una capacidad o beneficio con icono y descripción: grillas de “qué incluye”, onboarding, páginas de marketing. Si el contenido es operable (datos, acciones), es un `Card` normal.\n\n## Composición\n\nUna sola pieza (`FeatureCard`) con icono, título y descripción. Se usa en grillas de dos o tres columnas.\n\n## Uso básico\n\n```tsx\n<FeatureCard />\n```\n\n## Reglas\n\n- Usa FeatureCard según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { FeatureCard } from \"@/components/base/FeatureCard\";\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 FeatureCard 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"
21
21
  }
22
22
  ]
23
23
  }
@@ -3,7 +3,7 @@
3
3
  "name": "form",
4
4
  "type": "registry:ui",
5
5
  "title": "Form",
6
- "description": "Formularios con validación (patrón shadcn sobre `react-hook-form` + `zod`). `Form` provee el contexto, `FormField` registra cada campo y `FormItem`/`FormLabel`/`FormControl` cablean ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo. `create-lexy add form` instala también `zod` y `@hookform/resolvers`. El componente vive en tu proyecto: instálalo con `create-lexy add form` y edítalo con libertad.",
6
+ "description": "Formularios con validación (patrón shadcn sobre `react-hook-form` + `zod`). `Form` provee el contexto, `FormField` registra cada campo y `FormItem`/`FormLabel`/`FormControl` cablean ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo. `create-lexy add form` instala también `zod` y `@hookform/resolvers`.",
7
7
  "dependencies": [
8
8
  "@radix-ui/react-slot",
9
9
  "react-hook-form@^7.62.0",
@@ -18,13 +18,13 @@
18
18
  "path": "components/base/Form.tsx",
19
19
  "type": "registry:ui",
20
20
  "target": "components/base/Form.tsx",
21
- "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport {\n Controller,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n FormProvider,\n useFormContext,\n useFormState,\n} from \"react-hook-form\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Label } from \"./Label\";\n\n// Patrón shadcn sobre react-hook-form: Form provee el contexto, FormField\n// registra el campo (Controller) y FormItem/FormLabel/FormControl cablean\n// ids y aria-* automáticamente. Validación con zod vía\n// zodResolver(@hookform/resolvers) — `add form` instala ambas deps.\n\nconst Form = FormProvider;\n\ninterface FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> {\n name: TName;\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n ...props\n}: ControllerProps<TFieldValues, TName>) => {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n );\n};\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext);\n const itemContext = React.useContext(FormItemContext);\n const { getFieldState } = useFormContext();\n const formState = useFormState({ name: fieldContext.name });\n const fieldState = getFieldState(fieldContext.name, formState);\n\n if (!fieldContext) {\n throw new Error(\"useFormField debe usarse dentro de <FormField>\");\n }\n\n const { id } = itemContext;\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n };\n};\n\ninterface FormItemContextValue {\n id: string;\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);\n\nconst FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n const id = React.useId();\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div ref={ref} className={cn(\"grid gap-2\", className)} {...props} />\n </FormItemContext.Provider>\n );\n },\n);\nFormItem.displayName = \"FormItem\";\n\nconst FormLabel = React.forwardRef<\n React.ElementRef<typeof Label>,\n React.ComponentPropsWithoutRef<typeof Label>\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField();\n\n return (\n <Label\n ref={ref}\n className={cn(error && \"text-destructive\", className)}\n htmlFor={formItemId}\n {...props}\n />\n );\n});\nFormLabel.displayName = \"FormLabel\";\n\nconst FormControl = React.forwardRef<\n React.ElementRef<typeof Slot>,\n React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField();\n\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}\n aria-invalid={!!error}\n {...props}\n />\n );\n});\nFormControl.displayName = \"FormControl\";\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField();\n\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n});\nFormDescription.displayName = \"FormDescription\";\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? \"\") : children;\n\n if (!body) {\n return null;\n }\n\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn(\"text-sm text-destructive\", className)}\n {...props}\n >\n {body}\n </p>\n );\n});\nFormMessage.displayName = \"FormMessage\";\n\nexport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n useFormField,\n};\n"
21
+ "content": "import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\nimport {\n Controller,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n FormProvider,\n useFormContext,\n useFormState,\n} from \"react-hook-form\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Label } from \"./Label\";\n\n// Patrón shadcn sobre react-hook-form: Form provee el contexto, FormField\n// registra el campo (Controller) y FormItem/FormLabel/FormControl cablean\n// ids y aria-* automáticamente. Validación con zod vía\n// zodResolver(@hookform/resolvers) — `add form` instala ambas deps.\n\nconst Form = FormProvider;\n\ninterface FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> {\n name: TName;\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n ...props\n}: ControllerProps<TFieldValues, TName>) => {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n );\n};\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext);\n const itemContext = React.useContext(FormItemContext);\n const { getFieldState } = useFormContext();\n const formState = useFormState({ name: fieldContext.name });\n const fieldState = getFieldState(fieldContext.name, formState);\n\n if (!fieldContext) {\n throw new Error(\"useFormField debe usarse dentro de <FormField>\");\n }\n\n const { id } = itemContext;\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n };\n};\n\ninterface FormItemContextValue {\n id: string;\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);\n\nconst FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => {\n const id = React.useId();\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div ref={ref} className={cn(\"grid gap-2\", className)} {...props} />\n </FormItemContext.Provider>\n );\n },\n);\nFormItem.displayName = \"FormItem\";\n\nconst FormLabel = React.forwardRef<\n React.ElementRef<typeof Label>,\n React.ComponentPropsWithoutRef<typeof Label>\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField();\n\n return (\n <Label\n ref={ref}\n className={cn(error && \"text-destructive\", className)}\n htmlFor={formItemId}\n {...props}\n />\n );\n});\nFormLabel.displayName = \"FormLabel\";\n\nconst FormControl = React.forwardRef<\n React.ElementRef<typeof Slot>,\n React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } = useFormField();\n\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}\n aria-invalid={!!error}\n {...props}\n />\n );\n});\nFormControl.displayName = \"FormControl\";\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField();\n\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn(\"type-supporting text-muted-foreground\", className)}\n {...props}\n />\n );\n});\nFormDescription.displayName = \"FormDescription\";\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? \"\") : children;\n\n if (!body) {\n return null;\n }\n\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn(\"type-supporting text-destructive\", className)}\n {...props}\n >\n {body}\n </p>\n );\n});\nFormMessage.displayName = \"FormMessage\";\n\nexport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n useFormField,\n};\n"
22
22
  },
23
23
  {
24
24
  "path": "components/base/Form.md",
25
25
  "type": "registry:file",
26
26
  "target": "components/base/Form.md",
27
- "content": "# Form — guía de uso\n\nFormularios con validación (patrón shadcn sobre `react-hook-form` + `zod`). `Form` provee el contexto, `FormField` registra cada campo y `FormItem`/`FormLabel`/`FormControl` cablean ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo. `create-lexy add form` instala también `zod` y `@hookform/resolvers`. El componente vive en tu proyecto: instálalo con `create-lexy add form` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara formularios con validación: registra cada campo, muestra errores que explican cómo salir de ellos y cablea la accesibilidad sola. Para un campo suelto sin validación (un buscador), no hace falta — usa el input directo.\n\n## Composición\n\nPatrón shadcn sobre react-hook-form + zod: `Form` provee el contexto, `FormField` registra cada campo, y `FormItem`/`FormLabel`/`FormControl` conectan ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo y `FormDescription` la ayuda.\n\n## Uso básico\n\n```tsx\nconst schema = z.object({\n email: z.string().email(\"Ingresa un correo válido\"),\n nombre: z.string().min(2, \"Ingresa tu nombre completo\"),\n});\n\nfunction PerfilForm() {\n const form = useForm<z.infer<typeof schema>>({\n resolver: zodResolver(schema),\n defaultValues: { email: \"\", nombre: \"\" },\n });\n\n function onSubmit(values: z.infer<typeof schema>) {\n // values ya viene validado y tipado\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"grid gap-6\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel required>Correo</FormLabel>\n <FormControl>\n <Input type=\"email\" placeholder=\"nombre@empresa.cl\" {...field} />\n </FormControl>\n <FormDescription>Usaremos este correo para avisarte.</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\">Guardar cambios</Button>\n </form>\n </Form>\n );\n}\n```\n\n## Integración con los inputs del sistema\n\n`FormControl` es un Slot: envuelve cualquier control del registry sin nodo extra. Con `Input`, `Textarea` y `Searchbox` pasa `{...field}` directo. Para controles con API propia, mapea explícito:\n\n```tsx\n{/* Select */}\n<Select onValueChange={field.onChange} value={field.value}>\n <FormControl>\n <SelectTrigger><SelectValue placeholder=\"Selecciona\" /></SelectTrigger>\n </FormControl>\n <SelectContent>…</SelectContent>\n</Select>\n\n{/* Checkbox / Switch */}\n<FormControl>\n <Checkbox checked={field.value} onCheckedChange={field.onChange} />\n</FormControl>\n\n{/* DatePicker */}\n<FormControl>\n <DatePicker value={field.value} onChange={field.onChange} />\n</FormControl>\n```\n\n## Reglas (UX writing de errores)\n\n- El mensaje de error dice **qué pasó y cómo arreglarlo, en tono neutro**: *\"Ingresa un correo válido\"*, no *\"Error de validación\"* ni *\"Campo inválido\"*.\n- Escribe los mensajes en el schema de zod (segundo argumento de cada validación): ahí viven junto a la regla.\n- El label existe siempre, aunque haya placeholder; campo obligatorio se marca con `required` en `FormLabel`.\n- Los inputs del sistema ya pintan el borde rojo con `aria-invalid` (se lo pone `FormControl`); no dupliques estilos de error.\n- Valida en el submit (default de react-hook-form); evita validar tecla a tecla salvo necesidad real (`mode: \"onBlur\"` si el flujo lo pide).\n\n## Cuándo NO usar\n\n- **Un solo campo suelto** (un buscador, un toggle) → usa el control directo con estado local; el patrón Form paga cuando hay validación y varios campos.\n- **Filtros que aplican al instante** → estado local o de URL, sin submit.\n\n## Import\n\n```tsx\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\n\nimport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from \"@/components/base/Form\";\n```\n\n## Props\n\n| Pieza | Qué es |\n|---|---|\n| `Form` | Alias de `FormProvider` de react-hook-form: recibe `{...form}`. |\n| `FormField` | `Controller` + contexto del nombre del campo (`control`, `name`, `render`). |\n| `FormItem` | Contenedor del campo (`grid gap-2`); genera el id base. `className`, `...props`, `ref`. |\n| `FormLabel` | `Label` del sistema conectado al control (`htmlFor` automático, rojo con error). Acepta `required`. |\n| `FormControl` | Slot que inyecta `id`, `aria-describedby`, `aria-invalid` al control envuelto. |\n| `FormDescription` | Ayuda contextual (`text-muted-foreground`). |\n| `FormMessage` | Error del campo (`text-destructive`); no renderiza nada si no hay error. |\n| `useFormField` | Hook con el estado del campo, para piezas custom. |\n\n## Para IA\n\n1. Define el schema zod arriba del componente; mensajes de error en español neutro que digan cómo corregir.\n2. Un `FormField` por campo, siempre con `FormItem` + `FormLabel` + `FormControl` + `FormMessage`.\n3. Usa los controles del registry dentro de `FormControl` (Input, Select, Checkbox, DatePicker…), no inputs nativos sin estilo.\n4. `FormDescription` solo si la ayuda aporta; no repitas el label.\n5. El botón de submit es `Button` con `type=\"submit\"` dentro del `<form>`.\n"
27
+ "content": "# Form\nFormularios con validación (patrón shadcn sobre `react-hook-form` + `zod`). `Form` provee el contexto, `FormField` registra cada campo y `FormItem`/`FormLabel`/`FormControl` cablean ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo. `create-lexy add form` instala también `zod` y `@hookform/resolvers`.\n\n## Cuándo usarlo\n\nPara formularios con validación: registra cada campo, muestra errores que explican cómo salir de ellos y cablea la accesibilidad sola. Para un campo suelto sin validación (un buscador), no hace falta — usa el input directo.\n\n## Composición\n\nPatrón shadcn sobre react-hook-form + zod: `Form` provee el contexto, `FormField` registra cada campo, y `FormItem`/`FormLabel`/`FormControl` conectan ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo y `FormDescription` la ayuda.\n\n## Uso básico\n\n```tsx\nconst schema = z.object({\n email: z.string().email(\"Ingresa un correo válido\"),\n nombre: z.string().min(2, \"Ingresa tu nombre completo\"),\n});\n\nfunction PerfilForm() {\n const form = useForm<z.infer<typeof schema>>({\n resolver: zodResolver(schema),\n defaultValues: { email: \"\", nombre: \"\" },\n });\n\n function onSubmit(values: z.infer<typeof schema>) {\n // values ya viene validado y tipado\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"grid gap-6\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel required>Correo</FormLabel>\n <FormControl>\n <Input type=\"email\" placeholder=\"nombre@empresa.cl\" {...field} />\n </FormControl>\n <FormDescription>Usaremos este correo para avisarte.</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\">Guardar cambios</Button>\n </form>\n </Form>\n );\n}\n```\n\n## Integración con los inputs del sistema\n\n`FormControl` es un Slot: envuelve cualquier control del registry sin nodo extra. Con `Input`, `Textarea` y `Searchbox` pasa `{...field}` directo. Para controles con API propia, mapea explícito:\n\n```tsx\n{/* Select */}\n<Select onValueChange={field.onChange} value={field.value}>\n <FormControl>\n <SelectTrigger><SelectValue placeholder=\"Selecciona\" /></SelectTrigger>\n </FormControl>\n <SelectContent>…</SelectContent>\n</Select>\n\n{/* Checkbox / Switch */}\n<FormControl>\n <Checkbox checked={field.value} onCheckedChange={field.onChange} />\n</FormControl>\n\n{/* DatePicker */}\n<FormControl>\n <DatePicker value={field.value} onChange={field.onChange} />\n</FormControl>\n```\n\n## Reglas (UX writing de errores)\n\n- El mensaje de error dice **qué pasó y cómo arreglarlo, en tono neutro**: *\"Ingresa un correo válido\"*, no *\"Error de validación\"* ni *\"Campo inválido\"*.\n- Escribe los mensajes en el schema de zod (segundo argumento de cada validación): ahí viven junto a la regla.\n- El label existe siempre, aunque haya placeholder; campo obligatorio se marca con `required` en `FormLabel`.\n- Los inputs del sistema ya pintan el borde rojo con `aria-invalid` (se lo pone `FormControl`); no dupliques estilos de error.\n- Valida en el submit (default de react-hook-form); evita validar tecla a tecla salvo necesidad real (`mode: \"onBlur\"` si el flujo lo pide).\n\n## Cuándo NO usar\n\n- **Un solo campo suelto** (un buscador, un toggle) → usa el control directo con estado local; el patrón Form paga cuando hay validación y varios campos.\n- **Filtros que aplican al instante** → estado local o de URL, sin submit.\n\n## Import\n\n```tsx\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\n\nimport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from \"@/components/base/Form\";\n```\n\n## Props\n\n| Pieza | Qué es |\n|---|---|\n| `Form` | Alias de `FormProvider` de react-hook-form: recibe `{...form}`. |\n| `FormField` | `Controller` + contexto del nombre del campo (`control`, `name`, `render`). |\n| `FormItem` | Contenedor del campo (`grid gap-2`); genera el id base. `className`, `...props`, `ref`. |\n| `FormLabel` | `Label` del sistema conectado al control (`htmlFor` automático, rojo con error). Acepta `required`. |\n| `FormControl` | Slot que inyecta `id`, `aria-describedby`, `aria-invalid` al control envuelto. |\n| `FormDescription` | Ayuda contextual (`text-muted-foreground`). |\n| `FormMessage` | Error del campo (`text-destructive`); no renderiza nada si no hay error. |\n| `useFormField` | Hook con el estado del campo, para piezas custom. |\n\n## Para IA\n\n1. Define el schema zod arriba del componente; mensajes de error en español neutro que digan cómo corregir.\n2. Un `FormField` por campo, siempre con `FormItem` + `FormLabel` + `FormControl` + `FormMessage`.\n3. Usa los controles del registry dentro de `FormControl` (Input, Select, Checkbox, DatePicker…), no inputs nativos sin estilo.\n4. `FormDescription` solo si la ayuda aporta; no repitas el label.\n5. El botón de submit es `Button` con `type=\"submit\"` dentro del `<form>`.\n"
28
28
  }
29
29
  ]
30
30
  }
@@ -3,7 +3,7 @@
3
3
  "name": "header-bar",
4
4
  "type": "registry:ui",
5
5
  "title": "HeaderBar",
6
- "description": "Barra superior de aplicación o sitio: marca a la izquierda y acciones a la derecha. Renderiza un `<header>` semántico. El componente vive en tu proyecto: instálalo con `create-lexy add header-bar` y edítalo con libertad.",
6
+ "description": "Barra superior de aplicación o sitio: marca a la izquierda y acciones a la derecha. Renderiza un `<header>` semántico.",
7
7
  "dependencies": [
8
8
  "class-variance-authority"
9
9
  ],
@@ -21,7 +21,7 @@
21
21
  "path": "components/base/HeaderBar.md",
22
22
  "type": "registry:file",
23
23
  "target": "components/base/HeaderBar.md",
24
- "content": "# HeaderBar — guía de uso\n\nBarra superior de aplicación o sitio: marca a la izquierda y acciones a la derecha. Renderiza un `<header>` semántico. El componente vive en tu proyecto: instálalo con `create-lexy add header-bar` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara la barra superior de un sitio o app: marca a la izquierda, acciones a la derecha, semántica de `<header>`. Si puedes describir la navegación como datos, `AppHeaderBar` te ahorra la composición.\n\n## Composición\n\nUna pieza (`HeaderBar`) con slots `brand` y `actions`, variantes de superficie (clara / navy) y modo sticky.\n\n## Uso básico\n\n```tsx\n<HeaderBar\n actions={\n <>\n <Button variant=\"ghost\">Ingresar</Button>\n <Button>Crear cuenta</Button>\n </>\n }\n/>\n```\n\nPor defecto muestra el `Logo`. Sobrescribe la marca con `brand`:\n\n```tsx\n<HeaderBar brand={<Logo layout=\"horizontal\" />} actions={<UserMenu />} />\n```\n\n## Reglas\n\n- Mantén pocas acciones a la derecha; agrupa el resto en un `DropdownMenu`.\n- `surface=\"navy\"` ajusta el logo a su versión clara automáticamente; no fuerces el color del logo.\n- Si es una app con barra lateral, usa `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n- `sticky` para navegación persistente; cuida no tapar contenido al anclar.\n\n## Cuándo NO usar\n\n- **Layout completo de app con sidebar** → `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n- **Navegación lateral** → `AppSidebar`.\n- **Menús de navegación con dropdowns ricos** → combínalo con `NavigationMenu` dentro de `actions`/`brand`.\n\n## Import\n\n```tsx\nimport { HeaderBar } from \"@/components/base/HeaderBar\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `surface` | `\"default\" \\| \"transparent\" \\| \"navy\"` | `\"default\"` | Fondo. `navy` usa el ink de marca con texto blanco. |\n| `bordered` | `boolean` | `true` | Borde inferior. |\n| `padding` | `\"page\" \\| \"none\"` | `\"page\"` | Padding horizontal estándar o nulo. |\n| `sticky` | `boolean` | `false` | Fija la barra al hacer scroll. |\n| `actions` | `ReactNode` | — | Contenido alineado a la derecha. |\n| `brand` | `ReactNode` | `<Logo>` | Sobrescribe el área de marca. |\n\n## Para IA\n\n1. Úsalo como cabecera de sitio o barra superior simple.\n2. Pon la marca (o deja el `Logo` por defecto) y 1–3 acciones en `actions`.\n3. Elige `surface`/`sticky` según el contexto; agrupa acciones extra en `DropdownMenu`.\n4. Si la pantalla es una app con sidebar, prefiere `SidebarProvider` + `AppSidebar` + `SidebarInset` en lugar de componer a mano.\n"
24
+ "content": "# HeaderBar\nBarra superior de aplicación o sitio: marca a la izquierda y acciones a la derecha. Renderiza un `<header>` semántico.\n\n## Cuándo usarlo\n\nPara la barra superior de un sitio o app: marca a la izquierda, acciones a la derecha, semántica de `<header>`. Si puedes describir la navegación como datos, `AppHeaderBar` te ahorra la composición.\n\n## Composición\n\nUna pieza (`HeaderBar`) con slots `brand` y `actions`, variantes de superficie (clara / navy) y modo sticky.\n\n## Uso básico\n\n```tsx\n<HeaderBar\n actions={\n <>\n <Button variant=\"ghost\">Ingresar</Button>\n <Button>Crear cuenta</Button>\n </>\n }\n/>\n```\n\nPor defecto muestra el `Logo`. Sobrescribe la marca con `brand`:\n\n```tsx\n<HeaderBar brand={<Logo layout=\"horizontal\" />} actions={<UserMenu />} />\n```\n\n## Reglas\n\n- Mantén pocas acciones a la derecha; agrupa el resto en un `DropdownMenu`.\n- `surface=\"navy\"` ajusta el logo a su versión clara automáticamente; no fuerces el color del logo.\n- Si es una app con barra lateral, usa `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n- `sticky` para navegación persistente; cuida no tapar contenido al anclar.\n\n## Cuándo NO usar\n\n- **Layout completo de app con sidebar** → `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n- **Navegación lateral** → `AppSidebar`.\n- **Menús de navegación con dropdowns ricos** → combínalo con `NavigationMenu` dentro de `actions`/`brand`.\n\n## Import\n\n```tsx\nimport { HeaderBar } from \"@/components/base/HeaderBar\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `surface` | `\"default\" \\| \"transparent\" \\| \"navy\"` | `\"default\"` | Fondo. `navy` usa el ink de marca con texto blanco. |\n| `bordered` | `boolean` | `true` | Borde inferior. |\n| `padding` | `\"page\" \\| \"none\"` | `\"page\"` | Padding horizontal estándar o nulo. |\n| `sticky` | `boolean` | `false` | Fija la barra al hacer scroll. |\n| `actions` | `ReactNode` | — | Contenido alineado a la derecha. |\n| `brand` | `ReactNode` | `<Logo>` | Sobrescribe el área de marca. |\n\n## Para IA\n\n1. Úsalo como cabecera de sitio o barra superior simple.\n2. Pon la marca (o deja el `Logo` por defecto) y 1–3 acciones en `actions`.\n3. Elige `surface`/`sticky` según el contexto; agrupa acciones extra en `DropdownMenu`.\n4. Si la pantalla es una app con sidebar, prefiere `SidebarProvider` + `AppSidebar` + `SidebarInset` en lugar de componer a mano.\n"
25
25
  }
26
26
  ]
27
27
  }
@@ -3,7 +3,7 @@
3
3
  "name": "input",
4
4
  "type": "registry:ui",
5
5
  "title": "Input",
6
- "description": "Campo de texto de una línea. Renderiza un `<input>` nativo; acepta todos sus atributos (`type`, `value`, `onChange`, `placeholder`, `aria-*`). El componente vive en tu proyecto: instálalo con `create-lexy add input` y edítalo con libertad.",
6
+ "description": "Campo de texto de una línea. Renderiza un `<input>` nativo; acepta todos sus atributos (`type`, `value`, `onChange`, `placeholder`, `aria-*`).",
7
7
  "dependencies": [],
8
8
  "registryDependencies": [],
9
9
  "files": [
@@ -11,13 +11,13 @@
11
11
  "path": "components/base/Input.tsx",
12
12
  "type": "registry:ui",
13
13
  "target": "components/base/Input.tsx",
14
- "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded border border-input bg-background px-3 py-2 text-sm text-foreground outline-none transition-colors duration-150\",\n \"placeholder:text-muted-foreground\",\n \"file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground\",\n \"hover:border-muted-foreground/40\",\n \"focus-visible:border-primary\",\n \"aria-invalid:border-destructive aria-invalid:hover:border-destructive aria-invalid:focus-visible:border-destructive\",\n \"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input\",\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
14
+ "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"type-supporting flex h-10 w-full rounded-control border border-input bg-background px-3 py-2 text-foreground outline-none transition-colors duration-[var(--duration-fast)] ease-[var(--ease-standard)] motion-reduce:transition-none\",\n \"placeholder:text-muted-foreground\",\n \"file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground\",\n \"hover:border-muted-foreground/40\",\n \"focus-visible:border-primary\",\n \"aria-invalid:border-destructive aria-invalid:hover:border-destructive aria-invalid:focus-visible:border-destructive\",\n \"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input\",\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
15
15
  },
16
16
  {
17
17
  "path": "components/base/Input.md",
18
18
  "type": "registry:file",
19
19
  "target": "components/base/Input.md",
20
- "content": "# Input — guía de uso\n\nCampo de texto de una línea. Renderiza un `<input>` nativo; acepta todos sus atributos (`type`, `value`, `onChange`, `placeholder`, `aria-*`). El componente vive en tu proyecto: instálalo con `create-lexy add input` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara texto corto de una línea: nombre, RUT, correo, monto. El tipo correcto (`email`, `tel`, `number`) trae el teclado correcto en móvil. Para texto que respira (comentarios, relatos), usa `Textarea`; para buscar, `Searchbox`.\n\n## Composición\n\nUna sola pieza (`Input`, un `<input>` nativo) sin label propio: siempre con `Label` visible arriba — el placeholder no reemplaza al label. En formularios validados se envuelve con `FormControl`.\n\n## Uso básico\n\nSiempre con un `Label` asociado por `id`/`htmlFor`:\n\n```tsx\n<div className=\"grid gap-1.5\">\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"nombre@empresa.cl\" />\n</div>\n```\n\n## Estado de error\n\n```tsx\n<div className=\"grid gap-1.5\">\n <Label htmlFor=\"rut\">RUT</Label>\n <Input id=\"rut\" aria-invalid aria-describedby=\"rut-error\" />\n <p id=\"rut-error\" className=\"text-sm text-destructive\">\n Ingresa un RUT válido, por ejemplo 12.345.678-9.\n </p>\n</div>\n```\n\n## Reglas\n\n- **Siempre** asocia un `Label` por `htmlFor`/`id`. El `placeholder` no es un label: desaparece al escribir.\n- Marca el error con `aria-invalid` **y** un mensaje de texto cercano; el color del borde no basta (accesibilidad).\n- Enlaza el mensaje de error con `aria-describedby` para lectores de pantalla.\n- Usa el `type` correcto (`email`, `number`, `tel`): mejora el teclado móvil y la validación.\n\n## Cuándo NO usar\n\n- **Texto largo / multilínea** → `Textarea`.\n- **Elegir de una lista fija** → `Select` (o `Combobox` si hay búsqueda).\n- **Sí/no o selección múltiple** → `Checkbox` / `RadioGroup` / `Switch`.\n- **Buscar con icono y limpiar** → `Searchbox`.\n\n## Import\n\n```tsx\nimport { Input } from \"@/components/base/Input\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `type` | `string` | `\"text\"` | `text`, `email`, `password`, `number`, `search`, `file`, etc. |\n| `aria-invalid` | `boolean` | — | Activa el estilo de error (borde destructive). |\n| `disabled` | `boolean` | `false` | Deshabilita el campo. |\n| ...resto | `input` props | — | `value`, `onChange`, `placeholder`, `name`, `required`, etc. |\n\n## Para IA\n\n1. Confirma que el dato es texto corto de una línea; si no, elige el control adecuado (ver arriba).\n2. Crea el par `Label` + `Input` con `htmlFor`/`id` coincidentes.\n3. Elige `type` según el dato.\n4. Para validación, usa `aria-invalid` + mensaje con `aria-describedby`, redactado como corregir (no solo \"campo inválido\").\n5. No uses el `placeholder` como única etiqueta.\n"
20
+ "content": "# Input\nCampo de texto de una línea. Renderiza un `<input>` nativo; acepta todos sus atributos (`type`, `value`, `onChange`, `placeholder`, `aria-*`).\n\n## Cuándo usarlo\n\nPara texto corto de una línea: nombre, RUT, correo, monto. El tipo correcto (`email`, `tel`, `number`) trae el teclado correcto en móvil. Para texto que respira (comentarios, relatos), usa `Textarea`; para buscar, `Searchbox`.\n\n## Composición\n\nUna sola pieza (`Input`, un `<input>` nativo) sin label propio: siempre con `Label` visible arriba — el placeholder no reemplaza al label. En formularios validados se envuelve con `FormControl`.\n\n## Uso básico\n\nSiempre con un `Label` asociado por `id`/`htmlFor`:\n\n```tsx\n<div className=\"grid gap-1.5\">\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"nombre@empresa.cl\" />\n</div>\n```\n\n## Estado de error\n\n```tsx\n<div className=\"grid gap-1.5\">\n <Label htmlFor=\"rut\">RUT</Label>\n <Input id=\"rut\" aria-invalid aria-describedby=\"rut-error\" />\n <p id=\"rut-error\" className=\"text-sm text-destructive\">\n Ingresa un RUT válido, por ejemplo 12.345.678-9.\n </p>\n</div>\n```\n\n## Reglas\n\n- **Siempre** asocia un `Label` por `htmlFor`/`id`. El `placeholder` no es un label: desaparece al escribir.\n- Marca el error con `aria-invalid` **y** un mensaje de texto cercano; el color del borde no basta (accesibilidad).\n- Enlaza el mensaje de error con `aria-describedby` para lectores de pantalla.\n- Usa el `type` correcto (`email`, `number`, `tel`): mejora el teclado móvil y la validación.\n\n## Cuándo NO usar\n\n- **Texto largo / multilínea** → `Textarea`.\n- **Elegir de una lista fija** → `Select` (o `Combobox` si hay búsqueda).\n- **Sí/no o selección múltiple** → `Checkbox` / `RadioGroup` / `Switch`.\n- **Buscar con icono y limpiar** → `Searchbox`.\n\n## Import\n\n```tsx\nimport { Input } from \"@/components/base/Input\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `type` | `string` | `\"text\"` | `text`, `email`, `password`, `number`, `search`, `file`, etc. |\n| `aria-invalid` | `boolean` | — | Activa el estilo de error (borde destructive). |\n| `disabled` | `boolean` | `false` | Deshabilita el campo. |\n| ...resto | `input` props | — | `value`, `onChange`, `placeholder`, `name`, `required`, etc. |\n\n## Para IA\n\n1. Confirma que el dato es texto corto de una línea; si no, elige el control adecuado (ver arriba).\n2. Crea el par `Label` + `Input` con `htmlFor`/`id` coincidentes.\n3. Elige `type` según el dato.\n4. Para validación, usa `aria-invalid` + mensaje con `aria-describedby`, redactado como corregir (no solo \"campo inválido\").\n5. No uses el `placeholder` como única etiqueta.\n"
21
21
  }
22
22
  ]
23
23
  }
@@ -16,7 +16,7 @@
16
16
  "path": "views/IntakeDatosPersonales.tsx",
17
17
  "type": "registry:file",
18
18
  "target": "views/IntakeDatosPersonales.tsx",
19
- "content": "import { Button } from \"@/components/base/Button\";\nimport { Input } from \"@/components/base/Input\";\nimport { Label } from \"@/components/base/Label\";\nimport { Progress } from \"@/components/base/Progress\";\n\n// Receta cliente — intake / ficha web (paso de wizard): un paso enfocado,\n// columna angosta, calma, CTA al final. Móvil primero: bajo sm las\n// acciones apilan full-width (pauta buenas-practicas.md §5). Ver intake-wizard.md.\n\nexport function IntakeDatosPersonales() {\n return (\n <main className=\"mx-auto w-full max-w-xl px-margin-mobile py-6 sm:py-10\">\n <div className=\"mb-8\">\n <p className=\"mb-2 text-sm text-muted-foreground\">Paso 2 de 4</p>\n <Progress value={50} aria-label=\"Avance del formulario\" />\n </div>\n\n <header className=\"mb-6\">\n <h1 className=\"text-2xl font-bold text-foreground\">Cuéntanos tus datos</h1>\n <p className=\"mt-2 text-base text-muted-foreground\">\n Usaremos esta información para preparar la primera revisión de tu caso.\n </p>\n </header>\n\n <form className=\"space-y-5\">\n <fieldset className=\"space-y-5 border-0 p-0\">\n <legend className=\"sr-only\">Datos personales</legend>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"nombre\">Nombre completo</Label>\n <Input id=\"nombre\" name=\"nombre\" placeholder=\"Escribe tu nombre\" />\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"correo\">Correo</Label>\n <Input id=\"correo\" name=\"correo\" type=\"email\" placeholder=\"tu@correo.cl\" />\n <p className=\"text-xs text-muted-foreground\">\n Te escribiremos solo para avanzar con tu caso.\n </p>\n </div>\n </fieldset>\n\n <div className=\"flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:items-center sm:justify-between\">\n <Button type=\"button\" variant=\"ghost\" className=\"w-full sm:w-auto\">\n Volver\n </Button>\n <Button type=\"submit\" className=\"w-full sm:w-auto\">\n Guardar y continuar\n </Button>\n </div>\n </form>\n </main>\n );\n}\n"
19
+ "content": "import { Button } from \"@/components/base/Button\";\nimport { Input } from \"@/components/base/Input\";\nimport { Label } from \"@/components/base/Label\";\nimport { Progress } from \"@/components/base/Progress\";\n\n// Receta cliente — intake / ficha web (paso de wizard): un paso enfocado,\n// columna angosta, calma, CTA al final. Móvil primero: bajo sm las\n// acciones apilan full-width (pauta buenas-practicas.md §5). Ver intake-wizard.md.\n\nexport function IntakeDatosPersonales() {\n return (\n <main className=\"mx-auto w-full max-w-xl px-margin-mobile py-6 sm:py-10\">\n <div className=\"mb-8\">\n <p className=\"mb-2 type-supporting text-muted-foreground\">Paso 2 de 4</p>\n <Progress value={50} aria-label=\"Avance del formulario\" />\n </div>\n\n <header className=\"mb-6\">\n <h1 className=\"type-page-title text-foreground\">Cuéntanos tus datos</h1>\n <p className=\"mt-2 type-body text-muted-foreground\">\n Usaremos esta información para preparar la primera revisión de tu caso.\n </p>\n </header>\n\n <form className=\"space-y-5\">\n <fieldset className=\"space-y-5 border-0 p-0\">\n <legend className=\"sr-only\">Datos personales</legend>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"nombre\">Nombre completo</Label>\n <Input id=\"nombre\" name=\"nombre\" placeholder=\"Escribe tu nombre\" />\n </div>\n\n <div className=\"space-y-2\">\n <Label htmlFor=\"correo\">Correo</Label>\n <Input id=\"correo\" name=\"correo\" type=\"email\" placeholder=\"tu@correo.cl\" />\n <p className=\"type-meta text-muted-foreground\">\n Te escribiremos solo para avanzar con tu caso.\n </p>\n </div>\n </fieldset>\n\n <div className=\"flex flex-col-reverse gap-2 pt-2 sm:flex-row sm:items-center sm:justify-between\">\n <Button type=\"button\" variant=\"ghost\" className=\"w-full sm:w-auto\">\n Volver\n </Button>\n <Button type=\"submit\" className=\"w-full sm:w-auto\">\n Guardar y continuar\n </Button>\n </div>\n </form>\n </main>\n );\n}\n"
20
20
  },
21
21
  {
22
22
  "path": "views/intake-wizard.md",