create-lexy 0.2.0 → 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.
- package/assets/r/accordion.json +1 -0
- package/assets/r/alert-dialog.json +28 -0
- package/assets/r/app-accordion.json +1 -0
- package/assets/r/app-dialog.json +1 -0
- package/assets/r/app-header-bar.json +1 -0
- package/assets/r/app-sidebar.json +1 -0
- package/assets/r/avatar.json +1 -0
- package/assets/r/badge.json +1 -0
- package/assets/r/breadcrumb.json +1 -0
- package/assets/r/button-group.json +1 -0
- package/assets/r/button.json +1 -0
- package/assets/r/calendar.json +1 -0
- package/assets/r/card.json +1 -0
- package/assets/r/chart.json +1 -0
- package/assets/r/checkbox.json +1 -0
- package/assets/r/combobox.json +1 -0
- package/assets/r/command.json +1 -0
- package/assets/r/confirmacion.json +27 -0
- package/assets/r/counter-badge.json +1 -0
- package/assets/r/crm-app-layout.json +29 -0
- package/assets/r/crm-desk.json +31 -0
- package/assets/r/crm-detalle-caso.json +28 -0
- package/assets/r/date-picker.json +1 -0
- package/assets/r/dialog.json +1 -0
- package/assets/r/dropdown-menu.json +1 -0
- package/assets/r/empty.json +1 -0
- package/assets/r/feature-card.json +1 -0
- package/assets/r/form.json +30 -0
- package/assets/r/header-bar.json +1 -0
- package/assets/r/input.json +1 -0
- package/assets/r/intake-wizard.json +28 -0
- package/assets/r/label.json +1 -0
- package/assets/r/login.json +28 -0
- package/assets/r/logo.json +1 -0
- package/assets/r/menubar.json +1 -0
- package/assets/r/navigation-menu.json +1 -0
- package/assets/r/pagination.json +1 -0
- package/assets/r/popover.json +1 -0
- package/assets/r/profile-card.json +1 -0
- package/assets/r/progress.json +1 -0
- package/assets/r/radio-group.json +1 -0
- package/assets/r/registry.json +329 -51
- package/assets/r/scroll-area.json +1 -0
- package/assets/r/searchbox.json +1 -0
- package/assets/r/select.json +1 -0
- package/assets/r/separator.json +1 -0
- package/assets/r/sheet.json +1 -0
- package/assets/r/sidebar.json +1 -0
- package/assets/r/skeleton.json +1 -0
- package/assets/r/slider.json +1 -0
- package/assets/r/snippet.json +1 -0
- package/assets/r/spinner.json +1 -0
- package/assets/r/status-dot.json +1 -0
- package/assets/r/switch.json +1 -0
- package/assets/r/table.json +1 -0
- package/assets/r/tabs.json +1 -0
- package/assets/r/tag.json +1 -0
- package/assets/r/textarea.json +1 -0
- package/assets/r/toaster.json +25 -0
- package/assets/r/tooltip.json +1 -0
- package/assets/r/tree.json +1 -0
- package/dist/index.js +26 -25
- package/package.json +1 -1
package/assets/r/empty.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"path": "components/base/Empty.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
|
+
"target": "components/base/Empty.tsx",
|
|
15
16
|
"content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nfunction Empty({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty\"\n className={cn(\n \"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-lg border-dashed p-12 text-center text-balance\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction EmptyHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-header\"\n className={cn(\"flex max-w-sm flex-col items-center gap-2\", className)}\n {...props}\n />\n );\n}\n\nconst emptyMediaVariants = cva(\n \"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n icon: \"flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\nfunction EmptyMedia({\n className,\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof emptyMediaVariants>) {\n return (\n <div\n data-slot=\"empty-icon\"\n data-variant={variant}\n className={cn(emptyMediaVariants({ variant, className }))}\n {...props}\n />\n );\n}\n\nfunction EmptyTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-title\"\n className={cn(\n \"font-sans text-lg font-medium tracking-tight\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction EmptyDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <div\n data-slot=\"empty-description\"\n className={cn(\n \"text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction EmptyContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"empty-content\"\n className={cn(\n \"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport {\n Empty,\n EmptyHeader,\n EmptyTitle,\n EmptyDescription,\n EmptyContent,\n EmptyMedia,\n emptyMediaVariants,\n};\n"
|
|
16
17
|
},
|
|
17
18
|
{
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"path": "components/base/FeatureCard.tsx",
|
|
12
12
|
"type": "registry:ui",
|
|
13
|
+
"target": "components/base/FeatureCard.tsx",
|
|
13
14
|
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nexport interface FeatureCardProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"title\"> {\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Icono (cualquier nodo: SVG, lucide, etc.). */\n icon?: React.ReactNode;\n /** Si se define, renderiza como <a> con afordancia de hover/foco. */\n href?: string;\n target?: string;\n rel?: string;\n}\n\nconst cardClassName = (interactive: boolean) =>\n cn(\n \"group flex w-full flex-col gap-4 rounded-lg border border-border bg-background p-6 transition-[border-color,box-shadow] duration-150\",\n interactive &&\n \"cursor-pointer hover:border-primary/40 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n );\n\nfunction FeatureCardContent({\n title,\n description,\n icon,\n interactive,\n}: {\n title: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n interactive: boolean;\n}) {\n return (\n <>\n {(icon || interactive) && (\n <div className='flex items-start justify-between gap-3'>\n {icon ? (\n <span\n className='inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-accent text-primary [&>svg]:h-4 [&>svg]:w-4'\n aria-hidden>\n {icon}\n </span>\n ) : (\n <span className='min-w-0 flex-1' />\n )}\n {interactive && (\n <svg\n className='h-4 w-4 shrink-0 text-muted-foreground transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-foreground'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'\n aria-hidden>\n <path d='M7 7h10v10M7 17 17 7' />\n </svg>\n )}\n </div>\n )}\n <div className='flex flex-col gap-1'>\n <h3 className='text-sm font-medium text-foreground'>{title}</h3>\n {description != null && description !== \"\" && (\n <p className='text-xs leading-relaxed text-muted-foreground'>\n {description}\n </p>\n )}\n </div>\n </>\n );\n}\n\nexport const FeatureCard = React.forwardRef<HTMLElement, FeatureCardProps>(\n function FeatureCard(\n { title, description, icon, href, target, rel, className, ...props },\n ref,\n ) {\n const interactive = href != null && href !== \"\";\n const classes = cn(cardClassName(interactive), className);\n\n if (interactive) {\n const resolvedRel =\n rel ?? (target === \"_blank\" ? \"noopener noreferrer\" : undefined);\n return (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={resolvedRel}\n className={classes}\n {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}>\n <FeatureCardContent\n title={title}\n description={description}\n icon={icon}\n interactive\n />\n </a>\n );\n }\n\n return (\n <article\n ref={ref as React.Ref<HTMLElement>}\n className={classes}\n {...(props as React.HTMLAttributes<HTMLElement>)}>\n <FeatureCardContent\n title={title}\n description={description}\n icon={icon}\n interactive={false}\n />\n </article>\n );\n },\n);\n"
|
|
14
15
|
},
|
|
15
16
|
{
|
|
@@ -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
|
+
}
|
package/assets/r/header-bar.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
"path": "components/base/HeaderBar.tsx",
|
|
16
16
|
"type": "registry:ui",
|
|
17
|
+
"target": "components/base/HeaderBar.tsx",
|
|
17
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"
|
|
18
19
|
},
|
|
19
20
|
{
|
package/assets/r/input.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"path": "components/base/Input.tsx",
|
|
12
12
|
"type": "registry:ui",
|
|
13
|
+
"target": "components/base/Input.tsx",
|
|
13
14
|
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Input = React.forwardRef<HTMLInputElement, React.ComponentProps<\"input\">>(\n ({ className, type, ...props }, ref) => {\n return (\n <input\n type={type}\n className={cn(\n \"flex h-10 w-full rounded border border-input bg-background px-3 py-2 text-sm text-foreground outline-none transition-colors duration-150\",\n \"placeholder:text-muted-foreground\",\n \"file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground\",\n \"hover:border-muted-foreground/40\",\n \"focus-visible:border-primary\",\n \"aria-invalid:border-destructive aria-invalid:hover:border-destructive aria-invalid:focus-visible:border-destructive\",\n \"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-input\",\n className,\n )}\n ref={ref}\n {...props}\n />\n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
|
|
14
15
|
},
|
|
15
16
|
{
|
|
@@ -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
|
+
}
|
package/assets/r/label.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"path": "components/base/Label.tsx",
|
|
15
15
|
"type": "registry:ui",
|
|
16
|
+
"target": "components/base/Label.tsx",
|
|
16
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"
|
|
17
18
|
},
|
|
18
19
|
{
|
|
@@ -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
|
+
}
|
package/assets/r/logo.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"path": "components/base/Logo.tsx",
|
|
12
12
|
"type": "registry:ui",
|
|
13
|
+
"target": "components/base/Logo.tsx",
|
|
13
14
|
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nimport lexyDarkHorizontal from \"./assets/logo/lexy-dark-horizontal.svg\";\nimport lexyDarkHorizontalSlogan from \"./assets/logo/lexy-dark-horizontal-slogan.svg\";\nimport lexyDarkVertical from \"./assets/logo/lexy-dark-vertical.svg\";\nimport lexyDarkVerticalSlogan from \"./assets/logo/lexy-dark-vertical-slogan.svg\";\nimport lexyDarkIsotipe from \"./assets/logo/lexy-dark-isotipe.svg\";\nimport lexyLightHorizontal from \"./assets/logo/lexy-light-horizontal.svg\";\nimport lexyLightHorizontalSlogan from \"./assets/logo/lexy-light-horizontal-slogan.svg\";\nimport lexyLightVertical from \"./assets/logo/lexy-light-vertical.svg\";\nimport lexyLightVerticalSlogan from \"./assets/logo/lexy-light-vertical-slogan.svg\";\nimport lexyLightIsotipe from \"./assets/logo/lexy-light-isotipe.svg\";\nimport saludDarkV1 from \"./assets/logo/lexysalud-dark-v1.svg\";\nimport saludDarkV2 from \"./assets/logo/lexysalud-dark-v2.svg\";\nimport saludLightV1 from \"./assets/logo/lexysalud-light-v1.svg\";\nimport saludLightV2 from \"./assets/logo/lexysalud-light-v2.svg\";\nimport deudorDarkV1 from \"./assets/logo/lexydeudor-dark-v1.svg\";\nimport deudorDarkV2 from \"./assets/logo/lexydeudor-dark-v2.svg\";\nimport deudorLightV1 from \"./assets/logo/lexydeudor-light-v1.svg\";\nimport deudorLightV2 from \"./assets/logo/lexydeudor-light-v2.svg\";\n\nexport type LogoBrand = \"lexy\" | \"salud\" | \"deudor\";\n/**\n * Color del fondo sobre el que va el logo:\n * - `light` (fondo claro) → logo OSCURO (navy). ← por defecto\n * - `dark` (fondo oscuro) → logo CLARO (blanco).\n */\nexport type LogoSurface = \"light\" | \"dark\";\nexport type LogoLayout = \"horizontal\" | \"vertical\" | \"isotipe\";\nexport type LogoVersion = \"v1\" | \"v2\";\n\n// Clave del mapa = el `surface` (fondo). El archivo `*-light-*` trae arte oscuro\n// (para fondo claro) y `*-dark-*` trae arte claro (para fondo oscuro).\nconst lexyMap = {\n light: {\n horizontal: { false: lexyLightHorizontal, true: lexyLightHorizontalSlogan },\n vertical: { false: lexyLightVertical, true: lexyLightVerticalSlogan },\n isotipe: { false: lexyLightIsotipe, true: lexyLightIsotipe },\n },\n dark: {\n horizontal: { false: lexyDarkHorizontal, true: lexyDarkHorizontalSlogan },\n vertical: { false: lexyDarkVertical, true: lexyDarkVerticalSlogan },\n isotipe: { false: lexyDarkIsotipe, true: lexyDarkIsotipe },\n },\n} as const;\n\nconst subBrandMap = {\n salud: {\n light: { v1: saludLightV1, v2: saludLightV2 },\n dark: { v1: saludDarkV1, v2: saludDarkV2 },\n },\n deudor: {\n light: { v1: deudorLightV1, v2: deudorLightV2 },\n dark: { v1: deudorDarkV1, v2: deudorDarkV2 },\n },\n} as const;\n\nexport interface LogoProps\n extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, \"src\"> {\n brand?: LogoBrand;\n /** Fondo sobre el que va el logo. `light` (def.) = logo oscuro; `dark` = logo claro. */\n surface?: LogoSurface;\n /** Solo Lexy. */\n layout?: LogoLayout;\n /** Solo Lexy. */\n slogan?: boolean;\n /** Solo sub-marcas (salud / deudor). */\n version?: LogoVersion;\n}\n\nexport const Logo = React.forwardRef<HTMLImageElement, LogoProps>(function Logo(\n {\n brand = \"lexy\",\n surface = \"light\",\n layout = \"horizontal\",\n slogan = false,\n version = \"v1\",\n alt,\n className,\n draggable = false,\n ...props\n },\n ref,\n) {\n let src: string;\n let label: string;\n\n if (brand === \"lexy\") {\n src = lexyMap[surface][layout][slogan ? \"true\" : \"false\"];\n label = \"Lexy\";\n } else {\n src = subBrandMap[brand][surface][version];\n label = brand === \"salud\" ? \"Lexy Salud\" : \"Lexy Deudor\";\n }\n\n return (\n <img\n ref={ref}\n src={src}\n alt={alt ?? label}\n draggable={draggable}\n className={cn(\"block h-8 w-auto max-w-full select-none\", className)}\n {...props}\n />\n );\n});\n\nLogo.displayName = \"Logo\";\n"
|
|
14
15
|
},
|
|
15
16
|
{
|
package/assets/r/menubar.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"path": "components/base/Menubar.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
|
+
"target": "components/base/Menubar.tsx",
|
|
15
16
|
"content": "import * as React from \"react\";\nimport * as MenubarPrimitive from \"@radix-ui/react-menubar\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst MenubarMenu = MenubarPrimitive.Menu;\nconst MenubarGroup = MenubarPrimitive.Group;\nconst MenubarPortal = MenubarPrimitive.Portal;\nconst MenubarSub = MenubarPrimitive.Sub;\nconst MenubarRadioGroup = MenubarPrimitive.RadioGroup;\n\nconst ChevronRightIcon = () => (\n <svg\n className='ml-auto h-4 w-4'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'>\n <path d='m9 18 6-6-6-6' />\n </svg>\n);\n\nconst CheckIcon = () => (\n <svg\n className='h-4 w-4'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'>\n <path d='M20 6 9 17l-5-5' />\n </svg>\n);\n\nconst DotIcon = () => (\n <svg className='h-2 w-2 fill-current' viewBox='0 0 24 24'>\n <circle cx='12' cy='12' r='12' />\n </svg>\n);\n\nconst Menubar = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <MenubarPrimitive.Root\n ref={ref}\n className={cn(\n \"flex h-10 items-center gap-1 rounded border border-border bg-background p-1\",\n className,\n )}\n {...props}\n />\n));\nMenubar.displayName = MenubarPrimitive.Root.displayName;\n\nconst MenubarTrigger = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n <MenubarPrimitive.Trigger\n ref={ref}\n className={cn(\n \"flex cursor-pointer select-none items-center rounded px-3 py-1.5 text-sm font-medium text-foreground outline-none focus:bg-primary/15 focus:text-primary data-[state=open]:bg-primary/15 data-[state=open]:text-primary\",\n className,\n )}\n {...props}\n />\n));\nMenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;\n\nconst MenubarSubTrigger = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {\n inset?: boolean;\n }\n>(({ className, inset, children, ...props }, ref) => (\n <MenubarPrimitive.SubTrigger\n ref={ref}\n className={cn(\n \"flex cursor-pointer select-none items-center rounded px-2 py-1.5 text-sm outline-none focus:bg-primary/15 focus:text-primary data-[state=open]:bg-primary/15 data-[state=open]:text-primary\",\n inset && \"pl-8\",\n className,\n )}\n {...props}>\n {children}\n <ChevronRightIcon />\n </MenubarPrimitive.SubTrigger>\n));\nMenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;\n\nconst MenubarSubContent = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n <MenubarPrimitive.SubContent\n ref={ref}\n className={cn(\n \"z-50 min-w-32 overflow-hidden rounded border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n className,\n )}\n {...props}\n />\n));\nMenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;\n\nconst MenubarContent = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>\n>(\n (\n { className, align = \"start\", alignOffset = -4, sideOffset = 8, ...props },\n ref,\n ) => (\n <MenubarPortal>\n <MenubarPrimitive.Content\n ref={ref}\n align={align}\n alignOffset={alignOffset}\n sideOffset={sideOffset}\n className={cn(\n \"z-50 min-w-48 overflow-hidden rounded border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n className,\n )}\n {...props}\n />\n </MenubarPortal>\n ),\n);\nMenubarContent.displayName = MenubarPrimitive.Content.displayName;\n\nconst MenubarItem = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <MenubarPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded px-2 py-1.5 text-sm outline-none focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n));\nMenubarItem.displayName = MenubarPrimitive.Item.displayName;\n\nconst MenubarCheckboxItem = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <MenubarPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n checked={checked}\n {...props}>\n <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>\n <MenubarPrimitive.ItemIndicator>\n <CheckIcon />\n </MenubarPrimitive.ItemIndicator>\n </span>\n {children}\n </MenubarPrimitive.CheckboxItem>\n));\nMenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;\n\nconst MenubarRadioItem = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <MenubarPrimitive.RadioItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-primary/15 focus:text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n )}\n {...props}>\n <span className='absolute left-2 flex h-3.5 w-3.5 items-center justify-center'>\n <MenubarPrimitive.ItemIndicator>\n <DotIcon />\n </MenubarPrimitive.ItemIndicator>\n </span>\n {children}\n </MenubarPrimitive.RadioItem>\n));\nMenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;\n\nconst MenubarLabel = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {\n inset?: boolean;\n }\n>(({ className, inset, ...props }, ref) => (\n <MenubarPrimitive.Label\n ref={ref}\n className={cn(\n \"px-2 py-1.5 text-sm font-semibold text-foreground\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n));\nMenubarLabel.displayName = MenubarPrimitive.Label.displayName;\n\nconst MenubarSeparator = React.forwardRef<\n React.ElementRef<typeof MenubarPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <MenubarPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n));\nMenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;\n\nconst MenubarShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => (\n <span\n className={cn(\n \"ml-auto text-xs tracking-widest text-muted-foreground\",\n className,\n )}\n {...props}\n />\n);\nMenubarShortcut.displayName = \"MenubarShortcut\";\n\nexport {\n Menubar,\n MenubarMenu,\n MenubarTrigger,\n MenubarContent,\n MenubarItem,\n MenubarSeparator,\n MenubarLabel,\n MenubarCheckboxItem,\n MenubarRadioGroup,\n MenubarRadioItem,\n MenubarPortal,\n MenubarSubContent,\n MenubarSubTrigger,\n MenubarGroup,\n MenubarSub,\n MenubarShortcut,\n};\n"
|
|
16
17
|
},
|
|
17
18
|
{
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"path": "components/base/NavigationMenu.tsx",
|
|
15
15
|
"type": "registry:ui",
|
|
16
|
+
"target": "components/base/NavigationMenu.tsx",
|
|
16
17
|
"content": "import * as React from \"react\";\nimport * as NavigationMenuPrimitive from \"@radix-ui/react-navigation-menu\";\nimport { cva } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst NavigationMenu = React.forwardRef<\n React.ElementRef<typeof NavigationMenuPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>\n>(({ className, children, ...props }, ref) => (\n <NavigationMenuPrimitive.Root\n ref={ref}\n className={cn(\n \"relative z-10 flex max-w-max flex-1 items-center justify-center\",\n className,\n )}\n {...props}>\n {children}\n <NavigationMenuViewport />\n </NavigationMenuPrimitive.Root>\n));\nNavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;\n\nconst NavigationMenuList = React.forwardRef<\n React.ElementRef<typeof NavigationMenuPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>\n>(({ className, ...props }, ref) => (\n <NavigationMenuPrimitive.List\n ref={ref}\n className={cn(\n \"group flex flex-1 list-none items-center justify-center gap-1\",\n className,\n )}\n {...props}\n />\n));\nNavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;\n\nconst NavigationMenuItem = NavigationMenuPrimitive.Item;\n\nconst navigationMenuTriggerStyle = cva(\n \"group inline-flex h-10 w-max cursor-pointer items-center justify-center rounded bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors hover:bg-primary/15 hover:text-primary focus:bg-primary/15 focus:text-primary focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-secondary data-[state=open]:bg-secondary\",\n);\n\nconst NavigationMenuTrigger = React.forwardRef<\n React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>\n>(({ className, children, onPointerMove, onPointerLeave, ...props }, ref) => (\n <NavigationMenuPrimitive.Trigger\n ref={ref}\n className={cn(navigationMenuTriggerStyle(), \"group\", className)}\n // Desactiva la apertura por hover para evitar el conflicto hover/click de\n // Radix (el menú se cerraba al reabrirlo tras un click). Queda controlado\n // por click y teclado. Radix omite su handler si el evento está prevenido.\n onPointerMove={(event) => {\n event.preventDefault();\n onPointerMove?.(event);\n }}\n onPointerLeave={(event) => {\n event.preventDefault();\n onPointerLeave?.(event);\n }}\n {...props}>\n {children}{\" \"}\n <svg\n className='relative top-px ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180'\n viewBox='0 0 24 24'\n fill='none'\n stroke='currentColor'\n strokeWidth='2'\n strokeLinecap='round'\n strokeLinejoin='round'\n aria-hidden='true'>\n <path d='m6 9 6 6 6-6' />\n </svg>\n </NavigationMenuPrimitive.Trigger>\n));\nNavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;\n\nconst NavigationMenuContent = React.forwardRef<\n React.ElementRef<typeof NavigationMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <NavigationMenuPrimitive.Content\n ref={ref}\n className={cn(\n \"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out md:absolute md:w-auto\",\n className,\n )}\n {...props}\n />\n));\nNavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;\n\nconst NavigationMenuLink = NavigationMenuPrimitive.Link;\n\nconst NavigationMenuViewport = React.forwardRef<\n React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>\n>(({ className, ...props }, ref) => (\n <div className={cn(\"absolute left-0 top-full flex justify-center\")}>\n <NavigationMenuPrimitive.Viewport\n ref={ref}\n className={cn(\n \"origin-top-center relative mt-2 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]\",\n className,\n )}\n {...props}\n />\n </div>\n));\nNavigationMenuViewport.displayName =\n NavigationMenuPrimitive.Viewport.displayName;\n\nconst NavigationMenuIndicator = React.forwardRef<\n React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,\n React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>\n>(({ className, ...props }, ref) => (\n <NavigationMenuPrimitive.Indicator\n ref={ref}\n className={cn(\n \"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in\",\n className,\n )}\n {...props}>\n <div className='relative top-[60%] h-2 w-2 rotate-45 rounded-tl bg-border shadow-md' />\n </NavigationMenuPrimitive.Indicator>\n));\nNavigationMenuIndicator.displayName =\n NavigationMenuPrimitive.Indicator.displayName;\n\nexport {\n navigationMenuTriggerStyle,\n NavigationMenu,\n NavigationMenuList,\n NavigationMenuItem,\n NavigationMenuContent,\n NavigationMenuTrigger,\n NavigationMenuLink,\n NavigationMenuIndicator,\n NavigationMenuViewport,\n};\n"
|
|
17
18
|
},
|
|
18
19
|
{
|
package/assets/r/pagination.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
{
|
|
16
16
|
"path": "components/base/Pagination.tsx",
|
|
17
17
|
"type": "registry:ui",
|
|
18
|
+
"target": "components/base/Pagination.tsx",
|
|
18
19
|
"content": "import * as React from \"react\";\n\nimport { type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils/cn\";\nimport { buttonVariants } from \"./Button\";\nimport { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from \"lucide-react\";\n\nfunction Pagination({ className, ...props }: React.ComponentProps<\"nav\">) {\n return (\n <nav\n role=\"navigation\"\n aria-label=\"pagination\"\n data-slot=\"pagination\"\n className={cn(\"mx-auto flex w-full justify-center\", className)}\n {...props}\n />\n );\n}\n\nfunction PaginationContent({\n className,\n ...props\n}: React.ComponentProps<\"ul\">) {\n return (\n <ul\n data-slot=\"pagination-content\"\n className={cn(\"flex items-center gap-1\", className)}\n {...props}\n />\n );\n}\n\nfunction PaginationItem({ ...props }: React.ComponentProps<\"li\">) {\n return <li data-slot=\"pagination-item\" {...props} />;\n}\n\ntype PaginationLinkProps = {\n isActive?: boolean;\n} & Pick<VariantProps<typeof buttonVariants>, \"size\"> &\n Omit<React.ComponentProps<\"a\">, \"size\">;\n\nfunction PaginationLink({\n className,\n isActive,\n size = \"icon\",\n ...props\n}: PaginationLinkProps) {\n return (\n <a\n aria-current={isActive ? \"page\" : undefined}\n data-slot=\"pagination-link\"\n data-active={isActive}\n className={cn(\n buttonVariants({\n variant: isActive ? \"outline\" : \"ghost\",\n size,\n }),\n \"cursor-pointer\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction PaginationPrevious({\n className,\n text = \"Anterior\",\n ...props\n}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {\n return (\n <PaginationLink\n aria-label=\"Ir a la página anterior\"\n size=\"default\"\n className={cn(\"pl-2!\", className)}\n {...props}\n >\n <ChevronLeftIcon data-icon=\"inline-start\" className=\"cn-rtl-flip\" />\n <span className=\"hidden sm:block\">{text}</span>\n </PaginationLink>\n );\n}\n\nfunction PaginationNext({\n className,\n text = \"Siguiente\",\n ...props\n}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {\n return (\n <PaginationLink\n aria-label=\"Ir a la página siguiente\"\n size=\"default\"\n className={cn(\"pr-2!\", className)}\n {...props}\n >\n <span className=\"hidden sm:block\">{text}</span>\n <ChevronRightIcon data-icon=\"inline-end\" className=\"cn-rtl-flip\" />\n </PaginationLink>\n );\n}\n\nfunction PaginationEllipsis({\n className,\n ...props\n}: React.ComponentProps<\"span\">) {\n return (\n <span\n aria-hidden\n data-slot=\"pagination-ellipsis\"\n className={cn(\n \"flex size-9 items-center justify-center [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <MoreHorizontalIcon />\n <span className=\"sr-only\">Más páginas</span>\n </span>\n );\n}\n\nexport {\n Pagination,\n PaginationContent,\n PaginationEllipsis,\n PaginationItem,\n PaginationLink,\n PaginationNext,\n PaginationPrevious,\n};\n"
|
|
19
20
|
},
|
|
20
21
|
{
|
package/assets/r/popover.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"path": "components/base/Popover.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
|
+
"target": "components/base/Popover.tsx",
|
|
15
16
|
"content": "import * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Popover = PopoverPrimitive.Root;\nconst PopoverTrigger = PopoverPrimitive.Trigger;\nconst PopoverAnchor = PopoverPrimitive.Anchor;\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = \"center\", sideOffset = 4, ...props }, ref) => (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n align={align}\n sideOffset={sideOffset}\n className={cn(\n \"z-50 w-72 rounded border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\",\n className,\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n));\nPopoverContent.displayName = PopoverPrimitive.Content.displayName;\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };\n"
|
|
16
17
|
},
|
|
17
18
|
{
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"path": "components/base/ProfileCard.tsx",
|
|
15
15
|
"type": "registry:ui",
|
|
16
|
+
"target": "components/base/ProfileCard.tsx",
|
|
16
17
|
"content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils/cn\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"./Avatar\";\nimport { Button } from \"./Button\";\n\nexport interface ProfileCardProps extends React.HTMLAttributes<HTMLDivElement> {\n name: string;\n role?: string;\n initials?: string;\n src?: string;\n primaryAction?: string;\n secondaryAction?: string;\n onPrimary?: () => void;\n onSecondary?: () => void;\n /** Oculta la fila de acciones cuando es `false`. */\n showActions?: boolean;\n}\n\nexport const ProfileCard = React.forwardRef<HTMLDivElement, ProfileCardProps>(\n (\n {\n name,\n role,\n initials,\n src,\n primaryAction = \"Ver perfil\",\n secondaryAction = \"Mensaje\",\n onPrimary,\n onSecondary,\n showActions = true,\n className,\n ...props\n },\n ref,\n ) => {\n return (\n <article\n ref={ref}\n className={cn(\n \"flex w-70 flex-col gap-4 rounded-lg border border-border bg-background p-6 transition-shadow hover:shadow-md\",\n className,\n )}\n {...props}>\n <Avatar className='h-16 w-16'>\n {src && <AvatarImage src={src} alt={name} />}\n <AvatarFallback>{initials ?? name.slice(0, 2)}</AvatarFallback>\n </Avatar>\n <div className='flex flex-col gap-0.5'>\n <h3 className='text-sm font-medium text-foreground'>{name}</h3>\n {role && <p className='text-xs text-muted-foreground'>{role}</p>}\n </div>\n {showActions && (\n <div className='flex gap-2'>\n <Button size='sm' onClick={onPrimary}>\n {primaryAction}\n </Button>\n <Button size='sm' variant='secondary' onClick={onSecondary}>\n {secondaryAction}\n </Button>\n </div>\n )}\n </article>\n );\n },\n);\n\nProfileCard.displayName = \"ProfileCard\";\n"
|
|
17
18
|
},
|
|
18
19
|
{
|
package/assets/r/progress.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"path": "components/base/Progress.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
|
+
"target": "components/base/Progress.tsx",
|
|
15
16
|
"content": "import * as React from \"react\";\nimport * as ProgressPrimitive from \"@radix-ui/react-progress\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst Progress = React.forwardRef<\n React.ElementRef<typeof ProgressPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>\n>(({ className, value, ...props }, ref) => (\n <ProgressPrimitive.Root\n ref={ref}\n className={cn(\n \"relative h-2 w-full overflow-hidden rounded-full bg-secondary\",\n className,\n )}\n {...props}>\n <ProgressPrimitive.Indicator\n className='h-full w-full flex-1 bg-primary transition-all'\n style={{ transform: `translateX(-${100 - (value || 0)}%)` }}\n />\n </ProgressPrimitive.Root>\n));\nProgress.displayName = ProgressPrimitive.Root.displayName;\n\nexport { Progress };\n"
|
|
16
17
|
},
|
|
17
18
|
{
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
"path": "components/base/RadioGroup.tsx",
|
|
14
14
|
"type": "registry:ui",
|
|
15
|
+
"target": "components/base/RadioGroup.tsx",
|
|
15
16
|
"content": "import * as React from \"react\";\nimport * as RadioGroupPrimitive from \"@radix-ui/react-radio-group\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst RadioGroup = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Root\n ref={ref}\n className={cn(\"grid gap-2\", className)}\n {...props}\n />\n));\nRadioGroup.displayName = RadioGroupPrimitive.Root.displayName;\n\nconst RadioGroupItem = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Item\n ref={ref}\n className={cn(\n \"aspect-square h-4 w-4 cursor-pointer rounded-full border border-primary text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n {...props}>\n <RadioGroupPrimitive.Indicator className='flex items-center justify-center'>\n <span className='h-2.5 w-2.5 rounded-full bg-current' />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n));\nRadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;\n\nexport { RadioGroup, RadioGroupItem };\n"
|
|
16
17
|
},
|
|
17
18
|
{
|