@flikk/ui 1.0.0-beta.23 → 1.0.0-beta.25
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/Accordion.theme.js +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/Breadcrumbs/Breadcrumbs.theme.js +2 -2
- package/dist/components/core/Calendar/Calendar.js +1 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +4 -2
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.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/Empty/Empty.theme.js +1 -1
- package/dist/components/core/Link/Link.js +1 -1
- 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/data-display/Timeline/Timeline.animations.d.ts +0 -7
- package/dist/components/data-display/Timeline/Timeline.animations.js +1 -27
- package/dist/components/data-display/Timeline/Timeline.js +3 -4
- package/dist/components/data-display/Timeline/Timeline.types.d.ts +0 -13
- package/dist/components/data-display/Timeline/TimelineContent.js +3 -7
- package/dist/components/data-display/Timeline/TimelineItem.js +4 -17
- package/dist/components/data-display/Timeline/index.d.ts +2 -2
- 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/effects/index.d.ts +7 -0
- package/dist/components/effects/index.js +3 -0
- 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 +27 -57
- 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;
|
|
@@ -4,13 +4,6 @@ import { Variants } from "motion/react";
|
|
|
4
4
|
* Provides a fade + slide effect when items enter the viewport
|
|
5
5
|
*/
|
|
6
6
|
export declare const timelineItemVariants: Variants;
|
|
7
|
-
/**
|
|
8
|
-
* Get directional slide variants based on alignment side
|
|
9
|
-
* Items slide in from the opposite direction of their placement
|
|
10
|
-
*
|
|
11
|
-
* @param side - 'left' slides from left, 'right' slides from right
|
|
12
|
-
*/
|
|
13
|
-
export declare const getDirectionalVariants: (side: "left" | "right") => Variants;
|
|
14
7
|
/**
|
|
15
8
|
* Horizontal layout item entry animation
|
|
16
9
|
* Items fade in from below
|
|
@@ -20,32 +20,6 @@ const timelineItemVariants = {
|
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
22
|
};
|
|
23
|
-
/**
|
|
24
|
-
* Get directional slide variants based on alignment side
|
|
25
|
-
* Items slide in from the opposite direction of their placement
|
|
26
|
-
*
|
|
27
|
-
* @param side - 'left' slides from left, 'right' slides from right
|
|
28
|
-
*/
|
|
29
|
-
const getDirectionalVariants = (side) => ({
|
|
30
|
-
hidden: {
|
|
31
|
-
opacity: 0,
|
|
32
|
-
x: side === "left" ? -30 : 30,
|
|
33
|
-
y: 10,
|
|
34
|
-
transition: {
|
|
35
|
-
duration: 0.3,
|
|
36
|
-
ease: "easeOut",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
visible: {
|
|
40
|
-
opacity: 1,
|
|
41
|
-
x: 0,
|
|
42
|
-
y: 0,
|
|
43
|
-
transition: {
|
|
44
|
-
duration: 0.5,
|
|
45
|
-
ease: [0.04, 0.62, 0.23, 0.98],
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
23
|
/**
|
|
50
24
|
* Horizontal layout item entry animation
|
|
51
25
|
* Items fade in from below
|
|
@@ -89,4 +63,4 @@ const timelineMarkerVariants = {
|
|
|
89
63
|
},
|
|
90
64
|
};
|
|
91
65
|
|
|
92
|
-
export {
|
|
66
|
+
export { timelineHorizontalItemVariants, timelineItemVariants, timelineMarkerVariants };
|
|
@@ -14,7 +14,7 @@ const useTimelineContext = () => {
|
|
|
14
14
|
throw new Error("Timeline compound components must be used within <Timeline>");
|
|
15
15
|
return ctx;
|
|
16
16
|
};
|
|
17
|
-
const TimelineRoot = React__default.forwardRef(({ children, className, layout = "vertical",
|
|
17
|
+
const TimelineRoot = React__default.forwardRef(({ children, className, layout = "vertical", theme: themeOverrides, ...props }, ref) => {
|
|
18
18
|
// Merge theme with any overrides
|
|
19
19
|
const theme = useMemo(() => ({
|
|
20
20
|
...timelineTheme,
|
|
@@ -29,12 +29,11 @@ const TimelineRoot = React__default.forwardRef(({ children, className, layout =
|
|
|
29
29
|
// Context value to be provided to children
|
|
30
30
|
const contextValue = useMemo(() => ({
|
|
31
31
|
layout,
|
|
32
|
-
align,
|
|
33
32
|
theme,
|
|
34
33
|
totalItems,
|
|
35
|
-
}), [layout,
|
|
34
|
+
}), [layout, theme, totalItems]);
|
|
36
35
|
const isHorizontal = layout === "horizontal";
|
|
37
|
-
return (jsx(TimelineContext.Provider, { value: contextValue, children: jsx("div", { ref: ref, className: cn(isHorizontal ? theme.baseStyleHorizontal : theme.baseStyle, className), "data-layout": layout,
|
|
36
|
+
return (jsx(TimelineContext.Provider, { value: contextValue, children: jsx("div", { ref: ref, className: cn(isHorizontal ? theme.baseStyleHorizontal : theme.baseStyle, className), "data-layout": layout, role: "list", "aria-label": "Timeline", ...props, children: children }) }));
|
|
38
37
|
});
|
|
39
38
|
TimelineRoot.displayName = "Timeline";
|
|
40
39
|
// Attach compound subcomponents
|
|
@@ -3,13 +3,6 @@ import React from "react";
|
|
|
3
3
|
* Layout orientation for the Timeline
|
|
4
4
|
*/
|
|
5
5
|
export type TimelineLayout = "vertical" | "horizontal";
|
|
6
|
-
/**
|
|
7
|
-
* Alignment mode for vertical timeline items
|
|
8
|
-
* - left: all items on the left side of the line
|
|
9
|
-
* - right: all items on the right side of the line
|
|
10
|
-
* - alternate: items alternate between left and right
|
|
11
|
-
*/
|
|
12
|
-
export type TimelineAlign = "left" | "right" | "alternate";
|
|
13
6
|
/**
|
|
14
7
|
* Status of an individual timeline item
|
|
15
8
|
* - completed: item is finished (success color)
|
|
@@ -41,11 +34,6 @@ export interface TimelineProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
41
34
|
* @default 'vertical'
|
|
42
35
|
*/
|
|
43
36
|
layout?: TimelineLayout;
|
|
44
|
-
/**
|
|
45
|
-
* Alignment of items relative to the timeline line (vertical only)
|
|
46
|
-
* @default 'left'
|
|
47
|
-
*/
|
|
48
|
-
align?: TimelineAlign;
|
|
49
37
|
/**
|
|
50
38
|
* Theme overrides for customizing styles
|
|
51
39
|
*/
|
|
@@ -60,7 +48,6 @@ export interface TimelineProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
60
48
|
*/
|
|
61
49
|
export interface TimelineContextValue {
|
|
62
50
|
layout: TimelineLayout;
|
|
63
|
-
align: TimelineAlign;
|
|
64
51
|
theme: TimelineThemeOverrides & {
|
|
65
52
|
baseStyle: string;
|
|
66
53
|
baseStyleHorizontal: string;
|
|
@@ -5,16 +5,12 @@ import { useTimelineContext } from './Timeline.js';
|
|
|
5
5
|
import { useTimelineItemContext } from './TimelineItem.js';
|
|
6
6
|
|
|
7
7
|
const TimelineContent = React__default.forwardRef(({ children, className, ...props }, ref) => {
|
|
8
|
-
const { theme, layout
|
|
9
|
-
const { isLast
|
|
8
|
+
const { theme, layout } = useTimelineContext();
|
|
9
|
+
const { isLast } = useTimelineItemContext();
|
|
10
10
|
const isHorizontal = layout === "horizontal";
|
|
11
|
-
// For alternate layout, determine text alignment
|
|
12
|
-
const isAlternateRight = align === "alternate" && index % 2 !== 0;
|
|
13
11
|
return (jsx("div", { ref: ref, className: cn(isHorizontal ? theme.contentStyleHorizontal : theme.contentStyle,
|
|
14
12
|
// Remove bottom padding on last item
|
|
15
|
-
!isHorizontal && isLast && "pb-0",
|
|
16
|
-
// Align text based on side in alternate mode
|
|
17
|
-
!isHorizontal && isAlternateRight && "text-right", !isHorizontal && align === "right" && "text-right", className), ...props, children: children }));
|
|
13
|
+
!isHorizontal && isLast && "pb-0", className), ...props, children: children }));
|
|
18
14
|
});
|
|
19
15
|
TimelineContent.displayName = "TimelineContent";
|
|
20
16
|
|
|
@@ -3,7 +3,7 @@ import React__default, { createContext, useRef, useState, useEffect, useContext
|
|
|
3
3
|
import { useReducedMotion, motion } from 'motion/react';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
5
|
import { useTimelineContext } from './Timeline.js';
|
|
6
|
-
import { timelineHorizontalItemVariants,
|
|
6
|
+
import { timelineHorizontalItemVariants, timelineItemVariants } from './Timeline.animations.js';
|
|
7
7
|
|
|
8
8
|
// Create context for TimelineItem
|
|
9
9
|
const TimelineItemContext = createContext(undefined);
|
|
@@ -15,7 +15,7 @@ const useTimelineItemContext = () => {
|
|
|
15
15
|
return ctx;
|
|
16
16
|
};
|
|
17
17
|
const TimelineItem = React__default.forwardRef(({ children, className, status = "pending", ...props }, ref) => {
|
|
18
|
-
const { layout,
|
|
18
|
+
const { layout, theme, totalItems } = useTimelineContext();
|
|
19
19
|
const shouldReduceMotion = useReducedMotion();
|
|
20
20
|
const itemRef = useRef(null);
|
|
21
21
|
const [isVisible, setIsVisible] = useState(false);
|
|
@@ -48,21 +48,12 @@ const TimelineItem = React__default.forwardRef(({ children, className, status =
|
|
|
48
48
|
const isFirst = index === 0;
|
|
49
49
|
const isLast = index === totalItems - 1;
|
|
50
50
|
const isHorizontal = layout === "horizontal";
|
|
51
|
-
//
|
|
52
|
-
const itemSide = align === "alternate"
|
|
53
|
-
? index % 2 === 0
|
|
54
|
-
? "left"
|
|
55
|
-
: "right"
|
|
56
|
-
: align;
|
|
57
|
-
// Select animation variants based on layout and alignment
|
|
51
|
+
// Select animation variants based on layout
|
|
58
52
|
const getVariants = () => {
|
|
59
53
|
if (shouldReduceMotion)
|
|
60
54
|
return undefined;
|
|
61
55
|
if (isHorizontal)
|
|
62
56
|
return timelineHorizontalItemVariants;
|
|
63
|
-
if (align === "alternate") {
|
|
64
|
-
return getDirectionalVariants(itemSide);
|
|
65
|
-
}
|
|
66
57
|
return timelineItemVariants;
|
|
67
58
|
};
|
|
68
59
|
// Context value for Marker and Content
|
|
@@ -72,14 +63,10 @@ const TimelineItem = React__default.forwardRef(({ children, className, status =
|
|
|
72
63
|
isFirst,
|
|
73
64
|
isLast,
|
|
74
65
|
};
|
|
75
|
-
// Build className based on layout
|
|
76
|
-
const isAlternateRight = align === "alternate" && index % 2 !== 0;
|
|
77
66
|
// Filter out conflicting motion props from rest
|
|
78
67
|
const { onDrag, onDragEnd, onDragStart, onAnimationStart, onAnimationEnd, ...safeProps } = props;
|
|
79
68
|
const variants = getVariants();
|
|
80
|
-
return (jsx(TimelineItemContext.Provider, { value: contextValue, children: jsx(motion.div, { ref: ref || itemRef, className: cn(isHorizontal ? theme.itemStyleHorizontal : theme.itemStyle,
|
|
81
|
-
// For right-aligned or alternate-right, reverse the flex direction
|
|
82
|
-
!isHorizontal && align === "right" && "flex-row-reverse", !isHorizontal && isAlternateRight && "flex-row-reverse", className), "data-status": status, "data-side": itemSide, "data-layout": layout, role: "listitem", initial: variants ? "hidden" : undefined, animate: variants && isVisible ? "visible" : variants ? "hidden" : undefined, variants: variants, ...safeProps, children: children }) }));
|
|
69
|
+
return (jsx(TimelineItemContext.Provider, { value: contextValue, children: jsx(motion.div, { ref: ref || itemRef, className: cn(isHorizontal ? theme.itemStyleHorizontal : theme.itemStyle, className), "data-status": status, "data-layout": layout, role: "listitem", initial: variants ? "hidden" : undefined, animate: variants && isVisible ? "visible" : variants ? "hidden" : undefined, variants: variants, ...safeProps, children: children }) }));
|
|
83
70
|
});
|
|
84
71
|
TimelineItem.displayName = "TimelineItem";
|
|
85
72
|
|