@flikk/ui 1.0.0-beta.7 → 1.0.0-beta.9

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 (155) hide show
  1. package/dist/components/ai/ApprovalCard/ApprovalCard.js +1 -1
  2. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  3. package/dist/components/ai/PromptInput/PromptInput.js +2 -2
  4. package/dist/components/ai/PromptInput/PromptInput.theme.js +1 -1
  5. package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +1 -1
  6. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +6 -6
  7. package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +4 -4
  8. package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.theme.js +2 -2
  9. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +6 -6
  10. package/dist/components/charts/ActivityRings/ActivityRings.js +1 -1
  11. package/dist/components/charts/ActivityRings/ActivityRings.theme.js +2 -2
  12. package/dist/components/charts/BarChart/BarChart.js +3 -3
  13. package/dist/components/charts/ChartContainer.js +1 -1
  14. package/dist/components/charts/ComboChart/ComboChart.js +2 -2
  15. package/dist/components/charts/DonutChart/DonutChart.theme.js +1 -1
  16. package/dist/components/charts/DonutChart/MultiSegmentDonutChart.js +1 -1
  17. package/dist/components/charts/DonutChart/MultiSegmentDonutChart.theme.js +1 -1
  18. package/dist/components/charts/FunnelChart/FunnelChart.js +7 -7
  19. package/dist/components/charts/Heatmap/Heatmap.theme.js +1 -1
  20. package/dist/components/charts/Heatmap/HeatmapCell.js +1 -1
  21. package/dist/components/charts/Heatmap/HeatmapLegend.js +1 -1
  22. package/dist/components/charts/RadarChart/RadarChart.theme.js +2 -2
  23. package/dist/components/charts/ScatterPlot/ScatterPlot.js +1 -1
  24. package/dist/components/charts/ScatterPlot/ScatterPlot.theme.js +2 -2
  25. package/dist/components/charts/StackedBarChart/StackedBarChart.js +2 -2
  26. package/dist/components/charts/shared/ChartErrorBoundary/ChartErrorBoundary.js +1 -1
  27. package/dist/components/charts/shared/ChartLegend/ChartLegendContent.js +1 -1
  28. package/dist/components/charts/shared/ChartText/ChartText.js +1 -1
  29. package/dist/components/charts/shared/ChartText/ChartText.theme.js +1 -1
  30. package/dist/components/charts/shared/ChartTooltip/ChartTooltip.theme.js +4 -4
  31. package/dist/components/core/Alert/Alert.js +1 -0
  32. package/dist/components/core/Alert/Alert.theme.js +4 -8
  33. package/dist/components/core/Alert/Alert.types.d.ts +2 -1
  34. package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
  35. package/dist/components/core/Avatar/Avatar.theme.js +5 -5
  36. package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +1 -1
  37. package/dist/components/core/Badge/Badge.theme.js +11 -6
  38. package/dist/components/core/Badge/Badge.types.d.ts +2 -1
  39. package/dist/components/core/Breadcrumbs/Breadcrumbs.js +1 -1
  40. package/dist/components/core/Button/Button.theme.js +38 -32
  41. package/dist/components/core/Button/Button.types.d.ts +2 -1
  42. package/dist/components/core/ButtonGroup/ButtonGroup.theme.js +1 -1
  43. package/dist/components/core/Calendar/Calendar.js +1 -1
  44. package/dist/components/core/Calendar/Calendar.theme.js +5 -5
  45. package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +1 -1
  46. package/dist/components/core/Card/Card.theme.js +2 -2
  47. package/dist/components/core/CommandPalette/CommandItem.js +1 -1
  48. package/dist/components/core/CommandPalette/CommandPalette.theme.js +3 -3
  49. package/dist/components/core/ContextMenu/ContextMenu.theme.js +3 -3
  50. package/dist/components/core/ContextMenu/ContextMenuItem.js +1 -1
  51. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  52. package/dist/components/core/Dropdown/Dropdown.theme.js +1 -1
  53. package/dist/components/core/Empty/Empty.theme.js +2 -2
  54. package/dist/components/core/Kbd/Kbd.theme.js +3 -3
  55. package/dist/components/core/Loader/Loader.theme.js +1 -1
  56. package/dist/components/core/MenuItem/MenuItem.theme.js +2 -2
  57. package/dist/components/core/Message/Message.js +1 -1
  58. package/dist/components/core/Message/Message.theme.js +4 -4
  59. package/dist/components/core/Modal/Modal.theme.js +2 -2
  60. package/dist/components/core/NavItem/NavItem.js +1 -1
  61. package/dist/components/core/NavItem/NavItem.theme.js +1 -1
  62. package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +2 -2
  63. package/dist/components/core/PageHeading/PageHeading.theme.js +1 -1
  64. package/dist/components/core/Pagination/Pagination.theme.js +2 -2
  65. package/dist/components/core/Pill/Pill.theme.js +3 -3
  66. package/dist/components/core/Popover/PopoverContent.js +2 -0
  67. package/dist/components/core/Progress/Progress.theme.js +10 -7
  68. package/dist/components/core/Progress/Progress.types.d.ts +2 -1
  69. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  70. package/dist/components/core/Sidebar/SidebarNavGroup.js +1 -1
  71. package/dist/components/core/Spinner/Spinner.theme.js +3 -5
  72. package/dist/components/core/Spinner/Spinner.types.d.ts +2 -1
  73. package/dist/components/core/Tabs/Tabs.theme.js +2 -2
  74. package/dist/components/core/Tag/Tag.theme.js +2 -2
  75. package/dist/components/core/Toast/Toast.theme.js +4 -4
  76. package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
  77. package/dist/components/core/Tree/Tree.theme.js +1 -1
  78. package/dist/components/data-display/DescriptionList/DescriptionList.theme.js +2 -2
  79. package/dist/components/data-display/Feed/Feed.js +1 -1
  80. package/dist/components/data-display/Feed/Feed.theme.js +2 -2
  81. package/dist/components/data-display/GanttChart/GanttChart.theme.js +2 -2
  82. package/dist/components/data-display/GanttChart/GanttChartPanel.js +1 -1
  83. package/dist/components/data-display/GanttChart/GanttMilestone.js +2 -2
  84. package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
  85. package/dist/components/data-display/GanttChart/GanttTaskBar.js +2 -2
  86. package/dist/components/data-display/KPI/KPI.js +1 -1
  87. package/dist/components/data-display/KPI/KPI.theme.js +10 -11
  88. package/dist/components/data-display/KPI/KPI.types.d.ts +2 -1
  89. package/dist/components/data-display/Metric/Metric.js +1 -1
  90. package/dist/components/data-display/Metric/Metric.theme.js +11 -7
  91. package/dist/components/data-display/Metric/Metric.types.d.ts +2 -1
  92. package/dist/components/data-display/Table/Table.theme.js +3 -3
  93. package/dist/components/data-display/Table/TableActionsMenu.js +1 -1
  94. package/dist/components/data-display/Table/TableColumnManager.js +1 -1
  95. package/dist/components/data-display/Table/TableFilter.js +3 -3
  96. package/dist/components/data-display/Table/TableHeader.js +1 -1
  97. package/dist/components/data-display/Table/TablePagination.js +1 -1
  98. package/dist/components/data-display/Table/TableSelectionHeader.js +1 -1
  99. package/dist/components/effects/CustomCursor/CustomCursor.js +1 -1
  100. package/dist/components/effects/CustomCursor/CustomCursor.theme.js +3 -1
  101. package/dist/components/effects/CustomCursor/CustomCursor.types.d.ts +2 -1
  102. package/dist/components/effects/StickyScroll/StickyScroll.js +1 -1
  103. package/dist/components/forms/Checkbox/Checkbox.d.ts +3 -4
  104. package/dist/components/forms/Checkbox/Checkbox.js +33 -9
  105. package/dist/components/forms/Checkbox/Checkbox.theme.js +7 -1
  106. package/dist/components/forms/Checkbox/Checkbox.types.d.ts +28 -2
  107. package/dist/components/forms/Checkbox/CheckboxContext.d.ts +14 -0
  108. package/dist/components/forms/Checkbox/CheckboxContext.js +12 -0
  109. package/dist/components/forms/Checkbox/CheckboxGroup.d.ts +3 -0
  110. package/dist/components/forms/Checkbox/CheckboxGroup.js +40 -0
  111. package/dist/components/forms/Checkbox/index.d.ts +1 -1
  112. package/dist/components/forms/ColorPicker/ColorPicker.js +1 -1
  113. package/dist/components/forms/ColorPicker/ColorPicker.theme.js +1 -1
  114. package/dist/components/forms/ColorPicker/ColorPickerInput.js +7 -7
  115. package/dist/components/forms/CronInput/CronInput.js +1 -1
  116. package/dist/components/forms/CronInput/CronInput.theme.js +8 -8
  117. package/dist/components/forms/DatePicker/DatePicker.theme.js +1 -1
  118. package/dist/components/forms/DatePicker/DatePickerContent.js +1 -1
  119. package/dist/components/forms/FileUpload/FileUpload.js +2 -2
  120. package/dist/components/forms/FileUpload/FileUpload.theme.js +4 -4
  121. package/dist/components/forms/FormLabel/FormLabel.theme.js +1 -1
  122. package/dist/components/forms/Input/Input.theme.js +1 -1
  123. package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
  124. package/dist/components/forms/InputOTP/InputOTP.theme.js +1 -1
  125. package/dist/components/forms/InputTag/InputTag.js +1 -1
  126. package/dist/components/forms/Mention/Mention.theme.js +3 -3
  127. package/dist/components/forms/Radio/Radio.d.ts +3 -4
  128. package/dist/components/forms/Radio/Radio.js +21 -4
  129. package/dist/components/forms/Radio/Radio.theme.js +7 -1
  130. package/dist/components/forms/Radio/Radio.types.d.ts +30 -4
  131. package/dist/components/forms/Radio/RadioContext.d.ts +14 -0
  132. package/dist/components/forms/Radio/RadioContext.js +12 -0
  133. package/dist/components/forms/Radio/RadioGroup.d.ts +3 -0
  134. package/dist/components/forms/Radio/RadioGroup.js +36 -0
  135. package/dist/components/forms/Radio/index.d.ts +1 -1
  136. package/dist/components/forms/RichTextEditor/RichTextEditor.js +2 -2
  137. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +1 -1
  138. package/dist/components/forms/Select/Select.theme.js +1 -1
  139. package/dist/components/forms/Signature/Signature.theme.js +1 -1
  140. package/dist/components/forms/Slider/Slider.theme.js +3 -3
  141. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  142. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  143. package/dist/components/forms/TransferList/TransferList.js +2 -2
  144. package/dist/components/forms/TransferList/TransferList.theme.js +6 -6
  145. package/dist/components/forms/forms.theme.js +6 -6
  146. package/dist/components/forms/index.d.ts +2 -2
  147. package/dist/components/layout/FormLayout/FormLayout.theme.js +4 -4
  148. package/dist/hooks/useSelectPortal.d.ts +3 -2
  149. package/dist/hooks/useSelectPortal.js +44 -19
  150. package/dist/styles.css +1 -1
  151. package/dist/utils/colorUtils.d.ts +23 -0
  152. package/dist/utils/colorUtils.js +45 -0
  153. package/dist/utils/index.d.ts +1 -0
  154. package/package.json +1 -1
  155. package/src/styles/theme.css +19 -17
@@ -5,7 +5,7 @@
5
5
  const inputCounterTheme = {
6
6
  baseStyle: "flex items-center gap-3 transition-all p-1 border border-[var(--color-border)] rounded-[var(--form-radius)] w-fit",
7
7
  buttonWrapperStyle: "flex-shrink-0",
8
- numberStyle: "min-w-fit text-center text-sm font-medium text-[var(--color-text-primary)] tabular-nums select-none",
8
+ numberStyle: "min-w-fit text-center text-base font-medium text-[var(--color-text-primary)] tabular-nums select-none",
9
9
  };
10
10
 
11
11
  export { inputCounterTheme };
@@ -10,7 +10,7 @@ const inputOTPTheme = {
10
10
  // OTP input container
11
11
  containerStyle: 'flex items-center gap-3',
12
12
  // Individual input field styles with design tokens
13
- inputStyle: 'text-center text-lg text-[var(--color-text-primary)] h-full',
13
+ inputStyle: 'text-center text-xl text-[var(--color-text-primary)] h-full',
14
14
  // Input group wrapper for grouped inputs (overrides default Input styling)
15
15
  inputGroupStyle: 'w-10 min-w-10 h-10 min-h-10 sm:w-12 sm:min-w-12 sm:h-12 sm:min-h-12',
16
16
  // Separator between input groups
@@ -480,7 +480,7 @@ suggestions, creatable = true, openOnFocus = true, filterFn = defaultFilterFn, e
480
480
  : placeholder, size: size, state: state, label: label,
481
481
  // Only pass helperText to Input when NOT in dropdown mode
482
482
  // Otherwise the dropdown positions below the helper text
483
- helperText: hasDropdown ? undefined : displayHelperText, disabled: isInputDisabled, className: inputClassName, "aria-expanded": hasDropdown ? isOpen : undefined, "aria-haspopup": hasDropdown ? "listbox" : undefined, "aria-autocomplete": hasDropdown ? "list" : undefined }), hasDropdown && (jsx(InputTagOptions, { isOpen: isOpen, onClose: handleCloseDropdown, triggerRef: wrapperRef, filteredOptions: filteredOptions, selectedTags: tags, highlightedIndex: highlightedIndex, setHighlightedIndex: setHighlightedIndex, onSelectOption: handleSelectOption, showSelectedIndicator: showSelectedIndicator, portal: portal, placement: placement, offset: offset, emptyMessage: emptyMessage, className: dropdownClassName, theme: theme }))] }), hasDropdown && displayHelperText && (jsx("p", { className: "text-xs text-[var(--color-text-muted)] mt-1.5", children: displayHelperText })), jsx("div", { className: cn(theme.tagsContainerStyle, "empty:hidden", tagsContainerClassName), children: jsx(AnimatePresence, { mode: "popLayout", children: tags.map((tag, index) => {
483
+ helperText: hasDropdown ? undefined : displayHelperText, disabled: isInputDisabled, className: inputClassName, "aria-expanded": hasDropdown ? isOpen : undefined, "aria-haspopup": hasDropdown ? "listbox" : undefined, "aria-autocomplete": hasDropdown ? "list" : undefined }), hasDropdown && (jsx(InputTagOptions, { isOpen: isOpen, onClose: handleCloseDropdown, triggerRef: wrapperRef, filteredOptions: filteredOptions, selectedTags: tags, highlightedIndex: highlightedIndex, setHighlightedIndex: setHighlightedIndex, onSelectOption: handleSelectOption, showSelectedIndicator: showSelectedIndicator, portal: portal, placement: placement, offset: offset, emptyMessage: emptyMessage, className: dropdownClassName, theme: theme }))] }), hasDropdown && displayHelperText && (jsx("p", { className: "text-sm text-[var(--color-text-muted)] mt-1.5", children: displayHelperText })), jsx("div", { className: cn(theme.tagsContainerStyle, "empty:hidden", tagsContainerClassName), children: jsx(AnimatePresence, { mode: "popLayout", children: tags.map((tag, index) => {
484
484
  return (jsx(Tag, { size: size, onRemove: () => removeTag(index), disabled: disabled, className: tagClassName, children: tag }, `${tag}-${occurrenceMap[index]}`));
485
485
  }) }) })] }));
486
486
  });
@@ -35,13 +35,13 @@ const mentionTheme = {
35
35
  suggestionAvatarStyle: "size-7 rounded-full object-cover shrink-0 " +
36
36
  "bg-[var(--color-background-tertiary)] dark:bg-[var(--color-neutral-700)]",
37
37
  // Avatar fallback (initials circle)
38
- suggestionAvatarFallbackStyle: "size-7 rounded-full shrink-0 flex items-center justify-center text-xs font-medium " +
38
+ suggestionAvatarFallbackStyle: "size-7 rounded-full shrink-0 flex items-center justify-center text-sm font-medium " +
39
39
  "bg-[var(--color-primary-100)] text-[var(--color-primary-700)] " +
40
40
  "dark:bg-[var(--color-primary-900)] dark:text-[var(--color-primary-300)]",
41
41
  // Suggestion label text
42
- suggestionLabelStyle: "text-sm font-medium truncate",
42
+ suggestionLabelStyle: "text-base font-medium truncate",
43
43
  // Suggestion description text
44
- suggestionDescriptionStyle: "text-xs text-[var(--color-text-muted)] truncate " +
44
+ suggestionDescriptionStyle: "text-sm text-[var(--color-text-muted)] truncate " +
45
45
  "dark:text-[var(--color-neutral-500)]",
46
46
  // Empty state
47
47
  emptyMessageStyle: formsBaseTheme.dropdownStyles.emptyMessage,
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
2
  import { RadioProps } from "./Radio.types";
3
- /**
4
- * Radio component for selecting one option from a group
5
- */
6
- export declare const Radio: React.ForwardRefExoticComponent<RadioProps & React.RefAttributes<HTMLInputElement>>;
3
+ export declare const Radio: React.ForwardRefExoticComponent<RadioProps & React.RefAttributes<HTMLInputElement>> & {
4
+ Group: React.ForwardRefExoticComponent<import("./Radio.types").RadioGroupProps & React.RefAttributes<HTMLFieldSetElement>>;
5
+ };
@@ -1,18 +1,34 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import React__default from 'react';
2
+ import React__default, { useId } from 'react';
3
3
  import { radioTheme } from './Radio.theme.js';
4
4
  import { formLabelTheme } from '../FormLabel/FormLabel.theme.js';
5
5
  import { cn } from '../../../utils/cn.js';
6
+ import { useRadioGroupContext } from './RadioContext.js';
7
+ import { RadioGroup } from './RadioGroup.js';
6
8
 
7
9
  /**
8
10
  * Radio component for selecting one option from a group
9
11
  */
10
- const Radio = React__default.forwardRef(({ id, name, value, checked, defaultChecked, onChange, label, description, state = "default", size = "md", className, ...restProps }, ref) => {
11
- const isControlled = checked !== undefined;
12
+ const RadioComponent = React__default.forwardRef(({ id: idProp, name: nameProp, value, checked: checkedProp, defaultChecked, onChange, label, description, state: stateProp = "default", size: sizeProp = "md", className, ...restProps }, ref) => {
13
+ var _a, _b, _c, _d;
14
+ const autoId = useId();
15
+ const groupContext = useRadioGroupContext();
16
+ // Resolve props from group context or individual props
17
+ const id = idProp !== null && idProp !== void 0 ? idProp : autoId;
18
+ const name = (_b = (_a = groupContext === null || groupContext === void 0 ? void 0 : groupContext.name) !== null && _a !== void 0 ? _a : nameProp) !== null && _b !== void 0 ? _b : autoId;
19
+ const size = (_c = groupContext === null || groupContext === void 0 ? void 0 : groupContext.size) !== null && _c !== void 0 ? _c : sizeProp;
20
+ const state = stateProp === "disabled" ? "disabled" : ((_d = groupContext === null || groupContext === void 0 ? void 0 : groupContext.state) !== null && _d !== void 0 ? _d : stateProp);
12
21
  const isDisabled = state === "disabled";
22
+ // Determine checked state
23
+ const isInGroup = groupContext !== undefined;
24
+ const checked = isInGroup ? groupContext.value === value : checkedProp;
25
+ const isControlled = checked !== undefined;
13
26
  const handleChange = (e) => {
14
27
  if (isDisabled)
15
28
  return;
29
+ if (isInGroup) {
30
+ groupContext.onChange(value);
31
+ }
16
32
  onChange === null || onChange === void 0 ? void 0 : onChange(e.target.checked, value);
17
33
  };
18
34
  const inputProps = isControlled
@@ -20,6 +36,7 @@ const Radio = React__default.forwardRef(({ id, name, value, checked, defaultChec
20
36
  : { defaultChecked };
21
37
  return (jsxs("div", { className: cn(radioTheme.baseStyle, className), ...restProps, children: [jsxs("div", { className: cn(radioTheme.inputContainerStyle, radioTheme.sizes[size]), children: [jsx("input", { ref: ref, type: "radio", id: id, name: name, value: value, ...inputProps, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : "default", className: cn("peer", radioTheme.inputStyle, radioTheme.sizes[size]) }), jsx("div", { className: cn(radioTheme.dotStyle, size === "sm" ? "size-2.5 top-0.25" : "size-3") })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (jsx("span", { className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], "mb-0"), children: label })), description && (jsx("div", { id: `${id}-description`, className: radioTheme.descriptionStyle, children: description }))] }))] }));
22
38
  });
23
- Radio.displayName = "Radio";
39
+ RadioComponent.displayName = "Radio";
40
+ const Radio = Object.assign(RadioComponent, { Group: RadioGroup });
24
41
 
25
42
  export { Radio };
@@ -29,11 +29,17 @@ const radioTheme = {
29
29
  "dark:disabled:bg-[var(--color-neutral-700)] dark:disabled:border-[var(--color-neutral-600)] " +
30
30
  "dark:data-[state=disabled]:bg-[var(--color-neutral-700)] dark:data-[state=disabled]:border-[var(--color-neutral-600)]",
31
31
  dotStyle: "absolute inset-0 m-auto pointer-events-none rounded-full bg-[var(--color-primary)] opacity-0 transition-all duration-400 peer-checked:opacity-100 peer-data-[state=disabled]:saturate-0 peer-data-[state=disabled]:peer-checked:opacity-80",
32
- descriptionStyle: "text-sm text-[var(--color-text-secondary)]/80 mt-0.5",
32
+ descriptionStyle: "text-base text-[var(--color-text-secondary)]/80 mt-0.5",
33
33
  sizes: {
34
34
  sm: "size-5 mt-0.25",
35
35
  md: "size-6",
36
36
  },
37
+ // Group styles
38
+ groupStyle: "flex flex-col gap-1.5",
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",
42
+ groupItemsStyle: "flex flex-col gap-3",
37
43
  };
38
44
 
39
45
  export { radioTheme };
@@ -7,21 +7,42 @@ export type RadioState = 'default' | 'disabled';
7
7
  * Size variants for the radio component
8
8
  */
9
9
  export type RadioSize = 'sm' | 'md';
10
+ /**
11
+ * Orientation for Radio.Group layout
12
+ */
13
+ export type RadioGroupOrientation = 'vertical' | 'horizontal';
10
14
  /**
11
15
  * Radio component props
12
16
  */
13
17
  export interface RadioProps extends Omit<React.HTMLAttributes<HTMLInputElement>, 'onChange' | 'size'> {
14
- id: string;
15
- name: string;
16
- value: string;
18
+ id?: string;
19
+ name?: string;
20
+ value: string | number;
17
21
  checked?: boolean;
18
- onChange?: (checked: boolean, value: string) => void;
22
+ onChange?: (checked: boolean, value: string | number) => void;
19
23
  label?: string;
20
24
  description?: string;
21
25
  state?: RadioState;
22
26
  size?: RadioSize;
23
27
  className?: string;
24
28
  }
29
+ /**
30
+ * Radio.Group component props
31
+ */
32
+ export interface RadioGroupProps extends Omit<React.HTMLAttributes<HTMLFieldSetElement>, 'onChange'> {
33
+ value?: string | number;
34
+ defaultValue?: string | number;
35
+ onChange?: (value: string | number) => void;
36
+ name?: string;
37
+ size?: RadioSize;
38
+ state?: RadioState;
39
+ orientation?: RadioGroupOrientation;
40
+ label?: string;
41
+ description?: string;
42
+ error?: string;
43
+ required?: boolean;
44
+ className?: string;
45
+ }
25
46
  /**
26
47
  * Radio theme configuration
27
48
  */
@@ -32,4 +53,9 @@ export interface RadioTheme {
32
53
  dotStyle: string;
33
54
  descriptionStyle: string;
34
55
  sizes: Record<RadioSize, string>;
56
+ groupStyle?: string;
57
+ groupHorizontalStyle?: string;
58
+ groupDescriptionStyle?: string;
59
+ groupErrorStyle?: string;
60
+ groupItemsStyle?: string;
35
61
  }
@@ -0,0 +1,14 @@
1
+ import type { RadioSize, RadioState } from "./Radio.types";
2
+ export interface RadioGroupContextValue {
3
+ name: string;
4
+ value: string | number | undefined;
5
+ onChange: (value: string | number) => void;
6
+ size: RadioSize;
7
+ state: RadioState;
8
+ }
9
+ export declare const RadioGroupContext: import("react").Context<RadioGroupContextValue | undefined>;
10
+ /**
11
+ * Returns the group context, or undefined when used outside a Radio.Group.
12
+ * This allows standalone Radio usage without a Group wrapper.
13
+ */
14
+ export declare function useRadioGroupContext(): RadioGroupContextValue | undefined;
@@ -0,0 +1,12 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ const RadioGroupContext = createContext(undefined);
4
+ /**
5
+ * Returns the group context, or undefined when used outside a Radio.Group.
6
+ * This allows standalone Radio usage without a Group wrapper.
7
+ */
8
+ function useRadioGroupContext() {
9
+ return useContext(RadioGroupContext);
10
+ }
11
+
12
+ export { RadioGroupContext, useRadioGroupContext };
@@ -0,0 +1,3 @@
1
+ import React from "react";
2
+ import type { RadioGroupProps } from "./Radio.types";
3
+ export declare const RadioGroup: React.ForwardRefExoticComponent<RadioGroupProps & React.RefAttributes<HTMLFieldSetElement>>;
@@ -0,0 +1,36 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import React__default, { useId, useState, useMemo } from 'react';
3
+ import { cn } from '../../../utils/cn.js';
4
+ import { radioTheme } from './Radio.theme.js';
5
+ import { formLabelTheme } from '../FormLabel/FormLabel.theme.js';
6
+ import { RadioGroupContext } from './RadioContext.js';
7
+
8
+ const RadioGroup = React__default.forwardRef(({ value: controlledValue, defaultValue, onChange, name, size = "md", state = "default", orientation = "vertical", label, description, error, required, className, children, ...props }, ref) => {
9
+ const autoId = useId();
10
+ const groupName = name !== null && name !== void 0 ? name : `radio-group-${autoId}`;
11
+ const labelId = `${autoId}-label`;
12
+ const descriptionId = `${autoId}-desc`;
13
+ const errorId = `${autoId}-error`;
14
+ // Controlled vs uncontrolled
15
+ const isControlled = controlledValue !== undefined;
16
+ const [internalValue, setInternalValue] = useState(defaultValue);
17
+ const currentValue = isControlled ? controlledValue : internalValue;
18
+ const handleChange = (itemValue) => {
19
+ if (!isControlled) {
20
+ setInternalValue(itemValue);
21
+ }
22
+ onChange === null || onChange === void 0 ? void 0 : onChange(itemValue);
23
+ };
24
+ const contextValue = useMemo(() => ({
25
+ name: groupName,
26
+ value: currentValue,
27
+ onChange: handleChange,
28
+ size,
29
+ state,
30
+ }), [groupName, currentValue, size, state]);
31
+ const labelState = error ? "invalid" : state === "disabled" ? "disabled" : "default";
32
+ return (jsx(RadioGroupContext.Provider, { value: contextValue, children: jsxs("fieldset", { ref: ref, role: "radiogroup", "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", radioTheme.groupStyle, className), ...props, children: [label && (jsxs("legend", { id: labelId, className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[labelState]), children: [label, required && (jsx("span", { className: formLabelTheme.requiredIndicatorStyle, children: "*" }))] })), description && (jsx("p", { id: descriptionId, className: radioTheme.groupDescriptionStyle, children: description })), jsx("div", { className: cn(radioTheme.groupItemsStyle, orientation === "horizontal" && radioTheme.groupHorizontalStyle), children: children }), error && (jsx("p", { id: errorId, role: "alert", className: radioTheme.groupErrorStyle, children: error }))] }) }));
33
+ });
34
+ RadioGroup.displayName = "Radio.Group";
35
+
36
+ export { RadioGroup };
@@ -1,3 +1,3 @@
1
1
  export { Radio } from './Radio';
2
- export type { RadioProps, RadioSize, RadioState } from './Radio.types';
2
+ export type { RadioProps, RadioSize, RadioState, RadioGroupProps, RadioGroupOrientation } from './Radio.types';
3
3
  export { radioTheme } from './Radio.theme';
@@ -558,7 +558,7 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
558
558
  event.preventDefault();
559
559
  event.stopPropagation();
560
560
  handleFontSize(option.key);
561
- }, role: "option", "aria-selected": option.key === formattingState.fontSize, children: jsxs("span", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "uppercase text-xs font-medium", children: option.label }), jsxs("span", { className: "text-xs text-[var(--color-text-muted)]", children: [option.px, "px"] })] }) }, option.key))) }));
561
+ }, role: "option", "aria-selected": option.key === formattingState.fontSize, children: jsxs("span", { className: "flex items-center justify-between gap-3", children: [jsx("span", { className: "uppercase text-sm font-medium", children: option.label }), jsxs("span", { className: "text-sm text-[var(--color-text-muted)]", children: [option.px, "px"] })] }) }, option.key))) }));
562
562
  let renderedDropdown = null;
563
563
  if (isFloatingToolbar && isClient) {
564
564
  const triggerWidth = (_a = fontSizeTriggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width;
@@ -590,7 +590,7 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
590
590
  focusEditor();
591
591
  setShowFontSizePicker((previous) => !previous);
592
592
  setShowColorPicker(false);
593
- }, disabled: isDisabled, "aria-label": "Font size", "aria-expanded": showFontSizePicker, "aria-haspopup": "listbox", children: jsx("span", { className: "text-xs font-medium tracking-wide uppercase", children: currentOption.label }) }), toolbarConfig.position === "floating" ? null : renderedDropdown, toolbarConfig.position === "floating" ? renderedDropdown : null] }, index));
593
+ }, disabled: isDisabled, "aria-label": "Font size", "aria-expanded": showFontSizePicker, "aria-haspopup": "listbox", children: jsx("span", { className: "text-sm font-medium tracking-wide uppercase", children: currentOption.label }) }), toolbarConfig.position === "floating" ? null : renderedDropdown, toolbarConfig.position === "floating" ? renderedDropdown : null] }, index));
594
594
  }
595
595
  case "textColor":
596
596
  {
@@ -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-slate-200 focus:outline-none",
34
34
  // Dropdown item styling
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",
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",
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-xs font-medium " +
63
+ chipOverflowStyle: "inline-flex items-center shrink-0 whitespace-nowrap px-1.5 py-0.5 text-sm font-medium " +
64
64
  "text-[var(--color-text-muted)]",
65
65
  };
66
66
 
@@ -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-sm",
17
+ placeholderStyle: "absolute inset-0 flex items-center justify-center pointer-events-none text-[var(--color-text-placeholder)] text-base",
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-sm font-medium text-[var(--color-text-primary)] mb-2",
7
+ labelStyle: "block text-base 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-8 rounded-full border-2 border-[var(--color-primary)] ring-2 ring-white shadow-sm cursor-grab active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border)] focus-visible:ring-offset-2 transition-all hover:scale-110 disabled:cursor-not-allowed disabled:bg-[var(--color-background-disabled)] disabled:hover:scale-100 data-[disabled]:cursor-not-allowed data-[disabled]:bg-[var(--color-background-disabled)] data-[disabled]:border-[var(--color-border)] data-[disabled]:hover:scale-100 data-[invalid]:border-[var(--color-danger)] data-[invalid]:focus-visible:ring-[var(--color-danger)] glass-effect-5",
18
18
  // Value display
19
- valueDisplayStyle: "text-sm font-medium text-[var(--color-text-secondary)] w-fit min-w-12",
19
+ valueDisplayStyle: "text-base font-medium text-[var(--color-text-secondary)] w-fit min-w-12",
20
20
  // Min/max labels
21
- minMaxLabelStyle: "text-sm font-medium text-[var(--color-text-muted)]",
21
+ minMaxLabelStyle: "text-base font-medium text-[var(--color-text-muted)]",
22
22
  // Tick marks
23
23
  tickStyle: "absolute w-0.5 h-2 bg-white/60 -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 transition-colors cursor-pointer shadow-inner bg-[var(--color-border)] 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)]",
7
7
  thumb: "w-7 h-5 rounded-full shadow-md glass-effect !bg-white/80",
8
- description: "text-sm text-[var(--color-text-muted)] -mt-1.5",
8
+ description: "text-base 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-xs font-semibold text-[var(--color-text-muted)] uppercase",
65
+ wheelHeaderStyle: "text-center py-2 text-sm 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-xs font-semibold rounded " +
80
+ periodToggleStyle: "px-1.5 py-0.5 text-sm 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)] " +
@@ -173,7 +173,7 @@ const TransferList = React__default.forwardRef(({ items, value: controlledValue,
173
173
  return (jsxs("div", { className: cn(theme.panelStyle, sizeStyles.panel), role: "listbox", "aria-label": panelAriaLabel, "aria-multiselectable": "true", children: [jsxs("div", { className: theme.headerStyle, children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx("input", { type: "checkbox", checked: allSelected, ref: (el) => {
174
174
  if (el)
175
175
  el.indeterminate = indeterminate;
176
- }, onChange: () => toggleSelectAll(filteredItems, selected, setSelected), disabled: selectableCount === 0, className: cn(checkboxSize, "rounded-[calc(var(--radius-base)*0.5)] border-2 border-[var(--color-border)] accent-[var(--color-primary)] cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"), "aria-label": `Select all ${title}` }), jsx("span", { className: cn(theme.titleStyle, size === "sm" ? "text-xs" : "text-sm"), children: title })] }), jsxs("span", { className: cn(theme.countStyle, size === "sm" ? "text-xs" : "text-xs"), children: [selectedCount > 0 ? `${selectedCount}/` : "", listItems.length] })] }), searchable && (jsxs("div", { className: theme.searchWrapperStyle, children: [jsx(MagnifyingGlassIcon, { className: cn(searchIconSize, "absolute text-[var(--color-text-placeholder)] pointer-events-none", searchIconPosition) }), jsx("input", { type: "text", value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search...", className: cn(theme.searchInputStyle, sizeStyles.searchInput), "aria-label": `Search ${title}` })] })), jsxs("div", { className: theme.listStyle, children: [jsx(AnimatePresence, { mode: "popLayout", initial: false, children: filteredItems.map((item) => {
176
+ }, onChange: () => toggleSelectAll(filteredItems, selected, setSelected), disabled: selectableCount === 0, className: cn(checkboxSize, "rounded-[calc(var(--radius-base)*0.5)] border-2 border-[var(--color-border)] accent-[var(--color-primary)] cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"), "aria-label": `Select all ${title}` }), jsx("span", { className: cn(theme.titleStyle, size === "sm" ? "text-sm" : "text-base"), children: title })] }), jsxs("span", { className: cn(theme.countStyle, size === "sm" ? "text-sm" : "text-sm"), children: [selectedCount > 0 ? `${selectedCount}/` : "", listItems.length] })] }), searchable && (jsxs("div", { className: theme.searchWrapperStyle, children: [jsx(MagnifyingGlassIcon, { className: cn(searchIconSize, "absolute text-[var(--color-text-placeholder)] pointer-events-none", searchIconPosition) }), jsx("input", { type: "text", value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Search...", className: cn(theme.searchInputStyle, sizeStyles.searchInput), "aria-label": `Search ${title}` })] })), jsxs("div", { className: theme.listStyle, children: [jsx(AnimatePresence, { mode: "popLayout", initial: false, children: filteredItems.map((item) => {
177
177
  const isSelected = selected.has(item.id);
178
178
  const isDisabled = item.disabled === true;
179
179
  const itemContent = (jsxs(Fragment, { children: [jsx("input", { type: "checkbox", checked: isSelected, onChange: () => toggleSelection(item.id, selected, setSelected), disabled: isDisabled, className: cn(checkboxSize, "rounded-[calc(var(--radius-base)*0.5)] border-2 border-[var(--color-border)] accent-[var(--color-primary)] cursor-pointer flex-shrink-0", isDisabled && "cursor-not-allowed"), tabIndex: -1, "aria-hidden": "true" }), jsx("span", { className: "truncate flex-1", children: renderItem ? renderItem(item) : item.label })] }));
@@ -190,7 +190,7 @@ const TransferList = React__default.forwardRef(({ items, value: controlledValue,
190
190
  return (jsx(motion.div, { ...itemProps, layout: true, variants: itemVariants, initial: "initial", animate: "animate", exit: "exit", children: itemContent }));
191
191
  }
192
192
  return jsx("div", { ...itemProps, children: itemContent });
193
- }) }), filteredItems.length === 0 && (jsx("div", { className: cn("flex items-center justify-center text-[var(--color-text-muted)]", size === "sm" ? "py-4 text-xs" : size === "lg" ? "py-8 text-sm" : "py-6 text-xs"), children: search.trim() ? "No matches found" : "No items" }))] })] }));
193
+ }) }), filteredItems.length === 0 && (jsx("div", { className: cn("flex items-center justify-center text-[var(--color-text-muted)]", size === "sm" ? "py-4 text-sm" : size === "lg" ? "py-8 text-base" : "py-6 text-sm"), children: search.trim() ? "No matches found" : "No items" }))] })] }));
194
194
  };
195
195
  return (jsxs("div", { ref: ref, className: cn(theme.baseStyle, className), ...restProps, children: [renderPanel(sourceTitle, sourceItems, filteredSourceItems, sourceSelected, setSourceSelected, sourceSearch, setSourceSearch, sourceAllSelected, sourceIndeterminate, sourceTitle), jsxs("div", { className: theme.actionsStyle, children: [jsx("button", { type: "button", onClick: handleMoveAllToTarget, disabled: sourceItems.filter((i) => !i.disabled).length === 0, className: cn(theme.buttonStyle, sizeStyles.button), "aria-label": "Move all to target", children: jsx(ChevronDoubleRightIcon, { className: iconSize }) }), jsx("button", { type: "button", onClick: handleMoveToTarget, disabled: sourceSelected.size === 0, className: cn(theme.buttonStyle, sizeStyles.button), "aria-label": "Move selected to target", children: jsx(ChevronRightIcon, { className: iconSize }) }), jsx("button", { type: "button", onClick: handleMoveToSource, disabled: targetSelected.size === 0, className: cn(theme.buttonStyle, sizeStyles.button), "aria-label": "Move selected to source", children: jsx(ChevronLeftIcon, { className: iconSize }) }), jsx("button", { type: "button", onClick: handleMoveAllToSource, disabled: targetItems.filter((i) => !i.disabled).length === 0, className: cn(theme.buttonStyle, sizeStyles.button), "aria-label": "Move all to source", children: jsx(ChevronDoubleLeftIcon, { className: iconSize }) })] }), renderPanel(targetTitle, targetItems, filteredTargetItems, targetSelected, setTargetSelected, targetSearch, setTargetSearch, targetAllSelected, targetIndeterminate, targetTitle)] }));
196
196
  });
@@ -38,21 +38,21 @@ const transferListTheme = {
38
38
  sizes: {
39
39
  sm: {
40
40
  panel: "w-56 h-64",
41
- item: "px-2 py-1 text-xs gap-1.5",
41
+ item: "px-2 py-1 text-sm gap-1.5",
42
42
  button: "p-1",
43
- searchInput: "px-2 py-1.5 text-xs pl-7",
43
+ searchInput: "px-2 py-1.5 text-sm pl-7",
44
44
  },
45
45
  md: {
46
46
  panel: "w-72 h-80",
47
- item: "px-3 py-1.5 text-sm gap-2",
47
+ item: "px-3 py-1.5 text-base gap-2",
48
48
  button: "p-1.5",
49
- searchInput: "px-3 py-2 text-sm pl-8",
49
+ searchInput: "px-3 py-2 text-base pl-8",
50
50
  },
51
51
  lg: {
52
52
  panel: "w-80 h-96",
53
- item: "px-3 py-2 text-base gap-2.5",
53
+ item: "px-3 py-2 text-lg gap-2.5",
54
54
  button: "p-2",
55
- searchInput: "px-3 py-2.5 text-base pl-9",
55
+ searchInput: "px-3 py-2.5 text-lg pl-9",
56
56
  },
57
57
  },
58
58
  };
@@ -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-sm text-[var(--color-text-primary)] " +
18
+ controlBaseStyle: "w-full bg-transparent text-base 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-sm",
26
+ text: "text-base",
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-sm",
32
+ text: "text-base",
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-base",
38
+ text: "text-lg",
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-xs text-gray-500 mt-1.5 [.state-disabled_&]:text-gray-400 [.state-invalid_&]:text-[var(--color-danger-500)] " +
75
+ helperText: "text-sm text-gray-500 mt-1.5 [.state-disabled_&]:text-gray-400 [.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-sm border border-[var(--color-border)] " +
92
+ "rounded-[var(--form-radius)] bg-white/90 backdrop-blur-md text-base 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
@@ -11,10 +11,10 @@ export { FormLabel } from "./FormLabel";
11
11
  export type { FormLabelProps, FormLabelState, FormLabelTheme, } from "./FormLabel";
12
12
  export { formLabelTheme } from "./FormLabel";
13
13
  export { Radio } from "./Radio";
14
- export type { RadioProps, RadioSize, RadioState } from "./Radio";
14
+ export type { RadioProps, RadioSize, RadioState, RadioGroupProps, RadioGroupOrientation, } from "./Radio";
15
15
  export { radioTheme } from "./Radio";
16
16
  export { Checkbox } from "./Checkbox";
17
- export type { CheckboxProps, CheckboxSize, CheckboxState } from "./Checkbox";
17
+ export type { CheckboxProps, CheckboxSize, CheckboxState, CheckboxGroupProps, CheckboxGroupOrientation, } from "./Checkbox";
18
18
  export { checkboxTheme } from "./Checkbox";
19
19
  export { Switch } from "./Switch";
20
20
  export type { SwitchProps, SwitchThemeOverrides } from "./Switch";
@@ -1,13 +1,13 @@
1
1
  const formLayoutTheme = {
2
2
  root: "flex flex-col gap-6",
3
3
  header: "border-b border-[var(--color-border)] pb-6",
4
- headerTitle: "text-base font-semibold text-[var(--color-text-primary)]",
5
- headerSubtitle: "text-base text-[var(--color-text-muted)]",
4
+ headerTitle: "text-lg font-semibold text-[var(--color-text-primary)]",
5
+ headerSubtitle: "text-lg text-[var(--color-text-muted)]",
6
6
  body: "flex flex-col gap-6",
7
7
  section: "grid grid-cols-1 md:grid-cols-6 gap-6 md:gap-10",
8
8
  sectionSidebar: "col-span-full md:col-span-2",
9
- sectionTitle: "text-base font-semibold text-[var(--color-text-primary)]",
10
- sectionDescription: "text-sm text-[var(--color-text-muted)] mt-1",
9
+ sectionTitle: "text-lg font-semibold text-[var(--color-text-primary)]",
10
+ sectionDescription: "text-base text-[var(--color-text-muted)] mt-1",
11
11
  sectionAction: "mt-4",
12
12
  sectionContent: "col-span-full md:col-span-4 grid grid-cols-1 sm:grid-cols-2 gap-4",
13
13
  footer: "flex flex-col-reverse sm:flex-row sm:justify-between items-center gap-3 border-t border-[var(--color-border)] pt-3",
@@ -6,6 +6,7 @@ export interface UseSimpleDropdownOptions {
6
6
  offset?: number;
7
7
  estimatedWidth?: number;
8
8
  autoWidth?: boolean;
9
+ onClose?: () => void;
9
10
  }
10
11
  export interface SimpleDropdownReturn {
11
12
  position: {
@@ -24,5 +25,5 @@ export interface SimpleDropdownReturn {
24
25
  * Uses position: fixed with pure viewport coordinates to properly follow scroll
25
26
  * Fixes critical issues: dropdown not following scroll & multiple dropdowns not working
26
27
  */
27
- export declare const useSimpleDropdown: ({ triggerRef, isOpen, placement, offset, estimatedWidth, autoWidth, }: UseSimpleDropdownOptions) => SimpleDropdownReturn;
28
- export declare const useSelectPortal: ({ triggerRef, isOpen, placement, offset, estimatedWidth, autoWidth, }: UseSimpleDropdownOptions) => SimpleDropdownReturn;
28
+ export declare const useSimpleDropdown: ({ triggerRef, isOpen, placement, offset, estimatedWidth, autoWidth, onClose, }: UseSimpleDropdownOptions) => SimpleDropdownReturn;
29
+ export declare const useSelectPortal: ({ triggerRef, isOpen, placement, offset, estimatedWidth, autoWidth, onClose, }: UseSimpleDropdownOptions) => SimpleDropdownReturn;
@@ -1,19 +1,11 @@
1
- import { useRef, useState, useCallback, useLayoutEffect, useMemo } from 'react';
1
+ import { useRef, useState, useCallback, useLayoutEffect } from 'react';
2
2
 
3
- // Simple debounce utility
4
- const debounce = (func, wait) => {
5
- let timeout;
6
- return () => {
7
- clearTimeout(timeout);
8
- timeout = setTimeout(func, wait);
9
- };
10
- };
11
3
  /**
12
4
  * Fixed dropdown positioning hook
13
5
  * Uses position: fixed with pure viewport coordinates to properly follow scroll
14
6
  * Fixes critical issues: dropdown not following scroll & multiple dropdowns not working
15
7
  */
16
- const useSimpleDropdown = ({ triggerRef, isOpen, placement = "bottom-start", offset = 4, estimatedWidth, autoWidth = false, }) => {
8
+ const useSimpleDropdown = ({ triggerRef, isOpen, placement = "bottom-start", offset = 4, estimatedWidth, autoWidth = false, onClose, }) => {
17
9
  const contentRef = useRef(null);
18
10
  const [position, setPosition] = useState({ top: -9999, left: -9999, width: 0 });
19
11
  const [cssVariables, setCssVariables] = useState({});
@@ -280,20 +272,53 @@ const useSimpleDropdown = ({ triggerRef, isOpen, placement = "bottom-start", off
280
272
  return;
281
273
  calculatePosition();
282
274
  }, [isOpen, calculatePosition]);
283
- // Debounced position updates for better performance
284
- const debouncedCalculatePosition = useMemo(() => debounce(calculatePosition, 16), // ~60fps
285
- [calculatePosition]);
286
- // Update position on scroll and resize - debounced for performance
275
+ // rAF-synced scroll handler: skips internal scrolls, detects off-screen trigger
276
+ const scrollRafRef = useRef(null);
277
+ const handleScroll = useCallback((e) => {
278
+ // Skip if scroll originated inside the popover content
279
+ if (contentRef.current && contentRef.current.contains(e.target)) {
280
+ return;
281
+ }
282
+ // Close if trigger has scrolled off-screen
283
+ if (triggerRef.current) {
284
+ const rect = triggerRef.current.getBoundingClientRect();
285
+ const isOffScreen = rect.bottom < 0 || rect.top > window.innerHeight
286
+ || rect.right < 0 || rect.left > window.innerWidth;
287
+ if (isOffScreen && onClose) {
288
+ onClose();
289
+ return;
290
+ }
291
+ }
292
+ if (scrollRafRef.current)
293
+ cancelAnimationFrame(scrollRafRef.current);
294
+ scrollRafRef.current = requestAnimationFrame(() => {
295
+ scrollRafRef.current = null;
296
+ calculatePosition();
297
+ });
298
+ }, [calculatePosition, onClose, triggerRef]);
299
+ const handleResize = useCallback(() => {
300
+ if (scrollRafRef.current)
301
+ cancelAnimationFrame(scrollRafRef.current);
302
+ scrollRafRef.current = requestAnimationFrame(() => {
303
+ scrollRafRef.current = null;
304
+ calculatePosition();
305
+ });
306
+ }, [calculatePosition]);
307
+ // Update position on scroll and resize - rAF-synced for smooth tracking
287
308
  useLayoutEffect(() => {
288
309
  if (!isOpen)
289
310
  return;
290
- window.addEventListener("scroll", debouncedCalculatePosition, { passive: true, capture: true });
291
- window.addEventListener("resize", debouncedCalculatePosition, { passive: true });
311
+ window.addEventListener("scroll", handleScroll, { passive: true, capture: true });
312
+ window.addEventListener("resize", handleResize, { passive: true });
292
313
  return () => {
293
- window.removeEventListener("scroll", debouncedCalculatePosition, true);
294
- window.removeEventListener("resize", debouncedCalculatePosition);
314
+ window.removeEventListener("scroll", handleScroll, true);
315
+ window.removeEventListener("resize", handleResize);
316
+ if (scrollRafRef.current) {
317
+ cancelAnimationFrame(scrollRafRef.current);
318
+ scrollRafRef.current = null;
319
+ }
295
320
  };
296
- }, [isOpen, debouncedCalculatePosition]);
321
+ }, [isOpen, handleScroll, handleResize]);
297
322
  // Recalculate when dropdown content resizes (e.g. search filtering)
298
323
  useLayoutEffect(() => {
299
324
  if (!isOpen)