danielgm-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +372 -0
  3. package/TABLER-LICENSE.txt +21 -0
  4. package/dist/accordion.d.ts +49 -0
  5. package/dist/accordion.js +14 -0
  6. package/dist/aspect-ratio.d.ts +8 -0
  7. package/dist/aspect-ratio.js +8 -0
  8. package/dist/avatar.d.ts +27 -0
  9. package/dist/avatar.js +15 -0
  10. package/dist/breadcrumbs.d.ts +20 -0
  11. package/dist/breadcrumbs.js +5 -0
  12. package/dist/button.d.ts +44 -0
  13. package/dist/button.js +25 -0
  14. package/dist/carousel.d.ts +19 -0
  15. package/dist/carousel.js +133 -0
  16. package/dist/choice-group.d.ts +36 -0
  17. package/dist/choice-group.js +27 -0
  18. package/dist/combobox.d.ts +37 -0
  19. package/dist/combobox.js +57 -0
  20. package/dist/command-palette.d.ts +28 -0
  21. package/dist/command-palette.js +90 -0
  22. package/dist/container.d.ts +21 -0
  23. package/dist/container.js +10 -0
  24. package/dist/context-menu.d.ts +21 -0
  25. package/dist/context-menu.js +50 -0
  26. package/dist/custom-select.d.ts +45 -0
  27. package/dist/custom-select.js +26 -0
  28. package/dist/data-table.d.ts +53 -0
  29. package/dist/data-table.js +104 -0
  30. package/dist/date-picker.d.ts +43 -0
  31. package/dist/date-picker.js +142 -0
  32. package/dist/display.d.ts +63 -0
  33. package/dist/display.js +43 -0
  34. package/dist/drawer.d.ts +12 -0
  35. package/dist/drawer.js +29 -0
  36. package/dist/dropdown-menu.d.ts +47 -0
  37. package/dist/dropdown-menu.js +24 -0
  38. package/dist/empty-state.d.ts +13 -0
  39. package/dist/empty-state.js +8 -0
  40. package/dist/fields.d.ts +62 -0
  41. package/dist/fields.js +70 -0
  42. package/dist/file-upload.d.ts +33 -0
  43. package/dist/file-upload.js +111 -0
  44. package/dist/index.d.ts +48 -0
  45. package/dist/index.js +48 -0
  46. package/dist/internal/field-open.d.ts +2 -0
  47. package/dist/internal/field-open.js +15 -0
  48. package/dist/internal/focus.d.ts +4 -0
  49. package/dist/internal/focus.js +27 -0
  50. package/dist/internal/icons.d.ts +29 -0
  51. package/dist/internal/icons.js +32 -0
  52. package/dist/internal/navigation-origin.d.ts +2 -0
  53. package/dist/internal/navigation-origin.js +23 -0
  54. package/dist/internal/overlay-layer.d.ts +12 -0
  55. package/dist/internal/overlay-layer.js +14 -0
  56. package/dist/internal/pagination.d.ts +6 -0
  57. package/dist/internal/pagination.js +36 -0
  58. package/dist/internal/scroll-hints.d.ts +8 -0
  59. package/dist/internal/scroll-hints.js +108 -0
  60. package/dist/internal/state.d.ts +2 -0
  61. package/dist/internal/state.js +13 -0
  62. package/dist/internal/trigger-label.d.ts +5 -0
  63. package/dist/internal/trigger-label.js +26 -0
  64. package/dist/loader.d.ts +27 -0
  65. package/dist/loader.js +13 -0
  66. package/dist/modal.d.ts +23 -0
  67. package/dist/modal.js +30 -0
  68. package/dist/multi-select.d.ts +23 -0
  69. package/dist/multi-select.js +63 -0
  70. package/dist/navigation-menu.d.ts +26 -0
  71. package/dist/navigation-menu.js +27 -0
  72. package/dist/number-input.d.ts +14 -0
  73. package/dist/number-input.js +37 -0
  74. package/dist/pagination.d.ts +24 -0
  75. package/dist/pagination.js +60 -0
  76. package/dist/popover.d.ts +30 -0
  77. package/dist/popover.js +27 -0
  78. package/dist/progress.d.ts +18 -0
  79. package/dist/progress.js +9 -0
  80. package/dist/radio-group.d.ts +46 -0
  81. package/dist/radio-group.js +16 -0
  82. package/dist/resizable-panels.d.ts +22 -0
  83. package/dist/resizable-panels.js +70 -0
  84. package/dist/skeleton.d.ts +17 -0
  85. package/dist/skeleton.js +9 -0
  86. package/dist/slider.d.ts +41 -0
  87. package/dist/slider.js +29 -0
  88. package/dist/stepper.d.ts +25 -0
  89. package/dist/stepper.js +25 -0
  90. package/dist/styles.css +3369 -0
  91. package/dist/tabs.d.ts +32 -0
  92. package/dist/tabs.js +8 -0
  93. package/dist/toast.d.ts +54 -0
  94. package/dist/toast.js +107 -0
  95. package/dist/toggle-group.d.ts +32 -0
  96. package/dist/toggle-group.js +16 -0
  97. package/dist/toggles.d.ts +33 -0
  98. package/dist/toggles.js +29 -0
  99. package/dist/tooltip.d.ts +26 -0
  100. package/dist/tooltip.js +16 -0
  101. package/dist/transition.d.ts +27 -0
  102. package/dist/transition.js +148 -0
  103. package/dist/tree-view.d.ts +22 -0
  104. package/dist/tree-view.js +128 -0
  105. package/dist/typography.d.ts +23 -0
  106. package/dist/typography.js +20 -0
  107. package/dist/utils.d.ts +13 -0
  108. package/dist/utils.js +17 -0
  109. package/package.json +73 -0
package/dist/tabs.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ /** Paneles relacionados con foco itinerante y asociaciones ARIA proporcionadas por Radix. */
2
+ import type { ReactElement, ReactNode } from 'react';
3
+ export interface TabItem {
4
+ /** Identificador estable, único y no vacío dentro del grupo. */
5
+ value: string;
6
+ /** Etiqueta visible; puede incluir un icono decorativo junto al texto. */
7
+ label: ReactNode;
8
+ /** Contenido del panel. Se desmonta cuando la pestaña queda inactiva. */
9
+ content: ReactNode;
10
+ /** Excluye la pestaña de la interacción y del recorrido por flechas. */
11
+ disabled?: boolean;
12
+ }
13
+ export interface TabsProps {
14
+ /** Nombre accesible del grupo de pestañas. */
15
+ label: string;
16
+ /** Pestañas y paneles en su orden visual. */
17
+ items: readonly TabItem[];
18
+ /** Pestaña controlada. No combinar con defaultValue. */
19
+ value?: string;
20
+ /** Pestaña inicial. Por defecto, la primera habilitada. */
21
+ defaultValue?: string;
22
+ /** Recibe el identificador de la pestaña seleccionada. */
23
+ onValueChange?: (value: string) => void;
24
+ /** Determina la distribución y las flechas de navegación. @defaultValue 'horizontal' */
25
+ orientation?: 'horizontal' | 'vertical';
26
+ /** manual necesita Enter o Espacio para activar tras mover el foco. @defaultValue 'automatic' */
27
+ activationMode?: 'automatic' | 'manual';
28
+ /** Clase del contenedor del grupo. */
29
+ className?: string;
30
+ }
31
+ /** El estado de formularios que deba sobrevivir al cambio de panel se conserva en el consumidor. */
32
+ export declare function Tabs({ label, items, value, defaultValue, onValueChange, orientation, activationMode, className, }: TabsProps): ReactElement;
package/dist/tabs.js ADDED
@@ -0,0 +1,8 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as Primitive from '@radix-ui/react-tabs';
4
+ import { cx } from './utils.js';
5
+ /** El estado de formularios que deba sobrevivir al cambio de panel se conserva en el consumidor. */
6
+ export function Tabs({ label, items, value, defaultValue, onValueChange, orientation = 'horizontal', activationMode = 'automatic', className, }) {
7
+ return (_jsxs(Primitive.Root, { value: value, defaultValue: defaultValue ?? items.find((item) => !item.disabled)?.value, onValueChange: onValueChange, orientation: orientation, activationMode: activationMode, className: cx('dgm-tabs', className), children: [_jsx(Primitive.List, { "aria-label": label, className: "dgm-tabs-list", children: items.map((item) => (_jsx(Primitive.Trigger, { value: item.value, disabled: item.disabled, className: "dgm-tabs-trigger", children: item.label }, item.value))) }), items.map((item) => (_jsx(Primitive.Content, { value: item.value, className: "dgm-tabs-panel", children: item.content }, item.value)))] }));
8
+ }
@@ -0,0 +1,54 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ import { type AlertProps } from './display.js';
3
+ export interface ToastOptions {
4
+ /** Título visible y anunciado. */
5
+ title: ReactNode;
6
+ /** Cuerpo opcional del anuncio. Evita controles interactivos; utiliza action. */
7
+ description?: ReactNode;
8
+ /** La intención visual es independiente de la urgencia del anuncio. @defaultValue 'info' */
9
+ tone?: AlertProps['tone'];
10
+ /** Milisegundos; 0 conserva el aviso hasta cerrarlo. Hereda del Provider si se omite. */
11
+ duration?: number;
12
+ /** background anuncia con cortesía; foreground interrumpe. @defaultValue 'background' */
13
+ type?: 'foreground' | 'background';
14
+ /** Acción opcional que cierra el aviso al terminar. Siempre debe existir otra forma de realizarla. */
15
+ action?: {
16
+ label: string;
17
+ altText: string;
18
+ onClick: () => void;
19
+ };
20
+ /** Etiqueta localizada para la X de este aviso; hereda del Provider. */
21
+ closeLabel?: string;
22
+ /** Clase de la notificación, en el portal. */
23
+ className?: string;
24
+ }
25
+ export interface ToastContextValue {
26
+ /** Añade a la cola y devuelve un identificador para su cierre posterior. */
27
+ notify: (options: ToastOptions) => string;
28
+ /** Cierra un aviso por ID, o todos si se omite. También retira los pendientes. */
29
+ dismiss: (id?: string) => void;
30
+ }
31
+ export interface ToastProviderProps {
32
+ /** Área de la aplicación cuyos descendientes pueden usar useToast. */
33
+ children: ReactNode;
34
+ /** Duración predeterminada en ms. 0 desactiva el cierre automático. @defaultValue 5000 */
35
+ duration?: number;
36
+ /** Límite de avisos visibles; los demás esperan por orden de llegada. @defaultValue 3 */
37
+ maxVisible?: number;
38
+ /** Nombre accesible localizado de cada notificación. @defaultValue 'Notification' */
39
+ label?: string;
40
+ /** Nombre de la región; {hotkey} se sustituye por F8. @defaultValue 'Notifications ({hotkey})' */
41
+ viewportLabel?: string;
42
+ /** Nombre accesible localizado de las X. @defaultValue 'Dismiss notification' */
43
+ closeLabel?: string;
44
+ /** Clase de la pila fija abajo a la derecha. */
45
+ className?: string;
46
+ }
47
+ /** Se usa dentro de ToastProvider, en un Client Component de React/Next.js. */
48
+ export declare function useToast(): ToastContextValue;
49
+ /**
50
+ * Monta un único Provider alrededor del área que comparte avisos. En Astro debe
51
+ * envolver a sus consumidores dentro de la misma isla React. Solo accede al DOM
52
+ * después de hidratar: servidor y primer render cliente producen el mismo HTML.
53
+ */
54
+ export declare function ToastProvider({ children, duration, maxVisible, label, viewportLabel, closeLabel, className, }: ToastProviderProps): ReactElement;
package/dist/toast.js ADDED
@@ -0,0 +1,107 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /** Notificaciones por contexto: sin estado global compartido entre peticiones SSR. */
4
+ import { CloseIcon } from './internal/icons.js';
5
+ import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, } from 'react';
6
+ import { createPortal } from 'react-dom';
7
+ import * as Primitive from '@radix-ui/react-toast';
8
+ import { Alert } from './display.js';
9
+ import { Button } from './button.js';
10
+ import { cx, hasContent } from './utils.js';
11
+ const ToastContext = createContext(null);
12
+ /** Se usa dentro de ToastProvider, en un Client Component de React/Next.js. */
13
+ export function useToast() {
14
+ const context = useContext(ToastContext);
15
+ if (!context)
16
+ throw new Error('useToast must be used within ToastProvider.');
17
+ return context;
18
+ }
19
+ /** Evita que valores negativos o no finitos se conviertan en temporizadores inmediatos. */
20
+ function durationInMs(value) {
21
+ return value === 0 ? Infinity : Number.isFinite(value) && value > 0 ? value : 5000;
22
+ }
23
+ /** Respeta las listas CSS, sus delays y unidades; una salida no bloquea la cola indefinidamente. */
24
+ function exitDuration(style) {
25
+ const times = (value) => value.split(',').map((part) => {
26
+ const number = Number.parseFloat(part) || 0;
27
+ return part.trim().endsWith('ms') ? number : number * 1000;
28
+ });
29
+ const durations = times(style.animationDuration);
30
+ const delays = times(style.animationDelay);
31
+ const iterations = style.animationIterationCount.split(',').map((value) => {
32
+ const count = Number.parseFloat(value);
33
+ return Number.isFinite(count) ? Math.max(0, count) : 1;
34
+ });
35
+ return Math.max(0, ...style.animationName
36
+ .split(',')
37
+ .map((name, index) => name.trim() && name.trim() !== 'none'
38
+ ? durations[index % durations.length] * iterations[index % iterations.length] +
39
+ delays[index % delays.length]
40
+ : 0));
41
+ }
42
+ /**
43
+ * Cada aviso visible posee su ciclo de salida. Radix pausa el tiempo al enfocar,
44
+ * pasar el puntero por la pila o cambiar de ventana y permite cerrar por swipe.
45
+ */
46
+ function ToastItem({ notice, duration, closeLabel, dismiss, remove, }) {
47
+ const element = useRef(null);
48
+ useEffect(() => {
49
+ if (notice.open)
50
+ return;
51
+ if (element.current?.contains(document.activeElement)) {
52
+ element.current.closest('ol')?.focus({ preventScroll: true });
53
+ }
54
+ element.current?.setAttribute('inert', '');
55
+ // La cola avanza al terminar el CSS. El respaldo también cubre un CSS ausente
56
+ // o modificado por el consumidor; sin movimiento, no se introduce demora.
57
+ const style = element.current && getComputedStyle(element.current);
58
+ const milliseconds = style ? exitDuration(style) : 0;
59
+ if (milliseconds <= 0) {
60
+ remove(notice.id);
61
+ return;
62
+ }
63
+ const timeout = window.setTimeout(() => remove(notice.id), milliseconds + 50);
64
+ return () => window.clearTimeout(timeout);
65
+ }, [notice.open, notice.id, remove]);
66
+ return (_jsxs(Primitive.Root, { ref: element, open: notice.open, "aria-hidden": !notice.open || undefined, type: notice.type ?? 'background', duration: durationInMs(notice.duration ?? duration), onOpenChange: (open) => {
67
+ if (!open)
68
+ dismiss(notice.id);
69
+ }, className: cx('dgm-toast', `dgm-tone--${notice.tone ?? 'info'}`, notice.className), style: { touchAction: 'pan-y' }, onAnimationEnd: (event) => {
70
+ if (!notice.open && event.target === event.currentTarget)
71
+ remove(notice.id);
72
+ }, children: [_jsxs(Alert, { tone: notice.tone, title: _jsx(Primitive.Title, { children: notice.title }), children: [hasContent(notice.description) && (_jsx(Primitive.Description, { children: notice.description })), notice.action && (_jsx(Primitive.Action, { asChild: true, altText: notice.action.altText, children: _jsx(Button, { size: "sm", variant: "outline", className: "dgm-toast-action", onClick: notice.action.onClick, children: notice.action.label }) }))] }), _jsx(Primitive.Close, { className: "dgm-icon-close", "aria-label": notice.closeLabel ?? closeLabel, children: _jsx(CloseIcon, {}) })] }));
73
+ }
74
+ /**
75
+ * Monta un único Provider alrededor del área que comparte avisos. En Astro debe
76
+ * envolver a sus consumidores dentro de la misma isla React. Solo accede al DOM
77
+ * después de hidratar: servidor y primer render cliente producen el mismo HTML.
78
+ */
79
+ export function ToastProvider({ children, duration = 5000, maxVisible = 3, label = 'Notification', viewportLabel = 'Notifications ({hotkey})', closeLabel = 'Dismiss notification', className, }) {
80
+ const [notices, setNotices] = useState([]);
81
+ const [host, setHost] = useState(null);
82
+ const prefix = useId();
83
+ const sequence = useRef(0);
84
+ const limit = Number.isFinite(maxVisible) ? Math.max(1, Math.floor(maxVisible)) : 3;
85
+ useEffect(() => {
86
+ setHost(document.body);
87
+ }, []);
88
+ const notify = useCallback((options) => {
89
+ const id = `${prefix}-${++sequence.current}`;
90
+ setNotices((current) => [...current, { ...options, id, open: true }]);
91
+ return id;
92
+ }, [prefix]);
93
+ const remove = useCallback((id) => {
94
+ setNotices((current) => current.filter((notice) => notice.id !== id));
95
+ }, []);
96
+ const dismiss = useCallback((id) => {
97
+ setNotices((current) => current.flatMap((notice, index) => {
98
+ if (id !== undefined && notice.id !== id)
99
+ return [notice];
100
+ // Los avisos en espera no están montados: no tienen una salida que esperar.
101
+ return index < limit ? [{ ...notice, open: false }] : [];
102
+ }));
103
+ }, [limit]);
104
+ const context = useMemo(() => ({ notify, dismiss }), [notify, dismiss]);
105
+ return (_jsx(ToastContext.Provider, { value: context, children: _jsxs(Primitive.Provider, { label: label, duration: durationInMs(duration), swipeDirection: "right", children: [children, host &&
106
+ createPortal(_jsxs(_Fragment, { children: [notices.slice(0, limit).map((notice) => (_jsx(ToastItem, { notice: notice, duration: duration, closeLabel: closeLabel, dismiss: dismiss, remove: remove }, notice.id))), _jsx(Primitive.Viewport, { label: viewportLabel, className: cx('dgm-toast-viewport', className) })] }), host)] }) }));
107
+ }
@@ -0,0 +1,32 @@
1
+ /** Botones con estado: Radix aporta foco itinerante y flechas; anuncia aria-checked en single y aria-pressed en multiple. */
2
+ import type { ReactElement, ReactNode } from 'react';
3
+ export interface ToggleOption {
4
+ value: string;
5
+ label: string;
6
+ icon?: ReactNode;
7
+ disabled?: boolean;
8
+ }
9
+ interface ToggleGroupBase {
10
+ label: string;
11
+ options: readonly ToggleOption[];
12
+ disabled?: boolean;
13
+ orientation?: 'horizontal' | 'vertical';
14
+ size?: 'sm' | 'md' | 'lg';
15
+ /** Oculta el texto visible de las opciones; label sigue siendo su nombre accesible. */
16
+ iconOnly?: boolean;
17
+ className?: string;
18
+ }
19
+ export type ToggleGroupProps = ToggleGroupBase & ({
20
+ type?: 'single';
21
+ value?: string;
22
+ defaultValue?: string;
23
+ onValueChange?: (value: string) => void;
24
+ } | {
25
+ type: 'multiple';
26
+ value?: string[];
27
+ defaultValue?: string[];
28
+ onValueChange?: (value: string[]) => void;
29
+ });
30
+ /** No es un campo de formulario: single permite desmarcar la opción activa. Usa RadioGroup si es obligatoria. */
31
+ export declare function ToggleGroup({ label, options, disabled, orientation, size, iconOnly, className, ...state }: ToggleGroupProps): ReactElement;
32
+ export {};
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as Primitive from '@radix-ui/react-toggle-group';
4
+ import { cx } from './utils.js';
5
+ /** No es un campo de formulario: single permite desmarcar la opción activa. Usa RadioGroup si es obligatoria. */
6
+ export function ToggleGroup({ label, options, disabled, orientation = 'horizontal', size = 'md', iconOnly = false, className, ...state }) {
7
+ const children = options.map((option) => (_jsxs(Primitive.Item, { value: option.value, disabled: option.disabled, "aria-label": option.label, className: cx('dgm-toggle-item', `dgm-toggle-item--${size}`, iconOnly && 'dgm-toggle-item--icon'), children: [option.icon && _jsx("span", { "aria-hidden": "true", children: option.icon }), !iconOnly && option.label] }, option.value)));
8
+ const props = {
9
+ 'aria-label': label,
10
+ disabled,
11
+ orientation,
12
+ className: cx('dgm-toggle-group', className),
13
+ children,
14
+ };
15
+ return state.type === 'multiple' ? (_jsx(Primitive.Root, { ...props, ...state })) : (_jsx(Primitive.Root, { ...props, ...state, type: "single" }));
16
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Controles booleanos. Ambos usan un input nativo para conservar teclado, foco,
3
+ * formularios y eventos. Solo cambian su semántica anunciada y su presentación CSS.
4
+ */
5
+ import { type InputHTMLAttributes, type ReactNode } from 'react';
6
+ /**
7
+ * Atributos de un checkbox nativo, sin permitir cambiar type, size o children.
8
+ * checked/onChange controla el estado desde React; defaultChecked deja el estado al DOM.
9
+ * name y value se envían en FormData únicamente cuando el control está marcado.
10
+ */
11
+ export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'size' | 'children'> {
12
+ /** Etiqueta visible y estable que identifica la opción. */
13
+ label: ReactNode;
14
+ /** Ayuda opcional asociada mediante aria-describedby. */
15
+ description?: ReactNode;
16
+ /** Clase del contenedor. className afecta al input nativo. */
17
+ wrapperClassName?: string;
18
+ }
19
+ /** Mismo contrato de estado y formularios que Checkbox, con semántica de interruptor. */
20
+ export interface SwitchProps extends CheckboxProps {
21
+ }
22
+ /**
23
+ * Selección independiente con ref al HTMLInputElement.
24
+ * La etiqueta es clicable gracias a htmlFor; Espacio alterna el estado de forma nativa.
25
+ */
26
+ export declare const Checkbox: import("react").ForwardRefExoticComponent<CheckboxProps & import("react").RefAttributes<HTMLInputElement>>;
27
+ /**
28
+ * Interruptor binario con role="switch" y ref al HTMLInputElement.
29
+ * Sigue siendo type="checkbox": no necesita eventos artificiales para teclado o forms.
30
+ * La etiqueta debe permanecer igual al activar/desactivar; el estado lo aporta checked.
31
+ * No implementa un tercer estado indeterminado.
32
+ */
33
+ export declare const Switch: import("react").ForwardRefExoticComponent<SwitchProps & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Controles booleanos. Ambos usan un input nativo para conservar teclado, foco,
5
+ * formularios y eventos. Solo cambian su semántica anunciada y su presentación CSS.
6
+ */
7
+ import { forwardRef, useId } from 'react';
8
+ import { cx, hasContent } from './utils.js';
9
+ /**
10
+ * Selección independiente con ref al HTMLInputElement.
11
+ * La etiqueta es clicable gracias a htmlFor; Espacio alterna el estado de forma nativa.
12
+ */
13
+ export const Checkbox = forwardRef(function Checkbox({ label, description, wrapperClassName, className, id, 'aria-describedby': describedBy, ...props }, ref) {
14
+ // La llamada incondicional mantiene estable el orden de hooks si cambia la prop id.
15
+ const generatedId = useId();
16
+ const controlId = id ?? `dgm-${generatedId}`;
17
+ return (_jsxs("div", { className: cx('dgm-choice', wrapperClassName), children: [_jsx("input", { ...props, ref: ref, type: "checkbox", id: controlId, "aria-describedby": cx(describedBy, hasContent(description) && `${controlId}-description`) || undefined, className: cx('dgm-checkbox', className) }), _jsxs("div", { className: "dgm-choice-text", children: [_jsx("label", { className: "dgm-label", htmlFor: controlId, children: label }), hasContent(description) && (_jsx("span", { className: "dgm-description", id: `${controlId}-description`, children: description }))] })] }));
18
+ });
19
+ /**
20
+ * Interruptor binario con role="switch" y ref al HTMLInputElement.
21
+ * Sigue siendo type="checkbox": no necesita eventos artificiales para teclado o forms.
22
+ * La etiqueta debe permanecer igual al activar/desactivar; el estado lo aporta checked.
23
+ * No implementa un tercer estado indeterminado.
24
+ */
25
+ export const Switch = forwardRef(function Switch({ label, description, wrapperClassName, className, id, 'aria-describedby': describedBy, ...props }, ref) {
26
+ const generatedId = useId();
27
+ const controlId = id ?? `dgm-${generatedId}`;
28
+ return (_jsxs("div", { className: cx('dgm-choice', wrapperClassName), children: [_jsx("input", { ...props, ref: ref, type: "checkbox", role: "switch", id: controlId, "aria-describedby": cx(describedBy, hasContent(description) && `${controlId}-description`) || undefined, className: cx('dgm-switch', className) }), _jsxs("div", { className: "dgm-choice-text", children: [_jsx("label", { className: "dgm-label", htmlFor: controlId, children: label }), hasContent(description) && (_jsx("span", { className: "dgm-description", id: `${controlId}-description`, children: description }))] })] }));
29
+ });
@@ -0,0 +1,26 @@
1
+ /** Ayuda contextual sobre un disparador existente. Radix gestiona foco, hover, Escape y colisiones. */
2
+ import type { ReactElement, ReactNode } from 'react';
3
+ export interface TooltipProps {
4
+ /** Un único elemento enfocable que acepte ref y props, como Button. No usar Fragment. */
5
+ children: ReactElement;
6
+ /** Ayuda breve no interactiva. No colocar aquí enlaces, botones ni información imprescindible. */
7
+ content: ReactNode;
8
+ /** Lado preferido; puede cambiar si no hay espacio. @defaultValue 'top' */
9
+ side?: 'top' | 'right' | 'bottom' | 'left';
10
+ /** Alineación respecto al disparador. @defaultValue 'center' */
11
+ align?: 'start' | 'center' | 'end';
12
+ /** Separación del disparador en píxeles. @defaultValue 6 */
13
+ sideOffset?: number;
14
+ /** Espera al pasar el puntero; el foco de teclado abre inmediatamente. @defaultValue 300 */
15
+ delayDuration?: number;
16
+ /** Apertura controlada. No combinar con defaultOpen. */
17
+ open?: boolean;
18
+ /** Apertura inicial no controlada. @defaultValue false */
19
+ defaultOpen?: boolean;
20
+ /** Notifica los cambios solicitados por interacción. */
21
+ onOpenChange?: (open: boolean) => void;
22
+ /** Clase del contenido flotante, no del disparador. */
23
+ className?: string;
24
+ }
25
+ /** Provider local: se puede usar sin instalar un proveedor global en la aplicación. */
26
+ export declare function Tooltip({ children, content, side, align, sideOffset, delayDuration, open, defaultOpen, onOpenChange, className, }: TooltipProps): ReactElement;
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as Primitive from '@radix-ui/react-tooltip';
4
+ import { isRestoringFocus } from './internal/focus.js';
5
+ import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
6
+ import { cx } from './utils.js';
7
+ /** Provider local: se puede usar sin instalar un proveedor global en la aplicación. */
8
+ export function Tooltip({ children, content, side = 'top', align = 'center', sideOffset = 6, delayDuration = 300, open, defaultOpen, onOpenChange, className, }) {
9
+ const { depth, style: layerStyle } = useOverlayLayer();
10
+ return (_jsx(OverlayLayerProvider, { depth: depth, children: _jsx(Primitive.Provider, { delayDuration: delayDuration, children: _jsxs(Primitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, children: [_jsx(Primitive.Trigger, { asChild: true, onFocus: (event) => {
11
+ // El foco sigue llegando al botón y a sus manejadores. Solo se omite
12
+ // la apertura automática de Radix al volver desde una capa de la librería.
13
+ if (isRestoringFocus(event.currentTarget))
14
+ event.preventDefault();
15
+ }, children: children }), _jsx(Primitive.Portal, { children: _jsx(Primitive.Content, { style: layerStyle, side: side, align: align, sideOffset: sideOffset, collisionPadding: 12, className: cx('dgm-tooltip', className), children: content }) })] }) }) }));
16
+ }
@@ -0,0 +1,27 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ /** Entrada y salida de contenido condicional sin desmontarlo antes de terminar la secuencia. */
3
+ export interface TransitionProps {
4
+ /** Visibilidad solicitada. Mantén Transition montado para poder animar la salida. */
5
+ show: boolean;
6
+ /** Contenido dentro de divs; no usar directamente en table, select o listas que exijan li. */
7
+ children: ReactNode;
8
+ /** Efecto visual, independiente de la altura. @defaultValue 'fade' */
9
+ variant?: 'fade' | 'scale' | 'slide';
10
+ /** Milisegundos por fase. Con collapse hay dos fases por recorrido (altura y efecto). Cero es inmediato. @defaultValue 240 */
11
+ duration?: number;
12
+ /** Anima también si show ya es true al montar. @defaultValue false */
13
+ appear?: boolean;
14
+ /** Abre primero el espacio y después muestra el contenido; cierra en orden inverso. Incluye la separación dentro de children. @defaultValue false */
15
+ collapse?: boolean;
16
+ /** Se ejecuta una sola vez al completar toda la salida, para retirar el elemento de una colección. */
17
+ onExited?: () => void;
18
+ /** Clase del contenedor exterior. */
19
+ className?: string;
20
+ }
21
+ /**
22
+ * Con collapse: closed → expanding → entering → open; la salida invierte ese orden.
23
+ * El contenido conserva su altura intrínseca aunque sea invisible durante expanding/collapsing.
24
+ * Los eventos del propio contenedor avanzan las fases; un plazo de respaldo evita bloqueos.
25
+ * Reabrir o desmontar cancela los manejadores y plazos anteriores. El consumidor gestiona el foco.
26
+ */
27
+ export declare function Transition({ show, children, variant, duration, appear, collapse, onExited, className, }: TransitionProps): ReactElement | null;
@@ -0,0 +1,148 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState, } from 'react';
4
+ import { cx } from './utils.js';
5
+ /**
6
+ * Con collapse: closed → expanding → entering → open; la salida invierte ese orden.
7
+ * El contenido conserva su altura intrínseca aunque sea invisible durante expanding/collapsing.
8
+ * Los eventos del propio contenedor avanzan las fases; un plazo de respaldo evita bloqueos.
9
+ * Reabrir o desmontar cancela los manejadores y plazos anteriores. El consumidor gestiona el foco.
10
+ */
11
+ export function Transition({ show, children, variant = 'fade', duration = 240, appear = false, collapse = false, onExited, className, }) {
12
+ const [presence, setPresence] = useState({
13
+ show,
14
+ collapse,
15
+ phase: !show ? 'closed' : !appear ? 'open' : collapse ? 'expanding' : 'entering',
16
+ });
17
+ const [expanded, setExpanded] = useState(show && !appear);
18
+ const visualRef = useRef(null);
19
+ const layoutRef = useRef(null);
20
+ const exitCallback = useRef(onExited);
21
+ const milliseconds = Number.isFinite(duration) ? Math.max(0, duration) : 240;
22
+ // Ajuste derivado antes del commit: un cambio de intención no pinta la fase anterior.
23
+ if (presence.show !== show || presence.collapse !== collapse) {
24
+ let phase;
25
+ if (show) {
26
+ phase =
27
+ collapse &&
28
+ (presence.phase === 'closed' ||
29
+ presence.phase === 'collapsing' ||
30
+ presence.phase === 'expanding')
31
+ ? 'expanding'
32
+ : 'entering';
33
+ if (collapse && !presence.collapse)
34
+ setExpanded(true);
35
+ }
36
+ else {
37
+ phase =
38
+ presence.phase === 'closed'
39
+ ? 'closed'
40
+ : collapse && (presence.phase === 'expanding' || presence.phase === 'collapsing')
41
+ ? 'collapsing'
42
+ : 'exiting';
43
+ }
44
+ setPresence({ show, collapse, phase });
45
+ }
46
+ useEffect(() => {
47
+ exitCallback.current = onExited;
48
+ }, [onExited]);
49
+ const { phase } = presence;
50
+ const interactive = phase === 'entering' || phase === 'open';
51
+ useEffect(() => {
52
+ const root = collapse ? layoutRef.current : visualRef.current;
53
+ if (!root || phase === 'closed')
54
+ return;
55
+ // El atributo DOM conserva compatibilidad con los tipos antiguos de React 18.
56
+ root.toggleAttribute('inert', !interactive);
57
+ if (phase === 'open')
58
+ return;
59
+ const sizing = phase === 'expanding' || phase === 'collapsing';
60
+ const node = sizing ? layoutRef.current : visualRef.current;
61
+ if (!node)
62
+ return;
63
+ if (phase === 'expanding') {
64
+ // Registrar la fila cerrada permite arrancar la transición desde un montaje nuevo.
65
+ node.getBoundingClientRect();
66
+ setExpanded(true);
67
+ }
68
+ else if (phase === 'collapsing') {
69
+ setExpanded(false);
70
+ }
71
+ let finished = false;
72
+ const finish = (immediate = false) => {
73
+ if (finished)
74
+ return;
75
+ finished = true;
76
+ const next = immediate
77
+ ? show
78
+ ? 'open'
79
+ : 'closed'
80
+ : phase === 'expanding'
81
+ ? 'entering'
82
+ : phase === 'entering'
83
+ ? 'open'
84
+ : phase === 'exiting' && collapse
85
+ ? 'collapsing'
86
+ : 'closed';
87
+ if (immediate)
88
+ setExpanded(show);
89
+ setPresence((current) => ({ ...current, phase: next }));
90
+ if (next === 'closed')
91
+ exitCallback.current?.();
92
+ };
93
+ const onAnimationEnd = (event) => {
94
+ const name = `dgm-transition-${variant}-${phase === 'entering' ? 'in' : 'out'}`;
95
+ if (!sizing && event.target === node && event.animationName === name)
96
+ finish();
97
+ };
98
+ const onSizeEnd = (event) => {
99
+ if (sizing && event.target === node && event.propertyName === 'grid-template-rows')
100
+ finish();
101
+ };
102
+ const preference = window.matchMedia?.('(prefers-reduced-motion: reduce)');
103
+ const onPreferenceChange = () => {
104
+ if (preference?.matches)
105
+ finish(true);
106
+ };
107
+ if (milliseconds === 0 || preference?.matches) {
108
+ finish(true);
109
+ return;
110
+ }
111
+ const styles = window.getComputedStyle(node);
112
+ const hasMotion = sizing
113
+ ? styles.transitionProperty
114
+ .split(',')
115
+ .some((property) => ['grid-template-rows', 'all'].includes(property.trim())) &&
116
+ parseFloat(styles.transitionDuration) > 0
117
+ : !!styles.animationName && styles.animationName !== 'none';
118
+ // Sin CSS de esta fase, avanzar inmediatamente a la siguiente sin retener contenido.
119
+ if (!hasMotion) {
120
+ finish();
121
+ return;
122
+ }
123
+ node.addEventListener('animationend', onAnimationEnd);
124
+ node.addEventListener('animationcancel', onAnimationEnd);
125
+ node.addEventListener('transitionend', onSizeEnd);
126
+ preference?.addEventListener('change', onPreferenceChange);
127
+ const timeout = window.setTimeout(() => finish(), milliseconds + 50);
128
+ return () => {
129
+ finished = true;
130
+ window.clearTimeout(timeout);
131
+ node.removeEventListener('animationend', onAnimationEnd);
132
+ node.removeEventListener('animationcancel', onAnimationEnd);
133
+ node.removeEventListener('transitionend', onSizeEnd);
134
+ preference?.removeEventListener('change', onPreferenceChange);
135
+ };
136
+ }, [phase, show, collapse, interactive, milliseconds, variant]);
137
+ if (phase === 'closed')
138
+ return null;
139
+ const style = { '--dgm-transition-duration': `${milliseconds}ms` };
140
+ const visual = (_jsx("div", { ref: visualRef, className: cx('dgm-transition', !collapse && className), "data-state": phase === 'expanding' || phase === 'collapsing'
141
+ ? 'hidden'
142
+ : phase === 'exiting'
143
+ ? 'closed'
144
+ : 'open', "data-variant": variant, "data-enter": phase === 'entering' || undefined, "aria-hidden": !interactive || undefined, style: style, children: children }));
145
+ if (!collapse)
146
+ return visual;
147
+ return (_jsx("div", { ref: layoutRef, className: cx('dgm-transition-collapse', className), "data-state": show ? 'open' : 'closed', "data-expanded": (phase !== 'collapsing' && expanded) || undefined, "aria-hidden": !interactive || undefined, style: style, children: _jsx("div", { className: "dgm-transition-clip", children: visual }) }));
148
+ }
@@ -0,0 +1,22 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ export interface TreeNode {
3
+ /** ID único en todo el árbol, incluso entre ramas distintas. */
4
+ id: string;
5
+ label: string;
6
+ icon?: ReactNode;
7
+ disabled?: boolean;
8
+ children?: readonly TreeNode[];
9
+ }
10
+ export interface TreeViewProps {
11
+ label: string;
12
+ items: readonly TreeNode[];
13
+ value?: string;
14
+ defaultValue?: string;
15
+ onValueChange?: (value: string) => void;
16
+ expanded?: string[];
17
+ defaultExpanded?: string[];
18
+ onExpandedChange?: (expanded: string[]) => void;
19
+ className?: string;
20
+ }
21
+ /** Flechas, Home/End, Enter/Espacio y búsqueda por prefijo. Un nodo desactivado desactiva también su rama. */
22
+ export declare function TreeView({ label, items, value, defaultValue, onValueChange, expanded, defaultExpanded, onExpandedChange, className, }: TreeViewProps): ReactElement;