@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.
- package/dist/components/ai/ApprovalCard/ApprovalCard.js +1 -1
- package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
- package/dist/components/ai/PromptInput/PromptInput.js +2 -2
- package/dist/components/ai/PromptInput/PromptInput.theme.js +1 -1
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +1 -1
- package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +6 -6
- package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +4 -4
- package/dist/components/ai/ThinkingIndicator/ThinkingIndicator.theme.js +2 -2
- package/dist/components/ai/TokenCounter/TokenCounter.theme.js +6 -6
- package/dist/components/charts/ActivityRings/ActivityRings.js +1 -1
- package/dist/components/charts/ActivityRings/ActivityRings.theme.js +2 -2
- package/dist/components/charts/BarChart/BarChart.js +3 -3
- package/dist/components/charts/ChartContainer.js +1 -1
- package/dist/components/charts/ComboChart/ComboChart.js +2 -2
- package/dist/components/charts/DonutChart/DonutChart.theme.js +1 -1
- package/dist/components/charts/DonutChart/MultiSegmentDonutChart.js +1 -1
- package/dist/components/charts/DonutChart/MultiSegmentDonutChart.theme.js +1 -1
- package/dist/components/charts/FunnelChart/FunnelChart.js +7 -7
- package/dist/components/charts/Heatmap/Heatmap.theme.js +1 -1
- package/dist/components/charts/Heatmap/HeatmapCell.js +1 -1
- package/dist/components/charts/Heatmap/HeatmapLegend.js +1 -1
- package/dist/components/charts/RadarChart/RadarChart.theme.js +2 -2
- package/dist/components/charts/ScatterPlot/ScatterPlot.js +1 -1
- package/dist/components/charts/ScatterPlot/ScatterPlot.theme.js +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.js +2 -2
- package/dist/components/charts/shared/ChartErrorBoundary/ChartErrorBoundary.js +1 -1
- package/dist/components/charts/shared/ChartLegend/ChartLegendContent.js +1 -1
- package/dist/components/charts/shared/ChartText/ChartText.js +1 -1
- package/dist/components/charts/shared/ChartText/ChartText.theme.js +1 -1
- package/dist/components/charts/shared/ChartTooltip/ChartTooltip.theme.js +4 -4
- package/dist/components/core/Alert/Alert.js +1 -0
- package/dist/components/core/Alert/Alert.theme.js +4 -8
- package/dist/components/core/Alert/Alert.types.d.ts +2 -1
- package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/core/Avatar/Avatar.theme.js +5 -5
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +1 -1
- package/dist/components/core/Badge/Badge.theme.js +11 -6
- package/dist/components/core/Badge/Badge.types.d.ts +2 -1
- package/dist/components/core/Breadcrumbs/Breadcrumbs.js +1 -1
- package/dist/components/core/Button/Button.theme.js +38 -32
- package/dist/components/core/Button/Button.types.d.ts +2 -1
- package/dist/components/core/ButtonGroup/ButtonGroup.theme.js +1 -1
- package/dist/components/core/Calendar/Calendar.js +1 -1
- package/dist/components/core/Calendar/Calendar.theme.js +5 -5
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +1 -1
- package/dist/components/core/Card/Card.theme.js +2 -2
- package/dist/components/core/CommandPalette/CommandItem.js +1 -1
- package/dist/components/core/CommandPalette/CommandPalette.theme.js +3 -3
- package/dist/components/core/ContextMenu/ContextMenu.theme.js +3 -3
- package/dist/components/core/ContextMenu/ContextMenuItem.js +1 -1
- package/dist/components/core/Drawer/Drawer.theme.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.theme.js +1 -1
- package/dist/components/core/Empty/Empty.theme.js +2 -2
- package/dist/components/core/Kbd/Kbd.theme.js +3 -3
- package/dist/components/core/Loader/Loader.theme.js +1 -1
- package/dist/components/core/MenuItem/MenuItem.theme.js +2 -2
- package/dist/components/core/Message/Message.js +1 -1
- package/dist/components/core/Message/Message.theme.js +4 -4
- package/dist/components/core/Modal/Modal.theme.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +1 -1
- package/dist/components/core/NavItem/NavItem.theme.js +1 -1
- package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +2 -2
- package/dist/components/core/PageHeading/PageHeading.theme.js +1 -1
- package/dist/components/core/Pagination/Pagination.theme.js +2 -2
- package/dist/components/core/Pill/Pill.theme.js +3 -3
- package/dist/components/core/Popover/PopoverContent.js +2 -0
- package/dist/components/core/Progress/Progress.theme.js +10 -7
- package/dist/components/core/Progress/Progress.types.d.ts +2 -1
- package/dist/components/core/Segmented/Segmented.theme.js +3 -3
- package/dist/components/core/Sidebar/SidebarNavGroup.js +1 -1
- package/dist/components/core/Spinner/Spinner.theme.js +3 -5
- package/dist/components/core/Spinner/Spinner.types.d.ts +2 -1
- package/dist/components/core/Tabs/Tabs.theme.js +2 -2
- package/dist/components/core/Tag/Tag.theme.js +2 -2
- package/dist/components/core/Toast/Toast.theme.js +4 -4
- package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
- package/dist/components/core/Tree/Tree.theme.js +1 -1
- package/dist/components/data-display/DescriptionList/DescriptionList.theme.js +2 -2
- package/dist/components/data-display/Feed/Feed.js +1 -1
- package/dist/components/data-display/Feed/Feed.theme.js +2 -2
- package/dist/components/data-display/GanttChart/GanttChart.theme.js +2 -2
- package/dist/components/data-display/GanttChart/GanttChartPanel.js +1 -1
- package/dist/components/data-display/GanttChart/GanttMilestone.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
- package/dist/components/data-display/GanttChart/GanttTaskBar.js +2 -2
- package/dist/components/data-display/KPI/KPI.js +1 -1
- package/dist/components/data-display/KPI/KPI.theme.js +10 -11
- package/dist/components/data-display/KPI/KPI.types.d.ts +2 -1
- package/dist/components/data-display/Metric/Metric.js +1 -1
- package/dist/components/data-display/Metric/Metric.theme.js +11 -7
- package/dist/components/data-display/Metric/Metric.types.d.ts +2 -1
- package/dist/components/data-display/Table/Table.theme.js +3 -3
- package/dist/components/data-display/Table/TableActionsMenu.js +1 -1
- package/dist/components/data-display/Table/TableColumnManager.js +1 -1
- package/dist/components/data-display/Table/TableFilter.js +3 -3
- package/dist/components/data-display/Table/TableHeader.js +1 -1
- package/dist/components/data-display/Table/TablePagination.js +1 -1
- package/dist/components/data-display/Table/TableSelectionHeader.js +1 -1
- package/dist/components/effects/CustomCursor/CustomCursor.js +1 -1
- package/dist/components/effects/CustomCursor/CustomCursor.theme.js +3 -1
- package/dist/components/effects/CustomCursor/CustomCursor.types.d.ts +2 -1
- package/dist/components/effects/StickyScroll/StickyScroll.js +1 -1
- package/dist/components/forms/Checkbox/Checkbox.d.ts +3 -4
- package/dist/components/forms/Checkbox/Checkbox.js +33 -9
- package/dist/components/forms/Checkbox/Checkbox.theme.js +7 -1
- package/dist/components/forms/Checkbox/Checkbox.types.d.ts +28 -2
- package/dist/components/forms/Checkbox/CheckboxContext.d.ts +14 -0
- package/dist/components/forms/Checkbox/CheckboxContext.js +12 -0
- package/dist/components/forms/Checkbox/CheckboxGroup.d.ts +3 -0
- package/dist/components/forms/Checkbox/CheckboxGroup.js +40 -0
- package/dist/components/forms/Checkbox/index.d.ts +1 -1
- package/dist/components/forms/ColorPicker/ColorPicker.js +1 -1
- package/dist/components/forms/ColorPicker/ColorPicker.theme.js +1 -1
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +7 -7
- package/dist/components/forms/CronInput/CronInput.js +1 -1
- package/dist/components/forms/CronInput/CronInput.theme.js +8 -8
- package/dist/components/forms/DatePicker/DatePicker.theme.js +1 -1
- package/dist/components/forms/DatePicker/DatePickerContent.js +1 -1
- package/dist/components/forms/FileUpload/FileUpload.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.theme.js +4 -4
- package/dist/components/forms/FormLabel/FormLabel.theme.js +1 -1
- package/dist/components/forms/Input/Input.theme.js +1 -1
- package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
- package/dist/components/forms/InputOTP/InputOTP.theme.js +1 -1
- package/dist/components/forms/InputTag/InputTag.js +1 -1
- package/dist/components/forms/Mention/Mention.theme.js +3 -3
- package/dist/components/forms/Radio/Radio.d.ts +3 -4
- package/dist/components/forms/Radio/Radio.js +21 -4
- package/dist/components/forms/Radio/Radio.theme.js +7 -1
- package/dist/components/forms/Radio/Radio.types.d.ts +30 -4
- package/dist/components/forms/Radio/RadioContext.d.ts +14 -0
- package/dist/components/forms/Radio/RadioContext.js +12 -0
- package/dist/components/forms/Radio/RadioGroup.d.ts +3 -0
- package/dist/components/forms/Radio/RadioGroup.js +36 -0
- package/dist/components/forms/Radio/index.d.ts +1 -1
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +2 -2
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +1 -1
- package/dist/components/forms/Select/Select.theme.js +1 -1
- package/dist/components/forms/Signature/Signature.theme.js +1 -1
- package/dist/components/forms/Slider/Slider.theme.js +3 -3
- package/dist/components/forms/Switch/Switch.theme.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
- package/dist/components/forms/TransferList/TransferList.js +2 -2
- package/dist/components/forms/TransferList/TransferList.theme.js +6 -6
- package/dist/components/forms/forms.theme.js +6 -6
- package/dist/components/forms/index.d.ts +2 -2
- package/dist/components/layout/FormLayout/FormLayout.theme.js +4 -4
- package/dist/hooks/useSelectPortal.d.ts +3 -2
- package/dist/hooks/useSelectPortal.js +44 -19
- package/dist/styles.css +1 -1
- package/dist/utils/colorUtils.d.ts +23 -0
- package/dist/utils/colorUtils.js +45 -0
- package/dist/utils/index.d.ts +1 -0
- package/package.json +1 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
42
|
+
suggestionLabelStyle: "text-base font-medium truncate",
|
|
43
43
|
// Suggestion description text
|
|
44
|
-
suggestionDescriptionStyle: "text-
|
|
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
|
-
|
|
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
|
|
11
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
15
|
-
name
|
|
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,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 };
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
19
|
+
valueDisplayStyle: "text-base font-medium text-[var(--color-text-secondary)] w-fit min-w-12",
|
|
20
20
|
// Min/max labels
|
|
21
|
-
minMaxLabelStyle: "text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
41
|
+
item: "px-2 py-1 text-sm gap-1.5",
|
|
42
42
|
button: "p-1",
|
|
43
|
-
searchInput: "px-2 py-1.5 text-
|
|
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-
|
|
47
|
+
item: "px-3 py-1.5 text-base gap-2",
|
|
48
48
|
button: "p-1.5",
|
|
49
|
-
searchInput: "px-3 py-2 text-
|
|
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-
|
|
53
|
+
item: "px-3 py-2 text-lg gap-2.5",
|
|
54
54
|
button: "p-2",
|
|
55
|
-
searchInput: "px-3 py-2.5 text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
5
|
-
headerSubtitle: "text-
|
|
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-
|
|
10
|
-
sectionDescription: "text-
|
|
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
|
|
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
|
-
//
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
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",
|
|
291
|
-
window.addEventListener("resize",
|
|
311
|
+
window.addEventListener("scroll", handleScroll, { passive: true, capture: true });
|
|
312
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
292
313
|
return () => {
|
|
293
|
-
window.removeEventListener("scroll",
|
|
294
|
-
window.removeEventListener("resize",
|
|
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,
|
|
321
|
+
}, [isOpen, handleScroll, handleResize]);
|
|
297
322
|
// Recalculate when dropdown content resizes (e.g. search filtering)
|
|
298
323
|
useLayoutEffect(() => {
|
|
299
324
|
if (!isOpen)
|