create-lexy 0.6.0 → 0.6.2
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/assets/r/accordion.json +1 -1
- package/assets/r/alert-dialog.json +1 -1
- package/assets/r/app-accordion.json +1 -1
- package/assets/r/app-dialog.json +1 -1
- package/assets/r/app-header-bar.json +1 -1
- package/assets/r/app-sidebar.json +1 -1
- package/assets/r/avatar.json +1 -1
- package/assets/r/badge.json +1 -1
- package/assets/r/brand-background.json +135 -0
- package/assets/r/breadcrumb.json +1 -1
- package/assets/r/button-group.json +1 -1
- package/assets/r/button.json +1 -1
- package/assets/r/calendar.json +1 -1
- package/assets/r/card.json +1 -1
- package/assets/r/chart.json +1 -1
- package/assets/r/checkbox.json +1 -1
- package/assets/r/combobox.json +1 -1
- package/assets/r/command.json +1 -1
- package/assets/r/confirmacion.json +2 -2
- package/assets/r/counter-badge.json +1 -1
- package/assets/r/crm-app-layout.json +2 -2
- package/assets/r/crm-desk.json +2 -2
- package/assets/r/crm-detalle-caso.json +2 -2
- package/assets/r/date-picker.json +1 -1
- package/assets/r/dialog.json +1 -1
- package/assets/r/dropdown-menu.json +1 -1
- package/assets/r/empty.json +1 -1
- package/assets/r/feature-card.json +1 -1
- package/assets/r/form.json +1 -1
- package/assets/r/header-bar.json +1 -1
- package/assets/r/input.json +1 -1
- package/assets/r/intake-wizard.json +2 -2
- package/assets/r/label.json +1 -1
- package/assets/r/login.json +2 -2
- package/assets/r/logo.json +1 -1
- package/assets/r/menubar.json +1 -1
- package/assets/r/navigation-menu.json +1 -1
- package/assets/r/pagination.json +1 -1
- package/assets/r/popover.json +1 -1
- package/assets/r/profile-card.json +1 -1
- package/assets/r/progress.json +1 -1
- package/assets/r/radio-group.json +1 -1
- package/assets/r/registry.json +116 -0
- package/assets/r/scroll-area.json +1 -1
- package/assets/r/searchbox.json +1 -1
- package/assets/r/select.json +1 -1
- package/assets/r/separator.json +1 -1
- package/assets/r/sheet.json +1 -1
- package/assets/r/sidebar.json +2 -2
- package/assets/r/skeleton.json +1 -1
- package/assets/r/slider.json +1 -1
- package/assets/r/snippet.json +1 -1
- package/assets/r/spinner.json +1 -1
- package/assets/r/status-dot.json +1 -1
- package/assets/r/switch.json +1 -1
- package/assets/r/table.json +1 -1
- package/assets/r/tabs.json +1 -1
- package/assets/r/tag.json +1 -1
- package/assets/r/textarea.json +1 -1
- package/assets/r/toaster.json +1 -1
- package/assets/r/tooltip.json +1 -1
- package/assets/r/tree.json +1 -1
- package/assets/registry-version +1 -1
- package/assets/theme/lexy-theme.css +15 -4
- package/dist/index.js +1959 -1723
- package/package.json +6 -5
package/assets/r/label.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"path": "components/base/Label.md",
|
|
21
21
|
"type": "registry:file",
|
|
22
22
|
"target": "components/base/Label.md",
|
|
23
|
-
"content": "# Label — guía de uso\n\nEtiqueta para campos de formulario. Incluye indicador de requerido. El componente vive en tu proyecto: instálalo con `create-lexy add label` y edítalo con libertad.\n\n##
|
|
23
|
+
"content": "# Label — guía de uso\n\nEtiqueta para campos de formulario. Incluye indicador de requerido. El componente vive en tu proyecto: instálalo con `create-lexy add label` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara nombrar todo campo de formulario, siempre visible — el placeholder desaparece al escribir y la memoria de la persona no es parte del sistema. Incluye indicador de requerido cuando aplica.\n\n## Composición\n\nUna sola pieza (`Label`) asociada al control por `htmlFor`/`id`. En formularios validados la pone `FormLabel`, que hereda el estado de error.\n\n## Uso básico\n\n```tsx\n<Label />\n```\n\n## Reglas\n\n- Usa Label según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Label } from \"@/components/base/Label\";\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 Label 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"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
package/assets/r/login.json
CHANGED
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
"path": "views/Login.tsx",
|
|
17
17
|
"type": "registry:file",
|
|
18
18
|
"target": "views/Login.tsx",
|
|
19
|
-
"content": "import { Button } from \"@/components/base/Button\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/base/Card\";\nimport { Input } from \"@/components/base/Input\";\nimport { Label } from \"@/components/base/Label\";\n\n// Receta cliente — acceso (login): una sola tarea, foco único, ayuda\n// contextual breve. Para CRM, ver la variante en login.md. Ver login.md.\n\nexport function Login() {\n return (\n <main className=\"flex min-h-screen items-center justify-center bg-background px-
|
|
19
|
+
"content": "import { Button } from \"@/components/base/Button\";\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from \"@/components/base/Card\";\nimport { Input } from \"@/components/base/Input\";\nimport { Label } from \"@/components/base/Label\";\n\n// Receta cliente — acceso (login): una sola tarea, foco único, ayuda\n// contextual breve. Para CRM, ver la variante en login.md. Ver login.md.\n\nexport function Login() {\n return (\n <main className=\"flex min-h-screen items-center justify-center bg-background px-margin-mobile\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>Ingresa a Lexy</CardTitle>\n <CardDescription>Te acompañamos con tu caso desde aquí.</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">Correo</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"tu@correo.cl\" />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"pass\">Contraseña</Label>\n <Input id=\"pass\" type=\"password\" placeholder=\"Ingresa tu contraseña\" />\n </div>\n <Button type=\"submit\" className=\"w-full\">\n Entrar\n </Button>\n </form>\n </CardContent>\n </Card>\n </main>\n );\n}\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "views/login.md",
|
|
23
23
|
"type": "registry:file",
|
|
24
24
|
"target": "views/login.md",
|
|
25
|
-
"content": "# login — block\n\nPantalla de acceso: una sola tarea, foco único, ayuda contextual breve. Instálalo con `create-lexy add login` — trae `Card`, `Input`, `Label` y `Button`. La vista queda en tu proyecto (`views/Login.tsx`): edítala con libertad.\n\n## Por mundo\n\n- **Cliente** (como viene): card centrada, copy de acompañamiento (*\"Te acompañamos con tu caso desde aquí.\"*), un solo CTA ancho.\n- **CRM**: misma estructura con copy funcional (*\"Ingresa al panel de Lexy\"*, descripción corta o sin ella) y, si la marca lo pide, `Logo` arriba de la card:\n\n```tsx\nimport { Logo } from \"@/components/base/Logo\";\n\n<main className='flex min-h-screen flex-col items-center justify-center gap-6 bg-background px-4'>\n <Logo layout='horizontal' />\n <Card className='w-full max-w-sm'>…</Card>\n</main>\n```\n\n## Adaptación\n\n- Para validación y errores (credenciales inválidas), instala `form` y usa `FormField` + `FormMessage`; el error de credenciales va junto al campo de contraseña, en tono neutro.\n- \"¿Olvidaste tu contraseña?\" como `Button variant='link'` bajo el CTA, si el flujo existe.\n\n## Evita\n\nHero a dos columnas con marketing, social login decorativo que no funciona, y placeholders como único label.\n"
|
|
25
|
+
"content": "# login — block\n\nPantalla de acceso: una sola tarea, foco único, ayuda contextual breve. Instálalo con `create-lexy add login` — trae `Card`, `Input`, `Label` y `Button`. La vista queda en tu proyecto (`views/Login.tsx`): edítala con libertad.\n\n## Por mundo\n\n- **Cliente** (como viene): card centrada, copy de acompañamiento (*\"Te acompañamos con tu caso desde aquí.\"*), un solo CTA ancho.\n- **CRM**: misma estructura con copy funcional (*\"Ingresa al panel de Lexy\"*, descripción corta o sin ella) y, si la marca lo pide, `Logo` arriba de la card:\n\n```tsx\nimport { Logo } from \"@/components/base/Logo\";\n\n<main className='flex min-h-screen flex-col items-center justify-center gap-6 bg-background px-4'>\n <Logo layout='horizontal' />\n <Card className='w-full max-w-sm'>…</Card>\n</main>\n```\n\n## Adaptación\n\n- Para validación y errores (credenciales inválidas), instala `form` y usa `FormField` + `FormMessage`; el error de credenciales va junto al campo de contraseña, en tono neutro.\n- \"¿Olvidaste tu contraseña?\" como `Button variant='link'` bajo el CTA, si el flujo existe.\n\n## Evita\n\nHero a dos columnas con marketing, social login decorativo que no funciona, y placeholders como único label.\n\n## Degradación móvil\n\nYa es móvil primero: columna `max-w-sm` centrada con margen lateral de token (`px-margin-mobile`) y botón full-width. No requiere más adaptación.\n"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
28
|
}
|
package/assets/r/logo.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"path": "components/base/Logo.md",
|
|
18
18
|
"type": "registry:file",
|
|
19
19
|
"target": "components/base/Logo.md",
|
|
20
|
-
"content": "# Logo — guía de uso\n\nLogo de Lexy. Para marca del producto. El componente vive en tu proyecto: instálalo con `create-lexy add logo` y edítalo con libertad.\n\n##
|
|
20
|
+
"content": "# Logo — guía de uso\n\nLogo de Lexy. Para marca del producto. El componente vive en tu proyecto: instálalo con `create-lexy add logo` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara la marca del producto en headers, sidebars, logins y documentos. Siempre desde el componente — nunca un PNG suelto ni un texto que lo imite.\n\n## Composición\n\nUna sola pieza (`Logo`) con layouts horizontal e isotipo; el tamaño se ajusta con clases de altura.\n\n## Uso básico\n\n```tsx\n<Logo />\n```\n\n## Reglas\n\n- Usa Logo según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Logo } from \"@/components/base/Logo\";\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 Logo desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"path": "components/base/assets/logo/lexy-dark-horizontal-slogan.svg",
|
package/assets/r/menubar.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Menubar.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Menubar.md",
|
|
22
|
-
"content": "# Menubar — guía de uso\n\nBarra de menús horizontal. Para navegación de aplicación. El componente vive en tu proyecto: instálalo con `create-lexy add menubar` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# Menubar — guía de uso\n\nBarra de menús horizontal. Para navegación de aplicación. El componente vive en tu proyecto: instálalo con `create-lexy add menubar` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara barras de menús estilo aplicación de escritorio: Archivo, Edición, Ver — comandos agrupados con atajos. En productos web Lexy es raro: para navegación de sitio usa `NavigationMenu`; para acciones contextuales, `DropdownMenu`.\n\n## Composición\n\n`Menubar` contiene `MenubarMenu`s, cada uno con `MenubarTrigger` y `MenubarContent` (items, checkboxes, radios, submenús y `MenubarShortcut`).\n\n## Uso básico\n\n```tsx\n<Menubar />\n```\n\n## Reglas\n\n- Usa Menubar según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Menubar } from \"@/components/base/Menubar\";\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 Menubar 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
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"path": "components/base/NavigationMenu.md",
|
|
21
21
|
"type": "registry:file",
|
|
22
22
|
"target": "components/base/NavigationMenu.md",
|
|
23
|
-
"content": "# NavigationMenu — guía de uso\n\nMenú de navegación con dropdowns. Para sitios web. El componente vive en tu proyecto: instálalo con `create-lexy add navigation-menu` y edítalo con libertad.\n\n##
|
|
23
|
+
"content": "# NavigationMenu — guía de uso\n\nMenú de navegación con dropdowns. Para sitios web. El componente vive en tu proyecto: instálalo con `create-lexy add navigation-menu` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara navegación de sitio con paneles desplegables ricos: secciones con descripciones y listas de enlaces agrupadas. Es de mundo web/marketing; dentro de una app interna la sidebar ordena mejor.\n\n## Composición\n\n`NavigationMenu` > `NavigationMenuList` > `NavigationMenuItem`s, cada uno con `NavigationMenuTrigger` + `NavigationMenuContent` (o `NavigationMenuLink` directo para enlaces simples).\n\n## Uso básico\n\n```tsx\n<NavigationMenu />\n```\n\n## Reglas\n\n- Usa NavigationMenu según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { NavigationMenu } from \"@/components/base/NavigationMenu\";\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 NavigationMenu 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"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
package/assets/r/pagination.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"path": "components/base/Pagination.md",
|
|
23
23
|
"type": "registry:file",
|
|
24
24
|
"target": "components/base/Pagination.md",
|
|
25
|
-
"content": "# Pagination — guía de uso\n\nNavegación de paginación para listados, tablas y resultados de búsqueda.\nCompuesta por sub-componentes que permiten construir distintas configuraciones. El componente vive en tu proyecto: instálalo con `create-lexy add pagination` y edítalo con libertad.\n\n##
|
|
25
|
+
"content": "# Pagination — guía de uso\n\nNavegación de paginación para listados, tablas y resultados de búsqueda.\nCompuesta por sub-componentes que permiten construir distintas configuraciones. El componente vive en tu proyecto: instálalo con `create-lexy add pagination` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara dividir listados largos en páginas con posición visible: tablas de desk, resultados de búsqueda. Si el contexto es exploración continua (un feed), considera “cargar más” en su lugar.\n\n## Composición\n\n`Pagination` > `PaginationContent` > `PaginationItem`s con `PaginationPrevious`, `PaginationLink` (los números), `PaginationEllipsis` y `PaginationNext`.\n\n## Uso básico\n\n```tsx\n<Pagination>\n <PaginationContent>\n <PaginationItem>\n <PaginationPrevious href=\"/items?page=1\" />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"/items?page=1\" isActive>1</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"/items?page=2\">2</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"/items?page=3\">3</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationEllipsis />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"/items?page=10\">10</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationNext href=\"/items?page=2\" />\n </PaginationItem>\n </PaginationContent>\n</Pagination>\n```\n\n## Con muchas páginas\n\n```tsx\n<Pagination>\n <PaginationContent>\n <PaginationItem>\n <PaginationPrevious href=\"#\" />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">1</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationEllipsis />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">4</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\" isActive>5</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">6</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationEllipsis />\n </PaginationItem>\n <PaginationItem>\n <PaginationLink href=\"#\">20</PaginationLink>\n </PaginationItem>\n <PaginationItem>\n <PaginationNext href=\"#\" />\n </PaginationItem>\n </PaginationContent>\n</Pagination>\n```\n\n## Reglas\n\n- Muestra máximo 5–7 números de página visibles.\n- Usa `PaginationEllipsis` para ocultar páginas intermedias.\n- Marca la página actual con `isActive`.\n- Deshabilita u oculta \"Anterior\" en la primera página.\n- Deshabilita u oculta \"Siguiente\" en la última página.\n- No uses paginación para menos de 2 páginas.\n- No mezcles paginación con scroll infinito en la misma vista.\n\n## Import\n\n```tsx\nimport {\n Pagination,\n PaginationContent,\n PaginationItem,\n PaginationLink,\n PaginationPrevious,\n PaginationNext,\n PaginationEllipsis,\n} from \"@/shared/components/base/Pagination\"; // o @/components/base/Pagination\n```\n\n## Props\n\n### Sub-componentes\n\n| Componente | Props | Descripción |\n|---|---|---|\n| `Pagination` | `className?` | Contenedor `nav` con `aria-label=\"pagination\"`. |\n| `PaginationContent` | `className?` | Lista `ul` que contiene los items. |\n| `PaginationItem` | — | Item `li` de la lista. |\n| `PaginationLink` | `isActive?: boolean`, `size?`, `className?` | Link de página. `isActive` aplica estilo outline. |\n| `PaginationPrevious` | `text?: string`, `className?` | Botón \"Anterior\" con icono. |\n| `PaginationNext` | `text?: string`, `className?` | Botón \"Siguiente\" con icono. |\n| `PaginationEllipsis` | `className?` | Indicador de páginas omitidas (\"...\"). |\n\n## Para IA\n\n1. Identifica si el listado tiene más de una página de resultados.\n2. Si es paginable → usa `Pagination`.\n3. Calcula el rango de páginas visibles (máximo 5–7 items).\n4. Usa `PaginationEllipsis` para ocultar páginas intermedias.\n5. Marca la página actual con `isActive` en `PaginationLink`.\n6. Deshabilita/oculta los botones de navegación en los extremos.\n7. Considera scroll infinito si la navegación por página no aporta valor.\n"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
28
|
}
|
package/assets/r/popover.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Popover.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Popover.md",
|
|
22
|
-
"content": "# Popover — guía de uso\n\nPanel flotante que se ancla a un elemento. Para contenido interactivo. El componente vive en tu proyecto: instálalo con `create-lexy add popover` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# Popover — guía de uso\n\nPanel flotante que se ancla a un elemento. Para contenido interactivo. El componente vive en tu proyecto: instálalo con `create-lexy add popover` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara contenido interactivo anclado a un elemento: un filtro rápido, un mini-formulario, un picker. Más que un tooltip (que solo informa), menos que un modal (que interrumpe).\n\n## Composición\n\n`PopoverTrigger` abre `PopoverContent` anclado al disparador (o a `PopoverAnchor` si el ancla es otro elemento); dentro va contenido libre.\n\n## Uso básico\n\n```tsx\n<Popover />\n```\n\n## Reglas\n\n- Usa Popover según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Popover } from \"@/components/base/Popover\";\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 Popover 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
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"path": "components/base/ProfileCard.md",
|
|
21
21
|
"type": "registry:file",
|
|
22
22
|
"target": "components/base/ProfileCard.md",
|
|
23
|
-
"content": "# ProfileCard — guía de uso\n\nTarjeta de perfil. Para mostrar información de usuario. El componente vive en tu proyecto: instálalo con `create-lexy add profile-card` y edítalo con libertad.\n\n##
|
|
23
|
+
"content": "# ProfileCard — guía de uso\n\nTarjeta de perfil. Para mostrar información de usuario. El componente vive en tu proyecto: instálalo con `create-lexy add profile-card` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara presentar a una persona con su contexto: nombre, rol, contacto — en paneles de equipo, asignaciones o directorios.\n\n## Composición\n\nUna sola pieza (`ProfileCard`) que compone `Avatar` con nombre, rol y metadatos.\n\n## Uso básico\n\n```tsx\n<ProfileCard />\n```\n\n## Reglas\n\n- Usa ProfileCard según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { ProfileCard } from \"@/components/base/ProfileCard\";\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 ProfileCard 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"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
package/assets/r/progress.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Progress.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Progress.md",
|
|
22
|
-
"content": "# Progress — guía de uso\n\nBarra de progreso. Para mostrar avance. El componente vive en tu proyecto: instálalo con `create-lexy add progress` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# Progress — guía de uso\n\nBarra de progreso. Para mostrar avance. El componente vive en tu proyecto: instálalo con `create-lexy add progress` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara avance determinado — cuando sabes cuánto falta: pasos de un wizard, una carga con porcentaje. Si no puedes estimar el total, usa `Spinner` o `Skeleton`; una barra que salta a ciegas genera desconfianza.\n\n## Composición\n\nUna sola pieza (`Progress`) con `value` de 0 a 100. En wizards va arriba, acompañada del paso actual en texto.\n\n## Uso básico\n\n```tsx\n<Progress />\n```\n\n## Reglas\n\n- Usa Progress según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Progress } from \"@/components/base/Progress\";\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 Progress desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/RadioGroup.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/RadioGroup.md",
|
|
22
|
-
"content": "# RadioGroup — guía de uso\n\nSelección **única** entre opciones visibles (Radix). Usa `RadioGroup` como contenedor y `RadioGroupItem` por opción. El componente vive en tu proyecto: instálalo con `create-lexy add radio-group` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# RadioGroup — guía de uso\n\nSelección **única** entre opciones visibles (Radix). Usa `RadioGroup` como contenedor y `RadioGroupItem` por opción. El componente vive en tu proyecto: instálalo con `create-lexy add radio-group` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara elegir una sola opción entre pocas visibles (2–5): tipo de documento, modalidad. Todas las opciones a la vista permiten comparar de un vistazo. Con más opciones, ahorra pantalla con `Select` o `Combobox`.\n\n## Composición\n\n`RadioGroup` (estado y navegación por teclado) contiene un `RadioGroupItem` por opción, cada uno con su `Label`.\n\n## Uso básico\n\n```tsx\n<fieldset>\n <legend className=\"mb-2 text-sm font-medium\">Plan</legend>\n <RadioGroup defaultValue=\"mensual\">\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"mensual\" id=\"r-mensual\" />\n <Label htmlFor=\"r-mensual\">Mensual</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"anual\" id=\"r-anual\" />\n <Label htmlFor=\"r-anual\">Anual</Label>\n </div>\n </RadioGroup>\n</fieldset>\n```\n\n## Reglas\n\n- Cada `RadioGroupItem` necesita `value` único y un `Label` con `id`/`htmlFor`.\n- Envuelve el grupo en `fieldset` + `legend` que nombre la decisión.\n- Conceptualmente siempre hay una sola opción activa.\n- Mantén el orden estable; no reordenes opciones entre renders.\n\n## Cuándo NO usar\n\n- **Selección múltiple** → `Checkbox`.\n- **Muchas opciones (>5–7) o con búsqueda** → `Select` / `Combobox` (ahorra espacio vertical).\n- **Dos estados opuestos on/off de un ajuste** → `Switch`.\n\n## Import\n\n```tsx\nimport { RadioGroup, RadioGroupItem } from \"@/components/base/RadioGroup\";\n```\n\n## Props\n\n### `RadioGroup`\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `value` / `defaultValue` | `string` | — | Opción seleccionada (controlado / inicial). |\n| `onValueChange` | `(value) => void` | — | Cambio de selección. |\n| `disabled` | `boolean` | `false` | Deshabilita todo el grupo. |\n\n### `RadioGroupItem`\n| Prop | Tipo | Descripción |\n|---|---|---|\n| `value` | `string` | **Requerido.** Valor de la opción. |\n| `id` | `string` | Para asociar el `Label`. |\n| `disabled` | `boolean` | Deshabilita esa opción. |\n\n## Para IA\n\n1. Confirma que es \"una sola entre varias\" y que ver todas a la vez ayuda a decidir.\n2. Si son demasiadas o necesitan búsqueda, prefiere `Select`/`Combobox`.\n3. Empareja cada opción con un `Label`; envuelve en `fieldset`/`legend`.\n4. Controla con `value` + `onValueChange` cuando dependa del estado del formulario.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
package/assets/r/registry.json
CHANGED
|
@@ -190,6 +190,122 @@
|
|
|
190
190
|
}
|
|
191
191
|
]
|
|
192
192
|
},
|
|
193
|
+
{
|
|
194
|
+
"name": "brand-background",
|
|
195
|
+
"type": "registry:ui",
|
|
196
|
+
"title": "BrandBackground",
|
|
197
|
+
"description": "Fondos oficiales del manual de marca Lexy (sección 05. Fondos) como componente:",
|
|
198
|
+
"dependencies": [],
|
|
199
|
+
"registryDependencies": [],
|
|
200
|
+
"files": [
|
|
201
|
+
{
|
|
202
|
+
"path": "components/base/BrandBackground.tsx",
|
|
203
|
+
"type": "registry:ui",
|
|
204
|
+
"target": "components/base/BrandBackground.tsx"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"path": "components/base/BrandBackground.md",
|
|
208
|
+
"type": "registry:file",
|
|
209
|
+
"target": "components/base/BrandBackground.md"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-1.webp",
|
|
213
|
+
"type": "registry:file",
|
|
214
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-1.webp",
|
|
215
|
+
"encoding": "base64"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-10.webp",
|
|
219
|
+
"type": "registry:file",
|
|
220
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-10.webp",
|
|
221
|
+
"encoding": "base64"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-11.webp",
|
|
225
|
+
"type": "registry:file",
|
|
226
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-11.webp",
|
|
227
|
+
"encoding": "base64"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-12.webp",
|
|
231
|
+
"type": "registry:file",
|
|
232
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-12.webp",
|
|
233
|
+
"encoding": "base64"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-13.webp",
|
|
237
|
+
"type": "registry:file",
|
|
238
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-13.webp",
|
|
239
|
+
"encoding": "base64"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-14.webp",
|
|
243
|
+
"type": "registry:file",
|
|
244
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-14.webp",
|
|
245
|
+
"encoding": "base64"
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-15.webp",
|
|
249
|
+
"type": "registry:file",
|
|
250
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-15.webp",
|
|
251
|
+
"encoding": "base64"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-16.webp",
|
|
255
|
+
"type": "registry:file",
|
|
256
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-16.webp",
|
|
257
|
+
"encoding": "base64"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-2.webp",
|
|
261
|
+
"type": "registry:file",
|
|
262
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-2.webp",
|
|
263
|
+
"encoding": "base64"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-3.webp",
|
|
267
|
+
"type": "registry:file",
|
|
268
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-3.webp",
|
|
269
|
+
"encoding": "base64"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-4.webp",
|
|
273
|
+
"type": "registry:file",
|
|
274
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-4.webp",
|
|
275
|
+
"encoding": "base64"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-5.webp",
|
|
279
|
+
"type": "registry:file",
|
|
280
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-5.webp",
|
|
281
|
+
"encoding": "base64"
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-6.webp",
|
|
285
|
+
"type": "registry:file",
|
|
286
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-6.webp",
|
|
287
|
+
"encoding": "base64"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-7.webp",
|
|
291
|
+
"type": "registry:file",
|
|
292
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-7.webp",
|
|
293
|
+
"encoding": "base64"
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-8.webp",
|
|
297
|
+
"type": "registry:file",
|
|
298
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-8.webp",
|
|
299
|
+
"encoding": "base64"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"path": "components/base/assets/backgrounds/lexy-fondo-9.webp",
|
|
303
|
+
"type": "registry:file",
|
|
304
|
+
"target": "components/base/assets/backgrounds/lexy-fondo-9.webp",
|
|
305
|
+
"encoding": "base64"
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
},
|
|
193
309
|
{
|
|
194
310
|
"name": "breadcrumb",
|
|
195
311
|
"type": "registry:ui",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/ScrollArea.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/ScrollArea.md",
|
|
22
|
-
"content": "# ScrollArea — guía de uso\n\nÁrea de scroll estilizada con barras de scroll personalizadas.\nBasado en `@radix-ui/react-scroll-area`. El componente vive en tu proyecto: instálalo con `create-lexy add scroll-area` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# ScrollArea — guía de uso\n\nÁrea de scroll estilizada con barras de scroll personalizadas.\nBasado en `@radix-ui/react-scroll-area`. El componente vive en tu proyecto: instálalo con `create-lexy add scroll-area` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara paneles con alto fijo cuyo contenido crece: listas laterales, paneles de detalle, menús largos. Estiliza la barra y evita saltos de layout. Para el scroll de la página entera no hace falta — el navegador ya sabe.\n\n## Composición\n\n`ScrollArea` envuelve el contenido y `ScrollBar` (vertical u horizontal) dibuja la barra estilizada.\n\n## Uso básico\n\n```tsx\n<ScrollArea className=\"h-72 w-72 rounded-md border border-border p-4\">\n <div className=\"space-y-4\">\n {items.map((item) => (\n <div key={item.id}>\n <div className=\"font-medium\">{item.title}</div>\n <div className=\"text-muted-foreground\">{item.description}</div>\n </div>\n ))}\n </div>\n</ScrollArea>\n```\n\n## Scroll horizontal\n\n```tsx\n<ScrollArea className=\"w-96 rounded-md border border-border p-4\">\n <div className=\"flex w-max gap-4\">\n {items.map((item) => (\n <div key={item.id} className=\"w-32 shrink-0\">\n {item.title}\n </div>\n ))}\n </div>\n</ScrollArea>\n```\n\n## Reglas\n\n- Define una altura o ancho fijo para que el scroll funcione.\n- El contenido debe ser más grande que el contenedor para que aparezca scroll.\n- No envuelvas toda la página en `ScrollArea` → usa scroll nativo del body.\n- No anides múltiples `ScrollArea`.\n\n## Import\n\n```tsx\nimport { ScrollArea } from \"@/shared/components/base/ScrollArea\"; // o @/components/base/ScrollArea\n```\n\n## Props\n\n| Componente | Props | Descripción |\n|---|---|---|\n| `ScrollArea` | `className?`, `children`, `...` | Contenedor con scroll. |\n| `ScrollBar` | `className?`, `orientation?` | Barra de scroll. Renderiza automáticamente. |\n\n## Para IA\n\n1. Identifica si el contenido excede el espacio disponible.\n2. Si necesitas scroll estilizado → usa `ScrollArea`.\n3. Define `height` y/o `width` en el contenedor.\n4. No uses `ScrollArea` para scroll de página completa.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
package/assets/r/searchbox.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"path": "components/base/Searchbox.md",
|
|
18
18
|
"type": "registry:file",
|
|
19
19
|
"target": "components/base/Searchbox.md",
|
|
20
|
-
"content": "# Searchbox — guía de uso\n\nCampo de búsqueda con icono. Para búsquedas. El componente vive en tu proyecto: instálalo con `create-lexy add searchbox` y edítalo con libertad.\n\n##
|
|
20
|
+
"content": "# Searchbox — guía de uso\n\nCampo de búsqueda con icono. Para búsquedas. El componente vive en tu proyecto: instálalo con `create-lexy add searchbox` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara filtrar o buscar dentro de un conjunto: la tabla del desk, un listado. El icono de lupa fija la expectativa. Si en realidad se elige entre opciones (no se filtra contenido), eso es un `Combobox`.\n\n## Composición\n\nUna sola pieza (`Searchbox`): un `Input` con el icono de búsqueda integrado. En toolbars convive con las acciones a la derecha.\n\n## Uso básico\n\n```tsx\n<Searchbox />\n```\n\n## Reglas\n\n- Usa Searchbox según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Searchbox } from \"@/components/base/Searchbox\";\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 Searchbox desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
23
|
}
|
package/assets/r/select.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Select.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Select.md",
|
|
22
|
-
"content": "# Select — guía de uso\n\nLista desplegable de selección **única** (Radix). Ahorra espacio cuando hay muchas opciones y no necesitas verlas todas a la vez. El componente vive en tu proyecto: instálalo con `create-lexy add select` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# Select — guía de uso\n\nLista desplegable de selección **única** (Radix). Ahorra espacio cuando hay muchas opciones y no necesitas verlas todas a la vez. El componente vive en tu proyecto: instálalo con `create-lexy add select` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara selección única en listas medianas donde no necesitas ver todas las opciones a la vez: región, estado civil. Ahorra pantalla a cambio de un click. Con pocas opciones muestra `RadioGroup`; con muchas o con búsqueda, `Combobox`.\n\n## Composición\n\n`Select` con `SelectTrigger` (el campo, con `SelectValue`) y `SelectContent` con un `SelectItem` por opción; `SelectGroup` y `SelectLabel` organizan listas largas.\n\n## Uso básico\n\n```tsx\n<div className=\"grid gap-1.5\">\n <Label htmlFor=\"region\">Región</Label>\n <Select onValueChange={setRegion}>\n <SelectTrigger id=\"region\">\n <SelectValue placeholder=\"Selecciona una región\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"rm\">Metropolitana</SelectItem>\n <SelectItem value=\"v\">Valparaíso</SelectItem>\n <SelectItem value=\"viii\">Biobío</SelectItem>\n </SelectContent>\n </Select>\n</div>\n```\n\n## Con grupos\n\n```tsx\n<SelectContent>\n <SelectGroup>\n <SelectLabel>Zona norte</SelectLabel>\n <SelectItem value=\"i\">Tarapacá</SelectItem>\n </SelectGroup>\n <SelectSeparator />\n <SelectGroup>\n <SelectLabel>Zona centro</SelectLabel>\n <SelectItem value=\"rm\">Metropolitana</SelectItem>\n </SelectGroup>\n</SelectContent>\n```\n\n## Reglas\n\n- Asocia el `SelectTrigger` con un `Label` (`id`/`htmlFor`).\n- Usa `SelectValue` con `placeholder` claro; no dejes el trigger vacío.\n- Agrupa con `SelectGroup`/`SelectLabel` si hay categorías.\n- Cada `SelectItem` necesita `value` único.\n\n## Cuándo NO usar\n\n- **Pocas opciones (2–4) que conviene ver siempre** → `RadioGroup`.\n- **Necesitas escribir para filtrar** → `Combobox`.\n- **Selección múltiple** → `Checkbox` o `Combobox` con multiselección (no este Select).\n- **Acciones (no selección de valor)** → `DropdownMenu`.\n\n## Import\n\n```tsx\nimport {\n Select, SelectTrigger, SelectValue, SelectContent, SelectItem,\n} from \"@/components/base/Select\";\n```\n\n## Exports\n\n`Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`, `SelectScrollDownButton`.\n\n## Props (esenciales)\n\n| Parte | Prop | Descripción |\n|---|---|---|\n| `Select` | `value` / `defaultValue` | Selección controlada / inicial. |\n| `Select` | `onValueChange` | Cambio de selección. |\n| `SelectTrigger` | `id` | Para asociar el `Label`. |\n| `SelectValue` | `placeholder` | Texto cuando no hay selección. |\n| `SelectItem` | `value` | **Requerido.** Valor de la opción. |\n\n## Para IA\n\n1. Confirma que es selección única de un valor de formulario (no acciones, no multiselección).\n2. Si son ≤4 opciones siempre visibles, considera `RadioGroup`; si requieren búsqueda, `Combobox`.\n3. Arma `Select > SelectTrigger (con SelectValue) > SelectContent > SelectItem`.\n4. Asocia el trigger con un `Label`.\n5. Controla con `value`/`onValueChange` cuando alimente un formulario.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
package/assets/r/separator.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Separator.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Separator.md",
|
|
22
|
-
"content": "# Separator — guía de uso\n\nSeparador visual. Para dividir secciones. El componente vive en tu proyecto: instálalo con `create-lexy add separator` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# Separator — guía de uso\n\nSeparador visual. Para dividir secciones. El componente vive en tu proyecto: instálalo con `create-lexy add separator` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara marcar un límite entre zonas cuando el aire no basta: menús, toolbars, metadatos en línea. Primero prueba con espacio — la línea es el recurso de segunda instancia.\n\n## Composición\n\nUna sola pieza (`Separator`) con orientación horizontal o vertical. Decorativa por defecto: los lectores de pantalla la ignoran.\n\n## Uso básico\n\n```tsx\n<Separator />\n```\n\n## Reglas\n\n- Usa Separator según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Separator } from \"@/components/base/Separator\";\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 Separator 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/sheet.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"path": "components/base/Sheet.md",
|
|
21
21
|
"type": "registry:file",
|
|
22
22
|
"target": "components/base/Sheet.md",
|
|
23
|
-
"content": "# Sheet — guía de uso\n\nPanel lateral deslizante. Alternativa a `Dialog` para contenido secundario que\nrequiere más espacio o contexto persistente. El componente vive en tu proyecto: instálalo con `create-lexy add sheet` y edítalo con libertad.\n\n##
|
|
23
|
+
"content": "# Sheet — guía de uso\n\nPanel lateral deslizante. Alternativa a `Dialog` para contenido secundario que\nrequiere más espacio o contexto persistente. El componente vive en tu proyecto: instálalo con `create-lexy add sheet` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara tareas secundarias que no ameritan salir de la página: un detalle rápido, un formulario de creación, filtros avanzados. Se desliza desde un borde y mantiene visible el contexto de fondo.\n\n## Composición\n\nComposición Radix: `SheetTrigger` abre `SheetContent` (lado configurable), con `SheetHeader` (`SheetTitle`, `SheetDescription`), cuerpo libre y `SheetFooter`; `SheetClose` cierra.\n\n## Uso básico\n\n```tsx\n<Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\">Abrir panel</Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>Configuración</SheetTitle>\n <SheetDescription>Ajusta las preferencias de tu cuenta.</SheetDescription>\n </SheetHeader>\n <div className=\"py-4\">{/* formulario */}</div>\n <SheetFooter>\n <SheetClose asChild>\n <Button variant=\"ghost\">Cerrar</Button>\n </SheetClose>\n </SheetFooter>\n </SheetContent>\n</Sheet>\n```\n\n## Reglas\n\n- Usa `Sheet` para contenido secundario que necesita más espacio que un `Dialog`.\n- No uses `Sheet` para confirmaciones simples; usa `Dialog` o `AppDialog`.\n- El panel tiene ancho máximo `sm:max-w-sm` en lados (responsive).\n- Incluye `SheetHeader` + `SheetTitle` para accesibilidad.\n- Usa `side=\"bottom\"` para layouts mobile.\n\n## Import\n\n```tsx\nimport {\n Sheet,\n SheetTrigger,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetFooter,\n SheetClose,\n} from \"@/components/base/Sheet\";\n```\n\n## Props\n\n### `SheetContent`\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `side` | `\"top\" \\| \"bottom\" \\| \"left\" \\| \"right\"` | `\"right\"` | Desde qué borde aparece el panel. |\n| `className` | `string?` | — | Clase adicional. |\n\n### `Sheet` (Root)\n\n| Prop | Tipo | Descripción |\n|---|---|---|\n| `open` | `boolean?` | Estado controlado. |\n| `onOpenChange` | `(boolean) => void` | Callback de cambio. |\n\n## Para IA\n\n1. Identifica si el contenido es secundario y necesita más espacio que un `Dialog`.\n2. Si es secundario/formulario largo → usa `Sheet`.\n3. Elige el `side` apropiado (`right` para desktop, `bottom` para mobile).\n4. Incluye `SheetHeader` + `SheetTitle` para accesibilidad.\n5. Usa `SheetFooter` para botones de acción al final.\n6. Solo usa `Dialog` para confirmaciones simples o alertas.\n"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
26
26
|
}
|
package/assets/r/sidebar.json
CHANGED
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"path": "components/base/Sidebar.tsx",
|
|
23
23
|
"type": "registry:ui",
|
|
24
24
|
"target": "components/base/Sidebar.tsx",
|
|
25
|
-
"content": "\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport { Input } from \"./Input\";\nimport { Separator } from \"./Separator\";\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from \"./Sheet\";\nimport { Skeleton } from \"./Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"./Tooltip\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"20rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"20rem\";\nconst SIDEBAR_WIDTH_ICON = \"4rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\nconst MOBILE_BREAKPOINT = 768;\n\nfunction useIsMobile() {\n // Estado inicial calculado de inmediato (SPA: window siempre existe);\n // el efecto solo suscribe a los cambios.\n const [isMobile, setIsMobile] = React.useState(() => window.innerWidth < MOBILE_BREAKPOINT);\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n mql.addEventListener(\"change\", onChange);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return isMobile;\n}\n\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],\n );\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={0}>\n <div\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\n \"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n );\n },\n);\nSidebarProvider.displayName = \"SidebarProvider\";\n\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n }\n>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n <div\n className={cn(\n \"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n );\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n <SheetContent\n data-sidebar=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex h-full w-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n );\n }\n\n return (\n <div\n ref={ref}\n className=\"group peer hidden text-sidebar-foreground md:block\"\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n className={cn(\n \"relative h-svh w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n )}\n />\n <div\n className={cn(\n \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex\",\n side === \"left\"\n ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n // Adjust the padding for floating and inset variants.\n variant === \"floating\" || variant === \"inset\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l border-sidebar-border\",\n className,\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n className=\"flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow\"\n >\n {children}\n </div>\n </div>\n </div>\n );\n },\n);\nSidebar.displayName = \"Sidebar\";\n\nconst SidebarTrigger = React.forwardRef<\n React.ElementRef<typeof Button>,\n React.ComponentProps<typeof Button>\n>(({ className, onClick, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <Button\n ref={ref}\n data-sidebar=\"trigger\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\"h-8 w-8 rounded-md hover:bg-secondary\", className)}\n onClick={(event) => {\n onClick?.(event);\n toggleSidebar();\n }}\n {...props}\n >\n <PanelLeft />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </Button>\n );\n});\nSidebarTrigger.displayName = \"SidebarTrigger\";\n\nconst SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <button\n ref={ref}\n data-sidebar=\"rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(\n \"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex\",\n \"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize\",\n \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n \"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarRail.displayName = \"SidebarRail\";\n\nconst SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<\"main\">>(\n ({ className, ...props }, ref) => {\n return (\n <main\n ref={ref}\n className={cn(\n \"relative flex w-full flex-1 flex-col bg-background\",\n \"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-lg md:peer-data-[variant=inset]:shadow\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarInset.displayName = \"SidebarInset\";\n\nconst SidebarInput = React.forwardRef<\n React.ElementRef<typeof Input>,\n React.ComponentProps<typeof Input>\n>(({ className, ...props }, ref) => {\n return (\n <Input\n ref={ref}\n data-sidebar=\"input\"\n className={cn(\n \"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarInput.displayName = \"SidebarInput\";\n\nconst SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarHeader.displayName = \"SidebarHeader\";\n\nconst SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"footer\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarFooter.displayName = \"SidebarFooter\";\n\nconst SidebarSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n React.ComponentProps<typeof Separator>\n>(({ className, ...props }, ref) => {\n return (\n <Separator\n ref={ref}\n data-sidebar=\"separator\"\n className={cn(\"mx-2 w-auto bg-sidebar-border\", className)}\n {...props}\n />\n );\n});\nSidebarSeparator.displayName = \"SidebarSeparator\";\n\nconst SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"content\"\n className={cn(\n \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/50\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarContent.displayName = \"SidebarContent\";\n\nconst SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarGroup.displayName = \"SidebarGroup\";\n\nconst SidebarGroupLabel = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-label\"\n className={cn(\n \"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\";\n\nconst SidebarGroupAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-action\"\n className={cn(\n \"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupAction.displayName = \"SidebarGroupAction\";\n\nconst SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"group-content\"\n className={cn(\"w-full text-sm\", className)}\n {...props}\n />\n ),\n);\nSidebarGroupContent.displayName = \"SidebarGroupContent\";\n\nconst SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n {...props}\n />\n ),\n);\nSidebarMenu.displayName = \"SidebarMenu\";\n\nconst SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n ),\n);\nSidebarMenuItem.displayName = \"SidebarMenuItem\";\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[color,background-color,width,height,padding] duration-200 hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-secondary data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"hover:bg-secondary hover:text-sidebar-foreground\",\n outline:\n \"bg-background shadow-[0_0_0_1px_var(--color-sidebar-border)] hover:bg-secondary hover:text-sidebar-foreground hover:shadow-[0_0_0_1px_var(--color-sidebar-border)]\",\n },\n size: {\n default: \"h-8 text-sm\",\n sm: \"h-7 text-xs\",\n lg: \"h-12 text-sm group-data-[collapsible=icon]:!p-0\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nconst SidebarMenuButton = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n isActive?: boolean;\n tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n } & VariantProps<typeof sidebarMenuButtonVariants>\n>(\n (\n {\n asChild = false,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const { isMobile, state } = useSidebar();\n\n const button = (\n <Comp\n ref={ref}\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n />\n );\n\n if (!tooltip) {\n return button;\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent\n side=\"right\"\n align=\"center\"\n hidden={state !== \"collapsed\" || isMobile}\n {...tooltip}\n />\n </Tooltip>\n );\n },\n);\nSidebarMenuButton.displayName = \"SidebarMenuButton\";\n\nconst SidebarMenuAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n showOnHover?: boolean;\n }\n>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-action\"\n className={cn(\n \"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-colors hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-foreground [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n showOnHover &&\n \"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuAction.displayName = \"SidebarMenuAction\";\n\nconst SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"menu-badge\"\n className={cn(\n \"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground\",\n \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\";\n\nconst SidebarMenuSkeleton = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n showIcon?: boolean;\n }\n>(({ className, showIcon = false, ...props }, ref) => {\n // Ancho aleatorio (50–90%) deliberado para variar el skeleton; estable\n // entre renders gracias al useMemo.\n const width = React.useMemo(() => {\n // eslint-disable-next-line react-hooks/purity -- aleatoriedad intencional, memoizada\n return `${Math.floor(Math.random() * 40) + 50}%`;\n }, []);\n\n return (\n <div\n ref={ref}\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && <Skeleton className=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />}\n <Skeleton\n className=\"h-4 max-w-(--skeleton-width) flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n );\n});\nSidebarMenuSkeleton.displayName = \"SidebarMenuSkeleton\";\n\nconst SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu-sub\"\n className={cn(\n \"mx-0 flex min-w-0 flex-col gap-1 px-0 py-0.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuSub.displayName = \"SidebarMenuSub\";\n\nconst SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ ...props }, ref) => <li ref={ref} {...props} />,\n);\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\";\n\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<\"a\"> & {\n asChild?: boolean;\n size?: \"sm\" | \"md\";\n isActive?: boolean;\n }\n>(({ asChild = false, size = \"md\", isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive}\n className={cn(\n \"flex h-8 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground/80 outline-none ring-sidebar-ring transition-colors duration-200 hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-70 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\",\n \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n size === \"sm\" && \"text-xs\",\n size === \"md\" && \"text-sm\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\";\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n"
|
|
25
|
+
"content": "\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { PanelLeft } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport { Button } from \"./Button\";\nimport { Input } from \"./Input\";\nimport { Separator } from \"./Separator\";\nimport { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from \"./Sheet\";\nimport { Skeleton } from \"./Skeleton\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"./Tooltip\";\nimport { useIsMobile } from \"./useIsMobile\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar_state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"20rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"20rem\";\nconst SIDEBAR_WIDTH_ICON = \"4rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\ntype SidebarContextProps = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext<SidebarContextProps | null>(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo<SidebarContextProps>(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],\n );\n\n return (\n <SidebarContext.Provider value={contextValue}>\n <TooltipProvider delayDuration={0}>\n <div\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH,\n \"--sidebar-width-icon\": SIDEBAR_WIDTH_ICON,\n ...style,\n } as React.CSSProperties\n }\n className={cn(\n \"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n </TooltipProvider>\n </SidebarContext.Provider>\n );\n },\n);\nSidebarProvider.displayName = \"SidebarProvider\";\n\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n }\n>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n <div\n className={cn(\n \"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground\",\n className,\n )}\n ref={ref}\n {...props}\n >\n {children}\n </div>\n );\n }\n\n if (isMobile) {\n return (\n <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>\n <SheetContent\n data-sidebar=\"sidebar\"\n data-mobile=\"true\"\n className=\"w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n <SheetHeader className=\"sr-only\">\n <SheetTitle>Sidebar</SheetTitle>\n <SheetDescription>Displays the mobile sidebar.</SheetDescription>\n </SheetHeader>\n <div className=\"flex h-full w-full flex-col\">{children}</div>\n </SheetContent>\n </Sheet>\n );\n }\n\n return (\n <div\n ref={ref}\n className=\"group peer hidden text-sidebar-foreground md:block\"\n data-state={state}\n data-collapsible={state === \"collapsed\" ? collapsible : \"\"}\n data-variant={variant}\n data-side={side}\n >\n {/* This is what handles the sidebar gap on desktop */}\n <div\n className={cn(\n \"relative h-svh w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear\",\n \"group-data-[collapsible=offcanvas]:w-0\",\n \"group-data-[side=right]:rotate-180\",\n variant === \"floating\" || variant === \"inset\"\n ? \"group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]\"\n : \"group-data-[collapsible=icon]:w-(--sidebar-width-icon)\",\n )}\n />\n <div\n className={cn(\n \"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex\",\n side === \"left\"\n ? \"left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]\"\n : \"right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]\",\n // Adjust the padding for floating and inset variants.\n variant === \"floating\" || variant === \"inset\"\n ? \"p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]\"\n : \"bg-sidebar group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l border-sidebar-border\",\n className,\n )}\n {...props}\n >\n <div\n data-sidebar=\"sidebar\"\n className=\"flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow\"\n >\n {children}\n </div>\n </div>\n </div>\n );\n },\n);\nSidebar.displayName = \"Sidebar\";\n\nconst SidebarTrigger = React.forwardRef<\n React.ElementRef<typeof Button>,\n React.ComponentProps<typeof Button>\n>(({ className, onClick, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <Button\n ref={ref}\n data-sidebar=\"trigger\"\n variant=\"ghost\"\n size=\"icon\"\n className={cn(\"h-8 w-8 rounded-md hover:bg-secondary\", className)}\n onClick={(event) => {\n onClick?.(event);\n toggleSidebar();\n }}\n {...props}\n >\n <PanelLeft />\n <span className=\"sr-only\">Toggle Sidebar</span>\n </Button>\n );\n});\nSidebarTrigger.displayName = \"SidebarTrigger\";\n\nconst SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<\"button\">>(\n ({ className, ...props }, ref) => {\n const { toggleSidebar } = useSidebar();\n\n return (\n <button\n ref={ref}\n data-sidebar=\"rail\"\n aria-label=\"Toggle Sidebar\"\n tabIndex={-1}\n onClick={toggleSidebar}\n title=\"Toggle Sidebar\"\n className={cn(\n \"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-0.5 hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex\",\n \"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize\",\n \"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize\",\n \"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar\",\n \"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2\",\n \"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarRail.displayName = \"SidebarRail\";\n\nconst SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<\"main\">>(\n ({ className, ...props }, ref) => {\n return (\n <main\n ref={ref}\n className={cn(\n \"relative flex w-full flex-1 flex-col bg-background\",\n \"md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-lg md:peer-data-[variant=inset]:shadow\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarInset.displayName = \"SidebarInset\";\n\nconst SidebarInput = React.forwardRef<\n React.ElementRef<typeof Input>,\n React.ComponentProps<typeof Input>\n>(({ className, ...props }, ref) => {\n return (\n <Input\n ref={ref}\n data-sidebar=\"input\"\n className={cn(\n \"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarInput.displayName = \"SidebarInput\";\n\nconst SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"header\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarHeader.displayName = \"SidebarHeader\";\n\nconst SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"footer\"\n className={cn(\"flex flex-col gap-2 p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarFooter.displayName = \"SidebarFooter\";\n\nconst SidebarSeparator = React.forwardRef<\n React.ElementRef<typeof Separator>,\n React.ComponentProps<typeof Separator>\n>(({ className, ...props }, ref) => {\n return (\n <Separator\n ref={ref}\n data-sidebar=\"separator\"\n className={cn(\"mx-2 w-auto bg-sidebar-border\", className)}\n {...props}\n />\n );\n});\nSidebarSeparator.displayName = \"SidebarSeparator\";\n\nconst SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"content\"\n className={cn(\n \"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-border/50\",\n className,\n )}\n {...props}\n />\n );\n },\n);\nSidebarContent.displayName = \"SidebarContent\";\n\nconst SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n data-sidebar=\"group\"\n className={cn(\"relative flex w-full min-w-0 flex-col p-2\", className)}\n {...props}\n />\n );\n },\n);\nSidebarGroup.displayName = \"SidebarGroup\";\n\nconst SidebarGroupLabel = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"div\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-label\"\n className={cn(\n \"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n \"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupLabel.displayName = \"SidebarGroupLabel\";\n\nconst SidebarGroupAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & { asChild?: boolean }\n>(({ className, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"group-action\"\n className={cn(\n \"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarGroupAction.displayName = \"SidebarGroupAction\";\n\nconst SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"group-content\"\n className={cn(\"w-full text-sm\", className)}\n {...props}\n />\n ),\n);\nSidebarGroupContent.displayName = \"SidebarGroupContent\";\n\nconst SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu\"\n className={cn(\"flex w-full min-w-0 flex-col gap-1\", className)}\n {...props}\n />\n ),\n);\nSidebarMenu.displayName = \"SidebarMenu\";\n\nconst SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ className, ...props }, ref) => (\n <li\n ref={ref}\n data-sidebar=\"menu-item\"\n className={cn(\"group/menu-item relative\", className)}\n {...props}\n />\n ),\n);\nSidebarMenuItem.displayName = \"SidebarMenuItem\";\n\nconst sidebarMenuButtonVariants = cva(\n \"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[color,background-color,width,height,padding] duration-200 hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-secondary data-[state=open]:hover:text-sidebar-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"hover:bg-secondary hover:text-sidebar-foreground\",\n outline:\n \"bg-background shadow-[0_0_0_1px_var(--color-sidebar-border)] hover:bg-secondary hover:text-sidebar-foreground hover:shadow-[0_0_0_1px_var(--color-sidebar-border)]\",\n },\n size: {\n default: \"h-8 text-sm\",\n sm: \"h-7 text-xs\",\n lg: \"h-12 text-sm group-data-[collapsible=icon]:!p-0\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nconst SidebarMenuButton = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n isActive?: boolean;\n tooltip?: string | React.ComponentProps<typeof TooltipContent>;\n } & VariantProps<typeof sidebarMenuButtonVariants>\n>(\n (\n {\n asChild = false,\n isActive = false,\n variant = \"default\",\n size = \"default\",\n tooltip,\n className,\n ...props\n },\n ref,\n ) => {\n const Comp = asChild ? Slot : \"button\";\n const { isMobile, state } = useSidebar();\n\n const button = (\n <Comp\n ref={ref}\n data-sidebar=\"menu-button\"\n data-size={size}\n data-active={isActive}\n className={cn(sidebarMenuButtonVariants({ variant, size }), className)}\n {...props}\n />\n );\n\n if (!tooltip) {\n return button;\n }\n\n if (typeof tooltip === \"string\") {\n tooltip = {\n children: tooltip,\n };\n }\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent\n side=\"right\"\n align=\"center\"\n hidden={state !== \"collapsed\" || isMobile}\n {...tooltip}\n />\n </Tooltip>\n );\n },\n);\nSidebarMenuButton.displayName = \"SidebarMenuButton\";\n\nconst SidebarMenuAction = React.forwardRef<\n HTMLButtonElement,\n React.ComponentProps<\"button\"> & {\n asChild?: boolean;\n showOnHover?: boolean;\n }\n>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-action\"\n className={cn(\n \"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-colors hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-foreground [&>svg]:size-4 [&>svg]:shrink-0\",\n // Increases the hit area of the button on mobile.\n \"after:absolute after:-inset-2 after:md:hidden\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n showOnHover &&\n \"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuAction.displayName = \"SidebarMenuAction\";\n\nconst SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<\"div\">>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n data-sidebar=\"menu-badge\"\n className={cn(\n \"pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground\",\n \"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground\",\n \"peer-data-[size=sm]/menu-button:top-1\",\n \"peer-data-[size=default]/menu-button:top-1.5\",\n \"peer-data-[size=lg]/menu-button:top-2.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuBadge.displayName = \"SidebarMenuBadge\";\n\nconst SidebarMenuSkeleton = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n showIcon?: boolean;\n }\n>(({ className, showIcon = false, ...props }, ref) => {\n // Ancho aleatorio (50–90%) deliberado para variar el skeleton; estable\n // entre renders gracias al useMemo.\n const width = React.useMemo(() => {\n // eslint-disable-next-line react-hooks/purity -- aleatoriedad intencional, memoizada\n return `${Math.floor(Math.random() * 40) + 50}%`;\n }, []);\n\n return (\n <div\n ref={ref}\n data-sidebar=\"menu-skeleton\"\n className={cn(\"flex h-8 items-center gap-2 rounded-md px-2\", className)}\n {...props}\n >\n {showIcon && <Skeleton className=\"size-4 rounded-md\" data-sidebar=\"menu-skeleton-icon\" />}\n <Skeleton\n className=\"h-4 max-w-(--skeleton-width) flex-1\"\n data-sidebar=\"menu-skeleton-text\"\n style={\n {\n \"--skeleton-width\": width,\n } as React.CSSProperties\n }\n />\n </div>\n );\n});\nSidebarMenuSkeleton.displayName = \"SidebarMenuSkeleton\";\n\nconst SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<\"ul\">>(\n ({ className, ...props }, ref) => (\n <ul\n ref={ref}\n data-sidebar=\"menu-sub\"\n className={cn(\n \"mx-0 flex min-w-0 flex-col gap-1 px-0 py-0.5\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarMenuSub.displayName = \"SidebarMenuSub\";\n\nconst SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<\"li\">>(\n ({ ...props }, ref) => <li ref={ref} {...props} />,\n);\nSidebarMenuSubItem.displayName = \"SidebarMenuSubItem\";\n\nconst SidebarMenuSubButton = React.forwardRef<\n HTMLAnchorElement,\n React.ComponentProps<\"a\"> & {\n asChild?: boolean;\n size?: \"sm\" | \"md\";\n isActive?: boolean;\n }\n>(({ asChild = false, size = \"md\", isActive, className, ...props }, ref) => {\n const Comp = asChild ? Slot : \"a\";\n\n return (\n <Comp\n ref={ref}\n data-sidebar=\"menu-sub-button\"\n data-size={size}\n data-active={isActive}\n className={cn(\n \"flex h-8 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground/80 outline-none ring-sidebar-ring transition-colors duration-200 hover:bg-secondary hover:text-sidebar-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-70 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground\",\n \"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground\",\n size === \"sm\" && \"text-xs\",\n size === \"md\" && \"text-sm\",\n \"group-data-[collapsible=icon]:hidden\",\n className,\n )}\n {...props}\n />\n );\n});\nSidebarMenuSubButton.displayName = \"SidebarMenuSubButton\";\n\nexport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupAction,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarInput,\n SidebarInset,\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuBadge,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSkeleton,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarProvider,\n SidebarRail,\n SidebarSeparator,\n SidebarTrigger,\n useSidebar,\n};\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "components/base/Sidebar.md",
|
|
29
29
|
"type": "registry:file",
|
|
30
30
|
"target": "components/base/Sidebar.md",
|
|
31
|
-
"content": "# Sidebar — guía de uso\n\nPrimitivas internas (estilo shadcn) que usa `AppSidebar` por debajo. **No las compongas a mano para una barra de navegación: usa `AppSidebar`** (data-driven). Recurre a estas primitivas solo para layouts a medida. El componente vive en tu proyecto: instálalo con `create-lexy add sidebar` y edítalo con libertad.\n\n##
|
|
31
|
+
"content": "# Sidebar — guía de uso\n\nPrimitivas internas (estilo shadcn) que usa `AppSidebar` por debajo. **No las compongas a mano para una barra de navegación: usa `AppSidebar`** (data-driven). Recurre a estas primitivas solo para layouts a medida. El componente vive en tu proyecto: instálalo con `create-lexy add sidebar` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPrimitivas internas para layouts laterales a medida (como la navegación de este showcase). Para la barra de navegación estándar de una app usa `AppSidebar` — estas piezas son el motor que corre debajo.\n\n## Composición\n\nKit completo estilo shadcn: `SidebarProvider` (estado), `Sidebar`, `SidebarHeader`/`SidebarContent`/`SidebarFooter`, grupos (`SidebarGroup*`), menús (`SidebarMenu*`), `SidebarTrigger` para colapsar y `SidebarInset` para el área de contenido.\n\n## Vía recomendada: AppSidebar\n\nPara una barra de navegación, **no** uses estas primitivas directamente. Describe la navegación como datos y deja que `AppSidebar` arme la composición correcta:\n\n```tsx\n<SidebarProvider>\n <AppSidebar groups={groups} user={user} logo={<Logo />} />\n <SidebarInset>{/* contenido */}</SidebarInset>\n</SidebarProvider>\n```\n\nLee `AppSidebar.md` para el modelo de datos (`groups`, `items`, `user`).\n\n## Cuándo SÍ usar las primitivas\n\nSolo para shells o composiciones a medida que no encajan en el modelo `groups` de `AppSidebar`:\n`SidebarProvider`, `SidebarInset`, `SidebarTrigger` para el armazón; y `SidebarGroup`, `SidebarMenu`, `SidebarMenuButton`, etc. si necesitas una estructura totalmente custom.\n\n## Reglas\n\n- Para navegación estándar → `AppSidebar`. No mezcles primitivas sueltas con `AppSidebar` en la misma barra.\n- No insertes piezas sueltas como `SidebarSeparator` dentro de una barra hecha con `AppSidebar`.\n- Si el caso es a medida, mantén un solo enfoque (todo primitivas) y documenta por qué no usaste `AppSidebar`.\n\n## Cuándo NO usar (directamente)\n\n- **Barra de navegación normal** → `AppSidebar`.\n- **Layout de app con header + sidebar** → `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n\n## Import\n\n```tsx\n// Para el armazón/contenedor (caso a medida):\nimport {\n SidebarProvider, SidebarInset, SidebarTrigger,\n} from \"@/components/base/Sidebar\";\n```\n\n## Exports\n\nContenedor: `Sidebar`, `SidebarProvider`, `SidebarInset`, `SidebarTrigger`, `SidebarRail`, `useSidebar`.\nEstructura: `SidebarHeader`, `SidebarContent`, `SidebarFooter`, `SidebarGroup`, `SidebarGroupLabel`, `SidebarGroupAction`, `SidebarGroupContent`, `SidebarSeparator`, `SidebarInput`.\nMenú: `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton`, `SidebarMenuAction`, `SidebarMenuBadge`, `SidebarMenuSkeleton`, `SidebarMenuSub`, `SidebarMenuSubItem`, `SidebarMenuSubButton`.\n\n## Para IA\n\n1. Por defecto, NO uses estas primitivas: usa `AppSidebar` y su prop `groups`.\n2. Usa `SidebarProvider`/`SidebarInset`/`SidebarTrigger` solo como armazón cuando un layout a medida lo exija.\n3. Si compones a mano, no mezcles con `AppSidebar`; documenta la decisión.\n"
|
|
32
32
|
}
|
|
33
33
|
]
|
|
34
34
|
}
|
package/assets/r/skeleton.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Skeleton.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Skeleton.md",
|
|
22
|
-
"content": "# Skeleton — guía de uso\n\nEsqueleto de carga. Para estados de espera. El componente vive en tu proyecto: instálalo con `create-lexy add skeleton` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# Skeleton — guía de uso\n\nEsqueleto de carga. Para estados de espera. El componente vive en tu proyecto: instálalo con `create-lexy add skeleton` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara esperas donde conoces la forma del contenido que viene: filas de tabla, cards, un detalle. Mantiene el layout estable y hace la espera sentirse más corta. Para acciones puntuales (guardar, enviar), el `Spinner` en el botón comunica mejor.\n\n## Composición\n\nUna sola pieza (`Skeleton`) que dibujas con las dimensiones del contenido real: unos cuantos rectángulos bastan para sugerir la estructura.\n\n## Uso básico\n\n```tsx\n<Skeleton />\n```\n\n## Reglas\n\n- Usa Skeleton según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Skeleton } from \"@/components/base/Skeleton\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Skeleton desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|
package/assets/r/slider.json
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"path": "components/base/Slider.md",
|
|
20
20
|
"type": "registry:file",
|
|
21
21
|
"target": "components/base/Slider.md",
|
|
22
|
-
"content": "# Slider — guía de uso\n\nControl deslizante. Para selección de rango numérico. El componente vive en tu proyecto: instálalo con `create-lexy add slider` y edítalo con libertad.\n\n##
|
|
22
|
+
"content": "# Slider — guía de uso\n\nControl deslizante. Para selección de rango numérico. El componente vive en tu proyecto: instálalo con `create-lexy add slider` y edítalo con libertad.\n\n## Cuándo usarlo\n\nPara elegir un valor o rango dentro de un continuo donde lo aproximado basta: montos estimados, porcentajes. Si el valor exacto importa (una cifra en pesos), un `Input` numérico es más honesto.\n\n## Composición\n\nUna sola pieza (`Slider`, Radix) de uno o dos pulgares (valor o rango). Acompáñalo del valor visible en texto.\n\n## Uso básico\n\n```tsx\n<Slider />\n```\n\n## Reglas\n\n- Usa Slider según el propósito descrito.\n- No abuses de este componente en contexts donde no aplica.\n\n## Import\n\n```tsx\nimport { Slider } from \"@/components/base/Slider\";\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\n\n## Para IA\n\n1. Identifica el contexto de uso del componente.\n2. Importa Slider desde el path correcto.\n3. Configura las props según la necesidad.\n4. Verifica que el componente se integre correctamente en el layout.\n5. Solo usa variantes documentadas.\n"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
25
25
|
}
|