@flikk/ui 1.0.0-beta.20 → 1.0.0-beta.22

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 (186) hide show
  1. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  2. package/dist/components/ai/PromptInput/PromptInput.theme.js +3 -3
  3. package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +1 -1
  4. package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +4 -4
  5. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +6 -6
  6. package/dist/components/charts/ActivityRings/ActivityRings.theme.js +2 -2
  7. package/dist/components/charts/DonutChart/DonutChart.theme.js +2 -2
  8. package/dist/components/charts/DonutChart/MultiSegmentDonutChart.theme.js +1 -1
  9. package/dist/components/charts/Heatmap/Heatmap.theme.js +1 -1
  10. package/dist/components/charts/RadarChart/RadarChart.theme.js +2 -2
  11. package/dist/components/charts/ScatterPlot/ScatterPlot.theme.js +2 -2
  12. package/dist/components/charts/shared/ChartText/ChartText.theme.js +1 -1
  13. package/dist/components/charts/shared/ChartTooltip/ChartTooltip.theme.js +4 -4
  14. package/dist/components/core/Alert/Alert.theme.js +1 -1
  15. package/dist/components/core/Avatar/Avatar.theme.js +10 -10
  16. package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +1 -1
  17. package/dist/components/core/Badge/Badge.js +2 -12
  18. package/dist/components/core/Badge/Badge.theme.js +2 -2
  19. package/dist/components/core/Button/Button.theme.js +6 -6
  20. package/dist/components/core/ButtonGroup/ButtonGroup.theme.js +1 -1
  21. package/dist/components/core/Calendar/Calendar.theme.js +5 -5
  22. package/dist/components/core/Card/Card.theme.js +2 -2
  23. package/dist/components/core/CommandPalette/CommandPalette.theme.js +4 -4
  24. package/dist/components/core/Drawer/Drawer.animations.js +5 -4
  25. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  26. package/dist/components/core/Dropdown/Dropdown.theme.js +1 -1
  27. package/dist/components/core/Empty/Empty.theme.js +2 -2
  28. package/dist/components/core/Icon/Icon.d.ts +3 -0
  29. package/dist/components/core/Icon/Icon.js +70 -0
  30. package/dist/components/core/Icon/Icon.theme.d.ts +2 -0
  31. package/dist/components/core/Icon/Icon.theme.js +19 -0
  32. package/dist/components/core/Icon/Icon.types.d.ts +32 -0
  33. package/dist/components/core/{FileTypeIcon → Icon/data}/fileTypes.d.ts +1 -1
  34. package/dist/components/core/Icon/data/paymentMethods.d.ts +27 -0
  35. package/dist/components/core/Icon/data/paymentMethods.js +169 -0
  36. package/dist/components/core/Icon/index.d.ts +4 -0
  37. package/dist/components/core/Kbd/Kbd.theme.js +2 -2
  38. package/dist/components/core/MenuItem/MenuItem.theme.js +2 -2
  39. package/dist/components/core/Message/Message.theme.js +6 -6
  40. package/dist/components/core/Modal/Modal.animations.js +3 -1
  41. package/dist/components/core/Modal/Modal.theme.js +2 -2
  42. package/dist/components/core/Modal/ModalBody.js +7 -8
  43. package/dist/components/core/NavItem/NavItem.theme.js +1 -1
  44. package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +2 -2
  45. package/dist/components/core/PageHeading/PageHeading.theme.js +2 -2
  46. package/dist/components/core/Pagination/Pagination.theme.js +2 -2
  47. package/dist/components/core/Pill/Pill.theme.js +3 -3
  48. package/dist/components/core/Progress/Progress.theme.js +2 -2
  49. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  50. package/dist/components/core/Tabs/Tabs.theme.js +2 -2
  51. package/dist/components/core/Tag/Tag.theme.js +2 -2
  52. package/dist/components/core/Toast/Toast.theme.js +2 -2
  53. package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
  54. package/dist/components/core/Tree/Tree.theme.js +1 -1
  55. package/dist/components/core/index.d.ts +1 -2
  56. package/dist/components/core/index.js +2 -2
  57. package/dist/components/data-display/DescriptionList/DescriptionList.theme.js +2 -2
  58. package/dist/components/data-display/Feed/Feed.theme.js +1 -1
  59. package/dist/components/data-display/GanttChart/GanttChart.theme.js +2 -2
  60. package/dist/components/data-display/KPI/KPI.theme.js +9 -9
  61. package/dist/components/data-display/Metric/Metric.theme.js +9 -9
  62. package/dist/components/data-display/Table/Table.animations.js +16 -10
  63. package/dist/components/data-display/Table/Table.filterUtils.js +8 -1
  64. package/dist/components/data-display/Table/Table.js +10 -14
  65. package/dist/components/data-display/Table/Table.theme.js +3 -3
  66. package/dist/components/data-display/Table/Table.types.d.ts +2 -0
  67. package/dist/components/data-display/Table/TableActions.js +3 -3
  68. package/dist/components/data-display/Table/TableActionsMenu.js +2 -3
  69. package/dist/components/data-display/Table/TableCell.js +6 -3
  70. package/dist/components/data-display/Table/TableDeclarative.js +5 -7
  71. package/dist/components/data-display/Table/TableHeader.js +5 -2
  72. package/dist/components/data-display/Table/TablePagination.js +2 -6
  73. package/dist/components/data-display/Table/TableRow.js +2 -6
  74. package/dist/components/data-display/Table/index.js +1 -0
  75. package/dist/components/effects/Animated/Animated.animations.js +29 -10
  76. package/dist/components/effects/Animated/Animated.d.ts +2 -2
  77. package/dist/components/effects/Animated/Animated.js +4 -3
  78. package/dist/components/effects/Animated/AnimatedItem.d.ts +1 -1
  79. package/dist/components/effects/Animated/AnimatedItem.js +5 -4
  80. package/dist/components/effects/CustomCursor/CustomCursor.theme.js +1 -1
  81. package/dist/components/effects/Overlay/Overlay.js +50 -21
  82. package/dist/components/forms/Checkbox/Checkbox.theme.js +3 -7
  83. package/dist/components/forms/ColorPicker/ColorPicker.d.ts +5 -9
  84. package/dist/components/forms/ColorPicker/ColorPicker.js +126 -22
  85. package/dist/components/forms/ColorPicker/ColorPicker.theme.js +9 -6
  86. package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +99 -0
  87. package/dist/components/forms/ColorPicker/ColorPicker2DCanvas.js +43 -6
  88. package/dist/components/forms/ColorPicker/ColorPickerBody.js +1 -1
  89. package/dist/components/forms/ColorPicker/ColorPickerContext.d.ts +4 -0
  90. package/dist/components/forms/ColorPicker/ColorPickerContext.js +12 -0
  91. package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +1 -1
  92. package/dist/components/forms/ColorPicker/ColorPickerFormatSelector.js +1 -1
  93. package/dist/components/forms/ColorPicker/ColorPickerGradient.d.ts +7 -0
  94. package/dist/components/forms/ColorPicker/ColorPickerGradient.js +186 -0
  95. package/dist/components/forms/ColorPicker/ColorPickerHeader.d.ts +14 -0
  96. package/dist/components/forms/ColorPicker/ColorPickerHeader.js +31 -0
  97. package/dist/components/forms/ColorPicker/ColorPickerImage.d.ts +6 -0
  98. package/dist/components/forms/ColorPicker/ColorPickerImage.js +154 -0
  99. package/dist/components/forms/ColorPicker/ColorPickerInput.d.ts +0 -4
  100. package/dist/components/forms/ColorPicker/ColorPickerInput.js +53 -61
  101. package/dist/components/forms/ColorPicker/ColorPickerPresets.js +13 -12
  102. package/dist/components/forms/ColorPicker/ColorPickerSavedColors.d.ts +3 -0
  103. package/dist/components/forms/ColorPicker/ColorPickerSavedColors.js +69 -0
  104. package/dist/components/forms/ColorPicker/ColorPickerSliders.d.ts +1 -2
  105. package/dist/components/forms/ColorPicker/ColorPickerSliders.js +84 -23
  106. package/dist/components/forms/ColorPicker/ColorPickerSwatch.d.ts +1 -1
  107. package/dist/components/forms/ColorPicker/ColorPickerSwatch.js +10 -9
  108. package/dist/components/forms/ColorPicker/ColorPickerTrigger.js +7 -3
  109. package/dist/components/forms/ColorPicker/index.d.ts +8 -2
  110. package/dist/components/forms/CronInput/CronInput.theme.js +8 -8
  111. package/dist/components/forms/DatePicker/DatePicker.theme.js +1 -1
  112. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +3 -3
  113. package/dist/components/forms/FileUpload/FileUpload.theme.js +4 -4
  114. package/dist/components/forms/FileUpload/FileUploadProgress.js +3 -3
  115. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
  116. package/dist/components/forms/FormLabel/FormLabel.theme.js +1 -1
  117. package/dist/components/forms/Input/Input.js +3 -3
  118. package/dist/components/forms/Input/Input.theme.js +1 -1
  119. package/dist/components/forms/Input/Input.types.d.ts +2 -0
  120. package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
  121. package/dist/components/forms/InputOTP/InputOTP.theme.js +1 -1
  122. package/dist/components/forms/Mention/Mention.theme.js +3 -3
  123. package/dist/components/forms/Radio/Radio.animations.js +6 -2
  124. package/dist/components/forms/Radio/Radio.js +1 -1
  125. package/dist/components/forms/Radio/Radio.theme.js +11 -11
  126. package/dist/components/forms/Radio/Radio.types.d.ts +1 -0
  127. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +1 -1
  128. package/dist/components/forms/Select/Select.theme.js +1 -1
  129. package/dist/components/forms/SelectableCard/SelectableCard.js +42 -27
  130. package/dist/components/forms/SelectableCard/SelectableCard.theme.js +15 -25
  131. package/dist/components/forms/SelectableCard/SelectableCard.types.d.ts +16 -3
  132. package/dist/components/forms/SelectableCard/SelectableCardContext.d.ts +1 -0
  133. package/dist/components/forms/SelectableCard/SelectableCardContext.js +4 -7
  134. package/dist/components/forms/SelectableCard/SelectableCardGroup.js +31 -10
  135. package/dist/components/forms/SelectableCard/index.d.ts +1 -1
  136. package/dist/components/forms/Signature/Signature.theme.js +1 -1
  137. package/dist/components/forms/Slider/Slider.theme.js +3 -3
  138. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  139. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  140. package/dist/components/forms/forms.theme.js +6 -6
  141. package/dist/components/forms/index.js +6 -1
  142. package/dist/components/layout/FormLayout/FormLayout.theme.js +2 -2
  143. package/dist/hooks/useOverlay.d.ts +2 -0
  144. package/dist/hooks/useOverlay.js +34 -4
  145. package/dist/index.js +8 -3
  146. package/dist/styles.css +1 -1
  147. package/package.json +2 -1
  148. package/src/global.scss +1 -1
  149. package/src/styles/theme.css +1 -1
  150. package/dist/bg/bg/1.jpg +0 -0
  151. package/dist/bg/bg/10.jpg +0 -0
  152. package/dist/bg/bg/11.jpg +0 -0
  153. package/dist/bg/bg/14.jpg +0 -0
  154. package/dist/bg/bg/15.jpg +0 -0
  155. package/dist/bg/bg/16.jpg +0 -0
  156. package/dist/bg/bg/17.jpg +0 -0
  157. package/dist/bg/bg/18.jpg +0 -0
  158. package/dist/bg/bg/19.jpg +0 -0
  159. package/dist/bg/bg/2.jpg +0 -0
  160. package/dist/bg/bg/20.jpg +0 -0
  161. package/dist/bg/bg/21.jpg +0 -0
  162. package/dist/bg/bg/22.jpg +0 -0
  163. package/dist/bg/bg/23.jpg +0 -0
  164. package/dist/bg/bg/24.jpg +0 -0
  165. package/dist/bg/bg/3.jpg +0 -0
  166. package/dist/bg/bg/4.jpg +0 -0
  167. package/dist/bg/bg/5.jpg +0 -0
  168. package/dist/bg/bg/6.jpg +0 -0
  169. package/dist/bg/bg/7.jpg +0 -0
  170. package/dist/bg/bg/8.jpg +0 -0
  171. package/dist/bg/bg/9.jpg +0 -0
  172. package/dist/components/core/FileTypeIcon/FileTypeIcon.d.ts +0 -3
  173. package/dist/components/core/FileTypeIcon/FileTypeIcon.js +0 -33
  174. package/dist/components/core/FileTypeIcon/FileTypeIcon.theme.d.ts +0 -2
  175. package/dist/components/core/FileTypeIcon/FileTypeIcon.theme.js +0 -12
  176. package/dist/components/core/FileTypeIcon/FileTypeIcon.types.d.ts +0 -19
  177. package/dist/components/core/FileTypeIcon/index.d.ts +0 -2
  178. package/dist/components/core/SocialIcon/SocialIcon.d.ts +0 -3
  179. package/dist/components/core/SocialIcon/SocialIcon.js +0 -30
  180. package/dist/components/core/SocialIcon/SocialIcon.theme.d.ts +0 -2
  181. package/dist/components/core/SocialIcon/SocialIcon.theme.js +0 -12
  182. package/dist/components/core/SocialIcon/SocialIcon.types.d.ts +0 -18
  183. package/dist/components/core/SocialIcon/index.d.ts +0 -2
  184. /package/dist/components/core/{FileTypeIcon → Icon/data}/fileTypes.js +0 -0
  185. /package/dist/components/core/{SocialIcon → Icon/data}/platforms.d.ts +0 -0
  186. /package/dist/components/core/{SocialIcon → Icon/data}/platforms.js +0 -0
@@ -11,13 +11,9 @@ const radioTheme = {
11
11
  // Default state - dark
12
12
  "dark:bg-[var(--color-background-tertiary)] " +
13
13
  // Checked state - light
14
- "checked:border-[var(--color-primary)] " +
14
+ "checked:border-[var(--color-primary)] checked:bg-white " +
15
15
  // Checked state - dark
16
- "checked:bg-[var(--color-neutral-200)] " +
17
- // Checked outline - light
18
- "checked:outline-2 checked:outline-[var(--color-primary-100)] checked:outline-offset-1 " +
19
- // Checked outline - dark
20
- "dark:checked:outline-[var(--color-primary-900)] " +
16
+ "dark:checked:bg-[var(--color-surface)] " +
21
17
  // Focus visible - light
22
18
  "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary-600)] " +
23
19
  // Focus visible - dark
@@ -28,17 +24,21 @@ const radioTheme = {
28
24
  // Disabled state - dark
29
25
  "dark:disabled:bg-[var(--color-neutral-700)] dark:disabled:border-[var(--color-neutral-600)] " +
30
26
  "dark:data-[state=disabled]:bg-[var(--color-neutral-700)] dark:data-[state=disabled]:border-[var(--color-neutral-600)]",
31
- dotStyle: "absolute inset-0 m-auto pointer-events-none rounded-full bg-[var(--color-primary)]",
32
- descriptionStyle: "text-base text-[var(--color-text-secondary)]/80 mt-0.5",
27
+ dotStyle: "absolute top-1/2 left-1/2 pointer-events-none rounded-full bg-[var(--color-primary)]",
28
+ dotSizes: {
29
+ sm: "size-2.5 mt-0.25",
30
+ md: "size-3",
31
+ },
32
+ descriptionStyle: "text-sm text-[var(--color-text-secondary)]/80 mt-0",
33
33
  sizes: {
34
- sm: "size-5 mt-0.25",
34
+ sm: "size-5 mt-0.5",
35
35
  md: "size-6",
36
36
  },
37
37
  // Group styles
38
38
  groupStyle: "flex flex-col gap-1.5",
39
39
  groupHorizontalStyle: "flex-row flex-wrap gap-x-6 gap-y-3",
40
- groupDescriptionStyle: "text-base text-[var(--color-text-secondary)]/80 -mt-0.5 mb-1",
41
- groupErrorStyle: "text-base text-[var(--color-danger)] mt-1.5",
40
+ groupDescriptionStyle: "text-sm text-[var(--color-text-secondary)]/80 -mt-0.5 mb-1",
41
+ groupErrorStyle: "text-sm text-[var(--color-danger)] mt-1.5",
42
42
  groupItemsStyle: "flex flex-col gap-3",
43
43
  };
44
44
 
@@ -51,6 +51,7 @@ export interface RadioTheme {
51
51
  inputContainerStyle: string;
52
52
  inputStyle: string;
53
53
  dotStyle: string;
54
+ dotSizes: Record<RadioSize, string>;
54
55
  descriptionStyle: string;
55
56
  sizes: Record<RadioSize, string>;
56
57
  groupStyle?: string;
@@ -32,7 +32,7 @@ const richTextEditorTheme = {
32
32
  // Dropdown styling (font size, color picker)
33
33
  dropdownStyle: "fixed z-[1000] min-w-[120px] py-1 bg-white/90 backdrop-blur-md rounded-[var(--form-radius)] shadow-xl ring-1 ring-[var(--color-border)] focus:outline-none",
34
34
  // Dropdown item styling
35
- dropdownItemStyle: "block w-full px-3 py-2 text-left text-base text-[var(--color-text-primary)] hover:bg-[var(--color-background-tertiary)] transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed",
35
+ dropdownItemStyle: "block w-full px-3 py-2 text-left text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-background-tertiary)] transition-colors duration-150 disabled:opacity-50 disabled:cursor-not-allowed",
36
36
  // Separator between toolbar sections
37
37
  separatorStyle: "w-px h-6 bg-[var(--color-border)] mx-1",
38
38
  // Color picker grid layout
@@ -60,7 +60,7 @@ const selectTheme = {
60
60
  iconPadding: formsBaseTheme.iconStyles.padding,
61
61
  // Multi-select chip styles
62
62
  chipContainerStyle: "flex flex-nowrap gap-1 items-center min-w-0 overflow-hidden -ml-2",
63
- chipOverflowStyle: "inline-flex items-center shrink-0 whitespace-nowrap px-1.5 py-0.5 text-sm font-medium " +
63
+ chipOverflowStyle: "inline-flex items-center shrink-0 whitespace-nowrap px-1.5 py-0.5 text-xs font-medium " +
64
64
  "text-[var(--color-text-muted)]",
65
65
  };
66
66
 
@@ -1,53 +1,68 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import React__default, { useId } from 'react';
3
3
  import { useReducedMotion, motion } from 'motion/react';
4
- import { CheckIcon } from '@heroicons/react/24/outline';
5
4
  import { cn } from '../../../utils/cn.js';
6
- import { useSelectableCardContext } from './SelectableCardContext.js';
5
+ import { Radio } from '../Radio/Radio.js';
6
+ import { Checkbox } from '../Checkbox/Checkbox.js';
7
+ import { useOptionalSelectableCardContext } from './SelectableCardContext.js';
8
+ import { selectableCardTheme } from './SelectableCard.theme.js';
7
9
  import { SelectableCardGroup } from './SelectableCardGroup.js';
8
10
  import { cardVariants } from './SelectableCard.animations.js';
9
11
 
10
- // ─── Radio Indicator ─────────────────────────────────────────────────────────
11
- const RadioIndicator = ({ checked }) => (jsxs("div", { className: "relative flex-shrink-0 flex items-center justify-center size-5", children: [jsx("div", { className: cn("size-5 rounded-full border-2 transition-colors duration-200", checked
12
- ? "border-[var(--color-primary)]"
13
- : "border-[var(--color-border)]") }), jsx("div", { className: cn("absolute inset-0 m-auto rounded-full bg-[var(--color-primary)] transition-all duration-200", checked ? "size-2.5 opacity-100" : "size-0 opacity-0") })] }));
14
- // ─── Checkbox Indicator ──────────────────────────────────────────────────────
15
- const CheckboxIndicator = ({ checked }) => (jsx("div", { className: cn("flex-shrink-0 flex items-center justify-center size-5 rounded-[4px] border-2 transition-colors duration-200", checked
16
- ? "border-[var(--color-primary)] bg-[var(--color-primary)]"
17
- : "border-[var(--color-border)] bg-[var(--color-background)]"), children: checked && jsx(CheckIcon, { className: "size-3.5 stroke-white", strokeWidth: 3 }) }));
18
12
  // ─── SelectableCard ──────────────────────────────────────────────────────────
19
- const SelectableCardRoot = React__default.forwardRef(({ value, disabled: cardDisabled, className, children, onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, ...props }, ref) => {
20
- const content = children;
21
- const ctx = useSelectableCardContext();
13
+ const SelectableCardRoot = React__default.forwardRef(({ value, disabled: cardDisabled, checked: standaloneChecked, onChange: standaloneOnChange, indicator: standaloneIndicator, indicatorPosition: cardIndicatorPosition, size: cardSize, className, children, onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, ...props }, ref) => {
14
+ var _a;
15
+ const ctx = useOptionalSelectableCardContext();
16
+ const isStandalone = ctx === undefined;
22
17
  const inputId = useId();
23
18
  const shouldReduceMotion = useReducedMotion();
24
- const isDisabled = cardDisabled || ctx.disabled;
25
- const isSelected = ctx.type === "single"
26
- ? ctx.value === value
27
- : Array.isArray(ctx.value) && ctx.value.includes(value);
19
+ const theme = isStandalone ? selectableCardTheme : ctx.theme;
20
+ const isDisabled = cardDisabled || (!isStandalone && ctx.disabled);
21
+ const isSelected = isStandalone
22
+ ? (standaloneChecked !== null && standaloneChecked !== void 0 ? standaloneChecked : false)
23
+ : ctx.type === "single"
24
+ ? ctx.value === value
25
+ : Array.isArray(ctx.value) && ctx.value.includes(value);
26
+ const resolvedIndicator = isStandalone
27
+ ? (standaloneIndicator !== null && standaloneIndicator !== void 0 ? standaloneIndicator : "radio")
28
+ : ctx.indicator;
29
+ const resolvedIndicatorPosition = cardIndicatorPosition !== null && cardIndicatorPosition !== void 0 ? cardIndicatorPosition : (isStandalone ? "end" : ctx.indicatorPosition);
30
+ const resolvedSize = cardSize !== null && cardSize !== void 0 ? cardSize : (isStandalone ? "md" : ctx.size);
31
+ const indicatorSize = "sm";
28
32
  const handleClick = () => {
29
33
  if (isDisabled)
30
34
  return;
31
- ctx.onChange(value);
35
+ if (isStandalone) {
36
+ standaloneOnChange === null || standaloneOnChange === void 0 ? void 0 : standaloneOnChange(!isSelected, value);
37
+ }
38
+ else {
39
+ ctx.onChange(value);
40
+ }
32
41
  };
33
42
  const handleKeyDown = (e) => {
34
43
  if (isDisabled)
35
44
  return;
36
45
  if (e.key === " " || e.key === "Enter") {
37
46
  e.preventDefault();
38
- ctx.onChange(value);
47
+ handleClick();
39
48
  }
40
49
  };
41
- const inputType = ctx.type === "single" ? "radio" : "checkbox";
50
+ const inputType = isStandalone
51
+ ? "checkbox"
52
+ : ctx.type === "single"
53
+ ? "radio"
54
+ : "checkbox";
55
+ const role = isStandalone
56
+ ? "checkbox"
57
+ : ctx.type === "single"
58
+ ? "radio"
59
+ : "checkbox";
42
60
  const animated = !shouldReduceMotion && !isDisabled;
43
61
  const motionProps = animated
44
- ? {
45
- variants: cardVariants,
46
- initial: "idle",
47
- whileTap: "tap",
48
- }
62
+ ? { variants: cardVariants, initial: "idle", whileTap: "tap" }
49
63
  : {};
50
- return (jsxs(motion.div, { ref: ref, role: ctx.type === "single" ? "radio" : "checkbox", "aria-checked": isSelected, "aria-disabled": isDisabled || undefined, tabIndex: isDisabled ? -1 : 0, "data-selected": isSelected, "data-disabled": isDisabled || undefined, className: cn(ctx.theme.card, isSelected && ctx.theme.selectedCard, isDisabled && ctx.theme.disabledCard, className), onClick: handleClick, onKeyDown: handleKeyDown, ...motionProps, ...props, children: [jsx("input", { type: inputType, id: inputId, name: ctx.name, value: value, checked: isSelected, disabled: isDisabled, onChange: () => { }, className: "sr-only", tabIndex: -1, "aria-hidden": true }), jsx("div", { className: ctx.theme.contentWrapper, children: content }), ctx.indicator !== "none" && (jsx("div", { className: ctx.theme.indicator, children: ctx.indicator === "radio" ? (jsx(RadioIndicator, { checked: isSelected })) : (jsx(CheckboxIndicator, { checked: isSelected })) }))] }));
64
+ const indicatorNode = resolvedIndicator !== "none" && (jsx("div", { className: cn(theme.indicator, "pointer-events-none"), "aria-hidden": "true", children: resolvedIndicator === "radio" ? (jsx(Radio, { value: value, checked: isSelected, onChange: () => { }, state: isDisabled ? "disabled" : "default", size: indicatorSize })) : (jsx(Checkbox, { value: value, checked: isSelected, onChange: () => { }, state: isDisabled ? "disabled" : "default", size: indicatorSize })) }));
65
+ return (jsxs(motion.div, { ref: ref, role: role, "aria-checked": isSelected, "aria-disabled": isDisabled || undefined, tabIndex: isDisabled ? -1 : 0, "data-selected": isSelected, "data-disabled": isDisabled || undefined, className: cn(theme.card, (_a = theme.cardSizes) === null || _a === void 0 ? void 0 : _a[resolvedSize], isSelected && theme.selectedCard, isDisabled && theme.disabledCard, className), onClick: handleClick, onKeyDown: handleKeyDown, ...motionProps, ...props, children: [jsx("input", { type: inputType, id: inputId, name: isStandalone ? undefined : ctx === null || ctx === void 0 ? void 0 : ctx.name, value: value, checked: isSelected, disabled: isDisabled, onChange: () => { }, className: "sr-only", tabIndex: -1, "aria-hidden": true }), resolvedIndicatorPosition === "start" && indicatorNode, jsx("div", { className: theme.contentWrapper, children: children }), resolvedIndicatorPosition === "end" && indicatorNode] }));
51
66
  });
52
67
  SelectableCardRoot.displayName = "SelectableCard";
53
68
  // ─── Compound Export ─────────────────────────────────────────────────────────
@@ -1,38 +1,28 @@
1
1
  const selectableCardTheme = {
2
2
  group: "flex flex-col gap-3",
3
3
  card:
4
- // Base layout
5
- "relative flex items-center justify-between gap-4 rounded-[var(--radius-base)] p-4 cursor-pointer transition-[border-color,background-color,box-shadow,outline] duration-200 " +
6
- // Default state - light
4
+ // Base layout — no justify-between; indicator position handled by render order
5
+ "relative flex items-start gap-3 rounded-[var(--radius-base)] cursor-pointer transition-[border-color,background-color,box-shadow,outline] duration-200 " +
6
+ // Default state
7
7
  "border border-[var(--color-border)] bg-[var(--color-surface)] " +
8
- // Default state - dark
9
8
  "dark:bg-[var(--color-background)] " +
10
- // Hover state - light
9
+ // Hover
11
10
  "hover:border-[var(--color-border-secondary)] " +
12
- // Hover state - dark
13
- " " +
14
- // Focus visible - light
11
+ // Focus visible
15
12
  "focus-visible:outline focus-visible:outline-offset-1 focus-visible:outline-[var(--color-primary)] " +
16
- // Focus visible - dark
17
13
  "dark:focus-visible:outline-[var(--color-primary)]/50",
18
- selectedCard:
19
- // Selected border & ring - light
20
- "border-[var(--color-primary)] ring-2 ring-[var(--color-primary)]/10 " +
21
- // Selected border & ring - dark
22
- "dark:border-[var(--color-primary)] dark:ring-[var(--color-primary)]/20 " +
23
- // Selected background gradient - light
24
- "bg-[var(--color-primary-50)] " +
25
- // Selected background gradient - dark
26
- "dark:bg-[var(--color-background-secondary)] " +
27
- // Selected hover - light
14
+ selectedCard: "border-[var(--color-primary)] ring-2 ring-[var(--color-primary)]/10 " +
15
+ "dark:border-[var(--color-primary)] dark:ring-[var(--color-primary)]/20 " +
28
16
  "hover:border-[var(--color-primary)] " +
29
- // Selected hover - dark
30
- "dark:hover:border-[var(--color-primary)] ",
31
- disabledCard:
32
- // Disabled state
33
- "opacity-50 cursor-not-allowed pointer-events-none",
17
+ "dark:hover:border-[var(--color-primary)]",
18
+ disabledCard: "opacity-50 cursor-not-allowed pointer-events-none",
34
19
  contentWrapper: "flex-1 min-w-0",
35
- indicator: "flex-shrink-0",
20
+ indicator: "flex-shrink-0 -mt-0.5",
21
+ cardSizes: {
22
+ sm: "p-3",
23
+ md: "p-4",
24
+ lg: "p-5",
25
+ },
36
26
  };
37
27
 
38
28
  export { selectableCardTheme };
@@ -1,6 +1,8 @@
1
1
  import React from "react";
2
2
  export type SelectableCardType = "single" | "multiple";
3
3
  export type SelectableCardIndicator = "radio" | "checkbox" | "none";
4
+ export type SelectableCardIndicatorPosition = "start" | "end";
5
+ export type SelectableCardSize = "sm" | "md" | "lg";
4
6
  export interface SelectableCardTheme {
5
7
  group?: string;
6
8
  card?: string;
@@ -8,10 +10,13 @@ export interface SelectableCardTheme {
8
10
  disabledCard?: string;
9
11
  contentWrapper?: string;
10
12
  indicator?: string;
13
+ cardSizes?: Record<SelectableCardSize, string>;
11
14
  }
12
15
  export interface SelectableCardContextValue {
13
16
  type: SelectableCardType;
14
17
  indicator: SelectableCardIndicator;
18
+ indicatorPosition: SelectableCardIndicatorPosition;
19
+ size: SelectableCardSize;
15
20
  value: string | string[];
16
21
  onChange: (cardValue: string) => void;
17
22
  disabled: boolean;
@@ -20,14 +25,22 @@ export interface SelectableCardContextValue {
20
25
  }
21
26
  export interface SelectableCardGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
22
27
  type?: SelectableCardType;
23
- value: string | string[];
24
- onChange: (value: string | string[]) => void;
28
+ value?: string | string[];
29
+ defaultValue?: string | string[];
30
+ onChange?: (value: string | string[]) => void;
25
31
  indicator?: SelectableCardIndicator;
32
+ indicatorPosition?: SelectableCardIndicatorPosition;
33
+ size?: SelectableCardSize;
26
34
  disabled?: boolean;
27
35
  name?: string;
28
36
  theme?: Partial<SelectableCardTheme>;
29
37
  }
30
- export interface SelectableCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "value"> {
38
+ export interface SelectableCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "value" | "onChange"> {
31
39
  value: string;
32
40
  disabled?: boolean;
41
+ checked?: boolean;
42
+ onChange?: (checked: boolean, value: string) => void;
43
+ indicator?: SelectableCardIndicator;
44
+ indicatorPosition?: SelectableCardIndicatorPosition;
45
+ size?: SelectableCardSize;
33
46
  }
@@ -1,3 +1,4 @@
1
1
  import type { SelectableCardContextValue } from "./SelectableCard.types";
2
2
  export declare const SelectableCardContext: import("react").Context<SelectableCardContextValue | undefined>;
3
3
  export declare function useSelectableCardContext(): SelectableCardContextValue;
4
+ export declare function useOptionalSelectableCardContext(): SelectableCardContextValue | undefined;
@@ -1,12 +1,9 @@
1
1
  import { createContext, useContext } from 'react';
2
2
 
3
3
  const SelectableCardContext = createContext(undefined);
4
- function useSelectableCardContext() {
5
- const context = useContext(SelectableCardContext);
6
- if (!context) {
7
- throw new Error("SelectableCard must be used within a SelectableCard.Group");
8
- }
9
- return context;
4
+ // Used by SelectableCard itself — returns undefined when used standalone
5
+ function useOptionalSelectableCardContext() {
6
+ return useContext(SelectableCardContext);
10
7
  }
11
8
 
12
- export { SelectableCardContext, useSelectableCardContext };
9
+ export { SelectableCardContext, useOptionalSelectableCardContext };
@@ -1,35 +1,56 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import React__default, { useId, useMemo } from 'react';
2
+ import React__default, { useId, useState, useMemo, useCallback } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { selectableCardTheme } from './SelectableCard.theme.js';
5
5
  import { SelectableCardContext } from './SelectableCardContext.js';
6
6
 
7
- const SelectableCardGroup = React__default.forwardRef(({ type = "single", value, onChange, indicator, disabled = false, name, className, theme: customTheme, children, ...props }, ref) => {
7
+ const SelectableCardGroup = React__default.forwardRef(({ type = "single", value, defaultValue, onChange, indicator, indicatorPosition = "end", size = "md", disabled = false, name, className, theme: customTheme, children, ...props }, ref) => {
8
8
  const autoId = useId();
9
9
  const groupName = name !== null && name !== void 0 ? name : `selectable-card-${autoId}`;
10
+ const isControlled = value !== undefined;
11
+ const [internalValue, setInternalValue] = useState(() => {
12
+ if (defaultValue !== undefined) {
13
+ if (process.env.NODE_ENV !== "production") {
14
+ if (type === "single" && Array.isArray(defaultValue)) {
15
+ console.warn("[SelectableCard.Group]: defaultValue is an array but type is 'single'. Use a string.");
16
+ }
17
+ if (type === "multiple" && typeof defaultValue === "string") {
18
+ console.warn("[SelectableCard.Group]: defaultValue is a string but type is 'multiple'. Use an array.");
19
+ }
20
+ }
21
+ return defaultValue;
22
+ }
23
+ return type === "single" ? "" : [];
24
+ });
25
+ const resolvedValue = value !== undefined ? value : internalValue;
10
26
  const resolvedIndicator = indicator !== null && indicator !== void 0 ? indicator : (type === "single" ? "radio" : "checkbox");
11
27
  const theme = useMemo(() => ({ ...selectableCardTheme, ...customTheme }), [customTheme]);
12
- const handleChange = (cardValue) => {
28
+ const handleChange = useCallback((cardValue) => {
29
+ let next;
13
30
  if (type === "single") {
14
- onChange(cardValue);
31
+ next = cardValue;
15
32
  }
16
33
  else {
17
- const current = Array.isArray(value) ? value : [];
18
- const next = current.includes(cardValue)
34
+ const current = Array.isArray(resolvedValue) ? resolvedValue : [];
35
+ next = current.includes(cardValue)
19
36
  ? current.filter((v) => v !== cardValue)
20
37
  : [...current, cardValue];
21
- onChange(next);
22
38
  }
23
- };
39
+ if (!isControlled)
40
+ setInternalValue(next);
41
+ onChange === null || onChange === void 0 ? void 0 : onChange(next);
42
+ }, [type, resolvedValue, isControlled, onChange]);
24
43
  const contextValue = useMemo(() => ({
25
44
  type,
26
45
  indicator: resolvedIndicator,
27
- value,
46
+ indicatorPosition,
47
+ size,
48
+ value: resolvedValue,
28
49
  onChange: handleChange,
29
50
  disabled,
30
51
  name: groupName,
31
52
  theme,
32
- }), [type, resolvedIndicator, value, disabled, groupName, theme]);
53
+ }), [type, resolvedIndicator, indicatorPosition, size, resolvedValue, handleChange, disabled, groupName, theme]);
33
54
  return (jsx(SelectableCardContext.Provider, { value: contextValue, children: jsx("div", { ref: ref, role: type === "single" ? "radiogroup" : "group", className: cn(theme.group, className), ...props, children: children }) }));
34
55
  });
35
56
  SelectableCardGroup.displayName = "SelectableCard.Group";
@@ -1,3 +1,3 @@
1
1
  export { SelectableCard } from "./SelectableCard";
2
- export type { SelectableCardProps, SelectableCardGroupProps, SelectableCardType, SelectableCardIndicator, SelectableCardTheme, } from "./SelectableCard.types";
2
+ export type { SelectableCardProps, SelectableCardGroupProps, SelectableCardType, SelectableCardIndicator, SelectableCardIndicatorPosition, SelectableCardSize, SelectableCardTheme, } from "./SelectableCard.types";
3
3
  export { selectableCardTheme } from "./SelectableCard.theme";
@@ -14,7 +14,7 @@ const signatureTheme = {
14
14
  // Canvas element style
15
15
  canvasStyle: "block w-full h-full cursor-crosshair touch-none focus:outline-none",
16
16
  // Placeholder text style
17
- placeholderStyle: "absolute inset-0 flex items-center justify-center pointer-events-none text-[var(--color-text-placeholder)] text-base",
17
+ placeholderStyle: "absolute inset-0 flex items-center justify-center pointer-events-none text-[var(--color-text-placeholder)] text-sm",
18
18
  // Control buttons container style
19
19
  controlsStyle: "absolute bottom-2 right-2 flex gap-2 z-10",
20
20
  // Size variants - use shared padding system
@@ -4,7 +4,7 @@ const sliderTheme = {
4
4
  // Wrapper styles
5
5
  wrapperStyle: formsBaseTheme.wrapperStyle,
6
6
  // Label styles
7
- labelStyle: "block text-base font-medium text-[var(--color-text-primary)] mb-2",
7
+ labelStyle: "block text-sm font-medium text-[var(--color-text-primary)] mb-2",
8
8
  // Helper text styles
9
9
  helperTextStyle: formsBaseTheme.helperText,
10
10
  // Root container with layout
@@ -16,9 +16,9 @@ const sliderTheme = {
16
16
  // Thumb (draggable handle)
17
17
  thumbStyle: "block h-5 w-7 rounded-full bg-transparent overflow-hidden cursor-grab active:cursor-grabbing active:scale-x-110 active:scale-y-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border)] focus-visible:ring-offset-2 transition-all hover:scale-110 data-[disabled]:cursor-not-allowed data-[disabled]:hover:scale-100 data-[invalid]:focus-visible:ring-[var(--color-danger)]",
18
18
  // Value display
19
- valueDisplayStyle: "text-base font-medium text-[var(--color-text-secondary)] w-fit min-w-12",
19
+ valueDisplayStyle: "text-sm font-medium text-[var(--color-text-secondary)] w-fit min-w-12",
20
20
  // Min/max labels
21
- minMaxLabelStyle: "text-base font-medium text-[var(--color-text-muted)]",
21
+ minMaxLabelStyle: "text-sm font-medium text-[var(--color-text-muted)]",
22
22
  // Tick marks
23
23
  tickStyle: "absolute w-0.5 h-2 bg-[var(--color-border)] -translate-x-0.5 pointer-events-none",
24
24
  };
@@ -5,7 +5,7 @@ const switchTheme = {
5
5
  container: "flex w-full justify-between items-center gap-4",
6
6
  track: "w-12 h-6 rounded-full cursor-pointer shadow-inner bg-[var(--color-background-tertiary)] data-[state=checked]:bg-[var(--color-primary)] data-[state=disabled]:bg-[var(--color-background-disabled)] data-[state=disabled]:cursor-not-allowed data-[state=invalid]:border-2 data-[state=invalid]:border-[var(--color-danger)] outline outline-[var(--color-border)] transition-colors duration-300",
7
7
  thumb: "w-7 h-5 rounded-full bg-transparent overflow-hidden border border-[var(--color-border)]/40 shadow-lg",
8
- description: "text-base text-[var(--color-text-muted)] -mt-1.5",
8
+ description: "text-sm text-[var(--color-text-muted)] -mt-1.5",
9
9
  };
10
10
  /**
11
11
  * Thumb position values for animation
@@ -62,7 +62,7 @@ const timePickerTheme = {
62
62
  // Individual wheel column - relative positioning for highlight overlay
63
63
  wheelColumnStyle: "flex-1 flex flex-col relative",
64
64
  // Wheel column header label
65
- wheelHeaderStyle: "text-center py-2 text-sm font-semibold text-[var(--color-text-muted)] uppercase",
65
+ wheelHeaderStyle: "text-center py-2 text-xs font-semibold text-[var(--color-text-muted)] uppercase",
66
66
  // Center highlight row - positioned behind selected item
67
67
  wheelHighlightStyle: "absolute -mt-4 inset-x-0 bg-[var(--color-border)] pointer-events-none z-0",
68
68
  // Wheel item - centered with transition for smooth opacity/color changes
@@ -77,7 +77,7 @@ const timePickerTheme = {
77
77
  "text-[var(--color-text-primary)] placeholder:text-[var(--color-text-placeholder)] " +
78
78
  "dark:text-[var(--color-neutral-200)] dark:placeholder:text-[var(--color-neutral-500)]",
79
79
  // AM/PM period toggle button
80
- periodToggleStyle: "px-1.5 py-0.5 text-sm font-semibold rounded " +
80
+ periodToggleStyle: "px-1.5 py-0.5 text-xs font-semibold rounded " +
81
81
  "bg-[var(--color-background-tertiary)] text-[var(--color-text-primary)] " +
82
82
  "hover:bg-[var(--color-background-quaternary)] transition-colors cursor-pointer select-none " +
83
83
  "dark:bg-[var(--color-neutral-700)] dark:text-[var(--color-neutral-200)] " +
@@ -15,7 +15,7 @@ const formsBaseTheme = {
15
15
  "dark:bg-[var(--color-neutral-900)] dark:outline-[var(--color-neutral-700)] " +
16
16
  "dark:focus-within:outline-[var(--color-primary-500)] dark:focus-within:ring-[var(--color-primary-500)]/20",
17
17
  // Common base input/control style
18
- controlBaseStyle: "w-full bg-transparent text-base text-[var(--color-text-primary)] " +
18
+ controlBaseStyle: "w-full bg-transparent text-sm text-[var(--color-text-primary)] " +
19
19
  "placeholder:text-[var(--color-text-placeholder)] focus:outline-none " +
20
20
  // Dark mode
21
21
  "dark:text-[var(--color-text-primary)] dark:placeholder:text-[var(--color-neutral-500)]",
@@ -23,19 +23,19 @@ const formsBaseTheme = {
23
23
  // These tokens can be composed together or used individually for maximum flexibility
24
24
  sizes: {
25
25
  sm: {
26
- text: "text-base",
26
+ text: "text-sm",
27
27
  padding: "py-[var(--form-py-sm)] px-[var(--form-px-sm)]",
28
28
  height: "h-[var(--form-min-h-sm)]",
29
29
  textareaHeight: "min-h-[calc(var(--form-min-h-sm)*2)]",
30
30
  },
31
31
  md: {
32
- text: "text-base",
32
+ text: "text-sm",
33
33
  padding: "py-[var(--form-py-md)] px-[var(--form-px-md)]",
34
34
  height: "h-[var(--form-min-h-md)]",
35
35
  textareaHeight: "min-h-[calc(var(--form-min-h-md)*2)]",
36
36
  },
37
37
  lg: {
38
- text: "text-lg",
38
+ text: "text-base",
39
39
  padding: "py-[var(--form-py-lg)] px-[var(--form-px-lg)]",
40
40
  height: "h-[var(--form-min-h-lg)]",
41
41
  textareaHeight: "min-h-[calc(var(--form-min-h-lg)*2)]",
@@ -72,7 +72,7 @@ const formsBaseTheme = {
72
72
  invalid: "",
73
73
  },
74
74
  // Common helper text style with state variations
75
- helperText: "text-sm text-[var(--color-text-muted)] mt-1.5 [.state-disabled_&]:text-[var(--color-text-disabled)] [.state-invalid_&]:text-[var(--color-danger-500)] " +
75
+ helperText: "text-xs text-[var(--color-text-muted)] mt-1.5 [.state-disabled_&]:text-[var(--color-text-disabled)] [.state-invalid_&]:text-[var(--color-danger-500)] " +
76
76
  "dark:text-[var(--color-neutral-400)] dark:[.state-invalid_&]:text-[var(--color-danger-400)]",
77
77
  // Common icon styles
78
78
  iconStyles: {
@@ -89,7 +89,7 @@ const formsBaseTheme = {
89
89
  dropdownStyles: {
90
90
  // Container for dropdown panel
91
91
  container: "fixed z-[1000] max-h-[var(--select-content-available-height,20rem)] overflow-auto overscroll-contain " +
92
- "rounded-[var(--form-radius)] bg-white/90 backdrop-blur-md text-base border border-[var(--color-border)] " +
92
+ "rounded-[var(--form-radius)] bg-white/90 backdrop-blur-md text-sm border border-[var(--color-border)] " +
93
93
  "shadow-real-lg ring-1 ring-[var(--color-border)] focus:outline-none " +
94
94
  "dark:bg-[var(--color-neutral-900)]/90 dark:ring-[var(--color-neutral-700)]",
95
95
  // Base option style
@@ -52,7 +52,8 @@ export { InputTag } from './InputTag/InputTag.js';
52
52
  export { inputTagTheme } from './InputTag/InputTag.theme.js';
53
53
  export { Combobox } from './Combobox/Combobox.js';
54
54
  export { comboboxTheme } from './Combobox/Combobox.theme.js';
55
- export { ColorPicker, ColorPickerContext, useColorPicker } from './ColorPicker/ColorPicker.js';
55
+ export { ColorPicker } from './ColorPicker/ColorPicker.js';
56
+ export { ColorPickerContext, useColorPicker } from './ColorPicker/ColorPickerContext.js';
56
57
  export { ColorPickerTrigger } from './ColorPicker/ColorPickerTrigger.js';
57
58
  export { ColorPickerBody } from './ColorPicker/ColorPickerBody.js';
58
59
  export { ColorPicker2DCanvas } from './ColorPicker/ColorPicker2DCanvas.js';
@@ -62,6 +63,10 @@ export { ColorPickerInput } from './ColorPicker/ColorPickerInput.js';
62
63
  export { ColorPickerFormatSelector } from './ColorPicker/ColorPickerFormatSelector.js';
63
64
  export { ColorPickerEyeDropper } from './ColorPicker/ColorPickerEyeDropper.js';
64
65
  export { ColorPickerPresets } from './ColorPicker/ColorPickerPresets.js';
66
+ export { ColorPickerSavedColors } from './ColorPicker/ColorPickerSavedColors.js';
67
+ export { ColorPickerHeader } from './ColorPicker/ColorPickerHeader.js';
68
+ export { ColorPickerGradient } from './ColorPicker/ColorPickerGradient.js';
69
+ export { ColorPickerImage } from './ColorPicker/ColorPickerImage.js';
65
70
  export { formatHex, formatHsl, formatRgb, hexToRgb, hslToRgb, hsvToRgb, parseColor, rgbToHex, rgbToHsl, rgbToHsv, validateHex, validateRgb } from './ColorPicker/colorUtils.js';
66
71
  export { SelectableCard } from './SelectableCard/SelectableCard.js';
67
72
  export { selectableCardTheme } from './SelectableCard/SelectableCard.theme.js';
@@ -2,8 +2,8 @@ const formLayoutTheme = {
2
2
  root: "flex flex-col gap-6",
3
3
  section: "grid grid-cols-1 md:grid-cols-6 gap-6 md:gap-10",
4
4
  sectionSidebar: "col-span-full md:col-span-2",
5
- sectionTitle: "text-lg font-semibold text-[var(--color-text-primary)]",
6
- sectionSubtitle: "text-base text-[var(--color-text-muted)] mt-1",
5
+ sectionTitle: "text-base font-semibold text-[var(--color-text-primary)]",
6
+ sectionSubtitle: "text-sm text-[var(--color-text-muted)] mt-1",
7
7
  sectionAction: "mt-4",
8
8
  sectionContent: "col-span-full md:col-span-4 grid grid-cols-1 sm:grid-cols-2 gap-4",
9
9
  sectionContentFull: "col-span-full grid grid-cols-1 sm:grid-cols-2 gap-4",
@@ -1,3 +1,5 @@
1
+ export declare function lockScroll(): void;
2
+ export declare function unlockScroll(): void;
1
3
  export interface UseOverlayOptions {
2
4
  /** Whether the overlay is currently open */
3
5
  isOpen: boolean;
@@ -1,5 +1,35 @@
1
1
  import { useCallback, useEffect } from 'react';
2
2
 
3
+ // ── Scroll-lock manager (ref-counted) ──────────────────────────────────
4
+ // Supports nested overlays: only the first lock measures the scrollbar
5
+ // and compensates; subsequent locks just bump the counter.
6
+ let lockCount = 0;
7
+ let savedOverflow = '';
8
+ let savedPaddingRight = '';
9
+ function lockScroll() {
10
+ lockCount++;
11
+ if (lockCount === 1) {
12
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
13
+ // Save originals before mutating
14
+ savedOverflow = document.body.style.overflow;
15
+ savedPaddingRight = document.body.style.paddingRight;
16
+ // Expose width as CSS variable so fixed-position elements can compensate
17
+ // e.g. position: fixed; padding-right: var(--scrollbar-width, 0px);
18
+ document.documentElement.style.setProperty('--scrollbar-width', `${scrollbarWidth}px`);
19
+ if (scrollbarWidth > 0) {
20
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
21
+ }
22
+ document.body.style.overflow = 'hidden';
23
+ }
24
+ }
25
+ function unlockScroll() {
26
+ lockCount = Math.max(0, lockCount - 1);
27
+ if (lockCount === 0) {
28
+ document.body.style.overflow = savedOverflow;
29
+ document.body.style.paddingRight = savedPaddingRight;
30
+ document.documentElement.style.removeProperty('--scrollbar-width');
31
+ }
32
+ }
3
33
  /**
4
34
  * Shared hook for overlay behavior (escape key, scroll lock, click handling)
5
35
  *
@@ -49,15 +79,15 @@ const useOverlay = ({ isOpen, onClose, closeOnEsc = true, closeOnOverlayClick =
49
79
  if (isOpen) {
50
80
  // Add Escape key listener
51
81
  document.addEventListener('keydown', handleEscKey);
52
- // Lock body scroll to prevent background scrolling
82
+ // Lock body scroll (ref-counted so nested overlays work)
53
83
  if (enableScrollLock) {
54
- document.body.style.overflow = 'hidden';
84
+ lockScroll();
55
85
  }
56
86
  // Cleanup on unmount or when overlay closes
57
87
  return () => {
58
88
  document.removeEventListener('keydown', handleEscKey);
59
89
  if (enableScrollLock) {
60
- document.body.style.overflow = 'unset';
90
+ unlockScroll();
61
91
  }
62
92
  };
63
93
  }
@@ -68,4 +98,4 @@ const useOverlay = ({ isOpen, onClose, closeOnEsc = true, closeOnOverlayClick =
68
98
  };
69
99
  };
70
100
 
71
- export { useOverlay };
101
+ export { lockScroll, unlockScroll, useOverlay };