mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 17:02:05 +00:00
89 lines
3 KiB
TypeScript
89 lines
3 KiB
TypeScript
|
|
import { Autocomplete, TextField } from "@mui/material";
|
||
|
|
import {
|
||
|
|
suggestAddresses,
|
||
|
|
type AddressParts,
|
||
|
|
type AddressSuggestion,
|
||
|
|
} from "@/lib/address/vendor-address";
|
||
|
|
|
||
|
|
type AddressAutocompleteFieldProps = {
|
||
|
|
value: string;
|
||
|
|
onInputChange: (value: string) => void;
|
||
|
|
/** Fired only when a candidate row is chosen, never while typing. */
|
||
|
|
onSelect: (parts: AddressParts) => void;
|
||
|
|
label?: string;
|
||
|
|
placeholder?: string;
|
||
|
|
required?: boolean;
|
||
|
|
error?: boolean;
|
||
|
|
helperText?: string;
|
||
|
|
disabled?: boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Street Address input with mocked City/State completion.
|
||
|
|
*
|
||
|
|
* `freeSolo` is the whole point: typing without picking a row is a valid way to
|
||
|
|
* finish the field. The candidates are a convenience for filling City and State
|
||
|
|
* in one action, not a constraint on what the address may be.
|
||
|
|
*
|
||
|
|
* Shared rather than local to Vendors because Sites needs the identical control
|
||
|
|
* when it is built (SH-273).
|
||
|
|
*/
|
||
|
|
export function AddressAutocompleteField({
|
||
|
|
value,
|
||
|
|
onInputChange,
|
||
|
|
onSelect,
|
||
|
|
label = "Street Address",
|
||
|
|
placeholder = "Start typing the street address…",
|
||
|
|
required = false,
|
||
|
|
error = false,
|
||
|
|
helperText,
|
||
|
|
disabled = false,
|
||
|
|
}: AddressAutocompleteFieldProps) {
|
||
|
|
return (
|
||
|
|
<Autocomplete<AddressSuggestion, false, false, true>
|
||
|
|
freeSolo
|
||
|
|
disabled={disabled}
|
||
|
|
options={suggestAddresses(value)}
|
||
|
|
// The options are already derived from the input; letting MUI filter them
|
||
|
|
// again would drop every row whose label does not literally contain the
|
||
|
|
// typed text.
|
||
|
|
filterOptions={(options) => options}
|
||
|
|
inputValue={value}
|
||
|
|
onInputChange={(_event, next, reason) => {
|
||
|
|
// Genuine typing and the clear control update the street. MUI also
|
||
|
|
// fires this callback with reason "reset" after a selection, carrying
|
||
|
|
// the option's full "<street>, <city>, <state>" label — forwarding
|
||
|
|
// that would overwrite the just-picked street with the label.
|
||
|
|
if (reason !== "input" && reason !== "clear") return;
|
||
|
|
onInputChange(next);
|
||
|
|
}}
|
||
|
|
onChange={(_event, selected) => {
|
||
|
|
if (selected && typeof selected !== "string") {
|
||
|
|
onSelect({ street: selected.street, city: selected.city, state: selected.state });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (selected === null) onSelect({ street: "", city: "", state: "" });
|
||
|
|
}}
|
||
|
|
getOptionLabel={(option) => (typeof option === "string" ? option : option.label)}
|
||
|
|
renderInput={(params) => (
|
||
|
|
<TextField
|
||
|
|
{...params}
|
||
|
|
label={label}
|
||
|
|
placeholder={placeholder}
|
||
|
|
error={error}
|
||
|
|
helperText={helperText}
|
||
|
|
fullWidth
|
||
|
|
// Required is set on the input itself, never via MUI's `required`
|
||
|
|
// prop: this codebase spells the requirement in the label text and
|
||
|
|
// asserts that no generated asterisk renders anywhere.
|
||
|
|
slotProps={{
|
||
|
|
...params.slotProps,
|
||
|
|
htmlInput: { ...params.slotProps?.htmlInput, required },
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|