danielgm-ui 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-palette.js +13 -2
- package/dist/context-menu.d.ts +3 -1
- package/dist/context-menu.js +4 -3
- package/dist/data-table.d.ts +3 -1
- package/dist/data-table.js +2 -2
- package/dist/display.d.ts +4 -2
- package/dist/drawer.js +19 -12
- package/dist/dropdown-menu.d.ts +5 -1
- package/dist/dropdown-menu.js +4 -3
- package/dist/fields.js +9 -1
- package/dist/internal/menu-item-focus.d.ts +5 -0
- package/dist/internal/menu-item-focus.js +11 -0
- package/dist/internal/tooltip-policy.d.ts +11 -0
- package/dist/internal/tooltip-policy.js +4 -0
- package/dist/modal.d.ts +1 -1
- package/dist/modal.js +22 -15
- package/dist/navigation-menu.d.ts +3 -1
- package/dist/navigation-menu.js +2 -2
- package/dist/pagination.js +3 -2
- package/dist/styles.css +112 -17
- package/dist/toast.d.ts +2 -2
- package/dist/toast.js +3 -1
- package/dist/tooltip.d.ts +6 -4
- package/dist/tooltip.js +35 -4
- package/package.json +1 -1
package/dist/command-palette.js
CHANGED
|
@@ -8,7 +8,9 @@ import { cx } from './utils.js';
|
|
|
8
8
|
import { ScrollHints } from './internal/scroll-hints.js';
|
|
9
9
|
export function CommandPalette({ open, onOpenChange, actions, title, placeholder, emptyMessage, shortcut = false, locale = 'en', closeLabel, className, }) {
|
|
10
10
|
const searchInput = useRef(null);
|
|
11
|
+
const results = useRef(null);
|
|
11
12
|
const interacted = useRef(false);
|
|
13
|
+
const [search, setSearch] = useState('');
|
|
12
14
|
const [active, setActive] = useState('');
|
|
13
15
|
const [inputMode, setInputMode] = useState('idle');
|
|
14
16
|
useEffect(() => {
|
|
@@ -18,11 +20,19 @@ export function CommandPalette({ open, onOpenChange, actions, title, placeholder
|
|
|
18
20
|
setInputMode('idle');
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
23
|
+
setSearch('');
|
|
21
24
|
// Modal registra primero el disparador para devolverle el foco al cerrar.
|
|
22
25
|
// En el siguiente frame llevamos el foco al buscador sin alterar ese destino.
|
|
23
26
|
const frame = requestAnimationFrame(() => searchInput.current?.focus({ preventScroll: true }));
|
|
24
27
|
return () => cancelAnimationFrame(frame);
|
|
25
28
|
}, [open]);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
// cmdk filtra y reordena las filas antes de este efecto. Cada consulta empieza
|
|
31
|
+
// arriba, incluso si conserva la misma acción activa; las flechas y la rueda
|
|
32
|
+
// mantienen su desplazamiento mientras no cambie el texto de búsqueda.
|
|
33
|
+
if (results.current)
|
|
34
|
+
results.current.scrollTop = 0;
|
|
35
|
+
}, [search]);
|
|
26
36
|
const latest = useRef({ open, onOpenChange });
|
|
27
37
|
latest.current = { open, onOpenChange };
|
|
28
38
|
useEffect(() => {
|
|
@@ -76,10 +86,11 @@ export function CommandPalette({ open, onOpenChange, actions, title, placeholder
|
|
|
76
86
|
interacted.current = true;
|
|
77
87
|
setInputMode('keyboard');
|
|
78
88
|
}
|
|
79
|
-
}, children: [_jsx(Command.Input, { ref: searchInput, onValueChange: () => {
|
|
89
|
+
}, children: [_jsx(Command.Input, { ref: searchInput, onValueChange: (value) => {
|
|
80
90
|
interacted.current = true;
|
|
81
91
|
setInputMode('keyboard');
|
|
82
|
-
|
|
92
|
+
setSearch(value);
|
|
93
|
+
}, className: "dgm-command-input", "aria-label": placeholder ?? (es ? 'Buscar acciones' : 'Search actions'), placeholder: placeholder ?? (es ? 'Buscar acciones…' : 'Search actions…') }), _jsx(ScrollHints, { children: _jsxs(Command.List, { ref: results, className: "dgm-command-list", children: [_jsx(Command.Empty, { className: "dgm-command-empty", children: emptyMessage ?? (es ? 'No hay acciones coincidentes.' : 'No matching actions.') }), groups.map(([group, groupActions]) => (_jsx(Command.Group, { heading: group || undefined, children: groupActions.map((action) => (_jsxs(Command.Item, { className: "dgm-command-item", value: action.id, keywords: [action.label, ...(action.keywords ?? [])], disabled: action.disabled, onPointerLeave: () => {
|
|
83
94
|
// El hover termina al salir de la fila, aunque el puntero siga en el modal.
|
|
84
95
|
if (inputMode === 'pointer')
|
|
85
96
|
setActive('');
|
package/dist/context-menu.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface ContextMenuProps {
|
|
|
8
8
|
children: ReactElement;
|
|
9
9
|
/** Acciones y separadores con IDs únicos. Los atajos son leyendas, no se registran. */
|
|
10
10
|
items: readonly ContextMenuItem[];
|
|
11
|
+
/** Apariencia de las opciones; no modifica el panel. @defaultValue 'default' */
|
|
12
|
+
itemVariant?: 'default' | 'ghost';
|
|
11
13
|
/** Encabezado visible y nombre accesible del menú. */
|
|
12
14
|
label: string;
|
|
13
15
|
/** Desactiva únicamente el menú personalizado y conserva el menú nativo. @defaultValue false */
|
|
@@ -18,4 +20,4 @@ export interface ContextMenuProps {
|
|
|
18
20
|
className?: string;
|
|
19
21
|
}
|
|
20
22
|
/** Clic derecho, pulsación prolongada o Shift+F10. El clic izquierdo conserva la acción del hijo. */
|
|
21
|
-
export declare function ContextMenu({ children, items, label, disabled, onOpenChange, className, }: ContextMenuProps): ReactElement;
|
|
23
|
+
export declare function ContextMenu({ children, items, itemVariant, label, disabled, onOpenChange, className, }: ContextMenuProps): ReactElement;
|
package/dist/context-menu.js
CHANGED
|
@@ -3,11 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
/** Menú contextual sobre una zona concreta. Radix gestiona el anclaje al puntero y la navegación. */
|
|
4
4
|
import { useId, useRef, useState } from 'react';
|
|
5
5
|
import * as Primitive from '@radix-ui/react-context-menu';
|
|
6
|
+
import { menuItemFocusProps } from './internal/menu-item-focus.js';
|
|
6
7
|
import { restoreFocus } from './internal/focus.js';
|
|
7
8
|
import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
|
|
8
9
|
import { cx } from './utils.js';
|
|
9
10
|
/** Clic derecho, pulsación prolongada o Shift+F10. El clic izquierdo conserva la acción del hijo. */
|
|
10
|
-
export function ContextMenu({ children, items, label, disabled = false, onOpenChange, className, }) {
|
|
11
|
+
export function ContextMenu({ children, items, itemVariant = 'default', label, disabled = false, onOpenChange, className, }) {
|
|
11
12
|
const { depth, style: layerStyle } = useOverlayLayer();
|
|
12
13
|
const trigger = useRef(null);
|
|
13
14
|
const outside = useRef(false);
|
|
@@ -34,7 +35,7 @@ export function ContextMenu({ children, items, label, disabled = false, onOpenCh
|
|
|
34
35
|
clientX: rect.left + 8,
|
|
35
36
|
clientY: rect.bottom,
|
|
36
37
|
}));
|
|
37
|
-
}, children: children }), _jsx(Primitive.Portal, { children: _jsxs(Primitive.Content, { style: layerStyle, id: menuId, "aria-label": label, collisionPadding: 12, className: cx('dgm-dropdown', 'dgm-context-menu', className), onContextMenu: (event) => {
|
|
38
|
+
}, children: children }), _jsx(Primitive.Portal, { children: _jsxs(Primitive.Content, { ...menuItemFocusProps, style: layerStyle, id: menuId, "aria-label": label, collisionPadding: 12, className: cx('dgm-dropdown', 'dgm-context-menu', className), onContextMenu: (event) => {
|
|
38
39
|
// El panel ya es el menú contextual: evita superponer el del navegador
|
|
39
40
|
// o propagar el gesto a otra zona contextual a través del portal.
|
|
40
41
|
event.preventDefault();
|
|
@@ -46,5 +47,5 @@ export function ContextMenu({ children, items, label, disabled = false, onOpenCh
|
|
|
46
47
|
if (!outside.current && trigger.current?.isConnected)
|
|
47
48
|
restoreFocus(trigger.current);
|
|
48
49
|
outside.current = false;
|
|
49
|
-
}, children: [_jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }), items.map((item) => item.type === 'separator' ? (_jsx(Primitive.Separator, { className: "dgm-dropdown-separator" }, item.id)) : (_jsxs(Primitive.Item, { textValue: item.label, disabled: item.disabled, onSelect: item.onSelect, className: cx('dgm-dropdown-item', item.danger && 'dgm-dropdown-item--danger'), children: [item.icon && (_jsx("span", { className: "dgm-option-icon", "aria-hidden": "true", children: item.icon })), _jsx("span", { className: "dgm-dropdown-text", children: item.label }), item.shortcut && (_jsx("span", { className: "dgm-dropdown-shortcut", "aria-hidden": "true", children: item.shortcut }))] }, item.id)))] }) })] }) }));
|
|
50
|
+
}, children: [_jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }), items.map((item) => item.type === 'separator' ? (_jsx(Primitive.Separator, { className: "dgm-dropdown-separator" }, item.id)) : (_jsxs(Primitive.Item, { textValue: item.label, disabled: item.disabled, onSelect: item.onSelect, className: cx('dgm-dropdown-item', (item.variant ?? itemVariant) === 'ghost' && 'dgm-dropdown-item--ghost', item.danger && 'dgm-dropdown-item--danger'), children: [item.icon && (_jsx("span", { className: "dgm-option-icon", "aria-hidden": "true", children: item.icon })), _jsx("span", { className: "dgm-dropdown-text", children: item.label }), item.shortcut && (_jsx("span", { className: "dgm-dropdown-shortcut", "aria-hidden": "true", children: item.shortcut }))] }, item.id)))] }) })] }) }));
|
|
50
51
|
}
|
package/dist/data-table.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export interface DataTableProps<T extends object> {
|
|
|
40
40
|
searchPlaceholder?: string;
|
|
41
41
|
/** Registros por página. @defaultValue 10 */
|
|
42
42
|
pageSize?: number;
|
|
43
|
+
/** Oculta solo los controles con cero o una página de resultados filtrados. @defaultValue false */
|
|
44
|
+
hidePaginationOnSinglePage?: boolean;
|
|
43
45
|
/** Página controlada, comenzando en 1. */
|
|
44
46
|
page?: number;
|
|
45
47
|
onPageChange?: (page: number) => void;
|
|
@@ -56,4 +58,4 @@ export interface DataTableProps<T extends object> {
|
|
|
56
58
|
className?: string;
|
|
57
59
|
}
|
|
58
60
|
/** Paginación y selección conservan IDs reales. El filtrado devuelve a la primera página. */
|
|
59
|
-
export declare function DataTable<T extends object>({ data, columns, getRowId, caption, sorting, sortable, manualSorting, defaultSorting, onSortingChange, searchable, searchPlaceholder, pageSize, page, onPageChange, selectable, selection, defaultSelection, onSelectionChange, loading, emptyMessage, locale, className, }: DataTableProps<T>): ReactElement;
|
|
61
|
+
export declare function DataTable<T extends object>({ data, columns, getRowId, caption, sorting, sortable, manualSorting, defaultSorting, onSortingChange, searchable, searchPlaceholder, pageSize, hidePaginationOnSinglePage, page, onPageChange, selectable, selection, defaultSelection, onSelectionChange, loading, emptyMessage, locale, className, }: DataTableProps<T>): ReactElement;
|
package/dist/data-table.js
CHANGED
|
@@ -20,7 +20,7 @@ const features = tableFeatures({
|
|
|
20
20
|
filterFns: { includesString: filterFn_includesString },
|
|
21
21
|
});
|
|
22
22
|
/** Paginación y selección conservan IDs reales. El filtrado devuelve a la primera página. */
|
|
23
|
-
export function DataTable({ data, columns, getRowId, caption, sorting, sortable = true, manualSorting = false, defaultSorting = [], onSortingChange, searchable = true, searchPlaceholder, pageSize = 10, page, onPageChange, selectable = false, selection, defaultSelection = [], onSelectionChange, loading = false, emptyMessage, locale = 'en', className, }) {
|
|
23
|
+
export function DataTable({ data, columns, getRowId, caption, sorting, sortable = true, manualSorting = false, defaultSorting = [], onSortingChange, searchable = true, searchPlaceholder, pageSize = 10, hidePaginationOnSinglePage = false, page, onPageChange, selectable = false, selection, defaultSelection = [], onSelectionChange, loading = false, emptyMessage, locale = 'en', className, }) {
|
|
24
24
|
const [sort, changeSort] = useControllable(sorting, defaultSorting, onSortingChange);
|
|
25
25
|
const [currentPage, changePage] = useControllable(page, 1, onPageChange);
|
|
26
26
|
const [selected, changeSelection] = useControllable(selection, defaultSelection, onSelectionChange ? (next) => onSelectionChange([...next]) : undefined);
|
|
@@ -117,5 +117,5 @@ export function DataTable({ data, columns, getRowId, caption, sorting, sortable
|
|
|
117
117
|
? es
|
|
118
118
|
? 'Cargando…'
|
|
119
119
|
: 'Loading…'
|
|
120
|
-
: `${filtered.length} ${es ? 'resultados' : 'results'}${selectable ? ` · ${selectedCount} ${es ? 'seleccionados' : 'selected'}` : ''}` }), _jsx(Pagination, { page: current, pageCount: pageCount, onPageChange: changePage, disabled: loading, locale: locale })] })] }));
|
|
120
|
+
: `${filtered.length} ${es ? (filtered.length === 1 ? 'resultado' : 'resultados') : 'results'}${selectable ? ` · ${selectedCount} ${es ? 'seleccionados' : 'selected'}` : ''}` }), (!hidePaginationOnSinglePage || pageCount > 1) && (_jsx(Pagination, { page: current, pageCount: pageCount, onPageChange: changePage, disabled: loading, locale: locale }))] })] }));
|
|
121
121
|
}
|
package/dist/display.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export type Tone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
|
|
|
4
4
|
/** Atributos de span con un tono semántico opcional. */
|
|
5
5
|
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
6
6
|
/**
|
|
7
|
-
* Paleta de la etiqueta.
|
|
7
|
+
* Paleta de la etiqueta; el CSS público sigue color-scheme del contenedor.
|
|
8
|
+
* Los tokens --dgm-tone-<tono>-<fg|bg|border>-<light|dark> permiten personalizarla.
|
|
8
9
|
* @defaultValue 'neutral'
|
|
9
10
|
*/
|
|
10
11
|
tone?: Tone;
|
|
@@ -30,7 +31,8 @@ export interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'
|
|
|
30
31
|
/** Anima la entrada al montar un aviso dinámico. Desactivado para contenido estático. @defaultValue false */
|
|
31
32
|
animate?: boolean;
|
|
32
33
|
/**
|
|
33
|
-
* Intención visual del aviso.
|
|
34
|
+
* Intención visual del aviso. Comparte con Badge las paletas según color-scheme
|
|
35
|
+
* y los tokens --dgm-tone-<tono>-<fg|bg|border>-<light|dark>.
|
|
34
36
|
* @defaultValue 'info'
|
|
35
37
|
*/
|
|
36
38
|
tone?: Exclude<Tone, 'neutral'>;
|
package/dist/drawer.js
CHANGED
|
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
/** Diálogo anclado a un borde. Comparte el contrato de Modal y añade side. */
|
|
4
4
|
import { useId, useRef } from 'react';
|
|
5
5
|
import * as Dialog from '@radix-ui/react-dialog';
|
|
6
|
+
import { InternalTooltip } from './tooltip.js';
|
|
7
|
+
import { TooltipPolicy } from './internal/tooltip-policy.js';
|
|
6
8
|
import { restoreFocus } from './internal/focus.js';
|
|
7
9
|
import { CloseIcon } from './internal/icons.js';
|
|
8
10
|
import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
|
|
@@ -13,18 +15,23 @@ export function Drawer({ open, onOpenChange, title, description, children, foote
|
|
|
13
15
|
const scrollContainer = useRef(null);
|
|
14
16
|
const returnFocus = useRef(null);
|
|
15
17
|
const descriptionId = useId();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
const autoFocusing = useRef(false);
|
|
19
|
+
return (_jsx(OverlayLayerProvider, { depth: depth, scrollContainer: scrollContainer, children: _jsx(TooltipPolicy.Provider, { value: { autoFocusing, depth, dismiss: () => onOpenChange(false) }, children: _jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { children: [_jsx(Dialog.Overlay, { className: "dgm-modal-overlay", style: layerStyle }), _jsxs(Dialog.Content, { ref: scrollContainer, style: layerStyle, className: cx('dgm-drawer', `dgm-drawer--${side}`, className), "aria-describedby": hasContent(description) ? descriptionId : undefined, onOpenAutoFocus: () => {
|
|
20
|
+
autoFocusing.current = true;
|
|
21
|
+
queueMicrotask(() => {
|
|
22
|
+
autoFocusing.current = false;
|
|
23
|
+
});
|
|
24
|
+
returnFocus.current =
|
|
25
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
26
|
+
}, onCloseAutoFocus: (event) => {
|
|
27
|
+
if (returnFocus.current?.isConnected) {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
restoreFocus(returnFocus.current);
|
|
30
|
+
}
|
|
31
|
+
}, onPointerDownOutside: (event) => {
|
|
32
|
+
if (!closeOnOverlayClick)
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
}, children: [_jsxs("header", { className: "dgm-modal-header", children: [_jsx(Dialog.Title, { className: "dgm-modal-title", children: title }), hasContent(description) && (_jsx(Dialog.Description, { id: descriptionId, className: "dgm-description", children: description })), _jsx(InternalTooltip, { content: closeLabel, children: _jsx(Dialog.Close, { className: "dgm-icon-close", "aria-label": closeLabel, children: _jsx(CloseIcon, {}) }) })] }), _jsx("div", { className: "dgm-modal-body", children: children }), hasContent(footer) && _jsx("footer", { className: "dgm-modal-footer", children: footer })] })] }) }) }) }));
|
|
28
35
|
}
|
|
29
36
|
/** Alias de Drawer: Sheet comparte comportamiento y propiedades. */
|
|
30
37
|
export const Sheet = Drawer;
|
package/dist/dropdown-menu.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type DropdownMenuItem = {
|
|
|
14
14
|
/** Leyenda visual: no registra por sí misma un atajo de teclado. */
|
|
15
15
|
shortcut?: string;
|
|
16
16
|
disabled?: boolean;
|
|
17
|
+
/** Apariencia de esta opción; hereda itemVariant del menú. */
|
|
18
|
+
variant?: 'default' | 'ghost';
|
|
17
19
|
/** Resalta acciones destructivas sin ejecutarlas automáticamente. */
|
|
18
20
|
danger?: boolean;
|
|
19
21
|
/** Se ejecuta con puntero o teclado. preventDefault mantiene abierto el menú. */
|
|
@@ -24,6 +26,8 @@ export interface DropdownMenuProps {
|
|
|
24
26
|
children: ReactElement;
|
|
25
27
|
/** Acciones y separadores en orden de lectura. IDs únicos. */
|
|
26
28
|
items: readonly DropdownMenuItem[];
|
|
29
|
+
/** Apariencia de las opciones; no modifica el panel. @defaultValue 'default' */
|
|
30
|
+
itemVariant?: 'default' | 'ghost';
|
|
27
31
|
/** Encabezado opcional del menú. */
|
|
28
32
|
label?: string;
|
|
29
33
|
/** Apertura controlada, junto con onOpenChange. */
|
|
@@ -44,4 +48,4 @@ export interface DropdownMenuProps {
|
|
|
44
48
|
className?: string;
|
|
45
49
|
}
|
|
46
50
|
/** El menú no bloquea el scroll de la página y devuelve el foco sin desplazarla. */
|
|
47
|
-
export declare function DropdownMenu({ children, items, label, open, defaultOpen, onOpenChange, side, align, sideOffset, disabled, className, }: DropdownMenuProps): ReactElement;
|
|
51
|
+
export declare function DropdownMenu({ children, items, itemVariant, label, open, defaultOpen, onOpenChange, side, align, sideOffset, disabled, className, }: DropdownMenuProps): ReactElement;
|
package/dist/dropdown-menu.js
CHANGED
|
@@ -3,15 +3,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
/** Menú de acciones: Radix gestiona teclado, búsqueda por letras, portal y foco. */
|
|
4
4
|
import { useRef } from 'react';
|
|
5
5
|
import * as Primitive from '@radix-ui/react-dropdown-menu';
|
|
6
|
+
import { menuItemFocusProps } from './internal/menu-item-focus.js';
|
|
6
7
|
import { restoreFocus } from './internal/focus.js';
|
|
7
8
|
import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
|
|
8
9
|
import { cx } from './utils.js';
|
|
9
10
|
/** El menú no bloquea el scroll de la página y devuelve el foco sin desplazarla. */
|
|
10
|
-
export function DropdownMenu({ children, items, label, open, defaultOpen, onOpenChange, side = 'bottom', align = 'start', sideOffset = 6, disabled, className, }) {
|
|
11
|
+
export function DropdownMenu({ children, items, itemVariant = 'default', label, open, defaultOpen, onOpenChange, side = 'bottom', align = 'start', sideOffset = 6, disabled, className, }) {
|
|
11
12
|
const { depth, style: layerStyle } = useOverlayLayer();
|
|
12
13
|
const trigger = useRef(null);
|
|
13
14
|
const interactedOutside = useRef(false);
|
|
14
|
-
return (_jsx(OverlayLayerProvider, { depth: depth, children: _jsxs(Primitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, modal: false, children: [_jsx(Primitive.Trigger, { asChild: true, ref: trigger, disabled: disabled, children: children }), _jsx(Primitive.Portal, { children: _jsxs(Primitive.Content, { style: layerStyle, side: side, align: align, sideOffset: sideOffset, collisionPadding: 12, className: cx('dgm-dropdown', className), onInteractOutside: () => {
|
|
15
|
+
return (_jsx(OverlayLayerProvider, { depth: depth, children: _jsxs(Primitive.Root, { open: open, defaultOpen: defaultOpen, onOpenChange: onOpenChange, modal: false, children: [_jsx(Primitive.Trigger, { asChild: true, ref: trigger, disabled: disabled, children: children }), _jsx(Primitive.Portal, { children: _jsxs(Primitive.Content, { ...menuItemFocusProps, style: layerStyle, side: side, align: align, sideOffset: sideOffset, collisionPadding: 12, className: cx('dgm-dropdown', className), onInteractOutside: () => {
|
|
15
16
|
interactedOutside.current = true;
|
|
16
17
|
}, onCloseAutoFocus: (event) => {
|
|
17
18
|
// Un clic fuera conserva su destino; Escape o una acción vuelven al botón.
|
|
@@ -20,5 +21,5 @@ export function DropdownMenu({ children, items, label, open, defaultOpen, onOpen
|
|
|
20
21
|
restoreFocus(trigger.current);
|
|
21
22
|
}
|
|
22
23
|
interactedOutside.current = false;
|
|
23
|
-
}, children: [label && _jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }), items.map((item) => item.type === 'separator' ? (_jsx(Primitive.Separator, { className: "dgm-dropdown-separator" }, item.id)) : (_jsxs(Primitive.Item, { textValue: item.label, disabled: item.disabled, onSelect: item.onSelect, className: cx('dgm-dropdown-item', item.danger && 'dgm-dropdown-item--danger'), children: [item.icon && (_jsx("span", { className: "dgm-option-icon", "aria-hidden": "true", children: item.icon })), _jsx("span", { className: "dgm-dropdown-text", children: item.label }), item.shortcut && (_jsx("span", { className: "dgm-dropdown-shortcut", "aria-hidden": "true", children: item.shortcut }))] }, item.id)))] }) })] }) }));
|
|
24
|
+
}, children: [label && _jsx(Primitive.Label, { className: "dgm-dropdown-label", children: label }), items.map((item) => item.type === 'separator' ? (_jsx(Primitive.Separator, { className: "dgm-dropdown-separator" }, item.id)) : (_jsxs(Primitive.Item, { textValue: item.label, disabled: item.disabled, onSelect: item.onSelect, className: cx('dgm-dropdown-item', (item.variant ?? itemVariant) === 'ghost' && 'dgm-dropdown-item--ghost', item.danger && 'dgm-dropdown-item--danger'), children: [item.icon && (_jsx("span", { className: "dgm-option-icon", "aria-hidden": "true", children: item.icon })), _jsx("span", { className: "dgm-dropdown-text", children: item.label }), item.shortcut && (_jsx("span", { className: "dgm-dropdown-shortcut", "aria-hidden": "true", children: item.shortcut }))] }, item.id)))] }) })] }) }));
|
|
24
25
|
}
|
package/dist/fields.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { InternalTooltip } from './tooltip.js';
|
|
3
4
|
/**
|
|
4
5
|
* Campos de texto y selección con un contrato común de etiquetas, ayuda y errores.
|
|
5
6
|
* Este módulo agrupa la infraestructura compartida y sus tres controles nativos.
|
|
@@ -42,10 +43,17 @@ function Field({ id, label, description, error, wrapperClassName, required, chil
|
|
|
42
43
|
export const Input = forwardRef(function Input({ label, description, error, wrapperClassName, className, id, required, type = 'text', disabled, visible, defaultVisible = false, onVisibleChange, locale = 'en', 'aria-describedby': describedBy, 'aria-invalid': invalid, ...props }, ref) {
|
|
43
44
|
const field = useField(id, description, error, describedBy);
|
|
44
45
|
const [shown, changeVisibility] = useControllable(visible, defaultVisible, onVisibleChange);
|
|
46
|
+
const visibilityLabel = locale === 'es'
|
|
47
|
+
? shown
|
|
48
|
+
? 'Ocultar contraseña'
|
|
49
|
+
: 'Mostrar contraseña'
|
|
50
|
+
: shown
|
|
51
|
+
? 'Hide password'
|
|
52
|
+
: 'Show password';
|
|
45
53
|
const password = type === 'password';
|
|
46
54
|
// El mismo nodo input conserva valor, ref y validación al cambiar su presentación.
|
|
47
55
|
const control = (_jsx("input", { ...props, ...field, ref: ref, required: required, disabled: disabled, type: password && shown ? 'text' : type, "aria-invalid": hasContent(error) ? true : invalid, className: cx('dgm-input', password && 'dgm-input--password', className) }));
|
|
48
|
-
return (_jsx(Field, { label, description, error, wrapperClassName, required, id: field.id, children: _jsxs("div", { className: "dgm-input-control", children: [control, password && (_jsx("button", { type: "button", className: "dgm-password-toggle", disabled: disabled, "aria-label":
|
|
56
|
+
return (_jsx(Field, { label, description, error, wrapperClassName, required, id: field.id, children: _jsxs("div", { className: "dgm-input-control", children: [control, password && (_jsx(InternalTooltip, { content: visibilityLabel, children: _jsx("button", { type: "button", className: "dgm-password-toggle", disabled: disabled, "aria-label": visibilityLabel, "aria-pressed": shown, "aria-controls": field.id, onClick: () => changeVisibility(!shown), children: shown ? _jsx(EyeOffIcon, {}) : _jsx(EyeIcon, {}) }) }))] }) }));
|
|
49
57
|
});
|
|
50
58
|
/**
|
|
51
59
|
* Entrada de varias líneas con ref al HTMLTextAreaElement y altura inicial de 4 filas.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const menuItemFocusProps: {
|
|
2
|
+
onPointerMoveCapture(event: import("react").PointerEvent<HTMLDivElement>): void;
|
|
3
|
+
onPointerDownCapture(event: import("react").PointerEvent<HTMLDivElement>): void;
|
|
4
|
+
onKeyDownCapture(event: import("react").KeyboardEvent<HTMLDivElement>): void;
|
|
5
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const menuItemFocusProps = {
|
|
2
|
+
onPointerMoveCapture(event) {
|
|
3
|
+
event.currentTarget.dataset.inputMode = 'pointer';
|
|
4
|
+
},
|
|
5
|
+
onPointerDownCapture(event) {
|
|
6
|
+
event.currentTarget.dataset.inputMode = 'pointer';
|
|
7
|
+
},
|
|
8
|
+
onKeyDownCapture(event) {
|
|
9
|
+
event.currentTarget.dataset.inputMode = 'keyboard';
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Política local de autofocus y Escape; los portales conservan el contexto React. */
|
|
2
|
+
import { type RefObject } from 'react';
|
|
3
|
+
export declare const TooltipPolicy: import("react").Context<{
|
|
4
|
+
/** Solo durante el autofocus síncrono de Radix; se libera en la siguiente microtarea. */
|
|
5
|
+
autoFocusing?: RefObject<boolean>;
|
|
6
|
+
/** Toast tiene una base propia que sus ayudas portadas también necesitan. */
|
|
7
|
+
layerBase?: string;
|
|
8
|
+
/** Profundidad del propietario: un panel intermedio conserva su propio Escape. */
|
|
9
|
+
depth?: number;
|
|
10
|
+
dismiss?: () => void;
|
|
11
|
+
}>;
|
package/dist/modal.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface ModalProps {
|
|
|
12
12
|
children: ReactNode;
|
|
13
13
|
/** Acciones que permanecen fuera del área desplazable. */
|
|
14
14
|
footer?: ReactNode;
|
|
15
|
-
/**
|
|
15
|
+
/** Nombre accesible y Tooltip localizado del botón X. @defaultValue 'Close dialog' */
|
|
16
16
|
closeLabel?: string;
|
|
17
17
|
/** Permite cerrar al pulsar fuera. Escape y X siguen disponibles. @defaultValue true */
|
|
18
18
|
closeOnOverlayClick?: boolean;
|
package/dist/modal.js
CHANGED
|
@@ -4,6 +4,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
import { CloseIcon } from './internal/icons.js';
|
|
5
5
|
import { useId, useRef } from 'react';
|
|
6
6
|
import * as Dialog from '@radix-ui/react-dialog';
|
|
7
|
+
import { InternalTooltip } from './tooltip.js';
|
|
8
|
+
import { TooltipPolicy } from './internal/tooltip-policy.js';
|
|
7
9
|
import { restoreFocus } from './internal/focus.js';
|
|
8
10
|
import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
|
|
9
11
|
import { cx, hasContent } from './utils.js';
|
|
@@ -13,19 +15,24 @@ export function Modal({ open, onOpenChange, title, description, children, footer
|
|
|
13
15
|
const scrollContainer = useRef(null);
|
|
14
16
|
const returnFocus = useRef(null);
|
|
15
17
|
const descriptionId = useId();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
const autoFocusing = useRef(false);
|
|
19
|
+
return (_jsx(OverlayLayerProvider, { depth: depth, scrollContainer: scrollContainer, children: _jsx(TooltipPolicy.Provider, { value: { autoFocusing, depth, dismiss: () => onOpenChange(false) }, children: _jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { children: [_jsx(Dialog.Overlay, { className: "dgm-modal-overlay", style: layerStyle }), _jsxs(Dialog.Content, { ref: scrollContainer, style: layerStyle, className: cx('dgm-modal', className), "aria-describedby": hasContent(description) ? descriptionId : undefined, onOpenAutoFocus: () => {
|
|
20
|
+
autoFocusing.current = true;
|
|
21
|
+
queueMicrotask(() => {
|
|
22
|
+
autoFocusing.current = false;
|
|
23
|
+
});
|
|
24
|
+
// Se captura antes de que Radix enfoque el contenido, incluso sin Dialog.Trigger.
|
|
25
|
+
returnFocus.current =
|
|
26
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
27
|
+
}, onCloseAutoFocus: (event) => {
|
|
28
|
+
if (returnFocus.current?.isConnected) {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
// Restaurar el foco no debe desplazar la página hacia el disparador,
|
|
31
|
+
// especialmente si está dentro de una cabecera sticky.
|
|
32
|
+
restoreFocus(returnFocus.current);
|
|
33
|
+
}
|
|
34
|
+
}, onPointerDownOutside: (event) => {
|
|
35
|
+
if (!closeOnOverlayClick)
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
}, children: [_jsxs("header", { className: "dgm-modal-header", children: [_jsx(Dialog.Title, { className: "dgm-modal-title", children: title }), hasContent(description) && (_jsx(Dialog.Description, { id: descriptionId, className: "dgm-description", children: description })), _jsx(InternalTooltip, { content: closeLabel, children: _jsx(Dialog.Close, { className: "dgm-icon-close", "aria-label": closeLabel, children: _jsx(CloseIcon, {}) }) })] }), _jsx("div", { className: "dgm-modal-body", children: children }), hasContent(footer) && _jsx("footer", { className: "dgm-modal-footer", children: footer })] })] }) }) }) }));
|
|
31
38
|
}
|
|
@@ -17,10 +17,12 @@ export interface NavigationMenuProps {
|
|
|
17
17
|
items: readonly NavigationMenuItem[];
|
|
18
18
|
/** href activo exacto; el consumidor conoce la ruta actual. */
|
|
19
19
|
currentHref?: string;
|
|
20
|
+
/** Apariencia de enlaces y disparadores. @defaultValue 'default' */
|
|
21
|
+
variant?: 'default' | 'ghost';
|
|
20
22
|
/** Adaptador para routers: devuelve un único enlace que acepte props y ref. */
|
|
21
23
|
renderLink?: (item: NavigationLink) => ReactElement;
|
|
22
24
|
delayDuration?: number;
|
|
23
25
|
className?: string;
|
|
24
26
|
}
|
|
25
27
|
/** En móvil los elementos se distribuyen en filas y los paneles permanecen dentro del ancho disponible. */
|
|
26
|
-
export declare function NavigationMenu({ label, items, currentHref, renderLink, delayDuration, className, }: NavigationMenuProps): ReactElement;
|
|
28
|
+
export declare function NavigationMenu({ label, items, currentHref, variant, renderLink, delayDuration, className, }: NavigationMenuProps): ReactElement;
|
package/dist/navigation-menu.js
CHANGED
|
@@ -18,10 +18,10 @@ function NavigationPanel({ label, children }) {
|
|
|
18
18
|
return (_jsxs(_Fragment, { children: [_jsxs(Primitive.Trigger, { ref: trigger, className: "dgm-navigation-trigger", children: [label, _jsx(ChevronIcon, {})] }), _jsx(Primitive.Content, { ref: contentRef, className: "dgm-navigation-content", children: children })] }));
|
|
19
19
|
}
|
|
20
20
|
/** En móvil los elementos se distribuyen en filas y los paneles permanecen dentro del ancho disponible. */
|
|
21
|
-
export function NavigationMenu({ label, items, currentHref, renderLink, delayDuration = 150, className, }) {
|
|
21
|
+
export function NavigationMenu({ label, items, currentHref, variant = 'default', renderLink, delayDuration = 150, className, }) {
|
|
22
22
|
function link(item) {
|
|
23
23
|
const active = currentHref === item.href;
|
|
24
24
|
return (_jsx(Primitive.Link, { asChild: true, active: active, "aria-current": active ? 'page' : undefined, className: "dgm-navigation-link", children: renderLink ? (renderLink(item)) : (_jsxs("a", { href: item.href, children: [item.icon && _jsx("span", { "aria-hidden": "true", children: item.icon }), _jsxs("span", { children: [item.label, item.description && _jsx("small", { children: item.description })] })] })) }, item.id));
|
|
25
25
|
}
|
|
26
|
-
return (_jsx(Primitive.Root, { "aria-label": label, delayDuration: delayDuration, className: cx('dgm-navigation', className), children: _jsx(Primitive.List, { className: "dgm-navigation-list", children: items.map((item) => (_jsx(Primitive.Item, { className: "dgm-navigation-item", children: 'items' in item ? (_jsx(NavigationPanel, { label: item.label, children: _jsx("ul", { children: item.items.map((child) => (_jsx("li", { children: link(child) }, child.id))) }) })) : (link(item)) }, item.id))) }) }));
|
|
26
|
+
return (_jsx(Primitive.Root, { "aria-label": label, delayDuration: delayDuration, className: cx('dgm-navigation', `dgm-navigation--${variant}`, className), children: _jsx(Primitive.List, { className: "dgm-navigation-list", children: items.map((item) => (_jsx(Primitive.Item, { className: "dgm-navigation-item", children: 'items' in item ? (_jsx(NavigationPanel, { label: item.label, children: _jsx("ul", { children: item.items.map((child) => (_jsx("li", { children: link(child) }, child.id))) }) })) : (link(item)) }, item.id))) }) }));
|
|
27
27
|
}
|
package/dist/pagination.js
CHANGED
|
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
import { ChevronLeftIcon, ChevronRightIcon } from './internal/icons.js';
|
|
5
5
|
import { useEffect, useRef, useState } from 'react';
|
|
6
6
|
import { fitPaginationPages } from './internal/pagination.js';
|
|
7
|
+
import { InternalTooltip } from './tooltip.js';
|
|
7
8
|
import { Button } from './button.js';
|
|
8
9
|
import { cx } from './utils.js';
|
|
9
10
|
/** Número acotado de botones incluso con miles de páginas; los saltos no son interactivos. */
|
|
@@ -53,8 +54,8 @@ export function Pagination({ page, pageCount, onPageChange, siblings = 1, disabl
|
|
|
53
54
|
? fitPaginationPages(current, count, slots)
|
|
54
55
|
: [...pages].filter((value) => value > 0 && value <= count).sort((a, b) => a - b);
|
|
55
56
|
const es = locale === 'es';
|
|
56
|
-
return (_jsxs("nav", { ref: navigation, style: { '--dgm-page-digits': String(count).length }, "aria-label": label ?? (es ? 'Paginación' : 'Pagination'), className: cx('dgm-pagination', `dgm-pagination--${size}`, fullWidth && 'dgm-pagination--full', className), children: [_jsx(Button, { ref: previousButton, iconOnly: true, size: size, variant: "outline", disabled: disabled || current <= 1 || count === 0, onClick: () => onPageChange(current - 1), "aria-label": es ? 'Página anterior' : 'Previous page', children: _jsx(ChevronLeftIcon, {}) }), ordered.map((value, index) => (_jsxs("span", { className: "dgm-pagination-part", children: [index > 0 && value - ordered[index - 1] > 1 && (_jsx("span", { className: "dgm-pagination-gap", "aria-hidden": "true", children: "\u2026" })), _jsx(Button, { ref: value === ordered[ordered.length - 1] ? lastPageButton : undefined, size: size, variant: value === current ? 'primary' : 'ghost', "aria-label": `${es ? 'Página' : 'Page'} ${value}`, "aria-current": value === current ? 'page' : undefined, disabled: disabled, onClick: () => {
|
|
57
|
+
return (_jsxs("nav", { ref: navigation, style: { '--dgm-page-digits': String(count).length }, "aria-label": label ?? (es ? 'Paginación' : 'Pagination'), className: cx('dgm-pagination', `dgm-pagination--${size}`, fullWidth && 'dgm-pagination--full', className), children: [_jsx(InternalTooltip, { content: es ? 'Página anterior' : 'Previous page', children: _jsx(Button, { ref: previousButton, iconOnly: true, size: size, variant: "outline", disabled: disabled || current <= 1 || count === 0, onClick: () => onPageChange(current - 1), "aria-label": es ? 'Página anterior' : 'Previous page', children: _jsx(ChevronLeftIcon, {}) }) }), ordered.map((value, index) => (_jsxs("span", { className: "dgm-pagination-part", children: [index > 0 && value - ordered[index - 1] > 1 && (_jsx("span", { className: "dgm-pagination-gap", "aria-hidden": "true", children: "\u2026" })), _jsx(Button, { ref: value === ordered[ordered.length - 1] ? lastPageButton : undefined, size: size, variant: value === current ? 'primary' : 'ghost', "aria-label": `${es ? 'Página' : 'Page'} ${value}`, "aria-current": value === current ? 'page' : undefined, disabled: disabled, onClick: () => {
|
|
57
58
|
if (value !== current)
|
|
58
59
|
onPageChange(value);
|
|
59
|
-
}, children: value })] }, value))), _jsx(Button, { iconOnly: true, size: size, variant: "outline", disabled: disabled || current >= count || count === 0, onClick: () => onPageChange(current + 1), "aria-label": es ? 'Página siguiente' : 'Next page', children: _jsx(ChevronRightIcon, {}) })] }));
|
|
60
|
+
}, children: value })] }, value))), _jsx(InternalTooltip, { content: es ? 'Página siguiente' : 'Next page', children: _jsx(Button, { iconOnly: true, size: size, variant: "outline", disabled: disabled || current >= count || count === 0, onClick: () => onPageChange(current + 1), "aria-label": es ? 'Página siguiente' : 'Next page', children: _jsx(ChevronRightIcon, {}) }) })] }));
|
|
60
61
|
}
|
package/dist/styles.css
CHANGED
|
@@ -727,31 +727,76 @@ button.dgm-step:hover:not(:disabled),
|
|
|
727
727
|
.dgm-switch:checked::before {
|
|
728
728
|
transform: translateX(1.25rem);
|
|
729
729
|
}
|
|
730
|
-
/* 7. Tonos semánticos compartidos
|
|
730
|
+
/* 7. Tonos semánticos compartidos. color-scheme selecciona la paleta; los tokens por tono se heredan. */
|
|
731
731
|
.dgm-tone--neutral {
|
|
732
|
-
--dgm-tone-fg:
|
|
733
|
-
|
|
734
|
-
|
|
732
|
+
--dgm-tone-fg: light-dark(
|
|
733
|
+
var(--dgm-tone-neutral-fg-light, #3d4b60),
|
|
734
|
+
var(--dgm-tone-neutral-fg-dark, #d1dbea)
|
|
735
|
+
);
|
|
736
|
+
--dgm-tone-bg: light-dark(
|
|
737
|
+
var(--dgm-tone-neutral-bg-light, #f0f3f7),
|
|
738
|
+
var(--dgm-tone-neutral-bg-dark, #242424)
|
|
739
|
+
);
|
|
740
|
+
--dgm-tone-border: light-dark(
|
|
741
|
+
var(--dgm-tone-neutral-border-light, #d9e0ea),
|
|
742
|
+
var(--dgm-tone-neutral-border-dark, #474747)
|
|
743
|
+
);
|
|
735
744
|
}
|
|
736
745
|
.dgm-tone--info {
|
|
737
|
-
--dgm-tone-fg:
|
|
738
|
-
|
|
739
|
-
|
|
746
|
+
--dgm-tone-fg: light-dark(
|
|
747
|
+
var(--dgm-tone-info-fg-light, #3730a3),
|
|
748
|
+
var(--dgm-tone-info-fg-dark, #c7d2fe)
|
|
749
|
+
);
|
|
750
|
+
--dgm-tone-bg: light-dark(
|
|
751
|
+
var(--dgm-tone-info-bg-light, #eef2ff),
|
|
752
|
+
var(--dgm-tone-info-bg-dark, #25254a)
|
|
753
|
+
);
|
|
754
|
+
--dgm-tone-border: light-dark(
|
|
755
|
+
var(--dgm-tone-info-border-light, #c7d2fe),
|
|
756
|
+
var(--dgm-tone-info-border-dark, #50518c)
|
|
757
|
+
);
|
|
740
758
|
}
|
|
741
759
|
.dgm-tone--success {
|
|
742
|
-
--dgm-tone-fg:
|
|
743
|
-
|
|
744
|
-
|
|
760
|
+
--dgm-tone-fg: light-dark(
|
|
761
|
+
var(--dgm-tone-success-fg-light, #166444),
|
|
762
|
+
var(--dgm-tone-success-fg-dark, #a7f3d0)
|
|
763
|
+
);
|
|
764
|
+
--dgm-tone-bg: light-dark(
|
|
765
|
+
var(--dgm-tone-success-bg-light, #edfbf3),
|
|
766
|
+
var(--dgm-tone-success-bg-dark, #12382d)
|
|
767
|
+
);
|
|
768
|
+
--dgm-tone-border: light-dark(
|
|
769
|
+
var(--dgm-tone-success-border-light, #b9e8cc),
|
|
770
|
+
var(--dgm-tone-success-border-dark, #376f5a)
|
|
771
|
+
);
|
|
745
772
|
}
|
|
746
773
|
.dgm-tone--warning {
|
|
747
|
-
--dgm-tone-fg:
|
|
748
|
-
|
|
749
|
-
|
|
774
|
+
--dgm-tone-fg: light-dark(
|
|
775
|
+
var(--dgm-tone-warning-fg-light, #854d0e),
|
|
776
|
+
var(--dgm-tone-warning-fg-dark, #fde68a)
|
|
777
|
+
);
|
|
778
|
+
--dgm-tone-bg: light-dark(
|
|
779
|
+
var(--dgm-tone-warning-bg-light, #fff9e8),
|
|
780
|
+
var(--dgm-tone-warning-bg-dark, #3c2c13)
|
|
781
|
+
);
|
|
782
|
+
--dgm-tone-border: light-dark(
|
|
783
|
+
var(--dgm-tone-warning-border-light, #f1d48a),
|
|
784
|
+
var(--dgm-tone-warning-border-dark, #8c6a28)
|
|
785
|
+
);
|
|
750
786
|
}
|
|
751
787
|
.dgm-tone--danger {
|
|
752
|
-
--dgm-tone-fg:
|
|
753
|
-
|
|
754
|
-
|
|
788
|
+
--dgm-tone-fg: light-dark(
|
|
789
|
+
var(--dgm-tone-danger-fg-light, #a2221a),
|
|
790
|
+
var(--dgm-tone-danger-fg-dark, #fca5a5)
|
|
791
|
+
);
|
|
792
|
+
--dgm-tone-bg: light-dark(
|
|
793
|
+
var(--dgm-tone-danger-bg-light, #fff1f0),
|
|
794
|
+
var(--dgm-tone-danger-bg-dark, #421f27)
|
|
795
|
+
);
|
|
796
|
+
--dgm-tone-border: light-dark(
|
|
797
|
+
var(--dgm-tone-danger-border-light, #ffc9c5),
|
|
798
|
+
var(--dgm-tone-danger-border-dark, #87434e)
|
|
799
|
+
);
|
|
755
800
|
}
|
|
756
801
|
/* 8. Composición: etiquetas, tarjetas y avisos. Las cajas y el texto pueden adaptarse al ancho disponible. */
|
|
757
802
|
.dgm-badge {
|
|
@@ -1197,6 +1242,40 @@ button.dgm-step:hover:not(:disabled),
|
|
|
1197
1242
|
}
|
|
1198
1243
|
}
|
|
1199
1244
|
|
|
1245
|
+
/* El wrapper recibe hover incluso cuando Chromium descarta los eventos del botón. */
|
|
1246
|
+
.dgm-tooltip-trigger {
|
|
1247
|
+
display: inline-flex;
|
|
1248
|
+
min-width: 0;
|
|
1249
|
+
vertical-align: middle;
|
|
1250
|
+
}
|
|
1251
|
+
.dgm-tooltip-disabled > :disabled {
|
|
1252
|
+
pointer-events: none;
|
|
1253
|
+
}
|
|
1254
|
+
.dgm-tooltip-password {
|
|
1255
|
+
position: absolute;
|
|
1256
|
+
inset-inline-end: 0.25rem;
|
|
1257
|
+
top: 50%;
|
|
1258
|
+
transform: translateY(-50%);
|
|
1259
|
+
width: 2.5rem;
|
|
1260
|
+
height: 2.5rem;
|
|
1261
|
+
}
|
|
1262
|
+
.dgm-tooltip-password > .dgm-password-toggle {
|
|
1263
|
+
position: static;
|
|
1264
|
+
transform: none;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/* Los cierres absolutos también necesitan un ancla con su área real. */
|
|
1268
|
+
.dgm-tooltip-close {
|
|
1269
|
+
position: absolute;
|
|
1270
|
+
top: 0.5rem;
|
|
1271
|
+
right: 0.5rem;
|
|
1272
|
+
width: 2.5rem;
|
|
1273
|
+
height: 2.5rem;
|
|
1274
|
+
}
|
|
1275
|
+
.dgm-tooltip-close > .dgm-icon-close {
|
|
1276
|
+
position: static;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1200
1279
|
/* 13. Tooltip: ayuda flotante que hereda la paleta de la aplicación. */
|
|
1201
1280
|
.dgm-tooltip {
|
|
1202
1281
|
z-index: calc(var(--dgm-layer-base) + var(--dgm-layer-depth, 0) * var(--dgm-layer-step) + 1);
|
|
@@ -1609,7 +1688,10 @@ button.dgm-step:hover:not(:disabled),
|
|
|
1609
1688
|
cursor: pointer;
|
|
1610
1689
|
transition: var(--dgm-hover-transition);
|
|
1611
1690
|
}
|
|
1612
|
-
.
|
|
1691
|
+
/* Ghost distingue el foco de teclado del foco que Radix mueve con el puntero. */
|
|
1692
|
+
.dgm-dropdown-item:not(.dgm-dropdown-item--ghost)[data-highlighted]:not([data-disabled]),
|
|
1693
|
+
.dgm-dropdown:not([data-input-mode='pointer'])
|
|
1694
|
+
.dgm-dropdown-item--ghost:focus-visible:not([data-disabled]) {
|
|
1613
1695
|
outline: 2px solid var(--dgm-primary);
|
|
1614
1696
|
outline-offset: -2px;
|
|
1615
1697
|
}
|
|
@@ -3338,6 +3420,19 @@ button.dgm-step:disabled {
|
|
|
3338
3420
|
.dgm-navigation-trigger[data-state='open'] {
|
|
3339
3421
|
background: var(--dgm-subtle);
|
|
3340
3422
|
}
|
|
3423
|
+
/* Conserva la geometría y el foco compartidos, también dentro de los submenús. */
|
|
3424
|
+
.dgm-navigation--ghost .dgm-navigation-trigger,
|
|
3425
|
+
.dgm-navigation--ghost .dgm-navigation-link {
|
|
3426
|
+
background: transparent;
|
|
3427
|
+
border-color: transparent;
|
|
3428
|
+
}
|
|
3429
|
+
.dgm-navigation--ghost .dgm-navigation-link[data-active],
|
|
3430
|
+
.dgm-navigation--ghost .dgm-navigation-trigger[data-state='open'],
|
|
3431
|
+
.dgm-navigation--ghost .dgm-navigation-trigger:hover:not(:disabled),
|
|
3432
|
+
.dgm-navigation--ghost .dgm-navigation-link:hover:not([aria-disabled='true']) {
|
|
3433
|
+
background: var(--dgm-hover);
|
|
3434
|
+
color: var(--dgm-on-hover);
|
|
3435
|
+
}
|
|
3341
3436
|
.dgm-navigation-content {
|
|
3342
3437
|
/* Misma separación inicial que Popover y los selectores que lo componen. */
|
|
3343
3438
|
--dgm-navigation-gap: 8px;
|
package/dist/toast.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface ToastOptions {
|
|
|
17
17
|
altText: string;
|
|
18
18
|
onClick: () => void;
|
|
19
19
|
};
|
|
20
|
-
/**
|
|
20
|
+
/** Nombre accesible y Tooltip localizado para la X de este aviso; hereda del Provider. */
|
|
21
21
|
closeLabel?: string;
|
|
22
22
|
/** Clase de la notificación, en el portal. */
|
|
23
23
|
className?: string;
|
|
@@ -39,7 +39,7 @@ export interface ToastProviderProps {
|
|
|
39
39
|
label?: string;
|
|
40
40
|
/** Nombre de la región; {hotkey} se sustituye por F8. @defaultValue 'Notifications ({hotkey})' */
|
|
41
41
|
viewportLabel?: string;
|
|
42
|
-
/** Nombre accesible localizado de las X. @defaultValue 'Dismiss notification' */
|
|
42
|
+
/** Nombre accesible y Tooltip localizado de las X. @defaultValue 'Dismiss notification' */
|
|
43
43
|
closeLabel?: string;
|
|
44
44
|
/** Clase de la pila fija abajo a la derecha. */
|
|
45
45
|
className?: string;
|
package/dist/toast.js
CHANGED
|
@@ -6,6 +6,8 @@ import { createContext, useCallback, useContext, useEffect, useId, useMemo, useR
|
|
|
6
6
|
import { createPortal } from 'react-dom';
|
|
7
7
|
import * as Primitive from '@radix-ui/react-toast';
|
|
8
8
|
import { Alert } from './display.js';
|
|
9
|
+
import { InternalTooltip } from './tooltip.js';
|
|
10
|
+
import { TooltipPolicy } from './internal/tooltip-policy.js';
|
|
9
11
|
import { Button } from './button.js';
|
|
10
12
|
import { cx, hasContent } from './utils.js';
|
|
11
13
|
const ToastContext = createContext(null);
|
|
@@ -69,7 +71,7 @@ function ToastItem({ notice, duration, closeLabel, dismiss, remove, }) {
|
|
|
69
71
|
}, className: cx('dgm-toast', `dgm-tone--${notice.tone ?? 'info'}`, notice.className), style: { touchAction: 'pan-y' }, onAnimationEnd: (event) => {
|
|
70
72
|
if (!notice.open && event.target === event.currentTarget)
|
|
71
73
|
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, {}) })] }));
|
|
74
|
+
}, 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(TooltipPolicy.Provider, { value: { dismiss: () => dismiss(notice.id), layerBase: 'var(--dgm-layer-toast)' }, children: _jsx(InternalTooltip, { content: notice.closeLabel ?? closeLabel, children: _jsx(Primitive.Close, { className: "dgm-icon-close", "aria-label": notice.closeLabel ?? closeLabel, children: _jsx(CloseIcon, {}) }) }) })] }));
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* Monta un único Provider alrededor del área que comparte avisos. En Astro debe
|
package/dist/tooltip.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Ayuda contextual sobre un disparador existente. Radix gestiona foco, hover, Escape y colisiones. */
|
|
2
|
-
import type
|
|
2
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
3
3
|
export interface TooltipProps {
|
|
4
|
-
/** Un único elemento
|
|
4
|
+
/** Un único elemento que acepte ref y props, como Button. Un ancla inline-flex estable permite hover con disabled/loading sin añadir foco. No usar Fragment. */
|
|
5
5
|
children: ReactElement;
|
|
6
6
|
/** Ayuda breve no interactiva. No colocar aquí enlaces, botones ni información imprescindible. */
|
|
7
7
|
content: ReactNode;
|
|
@@ -22,5 +22,7 @@ export interface TooltipProps {
|
|
|
22
22
|
/** Clase del contenido flotante, no del disparador. */
|
|
23
23
|
className?: string;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
26
|
-
export declare function Tooltip(
|
|
25
|
+
/** Ayuda sobre un elemento que conserva sus props y ref originales. */
|
|
26
|
+
export declare function Tooltip(props: TooltipProps): ReactElement;
|
|
27
|
+
/** Los controles internos delegan Escape en su diálogo o notificación. */
|
|
28
|
+
export declare function InternalTooltip(props: TooltipProps): ReactElement;
|
package/dist/tooltip.js
CHANGED
|
@@ -1,16 +1,47 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/** Ayuda contextual sobre un disparador existente. Radix gestiona foco, hover, Escape y colisiones. */
|
|
4
|
+
import { cloneElement, forwardRef, useContext, } from 'react';
|
|
5
|
+
import { TooltipPolicy } from './internal/tooltip-policy.js';
|
|
3
6
|
import * as Primitive from '@radix-ui/react-tooltip';
|
|
4
7
|
import { isRestoringFocus } from './internal/focus.js';
|
|
5
8
|
import { OverlayLayerProvider, useOverlayLayer } from './internal/overlay-layer.js';
|
|
6
9
|
import { cx } from './utils.js';
|
|
10
|
+
/** Un ancla estable conserva el botón y su ref al alternar disabled o loading. */
|
|
11
|
+
const TooltipTrigger = forwardRef(function TooltipTrigger({ children, className, 'aria-describedby': describedBy, ...props }, ref) {
|
|
12
|
+
const child = children;
|
|
13
|
+
const unavailable = child.props.disabled || child.props.loading;
|
|
14
|
+
return (_jsx("span", { ...props, ref: ref, className: cx('dgm-tooltip-trigger', unavailable && 'dgm-tooltip-disabled', child.props.className === 'dgm-password-toggle' && 'dgm-tooltip-password', child.props.className === 'dgm-icon-close' && 'dgm-tooltip-close', child.props.fullWidth && !child.props.iconOnly && 'dgm-full', className), children: cloneElement(child, {
|
|
15
|
+
'aria-describedby': [child.props['aria-describedby'], describedBy].filter(Boolean).join(' ') || undefined,
|
|
16
|
+
}) }));
|
|
17
|
+
});
|
|
7
18
|
/** Provider local: se puede usar sin instalar un proveedor global en la aplicación. */
|
|
8
|
-
|
|
19
|
+
function TooltipContent({ children, content, side = 'top', align = 'center', sideOffset = 6, delayDuration = 300, open, defaultOpen, onOpenChange, className, internal = false, }) {
|
|
20
|
+
const policy = useContext(TooltipPolicy);
|
|
9
21
|
const { depth, style: layerStyle } = useOverlayLayer();
|
|
10
22
|
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
23
|
// El foco sigue llegando al botón y a sus manejadores. Solo se omite
|
|
12
|
-
// la apertura
|
|
13
|
-
if (isRestoringFocus(event.
|
|
24
|
+
// la apertura de Radix durante el autofocus inicial o el retorno desde otra capa.
|
|
25
|
+
if (policy.autoFocusing?.current || isRestoringFocus(event.target))
|
|
14
26
|
event.preventDefault();
|
|
15
|
-
}, children: children }), _jsx(Primitive.Portal, { children: _jsx(Primitive.Content, {
|
|
27
|
+
}, children: _jsx(TooltipTrigger, { children: children }) }), _jsx(Primitive.Portal, { children: _jsx(Primitive.Content, { onEscapeKeyDown: (event) => {
|
|
28
|
+
if (internal &&
|
|
29
|
+
policy.dismiss &&
|
|
30
|
+
(policy.depth === undefined || depth === policy.depth + 1)) {
|
|
31
|
+
// La ayuda no añade otra pulsación antes de cerrar su capa propietaria.
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
policy.dismiss();
|
|
34
|
+
}
|
|
35
|
+
}, style: {
|
|
36
|
+
...layerStyle,
|
|
37
|
+
...(policy.layerBase ? { '--dgm-layer-base': policy.layerBase } : {}),
|
|
38
|
+
}, side: side, align: align, sideOffset: sideOffset, collisionPadding: 12, className: cx('dgm-tooltip', className), children: content }) })] }) }) }));
|
|
39
|
+
}
|
|
40
|
+
/** Ayuda sobre un elemento que conserva sus props y ref originales. */
|
|
41
|
+
export function Tooltip(props) {
|
|
42
|
+
return _jsx(TooltipContent, { ...props });
|
|
43
|
+
}
|
|
44
|
+
/** Los controles internos delegan Escape en su diálogo o notificación. */
|
|
45
|
+
export function InternalTooltip(props) {
|
|
46
|
+
return _jsx(TooltipContent, { ...props, internal: true });
|
|
16
47
|
}
|