danielgm-ui 0.2.0 → 0.4.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/README.md CHANGED
@@ -1,429 +1,429 @@
1
- # danielgm-ui
2
-
3
- Componentes React con estilos y APIs propios. Parte de sus interacciones y comportamientos de accesibilidad se apoya en [Radix Primitives](https://www.radix-ui.com/primitives).
4
-
5
- Componentes React con TypeScript y CSS independiente, publicados con licencia MIT.
6
-
7
- **Componentes:** Button, Badge, Typography, Input, Textarea, Checkbox, Switch, Select, CustomSelect, Modal, Tooltip, Tabs, Accordion, Card, Container, Header, Alert, Separator, Transition, DropdownMenu, ContextMenu, RadioGroup, Toast, Skeleton y Loader. Card incluye CardHeader, CardContent y CardFooter; Header incluye HeaderBrand, HeaderNav y HeaderActions.
8
-
9
- ## Instalación
10
-
11
- ```sh
12
- npm install danielgm-ui
13
- ```
14
-
15
- Para probar una compilación local del monorepo:
16
-
17
- Desde el monorepo:
18
-
19
- ```sh
20
- npm install
21
- npm run pack:ui
22
- ```
23
-
24
- En el proyecto consumidor, instala el archivo generado (ajusta la ruta):
25
-
26
- ```sh
27
- npm install /ruta/danielgm-ui-<versión>.tgz
28
- ```
29
-
30
- ## Uso en Next.js
31
-
32
- Importa el CSS una vez en `app/layout.tsx`:
33
-
34
- ```tsx
35
- import 'danielgm-ui/styles.css';
36
- ```
37
-
38
- Para eventos y estado, crea un Client Component:
39
-
40
- ```tsx
41
- 'use client';
42
-
43
- import { useState } from 'react';
44
- import { Button, Input } from 'danielgm-ui';
45
-
46
- export default function Example() {
47
- const [name, setName] = useState('');
48
- return (
49
- <form onSubmit={(event) => event.preventDefault()}>
50
- <Input
51
- label="Nombre"
52
- name="name"
53
- autoComplete="name"
54
- value={name}
55
- onChange={(event) => setName(event.target.value)}
56
- />
57
- <Button type="submit">Guardar</Button>
58
- </form>
59
- );
60
- }
61
- ```
62
-
63
- Badge, Card, Container, Header, Alert, Separator, Skeleton y Loader se pueden renderizar desde Server Components. Los módulos que usan hooks o refs conservan `use client` en el paquete publicado. En React con Vite, importa el CSS en `main.tsx`. En Astro, configura `@astrojs/react` y usa una isla React con `client:load` o `client:visible` para los ejemplos interactivos.
64
-
65
- La cabecera de cristal de la documentación también está disponible para otras aplicaciones:
66
-
67
- ```tsx
68
- import { Header, HeaderActions, HeaderBrand, HeaderNav } from 'danielgm-ui';
69
-
70
- export function SiteHeader() {
71
- return (
72
- <Header sticky>
73
- <HeaderBrand href="/">Mi proyecto</HeaderBrand>
74
- <HeaderNav aria-label="Navegación principal">
75
- <a href="/productos">Productos</a>
76
- <a href="/ayuda">Ayuda</a>
77
- </HeaderNav>
78
- <HeaderActions>
79
- <a href="/cuenta">Mi cuenta</a>
80
- </HeaderActions>
81
- </Header>
82
- );
83
- }
84
- ```
85
-
86
- `Header` usa `header` nativo, centra la isla y permite que el contenido crezca en pantallas estrechas. `sticky` es opcional. Personaliza el ancho, el margen lateral, la separación superior y el cristal mediante las variables `--dgm-header-*`; la página de documentación utiliza estos mismos estilos del paquete.
87
-
88
- ## Personalización
89
-
90
- No requiere Tailwind. Solo el sistema de notificaciones necesita ToastProvider; los demás componentes funcionan sin un proveedor global. Sobrescribe variables después de importar el CSS:
91
-
92
- ```css
93
- :root {
94
- --dgm-primary: #0f766e;
95
- --dgm-primary-hover: #115e59;
96
- --dgm-radius: 0.5rem;
97
- --dgm-font: system-ui, sans-serif;
98
- }
99
- ```
100
-
101
- También disponibles: `--dgm-on-primary`, `--dgm-text`, `--dgm-muted`, `--dgm-surface`, `--dgm-subtle`, `--dgm-border`, `--dgm-control-border` y `--dgm-danger`. No hay resets globales ni selectores que modifiquen elementos ajenos a la librería. Al cambiar colores, comprueba el contraste de texto, bordes y foco.
102
-
103
- Los atributos nativos, `className`, `style` y `ref` disponibles dependen de la API de cada componente; los componentes compuestos no aceptan necesariamente todos los atributos HTML. Input, Textarea y Select incluyen `label`, `description`, `error` y `wrapperClassName`. Checkbox y Switch incluyen `label`, `description` y `wrapperClassName`. Los elementos de presentación aceptan atributos HTML, sin forwarding de refs en esta versión.
104
-
105
- Button mantiene su tamaño al pulsarlo y conserva las transiciones de color y borde. Checkbox conserva su tamaño al seleccionarlo. Alert es estático por defecto; `<Alert animate>` activa la entrada suave al montar una notificación dinámica. Switch conserva su transición entre estados. `prefers-reduced-motion: reduce` desactiva el movimiento, sin afectar la interacción. Alert no gestiona posición, pila, visibilidad, cierre ni temporizadores: puedes añadir esa lógica con ToastProvider y useToast.
106
-
107
- Select conserva el elemento nativo y su referencia. Un contenedor posiciona la flecha decorativa con margen interior; los selectores múltiples y las listas con `size > 1` no muestran esa flecha. En colores forzados se recupera la apariencia nativa.
108
-
109
- ## Compatibilidad y distribución
110
-
111
- Con `<Transition show={visible} collapse>` primero se expande el espacio y después se reproduce el efecto visual. Al cerrar, primero desaparece el contenido y después se contrae la altura. Cada fase usa `duration` y `ease`: con el valor por defecto, son 240 ms de altura y 240 ms de efecto. `onExited` espera al final de toda la secuencia. Sin `collapse`, solo se anima el efecto visual. La documentación permite comparar los tres efectos en ambos modos.
112
-
113
- La altura se adapta al contenido; no requiere fijar píxeles. Coloca el espaciado dentro de `children` (por ejemplo, en un div con padding superior): un `gap` o margen del contenedor exterior no forma parte de esta animación. `collapse` está desactivado por defecto y añade dos contenedores interiores para separar altura y efecto visual. Mientras cambia la altura, el contenido permanece invisible y conserva su tamaño natural para calcular el espacio.
114
-
115
- `Transition` mantiene su contenido montado durante la salida: usa `<Transition show={visible} variant="slide"><Alert>Guardado</Alert></Transition>`, sin una condición externa que elimine Transition. Admite `fade`, `scale` y `slide`, `duration` en milisegundos (240 por defecto), `appear` para animar el montaje inicial y `onExited` para retirar un elemento de una colección. Omite `Alert.animate` al envolverlo para evitar dos animaciones simultáneas. Para notificaciones apiladas, ToastProvider ya coordina entrada, salida y cierre con X.
116
-
117
- El contenido que sale deja de ser interactivo y se oculta del árbol accesible; el consumidor debe devolver antes el foco a un control visible. La preferencia de movimiento reducido y `duration={0}` hacen inmediato el cambio. Transition añade un div: úsalo donde el HTML admita ese contenedor. Es una frontera cliente de Next.js y no necesita dependencias adicionales.
118
-
119
- `Tooltip` recibe `content` y un único hijo enfocable que acepte ref y props, como `<Tooltip content="Guardar cambios"><Button>Guardar</Button></Tooltip>`. Admite lado, alineación, separación, retardo y apertura controlada o inicial. Es ayuda complementaria, sin controles interactivos ni información imprescindible; el tooltip no sustituye el texto visible en móvil. Su Provider es local y no exige envolver la aplicación.
120
-
121
- Al cerrar Modal, Drawer/Sheet, CommandPalette, Popover, DropdownMenu, ContextMenu o los paneles de Combobox y DatePicker/DateRangePicker, el retorno automático del foco no solicita reabrir el tooltip del disparador. El botón sigue recibiendo foco y sus eventos; un nuevo hover o foco con Tab muestra la ayuda normalmente. Esta coordinación es interna: no requiere refs, temporizadores ni manejadores especiales en el consumidor. Si controlas explícitamente `open`, ese valor sigue siendo la fuente de verdad.
122
-
123
- `Tabs` recibe `label` e `items` con `{ value, label, content, disabled? }`. Admite `value/onValueChange`, `defaultValue`, `orientation` y `activationMode`. Selecciona inicialmente la primera pestaña habilitada si no se indica otra. Los paneles inactivos se desmontan: conserva los datos de formularios en el consumidor si deben persistir.
124
-
125
- `Accordion` recibe `items` con `{ value, title, content, disabled? }`. En `type="single"` (por defecto), `value` es string y `collapsible` permite cerrar la última sección. En `type="multiple"`, `value` es string[] y cada sección se cierra independientemente. Ambos admiten estado inicial o controlado, grupo desactivado, `headingLevel` y `className`. Los valores de Tabs y Accordion deben ser únicos y estables.
126
-
127
- `CustomSelect` admite opciones `{ value, label, icon?, disabled? }`, `value/onValueChange` o `defaultValue`, `name`, `required`, `description` y `error`. Los valores deben ser únicos y no vacíos. `CustomSelect` es la opción predeterminada para código nuevo. `Select` está marcado como `@deprecated`, pero se mantiene para consumidores existentes y casos todavía sin equivalente: selección múltiple, `size`, `optgroup` o referencias al elemento nativo.
128
-
129
- Si CustomSelect, Combobox/Autocomplete, DatePicker/DateRangePicker o MultiSelect reciben `disabled` estando abiertos, cierran el panel, conservan su valor y bloquean cambios durante la salida. Al habilitarlos de nuevo permanecen cerrados. MultiSelect descarta el texto de búsqueda cada vez que se cierra, también con «Listo»; conserva las opciones seleccionadas.
130
-
131
- `Modal` necesita `open`, `onOpenChange` y `title`. Acepta `description`, `footer`, `closeLabel` traducido y `closeOnOverlayClick`. Utiliza un único ancho de 32rem, limitado al espacio de pantalla disponible. La entrada y salida hacen zoom desde el centro. El foco queda dentro mientras está abierto y vuelve al elemento que lo abrió. Los cambios se realizan dentro de un Client Component de Next.js.
132
-
133
- Radix Select, Dialog, Tooltip, Tabs, Accordion, Dropdown Menu, Context Menu, Radio Group y Toast son dependencias de ejecución instaladas automáticamente con este paquete. Gestionan las interacciones accesibles; el CSS de `danielgm-ui` determina su aspecto y animaciones. Los portales heredan los tokens de la raíz; para cambiar su tema, configura las variables en `:root`. Los campos utilizan una sombra interior como línea y una sombra exterior translúcida al enfocar, con transición de 180 ms. El error utiliza `--dgm-danger`. Movimiento reducido desactiva las transiciones y alto contraste utiliza contornos del sistema en lugar de sombras.
134
-
135
- Los paneles anidados coordinan sus capas automáticamente, de modo que un selector abierto dentro de Modal o Drawer aparece sobre su contenido. Para integrarlos con otras capas de tu aplicación, configura en `:root` `--dgm-layer-base` (1100), `--dgm-layer-step` (10, al menos 2) y `--dgm-layer-toast` (2100). La profundidad y los proveedores internos no requieren configuración. Conserva una posición suficiente para Toast al cambiar la escala.
136
-
137
- - React 18.2+ o 19; React no se incluye en el paquete.
138
- - ESM con declaraciones TypeScript; no se genera CommonJS.
139
- - CSS exportado explícitamente como `danielgm-ui/styles.css`.
140
- - Controles nativos de formulario, etiquetas asociadas y foco visible.
141
- - La documentación Astro del repositorio utiliza exactamente este paquete.
142
-
143
- Licencia MIT.
144
-
145
- ## Paneles, navegación y selección con búsqueda
146
-
147
- `Popover` recibe un único Button como hijo, `title` y `content`. Admite `open/onOpenChange` o `defaultOpen`, `side`, `align`, `sideOffset`, `disabled`, `closeLabel` y `className`. Su contenido puede incluir campos y botones. Escape devuelve el foco al disparador; pulsar otro campo conserva el foco en ese campo.
148
-
149
- `Drawer` comparte `open/onOpenChange`, título, descripción, contenido, pie y cierre de Modal. Añade `side="left" | "right" | "top" | "bottom"`; por defecto aparece por la derecha. El cuerpo tiene scroll independiente y el fondo queda bloqueado. `Sheet` es un alias con la misma API, útil para nombrar paneles inferiores. Traduce `closeLabel` para lectores de pantalla.
150
-
151
- `Combobox` recibe `label` y opciones `{ value, label, icon?, disabled?, keywords? }` con valores únicos y no vacíos. La búsqueda encuentra etiquetas y sinónimos; las flechas y Enter seleccionan. Admite `value/onValueChange` o `defaultValue`, borrado con `clearable`, estados de formulario, mensajes personalizados y `locale="es" | "en"`. Su ref apunta al botón visible. `Autocomplete` es un alias: ambos eligen una opción del catálogo; no envían texto libre. Usa CustomSelect para listas cortas sin buscador.
152
-
153
- `Pagination` recibe `page` desde 1, `pageCount` y `onPageChange`. Añade `siblings` (0–3), tamaños `sm/md/lg`, `disabled`, `locale` y etiqueta accesible. Las páginas visibles están acotadas; `pageCount=0` representa ausencia de resultados. El consumidor determina qué datos muestra cada página.
154
-
155
- `Breadcrumbs` recibe `items` con `{ id, label, href? }`, `label`, un `separator` decorativo y `renderLink` opcional para integrar enlaces del router. El último elemento representa la página actual. Se puede renderizar como Server Component.
156
-
157
- ## Identidad, progreso y grupos de opciones
158
-
159
- `Avatar` utiliza `name` como nombre accesible y como origen de las iniciales. `src` carga una imagen; si falta o falla, aparece `fallback` o las iniciales. Admite tamaños `sm/md/lg`, formas `circle/square` y `onLoadingStatusChange`. Usa `decorative` si el nombre ya aparece junto a él.
160
-
161
- `Progress` es un progress nativo, compatible con servidor. Recibe `label`, `value`, `max=100`, `showValue`, `valueText` y grosor `sm/md/lg`. Omite `value` para avance indeterminado. Los valores se limitan a 0..max; `valueText` puede expresar unidades como «3 de 8 archivos».
162
-
163
- `CheckboxGroup` y `SwitchGroup` reúnen opciones `{ value, label, description?, disabled? }` dentro de fieldset y legend. Admite `value/onValueChange` o `defaultValue`, disposición vertical/horizontal, ayuda, error y desactivación. `required` exige al menos una opción habilitada; `FormData.getAll(name)` obtiene las marcadas. En formularios que se reinician, controla `value` y restablécelo en `onReset`.
164
-
165
- ## Fechas y rangos
166
-
167
- `DatePicker` entrega `string | null`. `DateRangePicker` entrega `{ from: string; to?: string } | null`: el primer clic establece `from` y el segundo completa `to`. Todas las fechas son **YYYY-MM-DD**, sin conversión a UTC. `locale` traduce calendario y formato visible; no cambia el valor enviado.
168
-
169
- Ambos admiten estado controlado o inicial, `min` y `max` inclusivos, `disabledDates`, `defaultMonth`, `clearable`, etiqueta, ayuda, error, `name`, `required` y `disabled`. Un rango no puede atravesar fechas bloqueadas; un intervalo incompleto impide enviar el formulario. Los límites y excepciones deben ser fechas ISO válidas. Controla `value` y asígnale `null` al reiniciar el formulario.
170
-
171
- ```tsx
172
- 'use client';
173
- import { useState } from 'react';
174
- import { Button, DateRangePicker, type DateRangeValue } from 'danielgm-ui';
175
-
176
- export default function Booking() {
177
- const [stay, setStay] = useState<DateRangeValue | null>(null);
178
- return (
179
- <form
180
- onReset={() => setStay(null)}
181
- onSubmit={(event) => {
182
- event.preventDefault();
183
- const data = new FormData(event.currentTarget);
184
- // Los campos son stay.from y stay.to; aquí puedes enviarlos a tu aplicación.
185
- console.log(data.get('stay.from'), data.get('stay.to'));
186
- }}
187
- >
188
- <DateRangePicker
189
- label="Estancia"
190
- locale="es"
191
- name="stay"
192
- required
193
- value={stay}
194
- onValueChange={setStay}
195
- />
196
- <Button type="submit">Reservar</Button>
197
- <Button type="reset" variant="outline">
198
- Reiniciar
199
- </Button>
200
- </form>
201
- );
202
- }
203
- ```
204
-
205
- ## Datos, archivos, comandos y procesos
206
-
207
- `DataTable<T>` recibe `data`, `columns`, `getRowId` y `caption`. Cada columna tiene `id`, `header` y `accessor`; puede configurar `cell`, `sortable`, `searchable` y `align`. `accessor` devuelve un string o número para buscar y ordenar; `cell` personaliza su representación. Los IDs de filas y columnas deben ser únicos y estables.
208
-
209
- La tabla filtra, ordena y pagina los datos locales sin mutarlos. Admite `sorting/onSortingChange` o `defaultSorting`, página controlada con `page/onPageChange`, `pageSize`, búsqueda global y estados `loading`/vacío. `selectable` habilita selección: `selection/onSelectionChange` o `defaultSelection` contienen IDs que se conservan entre páginas. No incluye virtualización ni un protocolo para consultas de servidor. El contenedor permite scroll horizontal en móvil.
210
-
211
- `FileUpload` entrega `File[]` mediante `onValueChange`. Admite selección nativa y arrastre, `accept`, `multiple`, `maxFiles`, `maxSize` en bytes, vista previa, mensajes, `required`, `disabled` y `locale`. `onReject` informa de `{ file, reason }`, con reason `type`, `size`, `count` o `duplicate`. Controla `value` para reiniciar con `[]`. Las URLs de miniaturas se revocan al retirar los archivos.
212
-
213
- FileUpload no hace peticiones ni introduce archivos automáticamente en FormData. Añádelos con `files.forEach(file => data.append('attachments', file))` y elige el transporte desde la aplicación. La validación de tipo/tamaño del navegador no sustituye la comprobación del servidor.
214
-
215
- `CommandPalette` compone Modal y un buscador de acciones. Recibe `open/onOpenChange`, `title` y `actions` con `{ id, label, onSelect, keywords?, group?, icon?, disabled? }`. Permite textos localizados, personalización y `shortcut`: registra Ctrl/Cmd+K fuera de campos editables y elimina el listener al desmontar. Activa el atajo en una única instancia por aplicación. Cada callback decide la acción o navegación.
216
-
217
- `Stepper` representa `steps` con `{ id, title, description?, disabled? }`, `value` como ID actual y una etiqueta accesible. Con `onValueChange` permite navegación; sin él es informativo. `linear=true` bloquea saltos a pasos futuros, `orientation` cambia la disposición y `completed` marca el proceso terminado. La aplicación gestiona el contenido, validación y botones de avance. La orientación horizontal pasa a vertical en móvil.
218
-
219
- Los componentes nuevos comparten los tokens y el único import CSS del paquete. Radix Popover/Avatar, cmdk, React DayPicker, date-fns y TanStack Table son dependencias instaladas automáticamente. Las funciones y callbacks de ejemplos interactivos pertenecen a una frontera cliente en Next.js; Breadcrumbs y Progress pueden permanecer en servidor.
220
-
221
- ## Menús, elección única, notificaciones y carga
222
-
223
- `DropdownMenu` recibe un único botón como hijo e `items` con acciones `{ id, label, icon?, shortcut?, disabled?, danger?, onSelect? }` y separadores `{ id, type: 'separator' }`. Admite apertura controlada o inicial, lado, alineación y separación. `onSelect` funciona con teclado y puntero; `event.preventDefault()` mantiene abierto el menú. La leyenda `shortcut` no registra un atajo. Los disparadores con solo icono necesitan un nombre accesible.
224
-
225
- `ContextMenu` utiliza el mismo contrato de acciones que DropdownMenu y se abre con clic derecho sobre su único hijo. Admite iconos, separadores, acciones desactivadas y `danger`; `onSelect(event)` puede llamar a `preventDefault()` para mantener abierto el menú. El hijo transmite props y ref, conserva su clic izquierdo y recibe foco con Tab; Shift+F10 o la tecla de menú contextual abren junto al elemento. En pantallas táctiles se abre al mantener pulsado. Escape devuelve el foco sin desplazar la página y un clic fuera conserva su destino. `label` identifica el menú y `onOpenChange` notifica su estado. `disabled` conserva el menú nativo y no desactiva la acción del hijo. Ofrece una alternativa visible para las acciones importantes. Utiliza Radix ContextMenu como base y comparte los estilos de DropdownMenu.
226
-
227
- ```tsx
228
- <ContextMenu
229
- label="Acciones del archivo"
230
- items={[{ id: 'open', label: 'Abrir', onSelect: () => openFile() }]}
231
- >
232
- <Button variant="outline" onClick={() => openFile()}>
233
- Propuesta.pdf
234
- </Button>
235
- </ContextMenu>
236
- ```
237
-
238
- `RadioGroup` recibe `label` y `options` con `{ value, label, description?, disabled? }`. Admite `value/onValueChange` o `defaultValue`, orientación, navegación circular, grupo desactivado, ayuda y error. `name` y `required` participan en formularios. Los valores deben ser únicos y no vacíos. Si controlas el estado, actualízalo también en `onReset`.
239
-
240
- `Skeleton` es un span decorativo compatible con Server Components. Admite `variant="text" | "rectangular" | "circular"`, dimensiones CSS o numéricas, `animate`, `className` y `style`. Para círculos utiliza ancho y alto iguales. Marca el contenedor que carga con `aria-busy` y anuncia el estado una sola vez; no añadas un anuncio por marcador.
241
-
242
- ### Notificaciones compartidas
243
-
244
- Envuelve una vez el área cliente con `ToastProvider`. Sus descendientes acceden a `notify(options)` y `dismiss(id?)` mediante `useToast()`. El identificador devuelto permite cerrar un aviso concreto; omitirlo cierra toda la pila y la cola. En Astro, Provider y consumidores deben vivir dentro de la misma isla React. No hay un almacén global que pueda mezclar peticiones SSR.
245
-
246
- ```tsx
247
- 'use client';
248
- import { Button, ToastProvider, useToast } from 'danielgm-ui';
249
-
250
- export default function Example() {
251
- return (
252
- <ToastProvider
253
- label="Notificación"
254
- closeLabel="Cerrar notificación"
255
- viewportLabel="Notificaciones ({hotkey})"
256
- duration={5000}
257
- maxVisible={3}
258
- >
259
- <SaveButton />
260
- </ToastProvider>
261
- );
262
- }
263
-
264
- function SaveButton() {
265
- const { notify } = useToast();
266
- return (
267
- <Button
268
- onClick={() =>
269
- notify({
270
- title: 'Cambios guardados',
271
- tone: 'success',
272
- description: 'Tu proyecto está actualizado.',
273
- })
274
- }
275
- >
276
- Simular guardado
277
- </Button>
278
- );
279
- }
280
- ```
281
-
282
- La pila aparece abajo a la derecha y reutiliza Alert. La duración empieza cuando el aviso se muestra, se pausa con puntero, foco o al cambiar de ventana y admite `0` para avisos persistentes. La X, Escape y un gesto hacia la derecha permiten cerrar. F8 enfoca la pila. Entrada y salida usan el zoom compartido de los paneles desde la esquina inferior derecha; movimiento reducido las desactiva.
283
-
284
- Cada aviso admite `title`, `description`, `tone`, `duration`, `type`, `closeLabel`, `className` y `action: { label, altText, onClick }`. `type="background"` anuncia con cortesía; `foreground` interrumpe y se reserva para urgencias. El tono no decide la prioridad. Las acciones cierran el aviso y necesitan `altText` que explique una alternativa permanente, por ejemplo otro control de la página. Los tokens de portales deben definirse en la raíz.
285
-
286
- ## Container: estructura de página
287
-
288
- `Container` centra el contenido y limita su ancho al espacio disponible. Es estático y puede usarse en Server Components, sin `use client`.
289
-
290
- ```tsx
291
- import { Container } from 'danielgm-ui';
292
-
293
- export default function Page() {
294
- return (
295
- <Container as="main" size="xl">
296
- <h1>Mi proyecto</h1>
297
- <p>Contenido principal.</p>
298
- </Container>
299
- );
300
- }
301
- ```
302
-
303
- Importa también `danielgm-ui/styles.css` una vez en el layout. `size` admite `sm` (40rem), `md` (48rem), `lg` (64rem), `xl` (80rem, predeterminado) y `full` (sin límite adicional). El máximo incluye el padding. Todos se reducen al ancho del padre. `padding` vale `true` por defecto y aplica `clamp(1rem, 3vw, 2rem)` a ambos lados; usa `false` si el padre ya aporta ese espacio.
304
-
305
- `as` admite `div` (predeterminado), `main`, `section`, `article`, `header`, `footer`, `aside` y `nav`. Conserva atributos HTML comunes, `aria-*`, `className` y `style`; no expone `ref`. Usa un único `main` visible por página. Puedes personalizar `maxInlineSize` y `paddingInline` con `style`. No añade altura, separación vertical, fondo ni distribución de hijos; coloca los fondos de ancho completo en una sección exterior.
306
-
307
- ## Nuevos controles y estructuras
308
-
309
- Slider, RangeSlider, MultiSelect, NumberInput, ToggleGroup, EmptyState, NavigationMenu, AspectRatio, ResizablePanels, TreeView, Carousel y AnimatedGrid. Slider y RangeSlider comparten página de documentación. Cada componente tiene ejemplos independientes en español e inglés, sus propiedades y recomendaciones de accesibilidad.
310
-
311
- Todos se importan desde `danielgm-ui` y usan la misma hoja `danielgm-ui/styles.css`. `EmptyState` y `AspectRatio` son estáticos y compatibles con Server Components; las piezas interactivas mantienen su propia frontera `use client`.
312
-
313
- - Formularios: `MultiSelect` envía los valores habilitados con `FormData.getAll(name)`; `RangeSlider` utiliza el mismo nombre para sus dos extremos. Los controles basados en estado React necesitan `value` y `onReset` para un reset controlado. `NumberInput` e `Input type="password"` también permiten el modo nativo no controlado.
314
- - `ToggleGroup` permite selección única vacía o múltiple; no genera campos de formulario.
315
- - `ResizablePanels` ajusta dos paneles con porcentajes, límites, teclado y puntero.
316
- - `TreeView` ofrece selección única, expansión y búsqueda por prefijo; requiere IDs únicos y datos sin ciclos. El teclado mantiene visible la fila enfocada mediante un desplazamiento mínimo, sin seleccionar automáticamente ni desplazar al pulsar con el ratón.
317
- - `Carousel` muestra una diapositiva por vista, sin autoplay ni bucle infinito, con botones, indicadores opcionales y gestos de scroll nativos.
318
- - `AnimatedGrid` gestiona internamente la presencia y el movimiento de elementos con claves estables. Usa el efecto scale de Transition para altas, bajas y cambios de fila; desliza los cambios dentro de una fila y anima la altura. Conserva montado el contenido real para mantener su estado y foco.
319
-
320
- ```tsx
321
- 'use client';
322
-
323
- import { useState } from 'react';
324
- import { AnimatedGrid } from 'danielgm-ui';
325
-
326
- type Item = { id: string; title: string };
327
-
328
- function Projects() {
329
- const [items, setItems] = useState<Item[]>([]);
330
- return (
331
- <>
332
- <button
333
- onClick={() =>
334
- setItems((current) => [...current, { id: crypto.randomUUID(), title: 'Nuevo proyecto' }])
335
- }
336
- >
337
- Añadir
338
- </button>
339
- <AnimatedGrid
340
- items={items}
341
- getKey={(item) => item.id}
342
- renderItem={(item) => <article>{item.title}</article>}
343
- columns={{ base: 1, sm: 2, md: 3 }}
344
- gap="1rem"
345
- />
346
- </>
347
- );
348
- }
349
- ```
350
-
351
- Las columnas responden al ancho del contenedor (36, 56 y 76 rem). Usa identificadores únicos y estables; no uses el índice. El montaje inicial no se anima. Las salidas reservan su celda hasta finalizar y las copias visuales de cambios de fila son inertes y no accesibles. `duration` controla las fases (240 ms por defecto); con `duration={0}` o movimiento reducido, el resultado es inmediato.
352
-
353
- ### Iconografía y contraseñas
354
-
355
- Los iconos utilizan [Tabler Icons](https://tabler.io/icons), con licencia MIT. Conservamos sus avisos originales en `packages/ui/TABLER-LICENSE.txt` (incluido como `TABLER-LICENSE.txt` en npm) y en la página bilingüe de créditos. Los ejemplos que importan iconos requieren `npm install @tabler/icons-react`.
356
-
357
- Usa `<Input type="password" label="Contraseña" locale="es" />`: el ojo se integra en el campo y reserva espacio para el texto. `visible`, `defaultVisible` y `onVisibleChange` permiten gestionar la visibilidad.
358
-
359
- ### Presencia en Avatar
360
-
361
- `Avatar` admite `status="online" | "busy" | "away" | "offline"`: un punto verde, rojo, ámbar o gris en la esquina inferior, adaptado a `size` y compatible con ambas formas. Al omitir `status` desaparece el punto. La presencia la proporciona tu aplicación y es independiente de `onLoadingStatusChange`, que informa de la carga de la imagen.
362
-
363
- ```tsx
364
- <Avatar name="Daniel Gracia" src="/perfil.jpg" status="online" locale="es" />
365
- <Avatar name="Ana Torres" status="busy" statusLabel="En una reunión" locale="es" />
366
- ```
367
-
368
- El nombre accesible incluye la presencia. `locale` traduce las etiquetas (`en` por defecto) y `statusLabel` permite personalizarlas; la etiqueta también aparece como ayuda nativa del punto. Incluye texto visible si conocer la disponibilidad es importante, y usa `decorative` solo cuando el nombre y el estado ya estén indicados al lado. Los colores pueden ajustarse con `--dgm-avatar-online`, `--dgm-avatar-busy`, `--dgm-avatar-away` y `--dgm-avatar-offline`; el anillo usa `--dgm-surface`.
369
-
370
- ### Tipografía
371
-
372
- `Typography` aplica la escala sin proveedor, hooks ni JavaScript cliente. `variant` admite `title`, `subtitle`, `body`, `lead`, `label`, `caption` y `code`; `tone` admite `default`, `muted`, `primary`, `danger` e `inherit`. `as` elige el elemento HTML con independencia del estilo. Por defecto title produce h2, subtitle h3, label span, code code y las demás variantes p. Ajusta el nivel de encabezado a la página; `label` es un estilo visual, no sustituye la etiqueta accesible de un campo.
373
-
374
- ```tsx
375
- import { Typography } from 'danielgm-ui';
376
-
377
- <Typography variant="title" as="h1">Mi proyecto</Typography>
378
- <Typography>Un párrafo con los estilos de lectura predeterminados.</Typography>
379
- <Typography variant="caption" tone="muted">Actualizado hoy</Typography>
380
- ```
381
-
382
- Conserva `className`, `style`, atributos HTML y ARIA; no expone `ref`, añade márgenes ni carga fuentes. El contenedor controla la separación y los textos largos pueden ajustarse. La fuente especial del título de la web de documentación pertenece únicamente a esa landing, no a Typography ni al paquete.
383
-
384
- La fuente de los componentes se configura con `--dgm-font`; por defecto hereda la de la aplicación. Los controles internos utilizan esa misma familia, incluidos menús, TreeView, ToggleGroup y NavigationMenu. `--dgm-font-mono` configura la familia de texto monoespaciado. No se descargan fuentes externas.
385
-
386
- Los tamaños tienen roles compartidos: `--dgm-text-body` y `--dgm-text-control` (1rem), `--dgm-text-label` y `--dgm-text-action` (0.9375rem), `--dgm-text-help` (0.875rem), `--dgm-text-caption` (0.75rem) y `--dgm-text-title` (1.25rem). Los valores de campos y selectores son regulares; etiquetas y acciones usan semibold. `--dgm-font-weight-*`, `--dgm-leading-*` y `--dgm-tracking-*` completan la escala. Las variantes `sm` y `lg` conservan sus tamaños propios dentro de ella.
387
-
388
- ```css
389
- :root {
390
- --dgm-font: 'Segoe UI', system-ui, sans-serif;
391
- --dgm-text-control: 1rem;
392
- --dgm-text-label: 0.9375rem;
393
- --dgm-text-help: 0.875rem;
394
- }
395
- ```
396
-
397
- Define los tokens en `:root` para que también alcancen a los paneles portados a `body`. En un contenedor local puedes sobrescribir directamente la familia o los roles `--dgm-text-*`; configura la escala primitiva `--dgm-font-size-*` en la raíz, donde se resuelven sus alias. Cambiar selección, hover o foco conserva las métricas del texto para evitar saltos de línea.
398
-
399
- ### Medidas de los campos
400
-
401
- Input, NumberInput, Select, CustomSelect, Combobox/Autocomplete, DatePicker/DateRangePicker y MultiSelect comparten una altura mínima de `3rem` (48px con fuente raíz de 16px), configurable mediante `--dgm-control-min-height`. Los selectores mantienen la misma altura con placeholder, valor o icono. Los textos largos pueden aumentar la altura para evitar recortes; Textarea conserva sus filas y redimensionamiento.
402
-
403
- ### Hover y selección
404
-
405
- Los botones y controles de icono comparten `--dgm-hover-transition`: fondo, borde y texto cambian durante 150 ms con curva `ease`, tanto al entrar como al salir. No se anima su tamaño al pulsar y se respeta la preferencia de movimiento reducido. En enlaces que tengan apariencia de botón puedes reutilizar `transition: var(--dgm-hover-transition)` para mantener la misma respuesta.
406
-
407
- Los menús y opciones comparten también esa transición. Personaliza juntos `--dgm-hover` (fondo neutro) y `--dgm-on-hover` (texto), con un fondo distinto de `--dgm-subtle` para que los botones secundarios tengan una respuesta visible. Las selecciones rellenas del calendario, ToggleGroup, TreeView y Pagination usan `--dgm-primary-hover` con `--dgm-on-primary`, igual que Button primary; el hover no debe perder el color de selección.
408
-
409
- El foco de teclado debe seguir distinguiéndose de la selección: calendarios y TreeView utilizan `--dgm-on-primary` para el contorno interior sobre rellenos primarios. MultiSelect comparte con los demás campos la línea de control y los estados de foco y error. Las acciones secundarias, como quitar etiquetas o borrar valores, conservan los mismos tokens de hover.
410
-
411
- Los botones de peligro separan su relleno del texto de error: `--dgm-danger-surface`, `--dgm-danger-hover` y `--dgm-on-danger` controlan Button danger, mientras que `--dgm-danger` controla mensajes y acciones de texto. Para un tema oscuro, adapta las parejas de fondo y texto en tu contenedor de tema; los componentes no requieren reglas de hover propias.
412
-
413
- ### Scrollbars
414
-
415
- Los paneles de opciones mantienen márgenes simétricos mediante `--dgm-list-padding` (0.5rem); `--dgm-option-padding-inline` (0.75rem) controla el espacio interior horizontal de las opciones de selectores y menús. No hay una compensación lateral fija para la scrollbar ni un hueco reservado cuando la lista no desborda.
416
-
417
- CustomSelect, Combobox (y su alias Autocomplete), CommandPalette y MultiSelect desvanecen los extremos que tienen más opciones fuera de vista mediante una máscara CSS de 20 px. La transparencia deja ver el fondo real sin superponer un color; la barra nativa queda fuera del desvanecimiento cuando ocupa espacio propio. El menú lateral de la documentación comparte este estilo. Se actualiza al desplazar, filtrar o redimensionar; no intercepta clics ni cambia el foco. No se aplica si todo el contenido cabe y se desactiva en colores forzados. La implementación se comparte internamente; no requiere nuevas propiedades en los componentes.
418
-
419
- Los componentes comparten scrollbars nativas finas, de color neutro y con pista transparente. Aparecen cuando hay desbordamiento, sin reservar un hueco en listas cortas. La rueda, el arrastre, el teclado y los gestos táctiles conservan su funcionamiento nativo. Carousel mantiene su barra oculta porque ofrece controles y gestos propios.
420
-
421
- Para extender este estilo a toda tu página, añade `className="dgm-scrollbars"` a `html` (o `class="dgm-scrollbars"` en HTML/Astro). También puedes usar la clase en un contenedor concreto. Sobrescribe `--dgm-scrollbar-thumb`, `--dgm-scrollbar-thumb-hover` y `--dgm-scrollbar-track` para adaptar los colores a tu tema. La documentación usa el mismo estilo con tonos claros y oscuros; los bloques de código siempre usan los oscuros. La geometría y visibilidad finales respetan las capacidades del navegador y los ajustes del sistema.
422
-
423
- ### Loader
424
-
425
- `Loader` muestra actividad sin porcentaje con `variant="spinner" | "dots" | "bars" | "pulse"` y `size="sm" | "md" | "lg" | "xl"`. Admite `color`, `label`, `showLabel`, `locale` y `animate`. Las animaciones CSS respetan movimiento reducido y funcionan desde Server Components. Usa `decorative` si otro elemento ya anuncia la carga; en caso contrario incluye un estado accesible con texto localizado.
426
-
427
- ```tsx
428
- <Loader variant="dots" size="lg" locale="es" showLabel />
429
- ```
1
+ # danielgm-ui
2
+
3
+ Componentes React con estilos y APIs propios. Parte de sus interacciones y comportamientos de accesibilidad se apoya en [Radix Primitives](https://www.radix-ui.com/primitives).
4
+
5
+ Componentes React con TypeScript y CSS independiente, publicados con licencia MIT.
6
+
7
+ **Componentes:** Button, Badge, Typography, Input, Textarea, Checkbox, Switch, Select, CustomSelect, Modal, Tooltip, Tabs, Accordion, Card, Container, Header, Alert, Separator, Transition, DropdownMenu, ContextMenu, RadioGroup, Toast, Skeleton y Loader. Card incluye CardHeader, CardContent y CardFooter; Header incluye HeaderBrand, HeaderNav y HeaderActions.
8
+
9
+ ## Instalación
10
+
11
+ ```sh
12
+ npm install danielgm-ui
13
+ ```
14
+
15
+ Para probar una compilación local del monorepo:
16
+
17
+ Desde el monorepo:
18
+
19
+ ```sh
20
+ npm install
21
+ npm run pack:ui
22
+ ```
23
+
24
+ En el proyecto consumidor, instala el archivo generado (ajusta la ruta):
25
+
26
+ ```sh
27
+ npm install /ruta/danielgm-ui-<versión>.tgz
28
+ ```
29
+
30
+ ## Uso en Next.js
31
+
32
+ Importa el CSS una vez en `app/layout.tsx`:
33
+
34
+ ```tsx
35
+ import 'danielgm-ui/styles.css';
36
+ ```
37
+
38
+ Para eventos y estado, crea un Client Component:
39
+
40
+ ```tsx
41
+ 'use client';
42
+
43
+ import { useState } from 'react';
44
+ import { Button, Input } from 'danielgm-ui';
45
+
46
+ export default function Example() {
47
+ const [name, setName] = useState('');
48
+ return (
49
+ <form onSubmit={(event) => event.preventDefault()}>
50
+ <Input
51
+ label="Nombre"
52
+ name="name"
53
+ autoComplete="name"
54
+ value={name}
55
+ onChange={(event) => setName(event.target.value)}
56
+ />
57
+ <Button type="submit">Guardar</Button>
58
+ </form>
59
+ );
60
+ }
61
+ ```
62
+
63
+ Badge, Card, Container, Header, Alert, Separator, Skeleton y Loader se pueden renderizar desde Server Components. Los módulos que usan hooks o refs conservan `use client` en el paquete publicado. En React con Vite, importa el CSS en `main.tsx`. En Astro, configura `@astrojs/react` y usa una isla React con `client:load` o `client:visible` para los ejemplos interactivos.
64
+
65
+ La cabecera de cristal de la documentación también está disponible para otras aplicaciones:
66
+
67
+ ```tsx
68
+ import { Header, HeaderActions, HeaderBrand, HeaderNav } from 'danielgm-ui';
69
+
70
+ export function SiteHeader() {
71
+ return (
72
+ <Header sticky>
73
+ <HeaderBrand href="/">Mi proyecto</HeaderBrand>
74
+ <HeaderNav aria-label="Navegación principal">
75
+ <a href="/productos">Productos</a>
76
+ <a href="/ayuda">Ayuda</a>
77
+ </HeaderNav>
78
+ <HeaderActions>
79
+ <a href="/cuenta">Mi cuenta</a>
80
+ </HeaderActions>
81
+ </Header>
82
+ );
83
+ }
84
+ ```
85
+
86
+ `Header` usa `header` nativo, centra la isla y permite que el contenido crezca en pantallas estrechas. `sticky` es opcional. Personaliza el ancho, el margen lateral, la separación superior y el cristal mediante las variables `--dgm-header-*`; la página de documentación utiliza estos mismos estilos del paquete.
87
+
88
+ ## Personalización
89
+
90
+ No requiere Tailwind. Solo el sistema de notificaciones necesita ToastProvider; los demás componentes funcionan sin un proveedor global. Sobrescribe variables después de importar el CSS:
91
+
92
+ ```css
93
+ :root {
94
+ --dgm-primary: #0f766e;
95
+ --dgm-primary-hover: #115e59;
96
+ --dgm-radius: 0.5rem;
97
+ --dgm-font: system-ui, sans-serif;
98
+ }
99
+ ```
100
+
101
+ También disponibles: `--dgm-on-primary`, `--dgm-text`, `--dgm-muted`, `--dgm-surface`, `--dgm-subtle`, `--dgm-border`, `--dgm-control-border` y `--dgm-danger`. No hay resets globales ni selectores que modifiquen elementos ajenos a la librería. Al cambiar colores, comprueba el contraste de texto, bordes y foco.
102
+
103
+ Los atributos nativos, `className`, `style` y `ref` disponibles dependen de la API de cada componente; los componentes compuestos no aceptan necesariamente todos los atributos HTML. Input, Textarea y Select incluyen `label`, `description`, `error` y `wrapperClassName`. Checkbox y Switch incluyen `label`, `description` y `wrapperClassName`. Los elementos de presentación aceptan atributos HTML, sin forwarding de refs en esta versión.
104
+
105
+ Button mantiene su tamaño al pulsarlo y conserva las transiciones de color y borde. Checkbox conserva su tamaño al seleccionarlo. Alert es estático por defecto; `<Alert animate>` activa la entrada suave al montar una notificación dinámica. Switch conserva su transición entre estados. `prefers-reduced-motion: reduce` desactiva el movimiento, sin afectar la interacción. Alert no gestiona posición, pila, visibilidad, cierre ni temporizadores: puedes añadir esa lógica con ToastProvider y useToast.
106
+
107
+ Select conserva el elemento nativo y su referencia. Un contenedor posiciona la flecha decorativa con margen interior; los selectores múltiples y las listas con `size > 1` no muestran esa flecha. En colores forzados se recupera la apariencia nativa.
108
+
109
+ ## Compatibilidad y distribución
110
+
111
+ Con `<Transition show={visible} collapse>` primero se expande el espacio y después se reproduce el efecto visual. Al cerrar, primero desaparece el contenido y después se contrae la altura. Cada fase usa `duration` y `ease`: con el valor por defecto, son 240 ms de altura y 240 ms de efecto. `onExited` espera al final de toda la secuencia. Sin `collapse`, solo se anima el efecto visual. La documentación permite comparar los tres efectos en ambos modos.
112
+
113
+ La altura se adapta al contenido; no requiere fijar píxeles. Coloca el espaciado dentro de `children` (por ejemplo, en un div con padding superior): un `gap` o margen del contenedor exterior no forma parte de esta animación. `collapse` está desactivado por defecto y añade dos contenedores interiores para separar altura y efecto visual. Mientras cambia la altura, el contenido permanece invisible y conserva su tamaño natural para calcular el espacio.
114
+
115
+ `Transition` mantiene su contenido montado durante la salida: usa `<Transition show={visible} variant="slide"><Alert>Guardado</Alert></Transition>`, sin una condición externa que elimine Transition. Admite `fade`, `scale` y `slide`, `duration` en milisegundos (240 por defecto), `appear` para animar el montaje inicial y `onExited` para retirar un elemento de una colección. Omite `Alert.animate` al envolverlo para evitar dos animaciones simultáneas. Para notificaciones apiladas, ToastProvider ya coordina entrada, salida y cierre con X.
116
+
117
+ El contenido que sale deja de ser interactivo y se oculta del árbol accesible; el consumidor debe devolver antes el foco a un control visible. La preferencia de movimiento reducido y `duration={0}` hacen inmediato el cambio. Transition añade un div: úsalo donde el HTML admita ese contenedor. Es una frontera cliente de Next.js y no necesita dependencias adicionales.
118
+
119
+ `Tooltip` recibe `content` y un único hijo enfocable que acepte ref y props, como `<Tooltip content="Guardar cambios"><Button>Guardar</Button></Tooltip>`. Admite lado, alineación, separación, retardo y apertura controlada o inicial. Es ayuda complementaria, sin controles interactivos ni información imprescindible; el tooltip no sustituye el texto visible en móvil. Su Provider es local y no exige envolver la aplicación.
120
+
121
+ Al cerrar Modal, Drawer/Sheet, CommandPalette, Popover, DropdownMenu, ContextMenu o los paneles de Combobox y DatePicker/DateRangePicker, el retorno automático del foco no solicita reabrir el tooltip del disparador. El botón sigue recibiendo foco y sus eventos; un nuevo hover o foco con Tab muestra la ayuda normalmente. Esta coordinación es interna: no requiere refs, temporizadores ni manejadores especiales en el consumidor. Si controlas explícitamente `open`, ese valor sigue siendo la fuente de verdad.
122
+
123
+ `Tabs` recibe `label` e `items` con `{ value, label, content, disabled? }`. Admite `value/onValueChange`, `defaultValue`, `orientation` y `activationMode`. Selecciona inicialmente la primera pestaña habilitada si no se indica otra. Los paneles inactivos se desmontan: conserva los datos de formularios en el consumidor si deben persistir.
124
+
125
+ `Accordion` recibe `items` con `{ value, title, content, disabled? }`. En `type="single"` (por defecto), `value` es string y `collapsible` permite cerrar la última sección. En `type="multiple"`, `value` es string[] y cada sección se cierra independientemente. Ambos admiten estado inicial o controlado, grupo desactivado, `headingLevel` y `className`. Los valores de Tabs y Accordion deben ser únicos y estables.
126
+
127
+ `CustomSelect` admite opciones `{ value, label, icon?, disabled? }`, `value/onValueChange` o `defaultValue`, `name`, `required`, `description` y `error`. Los valores deben ser únicos y no vacíos. `CustomSelect` es la opción predeterminada para código nuevo. `Select` está marcado como `@deprecated`, pero se mantiene para consumidores existentes y casos todavía sin equivalente: selección múltiple, `size`, `optgroup` o referencias al elemento nativo.
128
+
129
+ Si CustomSelect, Combobox/Autocomplete, DatePicker/DateRangePicker o MultiSelect reciben `disabled` estando abiertos, cierran el panel, conservan su valor y bloquean cambios durante la salida. Al habilitarlos de nuevo permanecen cerrados. MultiSelect descarta el texto de búsqueda cada vez que se cierra, también con «Listo»; conserva las opciones seleccionadas.
130
+
131
+ `Modal` necesita `open`, `onOpenChange` y `title`. Acepta `description`, `footer`, `closeLabel` traducido y `closeOnOverlayClick`. Utiliza un único ancho de 32rem, limitado al espacio de pantalla disponible. La entrada y salida hacen zoom desde el centro. El foco queda dentro mientras está abierto y vuelve al elemento que lo abrió. Los cambios se realizan dentro de un Client Component de Next.js.
132
+
133
+ Radix Select, Dialog, Tooltip, Tabs, Accordion, Dropdown Menu, Context Menu, Radio Group y Toast son dependencias de ejecución instaladas automáticamente con este paquete. Gestionan las interacciones accesibles; el CSS de `danielgm-ui` determina su aspecto y animaciones. Los portales heredan los tokens de la raíz; para cambiar su tema, configura las variables en `:root`. Los campos utilizan una sombra interior como línea y una sombra exterior translúcida al enfocar, con transición de 180 ms. El error utiliza `--dgm-danger`. Movimiento reducido desactiva las transiciones y alto contraste utiliza contornos del sistema en lugar de sombras.
134
+
135
+ Los paneles anidados coordinan sus capas automáticamente, de modo que un selector abierto dentro de Modal o Drawer aparece sobre su contenido. Para integrarlos con otras capas de tu aplicación, configura en `:root` `--dgm-layer-base` (1100), `--dgm-layer-step` (10, al menos 2) y `--dgm-layer-toast` (2100). La profundidad y los proveedores internos no requieren configuración. Conserva una posición suficiente para Toast al cambiar la escala.
136
+
137
+ - React 18.2+ o 19; React no se incluye en el paquete.
138
+ - ESM con declaraciones TypeScript; no se genera CommonJS.
139
+ - CSS exportado explícitamente como `danielgm-ui/styles.css`.
140
+ - Controles nativos de formulario, etiquetas asociadas y foco visible.
141
+ - La documentación Astro del repositorio utiliza exactamente este paquete.
142
+
143
+ Licencia MIT.
144
+
145
+ ## Paneles, navegación y selección con búsqueda
146
+
147
+ `Popover` recibe un único Button como hijo, `title` y `content`. Admite `open/onOpenChange` o `defaultOpen`, `side`, `align`, `sideOffset`, `disabled`, `closeLabel` y `className`. Su contenido puede incluir campos y botones. Escape devuelve el foco al disparador; pulsar otro campo conserva el foco en ese campo.
148
+
149
+ `Drawer` comparte `open/onOpenChange`, título, descripción, contenido, pie y cierre de Modal. Añade `side="left" | "right" | "top" | "bottom"`; por defecto aparece por la derecha. El cuerpo tiene scroll independiente y el fondo queda bloqueado. `Sheet` es un alias con la misma API, útil para nombrar paneles inferiores. Traduce `closeLabel` para lectores de pantalla.
150
+
151
+ `Combobox` recibe `label` y opciones `{ value, label, icon?, disabled?, keywords? }` con valores únicos y no vacíos. La búsqueda encuentra etiquetas y sinónimos; las flechas y Enter seleccionan. Admite `value/onValueChange` o `defaultValue`, borrado con `clearable`, estados de formulario, mensajes personalizados y `locale="es" | "en"`. Su ref apunta al botón visible. `Autocomplete` es un alias: ambos eligen una opción del catálogo; no envían texto libre. Usa CustomSelect para listas cortas sin buscador.
152
+
153
+ `Pagination` recibe `page` desde 1, `pageCount` y `onPageChange`. Añade `siblings` (0–3), tamaños `sm/md/lg`, `disabled`, `locale` y etiqueta accesible. Las páginas visibles están acotadas; `pageCount=0` representa ausencia de resultados. El consumidor determina qué datos muestra cada página.
154
+
155
+ `Breadcrumbs` recibe `items` con `{ id, label, href? }`, `label`, un `separator` decorativo y `renderLink` opcional para integrar enlaces del router. El último elemento representa la página actual. Se puede renderizar como Server Component.
156
+
157
+ ## Identidad, progreso y grupos de opciones
158
+
159
+ `Avatar` utiliza `name` como nombre accesible y como origen de las iniciales. `src` carga una imagen; si falta o falla, aparece `fallback` o las iniciales. Admite tamaños `sm/md/lg`, formas `circle/square` y `onLoadingStatusChange`. Usa `decorative` si el nombre ya aparece junto a él.
160
+
161
+ `Progress` es un progress nativo, compatible con servidor. Recibe `label`, `value`, `max=100`, `showValue`, `valueText` y grosor `sm/md/lg`. Omite `value` para avance indeterminado. Los valores se limitan a 0..max; `valueText` puede expresar unidades como «3 de 8 archivos».
162
+
163
+ `CheckboxGroup` y `SwitchGroup` reúnen opciones `{ value, label, description?, disabled? }` dentro de fieldset y legend. Admite `value/onValueChange` o `defaultValue`, disposición vertical/horizontal, ayuda, error y desactivación. `required` exige al menos una opción habilitada; `FormData.getAll(name)` obtiene las marcadas. En formularios que se reinician, controla `value` y restablécelo en `onReset`.
164
+
165
+ ## Fechas y rangos
166
+
167
+ `DatePicker` entrega `string | null`. `DateRangePicker` entrega `{ from: string; to?: string } | null`: el primer clic establece `from` y el segundo completa `to`. Todas las fechas son **YYYY-MM-DD**, sin conversión a UTC. `locale` traduce calendario y formato visible; no cambia el valor enviado.
168
+
169
+ Ambos admiten estado controlado o inicial, `min` y `max` inclusivos, `disabledDates`, `defaultMonth`, `clearable`, etiqueta, ayuda, error, `name`, `required` y `disabled`. Un rango no puede atravesar fechas bloqueadas; un intervalo incompleto impide enviar el formulario. Los límites y excepciones deben ser fechas ISO válidas. Controla `value` y asígnale `null` al reiniciar el formulario.
170
+
171
+ ```tsx
172
+ 'use client';
173
+ import { useState } from 'react';
174
+ import { Button, DateRangePicker, type DateRangeValue } from 'danielgm-ui';
175
+
176
+ export default function Booking() {
177
+ const [stay, setStay] = useState<DateRangeValue | null>(null);
178
+ return (
179
+ <form
180
+ onReset={() => setStay(null)}
181
+ onSubmit={(event) => {
182
+ event.preventDefault();
183
+ const data = new FormData(event.currentTarget);
184
+ // Los campos son stay.from y stay.to; aquí puedes enviarlos a tu aplicación.
185
+ console.log(data.get('stay.from'), data.get('stay.to'));
186
+ }}
187
+ >
188
+ <DateRangePicker
189
+ label="Estancia"
190
+ locale="es"
191
+ name="stay"
192
+ required
193
+ value={stay}
194
+ onValueChange={setStay}
195
+ />
196
+ <Button type="submit">Reservar</Button>
197
+ <Button type="reset" variant="outline">
198
+ Reiniciar
199
+ </Button>
200
+ </form>
201
+ );
202
+ }
203
+ ```
204
+
205
+ ## Datos, archivos, comandos y procesos
206
+
207
+ `DataTable<T>` recibe `data`, `columns`, `getRowId` y `caption`. Cada columna tiene `id`, `header` y `accessor`; puede configurar `cell`, `sortable`, `searchable` y `align`. `accessor` devuelve un string o número para buscar y ordenar; `cell` personaliza su representación. Los IDs de filas y columnas deben ser únicos y estables.
208
+
209
+ La tabla filtra, ordena y pagina los datos locales sin mutarlos. Admite `sorting/onSortingChange` o `defaultSorting`, página controlada con `page/onPageChange`, `pageSize`, búsqueda global y estados `loading`/vacío. `selectable` habilita selección: `selection/onSelectionChange` o `defaultSelection` contienen IDs que se conservan entre páginas. No incluye virtualización ni un protocolo para consultas de servidor. El contenedor permite scroll horizontal en móvil.
210
+
211
+ `FileUpload` entrega `File[]` mediante `onValueChange`. Admite selección nativa y arrastre, `accept`, `multiple`, `maxFiles`, `maxSize` en bytes, vista previa, mensajes, `required`, `disabled` y `locale`. `onReject` informa de `{ file, reason }`, con reason `type`, `size`, `count` o `duplicate`. Controla `value` para reiniciar con `[]`. Las URLs de miniaturas se revocan al retirar los archivos.
212
+
213
+ FileUpload no hace peticiones ni introduce archivos automáticamente en FormData. Añádelos con `files.forEach(file => data.append('attachments', file))` y elige el transporte desde la aplicación. La validación de tipo/tamaño del navegador no sustituye la comprobación del servidor.
214
+
215
+ `CommandPalette` compone Modal y un buscador de acciones. Recibe `open/onOpenChange`, `title` y `actions` con `{ id, label, onSelect, keywords?, group?, icon?, disabled? }`. Permite textos localizados, personalización y `shortcut`: registra Ctrl/Cmd+K fuera de campos editables y elimina el listener al desmontar. Activa el atajo en una única instancia por aplicación. Cada callback decide la acción o navegación.
216
+
217
+ `Stepper` representa `steps` con `{ id, title, description?, disabled? }`, `value` como ID actual y una etiqueta accesible. Con `onValueChange` permite navegación; sin él es informativo. `linear=true` bloquea saltos a pasos futuros, `orientation` cambia la disposición y `completed` marca el proceso terminado. La aplicación gestiona el contenido, validación y botones de avance. La orientación horizontal pasa a vertical en móvil.
218
+
219
+ Los componentes nuevos comparten los tokens y el único import CSS del paquete. Radix Popover/Avatar, cmdk, React DayPicker, date-fns y TanStack Table son dependencias instaladas automáticamente. Las funciones y callbacks de ejemplos interactivos pertenecen a una frontera cliente en Next.js; Breadcrumbs y Progress pueden permanecer en servidor.
220
+
221
+ ## Menús, elección única, notificaciones y carga
222
+
223
+ `DropdownMenu` recibe un único botón como hijo e `items` con acciones `{ id, label, icon?, shortcut?, disabled?, danger?, onSelect? }` y separadores `{ id, type: 'separator' }`. Admite apertura controlada o inicial, lado, alineación y separación. `onSelect` funciona con teclado y puntero; `event.preventDefault()` mantiene abierto el menú. La leyenda `shortcut` no registra un atajo. Los disparadores con solo icono necesitan un nombre accesible.
224
+
225
+ `ContextMenu` utiliza el mismo contrato de acciones que DropdownMenu y se abre con clic derecho sobre su único hijo. Admite iconos, separadores, acciones desactivadas y `danger`; `onSelect(event)` puede llamar a `preventDefault()` para mantener abierto el menú. El hijo transmite props y ref, conserva su clic izquierdo y recibe foco con Tab; Shift+F10 o la tecla de menú contextual abren junto al elemento. En pantallas táctiles se abre al mantener pulsado. Escape devuelve el foco sin desplazar la página y un clic fuera conserva su destino. `label` identifica el menú y `onOpenChange` notifica su estado. `disabled` conserva el menú nativo y no desactiva la acción del hijo. Ofrece una alternativa visible para las acciones importantes. Utiliza Radix ContextMenu como base y comparte los estilos de DropdownMenu.
226
+
227
+ ```tsx
228
+ <ContextMenu
229
+ label="Acciones del archivo"
230
+ items={[{ id: 'open', label: 'Abrir', onSelect: () => openFile() }]}
231
+ >
232
+ <Button variant="outline" onClick={() => openFile()}>
233
+ Propuesta.pdf
234
+ </Button>
235
+ </ContextMenu>
236
+ ```
237
+
238
+ `RadioGroup` recibe `label` y `options` con `{ value, label, description?, disabled? }`. Admite `value/onValueChange` o `defaultValue`, orientación, navegación circular, grupo desactivado, ayuda y error. `name` y `required` participan en formularios. Los valores deben ser únicos y no vacíos. Si controlas el estado, actualízalo también en `onReset`.
239
+
240
+ `Skeleton` es un span decorativo compatible con Server Components. Admite `variant="text" | "rectangular" | "circular"`, dimensiones CSS o numéricas, `animate`, `className` y `style`. Para círculos utiliza ancho y alto iguales. Marca el contenedor que carga con `aria-busy` y anuncia el estado una sola vez; no añadas un anuncio por marcador.
241
+
242
+ ### Notificaciones compartidas
243
+
244
+ Envuelve una vez el área cliente con `ToastProvider`. Sus descendientes acceden a `notify(options)` y `dismiss(id?)` mediante `useToast()`. El identificador devuelto permite cerrar un aviso concreto; omitirlo cierra toda la pila y la cola. En Astro, Provider y consumidores deben vivir dentro de la misma isla React. No hay un almacén global que pueda mezclar peticiones SSR.
245
+
246
+ ```tsx
247
+ 'use client';
248
+ import { Button, ToastProvider, useToast } from 'danielgm-ui';
249
+
250
+ export default function Example() {
251
+ return (
252
+ <ToastProvider
253
+ label="Notificación"
254
+ closeLabel="Cerrar notificación"
255
+ viewportLabel="Notificaciones ({hotkey})"
256
+ duration={5000}
257
+ maxVisible={3}
258
+ >
259
+ <SaveButton />
260
+ </ToastProvider>
261
+ );
262
+ }
263
+
264
+ function SaveButton() {
265
+ const { notify } = useToast();
266
+ return (
267
+ <Button
268
+ onClick={() =>
269
+ notify({
270
+ title: 'Cambios guardados',
271
+ tone: 'success',
272
+ description: 'Tu proyecto está actualizado.',
273
+ })
274
+ }
275
+ >
276
+ Simular guardado
277
+ </Button>
278
+ );
279
+ }
280
+ ```
281
+
282
+ La pila aparece abajo a la derecha y reutiliza Alert. La duración empieza cuando el aviso se muestra, se pausa con puntero, foco o al cambiar de ventana y admite `0` para avisos persistentes. La X, Escape y un gesto hacia la derecha permiten cerrar. F8 enfoca la pila. Entrada y salida usan el zoom compartido de los paneles desde la esquina inferior derecha; movimiento reducido las desactiva.
283
+
284
+ Cada aviso admite `title`, `description`, `tone`, `duration`, `type`, `closeLabel`, `className` y `action: { label, altText, onClick }`. `type="background"` anuncia con cortesía; `foreground` interrumpe y se reserva para urgencias. El tono no decide la prioridad. Las acciones cierran el aviso y necesitan `altText` que explique una alternativa permanente, por ejemplo otro control de la página. Los tokens de portales deben definirse en la raíz.
285
+
286
+ ## Container: estructura de página
287
+
288
+ `Container` centra el contenido y limita su ancho al espacio disponible. Es estático y puede usarse en Server Components, sin `use client`.
289
+
290
+ ```tsx
291
+ import { Container } from 'danielgm-ui';
292
+
293
+ export default function Page() {
294
+ return (
295
+ <Container as="main" size="xl">
296
+ <h1>Mi proyecto</h1>
297
+ <p>Contenido principal.</p>
298
+ </Container>
299
+ );
300
+ }
301
+ ```
302
+
303
+ Importa también `danielgm-ui/styles.css` una vez en el layout. `size` admite `sm` (40rem), `md` (48rem), `lg` (64rem), `xl` (80rem, predeterminado) y `full` (sin límite adicional). El máximo incluye el padding. Todos se reducen al ancho del padre. `padding` vale `true` por defecto y aplica `clamp(1rem, 3vw, 2rem)` a ambos lados; usa `false` si el padre ya aporta ese espacio.
304
+
305
+ `as` admite `div` (predeterminado), `main`, `section`, `article`, `header`, `footer`, `aside` y `nav`. Conserva atributos HTML comunes, `aria-*`, `className` y `style`; no expone `ref`. Usa un único `main` visible por página. Puedes personalizar `maxInlineSize` y `paddingInline` con `style`. No añade altura, separación vertical, fondo ni distribución de hijos; coloca los fondos de ancho completo en una sección exterior.
306
+
307
+ ## Nuevos controles y estructuras
308
+
309
+ Slider, RangeSlider, MultiSelect, NumberInput, ToggleGroup, EmptyState, NavigationMenu, AspectRatio, ResizablePanels, TreeView, Carousel y AnimatedGrid. Slider y RangeSlider comparten página de documentación. Cada componente tiene ejemplos independientes en español e inglés, sus propiedades y recomendaciones de accesibilidad.
310
+
311
+ Todos se importan desde `danielgm-ui` y usan la misma hoja `danielgm-ui/styles.css`. `EmptyState` y `AspectRatio` son estáticos y compatibles con Server Components; las piezas interactivas mantienen su propia frontera `use client`.
312
+
313
+ - Formularios: `MultiSelect` envía los valores habilitados con `FormData.getAll(name)`; `RangeSlider` utiliza el mismo nombre para sus dos extremos. Los controles basados en estado React necesitan `value` y `onReset` para un reset controlado. `NumberInput` e `Input type="password"` también permiten el modo nativo no controlado.
314
+ - `ToggleGroup` permite selección única vacía o múltiple; no genera campos de formulario.
315
+ - `ResizablePanels` ajusta dos paneles con porcentajes, límites, teclado y puntero.
316
+ - `TreeView` ofrece selección única, expansión y búsqueda por prefijo; requiere IDs únicos y datos sin ciclos. El teclado mantiene visible la fila enfocada mediante un desplazamiento mínimo, sin seleccionar automáticamente ni desplazar al pulsar con el ratón.
317
+ - `Carousel` muestra una diapositiva por vista, sin autoplay ni bucle infinito, con botones, indicadores opcionales y gestos de scroll nativos.
318
+ - `AnimatedGrid` gestiona internamente la presencia y el movimiento de elementos con claves estables. Usa el efecto scale de Transition para altas, bajas y cambios de fila; desliza los cambios dentro de una fila y anima la altura. Conserva montado el contenido real para mantener su estado y foco.
319
+
320
+ ```tsx
321
+ 'use client';
322
+
323
+ import { useState } from 'react';
324
+ import { AnimatedGrid } from 'danielgm-ui';
325
+
326
+ type Item = { id: string; title: string };
327
+
328
+ function Projects() {
329
+ const [items, setItems] = useState<Item[]>([]);
330
+ return (
331
+ <>
332
+ <button
333
+ onClick={() =>
334
+ setItems((current) => [...current, { id: crypto.randomUUID(), title: 'Nuevo proyecto' }])
335
+ }
336
+ >
337
+ Añadir
338
+ </button>
339
+ <AnimatedGrid
340
+ items={items}
341
+ getKey={(item) => item.id}
342
+ renderItem={(item) => <article>{item.title}</article>}
343
+ columns={{ base: 1, sm: 2, md: 3 }}
344
+ gap="1rem"
345
+ />
346
+ </>
347
+ );
348
+ }
349
+ ```
350
+
351
+ Las columnas responden al ancho del contenedor (36, 56 y 76 rem). Usa identificadores únicos y estables; no uses el índice. El montaje inicial no se anima. Las salidas reservan su celda hasta finalizar y las copias visuales de cambios de fila son inertes y no accesibles. `duration` controla las fases (240 ms por defecto); con `duration={0}` o movimiento reducido, el resultado es inmediato.
352
+
353
+ ### Iconografía y contraseñas
354
+
355
+ Los iconos utilizan [Tabler Icons](https://tabler.io/icons), con licencia MIT. Conservamos sus avisos originales en `packages/ui/TABLER-LICENSE.txt` (incluido como `TABLER-LICENSE.txt` en npm) y en la página bilingüe de créditos. Los ejemplos que importan iconos requieren `npm install @tabler/icons-react`.
356
+
357
+ Usa `<Input type="password" label="Contraseña" locale="es" />`: el ojo se integra en el campo y reserva espacio para el texto. `visible`, `defaultVisible` y `onVisibleChange` permiten gestionar la visibilidad.
358
+
359
+ ### Presencia en Avatar
360
+
361
+ `Avatar` admite `status="online" | "busy" | "away" | "offline"`: un punto verde, rojo, ámbar o gris en la esquina inferior, adaptado a `size` y compatible con ambas formas. Al omitir `status` desaparece el punto. La presencia la proporciona tu aplicación y es independiente de `onLoadingStatusChange`, que informa de la carga de la imagen.
362
+
363
+ ```tsx
364
+ <Avatar name="Daniel Gracia" src="/perfil.jpg" status="online" locale="es" />
365
+ <Avatar name="Ana Torres" status="busy" statusLabel="En una reunión" locale="es" />
366
+ ```
367
+
368
+ El nombre accesible incluye la presencia. `locale` traduce las etiquetas (`en` por defecto) y `statusLabel` permite personalizarlas; la etiqueta también aparece como ayuda nativa del punto. Incluye texto visible si conocer la disponibilidad es importante, y usa `decorative` solo cuando el nombre y el estado ya estén indicados al lado. Los colores pueden ajustarse con `--dgm-avatar-online`, `--dgm-avatar-busy`, `--dgm-avatar-away` y `--dgm-avatar-offline`; el anillo usa `--dgm-surface`.
369
+
370
+ ### Tipografía
371
+
372
+ `Typography` aplica la escala sin proveedor, hooks ni JavaScript cliente. `variant` admite `title`, `subtitle`, `body`, `lead`, `label`, `caption` y `code`; `tone` admite `default`, `muted`, `primary`, `danger` e `inherit`. `as` elige el elemento HTML con independencia del estilo. Por defecto title produce h2, subtitle h3, label span, code code y las demás variantes p. Ajusta el nivel de encabezado a la página; `label` es un estilo visual, no sustituye la etiqueta accesible de un campo.
373
+
374
+ ```tsx
375
+ import { Typography } from 'danielgm-ui';
376
+
377
+ <Typography variant="title" as="h1">Mi proyecto</Typography>
378
+ <Typography>Un párrafo con los estilos de lectura predeterminados.</Typography>
379
+ <Typography variant="caption" tone="muted">Actualizado hoy</Typography>
380
+ ```
381
+
382
+ Conserva `className`, `style`, atributos HTML y ARIA; no expone `ref`, añade márgenes ni carga fuentes. El contenedor controla la separación y los textos largos pueden ajustarse. La fuente especial del título de la web de documentación pertenece únicamente a esa landing, no a Typography ni al paquete.
383
+
384
+ La fuente de los componentes se configura con `--dgm-font`; por defecto hereda la de la aplicación. Los controles internos utilizan esa misma familia, incluidos menús, TreeView, ToggleGroup y NavigationMenu. `--dgm-font-mono` configura la familia de texto monoespaciado. No se descargan fuentes externas.
385
+
386
+ Los tamaños tienen roles compartidos: `--dgm-text-body` y `--dgm-text-control` (1rem), `--dgm-text-label` y `--dgm-text-action` (0.9375rem), `--dgm-text-help` (0.875rem), `--dgm-text-caption` (0.75rem) y `--dgm-text-title` (1.25rem). Los valores de campos y selectores son regulares; etiquetas y acciones usan semibold. `--dgm-font-weight-*`, `--dgm-leading-*` y `--dgm-tracking-*` completan la escala. Las variantes `sm` y `lg` conservan sus tamaños propios dentro de ella.
387
+
388
+ ```css
389
+ :root {
390
+ --dgm-font: 'Segoe UI', system-ui, sans-serif;
391
+ --dgm-text-control: 1rem;
392
+ --dgm-text-label: 0.9375rem;
393
+ --dgm-text-help: 0.875rem;
394
+ }
395
+ ```
396
+
397
+ Define los tokens en `:root` para que también alcancen a los paneles portados a `body`. En un contenedor local puedes sobrescribir directamente la familia o los roles `--dgm-text-*`; configura la escala primitiva `--dgm-font-size-*` en la raíz, donde se resuelven sus alias. Cambiar selección, hover o foco conserva las métricas del texto para evitar saltos de línea.
398
+
399
+ ### Medidas de los campos
400
+
401
+ Input, NumberInput, Select, CustomSelect, Combobox/Autocomplete, DatePicker/DateRangePicker y MultiSelect comparten una altura mínima de `3rem` (48px con fuente raíz de 16px), configurable mediante `--dgm-control-min-height`. Los selectores mantienen la misma altura con placeholder, valor o icono. Los textos largos pueden aumentar la altura para evitar recortes; Textarea conserva sus filas y redimensionamiento.
402
+
403
+ ### Hover y selección
404
+
405
+ Los botones y controles de icono comparten `--dgm-hover-transition`: fondo, borde y texto cambian durante 150 ms con curva `ease`, tanto al entrar como al salir. No se anima su tamaño al pulsar y se respeta la preferencia de movimiento reducido. En enlaces que tengan apariencia de botón puedes reutilizar `transition: var(--dgm-hover-transition)` para mantener la misma respuesta.
406
+
407
+ Los menús y opciones comparten también esa transición. Personaliza juntos `--dgm-hover` (fondo neutro) y `--dgm-on-hover` (texto), con un fondo distinto de `--dgm-subtle` para que los botones secundarios tengan una respuesta visible. Las selecciones rellenas del calendario, ToggleGroup, TreeView y Pagination usan `--dgm-primary-hover` con `--dgm-on-primary`, igual que Button primary; el hover no debe perder el color de selección.
408
+
409
+ El foco de teclado debe seguir distinguiéndose de la selección: calendarios y TreeView utilizan `--dgm-on-primary` para el contorno interior sobre rellenos primarios. MultiSelect comparte con los demás campos la línea de control y los estados de foco y error. Las acciones secundarias, como quitar etiquetas o borrar valores, conservan los mismos tokens de hover.
410
+
411
+ Los botones de peligro separan su relleno del texto de error: `--dgm-danger-surface`, `--dgm-danger-hover` y `--dgm-on-danger` controlan Button danger, mientras que `--dgm-danger` controla mensajes y acciones de texto. Para un tema oscuro, adapta las parejas de fondo y texto en tu contenedor de tema; los componentes no requieren reglas de hover propias.
412
+
413
+ ### Scrollbars
414
+
415
+ Los paneles de opciones mantienen márgenes simétricos mediante `--dgm-list-padding` (0.5rem); `--dgm-option-padding-inline` (0.75rem) controla el espacio interior horizontal de las opciones de selectores y menús. No hay una compensación lateral fija para la scrollbar ni un hueco reservado cuando la lista no desborda.
416
+
417
+ CustomSelect, Combobox (y su alias Autocomplete), CommandPalette y MultiSelect desvanecen los extremos que tienen más opciones fuera de vista mediante una máscara CSS de 20 px. La transparencia deja ver el fondo real sin superponer un color; la barra nativa queda fuera del desvanecimiento cuando ocupa espacio propio. El menú lateral de la documentación comparte este estilo. Se actualiza al desplazar, filtrar o redimensionar; no intercepta clics ni cambia el foco. No se aplica si todo el contenido cabe y se desactiva en colores forzados. La implementación se comparte internamente; no requiere nuevas propiedades en los componentes.
418
+
419
+ Los componentes comparten scrollbars nativas finas, de color neutro y con pista transparente. Aparecen cuando hay desbordamiento, sin reservar un hueco en listas cortas. La rueda, el arrastre, el teclado y los gestos táctiles conservan su funcionamiento nativo. Carousel mantiene su barra oculta porque ofrece controles y gestos propios.
420
+
421
+ Para extender este estilo a toda tu página, añade `className="dgm-scrollbars"` a `html` (o `class="dgm-scrollbars"` en HTML/Astro). También puedes usar la clase en un contenedor concreto. Sobrescribe `--dgm-scrollbar-thumb`, `--dgm-scrollbar-thumb-hover` y `--dgm-scrollbar-track` para adaptar los colores a tu tema. La documentación usa el mismo estilo con tonos claros y oscuros; los bloques de código siempre usan los oscuros. La geometría y visibilidad finales respetan las capacidades del navegador y los ajustes del sistema.
422
+
423
+ ### Loader
424
+
425
+ `Loader` muestra actividad sin porcentaje con `variant="spinner" | "dots" | "bars" | "pulse"` y `size="sm" | "md" | "lg" | "xl"`. Admite `color`, `label`, `showLabel`, `locale` y `animate`. Las animaciones CSS respetan movimiento reducido y funcionan desde Server Components. Usa `decorative` si otro elemento ya anuncia la carga; en caso contrario incluye un estado accesible con texto localizado.
426
+
427
+ ```tsx
428
+ <Loader variant="dots" size="lg" locale="es" showLabel />
429
+ ```