@tangible/ui 0.0.3 → 0.0.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/README.md +21 -13
- package/components/Accordion/Accordion.d.ts +1 -1
- package/components/Accordion/Accordion.js +3 -3
- package/components/Accordion/types.d.ts +8 -1
- package/components/Avatar/Avatar.js +16 -7
- package/components/Avatar/AvatarGroup.js +7 -5
- package/components/Avatar/types.d.ts +11 -0
- package/components/Button/Button.js +10 -3
- package/components/Button/types.d.ts +9 -1
- package/components/Card/Card.js +26 -13
- package/components/Checkbox/Checkbox.d.ts +1 -1
- package/components/Chip/Chip.d.ts +37 -1
- package/components/Chip/Chip.js +10 -8
- package/components/ChipGroup/ChipGroup.js +5 -4
- package/components/ChipGroup/types.d.ts +3 -0
- package/components/Dropdown/Dropdown.d.ts +19 -1
- package/components/Dropdown/Dropdown.js +84 -28
- package/components/Dropdown/index.d.ts +2 -2
- package/components/Dropdown/index.js +1 -1
- package/components/Dropdown/types.d.ts +15 -0
- package/components/IconButton/IconButton.js +5 -4
- package/components/IconButton/index.d.ts +1 -1
- package/components/IconButton/types.d.ts +24 -4
- package/components/Modal/Modal.d.ts +16 -2
- package/components/Modal/Modal.js +45 -20
- package/components/MoveHandle/MoveHandle.js +3 -3
- package/components/MoveHandle/types.d.ts +12 -2
- package/components/Notice/Notice.js +32 -19
- package/components/Select/Select.js +6 -2
- package/components/Sidebar/Sidebar.d.ts +6 -1
- package/components/Sidebar/Sidebar.js +65 -11
- package/components/Sidebar/index.d.ts +1 -1
- package/components/Sidebar/types.d.ts +39 -14
- package/components/Tabs/Tabs.d.ts +1 -1
- package/components/Tabs/Tabs.js +12 -3
- package/components/Tabs/types.d.ts +20 -5
- package/components/TextInput/TextInput.js +10 -2
- package/components/Tooltip/Tooltip.d.ts +2 -2
- package/components/Tooltip/Tooltip.js +61 -40
- package/components/Tooltip/index.d.ts +1 -1
- package/components/Tooltip/types.d.ts +28 -1
- package/components/index.d.ts +2 -2
- package/components/index.js +1 -1
- package/package.json +1 -1
- package/styles/all.css +1 -1
- package/styles/all.expanded.css +310 -57
- package/styles/all.expanded.unlayered.css +310 -57
- package/styles/all.unlayered.css +1 -1
- package/styles/system/_tokens.scss +3 -0
- package/tui-manifest.json +278 -64
- package/utils/focus-trap.js +8 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useEffect, useId, useMemo, useRef, useState, cloneElement, isValidElement, } from 'react';
|
|
3
|
-
import { useFloating, offset, flip, shift, autoUpdate, FloatingPortal, } from '@floating-ui/react';
|
|
3
|
+
import { useFloating, offset, flip, shift, arrow, autoUpdate, FloatingPortal, FloatingArrow, } from '@floating-ui/react';
|
|
4
4
|
import { cx } from '../../utils/cx.js';
|
|
5
5
|
import { getPortalRootFor } from '../../utils/portal.js';
|
|
6
6
|
import { TooltipContext, TooltipProviderContext, useTooltipContext, useTooltipProviderContext, } from './TooltipContext.js';
|
|
@@ -8,7 +8,7 @@ import { toPlacement } from './types.js';
|
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// TooltipProvider
|
|
10
10
|
// =============================================================================
|
|
11
|
-
function TooltipProviderComponent({ delayDuration = 400, closeDelayDuration =
|
|
11
|
+
function TooltipProviderComponent({ delayDuration = 400, closeDelayDuration = 150, children, }) {
|
|
12
12
|
const value = useMemo(() => ({ delayDuration, closeDelayDuration }), [delayDuration, closeDelayDuration]);
|
|
13
13
|
return (_jsx(TooltipProviderContext.Provider, { value: value, children: children }));
|
|
14
14
|
}
|
|
@@ -28,46 +28,63 @@ function TooltipRoot({ open: controlledOpen, onOpenChange, defaultOpen = false,
|
|
|
28
28
|
}, [isControlled, onOpenChange]);
|
|
29
29
|
const triggerRef = useRef(null);
|
|
30
30
|
const contentId = useId();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
setOpen,
|
|
34
|
-
triggerRef,
|
|
35
|
-
contentId,
|
|
36
|
-
delayDuration: localDelay ?? providerContext.delayDuration,
|
|
37
|
-
closeDelayDuration: providerContext.closeDelayDuration,
|
|
38
|
-
}), [open, setOpen, contentId, localDelay, providerContext]);
|
|
39
|
-
return (_jsx(TooltipContext.Provider, { value: contextValue, children: children }));
|
|
40
|
-
}
|
|
41
|
-
// =============================================================================
|
|
42
|
-
// TooltipTrigger
|
|
43
|
-
// =============================================================================
|
|
44
|
-
function TooltipTriggerComponent({ asChild = false, children }) {
|
|
45
|
-
const { open, setOpen, triggerRef, contentId, delayDuration, closeDelayDuration } = useTooltipContext();
|
|
31
|
+
// Shared timer refs — lifted here so both Trigger and Content can
|
|
32
|
+
// cancel/schedule close for WCAG 1.4.13 hover persistence.
|
|
46
33
|
const openTimeoutRef = useRef(null);
|
|
47
34
|
const closeTimeoutRef = useRef(null);
|
|
48
|
-
const
|
|
49
|
-
|
|
35
|
+
const resolvedDelay = localDelay ?? providerContext.delayDuration;
|
|
36
|
+
const resolvedCloseDelay = providerContext.closeDelayDuration;
|
|
37
|
+
const clearAllTimeouts = useCallback(() => {
|
|
38
|
+
if (openTimeoutRef.current) {
|
|
50
39
|
clearTimeout(openTimeoutRef.current);
|
|
51
|
-
|
|
40
|
+
openTimeoutRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
if (closeTimeoutRef.current) {
|
|
52
43
|
clearTimeout(closeTimeoutRef.current);
|
|
44
|
+
closeTimeoutRef.current = null;
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
const cancelClose = useCallback(() => {
|
|
48
|
+
if (closeTimeoutRef.current) {
|
|
49
|
+
clearTimeout(closeTimeoutRef.current);
|
|
50
|
+
closeTimeoutRef.current = null;
|
|
51
|
+
}
|
|
53
52
|
}, []);
|
|
54
53
|
const handleOpen = useCallback(() => {
|
|
55
|
-
|
|
56
|
-
openTimeoutRef.current = setTimeout(() => setOpen(true),
|
|
57
|
-
}, [
|
|
54
|
+
clearAllTimeouts();
|
|
55
|
+
openTimeoutRef.current = setTimeout(() => setOpen(true), resolvedDelay);
|
|
56
|
+
}, [clearAllTimeouts, resolvedDelay, setOpen]);
|
|
58
57
|
const handleClose = useCallback(() => {
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
closeTimeoutRef.current = setTimeout(() => setOpen(false),
|
|
58
|
+
clearAllTimeouts();
|
|
59
|
+
if (resolvedCloseDelay > 0) {
|
|
60
|
+
closeTimeoutRef.current = setTimeout(() => setOpen(false), resolvedCloseDelay);
|
|
62
61
|
}
|
|
63
62
|
else {
|
|
64
63
|
setOpen(false);
|
|
65
64
|
}
|
|
66
|
-
}, [
|
|
65
|
+
}, [clearAllTimeouts, resolvedCloseDelay, setOpen]);
|
|
67
66
|
// Cleanup on unmount
|
|
68
67
|
useEffect(() => {
|
|
69
|
-
return () =>
|
|
70
|
-
}, [
|
|
68
|
+
return () => clearAllTimeouts();
|
|
69
|
+
}, [clearAllTimeouts]);
|
|
70
|
+
const contextValue = useMemo(() => ({
|
|
71
|
+
open,
|
|
72
|
+
setOpen,
|
|
73
|
+
triggerRef,
|
|
74
|
+
contentId,
|
|
75
|
+
delayDuration: resolvedDelay,
|
|
76
|
+
closeDelayDuration: resolvedCloseDelay,
|
|
77
|
+
handleOpen,
|
|
78
|
+
handleClose,
|
|
79
|
+
cancelClose,
|
|
80
|
+
}), [open, setOpen, contentId, resolvedDelay, resolvedCloseDelay, handleOpen, handleClose, cancelClose]);
|
|
81
|
+
return (_jsx(TooltipContext.Provider, { value: contextValue, children: children }));
|
|
82
|
+
}
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// TooltipTrigger
|
|
85
|
+
// =============================================================================
|
|
86
|
+
function TooltipTriggerComponent({ asChild = false, 'aria-label': ariaLabel, children, }) {
|
|
87
|
+
const { open, triggerRef, contentId, handleOpen, handleClose } = useTooltipContext();
|
|
71
88
|
// Merge child ref with our triggerRef
|
|
72
89
|
const setRefs = useCallback((node) => {
|
|
73
90
|
triggerRef.current = node;
|
|
@@ -97,20 +114,22 @@ function TooltipTriggerComponent({ asChild = false, children }) {
|
|
|
97
114
|
});
|
|
98
115
|
}
|
|
99
116
|
// Wrapper needs tabIndex to be keyboard-focusable for non-interactive children
|
|
100
|
-
return (_jsx("span", { ref: setRefs, ...triggerProps, tabIndex: 0, style: { display: 'inline-flex' }, children: children }));
|
|
117
|
+
return (_jsx("span", { ref: setRefs, ...triggerProps, tabIndex: 0, "aria-label": ariaLabel, style: { display: 'inline-flex' }, children: children }));
|
|
101
118
|
}
|
|
102
119
|
// =============================================================================
|
|
103
120
|
// TooltipContent
|
|
104
121
|
// =============================================================================
|
|
105
|
-
function TooltipContentComponent({ side = 'top', align = 'center', sideOffset = 8, className, children, }) {
|
|
106
|
-
const { open, setOpen, triggerRef, contentId } = useTooltipContext();
|
|
107
|
-
const
|
|
122
|
+
function TooltipContentComponent({ side = 'top', align = 'center', sideOffset = 8, theme = 'dark', className, children, }) {
|
|
123
|
+
const { open, setOpen, triggerRef, contentId, cancelClose, handleClose } = useTooltipContext();
|
|
124
|
+
const arrowRef = useRef(null);
|
|
125
|
+
const { refs, floatingStyles, context } = useFloating({
|
|
108
126
|
placement: toPlacement(side, align),
|
|
109
127
|
open,
|
|
110
128
|
middleware: [
|
|
111
129
|
offset(sideOffset),
|
|
112
130
|
flip(),
|
|
113
131
|
shift({ padding: 8 }),
|
|
132
|
+
arrow({ element: arrowRef }),
|
|
114
133
|
],
|
|
115
134
|
whileElementsMounted: autoUpdate,
|
|
116
135
|
});
|
|
@@ -121,17 +140,19 @@ function TooltipContentComponent({ side = 'top', align = 'center', sideOffset =
|
|
|
121
140
|
refs.setReference(triggerRef.current);
|
|
122
141
|
}
|
|
123
142
|
}, [triggerRef, refs]);
|
|
124
|
-
// Close on Escape
|
|
143
|
+
// Close on Escape
|
|
144
|
+
// - Focus on trigger: close + stopPropagation (avoid closing parent modal)
|
|
145
|
+
// - Hover-only (focus elsewhere): close without stopPropagation
|
|
125
146
|
useEffect(() => {
|
|
126
147
|
if (!open)
|
|
127
148
|
return;
|
|
128
149
|
const handleKeyDown = (e) => {
|
|
129
150
|
if (e.key === 'Escape') {
|
|
130
|
-
// Only close if focus is on or within the trigger element
|
|
131
151
|
const activeEl = document.activeElement;
|
|
132
152
|
const trigger = triggerRef.current;
|
|
133
|
-
|
|
134
|
-
|
|
153
|
+
const focusIsOnTrigger = trigger && (trigger === activeEl || trigger.contains(activeEl));
|
|
154
|
+
setOpen(false);
|
|
155
|
+
if (focusIsOnTrigger) {
|
|
135
156
|
e.stopPropagation(); // Prevent bubbling to modal's Escape handler
|
|
136
157
|
}
|
|
137
158
|
}
|
|
@@ -151,10 +172,10 @@ function TooltipContentComponent({ side = 'top', align = 'center', sideOffset =
|
|
|
151
172
|
'Tooltips should only contain plain text. Use Popover for interactive content.');
|
|
152
173
|
}
|
|
153
174
|
}
|
|
154
|
-
}, [open, refs.floating
|
|
175
|
+
}, [open, refs.floating]);
|
|
155
176
|
if (!open)
|
|
156
177
|
return null;
|
|
157
|
-
return (_jsx(FloatingPortal, { root: portalRoot, children:
|
|
178
|
+
return (_jsx(FloatingPortal, { root: portalRoot, children: _jsxs("div", { ref: refs.setFloating, id: contentId, role: "tooltip", className: cx('tui-tooltip', theme === 'light' && 'is-theme-light', className), style: floatingStyles, onMouseEnter: cancelClose, onMouseLeave: handleClose, children: [children, _jsx(FloatingArrow, { ref: arrowRef, context: context, className: "tui-tooltip__arrow" })] }) }));
|
|
158
179
|
}
|
|
159
180
|
export const Tooltip = TooltipRoot;
|
|
160
181
|
Tooltip.Provider = TooltipProviderComponent;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Tooltip, TooltipProvider, TooltipTrigger, TooltipContent } from './Tooltip';
|
|
2
|
-
export type { TooltipProviderProps, TooltipProps, TooltipTriggerProps, TooltipContentProps, Side, Align, } from './types';
|
|
2
|
+
export type { TooltipProviderProps, TooltipProps, TooltipTriggerProps, TooltipContentProps, TooltipTheme, Side, Align, } from './types';
|
|
@@ -15,7 +15,9 @@ export type TooltipProviderProps = {
|
|
|
15
15
|
delayDuration?: number;
|
|
16
16
|
/**
|
|
17
17
|
* Delay in ms before tooltip closes after pointer leaves.
|
|
18
|
-
*
|
|
18
|
+
* Provides a brief window for users to move their pointer to the tooltip
|
|
19
|
+
* content (partial WCAG 1.4.13 compliance).
|
|
20
|
+
* @default 150
|
|
19
21
|
*/
|
|
20
22
|
closeDelayDuration?: number;
|
|
21
23
|
children: React.ReactNode;
|
|
@@ -47,8 +49,22 @@ export type TooltipTriggerProps = {
|
|
|
47
49
|
* @default false
|
|
48
50
|
*/
|
|
49
51
|
asChild?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Accessible label for the non-asChild wrapper span. When `asChild` is false,
|
|
54
|
+
* the trigger renders as a `<span>` with no semantic role — this label gives
|
|
55
|
+
* assistive technology a name for the focusable element. Omitting this when
|
|
56
|
+
* the span wraps non-descriptive content (e.g. an icon) means screen readers
|
|
57
|
+
* will announce the element with no accessible name.
|
|
58
|
+
*/
|
|
59
|
+
'aria-label'?: string;
|
|
50
60
|
children: React.ReactNode;
|
|
51
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Visual theme for the tooltip.
|
|
64
|
+
* - `'dark'` (default): inverted background, high contrast against page content.
|
|
65
|
+
* - `'light'`: surface background with border, blends with surrounding UI.
|
|
66
|
+
*/
|
|
67
|
+
export type TooltipTheme = 'dark' | 'light';
|
|
52
68
|
export type TooltipContentProps = {
|
|
53
69
|
/**
|
|
54
70
|
* Preferred side of the trigger to place the tooltip.
|
|
@@ -65,6 +81,11 @@ export type TooltipContentProps = {
|
|
|
65
81
|
* @default 8
|
|
66
82
|
*/
|
|
67
83
|
sideOffset?: number;
|
|
84
|
+
/**
|
|
85
|
+
* Visual theme variant.
|
|
86
|
+
* @default 'dark'
|
|
87
|
+
*/
|
|
88
|
+
theme?: TooltipTheme;
|
|
68
89
|
/**
|
|
69
90
|
* Additional CSS class names.
|
|
70
91
|
*/
|
|
@@ -78,6 +99,12 @@ export type TooltipContextValue = {
|
|
|
78
99
|
contentId: string;
|
|
79
100
|
delayDuration: number;
|
|
80
101
|
closeDelayDuration: number;
|
|
102
|
+
/** Open tooltip after delayDuration. Cancels any pending close. */
|
|
103
|
+
handleOpen: () => void;
|
|
104
|
+
/** Close tooltip after closeDelayDuration. Cancels any pending open. */
|
|
105
|
+
handleClose: () => void;
|
|
106
|
+
/** Cancel any pending close timeout (for WCAG 1.4.13 hover persistence). */
|
|
107
|
+
cancelClose: () => void;
|
|
81
108
|
};
|
|
82
109
|
/**
|
|
83
110
|
* Convert side + align to Floating UI placement.
|
package/components/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { ChipGroup } from './ChipGroup';
|
|
|
11
11
|
export { Chips } from './Chips';
|
|
12
12
|
export { ContentIndicator } from './ContentIndicator';
|
|
13
13
|
export { Combobox, ComboboxContent, ComboboxOption, ComboboxGroup, ComboboxLabel, useCombobox } from './Combobox';
|
|
14
|
-
export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, useDropdown } from './Dropdown';
|
|
14
|
+
export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator, DropdownHeader, useDropdown } from './Dropdown';
|
|
15
15
|
export { Field } from './Field';
|
|
16
16
|
export { MultiSelect, MultiSelectTrigger, MultiSelectContent, MultiSelectOption, MultiSelectGroup, MultiSelectLabel, useMultiSelect } from './MultiSelect';
|
|
17
17
|
export { Select, SelectTrigger, SelectContent, SelectOption, SelectGroup, SelectLabel, useSelect } from './Select';
|
|
@@ -46,7 +46,7 @@ export type { ChipProps } from './Chip';
|
|
|
46
46
|
export type { ChipGroupProps, ChipGroupSingleProps, ChipGroupMultipleProps } from './ChipGroup';
|
|
47
47
|
export type { ChipsProps, ChipOption } from './Chips';
|
|
48
48
|
export type { ContentIndicatorProps } from './ContentIndicator';
|
|
49
|
-
export type { DropdownProps, DropdownTriggerProps, DropdownContentProps, DropdownItemProps, } from './Dropdown';
|
|
49
|
+
export type { DropdownProps, DropdownTriggerProps, DropdownContentProps, DropdownItemProps, DropdownSeparatorProps, DropdownHeaderProps, } from './Dropdown';
|
|
50
50
|
export type { ComboboxProps, ComboboxContentProps, ComboboxOptionProps, ComboboxGroupProps, ComboboxLabelProps, ComboboxRegisteredOption, } from './Combobox';
|
|
51
51
|
export type { FieldProps } from './Field';
|
|
52
52
|
export type { MultiSelectProps, MultiSelectTriggerProps, MultiSelectContentProps, MultiSelectOptionProps, MultiSelectGroupProps, MultiSelectLabelProps, MultiSelectValue, OptionValue as MultiSelectOptionValue, DisplayMode as MultiSelectDisplayMode, MultiSelectRegisteredOption, } from './MultiSelect';
|
package/components/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export { ChipGroup } from './ChipGroup/index.js';
|
|
|
9
9
|
export { Chips } from './Chips/index.js';
|
|
10
10
|
export { ContentIndicator } from './ContentIndicator/index.js';
|
|
11
11
|
export { Combobox, ComboboxContent, ComboboxOption, ComboboxGroup, ComboboxLabel, useCombobox } from './Combobox/index.js';
|
|
12
|
-
export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, useDropdown } from './Dropdown/index.js';
|
|
12
|
+
export { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator, DropdownHeader, useDropdown } from './Dropdown/index.js';
|
|
13
13
|
export { Field } from './Field/index.js';
|
|
14
14
|
export { MultiSelect, MultiSelectTrigger, MultiSelectContent, MultiSelectOption, MultiSelectGroup, MultiSelectLabel, useMultiSelect } from './MultiSelect/index.js';
|
|
15
15
|
export { Select, SelectTrigger, SelectContent, SelectOption, SelectGroup, SelectLabel, useSelect } from './Select/index.js';
|