textmachine/frontend/src/ui/Select.tsx

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>
);
}