@flikk/ui 1.0.0-beta.22 → 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.
Files changed (161) hide show
  1. package/dist/components/ai/PromptInput/PromptInput.js +139 -7
  2. package/dist/components/ai/PromptInput/VoiceRecorder.js +84 -0
  3. package/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
  4. package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
  5. package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
  6. package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
  7. package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
  8. package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
  9. package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
  10. package/dist/components/core/Alert/Alert.js +2 -1
  11. package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
  12. package/dist/components/core/Badge/Badge.js +6 -17
  13. package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
  14. package/dist/components/core/Calendar/Calendar.js +1 -1
  15. package/dist/components/core/Carousel/CarouselBody.js +6 -1
  16. package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
  17. package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
  18. package/dist/components/core/DragDrop/DragDrop.js +170 -17
  19. package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
  20. package/dist/components/core/Drawer/Drawer.d.ts +1 -1
  21. package/dist/components/core/Drawer/Drawer.js +2 -0
  22. package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
  23. package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
  24. package/dist/components/core/Drawer/DrawerTitle.js +2 -2
  25. package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
  26. package/dist/components/core/Masonry/Masonry.js +7 -2
  27. package/dist/components/core/Modal/Modal.d.ts +1 -1
  28. package/dist/components/core/Modal/Modal.js +3 -3
  29. package/dist/components/core/Modal/Modal.types.d.ts +2 -0
  30. package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
  31. package/dist/components/core/Modal/ModalTitle.js +2 -2
  32. package/dist/components/core/NavItem/NavItem.js +21 -6
  33. package/dist/components/core/NavItem/NavItem.theme.js +2 -2
  34. package/dist/components/core/Pagination/Pagination.js +1 -1
  35. package/dist/components/core/Popover/PopoverBody.js +3 -27
  36. package/dist/components/core/Popover/PopoverTrigger.js +1 -1
  37. package/dist/components/core/Progress/Progress.js +4 -4
  38. package/dist/components/core/Rating/Rating.js +1 -1
  39. package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
  40. package/dist/components/core/Segmented/Segmented.js +6 -6
  41. package/dist/components/core/Segmented/SegmentedItem.js +28 -2
  42. package/dist/components/core/Sidebar/Sidebar.d.ts +1 -0
  43. package/dist/components/core/Sidebar/Sidebar.js +5 -3
  44. package/dist/components/core/Sidebar/Sidebar.types.d.ts +7 -1
  45. package/dist/components/core/Sidebar/SidebarContent.js +1 -1
  46. package/dist/components/core/Sidebar/SidebarFooter.js +1 -1
  47. package/dist/components/core/Sidebar/SidebarHeader.js +5 -2
  48. package/dist/components/core/Sidebar/SidebarLogo.d.ts +3 -0
  49. package/dist/components/core/Sidebar/SidebarLogo.js +17 -0
  50. package/dist/components/core/Sidebar/SidebarNavGroup.js +2 -1
  51. package/dist/components/core/Sidebar/SidebarToggle.js +2 -4
  52. package/dist/components/core/Sidebar/SidebarUserProfile.js +3 -6
  53. package/dist/components/core/Sidebar/index.d.ts +2 -1
  54. package/dist/components/core/Skeleton/Skeleton.js +2 -2
  55. package/dist/components/core/Sortable/Sortable.d.ts +1 -1
  56. package/dist/components/core/Sortable/Sortable.js +20 -4
  57. package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
  58. package/dist/components/core/Tabs/Tabs.js +6 -4
  59. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  60. package/dist/components/core/Tabs/TabsBody.js +3 -3
  61. package/dist/components/core/Tabs/TabsList.js +19 -21
  62. package/dist/components/core/Tabs/TabsTrigger.js +47 -28
  63. package/dist/components/core/Toast/Toast.js +1 -1
  64. package/dist/components/core/Toast/ToastProvider.js +3 -2
  65. package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
  66. package/dist/components/core/Tooltip/Tooltip.js +26 -35
  67. package/dist/components/core/Tooltip/Tooltip.types.d.ts +5 -1
  68. package/dist/components/core/Tree/Tree.js +178 -8
  69. package/dist/components/core/index.js +1 -0
  70. package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
  71. package/dist/components/data-display/Table/Table.js +2 -2
  72. package/dist/components/data-display/Table/Table.types.d.ts +2 -0
  73. package/dist/components/data-display/Table/TableFilter.js +21 -18
  74. package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
  75. package/dist/components/effects/Overlay/Overlay.js +4 -9
  76. package/dist/components/effects/PageTransition/PageTransition.js +17 -6
  77. package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
  78. package/dist/components/forms/Checkbox/Checkbox.js +11 -1
  79. package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
  80. package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
  81. package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
  82. package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
  83. package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
  84. package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
  85. package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
  86. package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
  87. package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
  88. package/dist/components/forms/ColorPicker/index.d.ts +1 -1
  89. package/dist/components/forms/Combobox/Combobox.js +20 -45
  90. package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
  91. package/dist/components/forms/CronInput/CronInput.js +10 -4
  92. package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
  93. package/dist/components/forms/DatePicker/DatePicker.js +2 -2
  94. package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
  95. package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
  96. package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
  97. package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
  98. package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
  99. package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
  100. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
  101. package/dist/components/forms/FileUpload/FileUpload.js +99 -10
  102. package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
  103. package/dist/components/forms/FormLabel/FormLabel.js +2 -2
  104. package/dist/components/forms/Input/Input.js +5 -3
  105. package/dist/components/forms/Input/Input.types.d.ts +2 -0
  106. package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
  107. package/dist/components/forms/InputAddress/InputAddress.js +8 -9
  108. package/dist/components/forms/InputCounter/InputCounter.js +25 -18
  109. package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
  110. package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
  111. package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
  112. package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
  113. package/dist/components/forms/InputOTP/InputOTP.js +26 -17
  114. package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
  115. package/dist/components/forms/InputTag/InputTag.js +12 -39
  116. package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
  117. package/dist/components/forms/Mention/Mention.js +13 -6
  118. package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
  119. package/dist/components/forms/Radio/Radio.js +12 -2
  120. package/dist/components/forms/Radio/Radio.theme.js +5 -1
  121. package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
  122. package/dist/components/forms/Radio/RadioGroup.js +8 -3
  123. package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
  124. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
  125. package/dist/components/forms/Select/Select.d.ts +2 -0
  126. package/dist/components/forms/Select/Select.js +26 -56
  127. package/dist/components/forms/Select/Select.types.d.ts +14 -0
  128. package/dist/components/forms/Signature/Signature.js +1 -1
  129. package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
  130. package/dist/components/forms/Slider/Slider.js +18 -4
  131. package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
  132. package/dist/components/forms/Switch/Switch.js +12 -19
  133. package/dist/components/forms/Switch/Switch.theme.js +12 -2
  134. package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
  135. package/dist/components/forms/Textarea/Textarea.js +8 -3
  136. package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
  137. package/dist/components/forms/TimePicker/TimePicker.js +1 -1
  138. package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
  139. package/dist/components/forms/TimePicker/TimePickerContent.js +88 -30
  140. package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
  141. package/dist/components/forms/index.js +86 -1
  142. package/dist/components/layout/FormLayout/FormLayout.js +87 -1
  143. package/dist/components/layout/index.js +4 -0
  144. package/dist/hooks/index.d.ts +1 -0
  145. package/dist/hooks/useClickOutside.d.ts +10 -4
  146. package/dist/hooks/useClickOutside.js +44 -24
  147. package/dist/hooks/useDarkMode.d.ts +1 -0
  148. package/dist/hooks/useDarkMode.js +42 -0
  149. package/dist/hooks/useFormStateMachine.d.ts +1 -1
  150. package/dist/hooks/useNetworkStatus.js +1 -1
  151. package/dist/hooks/useOverlay.js +4 -0
  152. package/dist/hooks/useScaleBackground.js +5 -2
  153. package/dist/hooks/useSelectPortal.js +14 -5
  154. package/dist/index.d.ts +0 -2
  155. package/dist/index.js +174 -223
  156. package/dist/shadcn-compat.css +80 -0
  157. package/dist/styles.css +1 -1
  158. package/dist/utils/stateMachine.d.ts +4 -4
  159. package/package.json +12 -10
  160. package/tailwind.preset.cjs +32 -3
  161. /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, { useState, useRef, useMemo, useEffect } from 'react';
2
+ import React__default, { useState, useRef, useCallback, useEffect, useMemo } from 'react';
3
3
  import { useReducedMotion, motion } from 'motion/react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { tabsTheme } from './Tabs.theme.js';
@@ -18,26 +18,24 @@ const TabsList = React__default.forwardRef(({ children, className, ...props }, r
18
18
  // Ref to access the list element for calculations
19
19
  const listRef = useRef(null);
20
20
  // Calculate underline position based on active tab
21
- const updateUnderlinePosition = useMemo(() => {
22
- return () => {
23
- if (!activeValue || !listRef.current) {
24
- setShowUnderline(false);
25
- return;
26
- }
27
- // Find the active trigger element
28
- const activeTrigger = listRef.current.querySelector(`[data-value="${activeValue}"]`);
29
- if (!activeTrigger) {
30
- setShowUnderline(false);
31
- return;
32
- }
33
- // Get the position relative to the list container
34
- const listRect = listRef.current.getBoundingClientRect();
35
- const triggerRect = activeTrigger.getBoundingClientRect();
36
- const left = triggerRect.left - listRect.left;
37
- const width = triggerRect.width;
38
- setUnderlinePosition({ left, width });
39
- setShowUnderline(true);
40
- };
21
+ const updateUnderlinePosition = useCallback(() => {
22
+ if (!activeValue || !listRef.current) {
23
+ setShowUnderline(false);
24
+ return;
25
+ }
26
+ // Find the active trigger element
27
+ const activeTrigger = listRef.current.querySelector(`[data-value="${activeValue}"]`);
28
+ if (!activeTrigger) {
29
+ setShowUnderline(false);
30
+ return;
31
+ }
32
+ // Get the position relative to the list container
33
+ const listRect = listRef.current.getBoundingClientRect();
34
+ const triggerRect = activeTrigger.getBoundingClientRect();
35
+ const left = triggerRect.left - listRect.left;
36
+ const width = triggerRect.width;
37
+ setUnderlinePosition({ left, width });
38
+ setShowUnderline(true);
41
39
  }, [activeValue]);
42
40
  // Update underline position when active value changes or on mount
43
41
  useEffect(() => {
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import React__default, { useMemo, useCallback, useEffect } from 'react';
2
+ import React__default, { useRef, useEffect, useMemo, useCallback } from 'react';
3
3
  import { useReducedMotion, motion } from 'motion/react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { tabsTheme } from './Tabs.theme.js';
@@ -10,11 +10,14 @@ import { getTabsTriggerVariants } from './Tabs.animations.js';
10
10
  * TabsTrigger component displays individual tab headers with optional animations
11
11
  */
12
12
  const TabsTrigger = React__default.forwardRef(({ value, disabled = false, children, className, onClick, onKeyDown, ...props }, ref) => {
13
- const { value: selectedValue, onValueChange, orientation, size, disabled: contextDisabled, registerTrigger, unregisterTrigger } = useTabsContext();
13
+ const { value: selectedValue, onValueChange, orientation, size, disabled: contextDisabled, registerTrigger, unregisterTrigger, instanceId } = useTabsContext();
14
14
  const shouldReduceMotion = useReducedMotion();
15
- // Check if we're in Storybook and should disable animations
16
- const isInStorybook = typeof window !== 'undefined' && window.__STORYBOOK_DISABLE_ANIMATIONS__;
17
- const shouldDisableAnimations = !!shouldReduceMotion || !!isInStorybook;
15
+ // Check if we're in Storybook evaluated after mount to avoid SSR hydration mismatch
16
+ const isInStorybookRef = useRef(false);
17
+ useEffect(() => {
18
+ isInStorybookRef.current = typeof window !== 'undefined' && !!window.__STORYBOOK_DISABLE_ANIMATIONS__;
19
+ }, []);
20
+ const shouldDisableAnimations = !!shouldReduceMotion || !!isInStorybookRef.current;
18
21
  // Memoize derived state flags for performance
19
22
  const derivedState = useMemo(() => ({
20
23
  isSelected: value === selectedValue,
@@ -51,7 +54,6 @@ const TabsTrigger = React__default.forwardRef(({ value, disabled = false, childr
51
54
  }, [isDisabled, onValueChange, value, onClick]);
52
55
  // Enhanced keyboard navigation handler with error handling
53
56
  const handleKeyDown = useCallback((event) => {
54
- var _a, _b;
55
57
  // Handle keyboard navigation for tabs
56
58
  if (!isDisabled) {
57
59
  try {
@@ -78,20 +80,32 @@ const TabsTrigger = React__default.forwardRef(({ value, disabled = false, childr
78
80
  break;
79
81
  case "Home":
80
82
  event.preventDefault();
81
- // Move to first tab
82
- const firstTrigger = (_a = event.currentTarget.parentElement) === null || _a === void 0 ? void 0 : _a.firstElementChild;
83
- if (firstTrigger && firstTrigger.tagName === 'BUTTON' && !firstTrigger.disabled) {
84
- firstTrigger.focus();
85
- firstTrigger.click();
83
+ // Move to first tab — use role="tab" to skip the sliding underline element
84
+ {
85
+ const tabList = event.currentTarget.parentElement;
86
+ const allTabs = tabList
87
+ ? Array.from(tabList.querySelectorAll('[role="tab"]:not([disabled])'))
88
+ : [];
89
+ const firstTrigger = allTabs[0];
90
+ if (firstTrigger) {
91
+ firstTrigger.focus();
92
+ firstTrigger.click();
93
+ }
86
94
  }
87
95
  break;
88
96
  case "End":
89
97
  event.preventDefault();
90
- // Move to last tab
91
- const lastTrigger = (_b = event.currentTarget.parentElement) === null || _b === void 0 ? void 0 : _b.lastElementChild;
92
- if (lastTrigger && lastTrigger.tagName === 'BUTTON' && !lastTrigger.disabled) {
93
- lastTrigger.focus();
94
- lastTrigger.click();
98
+ // Move to last tab — use role="tab" to skip the sliding underline element
99
+ {
100
+ const tabList = event.currentTarget.parentElement;
101
+ const allTabs = tabList
102
+ ? Array.from(tabList.querySelectorAll('[role="tab"]:not([disabled])'))
103
+ : [];
104
+ const lastTrigger = allTabs[allTabs.length - 1];
105
+ if (lastTrigger) {
106
+ lastTrigger.focus();
107
+ lastTrigger.click();
108
+ }
95
109
  }
96
110
  break;
97
111
  }
@@ -114,18 +128,23 @@ const TabsTrigger = React__default.forwardRef(({ value, disabled = false, childr
114
128
  }, [size]);
115
129
  // Generate default aria-label for icon-only triggers if not provided (fallback only)
116
130
  const ariaLabel = props["aria-label"] || undefined;
117
- // Register/unregister this trigger for underline positioning
118
- useEffect(() => {
119
- // Register with a slight delay to ensure DOM is ready
120
- const timeoutId = setTimeout(() => {
121
- if (ref && typeof ref === 'object' && ref.current) {
122
- registerTrigger(value, ref.current);
123
- }
124
- }, 0);
125
- return () => {
126
- clearTimeout(timeoutId);
131
+ // Callback ref that registers/unregisters the trigger for underline positioning.
132
+ // Using a callback ref ensures registration fires whenever the DOM node mounts,
133
+ // not just when the ref object identity changes.
134
+ const triggerRef = useCallback((node) => {
135
+ if (node) {
136
+ registerTrigger(value, node);
137
+ }
138
+ else {
127
139
  unregisterTrigger(value);
128
- };
140
+ }
141
+ // Forward to the outer ref passed by the consumer, if any
142
+ if (typeof ref === 'function') {
143
+ ref(node);
144
+ }
145
+ else if (ref) {
146
+ ref.current = node;
147
+ }
129
148
  }, [value, registerTrigger, unregisterTrigger, ref]);
130
149
  // Determine animation props (user props will override due to spread order)
131
150
  const animationProps = shouldDisableAnimations ? {} : {
@@ -133,7 +152,7 @@ const TabsTrigger = React__default.forwardRef(({ value, disabled = false, childr
133
152
  whileTap: !isDisabled ? "tap" : undefined,
134
153
  variants: triggerVariants,
135
154
  };
136
- return (jsx(motion.button, { ref: ref, type: "button", role: "tab", "aria-selected": isSelected, "aria-controls": `${value}-content`, id: `${value}-tab`, tabIndex: isSelected ? 0 : -1, disabled: isDisabled, className: cn(themeClasses.triggerStyle, themeClasses.sizeStyle, className // User overrides take precedence
155
+ return (jsx(motion.button, { ref: triggerRef, type: "button", role: "tab", "aria-selected": isSelected, "aria-controls": `${instanceId}-${value}-content`, id: `${instanceId}-${value}-tab`, tabIndex: isSelected ? 0 : -1, disabled: isDisabled, className: cn(themeClasses.triggerStyle, themeClasses.sizeStyle, className // User overrides take precedence
137
156
  ), onClick: handleClick, onKeyDown: handleKeyDown, "data-value": value, "data-state": isSelected ? "active" : "inactive", "data-disabled": isDisabled, "data-orientation": orientation, "aria-disabled": isDisabled || undefined, "aria-label": ariaLabel, ...animationProps, ...props, children: children }));
138
157
  });
139
158
  TabsTrigger.displayName = "TabsTrigger";
@@ -84,7 +84,7 @@ const Toast = React__default.forwardRef(({ toast, className, onDismiss, index =
84
84
  }, [stackingStyle.zIndex, index]);
85
85
  const toastNode = (jsxs(motion.div, { ref: setRefs, layout: true, initial: shouldReduceMotion ? false : toastAnimations.initial, animate: animateValue, exit: shouldReduceMotion
86
86
  ? { ...toastAnimations.exit, transition: { duration: 0 } }
87
- : toastAnimations.exit, transition: shouldReduceMotion ? { duration: 0 } : toastAnimations.transition, className: cn(toastTheme.baseStyle, isTextOnly && "items-center", ((_a = toastTheme.variants) === null || _a === void 0 ? void 0 : _a[variant]) || "", "pointer-events-auto", index > 0 && !isExpanded && "absolute top-0 left-0", className), style: styleValue, role: "alert", "aria-live": "polite", children: [jsx("div", { className: cn(toastTheme.iconStyle, !isTextOnly && "mt-0.5"), children: resolvedIcon }), jsxs("div", { className: "flex-1 min-w-0", children: [title &&
87
+ : toastAnimations.exit, transition: shouldReduceMotion ? { duration: 0 } : toastAnimations.transition, className: cn(toastTheme.baseStyle, isTextOnly && "items-center", ((_a = toastTheme.variants) === null || _a === void 0 ? void 0 : _a[variant]) || "", "pointer-events-auto", index > 0 && !isExpanded && "absolute top-0 left-0", className), style: styleValue, role: "status", "aria-live": "polite", children: [jsx("div", { className: cn(toastTheme.iconStyle, !isTextOnly && "mt-0.5"), children: resolvedIcon }), jsxs("div", { className: "flex-1 min-w-0", children: [title &&
88
88
  (typeof title === "string" ? (jsx("div", { className: cn(toastTheme.titleStyle, !isTextOnly && "font-semibold"), children: title })) : (title)), subtitle &&
89
89
  (typeof subtitle === "string" ? (jsx("div", { className: toastTheme.subtitleStyle, children: subtitle })) : (subtitle)), action && (jsx("div", { className: "mt-4", children: isToastAction(action) ? (jsx(Button, { variant: "link", color: "primary", onClick: handleActionClick, children: action.label })) : (action) })), children] }), dismissible && (jsx("button", { type: "button", className: cn(toastTheme.closeButtonStyle, !isTextOnly && "mt-0.5"), onClick: handleDismiss, "aria-label": "Dismiss notification", children: jsx(XMarkIcon, { className: "size-5" }) }))] }));
90
90
  if (darkMode) {
@@ -24,7 +24,7 @@ const ToastProvider = ({ children, position = 'top-right', maxToasts = 5, defaul
24
24
  const hoverTimeoutRef = React__default.useRef(null);
25
25
  const addToast = useCallback((toastData) => {
26
26
  var _a;
27
- const id = Math.random().toString(36).substring(2, 9);
27
+ const id = crypto.randomUUID();
28
28
  const newToast = {
29
29
  ...toastData,
30
30
  id,
@@ -91,7 +91,8 @@ const ToastProvider = ({ children, position = 'top-right', maxToasts = 5, defaul
91
91
  removeToast,
92
92
  removeAllToasts,
93
93
  }), [toasts, addToast, removeToast, removeAllToasts]);
94
- return (jsxs(ToastContext.Provider, { value: contextValue, children: [children, isClient && createPortal(jsx("div", { className: cn(toastTheme.containerStyle, positionStyles[position]), "aria-live": "polite", "aria-label": "Notifications", children: jsx("div", { className: "relative w-80", style: { paddingBottom: isHovered && toasts.length > 1 ? totalExpandedHeight : undefined }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: jsx(AnimatePresence, { mode: "popLayout", children: toasts.map((toast, index) => (jsx(Toast, { toast: toast, onDismiss: removeToast, index: index, isExpanded: isHovered, totalToasts: toasts.length, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onHeightChange: handleToastHeight, expandedOffset: getExpandedOffset(index), darkMode: darkMode }, toast.id))) }) }) }), document.body)] }));
94
+ const portalTarget = isClient ? document.body : null;
95
+ return (jsxs(ToastContext.Provider, { value: contextValue, children: [children, portalTarget ? createPortal(jsx("div", { className: cn(toastTheme.containerStyle, positionStyles[position]), "aria-live": "polite", "aria-label": "Notifications", children: jsx("div", { className: "relative w-80", style: { paddingBottom: isHovered && toasts.length > 1 ? totalExpandedHeight : undefined }, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: jsx(AnimatePresence, { mode: "popLayout", children: toasts.map((toast, index) => (jsx(Toast, { toast: toast, onDismiss: removeToast, index: index, isExpanded: isHovered, totalToasts: toasts.length, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onHeightChange: handleToastHeight, expandedOffset: getExpandedOffset(index), darkMode: darkMode }, toast.id))) }) }) }), portalTarget) : null] }));
95
96
  };
96
97
  const useToastContext = () => {
97
98
  const context = useContext(ToastContext);
@@ -1,6 +1,6 @@
1
1
  import { TooltipProps } from "./Tooltip.types";
2
2
  declare const Tooltip: {
3
- ({ content, children, placement, delay, disabled, className, maxWidth, minWidth, showOnFocus, wrapText, id, theme: themeOverrides, "aria-label": ariaLabel, role, offset, darkMode, }: TooltipProps): import("react/jsx-runtime").JSX.Element;
3
+ ({ content, children, placement, delay, disabled, className, triggerClassName, maxWidth, minWidth, showOnFocus, wrapText, id, theme: themeOverrides, "aria-label": ariaLabel, role, offset, darkMode, }: TooltipProps): import("react/jsx-runtime").JSX.Element;
4
4
  displayName: string;
5
5
  };
6
6
  export { Tooltip };
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { useState, useRef, useCallback, useEffect } from 'react';
2
+ import React__default, { useState, useRef, useCallback, useEffect } from 'react';
3
3
  import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import { cn } from '../../../utils/cn.js';
@@ -7,37 +7,9 @@ import { tooltipTheme } from './Tooltip.theme.js';
7
7
  import { getTooltipVariants } from './Tooltip.animations.js';
8
8
  import { useSelectPortal } from '../../../hooks/useSelectPortal.js';
9
9
  import { useIsClient } from '../../../hooks/useIsClient.js';
10
+ import { useDarkMode } from '../../../hooks/useDarkMode.js';
10
11
 
11
- /**
12
- * Hook to detect dark mode state from document
13
- */
14
- const useDarkMode = () => {
15
- const [isDark, setIsDark] = useState(false);
16
- useEffect(() => {
17
- if (typeof document === "undefined")
18
- return;
19
- const checkDarkMode = () => {
20
- const hasDarkClass = document.documentElement.classList.contains("dark") ||
21
- document.body.classList.contains("dark") ||
22
- document.querySelector(".dark") !== null;
23
- setIsDark(hasDarkClass);
24
- };
25
- checkDarkMode();
26
- // Observe class changes on html and body
27
- const observer = new MutationObserver(checkDarkMode);
28
- observer.observe(document.documentElement, {
29
- attributes: true,
30
- attributeFilter: ["class"],
31
- });
32
- observer.observe(document.body, {
33
- attributes: true,
34
- attributeFilter: ["class"],
35
- });
36
- return () => observer.disconnect();
37
- }, []);
38
- return isDark;
39
- };
40
- const Tooltip = ({ content, children, placement = "top", delay = 300, disabled = false, className, maxWidth = "360px", minWidth = "fit-content", showOnFocus = true, wrapText = false, id, theme: themeOverrides, "aria-label": ariaLabel, role = "tooltip", offset = 8, darkMode = false, }) => {
12
+ const Tooltip = ({ content, children, placement = "top", delay = 300, disabled = false, className, triggerClassName, maxWidth = "360px", minWidth = "fit-content", showOnFocus = true, wrapText = false, id, theme: themeOverrides, "aria-label": ariaLabel, role = "tooltip", offset = 8, darkMode = false, }) => {
41
13
  const [isVisible, setIsVisible] = useState(false);
42
14
  const tooltipRef = useRef(null);
43
15
  const triggerRef = useRef(null);
@@ -91,11 +63,14 @@ const Tooltip = ({ content, children, placement = "top", delay = 300, disabled =
91
63
  handleHideTooltip();
92
64
  }
93
65
  }, [handleHideTooltip]);
94
- // Event handlers for trigger element
66
+ // Determine whether the child can receive focus events via cloneElement.
67
+ // If so, focus/blur are forwarded to the child; otherwise fall back to the wrapper.
68
+ const childCanReceiveFocus = showOnFocus && React__default.isValidElement(children);
69
+ // Event handlers for the wrapper div (focus/blur only go here as fallback)
95
70
  const triggerHandlers = {
96
71
  onMouseEnter: handleShowTooltip,
97
72
  onMouseLeave: handleHideTooltip,
98
- ...(showOnFocus && {
73
+ ...(showOnFocus && !childCanReceiveFocus && {
99
74
  onFocus: handleShowTooltip,
100
75
  onBlur: handleHideTooltip,
101
76
  }),
@@ -122,8 +97,24 @@ const Tooltip = ({ content, children, placement = "top", delay = 300, disabled =
122
97
  ...cssVariables,
123
98
  top: `${position.top}px`,
124
99
  left: `${position.left}px`,
125
- }, className: cn(theme.baseStyle, widthClasses, className), "aria-hidden": !isVisible, "aria-label": ariaLabel, "aria-live": role === "tooltip" ? "polite" : undefined, variants: shouldReduceMotion ? {} : getTooltipVariants(placement), initial: shouldReduceMotion ? undefined : "initial", animate: shouldReduceMotion ? undefined : "animate", exit: shouldReduceMotion ? undefined : "exit", children: content })) }));
126
- return (jsxs("div", { className: "relative inline-block", ref: triggerRef, ...triggerHandlers, tabIndex: showOnFocus ? 0 : undefined, "aria-describedby": isVisible && id ? id : undefined, children: [children, isClient &&
100
+ }, className: cn(theme.baseStyle, widthClasses, className), "aria-hidden": !isVisible, "aria-label": ariaLabel, variants: shouldReduceMotion ? {} : getTooltipVariants(placement), initial: shouldReduceMotion ? undefined : "initial", animate: shouldReduceMotion ? undefined : "animate", exit: shouldReduceMotion ? undefined : "exit", children: content })) }));
101
+ // When the child is a valid element, forward focus/blur events directly onto it
102
+ // instead of adding tabIndex to the wrapper (which would create a double Tab stop).
103
+ const childWithFocusHandlers = childCanReceiveFocus
104
+ ? React__default.cloneElement(children, {
105
+ onFocus: (e) => {
106
+ var _a, _b;
107
+ handleShowTooltip();
108
+ (_b = (_a = children.props).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, e);
109
+ },
110
+ onBlur: (e) => {
111
+ var _a, _b;
112
+ handleHideTooltip();
113
+ (_b = (_a = children.props).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, e);
114
+ },
115
+ })
116
+ : children;
117
+ return (jsxs("div", { className: cn("relative inline-block", triggerClassName), ref: triggerRef, ...triggerHandlers, "aria-describedby": isVisible && id ? id : undefined, children: [childWithFocusHandlers, isClient &&
127
118
  createPortal(jsx("div", { className: darkMode || isDarkMode ? "dark" : undefined, children: tooltipContent }), document.body)] }));
128
119
  };
129
120
  // Set display name for dev tools and Storybook
@@ -38,9 +38,13 @@ export interface TooltipProps {
38
38
  */
39
39
  disabled?: boolean;
40
40
  /**
41
- * Additional classes for the tooltip container
41
+ * Additional classes for the tooltip container (the tooltip bubble)
42
42
  */
43
43
  className?: string;
44
+ /**
45
+ * Additional classes for the trigger wrapper element
46
+ */
47
+ triggerClassName?: string;
44
48
  /**
45
49
  * Max width for the tooltip content
46
50
  * @default "360px"
@@ -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
- e.preventDefault();
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
- e.preventDefault();
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, tabIndex: node.disabled ? -1 : 0, role: "treeitem", "aria-expanded": isExpandable ? expanded : false, "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, role: "button", "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: {
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
@@ -100,6 +100,7 @@ export { SidebarSubmenu } from './Sidebar/SidebarSubmenu.js';
100
100
  export { SidebarToggle } from './Sidebar/SidebarToggle.js';
101
101
  export { SidebarMobileTrigger } from './Sidebar/SidebarMobileTrigger.js';
102
102
  export { SidebarUserProfile } from './Sidebar/SidebarUserProfile.js';
103
+ export { SidebarLogo } from './Sidebar/SidebarLogo.js';
103
104
  export { useSidebarContext } from './Sidebar/SidebarContext.js';
104
105
  export { OfflineIndicator } from './OfflineIndicator/OfflineIndicator.js';
105
106
  export { offlineIndicatorTheme } from './OfflineIndicator/OfflineIndicator.theme.js';