@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.
Files changed (51) hide show
  1. package/README.md +21 -13
  2. package/components/Accordion/Accordion.d.ts +1 -1
  3. package/components/Accordion/Accordion.js +3 -3
  4. package/components/Accordion/types.d.ts +8 -1
  5. package/components/Avatar/Avatar.js +16 -7
  6. package/components/Avatar/AvatarGroup.js +7 -5
  7. package/components/Avatar/types.d.ts +11 -0
  8. package/components/Button/Button.js +10 -3
  9. package/components/Button/types.d.ts +9 -1
  10. package/components/Card/Card.js +26 -13
  11. package/components/Checkbox/Checkbox.d.ts +1 -1
  12. package/components/Chip/Chip.d.ts +37 -1
  13. package/components/Chip/Chip.js +10 -8
  14. package/components/ChipGroup/ChipGroup.js +5 -4
  15. package/components/ChipGroup/types.d.ts +3 -0
  16. package/components/Dropdown/Dropdown.d.ts +19 -1
  17. package/components/Dropdown/Dropdown.js +84 -28
  18. package/components/Dropdown/index.d.ts +2 -2
  19. package/components/Dropdown/index.js +1 -1
  20. package/components/Dropdown/types.d.ts +15 -0
  21. package/components/IconButton/IconButton.js +5 -4
  22. package/components/IconButton/index.d.ts +1 -1
  23. package/components/IconButton/types.d.ts +24 -4
  24. package/components/Modal/Modal.d.ts +16 -2
  25. package/components/Modal/Modal.js +45 -20
  26. package/components/MoveHandle/MoveHandle.js +3 -3
  27. package/components/MoveHandle/types.d.ts +12 -2
  28. package/components/Notice/Notice.js +32 -19
  29. package/components/Select/Select.js +6 -2
  30. package/components/Sidebar/Sidebar.d.ts +6 -1
  31. package/components/Sidebar/Sidebar.js +65 -11
  32. package/components/Sidebar/index.d.ts +1 -1
  33. package/components/Sidebar/types.d.ts +39 -14
  34. package/components/Tabs/Tabs.d.ts +1 -1
  35. package/components/Tabs/Tabs.js +12 -3
  36. package/components/Tabs/types.d.ts +20 -5
  37. package/components/TextInput/TextInput.js +10 -2
  38. package/components/Tooltip/Tooltip.d.ts +2 -2
  39. package/components/Tooltip/Tooltip.js +61 -40
  40. package/components/Tooltip/index.d.ts +1 -1
  41. package/components/Tooltip/types.d.ts +28 -1
  42. package/components/index.d.ts +2 -2
  43. package/components/index.js +1 -1
  44. package/package.json +1 -1
  45. package/styles/all.css +1 -1
  46. package/styles/all.expanded.css +310 -57
  47. package/styles/all.expanded.unlayered.css +310 -57
  48. package/styles/all.unlayered.css +1 -1
  49. package/styles/system/_tokens.scss +3 -0
  50. package/tui-manifest.json +278 -64
  51. 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 = 0, children, }) {
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
- const contextValue = useMemo(() => ({
32
- open,
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 clearTimeouts = useCallback(() => {
49
- if (openTimeoutRef.current)
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
- if (closeTimeoutRef.current)
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
- clearTimeouts();
56
- openTimeoutRef.current = setTimeout(() => setOpen(true), delayDuration);
57
- }, [clearTimeouts, delayDuration, setOpen]);
54
+ clearAllTimeouts();
55
+ openTimeoutRef.current = setTimeout(() => setOpen(true), resolvedDelay);
56
+ }, [clearAllTimeouts, resolvedDelay, setOpen]);
58
57
  const handleClose = useCallback(() => {
59
- clearTimeouts();
60
- if (closeDelayDuration > 0) {
61
- closeTimeoutRef.current = setTimeout(() => setOpen(false), closeDelayDuration);
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
- }, [clearTimeouts, closeDelayDuration, setOpen]);
65
+ }, [clearAllTimeouts, resolvedCloseDelay, setOpen]);
67
66
  // Cleanup on unmount
68
67
  useEffect(() => {
69
- return () => clearTimeouts();
70
- }, [clearTimeouts]);
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 { refs, floatingStyles } = useFloating({
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 — scoped to trigger focus to avoid closing modal + tooltip together
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
- if (trigger && (trigger === activeEl || trigger.contains(activeEl))) {
134
- setOpen(false);
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, children]);
175
+ }, [open, refs.floating]);
155
176
  if (!open)
156
177
  return null;
157
- return (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: contentId, role: "tooltip", className: cx('tui-tooltip', className), style: floatingStyles, children: 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
- * @default 0
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.
@@ -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';
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangible/ui",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Tangible Design System",
5
5
  "type": "module",
6
6
  "main": "./components/index.js",