@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 { jsx } from 'react/jsx-runtime';
2
- import React__default, { useRef, useEffect, useCallback, useState } from 'react';
2
+ import React__default, { useRef, useEffect, useCallback } 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';
@@ -9,6 +9,7 @@ import { popoverTransitions, popoverContentVariants } from './Popover.animations
9
9
  import { useSelectPortal } from '../../../hooks/useSelectPortal.js';
10
10
  import { Overlay } from '../../effects/Overlay/Overlay.js';
11
11
  import { useIsClient } from '../../../hooks/useIsClient.js';
12
+ import { useDarkMode } from '../../../hooks/useDarkMode.js';
12
13
 
13
14
  /** Overlay backdrop animations (fade in/out) */
14
15
  const overlayBackdropAnimations = {
@@ -22,31 +23,6 @@ const overlayContentAnimations = {
22
23
  animate: { opacity: 1, scale: 1, transition: { duration: 0.2 } },
23
24
  exit: { opacity: 0, scale: 0.95, transition: { duration: 0.15 } },
24
25
  };
25
- const useDarkMode = () => {
26
- const [isDark, setIsDark] = useState(false);
27
- useEffect(() => {
28
- if (typeof document === "undefined")
29
- return;
30
- const checkDarkMode = () => {
31
- const hasDarkClass = document.documentElement.classList.contains("dark") ||
32
- document.body.classList.contains("dark") ||
33
- document.querySelector(".dark") !== null;
34
- setIsDark(hasDarkClass);
35
- };
36
- checkDarkMode();
37
- const observer = new MutationObserver(checkDarkMode);
38
- observer.observe(document.documentElement, {
39
- attributes: true,
40
- attributeFilter: ["class"],
41
- });
42
- observer.observe(document.body, {
43
- attributes: true,
44
- attributeFilter: ["class"],
45
- });
46
- return () => observer.disconnect();
47
- }, []);
48
- return isDark;
49
- };
50
26
  /**
51
27
  * PopoverBody component displays the body/content of the popover
52
28
  */
@@ -140,7 +116,7 @@ const PopoverBody = React__default.forwardRef(({ children, className, animation
140
116
  : undefined, children: jsx("div", { id: contentId, "data-state": isOpen ? "open" : "closed", "data-placement": placement, "data-trigger": triggerType, ...domProps, children: children }) }));
141
117
  }
142
118
  // Without backdrop: manual portal + positioning + escape + click-outside
143
- const popoverContent = (jsx(AnimatePresence, { children: isOpen && (jsx(motion.div, { ref: handleRef, id: contentId, className: cn("fixed z-50", contentClasses), style: contentPositionStyle, role: "dialog", "aria-modal": "true", variants: shouldReduceMotion || !animation ? {} : popoverContentVariants, initial: shouldReduceMotion || !animation ? undefined : "hidden", animate: shouldReduceMotion || !animation ? undefined : (isReady ? "visible" : "hidden"), exit: shouldReduceMotion || !animation ? undefined : "exit", transition: shouldReduceMotion || !animation
119
+ const popoverContent = (jsx(AnimatePresence, { children: isOpen && (jsx(motion.div, { ref: handleRef, id: contentId, className: cn("fixed z-50", contentClasses), style: contentPositionStyle, variants: shouldReduceMotion || !animation ? {} : popoverContentVariants, initial: shouldReduceMotion || !animation ? undefined : "hidden", animate: shouldReduceMotion || !animation ? undefined : (isReady ? "visible" : "hidden"), exit: shouldReduceMotion || !animation ? undefined : "exit", transition: shouldReduceMotion || !animation
144
120
  ? { duration: 0 }
145
121
  : popoverTransitions.content, "data-state": isOpen ? "open" : "closed", "data-placement": placement, "data-trigger": triggerType, onClick: (e) => e.stopPropagation(), ...domProps, children: children })) }));
146
122
  if (portal && isClient) {
@@ -60,7 +60,7 @@ const PopoverTrigger = React__default.forwardRef(({ children, className, ...prop
60
60
  return React__default.cloneElement(childElement, {
61
61
  ref: mergeRefs,
62
62
  "aria-expanded": context.isOpen,
63
- "aria-haspopup": true,
63
+ ...(context.hasBackdrop ? { "aria-haspopup": "dialog" } : {}),
64
64
  "aria-controls": context.contentId,
65
65
  className: cn("popover-trigger", (_a = childElement.props) === null || _a === void 0 ? void 0 : _a.className, className),
66
66
  "data-state": context.isOpen ? "open" : "closed",
@@ -35,7 +35,7 @@ const PROGRESS_SPRING_TRANSITION = {
35
35
  * // Indeterminate (loading)
36
36
  * <Progress />
37
37
  */
38
- const Progress = React__default.forwardRef(({ value, size = "md", showLabel = false, labelPosition = "right", formatLabel, animated = true, wrapperClassName, className, theme: customTheme, ...props }, ref) => {
38
+ const Progress = React__default.forwardRef(({ value, size = "md", showLabel = false, labelPosition = "right", formatLabel, animated = true, wrapperClassName, className, theme: customTheme, 'aria-label': consumerAriaLabel, ...props }, ref) => {
39
39
  const shouldReduceMotion = useReducedMotion();
40
40
  // Merge custom theme with default theme
41
41
  const theme = useMemo(() => {
@@ -98,11 +98,11 @@ const Progress = React__default.forwardRef(({ value, size = "md", showLabel = fa
98
98
  const renderTrack = () => (jsxs("div", { className: cn(theme.trackStyle, theme.sizes[size], "relative", wrapperClassName), children: [isIndeterminate ? (
99
99
  // Indeterminate animation
100
100
  jsx("div", { className: cn(theme.fillStyle, "absolute", theme.indeterminateStyle, className) })) : shouldAnimate ? (
101
- // Animated determinate progress with spring animation
102
- jsx(motion.div, { className: cn(theme.fillStyle, className), initial: { width: 0 }, animate: { width: `${percentage}%` }, transition: PROGRESS_SPRING_TRANSITION })) : (
101
+ // Animated determinate progress with spring animation (scaleX avoids layout recalculation)
102
+ jsx(motion.div, { className: cn(theme.fillStyle, className), style: { width: '100%', transformOrigin: 'left' }, initial: { scaleX: 0 }, animate: { scaleX: percentage / 100 }, transition: PROGRESS_SPRING_TRANSITION })) : (
103
103
  // Static determinate progress
104
104
  jsx("div", { className: cn(theme.fillStyle, className), style: { width: `${percentage}%` } })), showLabel && labelPosition === "inside" && size === "lg" && !isIndeterminate && (jsx("span", { className: theme.insideLabelStyle, children: label }))] }));
105
- return (jsxs("div", { ref: ref, className: cn(theme.containerStyle, containerLayoutClass), role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": isIndeterminate ? undefined : percentage, "aria-label": isIndeterminate ? "Loading" : `${percentage}% complete`, ...props, children: [labelPosition === "top" && renderLabel(), labelPosition === "right" ? (jsxs(Fragment, { children: [jsx("div", { className: "flex-1", children: renderTrack() }), renderLabel()] })) : (renderTrack()), labelPosition === "bottom" && renderLabel()] }));
105
+ return (jsxs("div", { ref: ref, className: cn(theme.containerStyle, containerLayoutClass), role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": isIndeterminate ? undefined : percentage, "aria-label": consumerAriaLabel !== null && consumerAriaLabel !== void 0 ? consumerAriaLabel : (isIndeterminate ? "Loading" : `${percentage}% complete`), ...props, children: [labelPosition === "top" && renderLabel(), labelPosition === "right" ? (jsxs(Fragment, { children: [jsx("div", { className: "flex-1", children: renderTrack() }), renderLabel()] })) : (renderTrack()), labelPosition === "bottom" && renderLabel()] }));
106
106
  });
107
107
  Progress.displayName = "Progress";
108
108
 
@@ -160,7 +160,7 @@ const Rating = React__default.memo(React__default.forwardRef(({ value: controlle
160
160
  return (jsx("span", { className: "inline-flex items-center justify-center", children: renderStarIcon() }, starIndex));
161
161
  }
162
162
  // Interactive: render as a button
163
- return (jsx("button", { type: "button", className: cn(theme.starStyle, "hover:scale-110 active:scale-95"), onClick: (e) => handleStarClick(starIndex, e), onMouseMove: (e) => handleStarMouseMove(starIndex, e), "aria-label": `Rate ${starIndex} out of ${max}`, tabIndex: -1, children: renderStarIcon() }, starIndex));
163
+ return (jsx("button", { type: "button", className: cn(theme.starStyle, "hover:scale-110 active:scale-95"), onClick: (e) => handleStarClick(starIndex, e), onMouseMove: (e) => handleStarMouseMove(starIndex, e), "aria-label": `Rate ${starIndex} out of ${max}`, tabIndex: -1, "aria-hidden": "true", children: renderStarIcon() }, starIndex));
164
164
  };
165
165
  // Build array of star indices (1-based)
166
166
  const stars = useMemo(() => Array.from({ length: max }, (_, i) => i + 1), [max]);
@@ -50,8 +50,9 @@ const ScrollArea = React__default.forwardRef(({ children, type = 'hover', size =
50
50
  const smoothAPI = useSmoothScroll(smooth ? internalRef : null, smoothScrollConfig, smoothOrientation);
51
51
  // Expose extended API via ref
52
52
  useImperativeHandle(ref, () => {
53
- if (!internalRef.current)
54
- return null;
53
+ // internalRef.current is always set by the time the imperative handle is
54
+ // accessed (refs are resolved after mount). The non-null assertion avoids
55
+ // the unsafe `null as unknown as ScrollAreaRef` double-cast.
55
56
  const element = internalRef.current;
56
57
  // Create extended ref object
57
58
  const extendedRef = element;
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React__default, { useRef, useState, useEffect, useMemo } from 'react';
2
+ import React__default, { useRef, useState, useCallback, useEffect, useMemo } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { debounce } from '../../../utils/debounce.js';
5
5
  import { segmentedTheme } from './Segmented.theme.js';
@@ -10,11 +10,11 @@ import { SegmentedItem } from './SegmentedItem.js';
10
10
  * Segmented component for selecting one option from a set of segments
11
11
  * Uses compound component pattern with Segmented.Item
12
12
  */
13
- const SegmentedComponent = React__default.forwardRef(({ value, onChange, size = "md", disabled = false, block = false, children, className, slidingBackgroundClassName, theme, ...props }, ref) => {
13
+ const SegmentedComponent = React__default.forwardRef(({ value, onChange, size = "md", disabled = false, block = false, children, className, slidingBackgroundClassName, theme, "aria-label": ariaLabel, ...props }, ref) => {
14
14
  var _a;
15
15
  const containerRef = useRef(null);
16
16
  const [activeStyle, setActiveStyle] = useState({});
17
- const updateActiveStyle = () => {
17
+ const updateActiveStyle = useCallback(() => {
18
18
  if (!containerRef.current || !value)
19
19
  return;
20
20
  const activeButton = containerRef.current.querySelector(`[data-value="${value}"]`);
@@ -28,10 +28,10 @@ const SegmentedComponent = React__default.forwardRef(({ value, onChange, size =
28
28
  transform: `translateX(${buttonRect.left - containerRect.left}px)`,
29
29
  });
30
30
  }
31
- };
31
+ }, [value]);
32
32
  useEffect(() => {
33
33
  updateActiveStyle();
34
- }, [value]);
34
+ }, [value, updateActiveStyle]);
35
35
  // Debounced resize handler for better performance
36
36
  const debouncedUpdateActiveStyle = useMemo(() => debounce(updateActiveStyle, 100), [updateActiveStyle]);
37
37
  useEffect(() => {
@@ -52,7 +52,7 @@ const SegmentedComponent = React__default.forwardRef(({ value, onChange, size =
52
52
  else if (ref) {
53
53
  ref.current = node;
54
54
  }
55
- }, role: "tablist", "aria-orientation": "horizontal", className: cn((theme === null || theme === void 0 ? void 0 : theme.baseStyle) || segmentedTheme.baseStyle, ((_a = theme === null || theme === void 0 ? void 0 : theme.sizes) === null || _a === void 0 ? void 0 : _a[size]) || segmentedTheme.sizes[size], block && ((theme === null || theme === void 0 ? void 0 : theme.blockStyle) || segmentedTheme.blockStyle), disabled && ((theme === null || theme === void 0 ? void 0 : theme.disabledStyle) || segmentedTheme.disabledStyle), className), "data-size": size, "data-disabled": disabled, "data-block": block, ...props, children: [value && (jsx("div", { className: cn((theme === null || theme === void 0 ? void 0 : theme.slidingBackgroundStyle) ||
55
+ }, role: "radiogroup", "aria-label": ariaLabel, className: cn((theme === null || theme === void 0 ? void 0 : theme.baseStyle) || segmentedTheme.baseStyle, ((_a = theme === null || theme === void 0 ? void 0 : theme.sizes) === null || _a === void 0 ? void 0 : _a[size]) || segmentedTheme.sizes[size], block && ((theme === null || theme === void 0 ? void 0 : theme.blockStyle) || segmentedTheme.blockStyle), disabled && ((theme === null || theme === void 0 ? void 0 : theme.disabledStyle) || segmentedTheme.disabledStyle), className), "data-size": size, "data-disabled": disabled, "data-block": block, ...props, children: [value && (jsx("div", { className: cn((theme === null || theme === void 0 ? void 0 : theme.slidingBackgroundStyle) ||
56
56
  segmentedTheme.slidingBackgroundStyle, slidingBackgroundClassName), style: activeStyle })), children] }) }));
57
57
  });
58
58
  // Set display name for dev tools and Storybook
@@ -18,10 +18,36 @@ const SegmentedItem = React__default.forwardRef(({ value, disabled = false, chil
18
18
  onChange === null || onChange === void 0 ? void 0 : onChange(value);
19
19
  onClick === null || onClick === void 0 ? void 0 : onClick(event);
20
20
  };
21
- return (jsx("button", { ref: ref, type: "button", role: "tab", "aria-selected": isSelected, disabled: isDisabled, className: cn(segmentedItemTheme.baseStyle, segmentedItemTheme.sizes[size], isSelected
21
+ const handleKeyDown = (event) => {
22
+ if (isDisabled)
23
+ return;
24
+ const radioGroup = event.currentTarget.parentElement;
25
+ if (!radioGroup)
26
+ return;
27
+ const radios = Array.from(radioGroup.querySelectorAll('[role="radio"]:not([disabled])'));
28
+ const currentIndex = radios.indexOf(event.currentTarget);
29
+ let targetRadio;
30
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
31
+ event.preventDefault();
32
+ targetRadio = radios[(currentIndex + 1) % radios.length];
33
+ }
34
+ else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
35
+ event.preventDefault();
36
+ targetRadio = radios[(currentIndex - 1 + radios.length) % radios.length];
37
+ }
38
+ if (targetRadio) {
39
+ targetRadio.focus();
40
+ // Read the value from the data attribute and trigger selection
41
+ const targetValue = targetRadio.getAttribute('data-value');
42
+ if (targetValue) {
43
+ onChange === null || onChange === void 0 ? void 0 : onChange(targetValue);
44
+ }
45
+ }
46
+ };
47
+ return (jsx("button", { ref: ref, type: "button", role: "radio", "aria-checked": isSelected, tabIndex: isSelected ? 0 : -1, disabled: isDisabled, className: cn(segmentedItemTheme.baseStyle, segmentedItemTheme.sizes[size], isSelected
22
48
  ? segmentedItemTheme.selectedStyle
23
49
  : segmentedItemTheme.unselectedStyle, isDisabled && segmentedItemTheme.disabledStyle, className // User overrides take precedence
24
- ), onClick: handleClick, "data-value": value, "data-selected": isSelected, "data-disabled": isDisabled, ...props, children: children }));
50
+ ), onClick: handleClick, onKeyDown: handleKeyDown, "data-value": value, "data-selected": isSelected, "data-disabled": isDisabled, ...props, children: children }));
25
51
  });
26
52
  // Set display name for dev tools and Storybook
27
53
  SegmentedItem.displayName = "Segmented.Item";
@@ -10,4 +10,5 @@ export declare const Sidebar: React.ForwardRefExoticComponent<SidebarProps & Rea
10
10
  Toggle: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarToggleProps & React.RefAttributes<HTMLButtonElement>>;
11
11
  MobileTrigger: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarMobileTriggerProps & React.RefAttributes<HTMLButtonElement>>;
12
12
  UserProfile: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarUserProfileProps & React.RefAttributes<HTMLDivElement>>;
13
+ Logo: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarLogoProps & React.RefAttributes<HTMLDivElement>>;
13
14
  };
@@ -13,11 +13,12 @@ import { SidebarSubmenu } from './SidebarSubmenu.js';
13
13
  import { SidebarToggle } from './SidebarToggle.js';
14
14
  import { SidebarMobileTrigger } from './SidebarMobileTrigger.js';
15
15
  import { SidebarUserProfile } from './SidebarUserProfile.js';
16
+ import { SidebarLogo } from './SidebarLogo.js';
16
17
 
17
18
  const SIDEBAR_TRANSITION = {
18
- type: "spring",
19
- stiffness: 300,
20
- damping: 30,
19
+ type: "tween",
20
+ duration: 0.22,
21
+ ease: [0.4, 0, 0.2, 1],
21
22
  };
22
23
  const SidebarDesktop = React__default.forwardRef(({ side = "left", variant = "sidebar", width, children, className, theme: customTheme = {}, ...props }, ref) => {
23
24
  var _a, _b, _c;
@@ -73,6 +74,7 @@ const Sidebar = Object.assign(SidebarRoot, {
73
74
  Toggle: SidebarToggle,
74
75
  MobileTrigger: SidebarMobileTrigger,
75
76
  UserProfile: SidebarUserProfile,
77
+ Logo: SidebarLogo,
76
78
  });
77
79
 
78
80
  export { Sidebar };
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { ReactNode, HTMLAttributes } from 'react';
2
2
  export type SidebarSide = 'left' | 'right';
3
3
  export type SidebarVariant = 'sidebar' | 'floating' | 'inset';
4
4
  export type SidebarUserStatus = 'online' | 'offline' | 'away' | 'busy';
@@ -158,6 +158,12 @@ export interface SidebarUserProfileProps extends React.HTMLAttributes<HTMLDivEle
158
158
  */
159
159
  dropdownContent?: ReactNode;
160
160
  }
161
+ export interface SidebarLogoProps extends HTMLAttributes<HTMLDivElement> {
162
+ /** Always-visible icon/mark (shown in both expanded and collapsed states) */
163
+ icon: ReactNode;
164
+ /** Text shown only when expanded — animates in/out automatically */
165
+ text?: ReactNode;
166
+ }
161
167
  export interface SidebarTheme {
162
168
  baseStyle: string;
163
169
  variants: {
@@ -8,7 +8,7 @@ const SidebarContent = React__default.forwardRef(({ children, className, ...prop
8
8
  const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
9
9
  const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
10
10
  const isCollapsedView = collapsed && !isMobile;
11
- return (jsx("div", { ref: ref, className: cn("flex-1 overflow-y-auto overflow-x-hidden py-2 transition-all duration-200", isCollapsedView ? "px-2" : "px-3", className), ...props, children: children }));
11
+ return (jsx("div", { ref: ref, className: cn("flex-1 overflow-y-auto overflow-x-hidden py-3 transition-all duration-200", isCollapsedView ? "px-2" : "px-2.5", className), ...props, children: children }));
12
12
  });
13
13
  SidebarContent.displayName = "SidebarContent";
14
14
 
@@ -8,7 +8,7 @@ const SidebarFooter = React__default.forwardRef(({ children, className, ...props
8
8
  const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
9
9
  const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
10
10
  const isCollapsedView = collapsed && !isMobile;
11
- return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-t border-[var(--color-border)] transition-all duration-200", isCollapsedView ? "p-2 flex justify-center" : "p-4", className), ...props, children: children }));
11
+ return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-t border-[var(--color-border)] transition-all duration-200", isCollapsedView ? "p-2 flex justify-center" : "p-3", className), ...props, children: children }));
12
12
  });
13
13
  SidebarFooter.displayName = "SidebarFooter";
14
14
 
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import React__default from 'react';
3
3
  import { XMarkIcon } from '@heroicons/react/24/outline';
4
4
  import { cn } from '../../../utils/cn.js';
@@ -15,7 +15,10 @@ const SidebarHeader = React__default.forwardRef(({ logo, showToggle = true, cust
15
15
  if (children) {
16
16
  return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-b border-[var(--color-border)]", className), ...props, children: children }));
17
17
  }
18
- return (jsxs("div", { ref: ref, className: cn("flex items-center justify-between flex-shrink-0 h-16 border-b border-[var(--color-border)] transition-all duration-200", collapsed && !isMobile ? "px-3" : "px-4", className), ...props, children: [jsx("div", { className: cn("flex items-center min-w-0 transition-all duration-200", collapsed && !isMobile ? "flex-shrink-0" : "flex-1"), children: logo }), isMobile && isMobileOpen && (jsx(Button, { variant: "soft", size: "sm", className: "size-8 p-0", onClick: () => sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.setMobileOpen(false), "aria-label": "Close navigation", children: jsx(XMarkIcon, { className: "size-5" }) })), !isMobile && showToggle && (jsx("div", { className: "flex-shrink-0", children: customToggle || jsx(SidebarToggle, {}) }))] }));
18
+ const isCollapsedDesktop = collapsed && !isMobile;
19
+ return (jsx("div", { ref: ref, className: cn("flex items-center flex-shrink-0 h-14 border-b border-[var(--color-border)] overflow-hidden", isCollapsedDesktop ? "justify-center px-2" : "justify-between px-3", className), ...props, children: isCollapsedDesktop ? (
20
+ /* Collapsed: only show the toggle (expand) button centered */
21
+ showToggle && (customToggle || jsx(SidebarToggle, {}))) : (jsxs(Fragment, { children: [jsx("div", { className: "flex items-center min-w-0 flex-1", children: logo }), isMobile && isMobileOpen && (jsx(Button, { variant: "ghost", size: "sm", className: "size-7 p-0 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]", onClick: () => sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.setMobileOpen(false), "aria-label": "Close navigation", children: jsx(XMarkIcon, { className: "size-4" }) })), !isMobile && showToggle && (jsx("div", { className: "flex-shrink-0", children: customToggle || jsx(SidebarToggle, {}) }))] })) }));
19
22
  });
20
23
  SidebarHeader.displayName = "SidebarHeader";
21
24
 
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ import { SidebarLogoProps } from "./Sidebar.types";
3
+ export declare const SidebarLogo: React.ForwardRefExoticComponent<SidebarLogoProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,17 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import React__default from 'react';
3
+ import { cn } from '../../../utils/cn.js';
4
+ import { AnimatePresence, motion } from 'motion/react';
5
+ import { useSidebarContext } from './SidebarContext.js';
6
+
7
+ const SidebarLogo = React__default.forwardRef(({ icon, text, className, ...props }, ref) => {
8
+ var _a, _b;
9
+ const context = useSidebarContext();
10
+ const collapsed = (_a = context === null || context === void 0 ? void 0 : context.collapsed) !== null && _a !== void 0 ? _a : false;
11
+ const isMobile = (_b = context === null || context === void 0 ? void 0 : context.isMobile) !== null && _b !== void 0 ? _b : false;
12
+ const isCollapsedView = collapsed && !isMobile;
13
+ return (jsxs("div", { ref: ref, className: cn("flex items-center gap-2 overflow-hidden", className), ...props, children: [jsx("div", { className: "flex-shrink-0", children: icon }), jsx(AnimatePresence, { initial: false, children: !isCollapsedView && text && (jsx(motion.div, { className: "overflow-hidden whitespace-nowrap", initial: { opacity: 0, x: -8 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -8 }, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] }, children: typeof text === "string" ? (jsx("span", { className: "text-lg font-bold text-[var(--color-text-primary)]", children: text })) : (text) }, "logo-text")) })] }));
14
+ });
15
+ SidebarLogo.displayName = "SidebarLogo";
16
+
17
+ export { SidebarLogo };
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import React__default from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
+ import { AnimatePresence, motion } from 'motion/react';
4
5
  import { useSidebarContext } from './SidebarContext.js';
5
6
 
6
7
  const SidebarNavGroup = React__default.forwardRef(({ title, showTitleWhenCollapsed = false, children, className, ...props }, ref) => {
@@ -9,7 +10,7 @@ const SidebarNavGroup = React__default.forwardRef(({ title, showTitleWhenCollaps
9
10
  const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
10
11
  const isCollapsedView = collapsed && !isMobile;
11
12
  const showTitle = title && (!isCollapsedView || showTitleWhenCollapsed);
12
- return (jsxs("div", { ref: ref, className: cn("space-y-1", className), ...props, children: [showTitle && (jsx("div", { className: "px-2 py-2", children: jsx("h3", { className: "text-sm font-semibold text-[var(--color-text-muted)] uppercase tracking-wider", children: title }) })), isCollapsedView && !showTitleWhenCollapsed && (jsx("div", { className: "h-px bg-[var(--color-border)] mx-2 my-2" })), jsx("div", { className: "space-y-1", children: children })] }));
13
+ return (jsxs("div", { ref: ref, className: cn("space-y-1", className), ...props, children: [jsx(AnimatePresence, { initial: false, children: showTitle && (jsx(motion.div, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.15, ease: "easeInOut" }, className: "overflow-hidden", children: jsx("div", { className: "px-2 py-1", children: jsx("h3", { className: "text-xs font-semibold text-[var(--color-text-muted)] uppercase tracking-wider", children: title }) }) }, "title")) }), jsx(AnimatePresence, { initial: false, children: isCollapsedView && !showTitleWhenCollapsed && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, className: "h-px bg-[var(--color-border)] mx-2 my-1" }, "sep")) }), jsx("div", { className: "space-y-1", children: children })] }));
13
14
  });
14
15
  SidebarNavGroup.displayName = "SidebarNavGroup";
15
16
 
@@ -1,6 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import React__default from 'react';
3
- import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/24/outline';
3
+ import { ChevronLeftIcon } from '@heroicons/react/24/outline';
4
4
  import { motion } from 'motion/react';
5
5
  import { cn } from '../../../utils/cn.js';
6
6
  import { useSidebarContext } from './SidebarContext.js';
@@ -20,9 +20,7 @@ const SidebarToggle = React__default.forwardRef(({ expandIcon, collapseIcon, cla
20
20
  onClick === null || onClick === void 0 ? void 0 : onClick(e);
21
21
  };
22
22
  const { onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, color: _color, ...buttonProps } = props;
23
- return (jsx(Button, { ref: ref, variant: "soft", size: "sm", className: cn("size-8 p-0", className), onClick: handleClick, "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar", title: collapsed ? "Expand sidebar" : "Collapse sidebar", ...buttonProps, children: jsx(motion.div, { initial: false, animate: { rotate: collapsed ? 180 : 0 }, transition: { duration: 0.2 }, children: collapsed
24
- ? expandIcon || jsx(ChevronRightIcon, { className: "size-4" })
25
- : collapseIcon || jsx(ChevronLeftIcon, { className: "size-4" }) }) }));
23
+ return (jsx(Button, { ref: ref, variant: "ghost", size: "sm", className: cn("size-7 p-0 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-background-secondary)]", className), onClick: handleClick, "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar", title: collapsed ? "Expand sidebar" : "Collapse sidebar", ...buttonProps, children: jsx(motion.div, { initial: false, animate: { rotate: collapsed ? 180 : 0 }, transition: { duration: 0.22, ease: [0.4, 0, 0.2, 1] }, children: jsx(ChevronLeftIcon, { className: "size-[15px]" }) }) }));
26
24
  });
27
25
  SidebarToggle.displayName = "SidebarToggle";
28
26
 
@@ -1,6 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import React__default from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
+ import { AnimatePresence, motion } from 'motion/react';
4
5
  import { useSidebarContext } from './SidebarContext.js';
5
6
  import { Avatar } from '../Avatar/Avatar.js';
6
7
  import { Tooltip } from '../Tooltip/Tooltip.js';
@@ -23,12 +24,8 @@ const SidebarUserProfile = React__default.forwardRef(({ name, email, avatar, sta
23
24
  e.preventDefault();
24
25
  e.currentTarget.click();
25
26
  }
26
- }, ...props, children: [avatarElement, !isCollapsedView && (jsxs("div", { className: "flex-1 min-w-0", children: [jsx("div", { className: "text-sm font-medium text-[var(--color-text-primary)] truncate", children: name }), email && (jsx("div", { className: "text-xs text-[var(--color-text-muted)] truncate", children: email }))] }))] }));
27
- // Tooltip when collapsed
28
- if (isCollapsedView) {
29
- return (jsx(Tooltip, { content: name, placement: "right", children: content }));
30
- }
31
- return content;
27
+ }, ...props, children: [avatarElement, jsx(AnimatePresence, { initial: false, children: !isCollapsedView && (jsxs(motion.div, { className: "flex-1 min-w-0", initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.12, ease: "easeOut" }, children: [jsx("div", { className: "text-sm font-medium text-[var(--color-text-primary)] truncate", children: name }), email && (jsx("div", { className: "text-xs text-[var(--color-text-muted)] truncate", children: email }))] }, "profile-text")) })] }));
28
+ return (jsx(Tooltip, { content: name, placement: "right", disabled: !isCollapsedView, triggerClassName: "block", children: content }));
32
29
  });
33
30
  SidebarUserProfile.displayName = "SidebarUserProfile";
34
31
 
@@ -8,5 +8,6 @@ export { SidebarSubmenu } from "./SidebarSubmenu";
8
8
  export { SidebarToggle } from "./SidebarToggle";
9
9
  export { SidebarMobileTrigger } from "./SidebarMobileTrigger";
10
10
  export { SidebarUserProfile } from "./SidebarUserProfile";
11
+ export { SidebarLogo } from "./SidebarLogo";
11
12
  export { useSidebarContext } from "./SidebarContext";
12
- export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarMobileTriggerProps, SidebarUserProfileProps, SidebarUserStatus, SidebarSide, SidebarVariant, SidebarContextValue, SidebarTheme, SidebarThemeOverrides, } from "./Sidebar.types";
13
+ export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarMobileTriggerProps, SidebarUserProfileProps, SidebarUserStatus, SidebarSide, SidebarVariant, SidebarContextValue, SidebarTheme, SidebarThemeOverrides, SidebarLogoProps, } from "./Sidebar.types";
@@ -50,11 +50,11 @@ const Skeleton = React__default.forwardRef(({ variant = 'rectangle', width, heig
50
50
  // Make last line 75% width for more natural look
51
51
  const isLastLine = index === lines - 1;
52
52
  const lineWidth = isLastLine && !width ? '75%' : computedWidth;
53
- return (jsx("div", { className: baseClasses, style: { ...computedStyle, width: lineWidth } }, index));
53
+ return (jsx("div", { className: baseClasses, style: { ...computedStyle, width: lineWidth }, "aria-hidden": "true" }, index));
54
54
  }) }));
55
55
  }
56
56
  // Single skeleton
57
- return (jsx("div", { ref: ref, className: baseClasses, style: computedStyle, "aria-busy": "true", "aria-live": "polite", ...props }));
57
+ return (jsx("div", { ref: ref, className: baseClasses, style: computedStyle, "aria-hidden": "true", ...props }));
58
58
  });
59
59
  Skeleton.displayName = 'Skeleton';
60
60
 
@@ -1,4 +1,4 @@
1
1
  import React from 'react';
2
2
  import { SortableProps, SortableItemProps } from './Sortable.types';
3
3
  export declare const Sortable: React.ForwardRefExoticComponent<SortableProps & React.RefAttributes<HTMLUListElement>>;
4
- export declare const SortableItem: React.ForwardRefExoticComponent<SortableItemProps & React.RefAttributes<HTMLDivElement>>;
4
+ export declare const SortableItem: React.ForwardRefExoticComponent<SortableItemProps & React.RefAttributes<HTMLLIElement>>;
@@ -1,5 +1,5 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React__default, { createContext, useState, useRef, useMemo, useCallback, useEffect, useContext } from 'react';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import React__default, { createContext, useState, useRef, useId, useMemo, useCallback, useEffect, useContext } from 'react';
3
3
  import { Reorder, useDragControls, motion } from 'motion/react';
4
4
  import { LockClosedIcon, Bars3Icon } from '@heroicons/react/24/outline';
5
5
  import { cn } from '../../../utils/cn.js';
@@ -19,6 +19,8 @@ const Sortable = React__default.forwardRef(({ children, onReorder, className, th
19
19
  const [dropPosition, setDropPosition] = useState(null);
20
20
  const itemRefs = useRef(new Map());
21
21
  const [draggedItemContent, setDraggedItemContent] = useState(null);
22
+ const [liveMessage, setLiveMessage] = useState('');
23
+ const liveRegionId = useId();
22
24
  const theme = {
23
25
  ...sortableTheme,
24
26
  ...customTheme,
@@ -63,8 +65,19 @@ const Sortable = React__default.forwardRef(({ children, onReorder, className, th
63
65
  itemRefs,
64
66
  draggedItemContent,
65
67
  setDraggedItemContent,
68
+ setLiveMessage,
66
69
  }), [direction, showDragHandle, theme, onReorder, onSortDragStart, onSortDragEnd, draggedItem, dropPosition, draggedItemContent]);
67
- return (jsx(SortableContext.Provider, { value: contextValue, children: jsx(Reorder.Group, { ref: ref, axis: direction === 'horizontal' ? 'x' : 'y', values: internalValues, onReorder: handleReorder, className: cn(theme.containerStyle, direction === 'horizontal' ? 'flex flex-row' : 'flex flex-col', className), as: "div", children: children }) }));
70
+ return (jsxs(SortableContext.Provider, { value: contextValue, children: [jsx("div", { id: liveRegionId, "aria-live": "assertive", "aria-atomic": "true", style: {
71
+ position: 'absolute',
72
+ width: '1px',
73
+ height: '1px',
74
+ padding: 0,
75
+ margin: '-1px',
76
+ overflow: 'hidden',
77
+ clip: 'rect(0, 0, 0, 0)',
78
+ whiteSpace: 'nowrap',
79
+ border: 0,
80
+ }, children: liveMessage }), jsx(Reorder.Group, { ref: ref, axis: direction === 'horizontal' ? 'x' : 'y', values: internalValues, onReorder: handleReorder, className: cn(theme.containerStyle, direction === 'horizontal' ? 'flex flex-row' : 'flex flex-col', className), as: "ul", children: children })] }));
68
81
  });
69
82
  Sortable.displayName = 'Sortable';
70
83
  const SortableItem = React__default.forwardRef(({ id, children, className, theme: customTheme = {}, disabled = false, value, }, forwardedRef) => {
@@ -136,6 +149,9 @@ const SortableItem = React__default.forwardRef(({ id, children, className, theme
136
149
  }
137
150
  if (targetIndex !== currentIndex) {
138
151
  context.onReorder(currentIndex, targetIndex);
152
+ // Announce the reorder to screen readers via the live region
153
+ const itemLabel = typeof value === 'string' ? value : id;
154
+ context.setLiveMessage(`${itemLabel} moved to position ${targetIndex + 1} of ${items.length}`);
139
155
  // Focus the item at new position after reorder
140
156
  setTimeout(() => {
141
157
  const targetItem = items[targetIndex];
@@ -185,7 +201,7 @@ const SortableItem = React__default.forwardRef(({ id, children, className, theme
185
201
  dragControls.start(event);
186
202
  }, [disabled, dragControls]);
187
203
  const isBeingDragged = context.draggedItem === id;
188
- return (jsxs(Reorder.Item, { ref: (node) => {
204
+ return (jsxs(Reorder.Item, { as: "li", ref: (node) => {
189
205
  itemRef.current = node;
190
206
  if (typeof forwardedRef === 'function') {
191
207
  forwardedRef(node);
@@ -70,4 +70,6 @@ export interface SortableContextValue {
70
70
  itemRefs: React.MutableRefObject<Map<string, HTMLDivElement>>;
71
71
  draggedItemContent: React.ReactNode | null;
72
72
  setDraggedItemContent: (content: React.ReactNode | null) => void;
73
+ /** Update the visually-hidden live region with a reorder announcement */
74
+ setLiveMessage: (message: string) => void;
73
75
  }
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import React__default, { useState, useRef, useCallback, useMemo } from 'react';
2
+ import React__default, { useState, useId, useRef, useCallback, useMemo } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { tabsTheme } from './Tabs.theme.js';
5
5
  import { TabsContext } from './TabsContext.js';
@@ -14,6 +14,8 @@ import { TabsBody } from './TabsBody.js';
14
14
  */
15
15
  const TabsComponent = React__default.forwardRef(({ value, defaultValue, onValueChange, orientation = "horizontal", size = "md", disabled = false, children, className, darkMode = false, ...props }, ref) => {
16
16
  const [currentValue, setCurrentValue] = useState(value || defaultValue || "");
17
+ // Unique instance prefix to scope all DOM IDs to this Tabs instance
18
+ const instanceId = useId();
17
19
  // Track trigger elements for underline positioning
18
20
  const triggerRefs = useRef(new Map());
19
21
  // Handle controlled vs uncontrolled
@@ -68,15 +70,15 @@ const TabsComponent = React__default.forwardRef(({ value, defaultValue, onValueC
68
70
  disabled,
69
71
  registerTrigger,
70
72
  unregisterTrigger,
71
- }), [activeValue, handleValueChange, orientation, size, disabled, registerTrigger, unregisterTrigger]);
73
+ instanceId,
74
+ }), [activeValue, handleValueChange, orientation, size, disabled, registerTrigger, unregisterTrigger, instanceId]);
72
75
  // Memoize theme classes for performance
73
76
  const themeClasses = useMemo(() => ({
74
77
  baseStyle: tabsTheme.baseStyle,
75
78
  disabledStyle: disabled ? "opacity-50 cursor-not-allowed pointer-events-none" : "",
76
79
  }), [disabled]);
77
80
  return (jsx(TabsContext.Provider, { value: contextValue, children: jsx("div", { ref: ref, className: cn(themeClasses.baseStyle, themeClasses.disabledStyle, darkMode && "dark", className // User overrides take precedence
78
- ), "data-orientation": orientation, "data-size": size, "data-disabled": disabled, role: "tablist" // Add semantic role for screen readers
79
- , "aria-orientation": orientation, ...props, children: children }) }));
81
+ ), "data-orientation": orientation, "data-size": size, "data-disabled": disabled, ...props, children: children }) }));
80
82
  });
81
83
  // Set display name for dev tools and Storybook
82
84
  TabsComponent.displayName = "Tabs";
@@ -139,4 +139,6 @@ export interface TabsContextValue {
139
139
  registerTrigger: (value: string, element: HTMLButtonElement) => void;
140
140
  /** Unregister a tab trigger when it unmounts */
141
141
  unregisterTrigger: (value: string) => void;
142
+ /** Unique prefix scoping DOM IDs to this Tabs instance */
143
+ instanceId: string;
142
144
  }
@@ -10,7 +10,7 @@ import { getTabsBodyVariants } from './Tabs.animations.js';
10
10
  * TabsBody component displays content panels for tabs with directional fade animations
11
11
  */
12
12
  const TabsBody = React__default.forwardRef(({ value, forceMount = false, children, className, ...props }, ref) => {
13
- const { value: selectedValue, orientation } = useTabsContext();
13
+ const { value: selectedValue, orientation, instanceId } = useTabsContext();
14
14
  const shouldReduceMotion = useReducedMotion();
15
15
  // Check if we're in Storybook and should disable animations
16
16
  const isInStorybook = typeof window !== 'undefined' && window.__STORYBOOK_DISABLE_ANIMATIONS__;
@@ -21,7 +21,7 @@ const TabsBody = React__default.forwardRef(({ value, forceMount = false, childre
21
21
  const contentVariants = useMemo(() => getTabsBodyVariants(shouldDisableAnimations), [shouldDisableAnimations]);
22
22
  // For forceMount, we handle visibility differently to preserve DOM
23
23
  if (forceMount) {
24
- return (jsx(motion.div, { ref: ref, role: "tabpanel", "aria-labelledby": `${value}-tab`, id: `${value}-content`, tabIndex: 0, className: cn(tabsTheme.bodyStyle, !isSelected && "hidden", // Hide when not selected but force mounted
24
+ return (jsx(motion.div, { ref: ref, role: "tabpanel", "aria-labelledby": `${instanceId}-${value}-tab`, id: `${instanceId}-${value}-content`, tabIndex: 0, className: cn(tabsTheme.bodyStyle, !isSelected && "hidden", // Hide when not selected but force mounted
25
25
  className // User overrides take precedence
26
26
  ), "data-state": isSelected ? "active" : "inactive", "data-orientation": orientation, style: {
27
27
  // Ensure proper stacking context for animations
@@ -39,7 +39,7 @@ const TabsBody = React__default.forwardRef(({ value, forceMount = false, childre
39
39
  exit: "exit",
40
40
  variants: contentVariants,
41
41
  };
42
- return (jsx(AnimatePresence, { mode: "wait", children: isSelected && (jsx(motion.div, { ref: ref, role: "tabpanel", "aria-labelledby": `${value}-tab`, id: `${value}-content`, tabIndex: 0, className: cn(tabsTheme.bodyStyle, className // User overrides take precedence
42
+ return (jsx(AnimatePresence, { mode: "wait", children: isSelected && (jsx(motion.div, { ref: ref, role: "tabpanel", "aria-labelledby": `${instanceId}-${value}-tab`, id: `${instanceId}-${value}-content`, tabIndex: 0, className: cn(tabsTheme.bodyStyle, className // User overrides take precedence
43
43
  ), "data-state": "active", "data-orientation": orientation, style: {
44
44
  // Ensure proper stacking context for animations
45
45
  position: "relative",