@flikk/ui 1.0.0-beta.23 → 1.0.0-beta.24
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/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
- package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
- package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
- package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
- package/dist/components/core/Alert/Alert.js +2 -1
- package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/core/Badge/Badge.js +6 -17
- package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
- package/dist/components/core/Calendar/Calendar.js +1 -1
- package/dist/components/core/Carousel/CarouselBody.js +6 -1
- package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
- package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
- package/dist/components/core/DragDrop/DragDrop.js +170 -17
- package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +2 -0
- package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
- package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
- package/dist/components/core/Drawer/DrawerTitle.js +2 -2
- package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
- package/dist/components/core/Masonry/Masonry.js +7 -2
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +3 -3
- package/dist/components/core/Modal/Modal.types.d.ts +2 -0
- package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
- package/dist/components/core/Modal/ModalTitle.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +13 -8
- package/dist/components/core/Pagination/Pagination.js +1 -1
- package/dist/components/core/Popover/PopoverBody.js +3 -27
- package/dist/components/core/Popover/PopoverTrigger.js +1 -1
- package/dist/components/core/Progress/Progress.js +4 -4
- package/dist/components/core/Rating/Rating.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
- package/dist/components/core/Segmented/Segmented.js +6 -6
- package/dist/components/core/Segmented/SegmentedItem.js +28 -2
- package/dist/components/core/Skeleton/Skeleton.js +2 -2
- package/dist/components/core/Sortable/Sortable.d.ts +1 -1
- package/dist/components/core/Sortable/Sortable.js +20 -4
- package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
- package/dist/components/core/Tabs/Tabs.js +6 -4
- package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
- package/dist/components/core/Tabs/TabsBody.js +3 -3
- package/dist/components/core/Tabs/TabsList.js +19 -21
- package/dist/components/core/Tabs/TabsTrigger.js +47 -28
- package/dist/components/core/Toast/Toast.js +1 -1
- package/dist/components/core/Toast/ToastProvider.js +3 -2
- package/dist/components/core/Tooltip/Tooltip.js +25 -34
- package/dist/components/core/Tree/Tree.js +178 -8
- package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
- package/dist/components/data-display/Table/Table.js +2 -2
- package/dist/components/data-display/Table/Table.types.d.ts +2 -0
- package/dist/components/data-display/Table/TableFilter.js +21 -18
- package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
- package/dist/components/effects/Overlay/Overlay.js +4 -9
- package/dist/components/effects/PageTransition/PageTransition.js +17 -6
- package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
- package/dist/components/forms/Checkbox/Checkbox.js +11 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
- package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
- package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
- package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
- package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
- package/dist/components/forms/ColorPicker/index.d.ts +1 -1
- package/dist/components/forms/Combobox/Combobox.js +20 -45
- package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
- package/dist/components/forms/CronInput/CronInput.js +10 -4
- package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
- package/dist/components/forms/DatePicker/DatePicker.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
- package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +14 -10
- package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
- package/dist/components/forms/FormLabel/FormLabel.js +2 -2
- package/dist/components/forms/Input/Input.js +5 -3
- package/dist/components/forms/Input/Input.types.d.ts +2 -0
- package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
- package/dist/components/forms/InputAddress/InputAddress.js +8 -9
- package/dist/components/forms/InputCounter/InputCounter.js +25 -18
- package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
- package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
- package/dist/components/forms/InputOTP/InputOTP.js +26 -17
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
- package/dist/components/forms/InputTag/InputTag.js +12 -39
- package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
- package/dist/components/forms/Mention/Mention.js +13 -6
- package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
- package/dist/components/forms/Radio/Radio.js +12 -2
- package/dist/components/forms/Radio/Radio.theme.js +5 -1
- package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
- package/dist/components/forms/Radio/RadioGroup.js +8 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
- package/dist/components/forms/Select/Select.d.ts +2 -0
- package/dist/components/forms/Select/Select.js +26 -56
- package/dist/components/forms/Select/Select.types.d.ts +14 -0
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
- package/dist/components/forms/Slider/Slider.js +18 -4
- package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
- package/dist/components/forms/Switch/Switch.js +12 -19
- package/dist/components/forms/Switch/Switch.theme.js +12 -2
- package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
- package/dist/components/forms/Textarea/Textarea.js +8 -3
- package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
- package/dist/components/forms/TimePicker/TimePicker.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
- package/dist/components/forms/TimePicker/TimePickerContent.js +2 -29
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
- package/dist/components/forms/index.js +1 -1
- package/dist/components/layout/index.js +4 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useClickOutside.d.ts +10 -4
- package/dist/hooks/useClickOutside.js +44 -24
- package/dist/hooks/useDarkMode.d.ts +1 -0
- package/dist/hooks/useDarkMode.js +42 -0
- package/dist/hooks/useFormStateMachine.d.ts +1 -1
- package/dist/hooks/useNetworkStatus.js +1 -1
- package/dist/hooks/useOverlay.js +4 -0
- package/dist/hooks/useScaleBackground.js +5 -2
- package/dist/hooks/useSelectPortal.js +14 -5
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -57
- package/dist/shadcn-compat.css +80 -0
- package/dist/styles.css +1 -1
- package/dist/utils/stateMachine.d.ts +4 -4
- package/package.json +11 -10
- package/tailwind.preset.cjs +32 -3
- /package/{src/theme-plugin.css → dist/theme.css} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useCallback, useState, useMemo } from 'react';
|
|
2
|
+
import React__default, { useCallback, useState, useRef, useMemo } from 'react';
|
|
3
3
|
import { motion, AnimatePresence, useReducedMotion } from 'motion/react';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
5
|
import { treeTheme } from './Tree.theme.js';
|
|
@@ -7,6 +7,32 @@ import { Checkbox } from '../../forms/Checkbox/Checkbox.js';
|
|
|
7
7
|
import { ChevronRightIcon, DocumentIcon } from '@heroicons/react/24/outline';
|
|
8
8
|
import { getReducedMotionVariants, treeNodeVariants, expandIconVariants, childNodeVariants, childrenStagger } from './Tree.animations.js';
|
|
9
9
|
|
|
10
|
+
// Build a flat ordered list of visible (non-collapsed) node ids via depth-first traversal.
|
|
11
|
+
// Disabled nodes are included in the list so that ArrowDown/Up skip over them in the
|
|
12
|
+
// keyboard handler rather than here — keeping the traversal pure.
|
|
13
|
+
const buildVisibleNodeList = (nodes, expandedSet) => {
|
|
14
|
+
const result = [];
|
|
15
|
+
const traverse = (currentNodes) => {
|
|
16
|
+
for (const node of currentNodes) {
|
|
17
|
+
result.push(node.id);
|
|
18
|
+
if (node.children && node.children.length > 0 && expandedSet.has(node.id)) {
|
|
19
|
+
traverse(node.children);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
traverse(nodes);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
// Build a map from node id → parent node id for ArrowLeft "move to parent" navigation.
|
|
27
|
+
const buildParentMap = (nodes, parentId = null, acc = new Map()) => {
|
|
28
|
+
for (const node of nodes) {
|
|
29
|
+
acc.set(node.id, parentId);
|
|
30
|
+
if (node.children && node.children.length > 0) {
|
|
31
|
+
buildParentMap(node.children, node.id, acc);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return acc;
|
|
35
|
+
};
|
|
10
36
|
// Simplified selection utilities (merged from Tree.utils.ts)
|
|
11
37
|
const getDescendantIds = (node) => {
|
|
12
38
|
const descendants = [];
|
|
@@ -72,7 +98,7 @@ const calculateSelection = (nodeId, selectedKeys, data) => {
|
|
|
72
98
|
indeterminateKeys
|
|
73
99
|
};
|
|
74
100
|
};
|
|
75
|
-
const InternalTreeNode = React__default.memo(({ node, level, expanded, selected, isIndeterminate, selectable, checkable, hierarchicalSelection, expandable, expandedKeysSet, selectedKeysSet, indeterminateKeysSet, onNodeClick, onToggleExpand, theme, className, shouldReduceMotion, }) => {
|
|
101
|
+
const InternalTreeNode = React__default.memo(({ node, level, expanded, selected, isIndeterminate, selectable, checkable, hierarchicalSelection, expandable, expandedKeysSet, selectedKeysSet, indeterminateKeysSet, onNodeClick, onToggleExpand, onKeyboardNavigate, nodeRefs, focusedNodeId, onFocusNode, theme, className, shouldReduceMotion, }) => {
|
|
76
102
|
var _a;
|
|
77
103
|
const hasChildren = node.children && node.children.length > 0;
|
|
78
104
|
const isExpandable = expandable && hasChildren;
|
|
@@ -80,20 +106,40 @@ const InternalTreeNode = React__default.memo(({ node, level, expanded, selected,
|
|
|
80
106
|
const nodeVariants = shouldReduceMotion ? getReducedMotionVariants(treeNodeVariants) : treeNodeVariants;
|
|
81
107
|
const iconVariants = shouldReduceMotion ? getReducedMotionVariants(expandIconVariants) : expandIconVariants;
|
|
82
108
|
const childVariants = shouldReduceMotion ? getReducedMotionVariants(childNodeVariants) : childNodeVariants;
|
|
109
|
+
// Register/unregister this node's DOM element in the shared refs map.
|
|
110
|
+
const itemRef = useCallback((el) => {
|
|
111
|
+
if (el) {
|
|
112
|
+
nodeRefs.current.set(node.id, el);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
nodeRefs.current.delete(node.id);
|
|
116
|
+
}
|
|
117
|
+
}, [node.id, nodeRefs]);
|
|
118
|
+
// Roving tabIndex: only the focused node is reachable via Tab.
|
|
119
|
+
// If no node is focused yet, the first root node (level 0) gets tabIndex 0 as
|
|
120
|
+
// a sensible initial entry point — this is handled at the Tree root.
|
|
121
|
+
const isFocused = focusedNodeId === node.id;
|
|
122
|
+
const tabIndexValue = node.disabled ? -1 : (isFocused ? 0 : -1);
|
|
83
123
|
const handleNodeClick = useCallback((e) => {
|
|
84
124
|
e.stopPropagation();
|
|
85
125
|
if (node.disabled)
|
|
86
126
|
return;
|
|
127
|
+
onFocusNode(node.id);
|
|
87
128
|
if (selectable) {
|
|
88
129
|
onNodeClick(node);
|
|
89
130
|
}
|
|
90
|
-
}, [node, selectable, onNodeClick]);
|
|
131
|
+
}, [node, selectable, onNodeClick, onFocusNode]);
|
|
91
132
|
const handleExpandClick = useCallback((e) => {
|
|
92
133
|
e.stopPropagation();
|
|
93
134
|
if (isExpandable && !node.disabled) {
|
|
94
135
|
onToggleExpand(node);
|
|
95
136
|
}
|
|
96
137
|
}, [isExpandable, node, onToggleExpand]);
|
|
138
|
+
const handleFocus = useCallback(() => {
|
|
139
|
+
if (!node.disabled) {
|
|
140
|
+
onFocusNode(node.id);
|
|
141
|
+
}
|
|
142
|
+
}, [node.id, node.disabled, onFocusNode]);
|
|
97
143
|
const handleKeyDown = (e) => {
|
|
98
144
|
if (node.disabled)
|
|
99
145
|
return;
|
|
@@ -106,16 +152,43 @@ const InternalTreeNode = React__default.memo(({ node, level, expanded, selected,
|
|
|
106
152
|
}
|
|
107
153
|
break;
|
|
108
154
|
case 'ArrowRight':
|
|
155
|
+
e.preventDefault();
|
|
109
156
|
if (isExpandable && !expanded) {
|
|
110
|
-
|
|
157
|
+
// Collapsed node with children: expand it
|
|
111
158
|
onToggleExpand(node);
|
|
112
159
|
}
|
|
160
|
+
else if (isExpandable && expanded) {
|
|
161
|
+
// Expanded node with children: move focus to first child
|
|
162
|
+
onKeyboardNavigate(node.id, 'ArrowRight');
|
|
163
|
+
}
|
|
164
|
+
// Leaf node: do nothing (APG spec)
|
|
113
165
|
break;
|
|
114
166
|
case 'ArrowLeft':
|
|
167
|
+
e.preventDefault();
|
|
115
168
|
if (isExpandable && expanded) {
|
|
116
|
-
|
|
169
|
+
// Expanded node: collapse it
|
|
117
170
|
onToggleExpand(node);
|
|
118
171
|
}
|
|
172
|
+
else {
|
|
173
|
+
// Collapsed node or leaf: move to parent
|
|
174
|
+
onKeyboardNavigate(node.id, 'ArrowLeft');
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'ArrowDown':
|
|
178
|
+
e.preventDefault();
|
|
179
|
+
onKeyboardNavigate(node.id, 'ArrowDown');
|
|
180
|
+
break;
|
|
181
|
+
case 'ArrowUp':
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
onKeyboardNavigate(node.id, 'ArrowUp');
|
|
184
|
+
break;
|
|
185
|
+
case 'Home':
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
onKeyboardNavigate(node.id, 'Home');
|
|
188
|
+
break;
|
|
189
|
+
case 'End':
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
onKeyboardNavigate(node.id, 'End');
|
|
119
192
|
break;
|
|
120
193
|
}
|
|
121
194
|
};
|
|
@@ -126,10 +199,10 @@ const InternalTreeNode = React__default.memo(({ node, level, expanded, selected,
|
|
|
126
199
|
const labelStyle = cn(theme.labelStyle, {
|
|
127
200
|
'text-[var(--color-text-muted)]': node.disabled,
|
|
128
201
|
});
|
|
129
|
-
return (jsxs("div", { children: [jsxs("div", { className: nodeStyle, style: { paddingLeft: `calc(${level} * ${theme.indentSize} + 0.25rem)` }, onClick: handleNodeClick, onKeyDown: handleKeyDown,
|
|
202
|
+
return (jsxs("div", { children: [jsxs("div", { ref: itemRef, className: nodeStyle, style: { paddingLeft: `calc(${level} * ${theme.indentSize} + 0.25rem)` }, onClick: handleNodeClick, onKeyDown: handleKeyDown, onFocus: handleFocus, tabIndex: tabIndexValue, role: "treeitem", "aria-expanded": isExpandable ? expanded : undefined, "aria-selected": selectable ? selected : undefined, "aria-disabled": node.disabled, children: [isExpandable ? (jsx(motion.span, { className: cn(theme.expandIconStyle), variants: iconVariants, initial: "collapsed", animate: expanded ? "expanded" : "collapsed", onClick: handleExpandClick, "aria-label": expanded ? 'Collapse' : 'Expand', tabIndex: -1, children: jsx(ChevronRightIcon, { className: "size-4", strokeWidth: 2 }) })) : (jsx("span", { className: "w-4 mr-1" })), checkable && (jsx(Checkbox, { size: 'sm', id: `tree-checkbox-${node.id}`, name: `tree-checkbox-${node.id}`, value: node.id, checked: selected, indeterminate: hierarchicalSelection && isIndeterminate, onChange: () => onNodeClick(node), state: node.disabled ? "disabled" : "default", className: "flex-shrink-0", onClick: (e) => e.stopPropagation() })), (node.icon || !isExpandable) && (jsx("span", { className: cn(theme.iconStyle), children: node.icon || jsx(DocumentIcon, { className: "w-4 h-4" }) })), jsx("span", { className: labelStyle, title: node.label, children: node.label })] }), hasChildren && (jsx(AnimatePresence, { initial: false, children: expanded && (jsx(motion.div, { variants: nodeVariants, initial: "collapsed", animate: "expanded", exit: "collapsed", style: {
|
|
130
203
|
overflow: 'hidden',
|
|
131
204
|
transformOrigin: 'top'
|
|
132
|
-
}, transition: shouldReduceMotion ? { duration: 0.01 } : childrenStagger, children: (_a = node.children) === null || _a === void 0 ? void 0 : _a.map((childNode, index) => (jsx(motion.div, { variants: childVariants, initial: "hidden", animate: "visible", exit: "hidden", custom: index, children: jsx(InternalTreeNode, { node: childNode, level: level + 1, expanded: expandedKeysSet.has(childNode.id), selected: selectedKeysSet.has(childNode.id), isIndeterminate: indeterminateKeysSet.has(childNode.id), selectable: selectable, checkable: checkable, hierarchicalSelection: hierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: onNodeClick, onToggleExpand: onToggleExpand, theme: theme, shouldReduceMotion: shouldReduceMotion }) }, childNode.id))) }, `${node.id}-children`)) }))] }));
|
|
205
|
+
}, transition: shouldReduceMotion ? { duration: 0.01 } : childrenStagger, children: (_a = node.children) === null || _a === void 0 ? void 0 : _a.map((childNode, index) => (jsx(motion.div, { variants: childVariants, initial: "hidden", animate: "visible", exit: "hidden", custom: index, children: jsx(InternalTreeNode, { node: childNode, level: level + 1, expanded: expandedKeysSet.has(childNode.id), selected: selectedKeysSet.has(childNode.id), isIndeterminate: indeterminateKeysSet.has(childNode.id), selectable: selectable, checkable: checkable, hierarchicalSelection: hierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: onNodeClick, onToggleExpand: onToggleExpand, onKeyboardNavigate: onKeyboardNavigate, nodeRefs: nodeRefs, focusedNodeId: focusedNodeId, onFocusNode: onFocusNode, theme: theme, shouldReduceMotion: shouldReduceMotion }) }, childNode.id))) }, `${node.id}-children`)) }))] }));
|
|
133
206
|
});
|
|
134
207
|
const TreeRoot = React__default.forwardRef(({ data, selectable = false, checkable = false, hierarchicalSelection, expandable = true, defaultExpandedKeys = [], defaultSelectedKeys = [], onSelectionChange, onExpand, className, theme: customTheme = {}, ...htmlProps }, ref) => {
|
|
135
208
|
var _a;
|
|
@@ -144,9 +217,106 @@ const TreeRoot = React__default.forwardRef(({ data, selectable = false, checkabl
|
|
|
144
217
|
const [expandedKeys, setExpandedKeys] = useState(defaultExpandedKeys);
|
|
145
218
|
const [selectedKeys, setSelectedKeys] = useState(defaultSelectedKeys);
|
|
146
219
|
const [indeterminateKeys, setIndeterminateKeys] = useState([]);
|
|
220
|
+
// Roving tabIndex: track which node currently owns tabIndex=0.
|
|
221
|
+
// Initialise to the first root node (if any) so the tree is keyboard-enterable.
|
|
222
|
+
const [focusedNodeId, setFocusedNodeId] = useState(data.length > 0 ? data[0].id : null);
|
|
223
|
+
// Map from node id → DOM element, populated by each InternalTreeNode on mount.
|
|
224
|
+
const nodeRefs = useRef(new Map());
|
|
147
225
|
const expandedKeysSet = useMemo(() => new Set(expandedKeys), [expandedKeys]);
|
|
148
226
|
const selectedKeysSet = useMemo(() => new Set(selectedKeys), [selectedKeys]);
|
|
149
227
|
const indeterminateKeysSet = useMemo(() => new Set(indeterminateKeys), [indeterminateKeys]);
|
|
228
|
+
// Parent map for ArrowLeft navigation (move to parent node).
|
|
229
|
+
const parentMap = useMemo(() => buildParentMap(data), [data]);
|
|
230
|
+
// Called by InternalTreeNode when it receives focus (mouse or keyboard).
|
|
231
|
+
const handleFocusNode = useCallback((nodeId) => {
|
|
232
|
+
setFocusedNodeId(nodeId);
|
|
233
|
+
}, []);
|
|
234
|
+
// Central keyboard navigation handler: moves focus within the tree.
|
|
235
|
+
const handleKeyboardNavigate = useCallback((nodeId, key) => {
|
|
236
|
+
const visibleIds = buildVisibleNodeList(data, expandedKeysSet);
|
|
237
|
+
const focusById = (targetId) => {
|
|
238
|
+
const el = nodeRefs.current.get(targetId);
|
|
239
|
+
if (el) {
|
|
240
|
+
setFocusedNodeId(targetId);
|
|
241
|
+
el.focus();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const currentIndex = visibleIds.indexOf(nodeId);
|
|
245
|
+
switch (key) {
|
|
246
|
+
case 'ArrowDown': {
|
|
247
|
+
// Move to next visible node, skipping disabled ones.
|
|
248
|
+
for (let i = currentIndex + 1; i < visibleIds.length; i++) {
|
|
249
|
+
const candidateId = visibleIds[i];
|
|
250
|
+
const candidateEl = nodeRefs.current.get(candidateId);
|
|
251
|
+
if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
|
|
252
|
+
focusById(candidateId);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case 'ArrowUp': {
|
|
259
|
+
// Move to previous visible node, skipping disabled ones.
|
|
260
|
+
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
261
|
+
const candidateId = visibleIds[i];
|
|
262
|
+
const candidateEl = nodeRefs.current.get(candidateId);
|
|
263
|
+
if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
|
|
264
|
+
focusById(candidateId);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case 'Home': {
|
|
271
|
+
// Move to the first non-disabled visible node.
|
|
272
|
+
for (let i = 0; i < visibleIds.length; i++) {
|
|
273
|
+
const candidateId = visibleIds[i];
|
|
274
|
+
const candidateEl = nodeRefs.current.get(candidateId);
|
|
275
|
+
if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
|
|
276
|
+
focusById(candidateId);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
case 'End': {
|
|
283
|
+
// Move to the last non-disabled visible node.
|
|
284
|
+
for (let i = visibleIds.length - 1; i >= 0; i--) {
|
|
285
|
+
const candidateId = visibleIds[i];
|
|
286
|
+
const candidateEl = nodeRefs.current.get(candidateId);
|
|
287
|
+
if (candidateEl && candidateEl.getAttribute('aria-disabled') !== 'true') {
|
|
288
|
+
focusById(candidateId);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
case 'ArrowRight': {
|
|
295
|
+
// Move focus to the first child of an already-expanded node.
|
|
296
|
+
// (Expand-when-collapsed is handled directly in InternalTreeNode.)
|
|
297
|
+
const firstChildIndex = currentIndex + 1;
|
|
298
|
+
if (firstChildIndex < visibleIds.length) {
|
|
299
|
+
const firstChildId = visibleIds[firstChildIndex];
|
|
300
|
+
const firstChildEl = nodeRefs.current.get(firstChildId);
|
|
301
|
+
if (firstChildEl && firstChildEl.getAttribute('aria-disabled') !== 'true') {
|
|
302
|
+
focusById(firstChildId);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
case 'ArrowLeft': {
|
|
308
|
+
// Move focus to the parent node (when current node is collapsed or a leaf).
|
|
309
|
+
const parentId = parentMap.get(nodeId);
|
|
310
|
+
if (parentId) {
|
|
311
|
+
const parentEl = nodeRefs.current.get(parentId);
|
|
312
|
+
if (parentEl && parentEl.getAttribute('aria-disabled') !== 'true') {
|
|
313
|
+
focusById(parentId);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}, [data, expandedKeysSet, parentMap]);
|
|
150
320
|
// Handle node selection
|
|
151
321
|
const handleNodeSelect = useCallback((node) => {
|
|
152
322
|
if (!selectable || node.disabled)
|
|
@@ -194,7 +364,7 @@ const TreeRoot = React__default.forwardRef(({ data, selectable = false, checkabl
|
|
|
194
364
|
setExpandedKeys(newExpandedKeys);
|
|
195
365
|
onExpand === null || onExpand === void 0 ? void 0 : onExpand(newExpandedKeys, node, !isExpanded);
|
|
196
366
|
}, [expandedKeys, expandable, onExpand]);
|
|
197
|
-
return (jsx("div", { ref: ref, className: cn(theme.baseStyle, 'tree-container', className), role: "tree", "aria-multiselectable": checkable, ...htmlProps, children: data.map((node) => (jsx(InternalTreeNode, { node: node, level: 0, expanded: expandedKeysSet.has(node.id), selected: selectedKeysSet.has(node.id), isIndeterminate: indeterminateKeysSet.has(node.id), selectable: selectable, checkable: checkable, hierarchicalSelection: enableHierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: handleNodeSelect, onToggleExpand: handleNodeToggleExpand, theme: theme, shouldReduceMotion: shouldReduceMotion }, node.id))) }));
|
|
367
|
+
return (jsx("div", { ref: ref, className: cn(theme.baseStyle, 'tree-container', className), role: "tree", "aria-multiselectable": checkable, ...htmlProps, children: data.map((node) => (jsx(InternalTreeNode, { node: node, level: 0, expanded: expandedKeysSet.has(node.id), selected: selectedKeysSet.has(node.id), isIndeterminate: indeterminateKeysSet.has(node.id), selectable: selectable, checkable: checkable, hierarchicalSelection: enableHierarchicalSelection, expandable: expandable, expandedKeysSet: expandedKeysSet, selectedKeysSet: selectedKeysSet, indeterminateKeysSet: indeterminateKeysSet, onNodeClick: handleNodeSelect, onToggleExpand: handleNodeToggleExpand, onKeyboardNavigate: handleKeyboardNavigate, nodeRefs: nodeRefs, focusedNodeId: focusedNodeId, onFocusNode: handleFocusNode, theme: theme, shouldReduceMotion: shouldReduceMotion }, node.id))) }));
|
|
198
368
|
});
|
|
199
369
|
TreeRoot.displayName = 'Tree';
|
|
200
370
|
// Simplified compound component
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useRef, useCallback } from 'react';
|
|
2
|
+
import { useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { useGanttContext } from './GanttChart.js';
|
|
5
5
|
|
|
@@ -10,6 +10,18 @@ const GanttSplitter = () => {
|
|
|
10
10
|
const { theme, tablePanelWidth, setTablePanelWidth } = useGanttContext();
|
|
11
11
|
const startX = useRef(0);
|
|
12
12
|
const startWidth = useRef(0);
|
|
13
|
+
// Track active pointer listeners so they can be cleaned up on unmount during a drag
|
|
14
|
+
const activeListenersRef = useRef({ move: null, up: null });
|
|
15
|
+
// Cleanup on unmount — removes any lingering listeners if component unmounts during a drag
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
return () => {
|
|
18
|
+
const { move, up } = activeListenersRef.current;
|
|
19
|
+
if (move)
|
|
20
|
+
document.removeEventListener("pointermove", move);
|
|
21
|
+
if (up)
|
|
22
|
+
document.removeEventListener("pointerup", up);
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
13
25
|
const onPointerDown = useCallback((e) => {
|
|
14
26
|
e.preventDefault();
|
|
15
27
|
startX.current = e.clientX;
|
|
@@ -20,9 +32,11 @@ const GanttSplitter = () => {
|
|
|
20
32
|
setTablePanelWidth(next);
|
|
21
33
|
};
|
|
22
34
|
const onPointerUp = () => {
|
|
35
|
+
activeListenersRef.current = { move: null, up: null };
|
|
23
36
|
document.removeEventListener("pointermove", onPointerMove);
|
|
24
37
|
document.removeEventListener("pointerup", onPointerUp);
|
|
25
38
|
};
|
|
39
|
+
activeListenersRef.current = { move: onPointerMove, up: onPointerUp };
|
|
26
40
|
document.addEventListener("pointermove", onPointerMove);
|
|
27
41
|
document.addEventListener("pointerup", onPointerUp);
|
|
28
42
|
}, [tablePanelWidth, setTablePanelWidth]);
|
|
@@ -38,7 +38,7 @@ const TableContext = createContext({
|
|
|
38
38
|
*/
|
|
39
39
|
function TableComponent(props) {
|
|
40
40
|
var _a;
|
|
41
|
-
const { data, columns, rowKey, variant = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, } = props;
|
|
41
|
+
const { data, columns, rowKey, variant = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, caption, } = props;
|
|
42
42
|
// Column management hook
|
|
43
43
|
const { visibleColumns, columnOrder, visibleColumnsList, handleToggleColumn, handleColumnReorder, handleResetToDefault, } = useTableColumns({
|
|
44
44
|
columns,
|
|
@@ -148,7 +148,7 @@ function TableComponent(props) {
|
|
|
148
148
|
selectedRows,
|
|
149
149
|
onSelectionChange,
|
|
150
150
|
]);
|
|
151
|
-
return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children:
|
|
151
|
+
return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children: jsxs("table", { className: cn(baseClasses, variantClasses, className), role: "table", "aria-label": isDeclarativeAPI ? "Data table" : `Data table with ${paginatedData.length} rows`, "aria-rowcount": pagination ? pagination.totalItems : data === null || data === void 0 ? void 0 : data.length, "aria-colcount": visibleColumnsList.length, children: [caption && (jsx("caption", { className: "sr-only", children: caption })), isDeclarativeAPI ? (children) : (jsxs(Fragment, { children: [jsx(TableHeader, { columns: visibleColumnsList, sortConfig: sortConfig, onSortChange: onSortChange, filterConfig: filterConfig, onFilterChange: onFilterChange, freezeHeader: freezeHeader, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, expandable: expandable, expandedRowRender: expandedRowRender, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, totalRowCount: paginatedData.length, theme: theme, data: paginatedData, rowKey: rowKey }), jsx(TableBody, { data: paginatedData, columns: visibleColumnsList, rowKey: rowKey, expandedRowRender: expandedRowRender, expandedRows: expandedRows, onExpandedRowsChange: onExpandedRowsChange, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, theme: theme })] }))] }) })] }) }));
|
|
152
152
|
}
|
|
153
153
|
TableComponent.displayName = "Table";
|
|
154
154
|
const Table = TableComponent;
|
|
@@ -193,6 +193,8 @@ export interface TableProps<T = unknown> {
|
|
|
193
193
|
totalItems: number;
|
|
194
194
|
};
|
|
195
195
|
children?: ReactNode;
|
|
196
|
+
/** Optional accessible caption for the table, rendered as a visually-hidden <caption> element */
|
|
197
|
+
caption?: string;
|
|
196
198
|
}
|
|
197
199
|
export interface TableHeaderProps {
|
|
198
200
|
children?: ReactNode;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useCallback } from 'react';
|
|
3
|
-
import { inferFilterType, getDefaultOperator, isUnaryOperator,
|
|
2
|
+
import { useRef, useState, useCallback } from 'react';
|
|
3
|
+
import { inferFilterType, getDefaultOperator, isUnaryOperator, isBetweenOperator, getOperatorsForFilterType } from './Table.filterUtils.js';
|
|
4
4
|
import { Button } from '../../core/Button/Button.js';
|
|
5
5
|
import { Input } from '../../forms/Input/Input.js';
|
|
6
6
|
import '../../forms/Input/Input.theme.js';
|
|
@@ -57,6 +57,7 @@ function FilterValueInput({ filter, column, filterType, onValueChange, }) {
|
|
|
57
57
|
}
|
|
58
58
|
function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: controlledLogic, onFilterLogicChange, data, className, }) {
|
|
59
59
|
const filterableColumns = columns.filter((col) => col.filterable);
|
|
60
|
+
const filterIdRef = useRef(0);
|
|
60
61
|
// Internal filter logic state
|
|
61
62
|
const [internalLogic, setInternalLogic] = useState("and");
|
|
62
63
|
const filterLogic = controlledLogic !== null && controlledLogic !== void 0 ? controlledLogic : internalLogic;
|
|
@@ -87,7 +88,7 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
|
|
|
87
88
|
const resolvedType = inferFilterType(defaultColumn, data);
|
|
88
89
|
return [
|
|
89
90
|
{
|
|
90
|
-
id: `filter-${
|
|
91
|
+
id: `filter-${++filterIdRef.current}`,
|
|
91
92
|
columnId: defaultColumn.id,
|
|
92
93
|
operator: getDefaultOperator(resolvedType),
|
|
93
94
|
value: "",
|
|
@@ -107,7 +108,7 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
|
|
|
107
108
|
return "text";
|
|
108
109
|
return inferFilterType(column, data);
|
|
109
110
|
};
|
|
110
|
-
const syncFilters = (filters) => {
|
|
111
|
+
const syncFilters = useCallback((filters) => {
|
|
111
112
|
const newFilterConfig = filters
|
|
112
113
|
.filter((f) => {
|
|
113
114
|
if (!f.columnId)
|
|
@@ -131,8 +132,8 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
|
|
|
131
132
|
operator: f.operator,
|
|
132
133
|
}));
|
|
133
134
|
onFilterChange(newFilterConfig);
|
|
134
|
-
};
|
|
135
|
-
const handleAddFilter = () => {
|
|
135
|
+
}, [onFilterChange]);
|
|
136
|
+
const handleAddFilter = useCallback(() => {
|
|
136
137
|
const defaultColumn = filterableColumns[0];
|
|
137
138
|
if (!defaultColumn)
|
|
138
139
|
return;
|
|
@@ -141,21 +142,22 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
|
|
|
141
142
|
setActiveFilters((prev) => [
|
|
142
143
|
...prev,
|
|
143
144
|
{
|
|
144
|
-
id: `filter-${
|
|
145
|
+
id: `filter-${++filterIdRef.current}`,
|
|
145
146
|
columnId: defaultColumn.id,
|
|
146
147
|
operator: defaultOp,
|
|
147
148
|
value: "",
|
|
148
149
|
},
|
|
149
150
|
]);
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
152
|
+
}, [filterableColumns, onFilterChange]);
|
|
153
|
+
const handleRemoveFilter = useCallback((filterId) => {
|
|
152
154
|
setActiveFilters((prev) => {
|
|
153
155
|
const updated = prev.filter((f) => f.id !== filterId);
|
|
154
156
|
syncFilters(updated);
|
|
155
157
|
return updated;
|
|
156
158
|
});
|
|
157
|
-
};
|
|
158
|
-
const handleFilterColumnChange = (filterId, columnId) => {
|
|
159
|
+
}, [syncFilters]);
|
|
160
|
+
const handleFilterColumnChange = useCallback((filterId, columnId) => {
|
|
159
161
|
setActiveFilters((prev) => {
|
|
160
162
|
const updated = prev.map((f) => {
|
|
161
163
|
if (f.id !== filterId)
|
|
@@ -168,8 +170,9 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
|
|
|
168
170
|
syncFilters(updated);
|
|
169
171
|
return updated;
|
|
170
172
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
174
|
+
}, [filterableColumns, data, syncFilters]);
|
|
175
|
+
const handleFilterOperatorChange = useCallback((filterId, operator) => {
|
|
173
176
|
setActiveFilters((prev) => {
|
|
174
177
|
const updated = prev.map((f) => {
|
|
175
178
|
if (f.id !== filterId)
|
|
@@ -188,18 +191,18 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
|
|
|
188
191
|
syncFilters(updated);
|
|
189
192
|
return updated;
|
|
190
193
|
});
|
|
191
|
-
};
|
|
192
|
-
const handleFilterValueChange = (filterId, value) => {
|
|
194
|
+
}, [syncFilters]);
|
|
195
|
+
const handleFilterValueChange = useCallback((filterId, value) => {
|
|
193
196
|
setActiveFilters((prev) => {
|
|
194
197
|
const updated = prev.map((f) => f.id === filterId ? { ...f, value } : f);
|
|
195
198
|
syncFilters(updated);
|
|
196
199
|
return updated;
|
|
197
200
|
});
|
|
198
|
-
};
|
|
199
|
-
const handleClearAll = () => {
|
|
201
|
+
}, [syncFilters]);
|
|
202
|
+
const handleClearAll = useCallback(() => {
|
|
200
203
|
setActiveFilters([]);
|
|
201
204
|
onFilterChange([]);
|
|
202
|
-
};
|
|
205
|
+
}, [onFilterChange]);
|
|
203
206
|
const appliedFilterCount = activeFilters.filter((f) => {
|
|
204
207
|
if (!f.columnId)
|
|
205
208
|
return false;
|
|
@@ -204,13 +204,9 @@ const CustomCursorFollow = React.forwardRef(({ children, sideOffset = 15, align
|
|
|
204
204
|
const { cursorPos, isActive, cursorRef } = useCustomCursor();
|
|
205
205
|
const cursorFollowRef = React.useRef(null);
|
|
206
206
|
const shouldReduceMotion = useReducedMotion();
|
|
207
|
-
// Select random color for user variant if not provided (stable across renders)
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
return color;
|
|
211
|
-
const randomIndex = Math.floor(Math.random() * userVariantColors.length);
|
|
212
|
-
return userVariantColors[randomIndex];
|
|
213
|
-
}, [color]);
|
|
207
|
+
// Select random color for user variant if not provided (stable across renders via ref)
|
|
208
|
+
const randomColorRef = React.useRef(userVariantColors[Math.floor(Math.random() * userVariantColors.length)]);
|
|
209
|
+
const resolvedColor = color !== null && color !== void 0 ? color : randomColorRef.current;
|
|
214
210
|
React.useImperativeHandle(ref, () => cursorFollowRef.current);
|
|
215
211
|
const x = useMotionValue(0);
|
|
216
212
|
const y = useMotionValue(0);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useRef, useCallback, useEffect
|
|
2
|
+
import React__default, { useRef, useCallback, useEffect } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
|
|
5
5
|
import { cn } from '../../../utils/cn.js';
|
|
6
6
|
import { useOverlay, lockScroll, unlockScroll } from '../../../hooks/useOverlay.js';
|
|
7
7
|
import { useFocusTrap } from '../../../hooks/useFocusTrap.js';
|
|
8
8
|
import { useIsClient } from '../../../hooks/useIsClient.js';
|
|
9
|
+
import { useDarkMode } from '../../../hooks/useDarkMode.js';
|
|
9
10
|
import { overlayTheme, overlayPositionVariants } from './Overlay.theme.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -138,14 +139,8 @@ const Overlay = React__default.forwardRef(({ isOpen, onClose, closeOnOverlayClic
|
|
|
138
139
|
};
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
// Detect dark mode
|
|
142
|
-
const isDarkMode =
|
|
143
|
-
if (!isClient)
|
|
144
|
-
return false;
|
|
145
|
-
return (document.documentElement.classList.contains("dark") ||
|
|
146
|
-
document.body.classList.contains("dark") ||
|
|
147
|
-
document.querySelector(".dark") !== null);
|
|
148
|
-
}, [isClient]);
|
|
142
|
+
// Detect dark mode reactively via a shared MutationObserver-backed hook (SSR-safe)
|
|
143
|
+
const isDarkMode = useDarkMode();
|
|
149
144
|
// Determine portal target
|
|
150
145
|
const target = portalTarget !== undefined
|
|
151
146
|
? portalTarget
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
3
|
import { motion } from 'motion/react';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
5
|
import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } from './PageTransition.animations.js';
|
|
@@ -7,7 +7,14 @@ import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } f
|
|
|
7
7
|
const PageTransition = ({ children, logo, type = "slide", direction = "up", duration = 0.8, delay = 0.8, color = "bg-[var(--color-primary)]", className, parallax = true, onStart, onComplete, }) => {
|
|
8
8
|
const [phase, setPhase] = useState("covered");
|
|
9
9
|
const [completedPanels, setCompletedPanels] = useState(0);
|
|
10
|
+
// Stable refs for callbacks to avoid stale closures without re-running the effect
|
|
11
|
+
const onStartRef = useRef(onStart);
|
|
12
|
+
const onCompleteRef = useRef(onComplete);
|
|
13
|
+
useEffect(() => { onStartRef.current = onStart; }, [onStart]);
|
|
14
|
+
useEffect(() => { onCompleteRef.current = onComplete; }, [onComplete]);
|
|
10
15
|
useEffect(() => {
|
|
16
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
17
|
+
return;
|
|
11
18
|
let cancelled = false;
|
|
12
19
|
const loadPromise = new Promise((resolve) => {
|
|
13
20
|
if (document.readyState === "complete") {
|
|
@@ -24,8 +31,9 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
|
|
|
24
31
|
if (!cancelled) {
|
|
25
32
|
setPhase("exit");
|
|
26
33
|
setTimeout(() => {
|
|
34
|
+
var _a;
|
|
27
35
|
if (!cancelled)
|
|
28
|
-
|
|
36
|
+
(_a = onStartRef.current) === null || _a === void 0 ? void 0 : _a.call(onStartRef);
|
|
29
37
|
}, 250);
|
|
30
38
|
}
|
|
31
39
|
});
|
|
@@ -34,10 +42,11 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
|
|
|
34
42
|
};
|
|
35
43
|
}, [delay]);
|
|
36
44
|
useEffect(() => {
|
|
45
|
+
var _a;
|
|
37
46
|
if (type === "split" && completedPanels === 2) {
|
|
38
|
-
|
|
47
|
+
(_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
|
|
39
48
|
}
|
|
40
|
-
}, [completedPanels, type
|
|
49
|
+
}, [completedPanels, type]);
|
|
41
50
|
if (type === "split") {
|
|
42
51
|
const axis = direction === "up" || direction === "down" ? "h" : "v";
|
|
43
52
|
const splitVars = buildSplitVariants(axis, duration);
|
|
@@ -58,13 +67,15 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
|
|
|
58
67
|
if (type === "slide" && parallax) {
|
|
59
68
|
const logoVariants = buildLogoParallaxVariants(direction, duration);
|
|
60
69
|
return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
|
|
70
|
+
var _a;
|
|
61
71
|
if (def === "exit")
|
|
62
|
-
|
|
72
|
+
(_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
|
|
63
73
|
}, children: logo && (jsx(motion.div, { className: "relative z-10 select-none", variants: logoVariants, initial: "covered", animate: phase, children: logo })) })] }));
|
|
64
74
|
}
|
|
65
75
|
return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
|
|
76
|
+
var _a;
|
|
66
77
|
if (def === "exit")
|
|
67
|
-
|
|
78
|
+
(_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
|
|
68
79
|
}, children: logo && jsx("div", { className: "relative z-10 select-none", children: logo }) })] }));
|
|
69
80
|
};
|
|
70
81
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useRef, useEffect } from 'react';
|
|
3
|
+
import { useReducedMotion } from 'motion/react';
|
|
3
4
|
import * as THREE from 'three';
|
|
4
5
|
import { cn } from '../../../utils/cn.js';
|
|
5
6
|
import { variationMap } from './SpotlightBorder.types.js';
|
|
@@ -21,12 +22,10 @@ import { fragmentShader, vertexShader } from './SpotlightBorder.shaders.js';
|
|
|
21
22
|
* ```
|
|
22
23
|
*/
|
|
23
24
|
const SpotlightBorder = ({ children, variation = "rounded-rect", rounded, borderWidth = 2, color = "#ffffff", spotlightSize = 0.4, spotlightEdge = 0.5, glowExtent = 20, glowIntensity = 0.6, tiltIntensity = 5, shouldAnimate = true, className, style, ...props }) => {
|
|
24
|
-
var _a;
|
|
25
25
|
const outerRef = useRef(null);
|
|
26
26
|
const containerRef = useRef(null);
|
|
27
|
-
//
|
|
28
|
-
const prefersReducedMotion =
|
|
29
|
-
((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)").matches);
|
|
27
|
+
// useReducedMotion is SSR-safe (returns null on server, actual value after hydration)
|
|
28
|
+
const prefersReducedMotion = useReducedMotion();
|
|
30
29
|
const isAnimating = shouldAnimate && !prefersReducedMotion;
|
|
31
30
|
// Refs to store THREE.js objects across effects
|
|
32
31
|
const rendererRef = useRef(null);
|
|
@@ -22,6 +22,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
|
|
|
22
22
|
const size = (_c = groupContext === null || groupContext === void 0 ? void 0 : groupContext.size) !== null && _c !== void 0 ? _c : sizeProp;
|
|
23
23
|
const state = stateProp === "disabled" ? "disabled" : ((_d = groupContext === null || groupContext === void 0 ? void 0 : groupContext.state) !== null && _d !== void 0 ? _d : stateProp);
|
|
24
24
|
const isDisabled = state === "disabled";
|
|
25
|
+
const isInvalid = state === "invalid";
|
|
25
26
|
// Determine checked state
|
|
26
27
|
const isInGroup = groupContext !== undefined;
|
|
27
28
|
const [internalChecked, setInternalChecked] = useState(false);
|
|
@@ -42,6 +43,15 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
|
|
|
42
43
|
inputRef.current.indeterminate = indeterminate;
|
|
43
44
|
}
|
|
44
45
|
}, [indeterminate]);
|
|
46
|
+
// Dev-only warning when no accessible name is provided
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
49
|
+
if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) {
|
|
50
|
+
console.warn('[Checkbox]: No accessible name provided. Add a `label` prop, `aria-label`, or `aria-labelledby`.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
54
|
+
}, []);
|
|
45
55
|
const handleChange = (e) => {
|
|
46
56
|
if (isDisabled)
|
|
47
57
|
return;
|
|
@@ -59,7 +69,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
|
|
|
59
69
|
ref(node);
|
|
60
70
|
else if (ref)
|
|
61
71
|
ref.current = node;
|
|
62
|
-
}, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
|
|
72
|
+
}, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "aria-invalid": isInvalid || undefined, "data-state": isDisabled ? "disabled" : isInvalid ? "invalid" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
|
|
63
73
|
});
|
|
64
74
|
CheckboxComponent.displayName = "Checkbox";
|
|
65
75
|
const Checkbox = Object.assign(CheckboxComponent, {
|