danielgm-ui 0.4.3 → 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/data-table.js +1 -1
- package/dist/display.d.ts +4 -2
- package/dist/drawer.js +19 -12
- package/dist/fields.js +9 -1
- 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/pagination.js +3 -2
- package/dist/styles.css +95 -16
- 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/data-table.js
CHANGED
|
@@ -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'}` : ''}` }), (!hidePaginationOnSinglePage || pageCount > 1) && (_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/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,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
|
}
|
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);
|
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
|
}
|