123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- import { h, Fragment } from 'preact';
- import ArrowDropdown from '../icons/ArrowDropdown';
- import ArrowDropup from '../icons/ArrowDropup';
- import Menu, { MenuItem } from './Menu';
- import TextField from './TextField';
- import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
- export default function Select({ label, onChange, options: inputOptions = [], selected: propSelected }) {
- const options = useMemo(
- () =>
- typeof inputOptions[0] === 'string' ? inputOptions.map((opt) => ({ value: opt, label: opt })) : inputOptions,
- [inputOptions]
- );
- const [showMenu, setShowMenu] = useState(false);
- const [selected, setSelected] = useState(
- Math.max(
- options.findIndex(({ value }) => value === propSelected),
- 0
- )
- );
- const [focused, setFocused] = useState(null);
- const ref = useRef(null);
- const handleSelect = useCallback(
- (value, label) => {
- setSelected(options.findIndex((opt) => opt.value === value));
- onChange && onChange(value, label);
- setShowMenu(false);
- },
- [onChange, options]
- );
- const handleClick = useCallback(() => {
- setShowMenu(true);
- }, [setShowMenu]);
- const handleKeydown = useCallback(
- (event) => {
- switch (event.key) {
- case 'Enter': {
- if (!showMenu) {
- setShowMenu(true);
- setFocused(selected);
- } else {
- setSelected(focused);
- onChange && onChange(options[focused].value, options[focused].label);
- setShowMenu(false);
- }
- break;
- }
- case 'ArrowDown': {
- const newIndex = focused + 1;
- newIndex < options.length && setFocused(newIndex);
- break;
- }
- case 'ArrowUp': {
- const newIndex = focused - 1;
- newIndex > -1 && setFocused(newIndex);
- break;
- }
- // no default
- }
- },
- [onChange, options, showMenu, setShowMenu, setFocused, focused, selected]
- );
- const handleDismiss = useCallback(() => {
- setShowMenu(false);
- }, [setShowMenu]);
- // Reset the state if the prop value changes
- useEffect(() => {
- const selectedIndex = Math.max(
- options.findIndex(({ value }) => value === propSelected),
- 0
- );
- if (propSelected && selectedIndex !== selected) {
- setSelected(selectedIndex);
- setFocused(selectedIndex);
- }
- // DO NOT include `selected`
- }, [options, propSelected]); // eslint-disable-line react-hooks/exhaustive-deps
- return (
- <Fragment>
- <TextField
- inputRef={ref}
- label={label}
- onchange={onChange}
- onclick={handleClick}
- onkeydown={handleKeydown}
- readonly
- trailingIcon={showMenu ? ArrowDropup : ArrowDropdown}
- value={options[selected]?.label}
- />
- {showMenu ? (
- <Menu className="rounded-t-none" onDismiss={handleDismiss} relativeTo={ref} widthRelative>
- {options.map(({ value, label }, i) => (
- <MenuItem key={value} label={label} focus={focused === i} onSelect={handleSelect} value={value} />
- ))}
- </Menu>
- ) : null}
- </Fragment>
- );
- }
|