create-lexy 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/assets/fonts/{OFL-NotoSans.txt → LICENSE-Geist.txt} +4 -5
- package/assets/fonts/geist-mono-variable-italic.woff2 +0 -0
- package/assets/fonts/geist-mono-variable.woff2 +0 -0
- package/assets/fonts/geist-sans-variable-italic.woff2 +0 -0
- package/assets/fonts/geist-sans-variable.woff2 +0 -0
- package/assets/r/accordion.json +3 -3
- package/assets/r/alert-dialog.json +3 -3
- package/assets/r/app-accordion.json +1 -1
- package/assets/r/app-dialog.json +3 -3
- package/assets/r/app-header-bar.json +2 -2
- package/assets/r/app-sidebar.json +2 -2
- package/assets/r/avatar.json +3 -3
- package/assets/r/badge.json +3 -3
- package/assets/r/brand-background.json +1 -1
- package/assets/r/breadcrumb.json +3 -3
- package/assets/r/button-group.json +3 -3
- package/assets/r/button.json +3 -3
- package/assets/r/calendar.json +2 -2
- package/assets/r/card.json +3 -3
- package/assets/r/chart.json +2 -2
- package/assets/r/checkbox.json +2 -2
- package/assets/r/combobox.json +3 -3
- package/assets/r/command.json +3 -3
- package/assets/r/confirmacion.json +1 -1
- package/assets/r/counter-badge.json +3 -3
- package/assets/r/crm-desk.json +1 -1
- package/assets/r/crm-detalle-caso.json +5 -5
- package/assets/r/date-picker.json +1 -1
- package/assets/r/dialog.json +3 -3
- package/assets/r/dropdown-menu.json +3 -3
- package/assets/r/empty.json +2 -2
- package/assets/r/feature-card.json +3 -3
- package/assets/r/form.json +3 -3
- package/assets/r/header-bar.json +2 -2
- package/assets/r/input.json +3 -3
- package/assets/r/intake-wizard.json +1 -1
- package/assets/r/label.json +3 -3
- package/assets/r/logo.json +2 -2
- package/assets/r/menubar.json +3 -3
- package/assets/r/navigation-menu.json +3 -3
- package/assets/r/pagination.json +2 -2
- package/assets/r/popover.json +3 -3
- package/assets/r/profile-card.json +3 -3
- package/assets/r/progress.json +2 -2
- package/assets/r/radio-group.json +2 -2
- package/assets/r/registry.json +47 -47
- package/assets/r/scroll-area.json +2 -2
- package/assets/r/searchbox.json +3 -3
- package/assets/r/select.json +3 -3
- package/assets/r/separator.json +2 -2
- package/assets/r/sheet.json +2 -2
- package/assets/r/sidebar.json +3 -3
- package/assets/r/skeleton.json +2 -2
- package/assets/r/slider.json +3 -3
- package/assets/r/snippet.json +3 -3
- package/assets/r/spinner.json +2 -2
- package/assets/r/status-dot.json +3 -3
- package/assets/r/switch.json +3 -3
- package/assets/r/table.json +3 -3
- package/assets/r/tabs.json +3 -3
- package/assets/r/tag.json +3 -3
- package/assets/r/textarea.json +3 -3
- package/assets/r/toaster.json +3 -3
- package/assets/r/tooltip.json +3 -3
- package/assets/r/tree.json +3 -3
- package/assets/registry-version +1 -1
- package/assets/theme/lexy-theme.css +562 -112
- package/dist/index.js +590 -276
- package/package.json +4 -2
- package/templates/.claude/skills/lexy-design/SKILL.md +189 -0
- package/templates/.claude/skills/lexy-dev/SKILL.md +168 -0
- package/templates/.claude/skills/lexy-mock-data/SKILL.md +50 -0
- package/templates/.github/copilot-instructions.md +50 -0
- package/templates/.mcp.json +8 -0
- package/templates/AGENTS.md +243 -0
- package/templates/CLAUDE.md +61 -0
- package/templates/ai/IMPLEMENTATION-PROTOCOL.md +148 -0
- package/templates/ai/PRODUCTION-CLEANUP.md +17 -0
- package/templates/ai/PROJECT-CONTEXT.md +65 -0
- package/templates/ai/README.md +19 -0
- package/templates/ai/TECHNICAL-USAGE.md +220 -0
- package/templates/ai/pautas/arquitectura-informacion-ux.md +243 -0
- package/templates/ai/pautas/buenas-practicas.md +236 -0
- package/templates/ai/pautas/calidad-industria.md +109 -0
- package/templates/ai/pautas/diseno-cliente.md +136 -0
- package/templates/ai/pautas/diseno-crm-lexy.md +109 -0
- package/templates/ai/pautas/patrones-de-codigo.md +234 -0
- package/templates/ai/pautas/recetas-layout.md +419 -0
- package/templates/ai/pautas/sistema-visual.md +197 -0
- package/templates/ai/pautas/ux-writing.md +214 -0
- package/templates/scripts/check-geometry.mjs +139 -0
- package/assets/fonts/noto-sans-latin.woff2 +0 -0
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"path": "components/base/BrandBackground.md",
|
|
18
18
|
"type": "registry:file",
|
|
19
19
|
"target": "components/base/BrandBackground.md",
|
|
20
|
-
"content": "# BrandBackground
|
|
20
|
+
"content": "# BrandBackground\nFondos oficiales del manual de marca Lexy (sección 05. Fondos) como componente:\nlas tramas isométricas de cubos y aspas en los siete tonos de la paleta, el\ngradiente grainient y el aspa de esquina para portadas. Llenan a su contenedor\ncon `object-cover` y son puramente decorativos.\n\n## Cuándo usarlo\n\nEn superficies expresivas de marca: el hero de una portada, la apertura de una\nsección, una tarjeta destacada o la pantalla de un pendón/demo. Nunca en\nsuperficies de trabajo — si la pantalla es operativa (formularios, tablas,\nflujos), el fondo debe ser plano.\n\n## Motivos y tonos\n\n- `motif=\"cubos\"` — trama isométrica de cubos pequeños. La más sobria: es la\n indicada cuando hay texto encima.\n- `motif=\"aspas\"` — trama de aspas (el isotipo) grandes. Más expresiva, para\n superficies con poco contenido.\n- `motif=\"grainient\"` — gradiente lavanda/índigo con grano. Solo lavanda.\n- `motif=\"aspa-esquina\"` — un aspa gigante en la esquina inferior derecha,\n pensado para portadas. Solo lavanda.\n\nLos motivos `cubos` y `aspas` aceptan `tone`: `navy` (por defecto),\n`indigo-oscuro`, `indigo`, `lavanda`, `blanco`, `magenta` y `teal`. Magenta y\nteal son tonos de sub-marca: úsalos solo en contextos de esas líneas.\n\n## Uso\n\nMontado absoluto detrás del contenido, con un color de fondo de respaldo en el\ncontenedor mientras la imagen carga:\n\n```tsx\n<section className=\"relative isolate overflow-hidden rounded-lg bg-brand-navy\">\n <BrandBackground motif=\"cubos\" tone=\"navy\" className=\"absolute inset-0\" />\n <div className=\"relative\">{/* contenido */}</div>\n</section>\n```\n\n## Atenuación\n\nCuando hay texto encima, la trama plena compite con la lectura. La prop `dim`\n(0–1) la atenúa fundiéndola hacia el color de fondo del contenedor — por eso\nel contenedor debe llevar el tono correspondiente (`bg-brand-navy` para\n`tone=\"navy\"`). Con `dim={0.9}` la trama queda apenas insinuada, como textura:\n\n```tsx\n<section className=\"relative isolate overflow-hidden rounded-lg bg-brand-navy\">\n <BrandBackground motif=\"cubos\" tone=\"navy\" dim={0.9} className=\"absolute inset-0\" />\n <div className=\"relative\">{/* contenido */}</div>\n</section>\n```\n\n## Contraste\n\nSobre `navy` e `indigo-oscuro` el texto va blanco; sobre `lavanda`, `blanco` y\nel `grainient` va en tinta (`brand-navy`). Verifica el contraste real: las\ntramas son sutiles pero existen. Para bloques largos de texto, usa `cubos` con\n`dim` alto o un fondo plano.\n\n## Accesibilidad\n\nLa imagen es decorativa: lleva `alt=\"\"` y `aria-hidden`, no porta significado\ny no debe usarse como único vehículo de identidad de una sección.\n"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"path": "components/base/assets/backgrounds/lexy-fondo-1.webp",
|
package/assets/r/breadcrumb.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "breadcrumb",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Breadcrumb",
|
|
6
|
-
"description": "Componente Breadcrumb.
|
|
6
|
+
"description": "Componente Breadcrumb.",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"@radix-ui/react-slot"
|
|
9
9
|
],
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"path": "components/base/Breadcrumb.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
15
|
"target": "components/base/Breadcrumb.tsx",
|
|
16
|
-
"content": "import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Breadcrumb = React.forwardRef<HTMLElement, React.ComponentPropsWithoutRef<\"nav\">>(\n (props, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />,\n);\nBreadcrumb.displayName = \"Breadcrumb\";\n\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<\"ol\">>(\n ({ className, ...props }, ref) => (\n <ol\n ref={ref}\n className={cn(\n \"flex flex-wrap items-center gap-2 break-words
|
|
16
|
+
"content": "import { Slot } from \"@radix-ui/react-slot\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Breadcrumb = React.forwardRef<HTMLElement, React.ComponentPropsWithoutRef<\"nav\">>(\n (props, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />,\n);\nBreadcrumb.displayName = \"Breadcrumb\";\n\nconst BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<\"ol\">>(\n ({ className, ...props }, ref) => (\n <ol\n ref={ref}\n className={cn(\n \"flex flex-wrap items-center gap-2 break-words type-supporting text-muted-foreground\",\n className,\n )}\n {...props}\n />\n ),\n);\nBreadcrumbList.displayName = \"BreadcrumbList\";\n\nconst BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<\"li\">>(\n ({ className, ...props }, ref) => (\n <li ref={ref} className={cn(\"inline-flex items-center gap-2\", className)} {...props} />\n ),\n);\nBreadcrumbItem.displayName = \"BreadcrumbItem\";\n\nconst BreadcrumbLink = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentPropsWithoutRef<\"a\"> & {\n /** Renderiza el hijo como el enlace (patrón Slot): `<BreadcrumbLink asChild><Link …/></BreadcrumbLink>`. */\n asChild?: boolean;\n }\n>(({ asChild = false, className, ...props }, ref) => {\n const Comp = asChild ? Slot : \"a\";\n return (\n <Comp\n ref={ref}\n className={cn(\n \"rounded text-primary transition-colors hover:text-primary/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n className,\n )}\n {...props}\n />\n );\n});\nBreadcrumbLink.displayName = \"BreadcrumbLink\";\n\nconst BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<\"span\">>(\n ({ className, ...props }, ref) => (\n <span\n ref={ref}\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n className={cn(\"font-medium text-foreground\", className)}\n {...props}\n />\n ),\n);\nBreadcrumbPage.displayName = \"BreadcrumbPage\";\n\nconst BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<\"li\">) => (\n <li\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\n \"flex items-center text-muted-foreground [&>svg]:size-(--icon-size-inline)\",\n className,\n )}\n {...props}\n >\n {children ?? (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n )}\n </li>\n);\nBreadcrumbSeparator.displayName = \"BreadcrumbSeparator\";\n\nconst BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<\"span\">) => (\n <span\n role=\"presentation\"\n aria-hidden=\"true\"\n className={cn(\"flex items-center text-muted-foreground\", className)}\n {...props}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n className=\"size-(--icon-size-inline)\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"1\" />\n <circle cx=\"19\" cy=\"12\" r=\"1\" />\n <circle cx=\"5\" cy=\"12\" r=\"1\" />\n </svg>\n <span className=\"sr-only\">More</span>\n </span>\n);\nBreadcrumbEllipsis.displayName = \"BreadcrumbEllipsis\";\n\nexport {\n Breadcrumb,\n BreadcrumbEllipsis,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n};\n"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "components/base/Breadcrumb.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Breadcrumb.md",
|
|
22
|
-
"content": "# Breadcrumb
|
|
22
|
+
"content": "# Breadcrumb\nComponente Breadcrumb.\n\n## Cuándo usarlo\n\nPara ubicar a la persona en jerarquías de más de dos niveles y darle retorno: Casos / Caso 482 / Documentos. Si la app es plana (una sidebar y ya), el breadcrumb es ruido.\n\n## Composición\n\n`Breadcrumb` > `BreadcrumbList` > `BreadcrumbItem`s: `BreadcrumbLink` para los antecesores, `BreadcrumbPage` para el nivel actual, con `BreadcrumbSeparator` entre medio y `BreadcrumbEllipsis` para rutas largas.\n\n## Uso básico\n\n```tsx\n<Breadcrumb />\n```\n\n## Reglas\n\n- Usa Breadcrumb según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Breadcrumb } from \"@/components/base/Breadcrumb\";\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 Breadcrumb desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "button-group",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "ButtonGroup",
|
|
6
|
-
"description": "Agrupa botones relacionados en una sola unidad visual. Sirve para acciones hermanas, comandos de toolbar o split actions. No mantiene estado de selección por sí mismo.
|
|
6
|
+
"description": "Agrupa botones relacionados en una sola unidad visual. Sirve para acciones hermanas, comandos de toolbar o split actions. No mantiene estado de selección por sí mismo.",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"@radix-ui/react-slot",
|
|
9
9
|
"class-variance-authority"
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
"path": "components/base/ButtonGroup.tsx",
|
|
17
17
|
"type": "registry:ui",
|
|
18
18
|
"target": "components/base/ButtonGroup.tsx",
|
|
19
|
-
"content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Separator } from \"./Separator\";\n\nconst buttonGroupVariants = cva(\n \"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:relative [&>*]:hover:z-10 [&>*]:focus-visible:z-10 [&>input]:flex-1\",\n {\n variants: {\n orientation: {\n horizontal:\n \"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px [&>[data-slot=button-group-separator]]:!ml-0 [&>[data-slot=button-group-separator]+*]:!ml-0 [&>[data-slot=button-group-separator]+*]:border-l-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-r-0\",\n vertical:\n \"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none [&>*:not(:first-child)]:-mt-px [&>[data-slot=button-group-separator]]:!mt-0 [&>[data-slot=button-group-separator]+*]:!mt-0 [&>[data-slot=button-group-separator]+*]:border-t-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-b-0\",\n },\n },\n defaultVariants: {\n orientation: \"horizontal\",\n },\n },\n);\n\nexport interface ButtonGroupProps\n extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof buttonGroupVariants> {}\n\nconst ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(\n ({ className, orientation = \"horizontal\", ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n data-slot=\"button-group\"\n data-orientation={orientation}\n className={cn(buttonGroupVariants({ orientation }), className)}\n {...props}\n />\n ),\n);\nButtonGroup.displayName = \"ButtonGroup\";\n\nexport type ButtonGroupSeparatorProps = React.ComponentPropsWithoutRef<typeof Separator>;\n\nconst ButtonGroupSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n ButtonGroupSeparatorProps\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n <Separator\n ref={ref}\n decorative\n orientation={orientation}\n data-slot=\"button-group-separator\"\n className={cn(\n orientation === \"vertical\" ? \"mx-0 h-auto self-stretch\" : \"my-0 w-auto self-stretch\",\n className,\n )}\n {...props}\n />\n));\nButtonGroupSeparator.displayName = \"ButtonGroupSeparator\";\n\nexport interface ButtonGroupTextProps extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n}\n\nconst ButtonGroupText = React.forwardRef<HTMLDivElement, ButtonGroupTextProps>(\n ({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-slot=\"button-group-text\"\n className={cn(\n \"flex h-10 items-center whitespace-nowrap rounded border border-border bg-background px-3
|
|
19
|
+
"content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Separator } from \"./Separator\";\n\nconst buttonGroupVariants = cva(\n \"flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:relative [&>*]:hover:z-10 [&>*]:focus-visible:z-10 [&>input]:flex-1\",\n {\n variants: {\n orientation: {\n horizontal:\n \"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none [&>*:not(:first-child)]:-ml-px [&>[data-slot=button-group-separator]]:!ml-0 [&>[data-slot=button-group-separator]+*]:!ml-0 [&>[data-slot=button-group-separator]+*]:border-l-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-r-0\",\n vertical:\n \"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none [&>*:not(:first-child)]:-mt-px [&>[data-slot=button-group-separator]]:!mt-0 [&>[data-slot=button-group-separator]+*]:!mt-0 [&>[data-slot=button-group-separator]+*]:border-t-0 [&>:not([data-slot=button-group-separator]):has(+[data-slot=button-group-separator])]:border-b-0\",\n },\n },\n defaultVariants: {\n orientation: \"horizontal\",\n },\n },\n);\n\nexport interface ButtonGroupProps\n extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof buttonGroupVariants> {}\n\nconst ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(\n ({ className, orientation = \"horizontal\", ...props }, ref) => (\n <div\n ref={ref}\n role=\"group\"\n data-slot=\"button-group\"\n data-orientation={orientation}\n className={cn(buttonGroupVariants({ orientation }), className)}\n {...props}\n />\n ),\n);\nButtonGroup.displayName = \"ButtonGroup\";\n\nexport type ButtonGroupSeparatorProps = React.ComponentPropsWithoutRef<typeof Separator>;\n\nconst ButtonGroupSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n ButtonGroupSeparatorProps\n>(({ className, orientation = \"vertical\", ...props }, ref) => (\n <Separator\n ref={ref}\n decorative\n orientation={orientation}\n data-slot=\"button-group-separator\"\n className={cn(\n orientation === \"vertical\" ? \"mx-0 h-auto self-stretch\" : \"my-0 w-auto self-stretch\",\n className,\n )}\n {...props}\n />\n));\nButtonGroupSeparator.displayName = \"ButtonGroupSeparator\";\n\nexport interface ButtonGroupTextProps extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n}\n\nconst ButtonGroupText = React.forwardRef<HTMLDivElement, ButtonGroupTextProps>(\n ({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-slot=\"button-group-text\"\n className={cn(\n \"flex h-10 items-center whitespace-nowrap rounded-control border border-border bg-background px-3 type-action-label text-muted-foreground\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nButtonGroupText.displayName = \"ButtonGroupText\";\n\nexport { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants };\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "components/base/ButtonGroup.md",
|
|
23
23
|
"type": "registry:file",
|
|
24
24
|
"target": "components/base/ButtonGroup.md",
|
|
25
|
-
"content": "# ButtonGroup
|
|
25
|
+
"content": "# ButtonGroup\nAgrupa botones relacionados en una sola unidad visual. Sirve para acciones hermanas, comandos de toolbar o split actions. No mantiene estado de selección por sí mismo.\n\n## Cuándo usarlo\n\nPara acciones hermanas que operan sobre lo mismo: comandos de toolbar, split actions, pares como “Aprobar / Rechazar”. Una sola unidad visual en vez de botones sueltos. No guarda selección: para elegir opciones usa `RadioGroup` o `Tabs`.\n\n## Composición\n\n`ButtonGroup` agrupa `Button`s; `ButtonGroupSeparator` divide segmentos y `ButtonGroupText` intercala texto auxiliar. Orientación horizontal o vertical.\n\n## Uso básico\n\n```tsx\n<ButtonGroup aria-label=\"Acciones del documento\">\n <Button variant=\"outline\">Editar</Button>\n <Button variant=\"outline\">Duplicar</Button>\n <Button variant=\"outline\">Archivar</Button>\n</ButtonGroup>\n```\n\n## Orientación vertical\n\n```tsx\n<ButtonGroup orientation=\"vertical\" aria-label=\"Acciones rápidas\">\n <Button variant=\"outline\">Asignar</Button>\n <Button variant=\"outline\">Comentar</Button>\n <Button variant=\"outline\">Cerrar</Button>\n</ButtonGroup>\n```\n\n## Separadores\n\nUsa `ButtonGroupSeparator` cuando el grupo mezcla acciones con pesos distintos o cuando las variantes sin borde necesitan separación explícita.\n\n```tsx\n<ButtonGroup aria-label=\"Exportar\">\n <Button variant=\"secondary\">Exportar PDF</Button>\n <ButtonGroupSeparator />\n <Button variant=\"secondary\" size=\"icon\" aria-label=\"Más formatos\">\n <ChevronDown />\n </Button>\n</ButtonGroup>\n```\n\n## Texto auxiliar\n\n`ButtonGroupText` permite agregar una etiqueta breve alineada con los controles.\n\n```tsx\n<ButtonGroup aria-label=\"Filtros de estado\">\n <ButtonGroupText>Estado</ButtonGroupText>\n <Button variant=\"outline\">Activo</Button>\n <Button variant=\"outline\">Pendiente</Button>\n</ButtonGroup>\n```\n\n## Reglas\n\n- Usa `ButtonGroup` solo para acciones relacionadas por contexto inmediato.\n- Agrega `aria-label` o `aria-labelledby` si el grupo no tiene un título visible.\n- Mantén los botones con variantes compatibles; `outline` funciona bien para grupos conectados.\n- En botones solo-icono, cada `Button` debe tener su propio `aria-label`.\n- No uses el grupo para mostrar selección persistente: visualmente son acciones, no opciones activas.\n\n## Cuándo NO usar\n\n- **Elegir una opción activa** → `Tabs`, `RadioGroup` o `Select`.\n- **Activar/desactivar una preferencia** → `Switch` o `Checkbox`.\n- **Barra de navegación** → navegación real con `Link`/`a`, no botones agrupados.\n- **Acciones sin relación fuerte** → botones separados con espaciado normal.\n\n## Import\n\n```tsx\nimport {\n Button,\n ButtonGroup,\n ButtonGroupSeparator,\n ButtonGroupText,\n} from \"@/components/base/ButtonGroup\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `orientation` | `\"horizontal\" \\| \"vertical\"` | `\"horizontal\"` | Dirección del grupo y forma de conectar bordes/radios. |\n| `aria-label` / `aria-labelledby` | `string` | — | Etiqueta accesible del grupo cuando no hay título visible. |\n| ...resto | `HTMLAttributes<HTMLDivElement>` | — | `className`, `id`, `data-*`, etc. |\n\n## Para IA\n\n1. Primero decide si son acciones hermanas o si realmente son opciones seleccionables.\n3. Usa `ButtonGroup` con `aria-label` claro.\n4. Usa `Button` dentro del grupo; no reconstruyas botones con `div` o clases manuales.\n5. Si hay icon-only buttons, agrega `aria-label` individual.\n6. Para selección persistente, cambia a `Tabs`/`RadioGroup` en vez de forzar `ButtonGroup`.\n"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
28
|
}
|
package/assets/r/button.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "button",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Button",
|
|
6
|
-
"description": "Botón de acción. Renderiza un `<button>` nativo; acepta todos sus atributos (`onClick`, `type`, `disabled`, `aria-*`).
|
|
6
|
+
"description": "Botón de acción. Renderiza un `<button>` nativo; acepta todos sus atributos (`onClick`, `type`, `disabled`, `aria-*`).",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"@radix-ui/react-slot",
|
|
9
9
|
"class-variance-authority"
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"path": "components/base/Button.tsx",
|
|
15
15
|
"type": "registry:ui",
|
|
16
16
|
"target": "components/base/Button.tsx",
|
|
17
|
-
"content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-
|
|
17
|
+
"content": "import { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst buttonVariants = cva(\n \"type-action-label inline-flex cursor-pointer select-none items-center justify-center gap-related whitespace-nowrap rounded-button transition-[background-color,border-color,color,box-shadow,transform] duration-[var(--duration-fast)] ease-[var(--ease-standard)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none motion-reduce:transform-none [&_svg]:pointer-events-none [&_svg]:size-(--icon-size-inline) [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground hover:bg-primary-hover active:scale-[0.97] active:bg-primary-active\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90 active:scale-[0.97] active:bg-destructive/80\",\n outline:\n \"border border-border bg-background text-foreground hover:bg-muted active:scale-[0.97] active:bg-muted\",\n secondary:\n \"border border-border bg-background text-secondary-foreground hover:bg-muted active:scale-[0.97] active:bg-muted\",\n ghost:\n \"text-secondary-foreground hover:bg-muted active:scale-[0.97] active:bg-muted\",\n link: \"text-primary underline-offset-4 hover:underline active:text-primary/80\",\n },\n size: {\n default: \"h-[var(--control-height)] px-3.5 py-1.5\",\n sm: \"h-[var(--control-height-sm)] px-3\",\n lg: \"h-[var(--control-height-lg)] px-4\",\n icon: \"size-[var(--control-height)] p-0\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {\n /** Renderiza el hijo como el elemento raíz (patrón Slot de Radix): `<Button asChild><Link …/></Button>`. */\n asChild?: boolean;\n /** Mantiene el botón en su lugar, bloquea nuevas activaciones y muestra progreso. */\n loading?: boolean;\n /** Etiqueta temporal durante loading. Si se omite, conserva el texto original. */\n loadingLabel?: React.ReactNode;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, loading = false, loadingLabel, children, disabled, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n const showLoading = loading && !asChild;\n\n return (\n <Comp\n className={cn(\n buttonVariants({ variant, size, className }),\n \"disabled:data-[loading=true]:cursor-wait disabled:data-[loading=true]:opacity-100\",\n )}\n ref={ref}\n aria-busy={showLoading || undefined}\n data-loading={showLoading ? \"true\" : undefined}\n disabled={asChild ? undefined : disabled || showLoading}\n {...props}\n >\n {showLoading ? (\n <>\n <span\n aria-hidden=\"true\"\n className=\"size-3.5 shrink-0 animate-spin rounded-full border-2 border-current border-r-transparent motion-reduce:animate-none\"\n />\n {loadingLabel ?? children}\n </>\n ) : children}\n </Comp>\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"path": "components/base/Button.md",
|
|
21
21
|
"type": "registry:file",
|
|
22
22
|
"target": "components/base/Button.md",
|
|
23
|
-
"content": "# Button
|
|
23
|
+
"content": "# Button\nBotón de acción. Renderiza un `<button>` nativo; acepta todos sus atributos (`onClick`, `type`, `disabled`, `aria-*`).\n\n## Cuándo usarlo\n\nPara ejecutar una acción: guardar, enviar, confirmar. La variante dice la jerarquía — una sola acción primaria por vista, el resto baja a `secondary`, `outline` o `ghost`. Si el click lleva a otra página, es un enlace con apariencia de botón (`asChild`), no un `<button>`.\n\n## Composición\n\nUna sola pieza (`Button`) con variantes de intención y tamaños; `size=\"icon\"` para una acción solo-icono (siempre con `aria-label`). Con `asChild` viste de botón a un enlace real; `buttonVariants` exporta las clases para casos sin envoltura.\n\n## Geometría de acción\n\n- Todas las variantes y tamaños usan el mismo radio de 6 px. La jerarquía se comunica con color, borde y contenido, no cambiando la silueta.\n- `--radius-button` hereda de `--radius-interactive`, la misma base de campos, navegación y opciones de menú. `--radius-button-lg` apunta al mismo valor.\n- `icon` conserva la misma geometría que el resto de los botones.\n- El glifo usa `--icon-size-inline` (16 px); el área del control se define por `--control-height`, no por el tamaño visual del icono.\n- La presión reduce brevemente el control a `97%`; el cambio confirma el gesto sin producir una animación ceremonial.\n- El índigo sigue perteneciendo a Lexy. La referencia a Notion está en la contención, la jerarquía y la respuesta, no en copiar su color.\n- La altura canónica es compacta: `32px` en `sm`, `36px` en `default` y `40px` en `lg`.\n\n## Loading\n\nCuando la operación comienza, el botón conserva su posición y geometría, bloquea nuevas activaciones y muestra el progreso dentro del propio control. El resultado puede reemplazarlo cuando ya sea suficiente para explicar lo ocurrido.\n\n```tsx\n<Button loading={isSaving} loadingLabel=\"Guardando…\">\n Guardar cambios\n</Button>\n```\n\nNo reemplaces inmediatamente el botón por un spinner separado: eso rompe la continuidad espacial entre intención y respuesta. `loading` no se aplica cuando `asChild` está activo, porque el hijo conserva su propia composición.\n\n## Uso básico\n\n```tsx\n<Button onClick={save}>Guardar cambios</Button>\n```\n\n## Variantes\n\n```tsx\n<Button variant=\"default\">Guardar</Button> {/* acción primaria */}\n<Button variant=\"secondary\">Cancelar</Button> {/* secundaria */}\n<Button variant=\"outline\">Filtrar</Button> {/* secundaria, menos peso */}\n<Button variant=\"ghost\">Ver más</Button> {/* terciaria, sin fondo */}\n<Button variant=\"link\">Olvidé mi contraseña</Button> {/* navegación inline */}\n<Button variant=\"destructive\">Eliminar</Button> {/* borrar o revertir */}\n```\n\n## Tamaños\n\n```tsx\n<Button size=\"sm\">Compacto</Button>\n<Button size=\"default\">Normal</Button>\n<Button size=\"lg\">Grande</Button>\n<Button size=\"icon\" aria-label=\"Buscar\"><Search /></Button> {/* solo icono */}\n```\n\n## asChild — navegación con apariencia de botón\n\n```tsx\n<Button asChild variant=\"outline\">\n <Link to=\"/cuenta\">Ir a mi cuenta</Link>\n</Button>\n```\n\nCon `asChild` el hijo se vuelve el elemento raíz (patrón Slot de Radix): el botón aporta estilos y props, el hijo aporta la semántica (`<a>`, `Link`). Es la vía preferida para enlaces con apariencia de botón; `buttonVariants` queda para casos donde no puedas envolver.\n\n## Reglas\n\n- **Una sola** acción primaria (`default`) por vista o sección: si todo es primario, nada lo es.\n- Usa `destructive` solo para acciones que borran o revierten; combínalo con confirmación (`AppDialog`).\n- `size=\"icon\"` **requiere** `aria-label`: sin texto visible, es la única etiqueta accesible.\n- `variant=\"link\"` es para navegación inline dentro de texto, no para la acción principal de un formulario.\n- No juntes muchas acciones de alto peso; baja a `ghost`/`link` las de menor prioridad.\n\n## Cuándo NO usar\n\n- **Navegación entre páginas/rutas** → usa `asChild` con un `<a>`/`Link` real. Un `<button>` no es un enlace.\n- **Activar/desactivar un estado** → `Switch` (ajustes) o `Checkbox` (selección en formulario).\n- **Elegir entre opciones** → `RadioGroup`, `Select` o `Tabs`, no varios botones.\n\n## Import\n\n```tsx\nimport { Button } from \"@/components/base/Button\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `variant` | `\"default\" \\| \"destructive\" \\| \"outline\" \\| \"secondary\" \\| \"ghost\" \\| \"link\"` | `\"default\"` | Jerarquía/intención visual. |\n| `size` | `\"default\" \\| \"sm\" \\| \"lg\" \\| \"icon\"` | `\"default\"` | Tamaño. `icon` para una acción solo-icono. |\n| `asChild` | `boolean` | `false` | Renderiza el hijo como elemento raíz (Slot). |\n| `loading` | `boolean` | `false` | Bloquea nuevas activaciones y muestra progreso dentro del botón. |\n| `loadingLabel` | `ReactNode` | texto original | Etiqueta que aparece durante `loading`. |\n| `disabled` | `boolean` | `false` | Deshabilita e ignora clics. |\n| ...resto | `ButtonHTMLAttributes` | — | `onClick`, `type`, `form`, `aria-*`, etc. |\n\n`buttonVariants` se exporta: úsalo para dar estilo de botón a un `<a>` real cuando no puedas usar `asChild` — `<a className={buttonVariants({ variant: \"outline\" })}>`.\n\n## Para IA\n\n1. Define la jerarquía: ¿esta acción es la principal de la vista, una alternativa o algo terciario?\n2. Asigna la variante por intención, no por color: primaria=`default`, secundaria=`secondary`/`outline`, terciaria=`ghost`, navegación inline=`link`, irreversible=`destructive`.\n3. Mantén una sola acción primaria visible por sección.\n4. Si es solo-icono, agrega `aria-label` con el verbo de la acción.\n5. Si la acción navega a otra ruta, usa `asChild` con un enlace real en vez de un `<button>`.\n6. Para acciones destructivas, envuélvelas en `AppDialog` con la consecuencia explícita.\n"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
package/assets/r/calendar.json
CHANGED
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
"path": "components/base/Calendar.tsx",
|
|
17
17
|
"type": "registry:ui",
|
|
18
18
|
"target": "components/base/Calendar.tsx",
|
|
19
|
-
"content": "import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { type DayButton, DayPicker, getDefaultClassNames, type Locale } from \"react-day-picker\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button, buttonVariants } from \"./Button\";\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = \"label\",\n buttonVariant = \"ghost\",\n locale,\n formatters,\n components,\n ...props\n}: React.ComponentProps<typeof DayPicker> & {\n buttonVariant?: React.ComponentProps<typeof Button>[\"variant\"];\n}) {\n const defaultClassNames = getDefaultClassNames();\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\n \"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent\",\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className,\n )}\n captionLayout={captionLayout}\n locale={locale}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: \"short\" }),\n ...formatters,\n }}\n classNames={{\n root: cn(\"w-fit\", defaultClassNames.root),\n months: cn(\"relative flex flex-col gap-4 md:flex-row\", defaultClassNames.months),\n month: cn(\"flex w-full flex-col gap-4\", defaultClassNames.month),\n nav: cn(\n \"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1\",\n defaultClassNames.nav,\n ),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_previous,\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_next,\n ),\n month_caption: cn(\n \"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)\",\n defaultClassNames.month_caption,\n ),\n dropdowns: cn(\n \"flex h-(--cell-size) w-full items-center justify-center gap-1.5
|
|
19
|
+
"content": "import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from \"lucide-react\";\nimport * as React from \"react\";\nimport { type DayButton, DayPicker, getDefaultClassNames, type Locale } from \"react-day-picker\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button, buttonVariants } from \"./Button\";\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = \"label\",\n buttonVariant = \"ghost\",\n locale,\n formatters,\n components,\n ...props\n}: React.ComponentProps<typeof DayPicker> & {\n buttonVariant?: React.ComponentProps<typeof Button>[\"variant\"];\n}) {\n const defaultClassNames = getDefaultClassNames();\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\n \"group/calendar bg-background p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent\",\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className,\n )}\n captionLayout={captionLayout}\n locale={locale}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: \"short\" }),\n ...formatters,\n }}\n classNames={{\n root: cn(\"w-fit\", defaultClassNames.root),\n months: cn(\"relative flex flex-col gap-4 md:flex-row\", defaultClassNames.months),\n month: cn(\"flex w-full flex-col gap-4\", defaultClassNames.month),\n nav: cn(\n \"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1\",\n defaultClassNames.nav,\n ),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_previous,\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n \"size-(--cell-size) p-0 select-none aria-disabled:opacity-50\",\n defaultClassNames.button_next,\n ),\n month_caption: cn(\n \"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)\",\n defaultClassNames.month_caption,\n ),\n dropdowns: cn(\n \"flex h-(--cell-size) w-full items-center justify-center gap-1.5 type-supporting font-medium\",\n defaultClassNames.dropdowns,\n ),\n dropdown_root: cn(\n \"cn-calendar-dropdown-root relative rounded-(--cell-radius)\",\n defaultClassNames.dropdown_root,\n ),\n dropdown: cn(\"absolute inset-0 bg-popover opacity-0\", defaultClassNames.dropdown),\n caption_label: cn(\n \"font-medium select-none\",\n captionLayout === \"label\"\n ? \"cn-calendar-caption type-supporting\"\n : \"cn-calendar-caption-label flex items-center gap-1 rounded-(--cell-radius) type-supporting [&>svg]:size-3.5 [&>svg]:text-muted-foreground\",\n defaultClassNames.caption_label,\n ),\n table: \"w-full border-collapse\",\n weekdays: cn(\"flex\", defaultClassNames.weekdays),\n weekday: cn(\n \"flex-1 rounded-(--cell-radius) type-meta text-muted-foreground select-none\",\n defaultClassNames.weekday,\n ),\n week: cn(\"mt-2 flex w-full\", defaultClassNames.week),\n week_number_header: cn(\"w-(--cell-size) select-none\", defaultClassNames.week_number_header),\n week_number: cn(\"type-meta text-muted-foreground select-none\", defaultClassNames.week_number),\n day: cn(\n \"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)\",\n props.showWeekNumber\n ? \"[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)\"\n : \"[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)\",\n defaultClassNames.day,\n ),\n range_start: cn(\n \"relative isolate z-0 rounded-l-(--cell-radius) bg-background after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-background\",\n defaultClassNames.range_start,\n ),\n range_middle: cn(\"rounded-none bg-background\", defaultClassNames.range_middle),\n range_end: cn(\n \"relative isolate z-0 rounded-r-(--cell-radius) bg-background after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-background\",\n defaultClassNames.range_end,\n ),\n today: cn(\n \"rounded-(--cell-radius) bg-background text-foreground data-[selected=true]:rounded-none\",\n defaultClassNames.today,\n ),\n outside: cn(\n \"text-muted-foreground aria-selected:text-muted-foreground\",\n defaultClassNames.outside,\n ),\n disabled: cn(\"text-muted-foreground opacity-50\", defaultClassNames.disabled),\n hidden: cn(\"invisible\", defaultClassNames.hidden),\n ...classNames,\n }}\n components={{\n Root: ({ className, rootRef, ...props }) => {\n return <div data-slot=\"calendar\" ref={rootRef} className={cn(className)} {...props} />;\n },\n Chevron: ({ className, orientation, ...props }) => {\n if (orientation === \"left\") {\n return (\n <ChevronLeftIcon\n className={cn(\"cn-rtl-flip size-(--icon-size-control)\", className)}\n {...props}\n />\n );\n }\n\n if (orientation === \"right\") {\n return (\n <ChevronRightIcon\n className={cn(\"cn-rtl-flip size-(--icon-size-control)\", className)}\n {...props}\n />\n );\n }\n\n return (\n <ChevronDownIcon className={cn(\"size-(--icon-size-inline)\", className)} {...props} />\n );\n },\n DayButton: ({ ...props }) => <CalendarDayButton locale={locale} {...props} />,\n WeekNumber: ({ children, ...props }) => {\n return (\n <td {...props}>\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">\n {children}\n </div>\n </td>\n );\n },\n ...components,\n }}\n {...props}\n />\n );\n}\n\nfunction CalendarDayButton({\n className,\n day,\n modifiers,\n locale,\n ...props\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {\n const defaultClassNames = getDefaultClassNames();\n\n const ref = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (modifiers.focused) ref.current?.focus();\n }, [modifiers.focused]);\n\n return (\n <Button\n ref={ref}\n variant=\"ghost\"\n size=\"icon\"\n data-day={day.date.toLocaleDateString(locale?.code)}\n data-selected-single={\n modifiers.selected &&\n !modifiers.range_start &&\n !modifiers.range_end &&\n !modifiers.range_middle\n }\n data-range-start={modifiers.range_start}\n data-range-end={modifiers.range_end}\n data-range-middle={modifiers.range_middle}\n className={cn(\n // Sin ring por data-focused: ese estado se marca al abrir aunque sea con\n // mouse. El foco de teclado lo cubre el focus-visible:ring del Button.\n \"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal focus-visible:z-10 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70\",\n defaultClassNames.day,\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Calendar, CalendarDayButton };\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "components/base/Calendar.md",
|
|
23
23
|
"type": "registry:file",
|
|
24
24
|
"target": "components/base/Calendar.md",
|
|
25
|
-
"content": "# Calendar
|
|
25
|
+
"content": "# Calendar\nSelector de fechas construido sobre [React DayPicker](https://daypicker.dev),\ncon los estilos y el `Button` de Lexy. Soporta selección simple, múltiple y de\nrango, navegación por mes/año y localización.\n\n## Cuándo usarlo\n\nPara elegir fechas viendo el contexto del mes: agendar, rangos de reserva, fechas próximas. Si el campo vive en un formulario, usa `DatePicker` (que ya lo envuelve en un `Popover`); usa `Calendar` directo cuando el calendario es protagonista de la vista.\n\n## Composición\n\nConstruido sobre React DayPicker: `Calendar` renderiza la grilla del mes con su navegación, y `CalendarDayButton` es el botón de día personalizable. Los modos `single`, `range` y `multiple` cambian qué se selecciona.\n\n## Uso básico (fecha única)\n\n```tsx\nconst [date, setDate] = React.useState<Date | undefined>(new Date());\n\n<Calendar\n mode=\"single\"\n selected={date}\n onSelect={setDate}\n locale={es}\n className=\"rounded-lg border border-border\"\n/>;\n```\n\n## Rango\n\n```tsx\nconst [range, setRange] = React.useState<DateRange | undefined>();\n\n<Calendar\n mode=\"range\"\n selected={range}\n onSelect={setRange}\n numberOfMonths={2}\n locale={es}\n className=\"rounded-lg border border-border\"\n/>;\n```\n\n## Selección múltiple\n\n```tsx\n<Calendar mode=\"multiple\" selected={dates} onSelect={setDates} locale={es} />\n```\n\n## Navegación con dropdowns\n\n`captionLayout=\"dropdown\"` reemplaza el título por selectores de mes y año.\n\n```tsx\n<Calendar\n mode=\"single\"\n captionLayout=\"dropdown\"\n locale={es}\n className=\"rounded-lg border border-border\"\n/>\n```\n\n## Reglas\n\n- Es un componente controlado: maneja `selected`/`onSelect` con estado propio.\n- Pasa `locale` (p. ej. `es`) para meses y días en español.\n- Para elegir una fecha dentro de un formulario, combínalo con `Popover` + un `Button` que muestre la fecha (patrón date-picker).\n- No reconstruyas la grilla a mano; toda la estructura sale de React DayPicker.\n\n## Cuándo NO usar\n\n- **Una hora/duración** sin fecha → un `Input type=\"time\"` o un selector dedicado.\n- **Rango de fechas en texto** con muchos presets → considera un date-picker con presets sobre este Calendar.\n\n## Import\n\n```tsx\nimport { Calendar } from \"@/components/base/Calendar\";\nimport { es } from \"react-day-picker/locale\";\n```\n\n## Props (las más usadas)\n\n| Prop | Tipo | Descripción |\n|---|---|---|\n| `mode` | `\"single\" \\| \"multiple\" \\| \"range\"` | Tipo de selección. |\n| `selected` / `onSelect` | según `mode` | Valor controlado y callback. |\n| `captionLayout` | `\"label\" \\| \"dropdown\" \\| \"dropdown-months\" \\| \"dropdown-years\"` | Encabezado como texto o como selectores. |\n| `numberOfMonths` | `number` | Cantidad de meses visibles. |\n| `locale` | `Locale` | Localización (p. ej. `es` de `react-day-picker/locale`). |\n| `buttonVariant` | variante de `Button` | Variante de los botones de navegación. |\n| `disabled` | `Matcher \\| Matcher[]` | Días deshabilitados. |\n| ...resto | `DayPickerProps` | Todas las props de React DayPicker. |\n\n## Para IA\n\n1. Decide el `mode` (`single`, `multiple` o `range`) según el caso.\n2. Importa `Calendar` local (`@/components/base/Calendar`) y el locale (`es`) desde `react-day-picker/locale`.\n3. Mantén el estado de `selected` y pásalo con su `onSelect`.\n4. Para un date-picker de formulario, envuélvelo en `Popover` con un `Button` disparador.\n5. Usa `captionLayout=\"dropdown\"` cuando el usuario deba saltar muchos meses/años.\n"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
28
|
}
|
package/assets/r/card.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "card",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Card",
|
|
6
|
-
"description": "Contenedor de contenido. Para agrupar información relacionada.
|
|
6
|
+
"description": "Contenedor de contenido. Para agrupar información relacionada.",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"class-variance-authority"
|
|
9
9
|
],
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"path": "components/base/Card.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
15
|
"target": "components/base/Card.tsx",
|
|
16
|
-
"content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst cardVariants = cva(\n \"group/card flex flex-col gap-(--card-
|
|
16
|
+
"content": "import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst cardVariants = cva(\n \"group/card flex flex-col gap-(--card-section-gap) overflow-hidden rounded-lg bg-background py-(--card-inset) text-card-foreground ring-1 ring-border-subtle has-[>img:first-child]:pt-0 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg\",\n {\n variants: {\n size: {\n default:\n \"[--card-inset:var(--spacing-item-gap)] [--card-section-gap:var(--spacing-grouped)]\",\n sm: \"[--card-inset:var(--spacing-grouped)] [--card-section-gap:var(--spacing-related)]\",\n },\n elevation: {\n flat: \"\",\n raised: \"shadow-raised\",\n overlay: \"shadow-overlay\",\n },\n },\n defaultVariants: { size: \"default\", elevation: \"flat\" },\n },\n);\n\nexport interface CardProps\n extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof cardVariants> {}\n\nconst Card = React.forwardRef<HTMLDivElement, CardProps>(({ className, size, elevation, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card\"\n data-size={size ?? \"default\"}\n className={cn(cardVariants({ size, elevation }), className)}\n {...props}\n />\n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-header\"\n className={cn(\n \"@container/card-header grid auto-rows-min items-start gap-coupled rounded-t-lg px-(--card-inset) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-inset)\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-title\"\n className={cn(\n \"type-item-title text-foreground group-data-[size=sm]/card:text-sm\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-description\"\n className={cn(\"type-supporting text-muted-foreground\", className)}\n {...props}\n />\n ),\n);\nCardDescription.displayName = \"CardDescription\";\n\nconst CardAction = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-action\"\n className={cn(\"col-start-2 row-span-2 row-start-1 self-start justify-self-end\", className)}\n {...props}\n />\n ),\n);\nCardAction.displayName = \"CardAction\";\n\nconst CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-content\"\n className={cn(\"px-(--card-inset)\", className)}\n {...props}\n />\n ),\n);\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-slot=\"card-footer\"\n className={cn(\n \"flex items-center rounded-b-lg px-(--card-inset) [.border-t]:pt-(--card-inset)\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n Card,\n CardAction,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n cardVariants,\n};\n"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "components/base/Card.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Card.md",
|
|
22
|
-
"content": "# Card
|
|
22
|
+
"content": "# Card\nContenedor de contenido. Para agrupar información relacionada.\n\n## Cuándo usarlo\n\nPara agrupar información relacionada en una unidad con jerarquía propia: un resumen, una entidad, un bloque de ajustes. Si la vista entera es un listado denso y comparable, una tabla escanea mejor que una grilla de cards.\n\n## Composición\n\nComposición por slots: `CardHeader` (con `CardTitle`, `CardDescription` y `CardAction` opcional), `CardContent` para el cuerpo y `CardFooter` para acciones o metadatos. Usa solo los slots que necesitas — la caja mantiene su ritmo interno.\n\n## Uso básico\n\n```tsx\n<Card />\n```\n\n## Reglas\n\n- Usa Card según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Card } from \"@/components/base/Card\";\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 Card 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
|
}
|
package/assets/r/chart.json
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"path": "components/base/Chart.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
15
|
"target": "components/base/Chart.tsx",
|
|
16
|
-
"content": "import * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\n// Equivalen a los tipos internos de recharts (ValueType/NameType), que en la\n// 2.15.x no se re-exportan desde la entrada pública del paquete.\ntype TooltipValueType = number | string | Array<number | string>;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n string,\n {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<keyof typeof THEMES, string> }\n )\n>;\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error(\"useChart must be used within a <ChartContainer />\");\n }\n\n return context;\n}\n\nfunction ChartContainer({\n id,\n className,\n children,\n config,\n initialDimension = INITIAL_DIMENSION,\n ...props\n}: React.ComponentProps<\"div\"> & {\n config: ChartConfig;\n children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>[\"children\"];\n initialDimension?: {\n width: number;\n height: number;\n };\n}) {\n const uniqueId = React.useId();\n const chartId = `chart-${id ?? uniqueId.replace(/:/g, \"\")}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer initialDimension={initialDimension}>\n {children}\n </RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n const colorConfig = Object.entries(config).filter(([, config]) => config.theme ?? config.color);\n\n if (!colorConfig.length) {\n return null;\n }\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(\n ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n .map(([key, itemConfig]) => {\n const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ?? itemConfig.color;\n return color ? ` --color-${key}: ${color};` : null;\n })\n .join(\"\\n\")}\n}\n`,\n )\n .join(\"\\n\"),\n }}\n />\n );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n active,\n payload,\n className,\n indicator = \"dot\",\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n React.ComponentProps<\"div\"> & {\n hideLabel?: boolean;\n hideIndicator?: boolean;\n indicator?: \"line\" | \"dot\" | \"dashed\";\n nameKey?: string;\n labelKey?: string;\n } & Omit<\n RechartsPrimitive.DefaultTooltipContentProps<TooltipValueType, TooltipNameType>,\n \"accessibilityLayer\"\n >) {\n const { config } = useChart();\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) {\n return null;\n }\n\n const [item] = payload;\n const key = `${labelKey ?? item?.dataKey ?? item?.name ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const value =\n !labelKey && typeof label === \"string\" ? (config[label]?.label ?? label) : itemConfig?.label;\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>\n );\n }\n\n if (!value) {\n return null;\n }\n\n return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n if (!active || !payload?.length) {\n return null;\n }\n\n const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n return (\n <div\n className={cn(\n \"grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-md\",\n className,\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.name ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n return (\n <div\n key={index}\n className={cn(\n \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n indicator === \"dot\" && \"items-center\",\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, item.payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn(\n \"shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)\",\n {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border border-dashed bg-transparent\": indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\",\n },\n )}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor,\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\n \"flex flex-1 justify-between leading-none\",\n nestLabel ? \"items-end\" : \"items-center\",\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-muted-foreground\">\n {itemConfig?.label ?? item.name}\n </span>\n </div>\n {item.value != null && (\n <span className=\"font-mono font-medium text-foreground tabular-nums\">\n {typeof item.value === \"number\"\n ? item.value.toLocaleString()\n : String(item.value)}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n className,\n hideIcon = false,\n payload,\n verticalAlign = \"bottom\",\n nameKey,\n}: React.ComponentProps<\"div\"> & {\n hideIcon?: boolean;\n nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div\n className={cn(\n \"flex items-center justify-center gap-4\",\n verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n className,\n )}\n >\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n return (\n <div\n key={index}\n className={cn(\n \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\",\n )}\n >\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div\n className=\"h-2 w-2 shrink-0 rounded-xs\"\n style={{\n backgroundColor: item.color,\n }}\n />\n )}\n {itemConfig?.label}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined;\n }\n\n const payloadPayload =\n \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n ? payload.payload\n : undefined;\n\n let configLabelKey: string = key;\n\n if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n configLabelKey = payload[key as keyof typeof payload] as string;\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n }\n\n return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n ChartTooltip,\n ChartTooltipContent,\n};\n"
|
|
16
|
+
"content": "import * as React from \"react\";\nimport * as RechartsPrimitive from \"recharts\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nconst THEMES = { light: \"\", dark: \".dark\" } as const;\n\nconst INITIAL_DIMENSION = { width: 320, height: 200 } as const;\n// Equivalen a los tipos internos de recharts (ValueType/NameType), que en la\n// 2.15.x no se re-exportan desde la entrada pública del paquete.\ntype TooltipValueType = number | string | Array<number | string>;\ntype TooltipNameType = number | string;\n\nexport type ChartConfig = Record<\n string,\n {\n label?: React.ReactNode;\n icon?: React.ComponentType;\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: Record<keyof typeof THEMES, string> }\n )\n>;\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nconst ChartContext = React.createContext<ChartContextProps | null>(null);\n\nfunction useChart() {\n const context = React.useContext(ChartContext);\n\n if (!context) {\n throw new Error(\"useChart must be used within a <ChartContainer />\");\n }\n\n return context;\n}\n\nfunction ChartContainer({\n id,\n className,\n children,\n config,\n initialDimension = INITIAL_DIMENSION,\n ...props\n}: React.ComponentProps<\"div\"> & {\n config: ChartConfig;\n children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>[\"children\"];\n initialDimension?: {\n width: number;\n height: number;\n };\n}) {\n const uniqueId = React.useId();\n const chartId = `chart-${id ?? uniqueId.replace(/:/g, \"\")}`;\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-slot=\"chart\"\n data-chart={chartId}\n className={cn(\n \"flex aspect-video justify-center type-meta [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\",\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <RechartsPrimitive.ResponsiveContainer initialDimension={initialDimension}>\n {children}\n </RechartsPrimitive.ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n );\n}\n\nconst ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {\n const colorConfig = Object.entries(config).filter(([, config]) => config.theme ?? config.color);\n\n if (!colorConfig.length) {\n return null;\n }\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: Object.entries(THEMES)\n .map(\n ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n .map(([key, itemConfig]) => {\n const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ?? itemConfig.color;\n return color ? ` --color-${key}: ${color};` : null;\n })\n .join(\"\\n\")}\n}\n`,\n )\n .join(\"\\n\"),\n }}\n />\n );\n};\n\nconst ChartTooltip = RechartsPrimitive.Tooltip;\n\nfunction ChartTooltipContent({\n active,\n payload,\n className,\n indicator = \"dot\",\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey,\n}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &\n React.ComponentProps<\"div\"> & {\n hideLabel?: boolean;\n hideIndicator?: boolean;\n indicator?: \"line\" | \"dot\" | \"dashed\";\n nameKey?: string;\n labelKey?: string;\n } & Omit<\n RechartsPrimitive.DefaultTooltipContentProps<TooltipValueType, TooltipNameType>,\n \"accessibilityLayer\"\n >) {\n const { config } = useChart();\n\n const tooltipLabel = React.useMemo(() => {\n if (hideLabel || !payload?.length) {\n return null;\n }\n\n const [item] = payload;\n const key = `${labelKey ?? item?.dataKey ?? item?.name ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const value =\n !labelKey && typeof label === \"string\" ? (config[label]?.label ?? label) : itemConfig?.label;\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>\n );\n }\n\n if (!value) {\n return null;\n }\n\n return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>;\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);\n\n if (!active || !payload?.length) {\n return null;\n }\n\n const nestLabel = payload.length === 1 && indicator !== \"dot\";\n\n return (\n <div\n className={cn(\n \"grid min-w-32 items-start gap-1.5 rounded-md border border-border/50 bg-background px-2.5 py-1.5 type-meta shadow-overlay\",\n className,\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.name ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n const indicatorColor = color ?? item.payload?.fill ?? item.color;\n\n return (\n <div\n key={index}\n className={cn(\n \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n indicator === \"dot\" && \"items-center\",\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, item.payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn(\n \"shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)\",\n {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border border-dashed bg-transparent\": indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\",\n },\n )}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor,\n } as React.CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\n \"flex flex-1 justify-between leading-none\",\n nestLabel ? \"items-end\" : \"items-center\",\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-muted-foreground\">\n {itemConfig?.label ?? item.name}\n </span>\n </div>\n {item.value != null && (\n <span className=\"font-mono font-medium text-foreground tabular-nums\">\n {typeof item.value === \"number\"\n ? item.value.toLocaleString()\n : String(item.value)}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n\nconst ChartLegend = RechartsPrimitive.Legend;\n\nfunction ChartLegendContent({\n className,\n hideIcon = false,\n payload,\n verticalAlign = \"bottom\",\n nameKey,\n}: React.ComponentProps<\"div\"> & {\n hideIcon?: boolean;\n nameKey?: string;\n} & RechartsPrimitive.DefaultLegendContentProps) {\n const { config } = useChart();\n\n if (!payload?.length) {\n return null;\n }\n\n return (\n <div\n className={cn(\n \"flex items-center justify-center gap-4\",\n verticalAlign === \"top\" ? \"pb-3\" : \"pt-3\",\n className,\n )}\n >\n {payload\n .filter((item) => item.type !== \"none\")\n .map((item, index) => {\n const key = `${nameKey ?? item.dataKey ?? \"value\"}`;\n const itemConfig = getPayloadConfigFromPayload(config, item, key);\n\n return (\n <div\n key={index}\n className={cn(\n \"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground\",\n )}\n >\n {itemConfig?.icon && !hideIcon ? (\n <itemConfig.icon />\n ) : (\n <div\n className=\"h-2 w-2 shrink-0 rounded-xs\"\n style={{\n backgroundColor: item.color,\n }}\n />\n )}\n {itemConfig?.label}\n </div>\n );\n })}\n </div>\n );\n}\n\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined;\n }\n\n const payloadPayload =\n \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n ? payload.payload\n : undefined;\n\n let configLabelKey: string = key;\n\n if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n configLabelKey = payload[key as keyof typeof payload] as string;\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;\n }\n\n return configLabelKey in config ? config[configLabelKey] : config[key];\n}\n\nexport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartStyle,\n ChartTooltip,\n ChartTooltipContent,\n};\n"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "components/base/Chart.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Chart.md",
|
|
22
|
-
"content": "# Chart
|
|
22
|
+
"content": "# Chart\nGráficos construidos sobre [Recharts](https://recharts.org). No es un componente\nde gráfico cerrado: son primitivos (`ChartContainer`, tooltip y leyenda con\nestilos de Lexy) que envuelven los charts de Recharts y los conectan a los\ntokens del tema vía un `ChartConfig`.\n\n## Cuándo usarlo\n\nPara mostrar tendencia, comparación o distribución — lo que un número solo no cuenta. Elige el tipo por la pregunta que responde: línea para evolución, barras para comparar, área para volumen. Si el dato importante es un valor puntual, un stat con texto grande comunica más rápido que un gráfico.\n\n## Composición\n\nNo es un componente único sino un kit sobre Recharts: `ChartContainer` fija tema y proporciones desde un `config`; `ChartTooltip`/`ChartTooltipContent` y `ChartLegend`/`ChartLegendContent` visten tooltip y leyenda con los tokens del sistema. Las series toman color de la paleta `chart-1…5`.\n\n## Config\n\nEl `ChartConfig` mapea cada serie a su etiqueta y color. El color se expone como\nvariable CSS `--color-<clave>`, que luego usas en el `fill`/`stroke` del chart.\n\n```tsx\nconst chartConfig = {\n desktop: { label: \"Escritorio\", color: \"var(--color-chart-1)\" },\n mobile: { label: \"Móvil\", color: \"var(--color-chart-2)\" },\n} satisfies ChartConfig;\n```\n\n## Uso básico\n\n```tsx\n<ChartContainer config={chartConfig} className=\"min-h-[200px] w-full\">\n <BarChart accessibilityLayer data={chartData}>\n <CartesianGrid vertical={false} />\n <XAxis\n dataKey=\"month\"\n tickLine={false}\n axisLine={false}\n tickMargin={10}\n tickFormatter={(value) => value.slice(0, 3)}\n />\n <ChartTooltip content={<ChartTooltipContent />} />\n <ChartLegend content={<ChartLegendContent />} />\n <Bar dataKey=\"desktop\" fill=\"var(--color-desktop)\" radius={4} />\n <Bar dataKey=\"mobile\" fill=\"var(--color-mobile)\" radius={4} />\n </BarChart>\n</ChartContainer>\n```\n\n## Colores\n\nUsa los tokens `--color-chart-1` … `--color-chart-5` del tema para series\ncategóricas. Forman una paleta propia, separada del índigo de acción y de los\ncolores semánticos: una serie no debe parecer accionable, urgente o exitosa.\nPara un color puntual, define un token con intención en el proyecto y pásalo en\n`color`; no hardcodees un hex dentro del gráfico.\n\n## Componentes\n\n| Componente | Rol |\n|---|---|\n| `ChartContainer` | Provee el contexto de config, dimensiona (responsive) y aplica los estilos base sobre los nodos de Recharts. |\n| `ChartTooltip` | Alias de `Tooltip` de Recharts. |\n| `ChartTooltipContent` | Contenido del tooltip con estilos Lexy (`indicator: dot \\| line \\| dashed`). |\n| `ChartLegend` | Alias de `Legend` de Recharts. |\n| `ChartLegendContent` | Contenido de la leyenda con estilos Lexy. |\n\n## Reglas\n\n- Siempre envuelve el chart de Recharts en `ChartContainer` y pásale un `config`.\n- Da al contenedor un **ancho definido** (p. ej. dentro de una `Card` o con `w-full` en un padre con ancho); el `ResponsiveContainer` necesita medir.\n- Referencia los colores por `var(--color-<clave>)`, no hardcodees hex en el chart.\n- Mantén las claves del `config` iguales a los `dataKey` de las series.\n\n## Cuándo NO usar\n\n- **Una sola métrica destacada** → un número grande / `StatusDot`, no un chart.\n- **Tabla de datos exactos** → `Table`.\n- **Progreso de una tarea** → `Progress`.\n\n## Import\n\n```tsx\nimport {\n ChartContainer,\n ChartTooltip,\n ChartTooltipContent,\n ChartLegend,\n ChartLegendContent,\n type ChartConfig,\n} from \"@/components/base/Chart\";\nimport { Bar, BarChart, CartesianGrid, XAxis } from \"recharts\";\n```\n\n## Para IA\n\n1. Define primero el `ChartConfig` (una entrada por serie, con `label` y `color`).\n2. Importa los primitivos de chart local (`@/components/base/Chart`) y los del gráfico (`Bar`, `BarChart`, `XAxis`, …) desde `recharts`.\n3. Envuelve todo en `ChartContainer` con ancho definido (normalmente dentro de una `Card`).\n4. Usa `var(--color-<clave>)` para `fill`/`stroke`; los colores salen del `config`.\n5. Agrega `ChartTooltip`/`ChartLegend` con sus `*Content` para tooltips y leyenda con estilo Lexy.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
package/assets/r/checkbox.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "checkbox",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Checkbox",
|
|
6
|
-
"description": "Casilla de verificación (Radix). Para un sí/no independiente o para elegir **varias** opciones de una lista. No trae label: emparéjalo con `Label`.
|
|
6
|
+
"description": "Casilla de verificación (Radix). Para un sí/no independiente o para elegir **varias** opciones de una lista. No trae label: emparéjalo con `Label`.",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"@radix-ui/react-checkbox"
|
|
9
9
|
],
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Checkbox.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Checkbox.md",
|
|
22
|
-
"content": "# Checkbox
|
|
22
|
+
"content": "# Checkbox\nCasilla de verificación (Radix). Para un sí/no independiente o para elegir **varias** opciones de una lista. No trae label: emparéjalo con `Label`.\n\n## Cuándo usarlo\n\nPara un sí/no independiente (acepto los términos) o para elegir varias opciones de una lista visible. Cada checkbox es autónomo: marcar uno no afecta a los demás. Si la elección es excluyente, es un `RadioGroup`; si es un ajuste de efecto inmediato, un `Switch`.\n\n## Composición\n\nUna sola pieza (`Checkbox`, Radix) sin label propio: emparéjala siempre con `Label` apuntando a su `id`. En listas, un checkbox por fila con su label clickeable.\n\n## Uso básico\n\n```tsx\n<div className=\"flex items-center gap-2\">\n <Checkbox id=\"terminos\" />\n <Label htmlFor=\"terminos\">Acepto los términos</Label>\n</div>\n```\n\n## Selección múltiple\n\n```tsx\n<fieldset className=\"grid gap-2\">\n <legend className=\"text-sm font-medium\">Notificarme sobre</legend>\n {opciones.map((o) => (\n <div key={o.id} className=\"flex items-center gap-2\">\n <Checkbox\n id={o.id}\n checked={seleccion.includes(o.id)}\n onCheckedChange={(c) => toggle(o.id, c)}\n />\n <Label htmlFor={o.id}>{o.label}</Label>\n </div>\n ))}\n</fieldset>\n```\n\n## Reglas\n\n- Empareja con `Label` por `htmlFor`/`id`; al pulsar el label debe activarse la casilla.\n- Para **grupos**, envuelve en `fieldset` + `legend` que describa el grupo.\n- Usa `\"indeterminate\"` para un \"seleccionar todo\" parcialmente marcado.\n- No uses checkbox para activar algo inmediato a nivel de sistema → eso es `Switch`.\n\n## Cuándo NO usar\n\n- **Encender/apagar un ajuste con efecto inmediato** → `Switch`.\n- **Elegir una sola opción entre varias** → `RadioGroup`.\n- **Más de ~7 opciones o con búsqueda** → `Select`/`Combobox` con multiselección.\n\n## Import\n\n```tsx\nimport { Checkbox } from \"@/components/base/Checkbox\";\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `checked` | `boolean \\| \"indeterminate\"` | — | Estado controlado. |\n| `defaultChecked` | `boolean` | — | Estado inicial no controlado. |\n| `onCheckedChange` | `(checked) => void` | — | Cambio de estado. |\n| `disabled` | `boolean` | `false` | Deshabilita. |\n\n## Para IA\n\n1. ¿Es selección múltiple o un consentimiento aislado? Si es \"una entre varias\", usa `RadioGroup`; si es ajuste de sistema, `Switch`.\n2. Crea el par `Checkbox` + `Label` con `id`/`htmlFor`.\n3. Agrupa opciones relacionadas en `fieldset`/`legend`.\n4. Controla con `checked` + `onCheckedChange` cuando dependa del estado.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
package/assets/r/combobox.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "combobox",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Combobox",
|
|
6
|
-
"description": "Campo de entrada con lista desplegable filtrable. Para búsqueda + selección.
|
|
6
|
+
"description": "Campo de entrada con lista desplegable filtrable. Para búsqueda + selección.",
|
|
7
7
|
"dependencies": [],
|
|
8
8
|
"registryDependencies": [
|
|
9
9
|
"@lexy/button",
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
"path": "components/base/Combobox.tsx",
|
|
16
16
|
"type": "registry:ui",
|
|
17
17
|
"target": "components/base/Combobox.tsx",
|
|
18
|
-
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./Command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./Popover\";\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n}\n\n/**\n * Wrapper ergonómico sobre la receta compuesta Popover + Command. Si necesitas\n * variar la estructura (grupos, ítems con iconos, multi-selección, creación de\n * opciones), compón las piezas directamente — ver \"Receta compuesta\" en\n * Combobox.md.\n */\nexport interface ComboboxProps extends Omit<\n React.ComponentPropsWithoutRef<typeof Button>,\n \"value\" | \"defaultValue\" | \"onChange\"\n> {\n options: ComboboxOption[];\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyMessage?: string;\n}\n\nconst Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(function Combobox(\n {\n options,\n value,\n defaultValue,\n onValueChange,\n placeholder = \"Seleccionar…\",\n searchPlaceholder = \"Buscar…\",\n emptyMessage = \"Sin resultados\",\n className,\n ...props\n },\n ref,\n) {\n const [open, setOpen] = React.useState(false);\n const [internal, setInternal] = React.useState(defaultValue ?? \"\");\n const current = value ?? internal;\n const selected = options.find((option) => option.value === current);\n\n const handleSelect = (next: string) => {\n const resolved = next === current ? \"\" : next;\n setInternal(resolved);\n onValueChange?.(resolved);\n setOpen(false);\n };\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n ref={ref}\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n className={cn(\"w-full justify-between font-normal\", className)}\n {...props}\n >\n {selected ? selected.label : <span className=\"text-muted-foreground\">{placeholder}</span>}\n <svg\n className=\"ml-2
|
|
18
|
+
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./Command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./Popover\";\n\nexport interface ComboboxOption {\n value: string;\n label: string;\n}\n\n/**\n * Wrapper ergonómico sobre la receta compuesta Popover + Command. Si necesitas\n * variar la estructura (grupos, ítems con iconos, multi-selección, creación de\n * opciones), compón las piezas directamente — ver \"Receta compuesta\" en\n * Combobox.md.\n */\nexport interface ComboboxProps extends Omit<\n React.ComponentPropsWithoutRef<typeof Button>,\n \"value\" | \"defaultValue\" | \"onChange\"\n> {\n options: ComboboxOption[];\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n placeholder?: string;\n searchPlaceholder?: string;\n emptyMessage?: string;\n}\n\nconst Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(function Combobox(\n {\n options,\n value,\n defaultValue,\n onValueChange,\n placeholder = \"Seleccionar…\",\n searchPlaceholder = \"Buscar…\",\n emptyMessage = \"Sin resultados\",\n className,\n ...props\n },\n ref,\n) {\n const [open, setOpen] = React.useState(false);\n const [internal, setInternal] = React.useState(defaultValue ?? \"\");\n const current = value ?? internal;\n const selected = options.find((option) => option.value === current);\n\n const handleSelect = (next: string) => {\n const resolved = next === current ? \"\" : next;\n setInternal(resolved);\n onValueChange?.(resolved);\n setOpen(false);\n };\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n ref={ref}\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n className={cn(\"w-full justify-between font-normal\", className)}\n {...props}\n >\n {selected ? selected.label : <span className=\"text-muted-foreground\">{placeholder}</span>}\n <svg\n className=\"ml-2 size-(--icon-size-inline) shrink-0 opacity-50\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"m7 15 5 5 5-5M7 9l5-5 5 5\" />\n </svg>\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-[var(--radix-popover-trigger-width)] p-0\">\n <Command>\n <CommandInput placeholder={searchPlaceholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => (\n <CommandItem\n key={option.value}\n value={option.label}\n onSelect={() => handleSelect(option.value)}\n >\n <svg\n className={cn(\n \"mr-2 size-(--icon-size-inline)\",\n current === option.value ? \"opacity-100\" : \"opacity-0\",\n )}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n {option.label}\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n});\nCombobox.displayName = \"Combobox\";\n\nexport { Combobox };\n"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"path": "components/base/Combobox.md",
|
|
22
22
|
"type": "registry:file",
|
|
23
23
|
"target": "components/base/Combobox.md",
|
|
24
|
-
"content": "# Combobox
|
|
24
|
+
"content": "# Combobox\nCampo de entrada con lista desplegable filtrable. Para búsqueda + selección.\n\n## Cuándo usarlo\n\nPara buscar y elegir en listas largas: clientes, comunas, causas. El filtrado mientras se escribe convierte doscientas opciones en tres. Con pocas opciones, un `Select` simple pide menos esfuerzo.\n\n## Composición\n\nReceta empaquetada: un `Button` de campo que abre un `Popover` con un `Command` dentro (input de búsqueda + lista filtrable).\n\n## Uso básico\n\n```tsx\n<Combobox />\n```\n\n## Receta compuesta\n\n`Combobox` es un wrapper delgado sobre `Popover` + `Command`. Sus props extra (`disabled`, `id`, `aria-*`…) pasan al botón trigger y el `ref` apunta a ese botón. Si necesitas variar la estructura (grupos, ítems con icono o descripción, multi-selección, crear opciones al vuelo), compón las piezas directamente:\n\n```tsx\nimport { Button } from \"@/components/base/Button\";\nimport { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from \"@/components/base/Command\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/base/Popover\";\n\n<Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button variant=\"outline\" role=\"combobox\" aria-expanded={open}>…</Button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[var(--radix-popover-trigger-width)] p-0\" align=\"start\">\n <Command>\n <CommandInput placeholder=\"Buscar…\" />\n <CommandList>\n <CommandEmpty>Sin resultados</CommandEmpty>\n <CommandGroup heading=\"Sugeridos\">{/* CommandItem con iconos, etc. */}</CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n</Popover>\n```\n\n## Reglas\n\n- Usa Combobox según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Combobox } from \"@/components/base/Combobox\";\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 Combobox 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"
|
|
25
25
|
}
|
|
26
26
|
]
|
|
27
27
|
}
|
package/assets/r/command.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "command",
|
|
4
4
|
"type": "registry:ui",
|
|
5
5
|
"title": "Command",
|
|
6
|
-
"description": "Paleta de comandos. Para búsqueda y acciones rápidas.
|
|
6
|
+
"description": "Paleta de comandos. Para búsqueda y acciones rápidas.",
|
|
7
7
|
"dependencies": [
|
|
8
8
|
"cmdk"
|
|
9
9
|
],
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
"path": "components/base/Command.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
15
|
"target": "components/base/Command.tsx",
|
|
16
|
-
"content": "import { Command as CommandPrimitive } from \"cmdk\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Command = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive\n ref={ref}\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded bg-popover text-popover-foreground\",\n className,\n )}\n {...props}\n />\n));\nCommand.displayName = CommandPrimitive.displayName;\n\nconst CommandInput = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Input>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>\n>(({ className, ...props }, ref) => (\n <div className=\"flex items-center border-b border-border px-3\" cmdk-input-wrapper=\"\">\n <svg\n className=\"mr-2
|
|
16
|
+
"content": "import { Command as CommandPrimitive } from \"cmdk\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Command = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive\n ref={ref}\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n className,\n )}\n {...props}\n />\n));\nCommand.displayName = CommandPrimitive.displayName;\n\nconst CommandInput = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Input>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>\n>(({ className, ...props }, ref) => (\n <div className=\"flex items-center border-b border-border px-3\" cmdk-input-wrapper=\"\">\n <svg\n className=\"mr-2 size-(--icon-size-inline) shrink-0 opacity-50\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n <CommandPrimitive.Input\n ref={ref}\n className={cn(\n \"flex h-10 w-full rounded bg-transparent py-3 type-supporting text-foreground outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}\n />\n </div>\n));\nCommandInput.displayName = CommandPrimitive.Input.displayName;\n\nconst CommandList = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.List\n ref={ref}\n className={cn(\"max-h-75 overflow-y-auto overflow-x-hidden\", className)}\n {...props}\n />\n));\nCommandList.displayName = CommandPrimitive.List.displayName;\n\nconst CommandEmpty = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Empty>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>\n>((props, ref) => (\n <CommandPrimitive.Empty\n ref={ref}\n className=\"py-6 text-center type-supporting text-muted-foreground\"\n {...props}\n />\n));\nCommandEmpty.displayName = CommandPrimitive.Empty.displayName;\n\nconst CommandGroup = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Group>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.Group\n ref={ref}\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n {...props}\n />\n));\nCommandGroup.displayName = CommandPrimitive.Group.displayName;\n\nconst CommandSeparator = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 h-px bg-border\", className)}\n {...props}\n />\n));\nCommandSeparator.displayName = CommandPrimitive.Separator.displayName;\n\nconst CommandItem = React.forwardRef<\n React.ElementRef<typeof CommandPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <CommandPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-menu-item px-2 py-1.5 type-supporting text-foreground outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-primary/15 data-[selected=true]:text-primary data-[disabled=true]:opacity-50\",\n className,\n )}\n {...props}\n />\n));\nCommandItem.displayName = CommandPrimitive.Item.displayName;\n\nconst CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn(\"ml-auto type-meta tracking-widest text-muted-foreground\", className)}\n {...props}\n />\n);\nCommandShortcut.displayName = \"CommandShortcut\";\n\nexport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut,\n};\n"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "components/base/Command.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Command.md",
|
|
22
|
-
"content": "# Command
|
|
22
|
+
"content": "# Command\nPaleta de comandos. Para búsqueda y acciones rápidas.\n\n## Cuándo usarlo\n\nPara búsqueda y comandos rápidos tipo paleta (⌘K): saltar a un caso, ejecutar una acción, navegar sin mouse. Para usuarios frecuentes es el atajo que más se agradece.\n\n## Composición\n\n`Command` contiene `CommandInput`, `CommandList` con `CommandGroup`s de `CommandItem`s, `CommandEmpty` para el sin-resultados, más `CommandSeparator` y `CommandShortcut`. Dentro de un `Dialog` se vuelve paleta flotante.\n\n## Uso básico\n\n```tsx\n<Command />\n```\n\n## Reglas\n\n- Usa Command según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Command } from \"@/components/base/Command\";\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 Command 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
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"path": "views/EnvioConfirmado.tsx",
|
|
16
16
|
"type": "registry:file",
|
|
17
17
|
"target": "views/EnvioConfirmado.tsx",
|
|
18
|
-
"content": "import { CheckCircle2 } from \"lucide-react\";\n\nimport { Button } from \"@/components/base/Button\";\n\n// Receta cliente — confirmación / éxito: celebra breve, di qué pasa ahora y\n// dónde seguir. Ver confirmacion.md.\n\nexport function EnvioConfirmado() {\n return (\n <main className=\"mx-auto flex min-h-[60vh] w-full max-w-md flex-col items-center justify-center px-margin-mobile text-center\">\n <span className=\"mb-4 flex size-12 items-center justify-center rounded-full bg-success/10\">\n <CheckCircle2 className=\"size-6 text-success\" aria-hidden />\n </span>\n <h1 className=\"
|
|
18
|
+
"content": "import { CheckCircle2 } from \"lucide-react\";\n\nimport { Button } from \"@/components/base/Button\";\n\n// Receta cliente — confirmación / éxito: celebra breve, di qué pasa ahora y\n// dónde seguir. Ver confirmacion.md.\n\nexport function EnvioConfirmado() {\n return (\n <main className=\"mx-auto flex min-h-[60vh] w-full max-w-md flex-col items-center justify-center px-margin-mobile text-center\">\n <span className=\"mb-4 flex size-12 items-center justify-center rounded-full bg-success/10\">\n <CheckCircle2 className=\"size-6 text-success\" aria-hidden />\n </span>\n <h1 className=\"type-page-title text-foreground\">¡Enviamos tus documentos!</h1>\n <p className=\"mt-2 type-body text-muted-foreground\">\n El equipo legal los está revisando. Podrás seguir su estado en la pestaña «Enviados» y te\n avisaremos si falta algo.\n </p>\n <Button className=\"mt-6 w-full sm:w-auto\">Volver al inicio</Button>\n </main>\n );\n}\n"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"path": "views/confirmacion.md",
|