@codecademy/gamut
71.0.071.0.1-alpha.69ab4c.0
dist/Form/SelectDropdown/SelectDropdown.js~
dist/Form/SelectDropdown/SelectDropdown.jsModified+35−18
Index: package/dist/Form/SelectDropdown/SelectDropdown.js
===================================================================
--- package/dist/Form/SelectDropdown/SelectDropdown.js
+++ package/dist/Form/SelectDropdown/SelectDropdown.js
@@ -3,9 +3,9 @@
import * as React from 'react';
import { parseOptions } from '../utils';
import { AbbreviatedSingleValue, CustomContainer, CustomInput, CustomValueContainer, DropdownButton, formatGroupLabel, formatOptionLabel, IconOption, MultiValueRemoveButton, MultiValueWithColorMode, onFocus, RemoveAllButton, SelectDropdownContext, TypedReactSelect } from './elements';
import { getMemoizedStyles } from './styles';
-import { filterValueFromOptions, isMultipleSelectProps, isOptionsGrouped, isSingleSelectProps, removeValueFromSelectedOptions } from './utils';
+import { filterValueFromOptions, getCreatedOptionValue, isMultipleSelectProps, isOptionsGrouped, isSingleSelectProps, removeValueFromSelectedOptions } from './utils';
import { jsx as _jsx } from "react/jsx-runtime";
const defaultProps = {
name: undefined,
components: {
@@ -72,24 +72,32 @@
export const SelectDropdown = ({
disabled,
dropdownWidth,
error,
+ formatCreateLabel = inputValue => `Add "${inputValue}"`,
id,
inputProps,
inputWidth,
- isSearchable = false,
+ isCreatable = false,
+ isSearchable: isSearchableProp = false,
+ isValidNewOption,
menuAlignment = 'left',
multiple,
name,
onChange,
+ onCreateOption,
+ onInputChange,
options,
placeholder = 'Select an option',
shownOptionsLimit = 6,
size,
+ validationMessage,
value,
zIndex,
...rest
}) => {
+ // isSearchable is forced true when isCreatable is true (CreatableSelect requires a text input)
+ const isSearchable = isCreatable || isSearchableProp;
const rawInputId = useId();
const inputId = name ?? `${id}-select-dropdown-${rawInputId}`;
const [activated, setActivated] = useState(false);
const [currentFocusedValue, setCurrentFocusedValue] = useState(undefined);
@@ -125,41 +133,43 @@
const [multiValues, setMultiValues] = useState(multiple &&
// To keep this efficient for non-multiSelect
filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions)));
- // If the caller changes the initial value, let's update our value to match.
+ // Sync multi-select value from props when controlled (`value` is a string[]).
+ // Uncontrolled multi (`value` undefined or '') keeps selection in local state.
useEffect(() => {
+ if (!multiple || !Array.isArray(value)) return;
const newMultiValues = filterValueFromOptions(selectOptions, value, isOptionsGrouped(selectOptions));
if (newMultiValues !== multiValues) setMultiValues(newMultiValues);
- //
// We only update this when our passed in options or value changes, not multiValues.
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [options, value]);
- const changeHandler = useCallback(optionEvent => {
+ }, [options, value, multiple]);
+ const changeHandler = useCallback((optionEvent, actionMeta) => {
setActivated(true);
-
- // 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!
- // Should probably come with: https://codecademy.atlassian.net/browse/GM-354
+ if (actionMeta.action === 'create-option') {
+ const createdValue = getCreatedOptionValue(optionEvent, actionMeta, multiple);
+ if (createdValue) {
+ onCreateOption?.(createdValue);
+ }
+ }
const onChangeProps = {
onChange,
multiple
};
+ const forwardedMeta = actionMeta.action === 'create-option' ? actionMeta : {
+ action: onChangeAction,
+ option: isMultipleSelectProps(onChangeProps) ? undefined : optionEvent
+ };
if (isSingleSelectProps(onChangeProps)) {
const singleOptionEvent = optionEvent;
- onChangeProps.onChange?.(singleOptionEvent, {
- action: onChangeAction,
- option: singleOptionEvent
- });
+ onChangeProps.onChange?.(singleOptionEvent, forwardedMeta);
}
if (isMultipleSelectProps(onChangeProps)) {
setMultiValues(optionEvent);
- onChangeProps.onChange?.(optionEvent, {
- action: onChangeAction,
- 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)
- });
+ onChangeProps.onChange?.(optionEvent, forwardedMeta);
}
- }, [onChange, multiple]);
+ }, [onChange, multiple, onCreateOption]);
const keyPressHandler = e => {
if (multiple && e.key === 'Enter' && currentFocusedValue && multiValues) {
const newMultiValues = removeValueFromSelectedOptions(multiValues, currentFocusedValue);
if (newMultiValues !== multiValues) setMultiValues(newMultiValues);
@@ -167,8 +177,10 @@
if (removeAllButtonRef.current !== null && e.key === 'ArrowRight' && multiValues && currentFocusedValue === multiValues[multiValues.length - 1].value) {
removeAllButtonRef.current.focus();
}
};
+ const noOptionsMessage = validationMessage === undefined ? undefined // fall back to react-select default ("No options")
+ : typeof validationMessage === 'function' ? validationMessage : () => validationMessage;
const theme = useTheme();
const memoizedStyles = useMemo(() => {
return getMemoizedStyles(theme, zIndex);
}, [theme, zIndex]);
@@ -187,30 +199,35 @@
onFocus
},
dropdownWidth: dropdownWidth,
error: Boolean(error),
+ formatCreateLabel: formatCreateLabel,
formatGroupLabel: formatGroupLabel,
formatOptionLabel: formatOptionLabel,
id: id || rest.htmlFor || rawInputId,
inputId: inputId,
inputProps: {
...inputProps
},
inputWidth: inputWidth,
+ isCreatable: isCreatable,
isDisabled: disabled,
isMulti: multiple,
isOptionDisabled: option => option.disabled,
isSearchable: isSearchable,
+ isValidNewOption: isValidNewOption,
menuAlignment: menuAlignment,
name: name,
+ noOptionsMessage: noOptionsMessage,
options: selectOptions,
placeholder: placeholder,
selectRef: selectInputRef,
shownOptionsLimit: shownOptionsLimit,
size: size,
styles: memoizedStyles,
value: multiple ? multiValues : parsedValue,
onChange: changeHandler,
+ onInputChange: onInputChange,
onKeyDown: multiple ? e => keyPressHandler(e) : undefined,
...rest
})
});