@tangible/ui 0.0.5 → 0.0.6

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 CHANGED
@@ -1,17 +1,17 @@
1
1
  # Tangible UI
2
2
 
3
- Design system for Tangible WordPress plugins. React components + CSS tokens + utility classes.
3
+ Design system for Tangible WordPress plugins. React components, CSS custom property tokens, and CSS-only form elements.
4
4
 
5
5
  **Live Storybook:** https://storybook-tangible-ui.pages.dev
6
6
 
7
7
  ## Components
8
8
 
9
- - **Primitives:** Button, Chip, ChipGroup, Icon, IconButton, Progress, Rating, Tooltip
9
+ - **Primitives:** Button, IconButton, Chip, ChipGroup, Icon, Progress, Rating, Tooltip
10
10
  - **Layout:** Accordion, Card, Modal, Notice, Sidebar, Tabs, Toolbar
11
11
  - **Data:** DataTable, StepList, StepIndicator, Pager
12
- - **Form Controls:** Select, MultiSelect, Combobox, TextInput, Textarea, Checkbox, Switch, Radio
12
+ - **Form Controls:** Select, MultiSelect, Combobox, TextInput, Textarea, Checkbox, Switch, Radio/RadioGroup
13
13
  - **Composites:** Avatar, Dropdown, MoveHandle, OverlapStack, SegmentedControl, Field
14
- - **CSS-only Inputs:** Text, textarea, select, checkbox, radio, toggle, file
14
+ - **CSS-only Inputs:** Text, textarea, select, checkbox, radio, toggle, file (no JS required)
15
15
 
16
16
  ## Getting Started
17
17
 
@@ -21,21 +21,40 @@ Design system for Tangible WordPress plugins. React components + CSS tokens + ut
21
21
  npm install @tangible/ui
22
22
  ```
23
23
 
24
+ #### Optional peer dependencies
25
+
26
+ Some components require additional packages. Install only what you use:
27
+
28
+ | Package | Required by | Size |
29
+ |---------|-------------|------|
30
+ | `@floating-ui/react` | Select, MultiSelect, Combobox, Dropdown, Tooltip | ~90 KB |
31
+ | `@tanstack/react-table` | DataTable | ~50 KB |
32
+
33
+ ```bash
34
+ # If using Select, Dropdown, Tooltip, etc.
35
+ npm install @floating-ui/react
36
+
37
+ # If using DataTable
38
+ npm install @tanstack/react-table
39
+ ```
40
+
41
+ Components without these dependencies (Button, Card, Accordion, Modal, Tabs, etc.) work with zero additional installs.
42
+
24
43
  ### Import styles
25
44
 
26
45
  ```tsx
27
- // In your app entry point
28
46
  import '@tangible/ui/styles';
29
47
  ```
30
48
 
31
- Or for WordPress contexts (no CSS layers):
49
+ For WordPress plugin contexts where CSS layers can lose to unlayered theme styles:
50
+
32
51
  ```tsx
33
52
  import '@tangible/ui/styles/unlayered';
34
53
  ```
35
54
 
36
- ### Wrap your app
55
+ ### Set up the interface wrapper
37
56
 
38
- Components require the `.tui-interface` wrapper to access design tokens:
57
+ All components require the `.tui-interface` wrapper to access design tokens:
39
58
 
40
59
  ```tsx
41
60
  function App() {
@@ -47,34 +66,112 @@ function App() {
47
66
  }
48
67
  ```
49
68
 
50
- Dark mode via `data-theme="dark"` on the wrapper.
69
+ ### Dark mode
70
+
71
+ Set `data-theme` on the wrapper:
72
+
73
+ ```tsx
74
+ <div className="tui-interface" data-theme="dark">
75
+ ```
76
+
77
+ - `"dark"` — force dark mode
78
+ - `"auto"` — follow `prefers-color-scheme`
79
+ - Omit attribute — light mode (inherits host colour)
51
80
 
52
81
  ### Use components
53
82
 
54
83
  ```tsx
55
- import { Button, Card, Tooltip, IconButton } from '@tangible/ui';
84
+ import { Button, Card, Select, SelectOption } from '@tangible/ui';
56
85
 
57
86
  function Example() {
58
87
  return (
59
88
  <Card>
60
89
  <Card.Body>
61
- <Button label="Click me" theme="primary" />
62
-
63
- <IconButton icon="system/settings" label="Settings" showTooltip />
64
-
65
- <Tooltip>
66
- <Tooltip.Trigger asChild>
67
- <Button label="Hover me" variant="outline" />
68
- </Tooltip.Trigger>
69
- <Tooltip.Content>Hello!</Tooltip.Content>
70
- </Tooltip>
90
+ <Button label="Save" theme="primary" />
91
+
92
+ <Select placeholder="Choose..." onValueChange={(v) => console.log(v)}>
93
+ <Select.Trigger />
94
+ <Select.Content>
95
+ <Select.Option value="a">Option A</Select.Option>
96
+ <Select.Option value="b">Option B</Select.Option>
97
+ </Select.Content>
98
+ </Select>
71
99
  </Card.Body>
72
100
  </Card>
73
101
  );
74
102
  }
75
103
  ```
76
104
 
77
- See the [Storybook](https://storybook-tangible-ui.pages.dev) for full component documentation and examples.
105
+ ### Tree-shaking
106
+
107
+ Individual component imports are available if your bundler doesn't tree-shake the barrel export:
108
+
109
+ ```tsx
110
+ import { Button } from '@tangible/ui/components/Button';
111
+ import { Tooltip } from '@tangible/ui/components/Tooltip';
112
+ ```
113
+
114
+ ## Customisation
115
+
116
+ ### Token overrides
117
+
118
+ Components are styled via CSS custom properties. Override them on `.tui-interface` or any ancestor:
119
+
120
+ ```css
121
+ /* Global overrides */
122
+ .tui-interface {
123
+ --tui-radius-md: 2px;
124
+ --tui-focus-ring-color: hotpink;
125
+ }
126
+
127
+ /* Scoped to a specific context */
128
+ .my-sidebar .tui-interface {
129
+ --tui-button-radius: 0;
130
+ --tui-control-height-md: 32px;
131
+ }
132
+ ```
133
+
134
+ ### Component API tokens
135
+
136
+ Each component reads its own `--tui-{component}-*` tokens via fallback chains. These are never defined by TUI — only read. Set them from consuming code:
137
+
138
+ ```css
139
+ .compact-form {
140
+ --tui-accordion-padding: 8px;
141
+ --tui-select-trigger-radius: 2px;
142
+ --tui-modal-spacing: 24px;
143
+ }
144
+ ```
145
+
146
+ See each component's `styles.scss` header for its full token API.
147
+
148
+ ### Form control sizing
149
+
150
+ All form controls share a unified sizing system:
151
+
152
+ ```css
153
+ .my-context .tui-interface {
154
+ --tui-control-height-sm: 28px;
155
+ --tui-control-height-md: 32px;
156
+ --tui-control-height-lg: 40px;
157
+
158
+ /* Optional: decouple font size from size tier */
159
+ --tui-control-font-size-sm: 13px;
160
+ --tui-control-font-size-md: 13px;
161
+ --tui-control-font-size-lg: 14px;
162
+ }
163
+ ```
164
+
165
+ ### Icons
166
+
167
+ Four icon sets available via the registry: `system`, `cred`, `reaction`, `player`.
168
+
169
+ ```tsx
170
+ import { Icon } from '@tangible/ui';
171
+
172
+ <Icon name="system/check" />
173
+ <Icon name="reaction/clap-fill" size="lg" />
174
+ ```
78
175
 
79
176
  ## Development
80
177
 
@@ -83,7 +180,7 @@ npm install
83
180
  npm run storybook # Dev server on port 6006
84
181
  ```
85
182
 
86
- ## Commands
183
+ ### Commands
87
184
 
88
185
  ```bash
89
186
  npm run storybook # Dev server
@@ -97,12 +194,12 @@ npm run test:visual:update # Regenerate visual baselines
97
194
 
98
195
  ## Documentation
99
196
 
100
- - `CLAUDE.md` — Development guide (architecture, patterns, conventions)
101
- - `CONTEXT.md` — Project background and LMS requirements
102
- - `TIMELINE.md` — Development roadmap (Jan–Mar 2026)
197
+ - `CLAUDE.md` — Architecture, patterns, conventions, gotchas
198
+ - `CONTEXT.md` — Project background and design philosophy
199
+ - `TIMELINE.md` — Development roadmap
103
200
  - `TESTING.md` — Testing strategy and infrastructure
104
- - `AGENTS.md` — Quality gate agent configurations
201
+ - `CHANGELOG.md` — Release history
105
202
 
106
203
  ## Status
107
204
 
108
- Under active development for Course Builder (LMS) and Quiz modules. Component APIs are stabilising but may change before 1.0.
205
+ Under active development. Component APIs are stabilising but may change before 1.0.
@@ -233,7 +233,7 @@ function AccordionTrigger({ asChild = false, 'aria-label': ariaLabel, children,
233
233
  return element;
234
234
  }
235
235
  // Default: render as button with built-in chevron indicator
236
- const button = (_jsxs("button", { ref: triggerRef, type: "button", id: triggerId, className: cx('tui-accordion__trigger', className), "aria-expanded": isOpen, "aria-controls": panelId, "aria-label": ariaLabel, disabled: disabled, "data-state": state, "data-disabled": disabled || undefined, onClick: handleClick, onKeyDown: handleKeyDown, children: [_jsx("span", { className: "tui-accordion__trigger-content", children: children }), _jsx(Icon, { name: "system/chevron-down", size: "lg", className: "tui-accordion__indicator", "aria-hidden": "true" })] }));
236
+ const button = (_jsxs("button", { ref: triggerRef, type: "button", id: triggerId, className: cx('tui-accordion__trigger', className), "aria-expanded": isOpen, "aria-controls": panelId, "aria-label": ariaLabel, disabled: disabled, "data-state": state, "data-disabled": disabled || undefined, onClick: handleClick, onKeyDown: handleKeyDown, children: [children && _jsx("span", { className: "tui-accordion__trigger-content", children: children }), _jsx(Icon, { name: "system/chevron-down", size: "lg", className: "tui-accordion__indicator", "aria-hidden": "true" })] }));
237
237
  // Wrap in heading if headingLevel is specified
238
238
  if (headingLevel) {
239
239
  const Heading = `h${headingLevel}`;
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
+ import { isDev } from '../../utils/is-dev.js';
3
4
  import { useFloating, offset, flip, shift, size as sizeMiddleware, autoUpdate, FloatingPortal, useDismiss, useInteractions, useListNavigation, useRole, } from '@floating-ui/react';
4
5
  import { cx } from '../../utils/cx.js';
5
6
  import { toKey } from '../../utils/value-key.js';
@@ -396,7 +397,7 @@ function ComboboxOptionComponent({ value: optionValue, disabled = false, textVal
396
397
  const textValue = explicitTextValue ?? (typeof children === 'string' ? children : '');
397
398
  // Warn in dev if textValue couldn't be derived
398
399
  useEffect(() => {
399
- if (import.meta.env.DEV && !textValue) {
400
+ if (isDev() && !textValue) {
400
401
  console.warn(`Combobox.Option with value="${optionValue}" has no textValue. Provide textValue prop when children is not a string.`);
401
402
  }
402
403
  }, [textValue, optionValue]);
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import { cx } from '../../utils/cx.js';
4
+ import { isDev } from '../../utils/is-dev.js';
4
5
  import { iconRegistry } from '../../icons/registry.js';
5
6
  /**
6
7
  * Icon component for SVG icons from the registry or emoji characters.
@@ -12,7 +13,7 @@ import { iconRegistry } from '../../icons/registry.js';
12
13
  export const Icon = React.forwardRef(({ name, emoji, label, size, className }, ref) => {
13
14
  const SvgIcon = name ? iconRegistry[name] : null;
14
15
  // Dev warning for invalid icon name
15
- if (import.meta.env.DEV && name && !SvgIcon) {
16
+ if (isDev() && name && !SvgIcon) {
16
17
  console.warn(`[Icon] Unknown icon name: "${name}". Check the registry.`);
17
18
  }
18
19
  // Decorative if no label provided
@@ -108,7 +108,7 @@ function ModalRoot({ open, onClose, size = 'md', stickyHead, stickyFoot, 'aria-l
108
108
  let target = null;
109
109
  if (initialFocusSelector) {
110
110
  target = dialog.querySelector(initialFocusSelector);
111
- if (!target && import.meta.env.DEV) {
111
+ if (!target && isDev()) {
112
112
  console.warn(`Modal: initialFocusSelector="${initialFocusSelector}" did not match any element. ` +
113
113
  `Falling back to first focusable element.`);
114
114
  }
@@ -118,7 +118,7 @@ function ModalRoot({ open, onClose, size = 'md', stickyHead, stickyFoot, 'aria-l
118
118
  }
119
119
  target.focus({ preventScroll: true });
120
120
  // Development warning for missing labelledBy target
121
- if (import.meta.env.DEV && labelledBy) {
121
+ if (isDev() && labelledBy) {
122
122
  const labelElement = document.getElementById(labelledBy);
123
123
  if (!labelElement) {
124
124
  console.warn(`Modal: aria-labelledby="${labelledBy}" references a non-existent element. ` +
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { forwardRef, useCallback, useEffect, useId, useRef } from 'react';
2
+ import { forwardRef, useCallback, useEffect, useId, useRef, useState } from 'react';
3
3
  import { cx } from '../../utils/cx.js';
4
4
  import { isDev } from '../../utils/is-dev.js';
5
5
  import { Icon } from '../Icon/index.js';
@@ -64,6 +64,17 @@ export const MoveHandle = forwardRef(function MoveHandle({ mode = 'full', size =
64
64
  fallback?.focus();
65
65
  }
66
66
  }, [mode, canMoveUp, canMoveDown]);
67
+ // Debounce the lock icon — prevents visual jitter when `locked` flashes
68
+ // briefly (e.g. during a save). Behaviour (disabled buttons) applies
69
+ // immediately; only the icon swap is delayed.
70
+ const [showLockIcon, setShowLockIcon] = useState(locked);
71
+ useEffect(() => {
72
+ if (locked) {
73
+ const id = setTimeout(() => setShowLockIcon(true), 150);
74
+ return () => clearTimeout(id);
75
+ }
76
+ setShowLockIcon(false);
77
+ }, [locked]);
67
78
  // Drag handle label precedence: dragHandleProps > labels.drag > default
68
79
  const resolvedDragLabel = dragHandleProps?.['aria-label'] ?? labels?.drag ?? 'Drag to reorder';
69
80
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -80,5 +91,5 @@ export const MoveHandle = forwardRef(function MoveHandle({ mode = 'full', size =
80
91
  const resolvedLockedDesc = locked
81
92
  ? (labels?.locked ?? 'This item is locked and cannot be reordered')
82
93
  : undefined;
83
- return (_jsxs("div", { ref: mergedRef, role: "group", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": locked ? lockedDescId : undefined, "aria-disabled": locked || undefined, className: cx('tui-move-handle', `is-size-${size}`, locked && 'is-locked', hasIndex && 'has-index', className), children: [locked && (_jsx("span", { id: lockedDescId, className: "tui-visually-hidden", children: resolvedLockedDesc })), onMoveUp && (_jsx("button", { type: "button", className: "tui-move-handle__up", "data-direction": "up", "aria-label": labels?.moveUp ?? 'Move up', disabled: locked || !canMoveUp, onClick: onMoveUp, children: _jsx(Icon, { name: "system/chevron-up" }) })), _jsx("div", { className: "tui-move-handle__center", children: locked ? (_jsx("span", { className: "tui-move-handle__lock", "aria-hidden": "true", children: _jsx(Icon, { name: "system/lock" }) })) : (_jsxs(_Fragment, { children: [hasIndex && (_jsx("span", { className: "tui-move-handle__index", "aria-hidden": "true", children: index })), _jsx("button", { type: "button", className: "tui-move-handle__handle", "data-role": "drag-handle", "aria-label": resolvedDragLabel, tabIndex: hasArrows ? -1 : 0, ...restDragProps, children: _jsx(Icon, { name: "system/handle-alt" }) })] })) }), onMoveDown && (_jsx("button", { type: "button", className: "tui-move-handle__down", "data-direction": "down", "aria-label": labels?.moveDown ?? 'Move down', disabled: locked || !canMoveDown, onClick: onMoveDown, children: _jsx(Icon, { name: "system/chevron-down" }) }))] }));
94
+ return (_jsxs("div", { ref: mergedRef, role: "group", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": locked ? lockedDescId : undefined, "aria-disabled": locked || undefined, className: cx('tui-move-handle', `is-size-${size}`, locked && 'is-locked', hasIndex && 'has-index', className), children: [locked && (_jsx("span", { id: lockedDescId, className: "tui-visually-hidden", children: resolvedLockedDesc })), onMoveUp && (_jsx("button", { type: "button", className: "tui-move-handle__up", "data-direction": "up", "aria-label": labels?.moveUp ?? 'Move up', disabled: locked || !canMoveUp, onClick: onMoveUp, children: _jsx(Icon, { name: "system/chevron-up" }) })), _jsx("div", { className: "tui-move-handle__center", children: showLockIcon ? (_jsx("span", { className: "tui-move-handle__lock", "aria-hidden": "true", children: _jsx(Icon, { name: "system/lock" }) })) : (_jsxs(_Fragment, { children: [hasIndex && (_jsx("span", { className: "tui-move-handle__index", "aria-hidden": "true", children: index })), _jsx("button", { type: "button", className: "tui-move-handle__handle", "data-role": "drag-handle", "aria-label": resolvedDragLabel, tabIndex: hasArrows ? -1 : 0, ...restDragProps, children: _jsx(Icon, { name: "system/handle-alt" }) })] })) }), onMoveDown && (_jsx("button", { type: "button", className: "tui-move-handle__down", "data-direction": "down", "aria-label": labels?.moveDown ?? 'Move down', disabled: locked || !canMoveDown, onClick: onMoveDown, children: _jsx(Icon, { name: "system/chevron-down" }) }))] }));
84
95
  });
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, cloneElement, isValidElement, } from 'react';
3
+ import { isDev } from '../../utils/is-dev.js';
3
4
  import { useFloating, offset, flip, shift, size as sizeMiddleware, autoUpdate, FloatingPortal, useDismiss, useInteractions, useListNavigation, useTypeahead, useRole, useClick, } from '@floating-ui/react';
4
5
  import { cx } from '../../utils/cx.js';
5
6
  import { getPortalRootFor } from '../../utils/portal.js';
@@ -549,7 +550,7 @@ function MultiSelectOptionComponent({ value: optionValue, disabled = false, text
549
550
  const textValue = explicitTextValue ?? (typeof children === 'string' ? children : '');
550
551
  // Warn in dev if textValue couldn't be derived
551
552
  useEffect(() => {
552
- if (import.meta.env.DEV && !textValue) {
553
+ if (isDev() && !textValue) {
553
554
  console.warn(`MultiSelect.Option with value="${optionValue}" has no textValue. Provide textValue prop when children is not a string.`);
554
555
  }
555
556
  }, [textValue, optionValue]);
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React from 'react';
3
3
  import { useProgressSegments } from './useProgressSegments.js';
4
4
  import { cx } from '../../utils/cx.js';
5
+ import { isDev } from '../../utils/is-dev.js';
5
6
  // =============================================================================
6
7
  // COMPONENT
7
8
  // =============================================================================
@@ -23,7 +24,7 @@ export function Progress(props) {
23
24
  // Calculate percentages for standard mode
24
25
  const pct = Math.max(0, Math.min(100, (value / max) * 100));
25
26
  // Dev warning: inside position only supports labelStart (or children)
26
- if (import.meta.env.DEV && labelPosition === 'inside' && labelStart && labelEnd) {
27
+ if (isDev() && labelPosition === 'inside' && labelStart && labelEnd) {
27
28
  console.warn('Progress: labelPosition="inside" only supports a single label. ' +
28
29
  'labelEnd will be ignored. Use labelStart or children for inside content.');
29
30
  }
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, cloneElement, isValidElement, } from 'react';
3
+ import { isDev } from '../../utils/is-dev.js';
3
4
  import { useFloating, offset, flip, shift, size as sizeMiddleware, autoUpdate, FloatingPortal, useDismiss, useInteractions, useListNavigation, useTypeahead, useRole, useClick, } from '@floating-ui/react';
4
5
  import { cx } from '../../utils/cx.js';
5
6
  import { toKey } from '../../utils/value-key.js';
@@ -435,7 +436,7 @@ function SelectOptionComponent({ value: optionValue, disabled = false, textValue
435
436
  const textValue = explicitTextValue ?? (typeof children === 'string' ? children : '');
436
437
  // Warn in dev if textValue couldn't be derived
437
438
  useEffect(() => {
438
- if (import.meta.env.DEV && !textValue) {
439
+ if (isDev() && !textValue) {
439
440
  console.warn(`Select.Option with value="${optionValue}" has no textValue. Provide textValue prop when children is not a string.`);
440
441
  }
441
442
  }, [textValue, optionValue]);
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo } from 'react';
3
3
  import { cx } from '../../utils/cx.js';
4
+ import { isDev } from '../../utils/is-dev.js';
4
5
  import { StepListContext, useStepListContext } from './StepListContext.js';
5
6
  import { StepIndicator } from '../StepIndicator/index.js';
6
7
  // =============================================================================
@@ -9,7 +10,7 @@ import { StepIndicator } from '../StepIndicator/index.js';
9
10
  function StepListRoot(props) {
10
11
  const { 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ariaCurrent = 'step', current, onSelect, children, className, } = props;
11
12
  // Dev warning for missing nav label
12
- if (import.meta.env.DEV && !ariaLabel && !ariaLabelledBy) {
13
+ if (isDev() && !ariaLabel && !ariaLabelledBy) {
13
14
  console.warn('StepList: Navigation landmark requires a label. ' +
14
15
  'Provide either `aria-label` or `aria-labelledby` prop for screen reader users.');
15
16
  }
@@ -104,7 +104,7 @@ function TabsRoot({ variant = 'underline', activationMode = 'auto', value: contr
104
104
  }, [registryVersion, activeValue, focusedValue, isControlled, onValueChange, getOrderedTabs]);
105
105
  // Dev-only: Tab-Panel pairing validation
106
106
  useEffect(() => {
107
- if (import.meta.env.DEV) {
107
+ if (isDev()) {
108
108
  // Defer validation to next microtask so all tabs/panels have registered
109
109
  queueMicrotask(() => {
110
110
  const tabValues = new Set(Array.from(tabsRef.current.keys()));
@@ -167,7 +167,7 @@ function TabsList({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy,
167
167
  const { orientation, activationMode, activeValue, focusedValue, getOrderedTabs, onSelect, setFocusedValue, tabsRef, } = useTabsContext();
168
168
  // Dev-only: Warn if missing accessible name
169
169
  useEffect(() => {
170
- if (import.meta.env.DEV) {
170
+ if (isDev()) {
171
171
  if (!ariaLabel && !ariaLabelledBy) {
172
172
  console.warn('Tabs.List: Missing accessible name. Provide aria-label or aria-labelledby.');
173
173
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useRef, cloneElement, isValidElement, } from 'react';
3
3
  import { cx } from '../../utils/cx.js';
4
+ import { isDev } from '../../utils/is-dev.js';
4
5
  // =============================================================================
5
6
  // Toolbar Component
6
7
  // =============================================================================
@@ -66,7 +67,7 @@ function ToolbarRoot({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledB
66
67
  const originalTabIndexMapRef = useRef(new WeakMap());
67
68
  // Dev warning for missing accessible name
68
69
  useEffect(() => {
69
- if (import.meta.env?.DEV && !ariaLabel && !ariaLabelledBy) {
70
+ if (isDev() && !ariaLabel && !ariaLabelledBy) {
70
71
  console.warn('[Toolbar] aria-label or aria-labelledby is required for accessibility.');
71
72
  }
72
73
  }, [ariaLabel, ariaLabelledBy]);
@@ -1,5 +1,6 @@
1
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 { isDev } from '../../utils/is-dev.js';
3
4
  import { useFloating, offset, flip, shift, arrow, autoUpdate, FloatingPortal, FloatingArrow, } from '@floating-ui/react';
4
5
  import { cx } from '../../utils/cx.js';
5
6
  import { getPortalRootFor } from '../../utils/portal.js';
@@ -165,7 +166,7 @@ function TooltipContentComponent({ side = 'top', align = 'center', sideOffset =
165
166
  // Dev warning: tooltips should not contain interactive content (WCAG 1.4.13)
166
167
  // Use Popover for interactive overlays instead
167
168
  useEffect(() => {
168
- if (import.meta.env?.DEV && open && refs.floating.current) {
169
+ if (isDev() && open && refs.floating.current) {
169
170
  const interactive = refs.floating.current.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
170
171
  if (interactive.length > 0) {
171
172
  console.warn('[Tooltip] Contains interactive elements which violates WCAG 1.4.13. ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangible/ui",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Tangible Design System",
5
5
  "type": "module",
6
6
  "main": "./components/index.js",
@@ -79,20 +79,18 @@
79
79
  "types": "./components/Dropdown/Dropdown.d.ts"
80
80
  }
81
81
  },
82
- "dependencies": {
83
- "@floating-ui/react": "^0.27.16",
84
- "@tanstack/react-table": "^8.21.3"
85
- },
86
82
  "peerDependencies": {
83
+ "@floating-ui/react": ">=0.27.0",
84
+ "@tanstack/react-table": ">=8.0.0",
87
85
  "react": ">=18.0.0",
88
86
  "react-dom": ">=18.0.0"
89
87
  },
90
88
  "peerDependenciesMeta": {
91
- "react": {
92
- "optional": false
89
+ "@floating-ui/react": {
90
+ "optional": true
93
91
  },
94
- "react-dom": {
95
- "optional": false
92
+ "@tanstack/react-table": {
93
+ "optional": true
96
94
  }
97
95
  },
98
96
  "homepage": "https://github.com/tangibleinc/tangible-ui",