@codecademy/gamut 72.0.1-alpha.db0637.0 → 72.0.1

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.
@@ -26,12 +26,6 @@ For typical product forms, prefer `GridForm` (declarative `fields`, `LayoutGrid`
26
26
 
27
27
  ---
28
28
 
29
- ## SelectDropdown
30
-
31
- For `SelectDropdown` — single vs multi value, controlled vs uncontrolled patterns, creatable options, and react-select action metadata — use [`gamut-select-dropdown`](../gamut-select-dropdown/SKILL.md). Generic `FormGroup` wiring (labels, errors, live regions) still applies as documented below; SelectDropdown-specific state contracts live in that skill.
32
-
33
- ---
34
-
35
29
  ## `FormGroup` (baseline)
36
30
 
37
31
  [`FormGroup.tsx`](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/Form/elements/FormGroup.tsx)
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import { parseOptions } from '../utils';
5
5
  import { AbbreviatedSingleValue, CustomContainer, CustomInput, CustomValueContainer, DropdownButton, formatGroupLabel, formatOptionLabel, IconOption, MultiValueRemoveButton, MultiValueWithColorMode, onFocus, RemoveAllButton, SelectDropdownContext, TypedReactSelect } from './elements';
6
6
  import { getMemoizedStyles } from './styles';
7
- import { filterValueFromOptions, getCreatedOptionValue, isMultipleSelectProps, isOptionsGrouped, isSingleSelectProps, removeValueFromSelectedOptions } from './utils';
7
+ import { filterValueFromOptions, isMultipleSelectProps, isOptionsGrouped, isSingleSelectProps, removeValueFromSelectedOptions } from './utils';
8
8
  import { jsx as _jsx } from "react/jsx-runtime";
9
9
  const defaultProps = {
10
10
  name: undefined,
@@ -73,30 +73,22 @@ export const SelectDropdown = ({
73
73
  disabled,
74
74
  dropdownWidth,
75
75
  error,
76
- formatCreateLabel = inputValue => `Add "${inputValue}"`,
77
76
  id,
78
77
  inputProps,
79
78
  inputWidth,
80
- isCreatable = false,
81
- isSearchable: isSearchableProp = false,
82
- isValidNewOption,
79
+ isSearchable = false,
83
80
  menuAlignment = 'left',
84
81
  multiple,
85
82
  name,
86
83
  onChange,
87
- onCreateOption,
88
- onInputChange,
89
84
  options,
90
85
  placeholder = 'Select an option',
91
86
  shownOptionsLimit = 6,
92
87
  size,
93
- validationMessage,
94
88
  value,
95
89
  zIndex,
96
90
  ...rest
97
91
  }) => {
98
- // isSearchable is forced true when isCreatable is true (CreatableSelect requires a text input)
99
- const isSearchable = isCreatable || isSearchableProp;
100
92
  const rawInputId = useId();
101
93
  const inputId = name ?? `${id}-select-dropdown-${rawInputId}`;
102
94
  const [activated, setActivated] = useState(false);
@@ -134,41 +126,39 @@ export const SelectDropdown = ({
134
126
  // To keep this efficient for non-multiSelect
135
127
  filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions)));
136
128
 
137
- // Sync multi-select value from props when controlled (`value` is a string[]).
138
- // Uncontrolled multi (`value` undefined or '') keeps selection in local state.
129
+ // If the caller changes the initial value, let's update our value to match.
139
130
  useEffect(() => {
140
- if (!multiple || !Array.isArray(value)) return;
141
131
  const newMultiValues = filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions));
142
132
  if (newMultiValues !== multiValues) setMultiValues(newMultiValues);
143
133
 
134
+ //
144
135
  // We only update this when our passed in options or value changes, not multiValues.
145
136
  // eslint-disable-next-line react-hooks/exhaustive-deps
146
- }, [options, value, multiple]);
147
- const changeHandler = useCallback((optionEvent, actionMeta) => {
137
+ }, [options, value]);
138
+ const changeHandler = useCallback(optionEvent => {
148
139
  setActivated(true);
149
- if (actionMeta.action === 'create-option') {
150
- const createdValue = getCreatedOptionValue(optionEvent, actionMeta, multiple);
151
- if (createdValue) {
152
- onCreateOption?.(createdValue);
153
- }
154
- }
140
+
141
+ // We have to do this because the version of typescript we have doesn't have the transitivity of these type guards yet. But, we will soon!
142
+ // Should probably come with: https://codecademy.atlassian.net/browse/GM-354
155
143
  const onChangeProps = {
156
144
  onChange,
157
145
  multiple
158
146
  };
159
- const forwardedMeta = actionMeta.action === 'create-option' ? actionMeta : {
160
- action: onChangeAction,
161
- option: isMultipleSelectProps(onChangeProps) ? undefined : optionEvent
162
- };
163
147
  if (isSingleSelectProps(onChangeProps)) {
164
148
  const singleOptionEvent = optionEvent;
165
- onChangeProps.onChange?.(singleOptionEvent, forwardedMeta);
149
+ onChangeProps.onChange?.(singleOptionEvent, {
150
+ action: onChangeAction,
151
+ option: singleOptionEvent
152
+ });
166
153
  }
167
154
  if (isMultipleSelectProps(onChangeProps)) {
168
155
  setMultiValues(optionEvent);
169
- onChangeProps.onChange?.(optionEvent, forwardedMeta);
156
+ onChangeProps.onChange?.(optionEvent, {
157
+ action: onChangeAction,
158
+ option: undefined // At the moment this isn't used, but when multi select is built for real, boom (https://codecademy.atlassian.net/browse/GM-354)
159
+ });
170
160
  }
171
- }, [onChange, multiple, onCreateOption]);
161
+ }, [onChange, multiple]);
172
162
  const keyPressHandler = e => {
173
163
  if (multiple && e.key === 'Enter' && currentFocusedValue && multiValues) {
174
164
  const newMultiValues = removeValueFromSelectedOptions(multiValues, currentFocusedValue);
@@ -178,8 +168,6 @@ export const SelectDropdown = ({
178
168
  removeAllButtonRef.current.focus();
179
169
  }
180
170
  };
181
- const noOptionsMessage = validationMessage === undefined ? undefined // fall back to react-select default ("No options")
182
- : typeof validationMessage === 'function' ? validationMessage : () => validationMessage;
183
171
  const theme = useTheme();
184
172
  const memoizedStyles = useMemo(() => {
185
173
  return getMemoizedStyles(theme, zIndex);
@@ -200,7 +188,6 @@ export const SelectDropdown = ({
200
188
  },
201
189
  dropdownWidth: dropdownWidth,
202
190
  error: Boolean(error),
203
- formatCreateLabel: formatCreateLabel,
204
191
  formatGroupLabel: formatGroupLabel,
205
192
  formatOptionLabel: formatOptionLabel,
206
193
  id: id || rest.htmlFor || rawInputId,
@@ -209,15 +196,12 @@ export const SelectDropdown = ({
209
196
  ...inputProps
210
197
  },
211
198
  inputWidth: inputWidth,
212
- isCreatable: isCreatable,
213
199
  isDisabled: disabled,
214
200
  isMulti: multiple,
215
201
  isOptionDisabled: option => option.disabled,
216
202
  isSearchable: isSearchable,
217
- isValidNewOption: isValidNewOption,
218
203
  menuAlignment: menuAlignment,
219
204
  name: name,
220
- noOptionsMessage: noOptionsMessage,
221
205
  options: selectOptions,
222
206
  placeholder: placeholder,
223
207
  selectRef: selectInputRef,
@@ -226,7 +210,6 @@ export const SelectDropdown = ({
226
210
  styles: memoizedStyles,
227
211
  value: multiple ? multiValues : parsedValue,
228
212
  onChange: changeHandler,
229
- onInputChange: onInputChange,
230
213
  onKeyDown: multiple ? e => keyPressHandler(e) : undefined,
231
214
  ...rest
232
215
  })
@@ -15,6 +15,14 @@ export declare const indicatorIcons: {
15
15
  size: number;
16
16
  icon: import("react").ForwardRefExoticComponent<import("@codecademy/gamut-icons").GamutIconProps & import("react").RefAttributes<SVGSVGElement>>;
17
17
  };
18
+ smallSearchable: {
19
+ size: number;
20
+ icon: import("react").ForwardRefExoticComponent<import("@codecademy/gamut-icons").GamutIconProps & import("react").RefAttributes<SVGSVGElement>>;
21
+ };
22
+ mediumSearchable: {
23
+ size: number;
24
+ icon: import("react").ForwardRefExoticComponent<import("@codecademy/gamut-icons").GamutIconProps & import("react").RefAttributes<SVGSVGElement>>;
25
+ };
18
26
  smallRemove: {
19
27
  size: number;
20
28
  icon: import("react").ForwardRefExoticComponent<import("@codecademy/gamut-icons").GamutIconProps & import("react").RefAttributes<SVGSVGElement>>;
@@ -1,4 +1,4 @@
1
- import { ArrowChevronDownIcon, CloseIcon, MiniChevronDownIcon, MiniDeleteIcon } from '@codecademy/gamut-icons';
1
+ import { ArrowChevronDownIcon, CloseIcon, MiniChevronDownIcon, MiniDeleteIcon, SearchIcon } from '@codecademy/gamut-icons';
2
2
  export const iconSize = {
3
3
  small: 12,
4
4
  medium: 16
@@ -16,6 +16,14 @@ export const indicatorIcons = {
16
16
  size: iconSize.medium,
17
17
  icon: ArrowChevronDownIcon
18
18
  },
19
+ smallSearchable: {
20
+ size: iconSize.small,
21
+ icon: SearchIcon
22
+ },
23
+ mediumSearchable: {
24
+ size: iconSize.medium,
25
+ icon: SearchIcon
26
+ },
19
27
  smallRemove: {
20
28
  size: iconSize.small,
21
29
  icon: MiniDeleteIcon
@@ -24,10 +24,6 @@ export declare const CustomValueContainer: ({ ...rest }: CustomSelectComponentPr
24
24
  export declare const CustomInput: ({ ...rest }: CustomSelectComponentProps<typeof SelectDropdownElements.Input>) => import("react/jsx-runtime").JSX.Element;
25
25
  /**
26
26
  * Typed wrapper around react-select component.
27
- * Renders CreatableSelect when isCreatable is true, ReactSelect otherwise.
28
- * Creatable-only props (formatCreateLabel, isValidNewOption) are stripped from
29
- * the non-creatable path so they don't reach ReactSelect. `onCreateOption` is
30
- * handled in SelectDropdown's changeHandler — do not pass it to CreatableSelect
31
- * or react-select will skip onChange on create.
27
+ * Provides type safety for the underlying react-select implementation.
32
28
  */
33
- export declare function TypedReactSelect<OptionType, IsMulti extends boolean = false, GroupType extends GroupBase<OptionType> = GroupBase<OptionType>>({ selectRef, isCreatable, formatCreateLabel, isValidNewOption, ...props }: Props<OptionType, IsMulti, GroupType> & TypedReactSelectProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function TypedReactSelect<OptionType, IsMulti extends boolean = false, GroupType extends GroupBase<OptionType> = GroupBase<OptionType>>({ selectRef, ...props }: Props<OptionType, IsMulti, GroupType> & TypedReactSelectProps): import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,5 @@
1
1
  import { createContext, useLayoutEffect } from 'react';
2
2
  import ReactSelect, { components as SelectDropdownElements } from 'react-select';
3
- import CreatableSelect from 'react-select/creatable';
4
3
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
4
  /**
6
5
  * React context for sharing state between SelectDropdown components.
@@ -117,27 +116,12 @@ export const CustomInput = ({
117
116
 
118
117
  /**
119
118
  * Typed wrapper around react-select component.
120
- * Renders CreatableSelect when isCreatable is true, ReactSelect otherwise.
121
- * Creatable-only props (formatCreateLabel, isValidNewOption) are stripped from
122
- * the non-creatable path so they don't reach ReactSelect. `onCreateOption` is
123
- * handled in SelectDropdown's changeHandler — do not pass it to CreatableSelect
124
- * or react-select will skip onChange on create.
119
+ * Provides type safety for the underlying react-select implementation.
125
120
  */
126
121
  export function TypedReactSelect({
127
122
  selectRef,
128
- isCreatable,
129
- formatCreateLabel,
130
- isValidNewOption,
131
123
  ...props
132
124
  }) {
133
- if (isCreatable) {
134
- return /*#__PURE__*/_jsx(CreatableSelect, {
135
- ...props,
136
- formatCreateLabel: formatCreateLabel,
137
- isValidNewOption: isValidNewOption,
138
- ref: selectRef
139
- });
140
- }
141
125
  return /*#__PURE__*/_jsx(ReactSelect, {
142
126
  ...props,
143
127
  ref: selectRef
@@ -36,13 +36,15 @@ export const onFocus = ({
36
36
  */
37
37
  export const DropdownButton = props => {
38
38
  const {
39
- size
39
+ size,
40
+ isSearchable
40
41
  } = props.selectProps;
41
42
  const color = props.isDisabled ? 'text-disabled' : 'text';
42
43
  const iconSize = size ?? 'medium';
44
+ const iconType = isSearchable ? 'Searchable' : 'Chevron';
43
45
  const {
44
46
  ...iconProps
45
- } = indicatorIcons[`${iconSize}Chevron`];
47
+ } = indicatorIcons[`${iconSize}${iconType}`];
46
48
  const {
47
49
  icon: IndicatorIcon
48
50
  } = iconProps;
@@ -64,7 +66,7 @@ const CustomStyledRemoveAllDiv = /*#__PURE__*/_styled('div', {
64
66
  '&:focus-visible': {
65
67
  outline: `2px solid ${theme.colors.primary}`
66
68
  }
67
- }), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9Gb3JtL1NlbGVjdERyb3Bkb3duL2VsZW1lbnRzL2NvbnRyb2xzLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFvRGlDIiwiZmlsZSI6Ii4uLy4uLy4uLy4uL3NyYy9Gb3JtL1NlbGVjdERyb3Bkb3duL2VsZW1lbnRzL2NvbnRyb2xzLnRzeCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcywgdGhlbWUgfSBmcm9tICdAY29kZWNhZGVteS9nYW11dC1zdHlsZXMnO1xuaW1wb3J0IHN0eWxlZCBmcm9tICdAZW1vdGlvbi9zdHlsZWQnO1xuaW1wb3J0IHsgS2V5Ym9hcmRFdmVudCwgdXNlQ29udGV4dCB9IGZyb20gJ3JlYWN0JztcbmltcG9ydCB7XG4gIEFyaWFPbkZvY3VzLFxuICBjb21wb25lbnRzIGFzIFNlbGVjdERyb3Bkb3duRWxlbWVudHMsXG59IGZyb20gJ3JlYWN0LXNlbGVjdCc7XG5cbmltcG9ydCB7IEV4dGVuZGVkT3B0aW9uLCBTaXplZEluZGljYXRvclByb3BzIH0gZnJvbSAnLi4vdHlwZXMnO1xuaW1wb3J0IHsgaW5kaWNhdG9ySWNvbnMgfSBmcm9tICcuL2NvbnN0YW50cyc7XG5pbXBvcnQgeyBTZWxlY3REcm9wZG93bkNvbnRleHQgfSBmcm9tICcuL2NvbnRhaW5lcnMnO1xuXG5jb25zdCB7IERyb3Bkb3duSW5kaWNhdG9yIH0gPSBTZWxlY3REcm9wZG93bkVsZW1lbnRzO1xuXG4vKipcbiAqIEdlbmVyYXRlcyBhY2Nlc3NpYmxlIGZvY3VzIG1lc3NhZ2VzIGZvciBzY3JlZW4gcmVhZGVycy5cbiAqIFByb3ZpZGVzIGRldGFpbGVkIGluZm9ybWF0aW9uIGFib3V0IHRoZSBjdXJyZW50bHkgZm9jdXNlZCBvcHRpb24uXG4gKlxuICogQHBhcmFtIHBhcmFtcyAtIE9iamVjdCBjb250YWluaW5nIHRoZSBmb2N1c2VkIG9wdGlvbiBkZXRhaWxzXG4gKiBAcmV0dXJucyBGb3JtYXR0ZWQgYWNjZXNzaWJpbGl0eSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBjb25zdCBvbkZvY3VzOiBBcmlhT25Gb2N1czxFeHRlbmRlZE9wdGlvbj4gPSAoe1xuICBmb2N1c2VkOiB7IGxhYmVsLCBzdWJ0aXRsZSwgcmlnaHRMYWJlbCwgZGlzYWJsZWQgfSxcbn0pID0+IHtcbiAgY29uc3QgZm9ybWF0dGVkU3VidGl0bGUgPSBgLCAke3N1YnRpdGxlfWA7XG4gIGNvbnN0IGZvcm1hdHRlZFJpZ2h0TGFiZWwgPSBgLCAke3JpZ2h0TGFiZWx9YDtcblxuICBjb25zdCBtc2cgPSBgWW91IGFyZSBjdXJyZW50bHkgZm9jdXNlZCBvbiBvcHRpb24gJHtsYWJlbH0ke1xuICAgIHN1YnRpdGxlID8gZm9ybWF0dGVkU3VidGl0bGUgOiAnJ1xuICB9ICR7cmlnaHRMYWJlbCA/IGZvcm1hdHRlZFJpZ2h0TGFiZWwgOiAnJ30ke2Rpc2FibGVkID8gJywgZGlzYWJsZWQnIDogJyd9YDtcblxuICByZXR1cm4gbXNnO1xufTtcblxuLyoqXG4gKiBDdXN0b20gZHJvcGRvd24gaW5kaWNhdG9yIHRoYXQgc2hvd3MgZWl0aGVyIGEgY2hldnJvbiBvciBzZWFyY2ggaWNvbi5cbiAqIFRoZSBpY29uIHR5cGUgZGVwZW5kcyBvbiB3aGV0aGVyIHRoZSBzZWxlY3QgaXMgc2VhcmNoYWJsZSBvciBub3QuXG4gKi9cbmV4cG9ydCBjb25zdCBEcm9wZG93bkJ1dHRvbiA9IChwcm9wczogU2l6ZWRJbmRpY2F0b3JQcm9wcykgPT4ge1xuICBjb25zdCB7IHNpemUgfSA9IHByb3BzLnNlbGVjdFByb3BzO1xuICBjb25zdCBjb2xvciA9IHByb3BzLmlzRGlzYWJsZWQgPyAndGV4dC1kaXNhYmxlZCcgOiAndGV4dCc7XG4gIGNvbnN0IGljb25TaXplID0gc2l6ZSA/PyAnbWVkaXVtJztcbiAgY29uc3QgeyAuLi5pY29uUHJvcHMgfSA9IGluZGljYXRvckljb25zW2Ake2ljb25TaXplfUNoZXZyb25gXTtcbiAgY29uc3QgeyBpY29uOiBJbmRpY2F0b3JJY29uIH0gPSBpY29uUHJvcHM7XG5cbiAgcmV0dXJuIChcbiAgICA8RHJvcGRvd25JbmRpY2F0b3Igey4uLnByb3BzfT5cbiAgICAgIDxJbmRpY2F0b3JJY29uIHsuLi5pY29uUHJvcHN9IGNvbG9yPXtjb2xvcn0gLz5cbiAgICA8L0Ryb3Bkb3duSW5kaWNhdG9yPlxuICApO1xufTtcblxuY29uc3QgQ3VzdG9tU3R5bGVkUmVtb3ZlQWxsRGl2ID0gc3R5bGVkKCdkaXYnKShcbiAgY3NzKHtcbiAgICAnJjpmb2N1cyc6IHtcbiAgICAgIG91dGxpbmU6IGAycHggc29saWQgJHt0aGVtZS5jb2xvcnMucHJpbWFyeX1gLFxuICAgIH0sXG4gICAgJyY6Zm9jdXMtdmlzaWJsZSc6IHtcbiAgICAgIG91dGxpbmU6IGAycHggc29saWQgJHt0aGVtZS5jb2xvcnMucHJpbWFyeX1gLFxuICAgIH0sXG4gIH0pXG4pO1xuXG4vKipcbiAqIEN1c3RvbSByZW1vdmUgYWxsIGJ1dHRvbiBmb3IgbXVsdGktc2VsZWN0IG1vZGUuXG4gKiBQcm92aWRlcyBrZXlib2FyZCBuYXZpZ2F0aW9uIGFuZCBhY2Nlc3NpYmxlIHJlbW92YWwgb2YgYWxsIHNlbGVjdGVkIHZhbHVlcy5cbiAqL1xuZXhwb3J0IGNvbnN0IFJlbW92ZUFsbEJ1dHRvbiA9IChwcm9wczogU2l6ZWRJbmRpY2F0b3JQcm9wcykgPT4ge1xuICBjb25zdCB7XG4gICAgZ2V0U3R5bGVzLFxuICAgIGlubmVyUHJvcHM6IHsgLi4ucmVzdElubmVyUHJvcHMgfSxcbiAgICBzZWxlY3RQcm9wczogeyBzaXplIH0sXG4gIH0gPSBwcm9wcztcblxuICBjb25zdCB7IHJlbW92ZUFsbEJ1dHRvblJlZiwgc2VsZWN0SW5wdXRSZWYgfSA9IHVzZUNvbnRleHQoXG4gICAgU2VsZWN0RHJvcGRvd25Db250ZXh0XG4gICk7XG5cbiAgY29uc3QgaWNvblNpemUgPSBzaXplID8/ICdtZWRpdW0nO1xuICBjb25zdCB7IC4uLmljb25Qcm9wcyB9ID0gaW5kaWNhdG9ySWNvbnNbYCR7aWNvblNpemV9UmVtb3ZlYF07XG4gIGNvbnN0IHsgaWNvbjogSW5kaWNhdG9ySWNvbiB9ID0gaWNvblByb3BzO1xuXG4gIGNvbnN0IG9uS2V5UHJlc3MgPSAoZTogS2V5Ym9hcmRFdmVudDxIVE1MRGl2RWxlbWVudD4pID0+IHtcbiAgICBpZiAoZS5rZXkgPT09ICdFbnRlcicgJiYgcmVzdElubmVyUHJvcHMub25Nb3VzZURvd24pIHtcbiAgICAgIHJlc3RJbm5lclByb3BzLm9uTW91c2VEb3duKGUgYXMgYW55KTtcbiAgICB9XG5cbiAgICBpZiAoXG4gICAgICBzZWxlY3RJbnB1dFJlZj8uY3VycmVudCAmJlxuICAgICAgKGUua2V5ID09PSAnQXJyb3dSaWdodCcgfHwgZS5rZXkgPT09ICdBcnJvd0xlZnQnIHx8IGUua2V5ID09PSAnQXJyb3dEb3duJylcbiAgICApIHtcbiAgICAgIHNlbGVjdElucHV0UmVmPy5jdXJyZW50LmZvY3VzKCk7XG4gICAgfVxuICB9O1xuXG4gIGNvbnN0IHN0eWxlID0gZ2V0U3R5bGVzKCdjbGVhckluZGljYXRvcicsIHByb3BzKSBhcyBSZWFjdC5DU1NQcm9wZXJ0aWVzO1xuXG4gIHJldHVybiAoXG4gICAgPEN1c3RvbVN0eWxlZFJlbW92ZUFsbERpdlxuICAgICAgYXJpYS1sYWJlbD1cIlJlbW92ZSBhbGwgc2VsZWN0ZWRcIlxuICAgICAgcm9sZT1cImJ1dHRvblwiXG4gICAgICB0YWJJbmRleD17MH1cbiAgICAgIHsuLi5yZXN0SW5uZXJQcm9wc31cbiAgICAgIHJlZj17cmVtb3ZlQWxsQnV0dG9uUmVmfVxuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGdhbXV0L25vLWlubGluZS1zdHlsZVxuICAgICAgc3R5bGU9e3N0eWxlfVxuICAgICAgb25LZXlEb3duPXtvbktleVByZXNzfVxuICAgID5cbiAgICAgIDxJbmRpY2F0b3JJY29uIHsuLi5pY29uUHJvcHN9IGNvbG9yPVwidGV4dFwiIC8+XG4gICAgPC9DdXN0b21TdHlsZWRSZW1vdmVBbGxEaXY+XG4gICk7XG59O1xuIl19 */");
69
+ }), process.env.NODE_ENV === "production" ? "" : "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9Gb3JtL1NlbGVjdERyb3Bkb3duL2VsZW1lbnRzL2NvbnRyb2xzLnRzeCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFxRGlDIiwiZmlsZSI6Ii4uLy4uLy4uLy4uL3NyYy9Gb3JtL1NlbGVjdERyb3Bkb3duL2VsZW1lbnRzL2NvbnRyb2xzLnRzeCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNzcywgdGhlbWUgfSBmcm9tICdAY29kZWNhZGVteS9nYW11dC1zdHlsZXMnO1xuaW1wb3J0IHN0eWxlZCBmcm9tICdAZW1vdGlvbi9zdHlsZWQnO1xuaW1wb3J0IHsgS2V5Ym9hcmRFdmVudCwgdXNlQ29udGV4dCB9IGZyb20gJ3JlYWN0JztcbmltcG9ydCB7XG4gIEFyaWFPbkZvY3VzLFxuICBjb21wb25lbnRzIGFzIFNlbGVjdERyb3Bkb3duRWxlbWVudHMsXG59IGZyb20gJ3JlYWN0LXNlbGVjdCc7XG5cbmltcG9ydCB7IEV4dGVuZGVkT3B0aW9uLCBTaXplZEluZGljYXRvclByb3BzIH0gZnJvbSAnLi4vdHlwZXMnO1xuaW1wb3J0IHsgaW5kaWNhdG9ySWNvbnMgfSBmcm9tICcuL2NvbnN0YW50cyc7XG5pbXBvcnQgeyBTZWxlY3REcm9wZG93bkNvbnRleHQgfSBmcm9tICcuL2NvbnRhaW5lcnMnO1xuXG5jb25zdCB7IERyb3Bkb3duSW5kaWNhdG9yIH0gPSBTZWxlY3REcm9wZG93bkVsZW1lbnRzO1xuXG4vKipcbiAqIEdlbmVyYXRlcyBhY2Nlc3NpYmxlIGZvY3VzIG1lc3NhZ2VzIGZvciBzY3JlZW4gcmVhZGVycy5cbiAqIFByb3ZpZGVzIGRldGFpbGVkIGluZm9ybWF0aW9uIGFib3V0IHRoZSBjdXJyZW50bHkgZm9jdXNlZCBvcHRpb24uXG4gKlxuICogQHBhcmFtIHBhcmFtcyAtIE9iamVjdCBjb250YWluaW5nIHRoZSBmb2N1c2VkIG9wdGlvbiBkZXRhaWxzXG4gKiBAcmV0dXJucyBGb3JtYXR0ZWQgYWNjZXNzaWJpbGl0eSBtZXNzYWdlXG4gKi9cbmV4cG9ydCBjb25zdCBvbkZvY3VzOiBBcmlhT25Gb2N1czxFeHRlbmRlZE9wdGlvbj4gPSAoe1xuICBmb2N1c2VkOiB7IGxhYmVsLCBzdWJ0aXRsZSwgcmlnaHRMYWJlbCwgZGlzYWJsZWQgfSxcbn0pID0+IHtcbiAgY29uc3QgZm9ybWF0dGVkU3VidGl0bGUgPSBgLCAke3N1YnRpdGxlfWA7XG4gIGNvbnN0IGZvcm1hdHRlZFJpZ2h0TGFiZWwgPSBgLCAke3JpZ2h0TGFiZWx9YDtcblxuICBjb25zdCBtc2cgPSBgWW91IGFyZSBjdXJyZW50bHkgZm9jdXNlZCBvbiBvcHRpb24gJHtsYWJlbH0ke1xuICAgIHN1YnRpdGxlID8gZm9ybWF0dGVkU3VidGl0bGUgOiAnJ1xuICB9ICR7cmlnaHRMYWJlbCA/IGZvcm1hdHRlZFJpZ2h0TGFiZWwgOiAnJ30ke2Rpc2FibGVkID8gJywgZGlzYWJsZWQnIDogJyd9YDtcblxuICByZXR1cm4gbXNnO1xufTtcblxuLyoqXG4gKiBDdXN0b20gZHJvcGRvd24gaW5kaWNhdG9yIHRoYXQgc2hvd3MgZWl0aGVyIGEgY2hldnJvbiBvciBzZWFyY2ggaWNvbi5cbiAqIFRoZSBpY29uIHR5cGUgZGVwZW5kcyBvbiB3aGV0aGVyIHRoZSBzZWxlY3QgaXMgc2VhcmNoYWJsZSBvciBub3QuXG4gKi9cbmV4cG9ydCBjb25zdCBEcm9wZG93bkJ1dHRvbiA9IChwcm9wczogU2l6ZWRJbmRpY2F0b3JQcm9wcykgPT4ge1xuICBjb25zdCB7IHNpemUsIGlzU2VhcmNoYWJsZSB9ID0gcHJvcHMuc2VsZWN0UHJvcHM7XG4gIGNvbnN0IGNvbG9yID0gcHJvcHMuaXNEaXNhYmxlZCA/ICd0ZXh0LWRpc2FibGVkJyA6ICd0ZXh0JztcbiAgY29uc3QgaWNvblNpemUgPSBzaXplID8/ICdtZWRpdW0nO1xuICBjb25zdCBpY29uVHlwZSA9IGlzU2VhcmNoYWJsZSA/ICdTZWFyY2hhYmxlJyA6ICdDaGV2cm9uJztcbiAgY29uc3QgeyAuLi5pY29uUHJvcHMgfSA9IGluZGljYXRvckljb25zW2Ake2ljb25TaXplfSR7aWNvblR5cGV9YF07XG4gIGNvbnN0IHsgaWNvbjogSW5kaWNhdG9ySWNvbiB9ID0gaWNvblByb3BzO1xuXG4gIHJldHVybiAoXG4gICAgPERyb3Bkb3duSW5kaWNhdG9yIHsuLi5wcm9wc30+XG4gICAgICA8SW5kaWNhdG9ySWNvbiB7Li4uaWNvblByb3BzfSBjb2xvcj17Y29sb3J9IC8+XG4gICAgPC9Ecm9wZG93bkluZGljYXRvcj5cbiAgKTtcbn07XG5cbmNvbnN0IEN1c3RvbVN0eWxlZFJlbW92ZUFsbERpdiA9IHN0eWxlZCgnZGl2JykoXG4gIGNzcyh7XG4gICAgJyY6Zm9jdXMnOiB7XG4gICAgICBvdXRsaW5lOiBgMnB4IHNvbGlkICR7dGhlbWUuY29sb3JzLnByaW1hcnl9YCxcbiAgICB9LFxuICAgICcmOmZvY3VzLXZpc2libGUnOiB7XG4gICAgICBvdXRsaW5lOiBgMnB4IHNvbGlkICR7dGhlbWUuY29sb3JzLnByaW1hcnl9YCxcbiAgICB9LFxuICB9KVxuKTtcblxuLyoqXG4gKiBDdXN0b20gcmVtb3ZlIGFsbCBidXR0b24gZm9yIG11bHRpLXNlbGVjdCBtb2RlLlxuICogUHJvdmlkZXMga2V5Ym9hcmQgbmF2aWdhdGlvbiBhbmQgYWNjZXNzaWJsZSByZW1vdmFsIG9mIGFsbCBzZWxlY3RlZCB2YWx1ZXMuXG4gKi9cbmV4cG9ydCBjb25zdCBSZW1vdmVBbGxCdXR0b24gPSAocHJvcHM6IFNpemVkSW5kaWNhdG9yUHJvcHMpID0+IHtcbiAgY29uc3Qge1xuICAgIGdldFN0eWxlcyxcbiAgICBpbm5lclByb3BzOiB7IC4uLnJlc3RJbm5lclByb3BzIH0sXG4gICAgc2VsZWN0UHJvcHM6IHsgc2l6ZSB9LFxuICB9ID0gcHJvcHM7XG5cbiAgY29uc3QgeyByZW1vdmVBbGxCdXR0b25SZWYsIHNlbGVjdElucHV0UmVmIH0gPSB1c2VDb250ZXh0KFxuICAgIFNlbGVjdERyb3Bkb3duQ29udGV4dFxuICApO1xuXG4gIGNvbnN0IGljb25TaXplID0gc2l6ZSA/PyAnbWVkaXVtJztcbiAgY29uc3QgeyAuLi5pY29uUHJvcHMgfSA9IGluZGljYXRvckljb25zW2Ake2ljb25TaXplfVJlbW92ZWBdO1xuICBjb25zdCB7IGljb246IEluZGljYXRvckljb24gfSA9IGljb25Qcm9wcztcblxuICBjb25zdCBvbktleVByZXNzID0gKGU6IEtleWJvYXJkRXZlbnQ8SFRNTERpdkVsZW1lbnQ+KSA9PiB7XG4gICAgaWYgKGUua2V5ID09PSAnRW50ZXInICYmIHJlc3RJbm5lclByb3BzLm9uTW91c2VEb3duKSB7XG4gICAgICByZXN0SW5uZXJQcm9wcy5vbk1vdXNlRG93bihlIGFzIGFueSk7XG4gICAgfVxuXG4gICAgaWYgKFxuICAgICAgc2VsZWN0SW5wdXRSZWY/LmN1cnJlbnQgJiZcbiAgICAgIChlLmtleSA9PT0gJ0Fycm93UmlnaHQnIHx8IGUua2V5ID09PSAnQXJyb3dMZWZ0JyB8fCBlLmtleSA9PT0gJ0Fycm93RG93bicpXG4gICAgKSB7XG4gICAgICBzZWxlY3RJbnB1dFJlZj8uY3VycmVudC5mb2N1cygpO1xuICAgIH1cbiAgfTtcblxuICBjb25zdCBzdHlsZSA9IGdldFN0eWxlcygnY2xlYXJJbmRpY2F0b3InLCBwcm9wcykgYXMgUmVhY3QuQ1NTUHJvcGVydGllcztcblxuICByZXR1cm4gKFxuICAgIDxDdXN0b21TdHlsZWRSZW1vdmVBbGxEaXZcbiAgICAgIGFyaWEtbGFiZWw9XCJSZW1vdmUgYWxsIHNlbGVjdGVkXCJcbiAgICAgIHJvbGU9XCJidXR0b25cIlxuICAgICAgdGFiSW5kZXg9ezB9XG4gICAgICB7Li4ucmVzdElubmVyUHJvcHN9XG4gICAgICByZWY9e3JlbW92ZUFsbEJ1dHRvblJlZn1cbiAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBnYW11dC9uby1pbmxpbmUtc3R5bGVcbiAgICAgIHN0eWxlPXtzdHlsZX1cbiAgICAgIG9uS2V5RG93bj17b25LZXlQcmVzc31cbiAgICA+XG4gICAgICA8SW5kaWNhdG9ySWNvbiB7Li4uaWNvblByb3BzfSBjb2xvcj1cInRleHRcIiAvPlxuICAgIDwvQ3VzdG9tU3R5bGVkUmVtb3ZlQWxsRGl2PlxuICApO1xufTtcbiJdfQ== */");
68
70
 
69
71
  /**
70
72
  * Custom remove all button for multi-select mode.
@@ -3,7 +3,6 @@ import { CustomSelectComponentProps, ExtendedOption, SelectDropdownGroup } from
3
3
  /**
4
4
  * Custom option component that displays a check icon for selected items.
5
5
  * Also manages ARIA attributes for accessibility.
6
- * Skips the check icon for react-select/creatable's "Add" row (__isNew__).
7
6
  */
8
7
  export declare const IconOption: ({ children, ...rest }: CustomSelectComponentProps<typeof SelectDropdownElements.Option>) => import("react/jsx-runtime").JSX.Element;
9
8
  /**
@@ -44,7 +44,6 @@ const IconOptionLabel = ({
44
44
  /**
45
45
  * Custom option component that displays a check icon for selected items.
46
46
  * Also manages ARIA attributes for accessibility.
47
- * Skips the check icon for react-select/creatable's "Add" row (__isNew__).
48
47
  */
49
48
  export const IconOption = ({
50
49
  children,
@@ -55,17 +54,15 @@ export const IconOption = ({
55
54
  } = rest.selectProps;
56
55
  const {
57
56
  isFocused,
58
- innerProps,
59
- data
57
+ innerProps
60
58
  } = rest;
61
- const isNew = data?.__isNew__;
62
59
  return /*#__PURE__*/_jsxs(SelectDropdownElements.Option, {
63
60
  ...rest,
64
61
  innerProps: {
65
62
  ...innerProps,
66
63
  'aria-selected': isFocused
67
64
  },
68
- children: [children, !isNew && rest?.isSelected && /*#__PURE__*/_jsx(CheckIcon, {
65
+ children: [children, rest?.isSelected && /*#__PURE__*/_jsx(CheckIcon, {
69
66
  size: selectedIconSize[size ?? 'medium']
70
67
  })]
71
68
  });
@@ -137,8 +137,6 @@ export const getMemoizedStyles = (theme, zIndex) => {
137
137
  error: state.selectProps.error,
138
138
  theme
139
139
  }),
140
- // Drop react-select's default menu drop shadow; the border above defines the edge.
141
- boxShadow: 'none',
142
140
  ...(dropdownWidth ? {
143
141
  minWidth: dropdownWidth,
144
142
  width: dropdownWidth
@@ -196,32 +194,16 @@ export const getMemoizedStyles = (theme, zIndex) => {
196
194
  backgroundColor: theme.colors['secondary-hover']
197
195
  }
198
196
  }),
199
- noOptionsMessage: provided => ({
200
- ...provided,
201
- color: theme.colors['text-secondary']
197
+ option: (provided, state) => ({
198
+ ...getOptionBackground(state.isSelected, state.isFocused)({
199
+ theme
200
+ }),
201
+ alignItems: 'center',
202
+ color: state.isDisabled ? 'text-disabled' : 'default',
203
+ cursor: state.isDisabled ? 'not-allowed' : 'pointer',
204
+ display: 'flex',
205
+ padding: state.selectProps.size === 'small' ? '3px 14px' : '11px 14px'
202
206
  }),
203
- option: (provided, state) => {
204
- const isNew = state.data?.__isNew__;
205
- const isSmall = state.selectProps.size === 'small';
206
- return {
207
- ...getOptionBackground(state.isSelected, state.isFocused)({
208
- theme
209
- }),
210
- alignItems: 'center',
211
- color: isNew ? state.isDisabled ? theme.colors['text-disabled'] : theme.colors.primary : state.isDisabled ? theme.colors['text-disabled'] : theme.colors.text,
212
- cursor: state.isDisabled ? 'not-allowed' : 'pointer',
213
- display: 'flex',
214
- padding: isSmall ? '3px 14px' : '11px 14px',
215
- ...(isNew && {
216
- // Gradient creates the 1px divider line centred in the 16px spacer above the option text
217
- backgroundImage: `linear-gradient(${theme.colors['text-disabled']} 1px, transparent 1px)`,
218
- backgroundPosition: '0 8px',
219
- backgroundRepeat: 'no-repeat',
220
- backgroundSize: '100% 1px',
221
- paddingTop: isSmall ? '19px' : '27px'
222
- })
223
- };
224
- },
225
207
  placeholder: provided => ({
226
208
  ...provided,
227
209
  ...placeholderColor({
@@ -1,5 +1,5 @@
1
1
  import { Ref, SelectHTMLAttributes } from 'react';
2
- import { Options as OptionsType, Props as NamedProps } from 'react-select';
2
+ import { Props as NamedProps } from 'react-select';
3
3
  import { SelectComponentProps } from '../../inputs/Select';
4
4
  import { OptionStrict, SelectDropdownGroup, SelectDropdownOptions } from './options';
5
5
  import { ReactSelectAdditionalProps, SelectDropdownSizes, SharedProps } from './styles';
@@ -28,7 +28,7 @@ export type SelectDropdownBaseProps = Omit<SelectComponentProps, 'onChange' | 'd
28
28
  * Core props interface that defines the essential properties for SelectDropdown.
29
29
  * This interface combines base props with react-select props and HTML select attributes.
30
30
  */
31
- export interface SelectDropdownCoreProps extends SelectDropdownBaseProps, Omit<NamedProps<OptionStrict, boolean>, 'formatOptionLabel' | 'isDisabled' | 'value' | 'options' | 'components' | 'styles' | 'theme' | 'onChange' | 'multiple' | 'isSearchable'>, Pick<SelectHTMLAttributes<HTMLSelectElement>, 'value' | 'disabled' | 'onClick'>, SharedProps {
31
+ export interface SelectDropdownCoreProps extends SelectDropdownBaseProps, Omit<NamedProps<OptionStrict, boolean>, 'formatOptionLabel' | 'isDisabled' | 'value' | 'options' | 'components' | 'styles' | 'theme' | 'onChange' | 'multiple'>, Pick<SelectHTMLAttributes<HTMLSelectElement>, 'value' | 'disabled' | 'onClick'>, SharedProps {
32
32
  /** Required name attribute for the select input */
33
33
  name: string;
34
34
  /** Placeholder text shown when no option is selected.
@@ -38,39 +38,6 @@ export interface SelectDropdownCoreProps extends SelectDropdownBaseProps, Omit<N
38
38
  placeholder?: string;
39
39
  /** Array of options or option groups to display in the dropdown */
40
40
  options?: SelectDropdownOptions | SelectDropdownGroup[];
41
- /**
42
- * Allows users to create new options by typing a value not in the options list.
43
- * When true, isSearchable is automatically set to true.
44
- * Pair with onCreateOption to persist new options.
45
- */
46
- isCreatable?: boolean;
47
- /**
48
- * Called when the user confirms a new option via the "Add" row.
49
- * Convenience callback for persisting the new value to your `options` list.
50
- * Selection updates are delivered through `onChange` with `action: 'create-option'`.
51
- */
52
- onCreateOption?: (inputValue: string) => void;
53
- /**
54
- * Customises the label shown in the "Add" row.
55
- * Defaults to: (inputValue) => `Add "${inputValue}"`.
56
- */
57
- formatCreateLabel?: (inputValue: string) => React.ReactNode;
58
- /**
59
- * Controls when the "Add" row is visible.
60
- * Receives the current input, selected values, and all options.
61
- * Defaults to react-select's built-in logic (hidden when input matches an existing option label).
62
- * Use cases: minimum-length gating, pattern validation, case-insensitive dedup, max-items cap.
63
- */
64
- isValidNewOption?: (inputValue: string, value: OptionsType<OptionStrict>, options: OptionsType<OptionStrict>) => boolean;
65
- /**
66
- * Customizes the message shown inside the dropdown menu when no option matches
67
- * the current input (react-select's "No options" state). Useful for surfacing
68
- * validation/error text directly in the dropdown. Accepts a node, or a function
69
- * receiving the current input value.
70
- */
71
- validationMessage?: React.ReactNode | ((obj: {
72
- inputValue: string;
73
- }) => React.ReactNode);
74
41
  }
75
42
  /**
76
43
  * Props for single-select mode.
@@ -92,23 +59,11 @@ export interface MultiSelectDropdownProps extends SelectDropdownCoreProps {
92
59
  /** Callback fired when the selected values change */
93
60
  onChange?: NamedProps<OptionStrict, true>['onChange'];
94
61
  }
95
- /**
96
- * Enforces that isSearchable cannot be false when isCreatable is true.
97
- * Creatable mode requires the search input so users can type new option values.
98
- */
99
- type CreatableConstraint = {
100
- isCreatable?: false | undefined;
101
- isSearchable?: boolean;
102
- } | {
103
- isCreatable: true;
104
- isSearchable?: true;
105
- };
106
62
  /**
107
63
  * Union type for all SelectDropdown prop variants.
108
- * Supports both single and multi-select modes through discriminated union,
109
- * intersected with CreatableConstraint to enforce isSearchable compatibility.
64
+ * Supports both single and multi-select modes through discriminated union.
110
65
  */
111
- export type SelectDropdownProps = (SingleSelectDropdownProps | MultiSelectDropdownProps) & CreatableConstraint;
66
+ export type SelectDropdownProps = SingleSelectDropdownProps | MultiSelectDropdownProps;
112
67
  /**
113
68
  * Base interface for onChange-related props.
114
69
  * Used internally for type checking and prop validation.
@@ -121,16 +76,9 @@ export interface BaseOnChangeProps {
121
76
  }
122
77
  /**
123
78
  * Props for the typed React Select component wrapper.
124
- * Extends ReactSelectAdditionalProps with an optional ref and creatable flag.
79
+ * Extends ReactSelectAdditionalProps with an optional ref.
125
80
  */
126
81
  export interface TypedReactSelectProps extends ReactSelectAdditionalProps {
127
82
  /** Optional ref to the underlying react-select component */
128
83
  selectRef?: Ref<any>;
129
- /** When true, renders CreatableSelect instead of ReactSelect */
130
- isCreatable?: boolean;
131
- /** Customises the "Add" row label */
132
- formatCreateLabel?: (inputValue: string) => React.ReactNode;
133
- /** Controls visibility of the "Add" row */
134
- isValidNewOption?: (inputValue: string, value: OptionsType<OptionStrict>, options: OptionsType<OptionStrict>) => boolean;
135
84
  }
136
- export {};
@@ -69,9 +69,5 @@ export type ControlState = BaseSelectComponentProps & InteractionStates & {
69
69
  export type OptionState = BaseSelectComponentProps & InteractionStates & {
70
70
  /** Whether the option is selected */
71
71
  isSelected: boolean;
72
- /** Option data — includes __isNew__ for react-select/creatable's "Add" row */
73
- data?: {
74
- __isNew__?: boolean;
75
- };
76
72
  };
77
73
  export {};
@@ -1,13 +1,7 @@
1
- import { ActionMeta, Options as OptionsType } from 'react-select';
2
1
  import { SelectOptionBase } from '../utils';
3
- import { BaseOnChangeProps, ExtendedOption, MultiSelectDropdownProps, OptionStrict, SelectDropdownGroup, SelectDropdownOptions, SelectDropdownProps, SingleSelectDropdownProps } from './types';
2
+ import { BaseOnChangeProps, ExtendedOption, MultiSelectDropdownProps, SelectDropdownGroup, SelectDropdownOptions, SelectDropdownProps, SingleSelectDropdownProps } from './types';
4
3
  export declare const isMultipleSelectProps: (props: BaseOnChangeProps) => props is MultiSelectDropdownProps;
5
4
  export declare const isSingleSelectProps: (props: BaseOnChangeProps) => props is SingleSelectDropdownProps;
6
- /**
7
- * Resolves the value for a newly created option from react-select action metadata
8
- * or the onChange option payload. Returns undefined when no reliable value exists.
9
- */
10
- export declare const getCreatedOptionValue: (optionEvent: OptionStrict | OptionsType<OptionStrict>, actionMeta: ActionMeta<OptionStrict>, multiple?: boolean) => string | undefined;
11
5
  export declare const isOptionGroup: (obj: unknown) => obj is SelectDropdownGroup;
12
6
  export declare const isOptionsGrouped: (options: SelectDropdownOptions) => options is SelectDropdownGroup[];
13
7
  /**
@@ -1,21 +1,5 @@
1
1
  export const isMultipleSelectProps = props => !!props.multiple;
2
2
  export const isSingleSelectProps = props => !props.multiple;
3
- /**
4
- * Resolves the value for a newly created option from react-select action metadata
5
- * or the onChange option payload. Returns undefined when no reliable value exists.
6
- */
7
- export const getCreatedOptionValue = (optionEvent, actionMeta, multiple) => {
8
- const metaValue = actionMeta.option?.value;
9
- if (metaValue) return metaValue;
10
- if (!multiple) {
11
- const {
12
- value
13
- } = optionEvent;
14
- return value || undefined;
15
- }
16
- const newOption = optionEvent.find(option => option.__isNew__);
17
- return newOption?.value || undefined;
18
- };
19
3
  export const isOptionGroup = obj => obj != null && typeof obj === 'object' && 'options' in obj && obj.options !== undefined;
20
4
  export const isOptionsGrouped = options => Array.isArray(options) && options.some(option => isOptionGroup(option));
21
5
 
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@codecademy/gamut",
3
3
  "description": "Styleguide & Component library for Codecademy",
4
- "version": "72.0.1-alpha.db0637.0",
4
+ "version": "72.0.1",
5
5
  "author": "Codecademy Engineering <dev@codecademy.com>",
6
6
  "bin": "./bin/gamut.mjs",
7
7
  "dependencies": {
8
- "@codecademy/gamut-icons": "9.57.9-alpha.db0637.0",
9
- "@codecademy/gamut-illustrations": "0.58.15-alpha.db0637.0",
10
- "@codecademy/gamut-patterns": "0.10.34-alpha.db0637.0",
11
- "@codecademy/gamut-styles": "20.0.2-alpha.db0637.0",
12
- "@codecademy/variance": "0.26.2-alpha.db0637.0",
8
+ "@codecademy/gamut-icons": "9.57.9",
9
+ "@codecademy/gamut-illustrations": "0.58.15",
10
+ "@codecademy/gamut-patterns": "0.10.34",
11
+ "@codecademy/gamut-styles": "20.0.2",
12
+ "@codecademy/variance": "0.26.1",
13
13
  "@formatjs/intl-locale": "5.3.1",
14
14
  "@react-aria/interactions": "3.25.0",
15
15
  "@types/marked": "^4.0.8",
@@ -1,183 +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 react-select action meta. Pair with gamut-forms for FormGroup/validation patterns.
4
- ---
5
-
6
- # Gamut SelectDropdown
7
-
8
- Styled dropdown built on react-select. Supports single and multi-select, searchable menus, creatable options, icons, groups, and abbreviations.
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
- ---
58
-
59
- ## onChange contract
60
-
61
- `onChange` receives option object(s), not `event.target.value`:
62
-
63
- ```tsx
64
- // Single
65
- onChange={(option) => setValue(option.value)}
66
-
67
- // Multi
68
- onChange={(selected) => setValue(selected.map((o) => o.value))}
69
- ```
70
-
71
- 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`.
72
-
73
- ---
74
-
75
- ## Creatable
76
-
77
- - `isCreatable` forces `isSearchable: true` (TypeScript enforces this).
78
- - `onCreateOption(inputValue)` — convenience hook to append to `options`.
79
- - `onChange(selected, meta)` — use `meta.action === 'create-option'` to sync controlled `value` and `options` together.
80
- - `isValidNewOption` — return `false` to hide the Add row.
81
- - `validationMessage` — replaces menu "No options" text; mirror in `FormGroup` `error` for field-level feedback.
82
-
83
- **Validation after blur:** react-select clears input on blur. Handle `onInputChange`: validate on `input-change`, re-validate from last typed value on `input-blur` so FormGroup error persists.
84
-
85
- ---
86
-
87
- ## FormGroup wiring
88
-
89
- - `FormGroup` `htmlFor` must match control `id` / `name`.
90
- - Pass `name` on SelectDropdown (required for forms).
91
- - Pass `aria-label` (required for forms); it must match the FormGroupLabel `htmlFor` / `name`.
92
- - Pass `error` boolean when FormGroup has an error.
93
- - Generic FormGroup live-region behavior: see [`gamut-forms`](../gamut-forms/SKILL.md).
94
-
95
- ```tsx
96
- <FormGroup htmlFor="country" isSoloField label="Country" error={errors.country}>
97
- <SelectDropdown
98
- name="country"
99
- aria-label="country"
100
- options={options}
101
- value={value}
102
- error={Boolean(errors.country)}
103
- onChange={(option) => setValue(option.value)}
104
- />
105
- </FormGroup>
106
- ```
107
-
108
- ---
109
-
110
- ## Styling & layout props
111
-
112
- | Prop | Type | Default | Notes |
113
- | ------------------- | ------------------------ | -------- | --------------------------------------------------------- |
114
- | `size` | `'small' \| 'medium'` | `medium` | Control height/density |
115
- | `shownOptionsLimit` | `1`–`6` | `6` | Visible options before the menu scrolls |
116
- | `inputWidth` | `string \| number` | — | Width of the input independent of the menu |
117
- | `dropdownWidth` | `string \| number` | — | Width of the menu independent of the input |
118
- | `menuAlignment` | `'left' \| 'right'` | `left` | Menu edge alignment |
119
- | `zIndex` | `number` | auto | Menu z-index |
120
- | `inputProps` | `{ hidden?, combobox? }` | — | `data-*` / `aria-*` only, forwarded to the input elements |
121
-
122
- ---
123
-
124
- ## Examples
125
-
126
- ### Single (controlled)
127
-
128
- ```tsx
129
- const [value, setValue] = useState('us');
130
-
131
- <SelectDropdown
132
- name="country"
133
- options={options}
134
- value={value}
135
- onChange={(option) => setValue(option.value)}
136
- />;
137
- ```
138
-
139
- ### Multi (uncontrolled)
140
-
141
- ```tsx
142
- <SelectDropdown
143
- multiple
144
- name="tags"
145
- options={options}
146
- onChange={(selected) => console.log(selected)}
147
- />
148
- ```
149
-
150
- ### Creatable multi (uncontrolled)
151
-
152
- ```tsx
153
- const [options, setOptions] = useState(['Apple', 'Banana']);
154
-
155
- <SelectDropdown
156
- isCreatable
157
- multiple
158
- name="fruits"
159
- options={options}
160
- onCreateOption={(v) => setOptions((prev) => [...prev, v])}
161
- />;
162
- ```
163
-
164
- ### Creatable multi (controlled)
165
-
166
- ```tsx
167
- const [options, setOptions] = useState(['Apple', 'Banana']);
168
- const [value, setValue] = useState<string[]>([]);
169
-
170
- <SelectDropdown
171
- isCreatable
172
- multiple
173
- name="fruits"
174
- options={options}
175
- value={value}
176
- onChange={(selected, meta) => {
177
- setValue(selected.map((o) => o.value));
178
- if (meta.action === 'create-option' && meta.option) {
179
- setOptions((prev) => [...prev, meta.option.value]);
180
- }
181
- }}
182
- />;
183
- ```