create-lexy 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/assets/r/accordion.json +25 -0
  2. package/assets/r/alert-dialog.json +28 -0
  3. package/assets/r/app-accordion.json +25 -0
  4. package/assets/r/app-dialog.json +26 -0
  5. package/assets/r/app-header-bar.json +29 -0
  6. package/assets/r/app-sidebar.json +28 -0
  7. package/assets/r/avatar.json +25 -0
  8. package/assets/r/badge.json +25 -0
  9. package/assets/r/breadcrumb.json +25 -0
  10. package/assets/r/button-group.json +28 -0
  11. package/assets/r/button.json +1 -0
  12. package/assets/r/calendar.json +28 -0
  13. package/assets/r/card.json +25 -0
  14. package/assets/r/chart.json +25 -0
  15. package/assets/r/checkbox.json +25 -0
  16. package/assets/r/combobox.json +27 -0
  17. package/assets/r/command.json +25 -0
  18. package/assets/r/confirmacion.json +27 -0
  19. package/assets/r/counter-badge.json +25 -0
  20. package/assets/r/crm-app-layout.json +29 -0
  21. package/assets/r/crm-desk.json +31 -0
  22. package/assets/r/crm-detalle-caso.json +28 -0
  23. package/assets/r/date-picker.json +30 -0
  24. package/assets/r/dialog.json +25 -0
  25. package/assets/r/dropdown-menu.json +25 -0
  26. package/assets/r/empty.json +25 -0
  27. package/assets/r/feature-card.json +23 -0
  28. package/assets/r/form.json +30 -0
  29. package/assets/r/header-bar.json +27 -0
  30. package/assets/r/input.json +23 -0
  31. package/assets/r/intake-wizard.json +28 -0
  32. package/assets/r/label.json +26 -0
  33. package/assets/r/login.json +28 -0
  34. package/assets/r/logo.json +131 -0
  35. package/assets/r/menubar.json +25 -0
  36. package/assets/r/navigation-menu.json +26 -0
  37. package/assets/r/pagination.json +28 -0
  38. package/assets/r/popover.json +25 -0
  39. package/assets/r/profile-card.json +26 -0
  40. package/assets/r/progress.json +25 -0
  41. package/assets/r/radio-group.json +25 -0
  42. package/assets/r/registry.json +1447 -1
  43. package/assets/r/scroll-area.json +25 -0
  44. package/assets/r/searchbox.json +23 -0
  45. package/assets/r/select.json +25 -0
  46. package/assets/r/separator.json +25 -0
  47. package/assets/r/sheet.json +26 -0
  48. package/assets/r/sidebar.json +34 -0
  49. package/assets/r/skeleton.json +25 -0
  50. package/assets/r/slider.json +25 -0
  51. package/assets/r/snippet.json +25 -0
  52. package/assets/r/spinner.json +26 -0
  53. package/assets/r/status-dot.json +25 -0
  54. package/assets/r/switch.json +25 -0
  55. package/assets/r/table.json +25 -0
  56. package/assets/r/tabs.json +25 -0
  57. package/assets/r/tag.json +25 -0
  58. package/assets/r/textarea.json +23 -0
  59. package/assets/r/toaster.json +25 -0
  60. package/assets/r/tooltip.json +25 -0
  61. package/assets/r/tree.json +23 -0
  62. package/assets/theme/lexy-theme.css +3 -0
  63. package/dist/index.js +37 -36
  64. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "form",
4
+ "type": "registry:ui",
5
+ "title": "Form",
6
+ "description": "Formularios con validación (patrón shadcn sobre `react-hook-form` + `zod`). `Form` provee el contexto, `FormField` registra cada campo y `FormItem`/`FormLabel`/`FormControl` cablean ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo. `create-lexy add form` instala también `zod` y `@hookform/resolvers`. El componente vive en tu proyecto: instálalo con `create-lexy add form` y edítalo con libertad.",
7
+ "dependencies": [
8
+ "@radix-ui/react-slot",
9
+ "react-hook-form@^7.62.0",
10
+ "zod@^4.1.0",
11
+ "@hookform/resolvers@^5.2.0"
12
+ ],
13
+ "registryDependencies": [
14
+ "label"
15
+ ],
16
+ "files": [
17
+ {
18
+ "path": "components/base/Form.tsx",
19
+ "type": "registry:ui",
20
+ "target": "components/base/Form.tsx",
21
+ "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n Controller,\n FormProvider,\n useFormContext,\n useFormState,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from \"react-hook-form\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Label } from \"./Label\";\n\n// Patrón shadcn sobre react-hook-form: Form provee el contexto, FormField\n// registra el campo (Controller) y FormItem/FormLabel/FormControl cablean\n// ids y aria-* automáticamente. Validación con zod vía\n// zodResolver(@hookform/resolvers) — `add form` instala ambas deps.\n\nconst Form = FormProvider;\n\ninterface FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> {\n name: TName;\n}\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n {} as FormFieldContextValue,\n);\n\nconst FormField = <\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n ...props\n}: ControllerProps<TFieldValues, TName>) => {\n return (\n <FormFieldContext.Provider value={{ name: props.name }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n );\n};\n\nconst useFormField = () => {\n const fieldContext = React.useContext(FormFieldContext);\n const itemContext = React.useContext(FormItemContext);\n const { getFieldState } = useFormContext();\n const formState = useFormState({ name: fieldContext.name });\n const fieldState = getFieldState(fieldContext.name, formState);\n\n if (!fieldContext) {\n throw new Error(\"useFormField debe usarse dentro de <FormField>\");\n }\n\n const { id } = itemContext;\n\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-form-item`,\n formDescriptionId: `${id}-form-item-description`,\n formMessageId: `${id}-form-item-message`,\n ...fieldState,\n };\n};\n\ninterface FormItemContextValue {\n id: string;\n}\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n {} as FormItemContextValue,\n);\n\nconst FormItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const id = React.useId();\n\n return (\n <FormItemContext.Provider value={{ id }}>\n <div ref={ref} className={cn(\"grid gap-2\", className)} {...props} />\n </FormItemContext.Provider>\n );\n});\nFormItem.displayName = \"FormItem\";\n\nconst FormLabel = React.forwardRef<\n React.ElementRef<typeof Label>,\n React.ComponentPropsWithoutRef<typeof Label>\n>(({ className, ...props }, ref) => {\n const { error, formItemId } = useFormField();\n\n return (\n <Label\n ref={ref}\n className={cn(error && \"text-destructive\", className)}\n htmlFor={formItemId}\n {...props}\n />\n );\n});\nFormLabel.displayName = \"FormLabel\";\n\nconst FormControl = React.forwardRef<\n React.ElementRef<typeof Slot>,\n React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n const { error, formItemId, formDescriptionId, formMessageId } =\n useFormField();\n\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={\n !error\n ? `${formDescriptionId}`\n : `${formDescriptionId} ${formMessageId}`\n }\n aria-invalid={!!error}\n {...props}\n />\n );\n});\nFormControl.displayName = \"FormControl\";\n\nconst FormDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n const { formDescriptionId } = useFormField();\n\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n});\nFormDescription.displayName = \"FormDescription\";\n\nconst FormMessage = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? \"\") : children;\n\n if (!body) {\n return null;\n }\n\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn(\"text-sm text-destructive\", className)}\n {...props}\n >\n {body}\n </p>\n );\n});\nFormMessage.displayName = \"FormMessage\";\n\nexport {\n useFormField,\n Form,\n FormItem,\n FormLabel,\n FormControl,\n FormDescription,\n FormMessage,\n FormField,\n};\n"
22
+ },
23
+ {
24
+ "path": "components/base/Form.md",
25
+ "type": "registry:file",
26
+ "target": "components/base/Form.md",
27
+ "content": "# Form — guía de uso\n\nFormularios con validación (patrón shadcn sobre `react-hook-form` + `zod`). `Form` provee el contexto, `FormField` registra cada campo y `FormItem`/`FormLabel`/`FormControl` cablean ids y `aria-*` automáticamente; `FormMessage` muestra el error del campo. `create-lexy add form` instala también `zod` y `@hookform/resolvers`. El componente vive en tu proyecto: instálalo con `create-lexy add form` y edítalo con libertad.\n\n## Import\n\n```tsx\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport { useForm } from \"react-hook-form\";\nimport { z } from \"zod\";\n\nimport {\n Form,\n FormControl,\n FormDescription,\n FormField,\n FormItem,\n FormLabel,\n FormMessage,\n} from \"@/components/base/Form\";\n```\n\n## Uso básico\n\n```tsx\nconst schema = z.object({\n email: z.string().email(\"Ingresa un correo válido\"),\n nombre: z.string().min(2, \"Ingresa tu nombre completo\"),\n});\n\nfunction PerfilForm() {\n const form = useForm<z.infer<typeof schema>>({\n resolver: zodResolver(schema),\n defaultValues: { email: \"\", nombre: \"\" },\n });\n\n function onSubmit(values: z.infer<typeof schema>) {\n // values ya viene validado y tipado\n }\n\n return (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"grid gap-6\">\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel required>Correo</FormLabel>\n <FormControl>\n <Input type=\"email\" placeholder=\"nombre@empresa.cl\" {...field} />\n </FormControl>\n <FormDescription>Usaremos este correo para avisarte.</FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n <Button type=\"submit\">Guardar cambios</Button>\n </form>\n </Form>\n );\n}\n```\n\n## Integración con los inputs del sistema\n\n`FormControl` es un Slot: envuelve cualquier control del registry sin nodo extra. Con `Input`, `Textarea` y `Searchbox` pasa `{...field}` directo. Para controles con API propia, mapea explícito:\n\n```tsx\n{/* Select */}\n<Select onValueChange={field.onChange} value={field.value}>\n <FormControl>\n <SelectTrigger><SelectValue placeholder=\"Selecciona\" /></SelectTrigger>\n </FormControl>\n <SelectContent>…</SelectContent>\n</Select>\n\n{/* Checkbox / Switch */}\n<FormControl>\n <Checkbox checked={field.value} onCheckedChange={field.onChange} />\n</FormControl>\n\n{/* DatePicker */}\n<FormControl>\n <DatePicker value={field.value} onChange={field.onChange} />\n</FormControl>\n```\n\n## Props\n\n| Pieza | Qué es |\n|---|---|\n| `Form` | Alias de `FormProvider` de react-hook-form: recibe `{...form}`. |\n| `FormField` | `Controller` + contexto del nombre del campo (`control`, `name`, `render`). |\n| `FormItem` | Contenedor del campo (`grid gap-2`); genera el id base. `className`, `...props`, `ref`. |\n| `FormLabel` | `Label` del sistema conectado al control (`htmlFor` automático, rojo con error). Acepta `required`. |\n| `FormControl` | Slot que inyecta `id`, `aria-describedby`, `aria-invalid` al control envuelto. |\n| `FormDescription` | Ayuda contextual (`text-muted-foreground`). |\n| `FormMessage` | Error del campo (`text-destructive`); no renderiza nada si no hay error. |\n| `useFormField` | Hook con el estado del campo, para piezas custom. |\n\n## Reglas (UX writing de errores)\n\n- El mensaje de error dice **qué pasó y cómo arreglarlo, en tono neutro**: *\"Ingresa un correo válido\"*, no *\"Error de validación\"* ni *\"Campo inválido\"*.\n- Escribe los mensajes en el schema de zod (segundo argumento de cada validación): ahí viven junto a la regla.\n- El label existe siempre, aunque haya placeholder; campo obligatorio se marca con `required` en `FormLabel`.\n- Los inputs del sistema ya pintan el borde rojo con `aria-invalid` (se lo pone `FormControl`); no dupliques estilos de error.\n- Valida en el submit (default de react-hook-form); evita validar tecla a tecla salvo necesidad real (`mode: \"onBlur\"` si el flujo lo pide).\n\n## Cuándo NO usar\n\n- **Un solo campo suelto** (un buscador, un toggle) → usa el control directo con estado local; el patrón Form paga cuando hay validación y varios campos.\n- **Filtros que aplican al instante** → estado local o de URL, sin submit.\n\n## Para IA\n\n1. Define el schema zod arriba del componente; mensajes de error en español neutro que digan cómo corregir.\n2. Un `FormField` por campo, siempre con `FormItem` + `FormLabel` + `FormControl` + `FormMessage`.\n3. Usa los controles del registry dentro de `FormControl` (Input, Select, Checkbox, DatePicker…), no inputs nativos sin estilo.\n4. `FormDescription` solo si la ayuda aporta; no repitas el label.\n5. El botón de submit es `Button` con `type=\"submit\"` dentro del `<form>`.\n"
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "header-bar",
4
+ "type": "registry:ui",
5
+ "title": "HeaderBar",
6
+ "description": "Barra superior de aplicación o sitio: marca a la izquierda y acciones a la derecha. Renderiza un `<header>` semántico. El componente vive en tu proyecto: instálalo con `create-lexy add header-bar` y edítalo con libertad.",
7
+ "dependencies": [
8
+ "class-variance-authority"
9
+ ],
10
+ "registryDependencies": [
11
+ "logo"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "components/base/HeaderBar.tsx",
16
+ "type": "registry:ui",
17
+ "target": "components/base/HeaderBar.tsx",
18
+ "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Logo } from \"./Logo\";\n\n// `brand-navy` = navy/ink de marca Lexy (mismo del wordmark oscuro), definido\n// en el theme como --color-brand-navy.\nconst headerVariants = cva(\n \"flex h-16 w-full shrink-0 items-center justify-between gap-6\",\n {\n variants: {\n surface: {\n default: \"bg-background text-foreground\",\n transparent: \"bg-transparent text-foreground\",\n navy: \"bg-brand-navy text-white\",\n },\n bordered: { true: \"border-b\", false: \"\" },\n padding: {\n page: \"px-4 md:px-6 lg:px-8\",\n none: \"px-0\",\n },\n sticky: { true: \"sticky top-0 z-50\", false: \"\" },\n },\n compoundVariants: [\n { surface: \"default\", bordered: true, class: \"border-border\" },\n { surface: \"transparent\", bordered: true, class: \"border-border\" },\n { surface: \"navy\", bordered: true, class: \"border-white/15\" },\n ],\n defaultVariants: {\n surface: \"default\",\n bordered: true,\n padding: \"page\",\n sticky: false,\n },\n },\n);\n\nexport interface HeaderBarProps\n extends React.HTMLAttributes<HTMLElement>,\n VariantProps<typeof headerVariants> {\n /** Acciones alineadas a la derecha (botones, avatar, menús). */\n actions?: React.ReactNode;\n /** Sobrescribe el área de marca (por defecto, el `Logo`). */\n brand?: React.ReactNode;\n}\n\nexport const HeaderBar = React.forwardRef<HTMLElement, HeaderBarProps>(\n function HeaderBar(\n { surface = \"default\", bordered, padding, sticky, actions, brand, className, ...props },\n ref,\n ) {\n const logoSurface = surface === \"navy\" ? \"dark\" : \"light\";\n\n return (\n <header\n ref={ref}\n className={cn(\n headerVariants({ surface, bordered, padding, sticky }),\n className,\n )}\n {...props}>\n <div className='flex min-w-0 items-center gap-6'>\n {brand ?? <Logo surface={logoSurface} />}\n </div>\n {actions != null && (\n <div className='flex shrink-0 items-center gap-2'>{actions}</div>\n )}\n </header>\n );\n },\n);\n\nHeaderBar.displayName = \"HeaderBar\";\n\nexport { headerVariants };\n"
19
+ },
20
+ {
21
+ "path": "components/base/HeaderBar.md",
22
+ "type": "registry:file",
23
+ "target": "components/base/HeaderBar.md",
24
+ "content": "# HeaderBar — guía de uso\n\nBarra superior de aplicación o sitio: marca a la izquierda y acciones a la derecha. Renderiza un `<header>` semántico. El componente vive en tu proyecto: instálalo con `create-lexy add header-bar` y edítalo con libertad.\n\n## Import\n\n```tsx\nimport { HeaderBar } from \"@/components/base/HeaderBar\";\n```\n\n## Uso básico\n\n```tsx\n<HeaderBar\n actions={\n <>\n <Button variant=\"ghost\">Ingresar</Button>\n <Button>Crear cuenta</Button>\n </>\n }\n/>\n```\n\nPor defecto muestra el `Logo`. Sobrescribe la marca con `brand`:\n\n```tsx\n<HeaderBar brand={<Logo layout=\"horizontal\" />} actions={<UserMenu />} />\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `surface` | `\"default\" \\| \"transparent\" \\| \"navy\"` | `\"default\"` | Fondo. `navy` usa el ink de marca con texto blanco. |\n| `bordered` | `boolean` | `true` | Borde inferior. |\n| `padding` | `\"page\" \\| \"none\"` | `\"page\"` | Padding horizontal estándar o nulo. |\n| `sticky` | `boolean` | `false` | Fija la barra al hacer scroll. |\n| `actions` | `ReactNode` | — | Contenido alineado a la derecha. |\n| `brand` | `ReactNode` | `<Logo>` | Sobrescribe el área de marca. |\n\n## Reglas\n\n- Mantén pocas acciones a la derecha; agrupa el resto en un `DropdownMenu`.\n- `surface=\"navy\"` ajusta el logo a su versión clara automáticamente; no fuerces el color del logo.\n- Si es una app con barra lateral, usa `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n- `sticky` para navegación persistente; cuida no tapar contenido al anclar.\n\n## Cuándo NO usar\n\n- **Layout completo de app con sidebar** → `SidebarProvider` + `AppSidebar` + `SidebarInset` (ver AppSidebar.md).\n- **Navegación lateral** → `AppSidebar`.\n- **Menús de navegación con dropdowns ricos** → combínalo con `NavigationMenu` dentro de `actions`/`brand`.\n\n## Para IA\n\n1. Úsalo como cabecera de sitio o barra superior simple.\n2. Pon la marca (o deja el `Logo` por defecto) y 1–3 acciones en `actions`.\n3. Elige `surface`/`sticky` según el contexto; agrupa acciones extra en `DropdownMenu`.\n4. Si la pantalla es una app con sidebar, prefiere `SidebarProvider` + `AppSidebar` + `SidebarInset` en lugar de componer a mano.\n"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "input",
4
+ "type": "registry:ui",
5
+ "title": "Input",
6
+ "description": "Campo de texto de una línea. Renderiza un `<input>` nativo; acepta todos sus atributos (`type`, `value`, `onChange`, `placeholder`, `aria-*`). El componente vive en tu proyecto: instálalo con `create-lexy add input` y edítalo con libertad.",
7
+ "dependencies": [],
8
+ "registryDependencies": [],
9
+ "files": [
10
+ {
11
+ "path": "components/base/Input.tsx",
12
+ "type": "registry:ui",
13
+ "target": "components/base/Input.tsx",
14
+ "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded border border-input bg-background px-3 py-2 text-sm text-foreground outline-none transition-colors duration-150\",\n \"placeholder:text-muted-foreground\",\n \"file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground\",\n \"hover:border-muted-foreground/40\",\n \"focus-visible:border-primary\",\n \"aria-invalid:border-destructive aria-invalid:hover:border-destructive aria-invalid:focus-visible:border-destructive\",\n \"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input\",\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
15
+ },
16
+ {
17
+ "path": "components/base/Input.md",
18
+ "type": "registry:file",
19
+ "target": "components/base/Input.md",
20
+ "content": "# Input — guía de uso\n\nCampo de texto de una línea. Renderiza un `<input>` nativo; acepta todos sus atributos (`type`, `value`, `onChange`, `placeholder`, `aria-*`). El componente vive en tu proyecto: instálalo con `create-lexy add input` y edítalo con libertad.\n\n## Import\n\n```tsx\nimport { Input } from \"@/components/base/Input\";\n```\n\n## Uso básico\n\nSiempre con un `Label` asociado por `id`/`htmlFor`:\n\n```tsx\n<div className=\"grid gap-1.5\">\n <Label htmlFor=\"email\">Email</Label>\n <Input id=\"email\" type=\"email\" placeholder=\"nombre@empresa.cl\" />\n</div>\n```\n\n## Estado de error\n\n```tsx\n<div className=\"grid gap-1.5\">\n <Label htmlFor=\"rut\">RUT</Label>\n <Input id=\"rut\" aria-invalid aria-describedby=\"rut-error\" />\n <p id=\"rut-error\" className=\"text-sm text-destructive\">\n Ingresa un RUT válido, por ejemplo 12.345.678-9.\n </p>\n</div>\n```\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `type` | `string` | `\"text\"` | `text`, `email`, `password`, `number`, `search`, `file`, etc. |\n| `aria-invalid` | `boolean` | — | Activa el estilo de error (borde destructive). |\n| `disabled` | `boolean` | `false` | Deshabilita el campo. |\n| ...resto | `input` props | — | `value`, `onChange`, `placeholder`, `name`, `required`, etc. |\n\n## Reglas\n\n- **Siempre** asocia un `Label` por `htmlFor`/`id`. El `placeholder` no es un label: desaparece al escribir.\n- Marca el error con `aria-invalid` **y** un mensaje de texto cercano; el color del borde no basta (accesibilidad).\n- Enlaza el mensaje de error con `aria-describedby` para lectores de pantalla.\n- Usa el `type` correcto (`email`, `number`, `tel`): mejora el teclado móvil y la validación.\n\n## Cuándo NO usar\n\n- **Texto largo / multilínea** → `Textarea`.\n- **Elegir de una lista fija** → `Select` (o `Combobox` si hay búsqueda).\n- **Sí/no o selección múltiple** → `Checkbox` / `RadioGroup` / `Switch`.\n- **Buscar con icono y limpiar** → `Searchbox`.\n\n## Para IA\n\n1. Confirma que el dato es texto corto de una línea; si no, elige el control adecuado (ver arriba).\n2. Crea el par `Label` + `Input` con `htmlFor`/`id` coincidentes.\n3. Elige `type` según el dato.\n4. Para validación, usa `aria-invalid` + mensaje con `aria-describedby`, redactado como corregir (no solo \"campo inválido\").\n5. No uses el `placeholder` como única etiqueta.\n"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "intake-wizard",
4
+ "type": "registry:block",
5
+ "title": "intake-wizard",
6
+ "description": "Paso de wizard de intake para **mundo cliente**: un paso enfocado, columna angosta (`max-w-xl`), indicador de avance arriba y CTA al final. Instálalo con `create-lexy add intake-wizard` — trae consigo `Progress`, `Input`, `Label` y `Button`. La vista queda en tu proyecto (`views/IntakeDatosPersonales.tsx`): edítala con libertad.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "button",
10
+ "input",
11
+ "label",
12
+ "progress"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "views/IntakeDatosPersonales.tsx",
17
+ "type": "registry:file",
18
+ "target": "views/IntakeDatosPersonales.tsx",
19
+ "content": "import { Button } from \"@/components/base/Button\";\nimport { Input } from \"@/components/base/Input\";\nimport { Label } from \"@/components/base/Label\";\nimport { Progress } from \"@/components/base/Progress\";\n\n// Receta cliente — intake / ficha web (paso de wizard): un paso enfocado,\n// columna angosta, calma, CTA al final. Ver intake-wizard.md.\n\nexport function IntakeDatosPersonales() {\n return (\n <main className='mx-auto w-full max-w-xl px-4 py-10'>\n <div className='mb-8'>\n <p className='mb-2 text-sm text-muted-foreground'>Paso 2 de 4</p>\n <Progress value={50} aria-label='Avance del formulario' />\n </div>\n\n <header className='mb-6'>\n <h1 className='text-2xl font-bold text-foreground'>Cuéntanos tus datos</h1>\n <p className='mt-2 text-base text-muted-foreground'>\n Usaremos esta información para preparar la primera revisión de tu caso.\n </p>\n </header>\n\n <form className='space-y-5'>\n <fieldset className='space-y-5 border-0 p-0'>\n <legend className='sr-only'>Datos personales</legend>\n\n <div className='space-y-2'>\n <Label htmlFor='nombre'>Nombre completo</Label>\n <Input id='nombre' name='nombre' placeholder='Escribe tu nombre' />\n </div>\n\n <div className='space-y-2'>\n <Label htmlFor='correo'>Correo</Label>\n <Input id='correo' name='correo' type='email' placeholder='tu@correo.cl' />\n <p className='text-xs text-muted-foreground'>\n Te escribiremos solo para avanzar con tu caso.\n </p>\n </div>\n </fieldset>\n\n <div className='flex items-center justify-between pt-2'>\n <Button type='button' variant='ghost'>Volver</Button>\n <Button type='submit'>Guardar y continuar</Button>\n </div>\n </form>\n </main>\n );\n}\n"
20
+ },
21
+ {
22
+ "path": "views/intake-wizard.md",
23
+ "type": "registry:file",
24
+ "target": "views/intake-wizard.md",
25
+ "content": "# intake-wizard — block\n\nPaso de wizard de intake para **mundo cliente**: un paso enfocado, columna angosta (`max-w-xl`), indicador de avance arriba y CTA al final. Instálalo con `create-lexy add intake-wizard` — trae consigo `Progress`, `Input`, `Label` y `Button`. La vista queda en tu proyecto (`views/IntakeDatosPersonales.tsx`): edítala con libertad.\n\n## Montaje\n\nEs una vista: conéctala a tu router y duplica el archivo por cada paso del wizard (un paso = una pantalla).\n\n```tsx\nimport { IntakeDatosPersonales } from \"@/views/IntakeDatosPersonales\";\n```\n\n## Adaptación\n\n- Cambia `Paso 2 de 4` y `value` del `Progress` según el paso real.\n- Para validación, instala `form` (`create-lexy add form`) y envuelve los campos con `FormField`.\n- El botón \"Volver\" es `ghost` y va a la izquierda; el primario siempre a la derecha.\n\n## Evita\n\nHero comercial, eyebrow decorativo, una card por campo, y mostrar los 4 pasos en una sola pantalla.\n"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "label",
4
+ "type": "registry:ui",
5
+ "title": "Label",
6
+ "description": "Etiqueta 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.",
7
+ "dependencies": [
8
+ "@radix-ui/react-label",
9
+ "class-variance-authority"
10
+ ],
11
+ "registryDependencies": [],
12
+ "files": [
13
+ {
14
+ "path": "components/base/Label.tsx",
15
+ "type": "registry:ui",
16
+ "target": "components/base/Label.tsx",
17
+ "content": "import * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst labelVariants = cva(\n \"text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n);\n\nconst Label = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n VariantProps<typeof labelVariants> & {\n required?: boolean;\n }\n>(({ className, required, children, ...props }, ref) => (\n <LabelPrimitive.Root\n ref={ref}\n className={cn(labelVariants(), className)}\n {...props}\n >\n {children}\n {required && (\n <span aria-hidden=\"true\" className=\"ml-0.5 text-destructive\">\n *\n </span>\n )}\n </LabelPrimitive.Root>\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label, labelVariants };\n"
18
+ },
19
+ {
20
+ "path": "components/base/Label.md",
21
+ "type": "registry:file",
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## Import\n\n```tsx\nimport { Label } from \"@/components/base/Label\";\n```\n\n## Uso básico\n\n```tsx\n<Label />\n```\n\n## Props\n\nConsulta la story en Storybook para ver las props disponibles.\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## 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
+ }
25
+ ]
26
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "login",
4
+ "type": "registry:block",
5
+ "title": "login",
6
+ "description": "Pantalla 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.",
7
+ "dependencies": [],
8
+ "registryDependencies": [
9
+ "button",
10
+ "card",
11
+ "input",
12
+ "label"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "views/Login.tsx",
17
+ "type": "registry:file",
18
+ "target": "views/Login.tsx",
19
+ "content": "import { Button } from \"@/components/base/Button\";\nimport { Card, CardHeader, CardTitle, CardDescription, CardContent } 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-4'>\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'>Entrar</Button>\n </form>\n </CardContent>\n </Card>\n </main>\n );\n}\n"
20
+ },
21
+ {
22
+ "path": "views/login.md",
23
+ "type": "registry:file",
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"
26
+ }
27
+ ]
28
+ }