@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.
Files changed (157) hide show
  1. package/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
  2. package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
  3. package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
  4. package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
  5. package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
  6. package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
  7. package/dist/components/core/Accordion/Accordion.theme.js +2 -2
  8. package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
  9. package/dist/components/core/Alert/Alert.js +2 -1
  10. package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
  11. package/dist/components/core/Badge/Badge.js +6 -17
  12. package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
  13. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
  14. package/dist/components/core/Calendar/Calendar.js +1 -1
  15. package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +4 -2
  16. package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
  17. package/dist/components/core/Carousel/CarouselBody.js +6 -1
  18. package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
  19. package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
  20. package/dist/components/core/DragDrop/DragDrop.js +170 -17
  21. package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
  22. package/dist/components/core/Drawer/Drawer.d.ts +1 -1
  23. package/dist/components/core/Drawer/Drawer.js +2 -0
  24. package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
  25. package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
  26. package/dist/components/core/Drawer/DrawerTitle.js +2 -2
  27. package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
  28. package/dist/components/core/Empty/Empty.theme.js +1 -1
  29. package/dist/components/core/Link/Link.js +1 -1
  30. package/dist/components/core/Masonry/Masonry.js +7 -2
  31. package/dist/components/core/Modal/Modal.d.ts +1 -1
  32. package/dist/components/core/Modal/Modal.js +3 -3
  33. package/dist/components/core/Modal/Modal.types.d.ts +2 -0
  34. package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
  35. package/dist/components/core/Modal/ModalTitle.js +2 -2
  36. package/dist/components/core/NavItem/NavItem.js +13 -8
  37. package/dist/components/core/Pagination/Pagination.js +1 -1
  38. package/dist/components/core/Popover/PopoverBody.js +3 -27
  39. package/dist/components/core/Popover/PopoverTrigger.js +1 -1
  40. package/dist/components/core/Progress/Progress.js +4 -4
  41. package/dist/components/core/Rating/Rating.js +1 -1
  42. package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
  43. package/dist/components/core/Segmented/Segmented.js +6 -6
  44. package/dist/components/core/Segmented/SegmentedItem.js +28 -2
  45. package/dist/components/core/Skeleton/Skeleton.js +2 -2
  46. package/dist/components/core/Sortable/Sortable.d.ts +1 -1
  47. package/dist/components/core/Sortable/Sortable.js +20 -4
  48. package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
  49. package/dist/components/core/Tabs/Tabs.js +6 -4
  50. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  51. package/dist/components/core/Tabs/TabsBody.js +3 -3
  52. package/dist/components/core/Tabs/TabsList.js +19 -21
  53. package/dist/components/core/Tabs/TabsTrigger.js +47 -28
  54. package/dist/components/core/Toast/Toast.js +1 -1
  55. package/dist/components/core/Toast/ToastProvider.js +3 -2
  56. package/dist/components/core/Tooltip/Tooltip.js +25 -34
  57. package/dist/components/core/Tree/Tree.js +178 -8
  58. package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
  59. package/dist/components/data-display/Table/Table.js +2 -2
  60. package/dist/components/data-display/Table/Table.types.d.ts +2 -0
  61. package/dist/components/data-display/Table/TableFilter.js +21 -18
  62. package/dist/components/data-display/Timeline/Timeline.animations.d.ts +0 -7
  63. package/dist/components/data-display/Timeline/Timeline.animations.js +1 -27
  64. package/dist/components/data-display/Timeline/Timeline.js +3 -4
  65. package/dist/components/data-display/Timeline/Timeline.types.d.ts +0 -13
  66. package/dist/components/data-display/Timeline/TimelineContent.js +3 -7
  67. package/dist/components/data-display/Timeline/TimelineItem.js +4 -17
  68. package/dist/components/data-display/Timeline/index.d.ts +2 -2
  69. package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
  70. package/dist/components/effects/Overlay/Overlay.js +4 -9
  71. package/dist/components/effects/PageTransition/PageTransition.js +17 -6
  72. package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
  73. package/dist/components/effects/index.d.ts +7 -0
  74. package/dist/components/effects/index.js +3 -0
  75. package/dist/components/forms/Checkbox/Checkbox.js +11 -1
  76. package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
  77. package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
  78. package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
  79. package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
  80. package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
  81. package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
  82. package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
  83. package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
  84. package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
  85. package/dist/components/forms/ColorPicker/index.d.ts +1 -1
  86. package/dist/components/forms/Combobox/Combobox.js +20 -45
  87. package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
  88. package/dist/components/forms/CronInput/CronInput.js +10 -4
  89. package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
  90. package/dist/components/forms/DatePicker/DatePicker.js +2 -2
  91. package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
  92. package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
  93. package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
  94. package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
  95. package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
  96. package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
  97. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
  98. package/dist/components/forms/FileUpload/FileUpload.js +14 -10
  99. package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
  100. package/dist/components/forms/FormLabel/FormLabel.js +2 -2
  101. package/dist/components/forms/Input/Input.js +5 -3
  102. package/dist/components/forms/Input/Input.types.d.ts +2 -0
  103. package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
  104. package/dist/components/forms/InputAddress/InputAddress.js +8 -9
  105. package/dist/components/forms/InputCounter/InputCounter.js +25 -18
  106. package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
  107. package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
  108. package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
  109. package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
  110. package/dist/components/forms/InputOTP/InputOTP.js +26 -17
  111. package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
  112. package/dist/components/forms/InputTag/InputTag.js +12 -39
  113. package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
  114. package/dist/components/forms/Mention/Mention.js +13 -6
  115. package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
  116. package/dist/components/forms/Radio/Radio.js +12 -2
  117. package/dist/components/forms/Radio/Radio.theme.js +5 -1
  118. package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
  119. package/dist/components/forms/Radio/RadioGroup.js +8 -3
  120. package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
  121. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
  122. package/dist/components/forms/Select/Select.d.ts +2 -0
  123. package/dist/components/forms/Select/Select.js +27 -57
  124. package/dist/components/forms/Select/Select.types.d.ts +14 -0
  125. package/dist/components/forms/Signature/Signature.js +1 -1
  126. package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
  127. package/dist/components/forms/Slider/Slider.js +18 -4
  128. package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
  129. package/dist/components/forms/Switch/Switch.js +12 -19
  130. package/dist/components/forms/Switch/Switch.theme.js +12 -2
  131. package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
  132. package/dist/components/forms/Textarea/Textarea.js +8 -3
  133. package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
  134. package/dist/components/forms/TimePicker/TimePicker.js +1 -1
  135. package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
  136. package/dist/components/forms/TimePicker/TimePickerContent.js +2 -29
  137. package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
  138. package/dist/components/forms/index.js +1 -1
  139. package/dist/components/layout/index.js +4 -0
  140. package/dist/hooks/index.d.ts +1 -0
  141. package/dist/hooks/useClickOutside.d.ts +10 -4
  142. package/dist/hooks/useClickOutside.js +44 -24
  143. package/dist/hooks/useDarkMode.d.ts +1 -0
  144. package/dist/hooks/useDarkMode.js +42 -0
  145. package/dist/hooks/useFormStateMachine.d.ts +1 -1
  146. package/dist/hooks/useNetworkStatus.js +1 -1
  147. package/dist/hooks/useOverlay.js +4 -0
  148. package/dist/hooks/useScaleBackground.js +5 -2
  149. package/dist/hooks/useSelectPortal.js +14 -5
  150. package/dist/index.d.ts +0 -2
  151. package/dist/index.js +1 -57
  152. package/dist/shadcn-compat.css +80 -0
  153. package/dist/styles.css +1 -1
  154. package/dist/utils/stateMachine.d.ts +4 -4
  155. package/package.json +11 -10
  156. package/tailwind.preset.cjs +32 -3
  157. /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, TimelineAlign, TimelineItemStatus } from "./Timeline.types";
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, TimelineAlign, TimelineItemStatus, };
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 resolvedColor = React.useMemo(() => {
209
- if (color)
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, useMemo } from 'react';
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 from document (memoized to avoid DOM traversal on every animation frame)
142
- const isDarkMode = useMemo(() => {
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
- onStart === null || onStart === void 0 ? void 0 : onStart();
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
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
47
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
39
48
  }
40
- }, [completedPanels, type, onComplete]);
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
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
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
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
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
- // Check for reduced motion preference
28
- const prefersReducedMotion = typeof window !== "undefined" &&
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.HTMLAttributes<HTMLInputElement>, 'onChange' | 'size'> {
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 = error ? "invalid" : state === "disabled" ? "disabled" : "default";
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": error ? true : undefined, "aria-errormessage": error ? 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" &&
36
- checkboxTheme.groupHorizontalStyle), children: children }), error && (typeof error === 'string' ? (jsx("p", { id: errorId, role: "alert", className: checkboxTheme.groupErrorStyle, children: error })) : (error))] }) }));
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, useRef, useEffect } from 'react';
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-${Date.now()}`,
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: "color-picker", required: required, state: state, children: label })), children, helperText && (jsx("div", { className: cn('mt-1.5 text-sm text-[var(--color-text-secondary)]', state === 'invalid' && 'text-[var(--color-danger)]'), children: helperText }))] }) }));
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?: string;
86
+ label?: React.ReactNode;
87
87
  /**
88
88
  * Helper text displayed below the picker
89
89
  */
90
- helperText?: string;
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
- return portal && isClient
70
- ? createPortal(content, document.body)
71
- : content;
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
- window.addEventListener('pointermove', handlePointerMove);
118
- window.addEventListener('pointerup', handlePointerUp);
119
- return () => {
120
- window.removeEventListener('pointermove', handlePointerMove);
121
- window.removeEventListener('pointerup', handlePointerUp);
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]); // eslint-disable-line react-hooks/exhaustive-deps
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, ColorPickerContext } from './ColorPickerContext';
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
- const isSelected = option.value === selectedValue;
120
- const isHighlighted = index === highlightedIndex;
121
- const isCreateOption = option.id === "__create__";
122
- return (jsx("div", { 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: () => {
123
- if (!option.disabled) {
124
- selectOption(option.value, isCreateOption);
125
- onClose();
126
- }
127
- }, 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));
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?: string;
36
+ label?: React.ReactNode;
37
37
  /** Helper text displayed below the input */
38
- helperText?: string;
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 */