flikkui 0.2.0-beta.2 → 0.2.0-beta.5
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/README.md +92 -0
- package/dist/components/ai/PromptInput/PromptInput.js +23 -15
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.d.ts +27 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.js +62 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.d.ts +10 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +12 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.types.d.ts +53 -0
- package/dist/components/ai/PromptSuggestions/index.d.ts +4 -2
- package/dist/components/ai/index.d.ts +2 -12
- package/dist/components/charts/ActivityRings/ActivityRings.js +70 -58
- package/dist/components/charts/ActivityRings/ActivityRings.theme.js +0 -1
- package/dist/components/charts/ActivityRings/ActivityRings.types.d.ts +17 -0
- package/dist/components/charts/BarChart/BarChart.js +8 -4
- package/dist/components/charts/BarChart/BarChart.types.d.ts +14 -0
- package/dist/components/charts/DonutChart/DonutChart.js +11 -8
- package/dist/components/charts/DonutChart/DonutChart.theme.d.ts +3 -0
- package/dist/components/charts/DonutChart/DonutChart.theme.js +5 -4
- package/dist/components/charts/DonutChart/donut-utils.d.ts +5 -0
- package/dist/components/charts/DonutChart/donut-utils.js +26 -1
- package/dist/components/charts/Heatmap/Heatmap.theme.js +2 -2
- package/dist/components/charts/shared/ChartAxis/XAxis.d.ts +2 -2
- package/dist/components/charts/shared/ChartAxis/XAxis.js +4 -4
- package/dist/components/charts/shared/ChartAxis/YAxis.d.ts +2 -2
- package/dist/components/charts/shared/ChartAxis/YAxis.js +8 -7
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.d.ts +1 -1
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.js +2 -2
- package/dist/components/charts/theme/chart.theme.d.ts +1 -1
- package/dist/components/charts/theme/chart.theme.js +39 -39
- package/dist/components/core/Accordion/Accordion.d.ts +1 -1
- package/dist/components/core/Accordion/Accordion.js +2 -2
- package/dist/components/core/Accordion/Accordion.types.d.ts +8 -0
- package/dist/components/core/Badge/Badge.js +11 -15
- package/dist/components/core/Badge/Badge.theme.js +7 -21
- package/dist/components/core/Badge/Badge.types.d.ts +9 -1
- package/dist/components/core/Button/Button.js +2 -2
- package/dist/components/core/Button/Button.theme.js +1 -1
- package/dist/components/core/Button/Button.types.d.ts +8 -0
- package/dist/components/core/Card/Card.js +8 -2
- package/dist/components/core/Card/Card.theme.js +1 -1
- package/dist/components/core/Card/Card.types.d.ts +24 -1
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +10 -40
- package/dist/components/core/Drawer/Drawer.theme.js +2 -1
- package/dist/components/core/Drawer/Drawer.types.d.ts +8 -0
- package/dist/components/core/Dropdown/Dropdown.d.ts +1 -1
- package/dist/components/core/Dropdown/Dropdown.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.types.d.ts +8 -0
- package/dist/components/core/Metric/Metric.d.ts +1 -1
- package/dist/components/core/Metric/Metric.js +9 -5
- package/dist/components/core/Metric/Metric.theme.d.ts +1 -1
- package/dist/components/core/Metric/Metric.theme.js +38 -28
- package/dist/components/core/Metric/Metric.types.d.ts +27 -8
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +17 -40
- package/dist/components/core/Modal/Modal.theme.js +8 -3
- package/dist/components/core/Modal/Modal.types.d.ts +18 -0
- package/dist/components/core/Modal/index.d.ts +1 -1
- package/dist/components/core/Notification/Notification.js +2 -0
- package/dist/components/core/Pill/Pill.d.ts +6 -11
- package/dist/components/core/Pill/Pill.theme.d.ts +2 -2
- package/dist/components/core/Pill/Pill.types.d.ts +9 -22
- package/dist/components/core/Pill/index.d.ts +1 -1
- package/dist/components/core/Popover/Popover.d.ts +1 -1
- package/dist/components/core/Popover/Popover.js +2 -2
- package/dist/components/core/Popover/Popover.types.d.ts +8 -0
- package/dist/components/core/Progress/Progress.d.ts +28 -0
- package/dist/components/core/Progress/Progress.js +114 -0
- package/dist/components/core/Progress/Progress.theme.d.ts +5 -0
- package/dist/components/core/Progress/Progress.theme.js +33 -0
- package/dist/components/core/Progress/Progress.types.d.ts +92 -0
- package/dist/components/core/Progress/index.d.ts +2 -0
- package/dist/components/core/Table/Table.animations.d.ts +5 -16
- package/dist/components/core/Table/Table.animations.js +46 -0
- package/dist/components/core/Table/Table.d.ts +0 -27
- package/dist/components/core/Table/Table.js +58 -156
- package/dist/components/core/Table/Table.theme.js +28 -19
- package/dist/components/core/Table/Table.types.d.ts +95 -8
- package/dist/components/core/Table/Table.utils.d.ts +7 -0
- package/dist/components/core/Table/Table.utils.js +11 -1
- package/dist/components/core/Table/{components/TableActions/TableActions.d.ts → TableActions.d.ts} +3 -3
- package/dist/components/core/Table/{components/TableActions/TableActions.js → TableActions.js} +14 -24
- package/dist/components/core/Table/{components/TableActions/TableActionsMenu.d.ts → TableActionsMenu.d.ts} +1 -1
- package/dist/components/core/Table/{components/TableActions/TableActionsMenu.js → TableActionsMenu.js} +4 -4
- package/dist/components/core/Table/{components/core/TableBody.d.ts → TableBody.d.ts} +1 -1
- package/dist/components/core/Table/{components/core/TableBody.js → TableBody.js} +14 -20
- package/dist/components/core/Table/{components/core/TableCell.d.ts → TableCell.d.ts} +1 -9
- package/dist/components/core/Table/{components/core/TableCell.js → TableCell.js} +5 -13
- package/dist/components/core/Table/TableColumnManager.d.ts +3 -0
- package/dist/components/core/Table/TableColumnManager.js +34 -0
- package/dist/components/core/Table/{components/DeclarativeComponents.d.ts → TableDeclarative.d.ts} +1 -1
- package/dist/components/core/Table/{components/DeclarativeComponents.js → TableDeclarative.js} +6 -56
- package/dist/components/core/Table/TableFilter.d.ts +3 -0
- package/dist/components/core/Table/TableFilter.js +122 -0
- package/dist/components/core/Table/{components/core/TableHeader.d.ts → TableHeader.d.ts} +1 -1
- package/dist/components/core/Table/{components/core/TableHeader.js → TableHeader.js} +15 -29
- package/dist/components/core/Table/TablePagination.d.ts +7 -0
- package/dist/components/core/Table/{components/TablePagination/TablePagination.js → TablePagination.js} +5 -16
- package/dist/components/core/Table/TableRow.d.ts +8 -0
- package/dist/components/core/Table/TableRow.js +45 -0
- package/dist/components/core/Table/TableSelectionHeader.d.ts +7 -0
- package/dist/components/core/Table/{components/TableSelectionHeader/TableSelectionHeader.js → TableSelectionHeader.js} +4 -5
- package/dist/components/core/Table/hooks/index.d.ts +10 -0
- package/dist/components/core/Table/hooks/useTableColumns.d.ts +16 -0
- package/dist/components/core/Table/hooks/useTableColumns.js +67 -0
- package/dist/components/core/Table/hooks/useTableExpansion.d.ts +8 -0
- package/dist/components/core/Table/hooks/useTableExpansion.js +15 -0
- package/dist/components/core/Table/hooks/useTableFilter.d.ts +12 -0
- package/dist/components/core/Table/hooks/useTableFilter.js +37 -0
- package/dist/components/core/Table/hooks/useTablePagination.d.ts +12 -0
- package/dist/components/core/Table/hooks/useTablePagination.js +13 -0
- package/dist/components/core/Table/hooks/useTableSelection.d.ts +17 -0
- package/dist/components/core/Table/hooks/useTableSelection.js +40 -0
- package/dist/components/core/Table/index.d.ts +9 -8
- package/dist/components/core/Table/index.js +7 -5
- package/dist/components/core/Tabs/Tabs.js +2 -2
- package/dist/components/core/Tabs/Tabs.types.d.ts +8 -0
- package/dist/components/core/Tag/Tag.animations.d.ts +3 -0
- package/dist/components/core/Tag/Tag.animations.js +31 -0
- package/dist/components/core/Tag/Tag.d.ts +14 -0
- package/dist/components/core/Tag/Tag.js +45 -0
- package/dist/components/core/Tag/Tag.theme.d.ts +2 -0
- package/dist/components/core/Tag/Tag.theme.js +21 -0
- package/dist/components/core/Tag/Tag.types.d.ts +40 -0
- package/dist/components/core/Tag/index.d.ts +3 -0
- package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +3 -3
- package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
- package/dist/components/core/Tooltip/Tooltip.types.d.ts +17 -0
- package/dist/components/core/index.d.ts +2 -1
- package/dist/components/core/index.js +12 -5
- package/dist/components/effects/CustomCursor/CustomCursor.d.ts +0 -13
- package/dist/components/effects/CustomCursor/CustomCursor.js +26 -2
- package/dist/components/effects/CustomCursor/CustomCursor.theme.js +12 -1
- package/dist/components/effects/CustomCursor/CustomCursor.types.d.ts +14 -1
- package/dist/components/forms/Combobox/Combobox.d.ts +25 -0
- package/dist/components/forms/Combobox/Combobox.js +412 -0
- package/dist/components/forms/Combobox/Combobox.theme.d.ts +6 -0
- package/dist/components/forms/Combobox/Combobox.theme.js +60 -0
- package/dist/components/forms/Combobox/Combobox.types.d.ts +111 -0
- package/dist/components/forms/Combobox/index.d.ts +3 -0
- package/dist/components/forms/FileUpload/FileUpload.js +2 -0
- package/dist/components/forms/Input/Input.js +25 -28
- package/dist/components/forms/Input/inputMasks.d.ts +15 -0
- package/dist/components/forms/Input/inputMasks.js +72 -1
- package/dist/components/forms/InputTag/InputTag.d.ts +40 -0
- package/dist/components/forms/InputTag/InputTag.js +491 -0
- package/dist/components/forms/InputTag/InputTag.theme.d.ts +2 -0
- package/dist/components/forms/InputTag/InputTag.theme.js +16 -0
- package/dist/components/forms/InputTag/InputTag.types.d.ts +107 -0
- package/dist/components/forms/InputTag/index.d.ts +3 -0
- package/dist/components/forms/Select/Select.d.ts +101 -2
- package/dist/components/forms/Select/Select.js +128 -132
- package/dist/components/forms/Select/Select.theme.js +10 -14
- package/dist/components/forms/Select/Select.types.d.ts +6 -2
- package/dist/components/forms/Select/index.d.ts +7 -4
- package/dist/components/forms/Select/useSelectState.d.ts +66 -0
- package/dist/components/forms/Select/useSelectState.js +134 -0
- package/dist/components/forms/SelectExpand/SelectExpand.animations.d.ts +20 -0
- package/dist/components/forms/SelectExpand/SelectExpand.animations.js +74 -0
- package/dist/components/forms/SelectExpand/SelectExpand.d.ts +9 -0
- package/dist/components/forms/SelectExpand/SelectExpand.js +223 -0
- package/dist/components/forms/SelectExpand/SelectExpand.theme.d.ts +5 -0
- package/dist/components/forms/SelectExpand/SelectExpand.theme.js +74 -0
- package/dist/components/forms/SelectExpand/SelectExpand.types.d.ts +126 -0
- package/dist/components/forms/SelectExpand/index.d.ts +4 -0
- package/dist/components/forms/Switch/Switch.js +3 -3
- package/dist/components/forms/Switch/Switch.theme.d.ts +1 -1
- package/dist/components/forms/Switch/Switch.theme.js +2 -2
- package/dist/components/forms/TimePicker/TimePicker.animations.d.ts +0 -46
- package/dist/components/forms/TimePicker/TimePicker.d.ts +15 -6
- package/dist/components/forms/TimePicker/TimePicker.js +285 -124
- package/dist/components/forms/TimePicker/TimePicker.theme.d.ts +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +39 -22
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +88 -34
- package/dist/components/forms/TimePicker/TimePickerContent.d.ts +7 -10
- package/dist/components/forms/TimePicker/TimePickerContent.js +149 -16
- package/dist/components/forms/TimePicker/TimePickerTrigger.d.ts +3 -3
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +22 -19
- package/dist/components/forms/TimePicker/WheelColumn.d.ts +14 -0
- package/dist/components/forms/TimePicker/WheelColumn.js +90 -0
- package/dist/components/forms/TimePicker/index.d.ts +4 -1
- package/dist/components/forms/TimePicker/useWheelPicker.d.ts +37 -0
- package/dist/components/forms/TimePicker/useWheelPicker.js +138 -0
- package/dist/components/forms/forms.theme.d.ts +14 -0
- package/dist/components/forms/forms.theme.js +31 -0
- package/dist/components/forms/index.d.ts +9 -3
- package/dist/components/forms/index.js +73 -2
- package/dist/hooks/index.d.ts +0 -4
- package/dist/icons/Icon.d.ts +7 -0
- package/dist/icons/Icon.js +6 -2
- package/dist/index.js +21 -19
- package/dist/styles.css +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/optimisticErrors.js +1 -70
- package/package.json +1 -1
- package/dist/components/ai/EditingIndicator/EditingIndicator.animations.d.ts +0 -31
- package/dist/components/ai/EditingIndicator/EditingIndicator.animations.js +0 -115
- package/dist/components/ai/EditingIndicator/EditingIndicator.d.ts +0 -35
- package/dist/components/ai/EditingIndicator/EditingIndicator.js +0 -94
- package/dist/components/ai/EditingIndicator/EditingIndicator.theme.d.ts +0 -2
- package/dist/components/ai/EditingIndicator/EditingIndicator.theme.js +0 -13
- package/dist/components/ai/EditingIndicator/EditingIndicator.types.d.ts +0 -54
- package/dist/components/ai/EditingIndicator/index.d.ts +0 -9
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.d.ts +0 -3
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.js +0 -126
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.theme.d.ts +0 -2
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.theme.js +0 -8
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.types.d.ts +0 -45
- package/dist/components/ai/GenerativeRenderer/index.d.ts +0 -3
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.animations.d.ts +0 -17
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.animations.js +0 -56
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.d.ts +0 -38
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.js +0 -110
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.theme.d.ts +0 -2
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.theme.js +0 -13
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.types.d.ts +0 -53
- package/dist/components/ai/PresenceIndicator/index.d.ts +0 -8
- package/dist/components/ai/PresenceProvider/PresenceContext.d.ts +0 -24
- package/dist/components/ai/PresenceProvider/PresenceContext.js +0 -34
- package/dist/components/ai/PresenceProvider/PresenceProvider.d.ts +0 -32
- package/dist/components/ai/PresenceProvider/PresenceProvider.js +0 -321
- package/dist/components/ai/PresenceProvider/PresenceProvider.types.d.ts +0 -140
- package/dist/components/ai/PresenceProvider/adapters/MockAdapter.d.ts +0 -102
- package/dist/components/ai/PresenceProvider/adapters/MockAdapter.js +0 -331
- package/dist/components/ai/PresenceProvider/adapters/PresenceAdapter.d.ts +0 -93
- package/dist/components/ai/PresenceProvider/adapters/SupabaseAdapter.d.ts +0 -134
- package/dist/components/ai/PresenceProvider/adapters/WebSocketAdapter.d.ts +0 -149
- package/dist/components/ai/PresenceProvider/adapters/index.d.ts +0 -11
- package/dist/components/ai/PresenceProvider/index.d.ts +0 -10
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.d.ts +0 -27
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.js +0 -61
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.types.d.ts +0 -65
- package/dist/components/ai/VersionSlider/VersionSlider.d.ts +0 -3
- package/dist/components/ai/VersionSlider/VersionSlider.js +0 -97
- package/dist/components/ai/VersionSlider/VersionSlider.theme.d.ts +0 -2
- package/dist/components/ai/VersionSlider/VersionSlider.theme.js +0 -18
- package/dist/components/ai/VersionSlider/VersionSlider.types.d.ts +0 -77
- package/dist/components/ai/VersionSlider/index.d.ts +0 -3
- package/dist/components/core/Pill/Pill.animations.js +0 -25
- package/dist/components/core/Pill/Pill.js +0 -145
- package/dist/components/core/Pill/Pill.theme.js +0 -65
- package/dist/components/core/RetryBoundary/RetryBoundary.d.ts +0 -35
- package/dist/components/core/RetryBoundary/RetryBoundary.js +0 -154
- package/dist/components/core/RetryBoundary/RetryBoundary.theme.d.ts +0 -2
- package/dist/components/core/RetryBoundary/RetryBoundary.theme.js +0 -7
- package/dist/components/core/RetryBoundary/RetryBoundary.types.d.ts +0 -51
- package/dist/components/core/RetryBoundary/index.d.ts +0 -3
- package/dist/components/core/Table/components/TableActions/TableActions.types.d.ts +0 -40
- package/dist/components/core/Table/components/TableActions/index.d.ts +0 -3
- package/dist/components/core/Table/components/TableActionsMenu.d.ts +0 -6
- package/dist/components/core/Table/components/TablePagination/TablePagination.d.ts +0 -17
- package/dist/components/core/Table/components/TablePagination/TablePagination.types.d.ts +0 -21
- package/dist/components/core/Table/components/TablePagination/index.d.ts +0 -2
- package/dist/components/core/Table/components/TableSelectionHeader/TableSelectionHeader.d.ts +0 -15
- package/dist/components/core/Table/components/TableSelectionHeader/index.d.ts +0 -3
- package/dist/components/core/Table/components/core/TableRow.d.ts +0 -3
- package/dist/components/core/Table/components/core/TableRow.js +0 -44
- package/dist/components/core/Table/components/core/index.d.ts +0 -4
- package/dist/components/forms/OptimisticForm/OptimisticForm.d.ts +0 -33
- package/dist/components/forms/OptimisticForm/OptimisticForm.js +0 -87
- package/dist/components/forms/OptimisticForm/OptimisticForm.theme.d.ts +0 -2
- package/dist/components/forms/OptimisticForm/OptimisticForm.theme.js +0 -8
- package/dist/components/forms/OptimisticForm/OptimisticForm.types.d.ts +0 -74
- package/dist/components/forms/OptimisticForm/index.d.ts +0 -3
- package/dist/hooks/useOptimisticMutation.d.ts +0 -109
- package/dist/hooks/useOptimisticMutation.js +0 -171
- package/dist/hooks/usePresence.d.ts +0 -88
- package/dist/utils/presenceUtils.d.ts +0 -66
- package/dist/utils/presenceUtils.js +0 -107
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SelectOption } from "./Select.types";
|
|
2
|
+
/**
|
|
3
|
+
* Props for the useSelectState hook
|
|
4
|
+
*/
|
|
5
|
+
export interface UseSelectStateProps<T = any> {
|
|
6
|
+
/** Array of available options */
|
|
7
|
+
options: SelectOption<T>[];
|
|
8
|
+
/** Currently selected value (controlled) */
|
|
9
|
+
value?: T;
|
|
10
|
+
/** Callback when selection changes */
|
|
11
|
+
onChange?: (value: T) => void;
|
|
12
|
+
/** Whether filtering/search is enabled */
|
|
13
|
+
searchable?: boolean;
|
|
14
|
+
/** Whether new options can be created */
|
|
15
|
+
creatable?: boolean;
|
|
16
|
+
/** Callback when creating a new option */
|
|
17
|
+
onCreateOption?: (inputValue: string) => void;
|
|
18
|
+
/** Label template for create option */
|
|
19
|
+
createLabel?: string | ((inputValue: string) => string);
|
|
20
|
+
/** Whether typeahead is enabled (default: true) */
|
|
21
|
+
enableTypeahead?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Return type for the useSelectState hook
|
|
25
|
+
*/
|
|
26
|
+
export interface UseSelectStateReturn<T = any> {
|
|
27
|
+
isOpen: boolean;
|
|
28
|
+
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
29
|
+
highlightedIndex: number;
|
|
30
|
+
setHighlightedIndex: React.Dispatch<React.SetStateAction<number>>;
|
|
31
|
+
searchValue: string;
|
|
32
|
+
setSearchValue: React.Dispatch<React.SetStateAction<string>>;
|
|
33
|
+
selectedValue: T | undefined;
|
|
34
|
+
setSelectedValue: React.Dispatch<React.SetStateAction<T | undefined>>;
|
|
35
|
+
typeaheadValue: string;
|
|
36
|
+
setTypeaheadValue: React.Dispatch<React.SetStateAction<string>>;
|
|
37
|
+
typeaheadTimeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
|
|
38
|
+
filteredOptions: SelectOption<T>[];
|
|
39
|
+
selectedOption: SelectOption<T> | undefined;
|
|
40
|
+
hasCreateOption: boolean;
|
|
41
|
+
selectOption: (value: T, isCreateAction?: boolean) => void;
|
|
42
|
+
getOptionByValue: (searchValue: T) => SelectOption<T> | undefined;
|
|
43
|
+
triggerRef: React.MutableRefObject<HTMLElement | null>;
|
|
44
|
+
optionsRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
45
|
+
options: SelectOption<T>[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* useSelectState
|
|
49
|
+
*
|
|
50
|
+
* A headless hook that manages all state for select/dropdown components.
|
|
51
|
+
* This hook can be used by Select, Combobox, MultiSelect, TagInput, etc.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* const selectState = useSelectState({
|
|
56
|
+
* options,
|
|
57
|
+
* value,
|
|
58
|
+
* onChange,
|
|
59
|
+
* searchable: true,
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* // Use in your component:
|
|
63
|
+
* const { isOpen, setIsOpen, filteredOptions, selectOption } = selectState;
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare function useSelectState<T = any>({ options, value, onChange, searchable, creatable, onCreateOption, createLabel, enableTypeahead, }: UseSelectStateProps<T>): UseSelectStateReturn<T>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useSelectState
|
|
5
|
+
*
|
|
6
|
+
* A headless hook that manages all state for select/dropdown components.
|
|
7
|
+
* This hook can be used by Select, Combobox, MultiSelect, TagInput, etc.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const selectState = useSelectState({
|
|
12
|
+
* options,
|
|
13
|
+
* value,
|
|
14
|
+
* onChange,
|
|
15
|
+
* searchable: true,
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // Use in your component:
|
|
19
|
+
* const { isOpen, setIsOpen, filteredOptions, selectOption } = selectState;
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function useSelectState({ options = [], value, onChange, searchable = false, creatable = false, onCreateOption, createLabel, enableTypeahead = true, }) {
|
|
23
|
+
// Core state
|
|
24
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
25
|
+
const [selectedValue, setSelectedValue] = useState(value);
|
|
26
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
27
|
+
const [searchValue, setSearchValue] = useState("");
|
|
28
|
+
// Typeahead state
|
|
29
|
+
const [typeaheadValue, setTypeaheadValue] = useState("");
|
|
30
|
+
const typeaheadTimeoutRef = useRef(null);
|
|
31
|
+
// Refs for trigger and options container
|
|
32
|
+
const triggerRef = useRef(null);
|
|
33
|
+
const optionsRef = useRef(null);
|
|
34
|
+
// Sync internal state with controlled value
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
setSelectedValue(value);
|
|
37
|
+
}, [value]);
|
|
38
|
+
// Get option by value
|
|
39
|
+
const getOptionByValue = useCallback((searchVal) => {
|
|
40
|
+
return options.find((option) => option.value === searchVal);
|
|
41
|
+
}, [options]);
|
|
42
|
+
// Selected option derived from selectedValue
|
|
43
|
+
const selectedOption = useMemo(() => {
|
|
44
|
+
return selectedValue !== undefined
|
|
45
|
+
? getOptionByValue(selectedValue)
|
|
46
|
+
: undefined;
|
|
47
|
+
}, [selectedValue, getOptionByValue]);
|
|
48
|
+
// Filter options based on search query
|
|
49
|
+
const baseFilteredOptions = useMemo(() => {
|
|
50
|
+
if (!searchValue)
|
|
51
|
+
return options;
|
|
52
|
+
return options.filter((option) => option.label.toLowerCase().includes(searchValue.toLowerCase()));
|
|
53
|
+
}, [options, searchValue]);
|
|
54
|
+
// Check if we should show the create option
|
|
55
|
+
const hasCreateOption = useMemo(() => {
|
|
56
|
+
return (creatable &&
|
|
57
|
+
searchable &&
|
|
58
|
+
searchValue.trim() !== "" &&
|
|
59
|
+
!baseFilteredOptions.some((option) => option.label.toLowerCase() === searchValue.toLowerCase()));
|
|
60
|
+
}, [creatable, searchable, searchValue, baseFilteredOptions]);
|
|
61
|
+
// Final filtered options with create option if applicable
|
|
62
|
+
const filteredOptions = useMemo(() => {
|
|
63
|
+
if (!hasCreateOption)
|
|
64
|
+
return baseFilteredOptions;
|
|
65
|
+
const createOptionLabel = typeof createLabel === "function"
|
|
66
|
+
? createLabel(searchValue)
|
|
67
|
+
: createLabel || `Create "${searchValue}"`;
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
id: "__create__",
|
|
71
|
+
label: createOptionLabel,
|
|
72
|
+
value: searchValue,
|
|
73
|
+
},
|
|
74
|
+
...baseFilteredOptions,
|
|
75
|
+
];
|
|
76
|
+
}, [hasCreateOption, baseFilteredOptions, searchValue, createLabel]);
|
|
77
|
+
// Select an option
|
|
78
|
+
const selectOption = useCallback((newValue, isCreateAction = false) => {
|
|
79
|
+
// Handle create action callback
|
|
80
|
+
if (isCreateAction && onCreateOption) {
|
|
81
|
+
onCreateOption(String(newValue));
|
|
82
|
+
}
|
|
83
|
+
// Update state if value changed
|
|
84
|
+
if (newValue !== selectedValue) {
|
|
85
|
+
setSelectedValue(newValue);
|
|
86
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
|
|
87
|
+
}
|
|
88
|
+
// Close and reset
|
|
89
|
+
setIsOpen(false);
|
|
90
|
+
setSearchValue("");
|
|
91
|
+
}, [selectedValue, onChange, onCreateOption]);
|
|
92
|
+
// Reset state when closing
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!isOpen) {
|
|
95
|
+
setSearchValue("");
|
|
96
|
+
setHighlightedIndex(-1);
|
|
97
|
+
setTypeaheadValue("");
|
|
98
|
+
// Clear any pending typeahead timeout
|
|
99
|
+
if (typeaheadTimeoutRef.current) {
|
|
100
|
+
clearTimeout(typeaheadTimeoutRef.current);
|
|
101
|
+
typeaheadTimeoutRef.current = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}, [isOpen]);
|
|
105
|
+
return {
|
|
106
|
+
// Core state
|
|
107
|
+
isOpen,
|
|
108
|
+
setIsOpen,
|
|
109
|
+
highlightedIndex,
|
|
110
|
+
setHighlightedIndex,
|
|
111
|
+
searchValue,
|
|
112
|
+
setSearchValue,
|
|
113
|
+
selectedValue,
|
|
114
|
+
setSelectedValue,
|
|
115
|
+
// Typeahead state
|
|
116
|
+
typeaheadValue,
|
|
117
|
+
setTypeaheadValue,
|
|
118
|
+
typeaheadTimeoutRef,
|
|
119
|
+
// Derived state
|
|
120
|
+
filteredOptions,
|
|
121
|
+
selectedOption,
|
|
122
|
+
hasCreateOption,
|
|
123
|
+
// Actions
|
|
124
|
+
selectOption,
|
|
125
|
+
getOptionByValue,
|
|
126
|
+
// Refs
|
|
127
|
+
triggerRef,
|
|
128
|
+
optionsRef,
|
|
129
|
+
// Original options
|
|
130
|
+
options,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { useSelectState };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Variants } from "motion/react";
|
|
2
|
+
/**
|
|
3
|
+
* Container expand/collapse animation variants
|
|
4
|
+
* Uses scale animation from center with spring physics
|
|
5
|
+
*/
|
|
6
|
+
export declare const containerAnimations: Variants;
|
|
7
|
+
/**
|
|
8
|
+
* Options panel content animation variants
|
|
9
|
+
* Scale and fade from center with spring physics
|
|
10
|
+
*/
|
|
11
|
+
export declare const optionsPanelAnimations: Variants;
|
|
12
|
+
/**
|
|
13
|
+
* Chevron rotation animation variants with spring
|
|
14
|
+
*/
|
|
15
|
+
export declare const chevronAnimations: Variants;
|
|
16
|
+
/**
|
|
17
|
+
* Individual option animation for staggered reveal
|
|
18
|
+
* Can be used with staggerChildren on parent
|
|
19
|
+
*/
|
|
20
|
+
export declare const optionAnimations: Variants;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spring configuration for smooth, natural animations
|
|
3
|
+
*/
|
|
4
|
+
const springConfig = {
|
|
5
|
+
type: "spring",
|
|
6
|
+
stiffness: 400,
|
|
7
|
+
damping: 30,
|
|
8
|
+
mass: 1,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Container expand/collapse animation variants
|
|
12
|
+
* Uses scale animation from center with spring physics
|
|
13
|
+
*/
|
|
14
|
+
const containerAnimations = {
|
|
15
|
+
collapsed: {
|
|
16
|
+
scaleY: 1,
|
|
17
|
+
transition: springConfig,
|
|
18
|
+
},
|
|
19
|
+
expanded: {
|
|
20
|
+
scaleY: 1,
|
|
21
|
+
transition: springConfig,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Options panel content animation variants
|
|
26
|
+
* Scale and fade from center with spring physics
|
|
27
|
+
*/
|
|
28
|
+
const optionsPanelAnimations = {
|
|
29
|
+
collapsed: {
|
|
30
|
+
opacity: 0,
|
|
31
|
+
scaleY: 0.8,
|
|
32
|
+
scaleX: 0.98,
|
|
33
|
+
transition: {
|
|
34
|
+
type: "spring",
|
|
35
|
+
stiffness: 500,
|
|
36
|
+
damping: 30,
|
|
37
|
+
mass: 0.8,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
expanded: {
|
|
41
|
+
opacity: 1,
|
|
42
|
+
scaleY: 1,
|
|
43
|
+
scaleX: 1,
|
|
44
|
+
transition: {
|
|
45
|
+
type: "spring",
|
|
46
|
+
stiffness: 400,
|
|
47
|
+
damping: 25,
|
|
48
|
+
mass: 0.8,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Chevron rotation animation variants with spring
|
|
54
|
+
*/
|
|
55
|
+
const chevronAnimations = {
|
|
56
|
+
collapsed: {
|
|
57
|
+
rotate: 0,
|
|
58
|
+
transition: {
|
|
59
|
+
type: "spring",
|
|
60
|
+
stiffness: 400,
|
|
61
|
+
damping: 25,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
expanded: {
|
|
65
|
+
rotate: 180,
|
|
66
|
+
transition: {
|
|
67
|
+
type: "spring",
|
|
68
|
+
stiffness: 400,
|
|
69
|
+
damping: 25,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { chevronAnimations, containerAnimations, optionsPanelAnimations };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SelectExpandProps } from "./SelectExpand.types";
|
|
3
|
+
/**
|
|
4
|
+
* SelectExpand component - an in-place expanding select
|
|
5
|
+
* Instead of a dropdown, options expand below the trigger with smooth animation
|
|
6
|
+
*
|
|
7
|
+
* @template T - The type of the option values
|
|
8
|
+
*/
|
|
9
|
+
export declare const SelectExpand: <T extends string | number = string>({ options, value, onChange, placeholder, multiple, searchable, searchPlaceholder, size, maxHeight, disabled, state, label, helperText, iconStart, iconEnd, id, name, required, className, wrapperClassName, theme: themeOverrides, ...restProps }: SelectExpandProps<T>) => React.JSX.Element;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import React__default, { useState, useRef, useMemo, useCallback, useEffect } from 'react';
|
|
2
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
3
|
+
import { ChevronDownIcon, MagnifyingGlassIcon, CheckIcon } from '@heroicons/react/24/outline';
|
|
4
|
+
import { selectExpandTheme } from './SelectExpand.theme.js';
|
|
5
|
+
import { containerAnimations, chevronAnimations, optionsPanelAnimations } from './SelectExpand.animations.js';
|
|
6
|
+
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
7
|
+
import { cn } from '../../../utils/cn.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* SelectExpand component - an in-place expanding select
|
|
11
|
+
* Instead of a dropdown, options expand below the trigger with smooth animation
|
|
12
|
+
*
|
|
13
|
+
* @template T - The type of the option values
|
|
14
|
+
*/
|
|
15
|
+
const SelectExpand = ({ options, value, onChange, placeholder = "Select an option", multiple = false, searchable = false, searchPlaceholder = "Search...", size = "md", maxHeight = "20rem", disabled = false, state = "default", label, helperText, iconStart, iconEnd, id, name, required = false, className, wrapperClassName, theme: themeOverrides, ...restProps }) => {
|
|
16
|
+
var _a;
|
|
17
|
+
// Merge theme with overrides
|
|
18
|
+
const theme = {
|
|
19
|
+
...selectExpandTheme,
|
|
20
|
+
...themeOverrides,
|
|
21
|
+
};
|
|
22
|
+
// State
|
|
23
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
24
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
25
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
26
|
+
// Refs
|
|
27
|
+
const containerRef = useRef(null);
|
|
28
|
+
const triggerRef = useRef(null);
|
|
29
|
+
const searchInputRef = useRef(null);
|
|
30
|
+
const optionsListRef = useRef(null);
|
|
31
|
+
// Determine if disabled
|
|
32
|
+
const isDisabled = disabled || state === "disabled";
|
|
33
|
+
// Generate unique ID
|
|
34
|
+
const componentId = id || `select-expand-${Math.random().toString(36).substring(2, 9)}`;
|
|
35
|
+
// Parse value to array for consistent handling
|
|
36
|
+
const selectedValues = useMemo(() => {
|
|
37
|
+
if (value === undefined || value === null)
|
|
38
|
+
return [];
|
|
39
|
+
return Array.isArray(value) ? value : [value];
|
|
40
|
+
}, [value]);
|
|
41
|
+
// Filter options based on search query
|
|
42
|
+
const filteredOptions = useMemo(() => {
|
|
43
|
+
if (!searchQuery.trim())
|
|
44
|
+
return options;
|
|
45
|
+
const query = searchQuery.toLowerCase();
|
|
46
|
+
return options.filter((option) => {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
return option.label.toLowerCase().includes(query) ||
|
|
49
|
+
((_b = (_a = option.description) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(query)) !== null && _b !== void 0 ? _b : false);
|
|
50
|
+
});
|
|
51
|
+
}, [options, searchQuery]);
|
|
52
|
+
// Get display value for trigger
|
|
53
|
+
const displayValue = useMemo(() => {
|
|
54
|
+
var _a, _b;
|
|
55
|
+
if (selectedValues.length === 0)
|
|
56
|
+
return null;
|
|
57
|
+
if (multiple) {
|
|
58
|
+
if (selectedValues.length === 1) {
|
|
59
|
+
const option = options.find((o) => o.value === selectedValues[0]);
|
|
60
|
+
return (_a = option === null || option === void 0 ? void 0 : option.label) !== null && _a !== void 0 ? _a : String(selectedValues[0]);
|
|
61
|
+
}
|
|
62
|
+
return `${selectedValues.length} selected`;
|
|
63
|
+
}
|
|
64
|
+
const option = options.find((o) => o.value === selectedValues[0]);
|
|
65
|
+
return (_b = option === null || option === void 0 ? void 0 : option.label) !== null && _b !== void 0 ? _b : String(selectedValues[0]);
|
|
66
|
+
}, [selectedValues, options, multiple]);
|
|
67
|
+
// Check if a value is selected
|
|
68
|
+
const isSelected = useCallback((optionValue) => {
|
|
69
|
+
return selectedValues.includes(optionValue);
|
|
70
|
+
}, [selectedValues]);
|
|
71
|
+
// Handle option selection
|
|
72
|
+
const handleSelect = useCallback((optionValue) => {
|
|
73
|
+
if (!onChange)
|
|
74
|
+
return;
|
|
75
|
+
if (multiple) {
|
|
76
|
+
const newValues = isSelected(optionValue)
|
|
77
|
+
? selectedValues.filter((v) => v !== optionValue)
|
|
78
|
+
: [...selectedValues, optionValue];
|
|
79
|
+
onChange(newValues);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
onChange(optionValue);
|
|
83
|
+
setIsOpen(false);
|
|
84
|
+
}
|
|
85
|
+
}, [multiple, selectedValues, onChange, isSelected]);
|
|
86
|
+
// Handle trigger click
|
|
87
|
+
const handleTriggerClick = () => {
|
|
88
|
+
if (isDisabled)
|
|
89
|
+
return;
|
|
90
|
+
setIsOpen((prev) => !prev);
|
|
91
|
+
};
|
|
92
|
+
// Handle click outside
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!isOpen)
|
|
95
|
+
return;
|
|
96
|
+
const handleClickOutside = (event) => {
|
|
97
|
+
if (containerRef.current &&
|
|
98
|
+
!containerRef.current.contains(event.target)) {
|
|
99
|
+
setIsOpen(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
103
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
104
|
+
}, [isOpen]);
|
|
105
|
+
// Focus search input when opening
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (isOpen && searchable && searchInputRef.current) {
|
|
108
|
+
// Small delay to allow animation to start
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
var _a;
|
|
111
|
+
(_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
112
|
+
}, 50);
|
|
113
|
+
return () => clearTimeout(timer);
|
|
114
|
+
}
|
|
115
|
+
}, [isOpen, searchable]);
|
|
116
|
+
// Reset search and highlighted index when closing
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (!isOpen) {
|
|
119
|
+
setSearchQuery("");
|
|
120
|
+
setHighlightedIndex(-1);
|
|
121
|
+
}
|
|
122
|
+
}, [isOpen]);
|
|
123
|
+
// Handle keyboard navigation
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!isOpen)
|
|
126
|
+
return;
|
|
127
|
+
const handleKeyDown = (event) => {
|
|
128
|
+
var _a;
|
|
129
|
+
switch (event.key) {
|
|
130
|
+
case "Escape":
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
setIsOpen(false);
|
|
133
|
+
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
134
|
+
break;
|
|
135
|
+
case "Tab":
|
|
136
|
+
setIsOpen(false);
|
|
137
|
+
break;
|
|
138
|
+
case "ArrowDown":
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
setHighlightedIndex((prev) => prev >= filteredOptions.length - 1 ? 0 : prev + 1);
|
|
141
|
+
break;
|
|
142
|
+
case "ArrowUp":
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
setHighlightedIndex((prev) => prev <= 0 ? filteredOptions.length - 1 : prev - 1);
|
|
145
|
+
break;
|
|
146
|
+
case "Enter":
|
|
147
|
+
case " ":
|
|
148
|
+
if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
const option = filteredOptions[highlightedIndex];
|
|
151
|
+
if (!option.disabled) {
|
|
152
|
+
handleSelect(option.value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
159
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
160
|
+
}, [isOpen, highlightedIndex, filteredOptions, handleSelect]);
|
|
161
|
+
// Scroll highlighted option into view
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (highlightedIndex >= 0 && optionsListRef.current) {
|
|
164
|
+
const option = optionsListRef.current.querySelector(`[data-option-index="${highlightedIndex}"]`);
|
|
165
|
+
if (option) {
|
|
166
|
+
option.scrollIntoView({ block: "nearest" });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}, [highlightedIndex]);
|
|
170
|
+
// Calculate max height value
|
|
171
|
+
const maxHeightValue = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
|
|
172
|
+
// CSS variable for collapsed height based on size
|
|
173
|
+
const collapsedHeightMap = {
|
|
174
|
+
sm: "var(--form-min-h-sm)",
|
|
175
|
+
md: "var(--form-min-h-md)",
|
|
176
|
+
lg: "var(--form-min-h-lg)",
|
|
177
|
+
};
|
|
178
|
+
return (React__default.createElement("div", { className: cn(theme.wrapperStyle, isOpen && "z-[100]", wrapperClassName), ...restProps },
|
|
179
|
+
label && (React__default.createElement(FormLabel, { htmlFor: componentId, required: required }, label)),
|
|
180
|
+
React__default.createElement("div", { className: "invisible", "aria-hidden": "true", style: { height: collapsedHeightMap[size] } }),
|
|
181
|
+
React__default.createElement(motion.div, { ref: containerRef, className: cn(theme.containerStyle, isOpen && theme.containerExpandedStyle, isDisabled && theme.disabledStyle), style: {
|
|
182
|
+
"--collapsed-height": collapsedHeightMap[size],
|
|
183
|
+
}, variants: containerAnimations, initial: "collapsed", animate: isOpen ? "expanded" : "collapsed" },
|
|
184
|
+
React__default.createElement("button", { ref: triggerRef, id: componentId, type: "button", className: cn(theme.triggerStyle, (_a = theme.triggerSizes) === null || _a === void 0 ? void 0 : _a[size], className), onClick: handleTriggerClick, disabled: isDisabled, "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": `${componentId}-options` },
|
|
185
|
+
iconStart && (React__default.createElement("span", { className: cn(theme.iconStartStyle, "mr-2") }, iconStart)),
|
|
186
|
+
React__default.createElement("span", { className: cn("flex-1 truncate text-left", displayValue ? theme.valueStyle : theme.placeholderStyle) }, displayValue || placeholder),
|
|
187
|
+
React__default.createElement(motion.span, { className: theme.iconEndStyle, variants: chevronAnimations, initial: "collapsed", animate: isOpen ? "expanded" : "collapsed" }, iconEnd || React__default.createElement(ChevronDownIcon, { className: "size-4" }))),
|
|
188
|
+
React__default.createElement(AnimatePresence, null, isOpen && (React__default.createElement(motion.div, { className: theme.optionsPanelStyle, style: { transformOrigin: "top center" }, variants: optionsPanelAnimations, initial: "collapsed", animate: "expanded", exit: "collapsed" },
|
|
189
|
+
searchable && (React__default.createElement("div", { className: theme.searchContainerStyle },
|
|
190
|
+
React__default.createElement("div", { className: "flex items-center gap-2" },
|
|
191
|
+
React__default.createElement(MagnifyingGlassIcon, { className: "size-4 shrink-0 text-[var(--color-text-muted)]" }),
|
|
192
|
+
React__default.createElement("input", { ref: searchInputRef, type: "text", className: theme.searchInputStyle, placeholder: searchPlaceholder, value: searchQuery, onChange: (e) => {
|
|
193
|
+
setSearchQuery(e.target.value);
|
|
194
|
+
setHighlightedIndex(-1);
|
|
195
|
+
}, "aria-label": "Search options" })))),
|
|
196
|
+
React__default.createElement("div", { ref: optionsListRef, id: `${componentId}-options`, role: "listbox", "aria-multiselectable": multiple, className: theme.optionsListStyle, style: { maxHeight: maxHeightValue } }, filteredOptions.length === 0 ? (React__default.createElement("div", { className: "py-3 px-3 text-center text-sm text-[var(--color-text-muted)]" }, "No options found")) : (filteredOptions.map((option, index) => {
|
|
197
|
+
const optionSelected = isSelected(option.value);
|
|
198
|
+
const isHighlighted = index === highlightedIndex;
|
|
199
|
+
return (React__default.createElement("div", { key: option.id, role: "option", "aria-selected": optionSelected, "aria-disabled": option.disabled, "data-option-index": index, className: cn(theme.optionStyle, theme.optionHoverStyle, optionSelected && !multiple && theme.optionSelectedStyle, option.disabled && theme.optionDisabledStyle, isHighlighted &&
|
|
200
|
+
"bg-[var(--color-primary-50)]/50 text-[var(--color-primary)] dark:bg-[var(--color-primary-900)]/20"), onClick: () => {
|
|
201
|
+
if (!option.disabled) {
|
|
202
|
+
handleSelect(option.value);
|
|
203
|
+
}
|
|
204
|
+
}, onMouseEnter: () => {
|
|
205
|
+
if (!option.disabled) {
|
|
206
|
+
setHighlightedIndex(index);
|
|
207
|
+
}
|
|
208
|
+
} },
|
|
209
|
+
multiple && (React__default.createElement("span", { className: cn(theme.checkboxStyle, optionSelected && theme.checkboxCheckedStyle) }, optionSelected && (React__default.createElement(CheckIcon, { className: "size-3", strokeWidth: 3 })))),
|
|
210
|
+
React__default.createElement("div", { className: "flex-1 min-w-0" },
|
|
211
|
+
React__default.createElement("span", { className: "block truncate" }, option.label),
|
|
212
|
+
option.description && (React__default.createElement("span", { className: "block text-xs text-[var(--color-text-muted)] truncate" }, option.description))),
|
|
213
|
+
!multiple && optionSelected && (React__default.createElement(CheckIcon, { className: "size-4 shrink-0 text-[var(--color-primary)]", strokeWidth: 2 }))));
|
|
214
|
+
}))))))),
|
|
215
|
+
helperText && (React__default.createElement("p", { className: theme.helperTextStyle }, helperText)),
|
|
216
|
+
name && (React__default.createElement("input", { type: "hidden", name: name, value: multiple
|
|
217
|
+
? selectedValues.join(",")
|
|
218
|
+
: selectedValues[0] !== undefined
|
|
219
|
+
? String(selectedValues[0])
|
|
220
|
+
: "" }))));
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export { SelectExpand };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { formsBaseTheme } from '../forms.theme.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default theme for the SelectExpand component
|
|
5
|
+
*/
|
|
6
|
+
const selectExpandTheme = {
|
|
7
|
+
// Wrapper style - maintains layout footprint
|
|
8
|
+
wrapperStyle: "relative inline-block w-full",
|
|
9
|
+
// Container style - the animated expanding container
|
|
10
|
+
containerStyle: "absolute top-0 left-0 w-full z-[100] overflow-hidden rounded-[var(--form-rounded)] " +
|
|
11
|
+
"border border-[var(--color-border)] bg-white transition-shadow " +
|
|
12
|
+
"dark:bg-[var(--color-neutral-900)] dark:border-[var(--color-neutral-700)]",
|
|
13
|
+
// Container expanded state
|
|
14
|
+
containerExpandedStyle: "ring-2 ring-[var(--color-primary)] border-[var(--color-primary)] shadow-lg " +
|
|
15
|
+
"dark:ring-[var(--color-primary-500)] dark:border-[var(--color-primary-500)]",
|
|
16
|
+
// Trigger button style
|
|
17
|
+
triggerStyle: "w-full flex items-center justify-between cursor-pointer bg-transparent " +
|
|
18
|
+
"text-left focus:outline-none",
|
|
19
|
+
// Trigger size variants
|
|
20
|
+
triggerSizes: {
|
|
21
|
+
sm: `${formsBaseTheme.sizes.sm.text} ${formsBaseTheme.sizes.sm.padding} ${formsBaseTheme.sizes.sm.height}`,
|
|
22
|
+
md: `${formsBaseTheme.sizes.md.text} ${formsBaseTheme.sizes.md.padding} ${formsBaseTheme.sizes.md.height}`,
|
|
23
|
+
lg: `${formsBaseTheme.sizes.lg.text} ${formsBaseTheme.sizes.lg.padding} ${formsBaseTheme.sizes.lg.height}`,
|
|
24
|
+
},
|
|
25
|
+
// Placeholder text style
|
|
26
|
+
placeholderStyle: "text-[var(--color-text-placeholder)] dark:text-[var(--color-neutral-500)]",
|
|
27
|
+
// Selected value text style
|
|
28
|
+
valueStyle: "text-[var(--color-text-primary)] dark:text-[var(--color-neutral-200)]",
|
|
29
|
+
// Options panel container
|
|
30
|
+
optionsPanelStyle: "border-t border-[var(--color-border)] dark:border-[var(--color-neutral-700)]",
|
|
31
|
+
// Scrollable options list
|
|
32
|
+
optionsListStyle: "overflow-y-auto overscroll-contain",
|
|
33
|
+
// Individual option style
|
|
34
|
+
optionStyle: "flex items-center gap-2 py-2 px-3 cursor-pointer select-none " +
|
|
35
|
+
"text-[var(--color-text-primary)] transition-colors " +
|
|
36
|
+
"dark:text-[var(--color-neutral-200)]",
|
|
37
|
+
// Selected option style
|
|
38
|
+
optionSelectedStyle: "bg-[var(--color-primary-50)] text-[var(--color-primary)] font-medium " +
|
|
39
|
+
"dark:bg-[var(--color-primary-900)]/30 dark:text-[var(--color-primary-400)]",
|
|
40
|
+
// Disabled option style
|
|
41
|
+
optionDisabledStyle: "cursor-not-allowed opacity-50 hover:bg-transparent",
|
|
42
|
+
// Option hover style
|
|
43
|
+
optionHoverStyle: "hover:bg-[var(--color-primary-50)]/50 hover:text-[var(--color-primary)] " +
|
|
44
|
+
"dark:hover:bg-[var(--color-primary-900)]/20 dark:hover:text-[var(--color-primary-400)]",
|
|
45
|
+
// Checkbox style for multi-select
|
|
46
|
+
checkboxStyle: "size-4 shrink-0 rounded border border-[var(--color-border)] bg-white " +
|
|
47
|
+
"flex items-center justify-center transition-colors " +
|
|
48
|
+
"dark:bg-[var(--color-neutral-800)] dark:border-[var(--color-neutral-600)]",
|
|
49
|
+
// Checkbox checked style
|
|
50
|
+
checkboxCheckedStyle: "bg-[var(--color-primary)] border-[var(--color-primary)] text-white " +
|
|
51
|
+
"dark:bg-[var(--color-primary-500)] dark:border-[var(--color-primary-500)]",
|
|
52
|
+
// Search input container
|
|
53
|
+
searchContainerStyle: "px-3 py-2 border-b border-[var(--color-border)] " +
|
|
54
|
+
"dark:border-[var(--color-neutral-700)]",
|
|
55
|
+
// Search input style
|
|
56
|
+
searchInputStyle: "w-full bg-transparent text-sm text-[var(--color-text-primary)] " +
|
|
57
|
+
"placeholder:text-[var(--color-text-placeholder)] focus:outline-none " +
|
|
58
|
+
"dark:text-[var(--color-neutral-200)] dark:placeholder:text-[var(--color-neutral-500)]",
|
|
59
|
+
// Icon start style
|
|
60
|
+
iconStartStyle: "shrink-0 text-[var(--color-text-muted)] dark:text-[var(--color-neutral-500)]",
|
|
61
|
+
// Icon end style
|
|
62
|
+
iconEndStyle: "shrink-0 text-[var(--color-text-muted)] transition-transform duration-200 " +
|
|
63
|
+
"dark:text-[var(--color-neutral-500)]",
|
|
64
|
+
// Label style
|
|
65
|
+
labelStyle: "block text-sm font-medium text-[var(--color-text-primary)] mb-1.5 " +
|
|
66
|
+
"dark:text-[var(--color-neutral-200)]",
|
|
67
|
+
// Helper text style
|
|
68
|
+
helperTextStyle: "text-xs text-[var(--color-text-muted)] mt-1.5 " +
|
|
69
|
+
"dark:text-[var(--color-neutral-400)]",
|
|
70
|
+
// Disabled state style
|
|
71
|
+
disabledStyle: "cursor-not-allowed opacity-60 pointer-events-none",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export { selectExpandTheme };
|