@codecademy/gamut 72.2.4-alpha.628984.0 → 72.2.4-alpha.716ed7.0
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/agent-tools/skills/gamut-forms/SKILL.md +0 -16
- package/dist/Anchor/index.js +3 -3
- package/dist/BarChart/layout/GridLines.js +5 -5
- package/dist/BodyPortal/index.d.ts +4 -3
- package/dist/BodyPortal/index.js +3 -3
- package/dist/Box/props.js +2 -2
- package/dist/Button/shared/styles.js +2 -2
- package/dist/DataList/EmptyRows.js +2 -1
- package/dist/DatePicker/DatePickerCalendar/Calendar/CalendarWrapper.js +2 -1
- package/dist/DatePicker/DatePickerCalendar/Calendar/utils/elements.js +5 -5
- package/dist/Flyout/index.js +2 -1
- package/dist/Form/SelectDropdown/SelectDropdown.js +101 -48
- package/dist/Form/SelectDropdown/elements/constants.d.ts +8 -0
- package/dist/Form/SelectDropdown/elements/constants.js +9 -1
- package/dist/Form/SelectDropdown/elements/containers.d.ts +2 -6
- package/dist/Form/SelectDropdown/elements/containers.js +1 -17
- package/dist/Form/SelectDropdown/elements/controls.d.ts +15 -1
- package/dist/Form/SelectDropdown/elements/controls.js +91 -2
- package/dist/Form/SelectDropdown/elements/index.d.ts +1 -2
- package/dist/Form/SelectDropdown/elements/index.js +1 -2
- package/dist/Form/SelectDropdown/elements/options.d.ts +0 -1
- package/dist/Form/SelectDropdown/elements/options.js +2 -5
- package/dist/Form/SelectDropdown/{core/styles.js → styles.js} +20 -33
- package/dist/Form/SelectDropdown/types/component-props.d.ts +6 -54
- package/dist/Form/SelectDropdown/types/internal.d.ts +3 -3
- package/dist/Form/SelectDropdown/types/styles.d.ts +1 -5
- package/dist/Form/SelectDropdown/{core/utils.d.ts → utils.d.ts} +2 -12
- package/dist/Form/SelectDropdown/{core/utils.js → utils.js} +0 -23
- package/dist/Form/styles/index.d.ts +1 -1
- package/dist/Form/styles/index.js +1 -1
- package/dist/List/TableHeader.js +2 -1
- package/dist/List/elements.js +12 -12
- package/dist/Menu/elements.js +8 -8
- package/dist/Overlay/index.d.ts +3 -2
- package/dist/Overlay/index.js +3 -3
- package/dist/PatternBackdrop/PatternBackdrop.js +4 -4
- package/dist/Popover/elements.js +8 -6
- package/dist/Popover/styles/base.js +2 -2
- package/dist/Popover/styles/variants.js +2 -2
- package/dist/PopoverContainer/PopoverContainer.js +3 -2
- package/dist/Tabs/TabButton.js +4 -4
- package/dist/Tabs/Tabs.js +4 -4
- package/dist/Tabs/styles.js +2 -2
- package/dist/Tip/PreviewTip/elements.js +4 -4
- package/dist/Tip/shared/InlineTip.js +2 -1
- package/dist/Tip/shared/elements.js +8 -8
- package/dist/Toaster/index.js +2 -1
- package/dist/Typography/Text.js +4 -4
- package/package.json +6 -6
- package/agent-tools/skills/gamut-select-dropdown/SKILL.md +0 -236
- package/dist/Form/SelectDropdown/core/accessibility.d.ts +0 -3
- package/dist/Form/SelectDropdown/core/accessibility.js +0 -12
- package/dist/Form/SelectDropdown/core/constants.d.ts +0 -13
- package/dist/Form/SelectDropdown/core/constants.js +0 -14
- package/dist/Form/SelectDropdown/hooks/useSelectHandlers.d.ts +0 -22
- package/dist/Form/SelectDropdown/hooks/useSelectHandlers.js +0 -62
- package/dist/Form/SelectDropdown/hooks/useSelectOptions.d.ts +0 -14
- package/dist/Form/SelectDropdown/hooks/useSelectOptions.js +0 -39
- /package/dist/Form/SelectDropdown/{core/styles.d.ts → styles.d.ts} +0 -0
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: gamut-select-dropdown
|
|
3
|
-
description: Use when implementing or auditing SelectDropdown — single/multi modes, controlled vs uncontrolled value, creatable options, FormGroup wiring, and onChange contract. Pair with gamut-forms for error live regions, ConnectedForm, and field-level validation.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Gamut SelectDropdown
|
|
7
|
-
|
|
8
|
-
Styled dropdown built on react-select.
|
|
9
|
-
|
|
10
|
-
Source: `@codecademy/gamut` — [SelectDropdown.tsx](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/SelectDropdown/SelectDropdown.tsx)
|
|
11
|
-
|
|
12
|
-
See also: [`gamut-forms`](../gamut-forms/SKILL.md) — FormGroup wiring, error regions, and validation UX.
|
|
13
|
-
|
|
14
|
-
Storybook: [Atoms / FormInputs / SelectDropdown](https://gamut.codecademy.com/?path=/docs-atoms-forminputs-selectdropdown--docs)
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## When to use SelectDropdown vs Select
|
|
19
|
-
|
|
20
|
-
Use `Select` for standard single-select forms with minimal bundle cost. Use `SelectDropdown` when designs specify the styled dropdown menu, search, multi-select tags, creatable options, icons, groups, or abbreviations. SelectDropdown has a larger JavaScript dependency (react-select).
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Options
|
|
25
|
-
|
|
26
|
-
`options` accepts plain strings or option objects. `value` is always a string and references an option's `value`.
|
|
27
|
-
|
|
28
|
-
| Field | Required | Notes |
|
|
29
|
-
| -------------- | -------- | -------------------------------------------------------------------- |
|
|
30
|
-
| `label` | yes | Display text |
|
|
31
|
-
| `value` | yes | Unique string; what `value` / `string[]` reference |
|
|
32
|
-
| `disabled` | no | Option cannot be selected |
|
|
33
|
-
| `subtitle` | no | Secondary text below the label |
|
|
34
|
-
| `rightLabel` | no | Text on the right side of the option |
|
|
35
|
-
| `icon` | no | A `@codecademy/gamut-icons` component |
|
|
36
|
-
| `abbreviation` | no | Short text shown in the input while the full label shows in the menu |
|
|
37
|
-
|
|
38
|
-
Grouped options: `{ label, options: [...], divider? }` (extends react-select `GroupBase`; `divider` draws a rule above the group).
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Controlled vs uncontrolled
|
|
43
|
-
|
|
44
|
-
SelectDropdown does **not** accept `defaultValue`.
|
|
45
|
-
|
|
46
|
-
| Mode | Uncontrolled | Controlled |
|
|
47
|
-
| ---------------- | -------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
48
|
-
| Single | Not supported | `value` (string) + update in `onChange` |
|
|
49
|
-
| Multi | Omit `value` or pass non-array (`undefined`, `''`) | `value: string[]` + update in `onChange` |
|
|
50
|
-
| Creatable single | Not supported | Same as single; `onCreateOption` appends to `options` |
|
|
51
|
-
| Creatable multi | Omit `value`; `onCreateOption` for options | `value: string[]`; update in `onChange` on every change including `create-option` |
|
|
52
|
-
|
|
53
|
-
Single-select selection is derived from the `value` prop only — internal state is not kept. Multi-select without `value: string[]` keeps selection in internal `multiValues`.
|
|
54
|
-
|
|
55
|
-
**Controlled creatable multi pitfall:** Updating `options` alone without syncing `value` in `onChange` clears selection when options re-render.
|
|
56
|
-
|
|
57
|
-
### When to use uncontrolled (multi only)
|
|
58
|
-
|
|
59
|
-
Uncontrolled multi is appropriate when:
|
|
60
|
-
|
|
61
|
-
- No other part of the UI needs to react to the current selection (no live summary, no dependent field, no enabled/disabled button).
|
|
62
|
-
- You only need the value at form submission — via `FormData`, a submit handler reading the DOM, or react-hook-form's `getValues`.
|
|
63
|
-
- Simplicity is the priority; omitting `value` means one less piece of state to manage.
|
|
64
|
-
|
|
65
|
-
```tsx
|
|
66
|
-
// Good fit: a "tags" field where only the submitted array matters
|
|
67
|
-
<SelectDropdown
|
|
68
|
-
multiple
|
|
69
|
-
name="tags"
|
|
70
|
-
options={tagOptions}
|
|
71
|
-
onCreateOption={(v) => setTagOptions((prev) => [...prev, v])}
|
|
72
|
-
/>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### When to use controlled
|
|
76
|
-
|
|
77
|
-
Use controlled when:
|
|
78
|
-
|
|
79
|
-
- Another part of the UI must reflect the current selection in real time (summary text, a filtered list, an enable/disable condition).
|
|
80
|
-
- You need to pre-populate from an API response, reset on cancel, or sync with a form library like react-hook-form.
|
|
81
|
-
- You are using single-select (the only supported mode for single).
|
|
82
|
-
|
|
83
|
-
```tsx
|
|
84
|
-
// Good fit: pre-populate from API, clear on cancel, show live summary
|
|
85
|
-
const [selected, setSelected] = useState<string[]>(initialValues);
|
|
86
|
-
|
|
87
|
-
<SelectDropdown
|
|
88
|
-
multiple
|
|
89
|
-
name="languages"
|
|
90
|
-
options={languageOptions}
|
|
91
|
-
value={selected}
|
|
92
|
-
onChange={(opts) => setSelected(opts.map((o) => o.value))}
|
|
93
|
-
/>
|
|
94
|
-
<p>Selected: {selected.join(', ') || 'none'}</p>
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## onChange contract
|
|
100
|
-
|
|
101
|
-
`onChange` receives option object(s), not `event.target.value`:
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
// Single
|
|
105
|
-
onChange={(option) => setValue(option.value)}
|
|
106
|
-
|
|
107
|
-
// Multi
|
|
108
|
-
onChange={(selected) => setValue(selected.map((o) => o.value))}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Second argument is react-select `ActionMeta`. For creatable creates: `meta.action === 'create-option'`. Do **not** pass `onCreateOption` to react-select directly — Gamut invokes it from `changeHandler` while still forwarding `create-option` to consumer `onChange`.
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## Creatable
|
|
116
|
-
|
|
117
|
-
- `isCreatable` forces `isSearchable: true` (TypeScript enforces this).
|
|
118
|
-
- `onCreateOption(inputValue)` — convenience hook to append to `options`.
|
|
119
|
-
- `onChange(selected, meta)` — use `meta.action === 'create-option'` to sync controlled `value` and `options` together.
|
|
120
|
-
- `isValidNewOption` — return `false` to hide the Add row.
|
|
121
|
-
- `validationMessage` — replaces menu "No options" text; mirror in `FormGroup` `error` for field-level feedback.
|
|
122
|
-
|
|
123
|
-
**Validation after blur:** react-select clears input on blur before `onBlur` fires, so the value is gone by the time you'd validate it. Store the last typed value in a ref and re-validate from it on `input-blur`:
|
|
124
|
-
|
|
125
|
-
```tsx
|
|
126
|
-
const lastInput = useRef('');
|
|
127
|
-
|
|
128
|
-
<SelectDropdown
|
|
129
|
-
isCreatable
|
|
130
|
-
onInputChange={(value, { action }) => {
|
|
131
|
-
if (action === 'input-change') lastInput.current = value;
|
|
132
|
-
if (action === 'input-blur') validate(lastInput.current);
|
|
133
|
-
}}
|
|
134
|
-
/>;
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## FormGroup wiring
|
|
140
|
-
|
|
141
|
-
- `FormGroup` `htmlFor` must match control `id` (not `name`). Alternatively, pass `htmlFor` directly on SelectDropdown and it becomes `id` downstream.
|
|
142
|
-
- Pass `name` on SelectDropdown (required for forms).
|
|
143
|
-
- Rely on FormGroupLabel's `htmlFor`/`id` connection for the accessible label — this is the standard HTML pattern and preferred method.
|
|
144
|
-
- Only add `aria-label` when absolutely necessary (e.g., no visible label, label is hidden, or form context requires it). Do not use `aria-label` when a FormGroupLabel is present, as it overrides the visible label.
|
|
145
|
-
- Pass `error` boolean when FormGroup has an error.
|
|
146
|
-
- Generic FormGroup live-region behavior: see [`gamut-forms`](../gamut-forms/SKILL.md).
|
|
147
|
-
|
|
148
|
-
```tsx
|
|
149
|
-
<FormGroup htmlFor="country" isSoloField label="Country" error={errors.country}>
|
|
150
|
-
<SelectDropdown
|
|
151
|
-
id="country"
|
|
152
|
-
name="country"
|
|
153
|
-
options={options}
|
|
154
|
-
value={value}
|
|
155
|
-
error={Boolean(errors.country)}
|
|
156
|
-
onChange={(option) => setValue(option.value)}
|
|
157
|
-
/>
|
|
158
|
-
</FormGroup>
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Styling & layout props
|
|
164
|
-
|
|
165
|
-
| Prop | Type | Default | Notes |
|
|
166
|
-
| ------------------- | ------------------------ | -------- | --------------------------------------------------------- |
|
|
167
|
-
| `size` | `'small' \| 'medium'` | `medium` | Control height/density |
|
|
168
|
-
| `shownOptionsLimit` | `1`–`6` | `6` | Visible options before the menu scrolls |
|
|
169
|
-
| `inputWidth` | `string \| number` | — | Width of the input independent of the menu |
|
|
170
|
-
| `dropdownWidth` | `string \| number` | — | Width of the menu independent of the input |
|
|
171
|
-
| `menuAlignment` | `'left' \| 'right'` | `left` | Menu edge alignment |
|
|
172
|
-
| `zIndex` | `number` | auto | Menu z-index |
|
|
173
|
-
| `inputProps` | `{ hidden?, combobox? }` | — | `data-*` / `aria-*` only, forwarded to the input elements |
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## Examples
|
|
178
|
-
|
|
179
|
-
### Single (controlled)
|
|
180
|
-
|
|
181
|
-
```tsx
|
|
182
|
-
const [value, setValue] = useState('us');
|
|
183
|
-
|
|
184
|
-
<SelectDropdown
|
|
185
|
-
name="country"
|
|
186
|
-
options={options}
|
|
187
|
-
value={value}
|
|
188
|
-
onChange={(option) => setValue(option.value)}
|
|
189
|
-
/>;
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### Multi (uncontrolled)
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
<SelectDropdown
|
|
196
|
-
multiple
|
|
197
|
-
name="tags"
|
|
198
|
-
options={options}
|
|
199
|
-
onChange={(selected) => console.log(selected)}
|
|
200
|
-
/>
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Creatable multi (uncontrolled)
|
|
204
|
-
|
|
205
|
-
```tsx
|
|
206
|
-
const [options, setOptions] = useState(['Apple', 'Banana']);
|
|
207
|
-
|
|
208
|
-
<SelectDropdown
|
|
209
|
-
isCreatable
|
|
210
|
-
multiple
|
|
211
|
-
name="fruits"
|
|
212
|
-
options={options}
|
|
213
|
-
onCreateOption={(v) => setOptions((prev) => [...prev, v])}
|
|
214
|
-
/>;
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Creatable multi (controlled)
|
|
218
|
-
|
|
219
|
-
```tsx
|
|
220
|
-
const [options, setOptions] = useState(['Apple', 'Banana']);
|
|
221
|
-
const [value, setValue] = useState<string[]>([]);
|
|
222
|
-
|
|
223
|
-
<SelectDropdown
|
|
224
|
-
isCreatable
|
|
225
|
-
multiple
|
|
226
|
-
name="fruits"
|
|
227
|
-
options={options}
|
|
228
|
-
value={value}
|
|
229
|
-
onChange={(selected, meta) => {
|
|
230
|
-
setValue(selected.map((o) => o.value));
|
|
231
|
-
if (meta.action === 'create-option' && meta.option) {
|
|
232
|
-
setOptions((prev) => [...prev, meta.option.value]);
|
|
233
|
-
}
|
|
234
|
-
}}
|
|
235
|
-
/>;
|
|
236
|
-
```
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export const onFocus = ({
|
|
2
|
-
focused: {
|
|
3
|
-
label,
|
|
4
|
-
subtitle,
|
|
5
|
-
rightLabel,
|
|
6
|
-
disabled
|
|
7
|
-
}
|
|
8
|
-
}) => {
|
|
9
|
-
const formattedSubtitle = `, ${subtitle}`;
|
|
10
|
-
const formattedRightLabel = `, ${rightLabel}`;
|
|
11
|
-
return `You are currently focused on option ${label}${subtitle ? formattedSubtitle : ''} ${rightLabel ? formattedRightLabel : ''}${disabled ? ', disabled' : ''}`;
|
|
12
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare const defaultComponents: {
|
|
2
|
-
DropdownIndicator: (props: import("../types").SizedIndicatorProps) => import("react/jsx-runtime").JSX.Element;
|
|
3
|
-
IndicatorSeparator: () => null;
|
|
4
|
-
ClearIndicator: (props: import("../types").SizedIndicatorProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
|
-
SelectContainer: ({ children, ...rest }: import("../types").CustomSelectComponentProps<(<Option_18, IsMulti_18 extends boolean, Group_18 extends import("react-select").GroupBase<Option_18>>(props: import("react-select").ContainerProps<Option_18, IsMulti_18, Group_18>) => import("@emotion/react").jsx.JSX.Element)>) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
-
ValueContainer: ({ ...rest }: import("../types").CustomSelectComponentProps<(<Option_20, IsMulti_20 extends boolean, Group_20 extends import("react-select").GroupBase<Option_20>>(props: import("react-select").ValueContainerProps<Option_20, IsMulti_20, Group_20>) => import("@emotion/react").jsx.JSX.Element)>) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
MultiValue: (props: import("react-select").MultiValueProps<import("..").ExtendedOption, true, import("react-select").GroupBase<import("..").ExtendedOption>>) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
MultiValueRemove: (props: import("react-select").MultiValueRemoveProps<import("..").ExtendedOption, true, import("react-select").GroupBase<import("..").ExtendedOption>>) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
Option: ({ children, ...rest }: import("../types").CustomSelectComponentProps<(<Option_16, IsMulti_16 extends boolean, Group_16 extends import("react-select").GroupBase<Option_16>>(props: import("react-select").OptionProps<Option_16, IsMulti_16, Group_16>) => import("@emotion/react").jsx.JSX.Element)>) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
-
SingleValue: (props: import("react-select").SingleValueProps<import("..").ExtendedOption, false>) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
-
Input: ({ ...rest }: import("../types").CustomSelectComponentProps<(<Option_7, IsMulti_7 extends boolean, Group_7 extends import("react-select").GroupBase<Option_7>>(props: import("react-select").InputProps<Option_7, IsMulti_7, Group_7>) => import("@emotion/react").jsx.JSX.Element)>) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
-
};
|
|
13
|
-
export declare const ON_CHANGE_ACTION: "select-option";
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { AbbreviatedSingleValue, CustomContainer, CustomInput, CustomValueContainer, DropdownButton, IconOption, MultiValueRemoveButton, MultiValueWithColorMode, RemoveAllButton } from '../elements';
|
|
2
|
-
export const defaultComponents = {
|
|
3
|
-
DropdownIndicator: DropdownButton,
|
|
4
|
-
IndicatorSeparator: () => null,
|
|
5
|
-
ClearIndicator: RemoveAllButton,
|
|
6
|
-
SelectContainer: CustomContainer,
|
|
7
|
-
ValueContainer: CustomValueContainer,
|
|
8
|
-
MultiValue: MultiValueWithColorMode,
|
|
9
|
-
MultiValueRemove: MultiValueRemoveButton,
|
|
10
|
-
Option: IconOption,
|
|
11
|
-
SingleValue: AbbreviatedSingleValue,
|
|
12
|
-
Input: CustomInput
|
|
13
|
-
};
|
|
14
|
-
export const ON_CHANGE_ACTION = 'select-option';
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { KeyboardEvent } from 'react';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { ActionMeta, Options as OptionsType } from 'react-select';
|
|
4
|
-
import { SelectOptionBase } from '../../utils';
|
|
5
|
-
import { MultiSelectDropdownProps, OptionStrict, SelectDropdownGroup, SelectDropdownProps, SingleSelectDropdownProps } from '../types';
|
|
6
|
-
interface UseSelectHandlersArgs {
|
|
7
|
-
onChange?: SingleSelectDropdownProps['onChange'] | MultiSelectDropdownProps['onChange'];
|
|
8
|
-
multiple?: boolean;
|
|
9
|
-
onCreateOption?: (inputValue: string) => void;
|
|
10
|
-
selectOptions: SelectOptionBase[] | SelectDropdownGroup[];
|
|
11
|
-
value?: SelectDropdownProps['value'];
|
|
12
|
-
currentFocusedValue: unknown;
|
|
13
|
-
removeAllButtonRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
14
|
-
}
|
|
15
|
-
interface UseSelectHandlersReturn {
|
|
16
|
-
activated: boolean;
|
|
17
|
-
multiValues: OptionStrict[] | false;
|
|
18
|
-
changeHandler: (optionEvent: OptionStrict | OptionsType<OptionStrict>, actionMeta: ActionMeta<OptionStrict>) => void;
|
|
19
|
-
keyPressHandler: (e: KeyboardEvent<HTMLDivElement>) => void;
|
|
20
|
-
}
|
|
21
|
-
export declare const useSelectHandlers: ({ onChange, multiple, onCreateOption, selectOptions, value, currentFocusedValue, removeAllButtonRef, }: UseSelectHandlersArgs) => UseSelectHandlersReturn;
|
|
22
|
-
export {};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
import { ON_CHANGE_ACTION } from '../core/constants';
|
|
3
|
-
import { filterValueFromOptions, getCreatedOptionValue, isMultipleSelectProps, isOptionsGrouped, isSingleSelectProps, removeValueFromSelectedOptions } from '../core/utils';
|
|
4
|
-
export const useSelectHandlers = ({
|
|
5
|
-
onChange,
|
|
6
|
-
multiple,
|
|
7
|
-
onCreateOption,
|
|
8
|
-
selectOptions,
|
|
9
|
-
value,
|
|
10
|
-
currentFocusedValue,
|
|
11
|
-
removeAllButtonRef
|
|
12
|
-
}) => {
|
|
13
|
-
const [activated, setActivated] = useState(false);
|
|
14
|
-
const [multiValues, setMultiValues] = useState(multiple ? filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions)) : false);
|
|
15
|
-
|
|
16
|
-
// Sync multi-select value from props when controlled (`value` is a string[]).
|
|
17
|
-
// Uncontrolled multi (`value` undefined or '') keeps selection in local state.
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!multiple || !Array.isArray(value)) return;
|
|
20
|
-
const newMultiValues = filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions));
|
|
21
|
-
if (newMultiValues !== multiValues) setMultiValues(newMultiValues);
|
|
22
|
-
|
|
23
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
24
|
-
}, [selectOptions, value, multiple]);
|
|
25
|
-
const changeHandler = useCallback((optionEvent, actionMeta) => {
|
|
26
|
-
setActivated(true);
|
|
27
|
-
if (actionMeta.action === 'create-option') {
|
|
28
|
-
const createdValue = getCreatedOptionValue(optionEvent, actionMeta, multiple);
|
|
29
|
-
if (createdValue) onCreateOption?.(createdValue);
|
|
30
|
-
}
|
|
31
|
-
const onChangeProps = {
|
|
32
|
-
onChange,
|
|
33
|
-
multiple
|
|
34
|
-
};
|
|
35
|
-
const forwardedMeta = actionMeta.action === 'create-option' ? actionMeta : {
|
|
36
|
-
action: ON_CHANGE_ACTION,
|
|
37
|
-
option: isMultipleSelectProps(onChangeProps) ? undefined : optionEvent
|
|
38
|
-
};
|
|
39
|
-
if (isSingleSelectProps(onChangeProps)) {
|
|
40
|
-
onChangeProps.onChange?.(optionEvent, forwardedMeta);
|
|
41
|
-
}
|
|
42
|
-
if (isMultipleSelectProps(onChangeProps)) {
|
|
43
|
-
setMultiValues(optionEvent);
|
|
44
|
-
onChangeProps.onChange?.(optionEvent, forwardedMeta);
|
|
45
|
-
}
|
|
46
|
-
}, [onChange, multiple, onCreateOption]);
|
|
47
|
-
const keyPressHandler = e => {
|
|
48
|
-
if (multiple && e.key === 'Enter' && currentFocusedValue && multiValues) {
|
|
49
|
-
const newMultiValues = removeValueFromSelectedOptions(multiValues, currentFocusedValue);
|
|
50
|
-
if (newMultiValues !== multiValues) setMultiValues(newMultiValues);
|
|
51
|
-
}
|
|
52
|
-
if (removeAllButtonRef.current !== null && e.key === 'ArrowRight' && multiValues && currentFocusedValue === multiValues[multiValues.length - 1].value) {
|
|
53
|
-
removeAllButtonRef.current.focus();
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
return {
|
|
57
|
-
activated,
|
|
58
|
-
multiValues,
|
|
59
|
-
changeHandler,
|
|
60
|
-
keyPressHandler
|
|
61
|
-
};
|
|
62
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { SelectOptionBase } from '../../utils';
|
|
2
|
-
import { SelectDropdownGroup, SelectDropdownOptions, SelectDropdownSizes } from '../types';
|
|
3
|
-
interface UseSelectOptionsArgs {
|
|
4
|
-
options?: SelectDropdownOptions | SelectDropdownGroup[];
|
|
5
|
-
id?: string;
|
|
6
|
-
size?: SelectDropdownSizes['size'];
|
|
7
|
-
value?: string | string[];
|
|
8
|
-
}
|
|
9
|
-
interface UseSelectOptionsReturn {
|
|
10
|
-
selectOptions: SelectOptionBase[] | SelectDropdownGroup[];
|
|
11
|
-
parsedValue: SelectOptionBase | undefined;
|
|
12
|
-
}
|
|
13
|
-
export declare const useSelectOptions: ({ options, id, size, value, }: UseSelectOptionsArgs) => UseSelectOptionsReturn;
|
|
14
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { parseOptions } from '../../utils';
|
|
3
|
-
import { isOptionsGrouped } from '../core/utils';
|
|
4
|
-
export const useSelectOptions = ({
|
|
5
|
-
options,
|
|
6
|
-
id,
|
|
7
|
-
size,
|
|
8
|
-
value
|
|
9
|
-
}) => {
|
|
10
|
-
const selectOptions = useMemo(() => {
|
|
11
|
-
if (!options || Array.isArray(options) && !options.length || typeof options === 'object' && !Array.isArray(options) && Object.keys(options).length === 0) {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
if (isOptionsGrouped(options)) {
|
|
15
|
-
return options;
|
|
16
|
-
}
|
|
17
|
-
return parseOptions({
|
|
18
|
-
options,
|
|
19
|
-
id,
|
|
20
|
-
size
|
|
21
|
-
});
|
|
22
|
-
}, [options, id, size]);
|
|
23
|
-
const parsedValue = useMemo(() => {
|
|
24
|
-
if (isOptionsGrouped(selectOptions)) {
|
|
25
|
-
for (const group of selectOptions) {
|
|
26
|
-
if (group.options) {
|
|
27
|
-
const foundOption = group.options.find(option => option.value === value);
|
|
28
|
-
if (foundOption) return foundOption;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
return selectOptions.find(option => option.value === value);
|
|
34
|
-
}, [selectOptions, value]);
|
|
35
|
-
return {
|
|
36
|
-
selectOptions,
|
|
37
|
-
parsedValue
|
|
38
|
-
};
|
|
39
|
-
};
|
|
File without changes
|