58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
import { ChevronDown } from 'lucide-react';
|
|
import {
|
|
Button,
|
|
Label,
|
|
ListBox,
|
|
ListBoxItem,
|
|
Popover,
|
|
Select as AriaSelect,
|
|
SelectValue,
|
|
} from 'react-aria-components';
|
|
|
|
import field from './Field.module.css';
|
|
import { icon } from './icon';
|
|
import styles from './Select.module.css';
|
|
|
|
export interface Option {
|
|
id: string;
|
|
label: string;
|
|
}
|
|
|
|
interface Props {
|
|
label: string;
|
|
options: Option[];
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
}
|
|
|
|
/**
|
|
* A choice out of a short fixed list — the language pair of a book.
|
|
*
|
|
* A native `<select>` would have been the cheaper answer, and it is refused for the reason the
|
|
* stack pins one primitive library: its popup is drawn by the operating system, so it is the one
|
|
* control in the application that could not be dressed in the tokens (STACK_DECISIONS §2).
|
|
* Everything a native one gives for free — the keyboard, type-ahead, the accessible name, closing
|
|
* on Escape — is the library's here, not ours.
|
|
*/
|
|
export function Select({ label, options, value, onChange }: Props) {
|
|
return (
|
|
<AriaSelect
|
|
className={styles.select}
|
|
selectedKey={value}
|
|
onSelectionChange={(key) => {
|
|
onChange(String(key));
|
|
}}
|
|
>
|
|
<Label className={styles.label}>{label}</Label>
|
|
<Button className={`${field.input} ${field.framed} ${styles.trigger}`}>
|
|
<SelectValue className={styles.value} />
|
|
<ChevronDown {...icon} className={styles.chevron} />
|
|
</Button>
|
|
<Popover className={styles.popover}>
|
|
<ListBox className={styles.list} items={options}>
|
|
{(option) => <ListBoxItem className={styles.option}>{option.label}</ListBoxItem>}
|
|
</ListBox>
|
|
</Popover>
|
|
</AriaSelect>
|
|
);
|
|
}
|