mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
91fbe4129f
- new reusable <Combobox> (Base UI Autocomplete): arrow-key + Enter navigation - New Appointment dialog: patient search and provider are now searchable comboboxes (provider sourced from /api/staff/providers) instead of a hand- rolled dropdown and a free-text input - appointment rows are clickable and open an <AppointmentDetailSheet> to edit date/time/type/provider/status or delete; "Added by AI" badge shown on rows and in the sheet for source="ai" records Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import type { ReactNode } from "react";
|
|
|
|
import {
|
|
Autocomplete,
|
|
AutocompleteEmpty,
|
|
AutocompleteInput,
|
|
AutocompleteItem,
|
|
AutocompleteList,
|
|
AutocompletePopup,
|
|
} from "@/components/ui/autocomplete";
|
|
|
|
export type ComboboxOption = {
|
|
// Stable value handed back to onSelect.
|
|
value: string;
|
|
// Text used both for type-to-filter matching and the committed input text.
|
|
// Fold any extra searchable terms (e.g. a file number) in here.
|
|
label: string;
|
|
// Optional rich row content for the dropdown; falls back to `label`.
|
|
node?: ReactNode;
|
|
};
|
|
|
|
// A searchable single-select built on Base UI's Autocomplete, so arrow-key
|
|
// navigation and Enter-to-select work out of the box (same primitive as the ⌘K
|
|
// command palette). Filtering matches the option `label`. `onSelect` fires for
|
|
// both mouse clicks and keyboard Enter.
|
|
export function Combobox({
|
|
options,
|
|
onSelect,
|
|
placeholder,
|
|
emptyText,
|
|
autoFocus,
|
|
value,
|
|
defaultValue,
|
|
onValueChange,
|
|
inputClassName,
|
|
}: {
|
|
options: ComboboxOption[];
|
|
onSelect: (value: string) => void;
|
|
placeholder?: string;
|
|
emptyText?: string;
|
|
autoFocus?: boolean;
|
|
// Optionally control (value) or seed (defaultValue) the input text.
|
|
value?: string;
|
|
defaultValue?: string;
|
|
onValueChange?: (value: string) => void;
|
|
inputClassName?: string;
|
|
}) {
|
|
return (
|
|
<Autocomplete
|
|
defaultValue={defaultValue}
|
|
items={options}
|
|
onValueChange={onValueChange}
|
|
value={value}
|
|
>
|
|
<AutocompleteInput
|
|
autoFocus={autoFocus}
|
|
className={inputClassName}
|
|
placeholder={placeholder}
|
|
showTrigger
|
|
/>
|
|
<AutocompletePopup>
|
|
{emptyText ? <AutocompleteEmpty>{emptyText}</AutocompleteEmpty> : null}
|
|
<AutocompleteList>
|
|
{(opt: ComboboxOption) => (
|
|
<AutocompleteItem
|
|
key={opt.value}
|
|
onClick={() => onSelect(opt.value)}
|
|
value={opt.label}
|
|
>
|
|
{opt.node ?? opt.label}
|
|
</AutocompleteItem>
|
|
)}
|
|
</AutocompleteList>
|
|
</AutocompletePopup>
|
|
</Autocomplete>
|
|
);
|
|
}
|