@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.
- package/dist/components/ai/PromptInput/PromptInput.js +139 -7
- package/dist/components/ai/PromptInput/VoiceRecorder.js +84 -0
- package/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
- package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
- package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
- package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
- package/dist/components/core/Alert/Alert.js +2 -1
- package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/core/Badge/Badge.js +6 -17
- package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
- package/dist/components/core/Calendar/Calendar.js +1 -1
- package/dist/components/core/Carousel/CarouselBody.js +6 -1
- package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
- package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
- package/dist/components/core/DragDrop/DragDrop.js +170 -17
- package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +2 -0
- package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
- package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
- package/dist/components/core/Drawer/DrawerTitle.js +2 -2
- package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
- package/dist/components/core/Masonry/Masonry.js +7 -2
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +3 -3
- package/dist/components/core/Modal/Modal.types.d.ts +2 -0
- package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
- package/dist/components/core/Modal/ModalTitle.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +21 -6
- package/dist/components/core/NavItem/NavItem.theme.js +2 -2
- package/dist/components/core/Pagination/Pagination.js +1 -1
- package/dist/components/core/Popover/PopoverBody.js +3 -27
- package/dist/components/core/Popover/PopoverTrigger.js +1 -1
- package/dist/components/core/Progress/Progress.js +4 -4
- package/dist/components/core/Rating/Rating.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
- package/dist/components/core/Segmented/Segmented.js +6 -6
- package/dist/components/core/Segmented/SegmentedItem.js +28 -2
- package/dist/components/core/Sidebar/Sidebar.d.ts +1 -0
- package/dist/components/core/Sidebar/Sidebar.js +5 -3
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +7 -1
- package/dist/components/core/Sidebar/SidebarContent.js +1 -1
- package/dist/components/core/Sidebar/SidebarFooter.js +1 -1
- package/dist/components/core/Sidebar/SidebarHeader.js +5 -2
- package/dist/components/core/Sidebar/SidebarLogo.d.ts +3 -0
- package/dist/components/core/Sidebar/SidebarLogo.js +17 -0
- package/dist/components/core/Sidebar/SidebarNavGroup.js +2 -1
- package/dist/components/core/Sidebar/SidebarToggle.js +2 -4
- package/dist/components/core/Sidebar/SidebarUserProfile.js +3 -6
- package/dist/components/core/Sidebar/index.d.ts +2 -1
- package/dist/components/core/Skeleton/Skeleton.js +2 -2
- package/dist/components/core/Sortable/Sortable.d.ts +1 -1
- package/dist/components/core/Sortable/Sortable.js +20 -4
- package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
- package/dist/components/core/Tabs/Tabs.js +6 -4
- package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
- package/dist/components/core/Tabs/TabsBody.js +3 -3
- package/dist/components/core/Tabs/TabsList.js +19 -21
- package/dist/components/core/Tabs/TabsTrigger.js +47 -28
- package/dist/components/core/Toast/Toast.js +1 -1
- package/dist/components/core/Toast/ToastProvider.js +3 -2
- package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +26 -35
- package/dist/components/core/Tooltip/Tooltip.types.d.ts +5 -1
- package/dist/components/core/Tree/Tree.js +178 -8
- package/dist/components/core/index.js +1 -0
- package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
- package/dist/components/data-display/Table/Table.js +2 -2
- package/dist/components/data-display/Table/Table.types.d.ts +2 -0
- package/dist/components/data-display/Table/TableFilter.js +21 -18
- package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
- package/dist/components/effects/Overlay/Overlay.js +4 -9
- package/dist/components/effects/PageTransition/PageTransition.js +17 -6
- package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
- package/dist/components/forms/Checkbox/Checkbox.js +11 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
- package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
- package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
- package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
- package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
- package/dist/components/forms/ColorPicker/index.d.ts +1 -1
- package/dist/components/forms/Combobox/Combobox.js +20 -45
- package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
- package/dist/components/forms/CronInput/CronInput.js +10 -4
- package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
- package/dist/components/forms/DatePicker/DatePicker.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
- package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +99 -10
- package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
- package/dist/components/forms/FormLabel/FormLabel.js +2 -2
- package/dist/components/forms/Input/Input.js +5 -3
- package/dist/components/forms/Input/Input.types.d.ts +2 -0
- package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
- package/dist/components/forms/InputAddress/InputAddress.js +8 -9
- package/dist/components/forms/InputCounter/InputCounter.js +25 -18
- package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
- package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
- package/dist/components/forms/InputOTP/InputOTP.js +26 -17
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
- package/dist/components/forms/InputTag/InputTag.js +12 -39
- package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
- package/dist/components/forms/Mention/Mention.js +13 -6
- package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
- package/dist/components/forms/Radio/Radio.js +12 -2
- package/dist/components/forms/Radio/Radio.theme.js +5 -1
- package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
- package/dist/components/forms/Radio/RadioGroup.js +8 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
- package/dist/components/forms/Select/Select.d.ts +2 -0
- package/dist/components/forms/Select/Select.js +26 -56
- package/dist/components/forms/Select/Select.types.d.ts +14 -0
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
- package/dist/components/forms/Slider/Slider.js +18 -4
- package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
- package/dist/components/forms/Switch/Switch.js +12 -19
- package/dist/components/forms/Switch/Switch.theme.js +12 -2
- package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
- package/dist/components/forms/Textarea/Textarea.js +8 -3
- package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
- package/dist/components/forms/TimePicker/TimePicker.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
- package/dist/components/forms/TimePicker/TimePickerContent.js +88 -30
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
- package/dist/components/forms/index.js +86 -1
- package/dist/components/layout/FormLayout/FormLayout.js +87 -1
- package/dist/components/layout/index.js +4 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useClickOutside.d.ts +10 -4
- package/dist/hooks/useClickOutside.js +44 -24
- package/dist/hooks/useDarkMode.d.ts +1 -0
- package/dist/hooks/useDarkMode.js +42 -0
- package/dist/hooks/useFormStateMachine.d.ts +1 -1
- package/dist/hooks/useNetworkStatus.js +1 -1
- package/dist/hooks/useOverlay.js +4 -0
- package/dist/hooks/useScaleBackground.js +5 -2
- package/dist/hooks/useSelectPortal.js +14 -5
- package/dist/index.d.ts +0 -2
- package/dist/index.js +174 -223
- package/dist/shadcn-compat.css +80 -0
- package/dist/styles.css +1 -1
- package/dist/utils/stateMachine.d.ts +4 -4
- package/package.json +12 -10
- package/tailwind.preset.cjs +32 -3
- /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
|
|
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,
|
|
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":
|
|
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),
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
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: "
|
|
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
|
-
|
|
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: "
|
|
19
|
-
|
|
20
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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,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-
|
|
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 {
|
|
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: "
|
|
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(
|
|
27
|
-
|
|
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-
|
|
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<
|
|
4
|
+
export declare const SortableItem: React.ForwardRefExoticComponent<SortableItemProps & React.RefAttributes<HTMLLIElement>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
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 (
|
|
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
|
-
|
|
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,
|
|
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",
|