Skip to content
Snippets Groups Projects
Select.tsx 3.75 KiB
import {
  ListboxInput,
  ListboxButton,
  ListboxPopover,
  ListboxList,
  ListboxOption,
} from '@reach/listbox';
import { type CSSProperties, css } from 'glamor';

import ExpandArrow from '../../icons/v0/ExpandArrow';
import { colors } from '../../style';

type CustomSelectProps = {
  options: Array<[string, string]>;
  value: string;
  defaultLabel?: string;
  onChange?: (newValue: string) => void;
  style?: CSSProperties;
  wrapperStyle?: CSSProperties;
  line: number;
  disabledKeys?: string[];
};

/**
 * @param {Array<[string, string]>} options - An array of options value-label pairs.
 * @param {string} value - The currently selected option value.
 * @param {string} [defaultLabel] - The label to display when the selected value is not in the options.
 * @param {function} [onChange] - A callback function invoked when the selected value changes.
 * @param {CSSProperties} [style] - Custom styles to apply to the selected button.
 * @param {CSSProperties} [wrapperStyle] - Custom style to apply to the select wrapper.
 * @param {string[]} [disabledKeys] - An array of option values to disable.
 *
 * @example
 * // Usage:
 * // <Select options={[['1', 'Option 1'], ['2', 'Option 2']]} value="1" onChange={handleOnChange} />
 * // <Select options={[['1', 'Option 1'], ['2', 'Option 2']]} value="3" defaultLabel="Select an option"  onChange={handleOnChange} />
 */

export default function Select({
  options,
  value,
  defaultLabel = '',
  onChange,
  style,
  wrapperStyle,
  line,
  disabledKeys = [],
}: CustomSelectProps) {
  const arrowSize = 7;
  const targetOption = options.filter(option => option[0] === value);
  return (
    <ListboxInput
      value={value}
      onChange={onChange}
      style={{ lineHeight: '1em', ...wrapperStyle }}
    >
      <ListboxButton
        {...css([
          { borderWidth: 0, padding: '2px 5px', borderRadius: 4 },
          style,
        ])}
        arrow={<ExpandArrow style={{ width: arrowSize, height: arrowSize }} />}
      >
        <span
          style={{
            display: 'flex',
            overflowX: 'hidden',
            textOverflow: 'ellipsis',
            whiteSpace: 'nowrap',
            maxWidth: `calc(100% - ${arrowSize + 5}px)`,
            minHeight: '18px',
            alignItems: 'center',
          }}
        >
          {targetOption.length !== 0 ? targetOption[0][1] : defaultLabel}
        </span>
      </ListboxButton>
      <ListboxPopover style={{ zIndex: 10000, outline: 0, borderRadius: 4 }}>
        {!line ? (
          <ListboxList style={{ maxHeight: 250, overflowY: 'auto' }}>
            {options.map(([value, label]) => (
              <ListboxOption
                key={value}
                value={value}
                disabled={disabledKeys.includes(value)}
              >
                {label}
              </ListboxOption>
            ))}
          </ListboxList>
        ) : (
          <ListboxList style={{ maxHeight: 250, overflowY: 'auto' }}>
            {options.slice(0, line).map(([value, label]) => (
              <ListboxOption
                key={value}
                value={value}
                disabled={disabledKeys.includes(value)}
              >
                {label}
              </ListboxOption>
            ))}
            <div
              style={{
                padding: '2px',
                marginTop: 5,
                borderTop: '1px solid ' + colors.n10,
              }}
            />
            {options.slice(line, options.length).map(([value, label]) => (
              <ListboxOption
                key={value}
                value={value}
                disabled={disabledKeys.includes(value)}
              >
                {label}
              </ListboxOption>
            ))}
          </ListboxList>
        )}
      </ListboxPopover>
    </ListboxInput>
  );
}