@flikk/ui 1.0.0-beta.23 → 1.0.0-beta.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
- package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
- package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
- package/dist/components/core/Accordion/Accordion.theme.js +2 -2
- package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
- package/dist/components/core/Alert/Alert.js +2 -1
- package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/core/Badge/Badge.js +6 -17
- package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
- package/dist/components/core/Calendar/Calendar.js +1 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +4 -2
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
- package/dist/components/core/Carousel/CarouselBody.js +6 -1
- package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
- package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
- package/dist/components/core/DragDrop/DragDrop.js +170 -17
- package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +2 -0
- package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
- package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
- package/dist/components/core/Drawer/DrawerTitle.js +2 -2
- package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
- package/dist/components/core/Empty/Empty.theme.js +1 -1
- package/dist/components/core/Link/Link.js +1 -1
- package/dist/components/core/Masonry/Masonry.js +7 -2
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +3 -3
- package/dist/components/core/Modal/Modal.types.d.ts +2 -0
- package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
- package/dist/components/core/Modal/ModalTitle.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +13 -8
- package/dist/components/core/Pagination/Pagination.js +1 -1
- package/dist/components/core/Popover/PopoverBody.js +3 -27
- package/dist/components/core/Popover/PopoverTrigger.js +1 -1
- package/dist/components/core/Progress/Progress.js +4 -4
- package/dist/components/core/Rating/Rating.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
- package/dist/components/core/Segmented/Segmented.js +6 -6
- package/dist/components/core/Segmented/SegmentedItem.js +28 -2
- package/dist/components/core/Skeleton/Skeleton.js +2 -2
- package/dist/components/core/Sortable/Sortable.d.ts +1 -1
- package/dist/components/core/Sortable/Sortable.js +20 -4
- package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
- package/dist/components/core/Tabs/Tabs.js +6 -4
- package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
- package/dist/components/core/Tabs/TabsBody.js +3 -3
- package/dist/components/core/Tabs/TabsList.js +19 -21
- package/dist/components/core/Tabs/TabsTrigger.js +47 -28
- package/dist/components/core/Toast/Toast.js +1 -1
- package/dist/components/core/Toast/ToastProvider.js +3 -2
- package/dist/components/core/Tooltip/Tooltip.js +25 -34
- package/dist/components/core/Tree/Tree.js +178 -8
- package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
- package/dist/components/data-display/Table/Table.js +2 -2
- package/dist/components/data-display/Table/Table.types.d.ts +2 -0
- package/dist/components/data-display/Table/TableFilter.js +21 -18
- package/dist/components/data-display/Timeline/Timeline.animations.d.ts +0 -7
- package/dist/components/data-display/Timeline/Timeline.animations.js +1 -27
- package/dist/components/data-display/Timeline/Timeline.js +3 -4
- package/dist/components/data-display/Timeline/Timeline.types.d.ts +0 -13
- package/dist/components/data-display/Timeline/TimelineContent.js +3 -7
- package/dist/components/data-display/Timeline/TimelineItem.js +4 -17
- package/dist/components/data-display/Timeline/index.d.ts +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
- package/dist/components/effects/Overlay/Overlay.js +4 -9
- package/dist/components/effects/PageTransition/PageTransition.js +17 -6
- package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
- package/dist/components/effects/index.d.ts +7 -0
- package/dist/components/effects/index.js +3 -0
- package/dist/components/forms/Checkbox/Checkbox.js +11 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
- package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
- package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
- package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
- package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
- package/dist/components/forms/ColorPicker/index.d.ts +1 -1
- package/dist/components/forms/Combobox/Combobox.js +20 -45
- package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
- package/dist/components/forms/CronInput/CronInput.js +10 -4
- package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
- package/dist/components/forms/DatePicker/DatePicker.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
- package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +14 -10
- package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
- package/dist/components/forms/FormLabel/FormLabel.js +2 -2
- package/dist/components/forms/Input/Input.js +5 -3
- package/dist/components/forms/Input/Input.types.d.ts +2 -0
- package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
- package/dist/components/forms/InputAddress/InputAddress.js +8 -9
- package/dist/components/forms/InputCounter/InputCounter.js +25 -18
- package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
- package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
- package/dist/components/forms/InputOTP/InputOTP.js +26 -17
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
- package/dist/components/forms/InputTag/InputTag.js +12 -39
- package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
- package/dist/components/forms/Mention/Mention.js +13 -6
- package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
- package/dist/components/forms/Radio/Radio.js +12 -2
- package/dist/components/forms/Radio/Radio.theme.js +5 -1
- package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
- package/dist/components/forms/Radio/RadioGroup.js +8 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
- package/dist/components/forms/Select/Select.d.ts +2 -0
- package/dist/components/forms/Select/Select.js +27 -57
- package/dist/components/forms/Select/Select.types.d.ts +14 -0
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
- package/dist/components/forms/Slider/Slider.js +18 -4
- package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
- package/dist/components/forms/Switch/Switch.js +12 -19
- package/dist/components/forms/Switch/Switch.theme.js +12 -2
- package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
- package/dist/components/forms/Textarea/Textarea.js +8 -3
- package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
- package/dist/components/forms/TimePicker/TimePicker.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
- package/dist/components/forms/TimePicker/TimePickerContent.js +2 -29
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
- package/dist/components/forms/index.js +1 -1
- package/dist/components/layout/index.js +4 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useClickOutside.d.ts +10 -4
- package/dist/hooks/useClickOutside.js +44 -24
- package/dist/hooks/useDarkMode.d.ts +1 -0
- package/dist/hooks/useDarkMode.js +42 -0
- package/dist/hooks/useFormStateMachine.d.ts +1 -1
- package/dist/hooks/useNetworkStatus.js +1 -1
- package/dist/hooks/useOverlay.js +4 -0
- package/dist/hooks/useScaleBackground.js +5 -2
- package/dist/hooks/useSelectPortal.js +14 -5
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -57
- package/dist/shadcn-compat.css +80 -0
- package/dist/styles.css +1 -1
- package/dist/utils/stateMachine.d.ts +4 -4
- package/package.json +11 -10
- package/tailwind.preset.cjs +32 -3
- /package/{src/theme-plugin.css → dist/theme.css} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Timeline } from "./Timeline";
|
|
2
|
-
import { TimelineProps, TimelineItemProps, TimelineMarkerProps, TimelineContentProps, TimelineContextValue, TimelineItemContextValue, TimelineThemeOverrides, TimelineLayout,
|
|
2
|
+
import { TimelineProps, TimelineItemProps, TimelineMarkerProps, TimelineContentProps, TimelineContextValue, TimelineItemContextValue, TimelineThemeOverrides, TimelineLayout, TimelineItemStatus } from "./Timeline.types";
|
|
3
3
|
export { Timeline };
|
|
4
|
-
export type { TimelineProps, TimelineItemProps, TimelineMarkerProps, TimelineContentProps, TimelineContextValue, TimelineItemContextValue, TimelineThemeOverrides, TimelineLayout,
|
|
4
|
+
export type { TimelineProps, TimelineItemProps, TimelineMarkerProps, TimelineContentProps, TimelineContextValue, TimelineItemContextValue, TimelineThemeOverrides, TimelineLayout, TimelineItemStatus, };
|
|
@@ -204,13 +204,9 @@ const CustomCursorFollow = React.forwardRef(({ children, sideOffset = 15, align
|
|
|
204
204
|
const { cursorPos, isActive, cursorRef } = useCustomCursor();
|
|
205
205
|
const cursorFollowRef = React.useRef(null);
|
|
206
206
|
const shouldReduceMotion = useReducedMotion();
|
|
207
|
-
// Select random color for user variant if not provided (stable across renders)
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
return color;
|
|
211
|
-
const randomIndex = Math.floor(Math.random() * userVariantColors.length);
|
|
212
|
-
return userVariantColors[randomIndex];
|
|
213
|
-
}, [color]);
|
|
207
|
+
// Select random color for user variant if not provided (stable across renders via ref)
|
|
208
|
+
const randomColorRef = React.useRef(userVariantColors[Math.floor(Math.random() * userVariantColors.length)]);
|
|
209
|
+
const resolvedColor = color !== null && color !== void 0 ? color : randomColorRef.current;
|
|
214
210
|
React.useImperativeHandle(ref, () => cursorFollowRef.current);
|
|
215
211
|
const x = useMotionValue(0);
|
|
216
212
|
const y = useMotionValue(0);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useRef, useCallback, useEffect
|
|
2
|
+
import React__default, { useRef, useCallback, useEffect } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
|
|
5
5
|
import { cn } from '../../../utils/cn.js';
|
|
6
6
|
import { useOverlay, lockScroll, unlockScroll } from '../../../hooks/useOverlay.js';
|
|
7
7
|
import { useFocusTrap } from '../../../hooks/useFocusTrap.js';
|
|
8
8
|
import { useIsClient } from '../../../hooks/useIsClient.js';
|
|
9
|
+
import { useDarkMode } from '../../../hooks/useDarkMode.js';
|
|
9
10
|
import { overlayTheme, overlayPositionVariants } from './Overlay.theme.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -138,14 +139,8 @@ const Overlay = React__default.forwardRef(({ isOpen, onClose, closeOnOverlayClic
|
|
|
138
139
|
};
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
// Detect dark mode
|
|
142
|
-
const isDarkMode =
|
|
143
|
-
if (!isClient)
|
|
144
|
-
return false;
|
|
145
|
-
return (document.documentElement.classList.contains("dark") ||
|
|
146
|
-
document.body.classList.contains("dark") ||
|
|
147
|
-
document.querySelector(".dark") !== null);
|
|
148
|
-
}, [isClient]);
|
|
142
|
+
// Detect dark mode reactively via a shared MutationObserver-backed hook (SSR-safe)
|
|
143
|
+
const isDarkMode = useDarkMode();
|
|
149
144
|
// Determine portal target
|
|
150
145
|
const target = portalTarget !== undefined
|
|
151
146
|
? portalTarget
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
3
|
import { motion } from 'motion/react';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
5
|
import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } from './PageTransition.animations.js';
|
|
@@ -7,7 +7,14 @@ import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } f
|
|
|
7
7
|
const PageTransition = ({ children, logo, type = "slide", direction = "up", duration = 0.8, delay = 0.8, color = "bg-[var(--color-primary)]", className, parallax = true, onStart, onComplete, }) => {
|
|
8
8
|
const [phase, setPhase] = useState("covered");
|
|
9
9
|
const [completedPanels, setCompletedPanels] = useState(0);
|
|
10
|
+
// Stable refs for callbacks to avoid stale closures without re-running the effect
|
|
11
|
+
const onStartRef = useRef(onStart);
|
|
12
|
+
const onCompleteRef = useRef(onComplete);
|
|
13
|
+
useEffect(() => { onStartRef.current = onStart; }, [onStart]);
|
|
14
|
+
useEffect(() => { onCompleteRef.current = onComplete; }, [onComplete]);
|
|
10
15
|
useEffect(() => {
|
|
16
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
17
|
+
return;
|
|
11
18
|
let cancelled = false;
|
|
12
19
|
const loadPromise = new Promise((resolve) => {
|
|
13
20
|
if (document.readyState === "complete") {
|
|
@@ -24,8 +31,9 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
|
|
|
24
31
|
if (!cancelled) {
|
|
25
32
|
setPhase("exit");
|
|
26
33
|
setTimeout(() => {
|
|
34
|
+
var _a;
|
|
27
35
|
if (!cancelled)
|
|
28
|
-
|
|
36
|
+
(_a = onStartRef.current) === null || _a === void 0 ? void 0 : _a.call(onStartRef);
|
|
29
37
|
}, 250);
|
|
30
38
|
}
|
|
31
39
|
});
|
|
@@ -34,10 +42,11 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
|
|
|
34
42
|
};
|
|
35
43
|
}, [delay]);
|
|
36
44
|
useEffect(() => {
|
|
45
|
+
var _a;
|
|
37
46
|
if (type === "split" && completedPanels === 2) {
|
|
38
|
-
|
|
47
|
+
(_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
|
|
39
48
|
}
|
|
40
|
-
}, [completedPanels, type
|
|
49
|
+
}, [completedPanels, type]);
|
|
41
50
|
if (type === "split") {
|
|
42
51
|
const axis = direction === "up" || direction === "down" ? "h" : "v";
|
|
43
52
|
const splitVars = buildSplitVariants(axis, duration);
|
|
@@ -58,13 +67,15 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
|
|
|
58
67
|
if (type === "slide" && parallax) {
|
|
59
68
|
const logoVariants = buildLogoParallaxVariants(direction, duration);
|
|
60
69
|
return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
|
|
70
|
+
var _a;
|
|
61
71
|
if (def === "exit")
|
|
62
|
-
|
|
72
|
+
(_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
|
|
63
73
|
}, children: logo && (jsx(motion.div, { className: "relative z-10 select-none", variants: logoVariants, initial: "covered", animate: phase, children: logo })) })] }));
|
|
64
74
|
}
|
|
65
75
|
return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
|
|
76
|
+
var _a;
|
|
66
77
|
if (def === "exit")
|
|
67
|
-
|
|
78
|
+
(_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
|
|
68
79
|
}, children: logo && jsx("div", { className: "relative z-10 select-none", children: logo }) })] }));
|
|
69
80
|
};
|
|
70
81
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useRef, useEffect } from 'react';
|
|
3
|
+
import { useReducedMotion } from 'motion/react';
|
|
3
4
|
import * as THREE from 'three';
|
|
4
5
|
import { cn } from '../../../utils/cn.js';
|
|
5
6
|
import { variationMap } from './SpotlightBorder.types.js';
|
|
@@ -21,12 +22,10 @@ import { fragmentShader, vertexShader } from './SpotlightBorder.shaders.js';
|
|
|
21
22
|
* ```
|
|
22
23
|
*/
|
|
23
24
|
const SpotlightBorder = ({ children, variation = "rounded-rect", rounded, borderWidth = 2, color = "#ffffff", spotlightSize = 0.4, spotlightEdge = 0.5, glowExtent = 20, glowIntensity = 0.6, tiltIntensity = 5, shouldAnimate = true, className, style, ...props }) => {
|
|
24
|
-
var _a;
|
|
25
25
|
const outerRef = useRef(null);
|
|
26
26
|
const containerRef = useRef(null);
|
|
27
|
-
//
|
|
28
|
-
const prefersReducedMotion =
|
|
29
|
-
((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)").matches);
|
|
27
|
+
// useReducedMotion is SSR-safe (returns null on server, actual value after hydration)
|
|
28
|
+
const prefersReducedMotion = useReducedMotion();
|
|
30
29
|
const isAnimating = shouldAnimate && !prefersReducedMotion;
|
|
31
30
|
// Refs to store THREE.js objects across effects
|
|
32
31
|
const rendererRef = useRef(null);
|
|
@@ -24,6 +24,8 @@ export { NoiseOverlay } from "./NoiseOverlay";
|
|
|
24
24
|
export type { NoiseOverlayProps, NoiseBlendMode } from "./NoiseOverlay";
|
|
25
25
|
export { Spotlight } from "./Spotlight";
|
|
26
26
|
export type { SpotlightProps } from "./Spotlight";
|
|
27
|
+
export { SpotlightBorder } from "./SpotlightBorder";
|
|
28
|
+
export type { SpotlightBorderProps, SpotlightBorderVariation, } from "./SpotlightBorder";
|
|
27
29
|
export { ParallaxSection } from "./ParallaxSection";
|
|
28
30
|
export type { ParallaxSectionProps } from "./ParallaxSection";
|
|
29
31
|
export { ScrollReveal } from "./ScrollReveal";
|
|
@@ -36,3 +38,8 @@ export { InteractiveCharacters } from "./InteractiveCharacters";
|
|
|
36
38
|
export type { InteractiveCharactersProps, CharacterState, CharacterSize, } from "./InteractiveCharacters";
|
|
37
39
|
export { InsetCircleButton, InsetPill } from "./Neumorphic";
|
|
38
40
|
export type { InsetCircleButtonProps, InsetPillProps, } from "./Neumorphic";
|
|
41
|
+
export { AIOrb } from "./AIOrb";
|
|
42
|
+
export type { AIOrbProps, OrbState } from "./AIOrb";
|
|
43
|
+
export type { DotSphereProps, LiquidProps, AuraProps } from "./AIOrb";
|
|
44
|
+
export { Particles } from "./Particles";
|
|
45
|
+
export type { ParticlesProps } from "./Particles";
|
|
@@ -11,6 +11,7 @@ export { GridPattern } from './GridPattern/GridPattern.js';
|
|
|
11
11
|
export { DotPattern } from './DotPattern/DotPattern.js';
|
|
12
12
|
export { NoiseOverlay } from './NoiseOverlay/NoiseOverlay.js';
|
|
13
13
|
export { Spotlight } from './Spotlight/Spotlight.js';
|
|
14
|
+
export { SpotlightBorder } from './SpotlightBorder/SpotlightBorder.js';
|
|
14
15
|
export { ParallaxSection } from './ParallaxSection/ParallaxSection.js';
|
|
15
16
|
export { ScrollReveal } from './ScrollReveal/ScrollReveal.js';
|
|
16
17
|
export { StickyScroll } from './StickyScroll/StickyScroll.js';
|
|
@@ -18,3 +19,5 @@ export { PageTransition } from './PageTransition/PageTransition.js';
|
|
|
18
19
|
export { InteractiveCharacters } from './InteractiveCharacters/InteractiveCharacters.js';
|
|
19
20
|
export { InsetCircleButton } from './Neumorphic/InsetCircleButton.js';
|
|
20
21
|
export { InsetPill } from './Neumorphic/InsetPill.js';
|
|
22
|
+
export { AIOrb } from './AIOrb/AIOrb.js';
|
|
23
|
+
export { Particles } from './Particles/Particles.js';
|
|
@@ -22,6 +22,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
|
|
|
22
22
|
const size = (_c = groupContext === null || groupContext === void 0 ? void 0 : groupContext.size) !== null && _c !== void 0 ? _c : sizeProp;
|
|
23
23
|
const state = stateProp === "disabled" ? "disabled" : ((_d = groupContext === null || groupContext === void 0 ? void 0 : groupContext.state) !== null && _d !== void 0 ? _d : stateProp);
|
|
24
24
|
const isDisabled = state === "disabled";
|
|
25
|
+
const isInvalid = state === "invalid";
|
|
25
26
|
// Determine checked state
|
|
26
27
|
const isInGroup = groupContext !== undefined;
|
|
27
28
|
const [internalChecked, setInternalChecked] = useState(false);
|
|
@@ -42,6 +43,15 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
|
|
|
42
43
|
inputRef.current.indeterminate = indeterminate;
|
|
43
44
|
}
|
|
44
45
|
}, [indeterminate]);
|
|
46
|
+
// Dev-only warning when no accessible name is provided
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
49
|
+
if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) {
|
|
50
|
+
console.warn('[Checkbox]: No accessible name provided. Add a `label` prop, `aria-label`, or `aria-labelledby`.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
54
|
+
}, []);
|
|
45
55
|
const handleChange = (e) => {
|
|
46
56
|
if (isDisabled)
|
|
47
57
|
return;
|
|
@@ -59,7 +69,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
|
|
|
59
69
|
ref(node);
|
|
60
70
|
else if (ref)
|
|
61
71
|
ref.current = node;
|
|
62
|
-
}, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
|
|
72
|
+
}, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "aria-invalid": isInvalid || undefined, "data-state": isDisabled ? "disabled" : isInvalid ? "invalid" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
|
|
63
73
|
});
|
|
64
74
|
CheckboxComponent.displayName = "Checkbox";
|
|
65
75
|
const Checkbox = Object.assign(CheckboxComponent, {
|
|
@@ -24,12 +24,15 @@ const checkboxTheme = {
|
|
|
24
24
|
"data-[state=disabled]:border-[var(--color-border)] data-[state=disabled]:bg-[var(--color-background-disabled)] data-[state=disabled]:cursor-not-allowed " +
|
|
25
25
|
// Disabled state - dark
|
|
26
26
|
"dark:disabled:bg-[var(--color-neutral-800)] dark:disabled:border-[var(--color-neutral-600)] " +
|
|
27
|
-
"dark:data-[state=disabled]:bg-[var(--color-neutral-700)] dark:data-[state=disabled]:border-[var(--color-neutral-600)]"
|
|
27
|
+
"dark:data-[state=disabled]:bg-[var(--color-neutral-700)] dark:data-[state=disabled]:border-[var(--color-neutral-600)] " +
|
|
28
|
+
// Invalid state
|
|
29
|
+
"data-[state=invalid]:border-[var(--color-danger)] data-[state=invalid]:focus-visible:outline-[var(--color-danger)]",
|
|
28
30
|
// Description text - light and dark
|
|
29
31
|
descriptionStyle: "text-sm mt-0.5 " + "text-[var(--color-text-secondary)]/80 " + " ",
|
|
30
32
|
sizes: {
|
|
31
33
|
sm: "size-5 mt-0.25",
|
|
32
34
|
md: "size-6",
|
|
35
|
+
lg: "size-7",
|
|
33
36
|
},
|
|
34
37
|
// Group styles
|
|
35
38
|
groupStyle: "flex flex-col gap-1.5",
|
|
@@ -2,11 +2,11 @@ import React from 'react';
|
|
|
2
2
|
/**
|
|
3
3
|
* State variants for the checkbox component
|
|
4
4
|
*/
|
|
5
|
-
export type CheckboxState = 'default' | 'disabled';
|
|
5
|
+
export type CheckboxState = 'default' | 'disabled' | 'invalid';
|
|
6
6
|
/**
|
|
7
7
|
* Size variants for the checkbox component
|
|
8
8
|
*/
|
|
9
|
-
export type CheckboxSize = 'sm' | 'md';
|
|
9
|
+
export type CheckboxSize = 'sm' | 'md' | 'lg';
|
|
10
10
|
/**
|
|
11
11
|
* Orientation for Checkbox.Group layout
|
|
12
12
|
*/
|
|
@@ -14,11 +14,9 @@ export type CheckboxGroupOrientation = 'vertical' | 'horizontal';
|
|
|
14
14
|
/**
|
|
15
15
|
* Checkbox component props
|
|
16
16
|
*/
|
|
17
|
-
export interface CheckboxProps extends Omit<React.
|
|
17
|
+
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size'> {
|
|
18
18
|
id?: string;
|
|
19
|
-
name?: string;
|
|
20
19
|
value: string;
|
|
21
|
-
checked?: boolean;
|
|
22
20
|
indeterminate?: boolean;
|
|
23
21
|
onChange?: (checked: boolean, value: string) => void;
|
|
24
22
|
label?: React.ReactNode;
|
|
@@ -40,7 +38,11 @@ export interface CheckboxGroupProps extends Omit<React.HTMLAttributes<HTMLFieldS
|
|
|
40
38
|
orientation?: CheckboxGroupOrientation;
|
|
41
39
|
label?: React.ReactNode;
|
|
42
40
|
description?: React.ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Use `state='invalid'` with `errorMessage` instead.
|
|
43
|
+
*/
|
|
43
44
|
error?: React.ReactNode;
|
|
45
|
+
errorMessage?: React.ReactNode;
|
|
44
46
|
required?: boolean;
|
|
45
47
|
className?: string;
|
|
46
48
|
}
|
|
@@ -5,7 +5,12 @@ import { checkboxTheme } from './Checkbox.theme.js';
|
|
|
5
5
|
import { formLabelTheme } from '../FormLabel/FormLabel.theme.js';
|
|
6
6
|
import { CheckboxGroupContext } from './CheckboxContext.js';
|
|
7
7
|
|
|
8
|
-
const CheckboxGroup = React__default.forwardRef(({ value: controlledValue, defaultValue, onChange, name, size = "md", state = "default", orientation = "vertical", label, description, error, required, className, children, ...props }, ref) => {
|
|
8
|
+
const CheckboxGroup = React__default.forwardRef(({ value: controlledValue, defaultValue, onChange, name, size = "md", state: stateProp = "default", orientation = "vertical", label, description, error, errorMessage, required, className, children, ...props }, ref) => {
|
|
9
|
+
// Prefer errorMessage; fall back to deprecated error prop
|
|
10
|
+
const resolvedError = errorMessage !== null && errorMessage !== void 0 ? errorMessage : error;
|
|
11
|
+
// If error (deprecated) is provided and state not explicitly set to something else,
|
|
12
|
+
// implicitly treat the group as invalid
|
|
13
|
+
const state = resolvedError && stateProp === "default" ? "invalid" : stateProp;
|
|
9
14
|
const autoId = useId();
|
|
10
15
|
const groupName = name !== null && name !== void 0 ? name : `checkbox-group-${autoId}`;
|
|
11
16
|
const labelId = `${autoId}-label`;
|
|
@@ -31,9 +36,9 @@ const CheckboxGroup = React__default.forwardRef(({ value: controlledValue, defau
|
|
|
31
36
|
size,
|
|
32
37
|
state,
|
|
33
38
|
}), [groupName, currentValue, size, state]);
|
|
34
|
-
const labelState =
|
|
35
|
-
return (jsx(CheckboxGroupContext.Provider, { value: contextValue, children: jsxs("fieldset", { ref: ref, role: "group", "aria-labelledby": label ? labelId : undefined, "aria-describedby": description ? descriptionId : undefined, "aria-required": required || undefined, "aria-invalid":
|
|
36
|
-
checkboxTheme.groupHorizontalStyle), children: children }),
|
|
39
|
+
const labelState = state === "invalid" ? "invalid" : state === "disabled" ? "disabled" : "default";
|
|
40
|
+
return (jsx(CheckboxGroupContext.Provider, { value: contextValue, children: jsxs("fieldset", { ref: ref, role: "group", "aria-labelledby": label ? labelId : undefined, "aria-describedby": description ? descriptionId : undefined, "aria-required": required || undefined, "aria-invalid": resolvedError ? true : undefined, "aria-errormessage": resolvedError ? errorId : undefined, className: cn("border-0 p-0 m-0", checkboxTheme.groupStyle, className), ...props, children: [label && (typeof label === 'string' ? (jsxs("legend", { id: labelId, className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[labelState]), children: [label, required && (jsx("span", { className: formLabelTheme.requiredIndicatorStyle, children: "*" }))] })) : (label)), description && (typeof description === 'string' ? (jsx("p", { id: descriptionId, className: checkboxTheme.groupDescriptionStyle, children: description })) : (description)), jsx("div", { className: cn(checkboxTheme.groupItemsStyle, orientation === "horizontal" &&
|
|
41
|
+
checkboxTheme.groupHorizontalStyle), children: children }), resolvedError && (typeof resolvedError === 'string' ? (jsx("p", { id: errorId, role: "alert", className: checkboxTheme.groupErrorStyle, children: resolvedError })) : (resolvedError))] }) }));
|
|
37
42
|
});
|
|
38
43
|
CheckboxGroup.displayName = "Checkbox.Group";
|
|
39
44
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useMemo, useState, useCallback,
|
|
2
|
+
import React__default, { useRef, useMemo, useState, useCallback, useEffect } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
5
5
|
import { colorPickerTheme } from './ColorPicker.theme.js';
|
|
@@ -31,7 +31,10 @@ const DEFAULT_GRADIENT = {
|
|
|
31
31
|
* ColorPicker Root Component
|
|
32
32
|
* Follows Flikkui compound component pattern (similar to Accordion, Tabs, Popover)
|
|
33
33
|
*/
|
|
34
|
-
const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000000', onChange, format: initialFormat = 'hex', showAlpha = true, showEyeDropper = true, presetColors, savedColors: savedColorsProp, onSavedColorsChange, defaultMode = 'solid', mode: modeProp, onModeChange, gradientValue: gradientProp, onGradientChange, size = 'md', state = 'default', disabled = false, label, helperText, required = false, theme, className, children, ...props }, ref) => {
|
|
34
|
+
const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000000', onChange, format: initialFormat = 'hex', showAlpha = true, showEyeDropper = true, presetColors, savedColors: savedColorsProp, onSavedColorsChange, defaultMode = 'solid', mode: modeProp, onModeChange, gradientValue: gradientProp, onGradientChange, size = 'md', state = 'default', disabled = false, label, helperText, required = false, theme, className, id, children, ...props }, ref) => {
|
|
35
|
+
const generatedId = React__default.useId();
|
|
36
|
+
const componentId = id || generatedId;
|
|
37
|
+
const stopIdRef = useRef(0);
|
|
35
38
|
// Parse initial color value
|
|
36
39
|
const initialColor = useMemo(() => {
|
|
37
40
|
const parsed = parseColor(value || defaultValue);
|
|
@@ -71,7 +74,7 @@ const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000
|
|
|
71
74
|
}, [gradient, setGradient]);
|
|
72
75
|
const addGradientStop = useCallback((position) => {
|
|
73
76
|
const newStop = {
|
|
74
|
-
id: `stop-${
|
|
77
|
+
id: `stop-${++stopIdRef.current}`,
|
|
75
78
|
color: formatHex(color.h, color.s, color.l),
|
|
76
79
|
position: Math.round(position),
|
|
77
80
|
};
|
|
@@ -252,7 +255,7 @@ const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000
|
|
|
252
255
|
addSavedColor,
|
|
253
256
|
removeSavedColor,
|
|
254
257
|
]);
|
|
255
|
-
return (jsx(ColorPickerContext.Provider, { value: contextValue, children: jsxs("div", { ref: ref, className: cn(mergedTheme.baseStyle, className), ...props, children: [label && (jsx(FormLabel, { htmlFor:
|
|
258
|
+
return (jsx(ColorPickerContext.Provider, { value: contextValue, children: jsxs("div", { ref: ref, className: cn(mergedTheme.baseStyle, className), ...props, children: [label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: componentId, required: required, state: state, children: label })) : (label)), children, helperText && (typeof helperText === 'string' ? (jsx("div", { id: `${componentId}-helper`, className: cn('mt-1.5 text-sm text-[var(--color-text-secondary)]', state === 'invalid' && 'text-[var(--color-danger)]'), children: helperText })) : (helperText))] }) }));
|
|
256
259
|
});
|
|
257
260
|
ColorPickerRoot.displayName = 'ColorPicker';
|
|
258
261
|
/**
|
|
@@ -83,11 +83,11 @@ export interface ColorPickerProps extends Omit<React.HTMLAttributes<HTMLDivEleme
|
|
|
83
83
|
/**
|
|
84
84
|
* Label for the color picker
|
|
85
85
|
*/
|
|
86
|
-
label?:
|
|
86
|
+
label?: React.ReactNode;
|
|
87
87
|
/**
|
|
88
88
|
* Helper text displayed below the picker
|
|
89
89
|
*/
|
|
90
|
-
helperText?:
|
|
90
|
+
helperText?: React.ReactNode;
|
|
91
91
|
/**
|
|
92
92
|
* Whether the picker is required
|
|
93
93
|
* @default false
|
|
@@ -66,9 +66,11 @@ const ColorPickerBody = ({ children, className, portal = true, animation = true,
|
|
|
66
66
|
damping: 30,
|
|
67
67
|
}, "data-state": isOpen ? 'open' : 'closed', ...domProps, children: children })) }));
|
|
68
68
|
// Use portal if specified (same as Dropdown)
|
|
69
|
-
|
|
70
|
-
?
|
|
71
|
-
:
|
|
69
|
+
if (portal) {
|
|
70
|
+
const target = isClient ? document.body : null;
|
|
71
|
+
return target ? createPortal(content, target) : null;
|
|
72
|
+
}
|
|
73
|
+
return content;
|
|
72
74
|
};
|
|
73
75
|
ColorPickerBody.displayName = 'ColorPicker.Body';
|
|
74
76
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { EyeDropperIcon } from '@heroicons/react/20/solid';
|
|
3
|
-
import { useContext } from 'react';
|
|
3
|
+
import { useContext, useMemo } from 'react';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
5
|
import { Button } from '../../core/Button/Button.js';
|
|
6
6
|
import { ColorPickerContext } from './ColorPickerContext.js';
|
|
7
7
|
import { parseColor } from './colorUtils.js';
|
|
8
8
|
|
|
9
|
-
// Check if browser supports EyeDropper API
|
|
10
|
-
const supportsEyeDropper = typeof window !== 'undefined' && 'EyeDropper' in window;
|
|
11
9
|
/**
|
|
12
10
|
* ColorPickerEyeDropper - Eyedropper tool to pick colors from screen
|
|
13
11
|
* Uses browser EyeDropper API (if available)
|
|
14
12
|
*/
|
|
15
13
|
const ColorPickerEyeDropper = ({ className, ...props }) => {
|
|
16
14
|
const context = useContext(ColorPickerContext);
|
|
15
|
+
// Check if browser supports EyeDropper API (inside component to avoid SSR module-caching issues)
|
|
16
|
+
const supportsEyeDropper = useMemo(() => typeof window !== 'undefined' && 'EyeDropper' in window, []);
|
|
17
17
|
if (!context) {
|
|
18
18
|
throw new Error('ColorPickerEyeDropper must be used within a ColorPicker');
|
|
19
19
|
}
|
|
@@ -113,14 +113,14 @@ const ColorPickerGradient = ({ className, ...props }) => {
|
|
|
113
113
|
setDragStopId(null);
|
|
114
114
|
}, []);
|
|
115
115
|
useEffect(() => {
|
|
116
|
-
if (dragStopId)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
116
|
+
if (!dragStopId)
|
|
117
|
+
return; // no listeners to add; no cleanup needed
|
|
118
|
+
window.addEventListener('pointermove', handlePointerMove);
|
|
119
|
+
window.addEventListener('pointerup', handlePointerUp);
|
|
120
|
+
return () => {
|
|
121
|
+
window.removeEventListener('pointermove', handlePointerMove);
|
|
122
|
+
window.removeEventListener('pointerup', handlePointerUp);
|
|
123
|
+
};
|
|
124
124
|
}, [dragStopId, handlePointerMove, handlePointerUp]);
|
|
125
125
|
// --- Selected stop color input editing ---
|
|
126
126
|
const [colorInput, setColorInput] = useState((_c = selectedStop === null || selectedStop === void 0 ? void 0 : selectedStop.color) !== null && _c !== void 0 ? _c : '');
|
|
@@ -20,7 +20,7 @@ const ColorPickerInput = ({ className, ...props }) => {
|
|
|
20
20
|
if (!isInvalid) {
|
|
21
21
|
setInputValue(getFormattedValue());
|
|
22
22
|
}
|
|
23
|
-
}, [color, format]);
|
|
23
|
+
}, [color, format, getFormattedValue, isInvalid]);
|
|
24
24
|
const handleCopy = async () => {
|
|
25
25
|
try {
|
|
26
26
|
await navigator.clipboard.writeText(getFormattedValue());
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Compound component following Flikkui patterns
|
|
4
4
|
*/
|
|
5
5
|
export { ColorPicker } from './ColorPicker';
|
|
6
|
-
export { useColorPicker
|
|
6
|
+
export { useColorPicker } from './ColorPickerContext';
|
|
7
7
|
export { ColorPickerTrigger } from './ColorPickerTrigger';
|
|
8
8
|
export { ColorPickerBody } from './ColorPickerBody';
|
|
9
9
|
export { ColorPicker2DCanvas } from './ColorPicker2DCanvas';
|
|
@@ -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, { useId, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { AnimatePresence, motion } from 'motion/react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
@@ -11,43 +11,16 @@ import { cn } from '../../../utils/cn.js';
|
|
|
11
11
|
import { SelectProvider, useSelectContext } from '../Select/Select.js';
|
|
12
12
|
import '../Select/Select.theme.js';
|
|
13
13
|
import { useIsClient } from '../../../hooks/useIsClient.js';
|
|
14
|
+
import { useDarkMode } from '../../../hooks/useDarkMode.js';
|
|
14
15
|
import { useSelectState } from '../Select/useSelectState.js';
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
* Hook to detect dark mode state from document
|
|
18
|
-
*/
|
|
19
|
-
const useDarkMode = () => {
|
|
20
|
-
const [isDark, setIsDark] = React__default.useState(false);
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (typeof document === "undefined")
|
|
23
|
-
return;
|
|
24
|
-
const checkDarkMode = () => {
|
|
25
|
-
const hasDarkClass = document.documentElement.classList.contains("dark") ||
|
|
26
|
-
document.body.classList.contains("dark") ||
|
|
27
|
-
document.querySelector(".dark") !== null;
|
|
28
|
-
setIsDark(hasDarkClass);
|
|
29
|
-
};
|
|
30
|
-
checkDarkMode();
|
|
31
|
-
const observer = new MutationObserver(checkDarkMode);
|
|
32
|
-
observer.observe(document.documentElement, {
|
|
33
|
-
attributes: true,
|
|
34
|
-
attributeFilter: ["class"],
|
|
35
|
-
});
|
|
36
|
-
observer.observe(document.body, {
|
|
37
|
-
attributes: true,
|
|
38
|
-
attributeFilter: ["class"],
|
|
39
|
-
});
|
|
40
|
-
return () => observer.disconnect();
|
|
41
|
-
}, []);
|
|
42
|
-
return isDark;
|
|
43
|
-
};
|
|
44
17
|
/**
|
|
45
18
|
* Default filter function for options
|
|
46
19
|
*/
|
|
47
20
|
const defaultFilterFn = (option, inputValue) => {
|
|
48
21
|
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
|
49
22
|
};
|
|
50
|
-
const ComboboxOptions = ({ isOpen, onClose, triggerRef, portal, placement, offset, emptyMessage, className, }) => {
|
|
23
|
+
const ComboboxOptions = ({ isOpen, onClose, triggerRef, portal, placement, offset, emptyMessage, className, listboxId, comboboxId, }) => {
|
|
51
24
|
var _a;
|
|
52
25
|
const { filteredOptions, highlightedIndex, setHighlightedIndex, selectedValue, selectOption, optionsRef, theme, } = useSelectContext();
|
|
53
26
|
const isDarkMode = useDarkMode();
|
|
@@ -105,7 +78,7 @@ const ComboboxOptions = ({ isOpen, onClose, triggerRef, portal, placement, offse
|
|
|
105
78
|
const disabledOptionStyle = theme.disabledOptionStyle || comboboxTheme.disabledOptionStyle;
|
|
106
79
|
const createOptionStyleTheme = theme.createOptionStyle || comboboxTheme.createOptionStyle;
|
|
107
80
|
const emptyMessageStyle = comboboxTheme.emptyMessageStyle;
|
|
108
|
-
const optionsContent = (jsx(AnimatePresence, { mode: "wait", children: isOpen && (jsx(motion.div, { ref: combinedRef, role: "listbox", className: cn(dropdownStyle, className), style: {
|
|
81
|
+
const optionsContent = (jsx(AnimatePresence, { mode: "wait", children: isOpen && (jsx(motion.div, { ref: combinedRef, id: listboxId, role: "listbox", className: cn(dropdownStyle, className), style: {
|
|
109
82
|
...cssVariables,
|
|
110
83
|
overscrollBehavior: "contain",
|
|
111
84
|
...(portal
|
|
@@ -115,17 +88,17 @@ const ComboboxOptions = ({ isOpen, onClose, triggerRef, portal, placement, offse
|
|
|
115
88
|
width: `${position.width}px`,
|
|
116
89
|
}
|
|
117
90
|
: {}),
|
|
118
|
-
}, tabIndex: -1, variants: animations, initial: "initial", animate: "visible", exit: "exit", children: filteredOptions.length === 0 ? (jsx("div", { className: emptyMessageStyle, children: emptyMessage })) : (filteredOptions.map((option, index) => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
91
|
+
}, tabIndex: -1, variants: animations, initial: "initial", animate: "visible", exit: "exit", children: filteredOptions.length === 0 ? (jsx("div", { className: emptyMessageStyle, children: emptyMessage })) : (jsxs(Fragment, { children: [(filteredOptions.length > 100 ? filteredOptions.slice(0, 100) : filteredOptions).map((option, index) => {
|
|
92
|
+
const isSelected = option.value === selectedValue;
|
|
93
|
+
const isHighlighted = index === highlightedIndex;
|
|
94
|
+
const isCreateOption = option.id === "__create__";
|
|
95
|
+
return (jsx("div", { id: comboboxId ? `${comboboxId}-option-${option.id}` : undefined, role: "option", "aria-selected": isSelected, "aria-disabled": option.disabled, "data-option-index": index, className: cn(optionStyle, isSelected && selectedOptionStyle, isHighlighted && highlightedOptionStyle, option.disabled && disabledOptionStyle, isCreateOption && createOptionStyleTheme), onMouseEnter: () => !option.disabled && setHighlightedIndex(index), onClick: () => {
|
|
96
|
+
if (!option.disabled) {
|
|
97
|
+
selectOption(option.value, isCreateOption);
|
|
98
|
+
onClose();
|
|
99
|
+
}
|
|
100
|
+
}, children: isCreateOption ? (jsxs("span", { className: "flex items-center gap-1", children: [jsx(PlusIcon, { className: "size-4 shrink-0", strokeWidth: 2 }), jsx("span", { className: "block truncate", children: option.label })] })) : (jsx("span", { className: "block truncate", children: option.label })) }, option.id));
|
|
101
|
+
}), filteredOptions.length > 100 && (jsxs("div", { className: "px-3 py-2 text-xs text-[var(--color-text-muted)] text-center border-t border-[var(--color-border)]", children: ["Showing 100 of ", filteredOptions.length, " results \u2014 refine your search"] }))] })) })) }));
|
|
129
102
|
if (portal && isClient) {
|
|
130
103
|
return createPortal(jsx("div", { className: isDarkMode ? "dark" : undefined, children: optionsContent }), document.body);
|
|
131
104
|
}
|
|
@@ -153,7 +126,7 @@ const ComboboxOptions = ({ isOpen, onClose, triggerRef, portal, placement, offse
|
|
|
153
126
|
* />
|
|
154
127
|
* ```
|
|
155
128
|
*/
|
|
156
|
-
const ComboboxInner = ({ options, value, onChange, placeholder = "Search...", label, helperText, size = "md", state = "default", clearable = true, iconStart, openOnFocus = true, filterFn = defaultFilterFn, emptyMessage = "No results found", creatable = false, onCreateOption, createLabel, required = false, name, className, wrapperClassName, inputClassName, dropdownClassName, theme: themeOverrides, portal = true, placement = "bottom-start", offset = 8, id, ...props }, ref) => {
|
|
129
|
+
const ComboboxInner = ({ options, value, onChange, placeholder = "Search...", label, helperText, size = "md", state = "default", clearable = true, iconStart, iconEnd, openOnFocus = true, filterFn = defaultFilterFn, emptyMessage = "No results found", creatable = false, onCreateOption, createLabel, required = false, name, className, wrapperClassName, inputClassName, dropdownClassName, theme: themeOverrides, portal = true, placement = "bottom-start", offset = 8, id, ...props }, ref) => {
|
|
157
130
|
var _a;
|
|
158
131
|
// Merge theme
|
|
159
132
|
const theme = React__default.useMemo(() => ({ ...comboboxTheme, ...(themeOverrides || {}) }), [themeOverrides]);
|
|
@@ -385,17 +358,19 @@ const ComboboxInner = ({ options, value, onChange, placeholder = "Search...", la
|
|
|
385
358
|
const showClearButton = clearable && value !== undefined && !isDisabled;
|
|
386
359
|
// Icon padding
|
|
387
360
|
const hasLeftIcon = Boolean(iconStart);
|
|
361
|
+
const hasRightIcon = Boolean(iconEnd);
|
|
388
362
|
const iconStartPadding = hasLeftIcon ? theme.iconPadding.left : "";
|
|
363
|
+
const iconEndPadding = hasRightIcon ? theme.iconPadding.right : "";
|
|
389
364
|
// Create a modified selectState for the provider with our custom filtered options
|
|
390
365
|
const modifiedSelectState = React__default.useMemo(() => ({
|
|
391
366
|
...selectState,
|
|
392
367
|
filteredOptions: customFilteredOptions,
|
|
393
368
|
selectOption: handleSelectOption,
|
|
394
369
|
}), [selectState, customFilteredOptions, handleSelectOption]);
|
|
395
|
-
return (jsx(SelectProvider, { selectState: modifiedSelectState, size: size, componentState: state, theme: theme, searchable: true, enableTypeahead: false, children: jsxs("div", { ref: ref, className: cn("relative", containerClasses, stateClass, wrapperClassName), ...props, children: [label && (jsx(FormLabel, { htmlFor: comboboxId, required: required, children: label })), jsxs("div", { ref: wrapperRef, className: theme.wrapperStyle, children: [jsx("div", { className: cn(inputGroupClasses, stateClasses, className), children: jsxs("div", { className: "relative flex-1", children: [jsx("input", { ref: inputRef, id: comboboxId, type: "text", role: "combobox", "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 &&
|
|
370
|
+
return (jsx(SelectProvider, { selectState: modifiedSelectState, size: size, componentState: state, theme: theme, searchable: true, enableTypeahead: false, children: jsxs("div", { ref: ref, className: cn("relative", containerClasses, stateClass, wrapperClassName), ...props, children: [label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: comboboxId, state: state, required: required, children: label })) : (label)), jsxs("div", { ref: wrapperRef, className: theme.wrapperStyle, children: [jsx("div", { className: cn(inputGroupClasses, stateClasses, className), children: jsxs("div", { className: "relative flex-1", children: [jsx("input", { ref: inputRef, id: comboboxId, type: "text", role: "combobox", "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": listboxId, "aria-activedescendant": highlightedIndex >= 0 &&
|
|
396
371
|
customFilteredOptions[highlightedIndex]
|
|
397
372
|
? `${comboboxId}-option-${customFilteredOptions[highlightedIndex].id}`
|
|
398
|
-
: undefined, "aria-autocomplete": "list", "aria-invalid": isInvalid, autoComplete: "off", className: cn(inputClasses, sizeClasses, iconStartPadding, showClearButton && "pr-8", inputClassName), value: searchValue, onChange: handleInputChange, onFocus: handleInputFocus, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: isDisabled, required: required }), hasLeftIcon && (jsx("div", { className: theme.iconStartStyle, children: iconStart })), showClearButton && (jsx("button", { type: "button", className: theme.clearButtonStyle, onClick: handleClear, "aria-label": "Clear selection", tabIndex: -1, children: jsx(XMarkIcon, { className: theme.clearIconStyle }) }))] }) }), jsx(ComboboxOptions, { isOpen: isOpen, onClose: () => setIsOpen(false), triggerRef: wrapperRef, portal: portal, placement: placement, offset: offset, emptyMessage: emptyMessage, className: dropdownClassName })] }), helperText && (jsx("p", { className: cn(helperTextClasses, helperTextStateClasses), children: helperText })), name && (jsx("input", { type: "hidden", name: name, value: value !== undefined ? String(value) : "" }))] }) }));
|
|
373
|
+
: undefined, "aria-autocomplete": "list", "aria-invalid": isInvalid, "aria-describedby": helperText ? `${comboboxId}-helper` : undefined, autoComplete: "off", className: cn(inputClasses, sizeClasses, iconStartPadding, (hasRightIcon || showClearButton) && iconEndPadding, showClearButton && "pr-8", inputClassName), value: searchValue, onChange: handleInputChange, onFocus: handleInputFocus, onKeyDown: handleKeyDown, placeholder: placeholder, disabled: isDisabled, required: required }), hasLeftIcon && (jsx("div", { className: theme.iconStartStyle, children: iconStart })), showClearButton && (jsx("button", { type: "button", className: theme.clearButtonStyle, onClick: handleClear, "aria-label": "Clear selection", tabIndex: -1, children: jsx(XMarkIcon, { className: theme.clearIconStyle }) })), hasRightIcon && !showClearButton && (jsx("div", { className: theme.iconEndStyle, children: iconEnd }))] }) }), jsx(ComboboxOptions, { isOpen: isOpen, onClose: () => setIsOpen(false), triggerRef: wrapperRef, portal: portal, placement: placement, offset: offset, emptyMessage: emptyMessage, className: dropdownClassName, listboxId: listboxId, comboboxId: comboboxId })] }), helperText && (typeof helperText === 'string' ? (jsx("p", { id: `${comboboxId}-helper`, className: cn(helperTextClasses, helperTextStateClasses), children: helperText })) : (helperText)), name && (jsx("input", { type: "hidden", name: name, value: value !== undefined ? String(value) : "" }))] }) }));
|
|
399
374
|
};
|
|
400
375
|
const ComboboxForwarded = React__default.forwardRef(ComboboxInner);
|
|
401
376
|
ComboboxForwarded.displayName = "Combobox";
|
|
@@ -33,9 +33,9 @@ export interface ComboboxProps<T = string> extends Omit<React.HTMLAttributes<HTM
|
|
|
33
33
|
/** Placeholder text when no value is selected */
|
|
34
34
|
placeholder?: string;
|
|
35
35
|
/** Label text displayed above the input */
|
|
36
|
-
label?:
|
|
36
|
+
label?: React.ReactNode;
|
|
37
37
|
/** Helper text displayed below the input */
|
|
38
|
-
helperText?:
|
|
38
|
+
helperText?: React.ReactNode;
|
|
39
39
|
/** Size variant */
|
|
40
40
|
size?: ComboboxSize;
|
|
41
41
|
/** State variant (controls styling) */
|
|
@@ -44,6 +44,8 @@ export interface ComboboxProps<T = string> extends Omit<React.HTMLAttributes<HTM
|
|
|
44
44
|
clearable?: boolean;
|
|
45
45
|
/** Icon displayed at the start of the input */
|
|
46
46
|
iconStart?: React.ReactNode;
|
|
47
|
+
/** Icon displayed at the end of the input */
|
|
48
|
+
iconEnd?: React.ReactNode;
|
|
47
49
|
/** Whether to open dropdown on focus (default: true) */
|
|
48
50
|
openOnFocus?: boolean;
|
|
49
51
|
/** Custom filter function */
|