@eduardoalvarez/arrecife 0.4.0 → 0.5.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/dist/index.d.cts CHANGED
@@ -32,16 +32,25 @@ import '@radix-ui/react-label';
32
32
  * El plegable. Lo pedían dos proyectos: el FAQ del portafolio y el temario de
33
33
  * cursos, que es literalmente una lista de secciones que se abren.
34
34
  *
35
- * NO se anima la altura, que es lo primero que hace todo el mundo con este
36
- * componente. Radix publica `--radix-accordion-content-height` justo para eso y
37
- * aquí no se usa: el sistema no anima posición ni tamaño, y un panel que crece
38
- * a 200ms es exactamente el desplazamiento que las tarjetas, los menús y los
39
- * modales ya evitan. El panel aparece donde va a quedarse.
35
+ * La altura SÍ se anima, y es la cuarta excepción declarada del sistema.
40
36
  *
41
- * Lo único que se mueve es el galón, y se mueve porque `rotate` bajo
42
- * `transition-standard` NO transiciona: la utilidad solo cubre color y borde,
43
- * así que el giro es instantáneo aunque la clase esté puesta. Es el mismo trato
44
- * que recibe el ancho de `Progress`.
37
+ * Merece explicarse, porque la regla general es la contraria y este componente
38
+ * nació sin animar citándola. La diferencia es que aquí no APARECE nada: se
39
+ * abre un hueco, y todo lo que hay debajo del acordeón se desplaza. Sin
40
+ * transición ese desplazamiento es un salto, y quien acaba de pulsar pierde el
41
+ * sitio en la página — que es justo el daño que la regla «nada de movimiento»
42
+ * existe para evitar. Es la misma categoría que el panel lateral, la segunda
43
+ * excepción, y no la de una animación de entrada.
44
+ *
45
+ * Va detrás de `motion-safe`, dura `--duration-standard` y usa
46
+ * `--ease-standard`, así que no estrena un tiempo ni una curva. Quien pidió
47
+ * menos movimiento sigue viendo el panel aparecer donde va a quedarse.
48
+ *
49
+ * Ver `docs/decisiones.md` § 20.
50
+ *
51
+ * El galón, en cambio, gira sin transición: `transition-standard` solo cubre
52
+ * color y borde, así que `rotate` salta aunque la clase esté puesta. Es el mismo
53
+ * trato que recibe el ancho de `Progress`.
45
54
  *
46
55
  * La división entre items es `hairline`, no `border`: es una separación de
47
56
  * lectura, no el borde de un control.
@@ -183,6 +192,40 @@ declare function Avatar({ className, size, ...props }: AvatarProps): react.JSX.E
183
192
  declare function AvatarImage({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>): react.JSX.Element;
184
193
  /** Iniciales mientras la imagen carga, o cuando no hay imagen. */
185
194
  declare function AvatarFallback({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>): react.JSX.Element;
195
+ type AvatarUploadProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & VariantProps<typeof avatar> & {
196
+ /** La imagen actual, ya subida. La previsualización local la gana mientras dure. */
197
+ src?: string | undefined;
198
+ /** Iniciales mientras no hay imagen. */
199
+ fallback?: ReactNode;
200
+ /** Se dispara con el archivo elegido. La subida la hace el proyecto. */
201
+ onSelectFile?: ((archivo: File) => void) | undefined;
202
+ /** Qué acepta el diálogo del sistema. */
203
+ accept?: string;
204
+ /** Nombre accesible del control. Es lo único que lo nombra: no hay texto visible. */
205
+ label?: string;
206
+ disabled?: boolean | undefined;
207
+ };
208
+ /**
209
+ * El avatar que se puede cambiar. `Avatar` muestra; este además deja elegir.
210
+ *
211
+ * Es presentacional, como `NewsletterForm`: emite `onSelectFile` con el `File` y
212
+ * ahí se acaba su trabajo. La subida no entra —cada proyecto tiene su
213
+ * almacenamiento y su endpoint— y un componente que hiciera el `POST` sería
214
+ * infraestructura, que es el tercer criterio de entrada y el que más se salta.
215
+ *
216
+ * La previsualización es LOCAL y no espera a que la subida termine. Es la
217
+ * diferencia entre un control que responde y uno que parece roto: entre elegir
218
+ * el archivo y que el servidor devuelva la URL pueden pasar segundos, y sin
219
+ * previa el avatar se queda con la foto vieja como si no hubiera pasado nada.
220
+ * El `objectURL` se revoca al cambiar y al desmontar; no revocarlo es una fuga
221
+ * de memoria que no da la cara hasta la décima foto.
222
+ *
223
+ * El control es un `<label>` con un `<input type="file">` oculto dentro, no un
224
+ * `<button>` que dispara un click sintético. El input real trae el diálogo del
225
+ * sistema, el arrastrar-y-soltar del navegador y el foco por teclado; el botón
226
+ * falso hay que reconstruirlo entero y siempre falta algo.
227
+ */
228
+ declare function AvatarUpload({ src, fallback, onSelectFile, accept, label, size, disabled, className, ...props }: AvatarUploadProps): react.JSX.Element;
186
229
 
187
230
  /**
188
231
  * TRES familias de etiqueta, no una.
@@ -560,17 +603,11 @@ type TextareaProps = ComponentPropsWithoutRef<'textarea'> & {
560
603
  };
561
604
  declare function Textarea({ className, invalid, ...props }: TextareaProps): react.JSX.Element;
562
605
 
563
- declare const ToastProvider: react.FC<ToastPrimitive.ToastProviderProps>;
564
606
  declare const ToastAction: react.ForwardRefExoticComponent<ToastPrimitive.ToastActionProps & react.RefAttributes<HTMLButtonElement>>;
565
- declare function ToastViewport({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Viewport>): react.JSX.Element;
566
607
  declare const toast$1: (props?: ({
567
608
  variant?: "error" | "success" | "neutral" | null | undefined;
568
609
  } & class_variance_authority_types.ClassProp) | undefined) => string;
569
610
  type ToastProps = ComponentPropsWithoutRef<typeof ToastPrimitive.Root> & VariantProps<typeof toast$1>;
570
- /** Sin deslizamiento de entrada: el aviso aparece donde va a quedarse. */
571
- declare function Toast({ className, variant, children, ...props }: ToastProps): react.JSX.Element;
572
- declare function ToastTitle({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Title>): react.JSX.Element;
573
- declare function ToastDescription({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Description>): react.JSX.Element;
574
611
 
575
612
  /**
576
613
  * La cara imperativa del `Toast` de Radix: `toast('Guardado')` desde cualquier
@@ -870,6 +907,53 @@ type EmptyStateProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
870
907
  };
871
908
  declare function EmptyState({ expresion, title, description, action, basePath, className, ...props }: EmptyStateProps): react.JSX.Element;
872
909
 
910
+ /**
911
+ * El calendario con eventos: verlos, crearlos, editarlos y borrarlos.
912
+ *
913
+ * `Calendar` es la primitiva y sigue siendo una rejilla de fechas: sirve para
914
+ * ELEGIR un día y no sabe nada de contenido. Esto es la agenda, y por eso vive
915
+ * en `components/` y no al lado de aquella — tiene estado, tiene formulario y
916
+ * codifica cómo se ve un día con cosas dentro.
917
+ *
918
+ * Es presentacional, como el resto de la librería: recibe `events` y emite
919
+ * `onCreateEvent`, `onUpdateEvent` y `onDeleteEvent`. No guarda nada, no llama a
920
+ * ninguna API y no genera ids — el id lo pone quien persiste, porque es quien
921
+ * sabe si viene de una base de datos o de un fichero.
922
+ *
923
+ * DOS COLUMNAS y no un popover sobre el día. El popover es lo que hace todo el
924
+ * mundo y esconde el contenido detrás de un clic: con la lista al lado, un mes
925
+ * con quince eventos se lee de un vistazo y el día seleccionado no tapa la
926
+ * rejilla. En pantalla estrecha se apilan.
927
+ *
928
+ * El borrado pasa por `AlertDialog` y no por un botón directo. Es exactamente el
929
+ * caso para el que existe: una acción destructiva sin deshacer.
930
+ */
931
+ type EventoCalendario = {
932
+ /** Lo pone el proyecto. La librería nunca lo inventa. */
933
+ id: string;
934
+ /** Cuándo empieza, con hora. */
935
+ start: Date;
936
+ title: string;
937
+ /** `warm` cuando el evento es el problema, igual que en `Stat`. */
938
+ tone?: 'accent' | 'warm' | undefined;
939
+ };
940
+ type EventCalendarProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & {
941
+ events: readonly EventoCalendario[];
942
+ /** Sin él, la agenda es de solo lectura y no se pinta el formulario. */
943
+ onCreateEvent?: ((evento: Omit<EventoCalendario, 'id'>) => void) | undefined;
944
+ onUpdateEvent?: ((evento: EventoCalendario) => void) | undefined;
945
+ onDeleteEvent?: ((id: string) => void) | undefined;
946
+ /** Día seleccionado, si el proyecto lo controla. Sin él, empieza en hoy. */
947
+ selected?: Date | undefined;
948
+ onSelectDay?: ((dia: Date) => void) | undefined;
949
+ /** Encabezado del panel. Por defecto, `es` de date-fns, como `Calendar`. */
950
+ formatDay?: ((dia: Date) => string) | undefined;
951
+ formatTime?: ((fecha: Date) => string) | undefined;
952
+ /** Texto del panel cuando el día elegido no tiene nada. */
953
+ emptyMessage?: ReactNode;
954
+ };
955
+ declare function EventCalendar({ events, onCreateEvent, onUpdateEvent, onDeleteEvent, selected, onSelectDay, formatDay, formatTime, emptyMessage, className, ...props }: EventCalendarProps): react.JSX.Element;
956
+
873
957
  /**
874
958
  * El pie, y la firma CLI del sitio: `$ cd ~/eduardoalvarez.dev/2026`.
875
959
  *
@@ -981,11 +1065,17 @@ declare function LinkRow({ name, description, icon, external, className, ...prop
981
1065
  * Renderiza `<header>` colgando directamente del `body`, así que ES el landmark
982
1066
  * `banner` del sitio. Por eso `PageHeader` va dentro de `<main>` y no es
983
1067
  * landmark: dos banners en una página son un fallo de accesibilidad.
1068
+ *
1069
+ * Los items van a la DERECHA, pegados a las acciones, no a continuación de la
1070
+ * marca. Con la marca a la izquierda y los items justo detrás, el bloque de
1071
+ * navegación queda flotando en medio de la barra y el ojo tiene que cruzar el
1072
+ * hueco dos veces: una para leer la marca y otra para volver a buscar la
1073
+ * sección. Agrupados a la derecha, marca y navegación son dos anclas y no tres.
984
1074
  */
985
1075
  type NavProps = ComponentPropsWithoutRef<'header'> & {
986
1076
  /** El logo, a la izquierda. */
987
1077
  brand?: ReactNode;
988
- /** Los items de sección, en el centro. */
1078
+ /** Los items de sección. Van a la derecha, pegados a `actions`. */
989
1079
  children?: ReactNode;
990
1080
  /** Acciones a la derecha: conversión, cambio de tema, buscar. */
991
1081
  actions?: ReactNode;
@@ -1004,6 +1094,13 @@ type NavItemProps = ComponentPropsWithoutRef<'a'> & {
1004
1094
  * no una convención que haya que recordar en cinco proyectos. Va `aria-hidden`,
1005
1095
  * así que un lector de pantalla anuncia «artículos» y no «punto barra
1006
1096
  * artículos».
1097
+ *
1098
+ * La sección actual va entre CORCHETES además de en bioluz y subrayada. No es
1099
+ * decoración: el subrayado y el color son la misma señal —«esto destaca»— y en
1100
+ * una barra de seis items en mono, a 13px, esa señal se lee peor de lo que
1101
+ * parece en una maqueta. Los corchetes son la forma en que una terminal marca la
1102
+ * ruta activa, así que dicen «estás aquí» sin depender de que se distinga el
1103
+ * color. Van `aria-hidden`, porque quien escucha ya tiene `aria-current`.
1007
1104
  */
1008
1105
  declare function NavItem({ active, asChild, className, children, ...props }: NavItemProps): react.JSX.Element;
1009
1106
 
@@ -1176,6 +1273,11 @@ declare function SidebarNav({ title, version, branch, children, className, ...pr
1176
1273
  * es un dato; un 0 de design systems es el problema del que trata la charla.
1177
1274
  * Por eso `tone` no es una paleta abierta — son dos valores y significan cosas
1178
1275
  * distintas.
1276
+ *
1277
+ * El orden de lectura es icono + título, el número grande, y la bajada debajo.
1278
+ * El número va en MEDIO y no al final a propósito: es lo que se viene a leer, y
1279
+ * una bajada de dos líneas entre el título y la cifra la entierra. Arriba queda
1280
+ * de qué va, en medio cuánto, y abajo el matiz que solo lee quien se para.
1179
1281
  */
1180
1282
  type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
1181
1283
  /** El número, ya formateado. La librería no impone locale. */
@@ -1186,8 +1288,18 @@ type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
1186
1288
  tone?: 'neutral' | 'alerta';
1187
1289
  /** Con `progress`, la métrica se lee como avance y añade la barra. */
1188
1290
  progress?: number | undefined;
1291
+ /**
1292
+ * Glifo al lado del título, a 1em. Hereda `currentColor`, así que sigue al
1293
+ * tono del título y no hay que teñirlo aparte.
1294
+ */
1295
+ icon?: ReactNode;
1296
+ /**
1297
+ * La bajada: el matiz que el número solo no da. «12 aplicaciones» no dice si
1298
+ * son muchas, y aquí es donde se dice.
1299
+ */
1300
+ description?: ReactNode;
1189
1301
  };
1190
- declare function Stat({ value, label, tone, progress, className, ...props }: StatProps): react.JSX.Element;
1302
+ declare function Stat({ value, label, tone, progress, icon, description, className, ...props }: StatProps): react.JSX.Element;
1191
1303
 
1192
1304
  type TalkCardProps = Omit<TarjetaProps, 'children' | 'title'> & {
1193
1305
  title: ReactNode;
@@ -1324,4 +1436,4 @@ declare namespace social {
1324
1436
  export { social_Correo as Correo, social_Discord as Discord, social_GitHub as GitHub, social_Instagram as Instagram, social_LinkedIn as LinkedIn, social_Rss as Rss, social_X as X, social_YouTube as YouTube };
1325
1437
  }
1326
1438
 
1327
- export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type CalendarProps, Cara, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryBadge, type CategoryBadgeProps, Checkbox, type CheckboxProps, Code, CodeBlock, type CodeBlockProps, type CodeProps, CourseCard, type CourseCardProps, DateField, type DateFieldProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type Entrada, Footer, FooterLink, type FooterLinkProps, type FooterProps, HOVER_TARJETA, Hero, type HeroProps, Input, type InputProps, LinkRow, type LinkRowProps, MetricBadge, type MetricBadgeProps, type Migaja, Nav, NavItem, type NavItemProps, type NavProps, NewsletterForm, type NewsletterFormProps, type NewsletterState, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Pose, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, type Red, SUPERFICIE_TARJETA, ScrollingProgressBar, type ScrollingProgressBarProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarNavProps, Skeleton, type SkeletonProps, Stat, type StatProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, TalkCard, type TalkCardProps, Tema, Text, type TextProps, Textarea, type TextareaProps, ThemeToggle, type ThemeToggleProps, Toast, ToastAction, ToastDescription, type ToastOptions, type ToastProps, ToastProvider, ToastTitle, type ToastVariant, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, categoria as categoryBadgeVariants, cn, social, texto as textVariants, toast, useTema };
1439
+ export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, AvatarUpload, type AvatarUploadProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type CalendarProps, Cara, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryBadge, type CategoryBadgeProps, Checkbox, type CheckboxProps, Code, CodeBlock, type CodeBlockProps, type CodeProps, CourseCard, type CourseCardProps, DateField, type DateFieldProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type Entrada, EventCalendar, type EventCalendarProps, type EventoCalendario, Footer, FooterLink, type FooterLinkProps, type FooterProps, HOVER_TARJETA, Hero, type HeroProps, Input, type InputProps, LinkRow, type LinkRowProps, MetricBadge, type MetricBadgeProps, type Migaja, Nav, NavItem, type NavItemProps, type NavProps, NewsletterForm, type NewsletterFormProps, type NewsletterState, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Pose, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, type Red, SUPERFICIE_TARJETA, ScrollingProgressBar, type ScrollingProgressBarProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarNavProps, Skeleton, type SkeletonProps, Stat, type StatProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, TalkCard, type TalkCardProps, Tema, Text, type TextProps, Textarea, type TextareaProps, ThemeToggle, type ThemeToggleProps, ToastAction, type ToastOptions, type ToastVariant, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, categoria as categoryBadgeVariants, cn, social, texto as textVariants, toast, useTema };
package/dist/index.d.ts CHANGED
@@ -32,16 +32,25 @@ import '@radix-ui/react-label';
32
32
  * El plegable. Lo pedían dos proyectos: el FAQ del portafolio y el temario de
33
33
  * cursos, que es literalmente una lista de secciones que se abren.
34
34
  *
35
- * NO se anima la altura, que es lo primero que hace todo el mundo con este
36
- * componente. Radix publica `--radix-accordion-content-height` justo para eso y
37
- * aquí no se usa: el sistema no anima posición ni tamaño, y un panel que crece
38
- * a 200ms es exactamente el desplazamiento que las tarjetas, los menús y los
39
- * modales ya evitan. El panel aparece donde va a quedarse.
35
+ * La altura SÍ se anima, y es la cuarta excepción declarada del sistema.
40
36
  *
41
- * Lo único que se mueve es el galón, y se mueve porque `rotate` bajo
42
- * `transition-standard` NO transiciona: la utilidad solo cubre color y borde,
43
- * así que el giro es instantáneo aunque la clase esté puesta. Es el mismo trato
44
- * que recibe el ancho de `Progress`.
37
+ * Merece explicarse, porque la regla general es la contraria y este componente
38
+ * nació sin animar citándola. La diferencia es que aquí no APARECE nada: se
39
+ * abre un hueco, y todo lo que hay debajo del acordeón se desplaza. Sin
40
+ * transición ese desplazamiento es un salto, y quien acaba de pulsar pierde el
41
+ * sitio en la página — que es justo el daño que la regla «nada de movimiento»
42
+ * existe para evitar. Es la misma categoría que el panel lateral, la segunda
43
+ * excepción, y no la de una animación de entrada.
44
+ *
45
+ * Va detrás de `motion-safe`, dura `--duration-standard` y usa
46
+ * `--ease-standard`, así que no estrena un tiempo ni una curva. Quien pidió
47
+ * menos movimiento sigue viendo el panel aparecer donde va a quedarse.
48
+ *
49
+ * Ver `docs/decisiones.md` § 20.
50
+ *
51
+ * El galón, en cambio, gira sin transición: `transition-standard` solo cubre
52
+ * color y borde, así que `rotate` salta aunque la clase esté puesta. Es el mismo
53
+ * trato que recibe el ancho de `Progress`.
45
54
  *
46
55
  * La división entre items es `hairline`, no `border`: es una separación de
47
56
  * lectura, no el borde de un control.
@@ -183,6 +192,40 @@ declare function Avatar({ className, size, ...props }: AvatarProps): react.JSX.E
183
192
  declare function AvatarImage({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>): react.JSX.Element;
184
193
  /** Iniciales mientras la imagen carga, o cuando no hay imagen. */
185
194
  declare function AvatarFallback({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>): react.JSX.Element;
195
+ type AvatarUploadProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & VariantProps<typeof avatar> & {
196
+ /** La imagen actual, ya subida. La previsualización local la gana mientras dure. */
197
+ src?: string | undefined;
198
+ /** Iniciales mientras no hay imagen. */
199
+ fallback?: ReactNode;
200
+ /** Se dispara con el archivo elegido. La subida la hace el proyecto. */
201
+ onSelectFile?: ((archivo: File) => void) | undefined;
202
+ /** Qué acepta el diálogo del sistema. */
203
+ accept?: string;
204
+ /** Nombre accesible del control. Es lo único que lo nombra: no hay texto visible. */
205
+ label?: string;
206
+ disabled?: boolean | undefined;
207
+ };
208
+ /**
209
+ * El avatar que se puede cambiar. `Avatar` muestra; este además deja elegir.
210
+ *
211
+ * Es presentacional, como `NewsletterForm`: emite `onSelectFile` con el `File` y
212
+ * ahí se acaba su trabajo. La subida no entra —cada proyecto tiene su
213
+ * almacenamiento y su endpoint— y un componente que hiciera el `POST` sería
214
+ * infraestructura, que es el tercer criterio de entrada y el que más se salta.
215
+ *
216
+ * La previsualización es LOCAL y no espera a que la subida termine. Es la
217
+ * diferencia entre un control que responde y uno que parece roto: entre elegir
218
+ * el archivo y que el servidor devuelva la URL pueden pasar segundos, y sin
219
+ * previa el avatar se queda con la foto vieja como si no hubiera pasado nada.
220
+ * El `objectURL` se revoca al cambiar y al desmontar; no revocarlo es una fuga
221
+ * de memoria que no da la cara hasta la décima foto.
222
+ *
223
+ * El control es un `<label>` con un `<input type="file">` oculto dentro, no un
224
+ * `<button>` que dispara un click sintético. El input real trae el diálogo del
225
+ * sistema, el arrastrar-y-soltar del navegador y el foco por teclado; el botón
226
+ * falso hay que reconstruirlo entero y siempre falta algo.
227
+ */
228
+ declare function AvatarUpload({ src, fallback, onSelectFile, accept, label, size, disabled, className, ...props }: AvatarUploadProps): react.JSX.Element;
186
229
 
187
230
  /**
188
231
  * TRES familias de etiqueta, no una.
@@ -560,17 +603,11 @@ type TextareaProps = ComponentPropsWithoutRef<'textarea'> & {
560
603
  };
561
604
  declare function Textarea({ className, invalid, ...props }: TextareaProps): react.JSX.Element;
562
605
 
563
- declare const ToastProvider: react.FC<ToastPrimitive.ToastProviderProps>;
564
606
  declare const ToastAction: react.ForwardRefExoticComponent<ToastPrimitive.ToastActionProps & react.RefAttributes<HTMLButtonElement>>;
565
- declare function ToastViewport({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Viewport>): react.JSX.Element;
566
607
  declare const toast$1: (props?: ({
567
608
  variant?: "error" | "success" | "neutral" | null | undefined;
568
609
  } & class_variance_authority_types.ClassProp) | undefined) => string;
569
610
  type ToastProps = ComponentPropsWithoutRef<typeof ToastPrimitive.Root> & VariantProps<typeof toast$1>;
570
- /** Sin deslizamiento de entrada: el aviso aparece donde va a quedarse. */
571
- declare function Toast({ className, variant, children, ...props }: ToastProps): react.JSX.Element;
572
- declare function ToastTitle({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Title>): react.JSX.Element;
573
- declare function ToastDescription({ className, ...props }: ComponentPropsWithoutRef<typeof ToastPrimitive.Description>): react.JSX.Element;
574
611
 
575
612
  /**
576
613
  * La cara imperativa del `Toast` de Radix: `toast('Guardado')` desde cualquier
@@ -870,6 +907,53 @@ type EmptyStateProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
870
907
  };
871
908
  declare function EmptyState({ expresion, title, description, action, basePath, className, ...props }: EmptyStateProps): react.JSX.Element;
872
909
 
910
+ /**
911
+ * El calendario con eventos: verlos, crearlos, editarlos y borrarlos.
912
+ *
913
+ * `Calendar` es la primitiva y sigue siendo una rejilla de fechas: sirve para
914
+ * ELEGIR un día y no sabe nada de contenido. Esto es la agenda, y por eso vive
915
+ * en `components/` y no al lado de aquella — tiene estado, tiene formulario y
916
+ * codifica cómo se ve un día con cosas dentro.
917
+ *
918
+ * Es presentacional, como el resto de la librería: recibe `events` y emite
919
+ * `onCreateEvent`, `onUpdateEvent` y `onDeleteEvent`. No guarda nada, no llama a
920
+ * ninguna API y no genera ids — el id lo pone quien persiste, porque es quien
921
+ * sabe si viene de una base de datos o de un fichero.
922
+ *
923
+ * DOS COLUMNAS y no un popover sobre el día. El popover es lo que hace todo el
924
+ * mundo y esconde el contenido detrás de un clic: con la lista al lado, un mes
925
+ * con quince eventos se lee de un vistazo y el día seleccionado no tapa la
926
+ * rejilla. En pantalla estrecha se apilan.
927
+ *
928
+ * El borrado pasa por `AlertDialog` y no por un botón directo. Es exactamente el
929
+ * caso para el que existe: una acción destructiva sin deshacer.
930
+ */
931
+ type EventoCalendario = {
932
+ /** Lo pone el proyecto. La librería nunca lo inventa. */
933
+ id: string;
934
+ /** Cuándo empieza, con hora. */
935
+ start: Date;
936
+ title: string;
937
+ /** `warm` cuando el evento es el problema, igual que en `Stat`. */
938
+ tone?: 'accent' | 'warm' | undefined;
939
+ };
940
+ type EventCalendarProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & {
941
+ events: readonly EventoCalendario[];
942
+ /** Sin él, la agenda es de solo lectura y no se pinta el formulario. */
943
+ onCreateEvent?: ((evento: Omit<EventoCalendario, 'id'>) => void) | undefined;
944
+ onUpdateEvent?: ((evento: EventoCalendario) => void) | undefined;
945
+ onDeleteEvent?: ((id: string) => void) | undefined;
946
+ /** Día seleccionado, si el proyecto lo controla. Sin él, empieza en hoy. */
947
+ selected?: Date | undefined;
948
+ onSelectDay?: ((dia: Date) => void) | undefined;
949
+ /** Encabezado del panel. Por defecto, `es` de date-fns, como `Calendar`. */
950
+ formatDay?: ((dia: Date) => string) | undefined;
951
+ formatTime?: ((fecha: Date) => string) | undefined;
952
+ /** Texto del panel cuando el día elegido no tiene nada. */
953
+ emptyMessage?: ReactNode;
954
+ };
955
+ declare function EventCalendar({ events, onCreateEvent, onUpdateEvent, onDeleteEvent, selected, onSelectDay, formatDay, formatTime, emptyMessage, className, ...props }: EventCalendarProps): react.JSX.Element;
956
+
873
957
  /**
874
958
  * El pie, y la firma CLI del sitio: `$ cd ~/eduardoalvarez.dev/2026`.
875
959
  *
@@ -981,11 +1065,17 @@ declare function LinkRow({ name, description, icon, external, className, ...prop
981
1065
  * Renderiza `<header>` colgando directamente del `body`, así que ES el landmark
982
1066
  * `banner` del sitio. Por eso `PageHeader` va dentro de `<main>` y no es
983
1067
  * landmark: dos banners en una página son un fallo de accesibilidad.
1068
+ *
1069
+ * Los items van a la DERECHA, pegados a las acciones, no a continuación de la
1070
+ * marca. Con la marca a la izquierda y los items justo detrás, el bloque de
1071
+ * navegación queda flotando en medio de la barra y el ojo tiene que cruzar el
1072
+ * hueco dos veces: una para leer la marca y otra para volver a buscar la
1073
+ * sección. Agrupados a la derecha, marca y navegación son dos anclas y no tres.
984
1074
  */
985
1075
  type NavProps = ComponentPropsWithoutRef<'header'> & {
986
1076
  /** El logo, a la izquierda. */
987
1077
  brand?: ReactNode;
988
- /** Los items de sección, en el centro. */
1078
+ /** Los items de sección. Van a la derecha, pegados a `actions`. */
989
1079
  children?: ReactNode;
990
1080
  /** Acciones a la derecha: conversión, cambio de tema, buscar. */
991
1081
  actions?: ReactNode;
@@ -1004,6 +1094,13 @@ type NavItemProps = ComponentPropsWithoutRef<'a'> & {
1004
1094
  * no una convención que haya que recordar en cinco proyectos. Va `aria-hidden`,
1005
1095
  * así que un lector de pantalla anuncia «artículos» y no «punto barra
1006
1096
  * artículos».
1097
+ *
1098
+ * La sección actual va entre CORCHETES además de en bioluz y subrayada. No es
1099
+ * decoración: el subrayado y el color son la misma señal —«esto destaca»— y en
1100
+ * una barra de seis items en mono, a 13px, esa señal se lee peor de lo que
1101
+ * parece en una maqueta. Los corchetes son la forma en que una terminal marca la
1102
+ * ruta activa, así que dicen «estás aquí» sin depender de que se distinga el
1103
+ * color. Van `aria-hidden`, porque quien escucha ya tiene `aria-current`.
1007
1104
  */
1008
1105
  declare function NavItem({ active, asChild, className, children, ...props }: NavItemProps): react.JSX.Element;
1009
1106
 
@@ -1176,6 +1273,11 @@ declare function SidebarNav({ title, version, branch, children, className, ...pr
1176
1273
  * es un dato; un 0 de design systems es el problema del que trata la charla.
1177
1274
  * Por eso `tone` no es una paleta abierta — son dos valores y significan cosas
1178
1275
  * distintas.
1276
+ *
1277
+ * El orden de lectura es icono + título, el número grande, y la bajada debajo.
1278
+ * El número va en MEDIO y no al final a propósito: es lo que se viene a leer, y
1279
+ * una bajada de dos líneas entre el título y la cifra la entierra. Arriba queda
1280
+ * de qué va, en medio cuánto, y abajo el matiz que solo lee quien se para.
1179
1281
  */
1180
1282
  type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
1181
1283
  /** El número, ya formateado. La librería no impone locale. */
@@ -1186,8 +1288,18 @@ type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
1186
1288
  tone?: 'neutral' | 'alerta';
1187
1289
  /** Con `progress`, la métrica se lee como avance y añade la barra. */
1188
1290
  progress?: number | undefined;
1291
+ /**
1292
+ * Glifo al lado del título, a 1em. Hereda `currentColor`, así que sigue al
1293
+ * tono del título y no hay que teñirlo aparte.
1294
+ */
1295
+ icon?: ReactNode;
1296
+ /**
1297
+ * La bajada: el matiz que el número solo no da. «12 aplicaciones» no dice si
1298
+ * son muchas, y aquí es donde se dice.
1299
+ */
1300
+ description?: ReactNode;
1189
1301
  };
1190
- declare function Stat({ value, label, tone, progress, className, ...props }: StatProps): react.JSX.Element;
1302
+ declare function Stat({ value, label, tone, progress, icon, description, className, ...props }: StatProps): react.JSX.Element;
1191
1303
 
1192
1304
  type TalkCardProps = Omit<TarjetaProps, 'children' | 'title'> & {
1193
1305
  title: ReactNode;
@@ -1324,4 +1436,4 @@ declare namespace social {
1324
1436
  export { social_Correo as Correo, social_Discord as Discord, social_GitHub as GitHub, social_Instagram as Instagram, social_LinkedIn as LinkedIn, social_Rss as Rss, social_X as X, social_YouTube as YouTube };
1325
1437
  }
1326
1438
 
1327
- export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type CalendarProps, Cara, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryBadge, type CategoryBadgeProps, Checkbox, type CheckboxProps, Code, CodeBlock, type CodeBlockProps, type CodeProps, CourseCard, type CourseCardProps, DateField, type DateFieldProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type Entrada, Footer, FooterLink, type FooterLinkProps, type FooterProps, HOVER_TARJETA, Hero, type HeroProps, Input, type InputProps, LinkRow, type LinkRowProps, MetricBadge, type MetricBadgeProps, type Migaja, Nav, NavItem, type NavItemProps, type NavProps, NewsletterForm, type NewsletterFormProps, type NewsletterState, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Pose, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, type Red, SUPERFICIE_TARJETA, ScrollingProgressBar, type ScrollingProgressBarProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarNavProps, Skeleton, type SkeletonProps, Stat, type StatProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, TalkCard, type TalkCardProps, Tema, Text, type TextProps, Textarea, type TextareaProps, ThemeToggle, type ThemeToggleProps, Toast, ToastAction, ToastDescription, type ToastOptions, type ToastProps, ToastProvider, ToastTitle, type ToastVariant, ToastViewport, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, categoria as categoryBadgeVariants, cn, social, texto as textVariants, toast, useTema };
1439
+ export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, AvatarUpload, type AvatarUploadProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type CalendarProps, Cara, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryBadge, type CategoryBadgeProps, Checkbox, type CheckboxProps, Code, CodeBlock, type CodeBlockProps, type CodeProps, CourseCard, type CourseCardProps, DateField, type DateFieldProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, type Entrada, EventCalendar, type EventCalendarProps, type EventoCalendario, Footer, FooterLink, type FooterLinkProps, type FooterProps, HOVER_TARJETA, Hero, type HeroProps, Input, type InputProps, LinkRow, type LinkRowProps, MetricBadge, type MetricBadgeProps, type Migaja, Nav, NavItem, type NavItemProps, type NavProps, NewsletterForm, type NewsletterFormProps, type NewsletterState, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Pose, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, type Red, SUPERFICIE_TARJETA, ScrollingProgressBar, type ScrollingProgressBarProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarNavProps, Skeleton, type SkeletonProps, Stat, type StatProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, TalkCard, type TalkCardProps, Tema, Text, type TextProps, Textarea, type TextareaProps, ThemeToggle, type ThemeToggleProps, ToastAction, type ToastOptions, type ToastVariant, Toaster, type ToasterProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, categoria as categoryBadgeVariants, cn, social, texto as textVariants, toast, useTema };