import { useMemo, useState } from "react";
import { Text } from "react-native";
import { MultiSelect, Select, type ComboboxOption } from "@/component-lib/combobox";
import { useKitTheme } from "@/component-lib/shared/native";
import { Section } from "./layout"; // Section and Row: page layout for the demo
const STATES: ComboboxOption[] = [
["KL", "Kerala", "South"],
["TN", "Tamil Nadu", "South"],
["KA", "Karnataka", "South"],
["AP", "Andhra Pradesh", "South"],
["TG", "Telangana", "South"],
["MH", "Maharashtra", "West"],
["GJ", "Gujarat", "West"],
["GA", "Goa", "West"],
["RJ", "Rajasthan", "North"],
["PB", "Punjab", "North"],
["UP", "Uttar Pradesh", "North"],
["DL", "Delhi", "North"],
["WB", "West Bengal", "East"],
["OD", "Odisha", "East"],
["AS", "Assam", "North-east"],
["MN", "Manipur", "North-east"],
].map(([value, label, group]) => ({ value, label, group, keywords: [value] }));
const SKILLS: ComboboxOption[] = [
"React", "React Native", "TypeScript", "Node.js", "GraphQL", "PostgreSQL", "Figma", "Kotlin", "Swift", "Go",
].map((label) => ({ value: label.toLowerCase(), label }));
export function ComboboxDemo() {
const { tokens } = useKitTheme();
const [state, setState] = useState<string | null>("KL");
const [skills, setSkills] = useState<string[]>(["react", "typescript"]);
const [extra, setExtra] = useState<ComboboxOption[]>([]);
const [account, setAccount] = useState<string | null>(null);
const many = useMemo<ComboboxOption[]>(
() =>
Array.from({ length: 500 }, (_, i) => ({
value: `acc-${i + 1}`,
label: `Account ${String(i + 1).padStart(3, "0")}`,
description: i % 7 === 0 ? "Dormant since 2024" : `Ledger ${(i % 12) + 1}`,
disabled: i % 41 === 40,
})),
[],
);
return (
<>
<Section title="Select · grouped, searchable">
<Select
label="State"
options={STATES}
value={state}
onChange={(value) => setState(value)}
description="Search by name or code (e.g. “KL”)."
/>
<Text style={{ color: tokens.muted }}>Value: {String(state)}</Text>
</Section>
<Section title="MultiSelect · tags, max, create">
<MultiSelect
label="Skills"
options={[...SKILLS, ...extra]}
value={skills}
onChange={setSkills}
max={5}
maxVisibleTags={3}
allowCreate
onCreate={(label) => {
const option = { value: label.toLowerCase(), label };
setExtra((list) => [...list, option]);
setSkills((list) => (list.length < 5 ? [...list, option.value] : list));
}}
/>
</Section>
<Section title="Select · 500 options, virtualised">
<Select
label="Ledger account"
options={many}
value={account}
onChange={setAccount}
placeholder="Pick an account"
/>
</Section>
<Section title="Select · error and disabled">
<Select label="Branch" options={STATES} required error="Choose a branch to continue." />
<Select label="Region" options={STATES} defaultValue="TN" disabled />
<MultiSelect label="Tags" options={SKILLS} searchable={false} size="sm" placeholder="No search box" />
</Section>
</>
);
}Install
npx @grampro/kit@native add SelectUsage
import { Select, MultiSelect } from "@/component-lib/combobox";
<Select
label="State"
options={[
{ value: "KL", label: "Kerala" },
{ value: "TN", label: "Tamil Nadu" },
]}
onChange={setState}
/>Props
SelectProps
| Prop | Type | Description |
|---|---|---|
value | V | null | |
defaultValue | V | null | |
onChange | (value: NoInfer<V> | null, option: ComboboxOption<NoInfer<V>> | null) => void | The chosen value and its option; both null once cleared. |
closeOnSelect | boolean | Close the list after choosing. Default true. |
options (required) | readonly ComboboxOption<V>[] | |
mode | "client" | "server" | client filters options locally; server expects options to be the current results. |
loading | boolean | Shows a loading row; keeps current options visible. |
onSearchChange | (search: string) => void | Called with the search text (debounced in server mode), and on open. |
searchDebounce | number | Debounce for onSearchChange in server mode. Default 250 ms. |
searchable | boolean | Show the search box. Default true. |
hasMore | boolean | More results are available; the list calls onLoadMore near its end. |
onLoadMore | () => void | |
filterFn | (option: ComboboxOption<NoInfer<V>>, search: string) => boolean | |
renderOption | (option: ComboboxOption<NoInfer<V>>, state: OptionState) => ReactNode | |
allowCreate | boolean | Offers "Create …" when the search matches no option. |
onCreate | (label: string) => void | |
label | ReactNode | |
placeholder | string | |
description | ReactNode | |
error | ReactNode | |
required | boolean | |
disabled | boolean | |
clearable | boolean | Show the clear button. Default true. |
size | ComboboxSize | Default md. |
maxHeight | number | Max height of the list in the tablet popover, in dp. Default 280. A phone sheet uses the screen. |
presentation | "auto" | "sheet" | "popover" | auto (default): bottom sheet on a phone, popover on a tablet. |
emptyMessage | ReactNode | Shown when nothing matches the search. Defaults to the locale text. |
style | Styleable<ViewStyle> | |
styles | ComboboxStyles | |
localeText | Partial<ComboboxLocaleText> | |
onOpenChange | (open: boolean) => void |
MultiSelectProps
| Prop | Type | Description |
|---|---|---|
value | V[] | |
defaultValue | V[] | |
onChange | (values: NoInfer<V>[], options: ComboboxOption<NoInfer<V>>[]) => void | |
max | number | Maximum number of selections. |
maxVisibleTags | number | Tags shown before collapsing into "+N more". Default 3. |
showSelectAll | boolean | Show "Select all" / "Clear all" under the list. Default true. |
closeOnSelect | boolean | Close the list after each choice. Default false. |
options (required) | readonly ComboboxOption<V>[] | |
mode | "client" | "server" | client filters options locally; server expects options to be the current results. |
loading | boolean | Shows a loading row; keeps current options visible. |
onSearchChange | (search: string) => void | Called with the search text (debounced in server mode), and on open. |
searchDebounce | number | Debounce for onSearchChange in server mode. Default 250 ms. |
searchable | boolean | Show the search box. Default true. |
hasMore | boolean | More results are available; the list calls onLoadMore near its end. |
onLoadMore | () => void | |
filterFn | (option: ComboboxOption<NoInfer<V>>, search: string) => boolean | |
renderOption | (option: ComboboxOption<NoInfer<V>>, state: OptionState) => ReactNode | |
allowCreate | boolean | Offers "Create …" when the search matches no option. |
onCreate | (label: string) => void | |
label | ReactNode | |
placeholder | string | |
description | ReactNode | |
error | ReactNode | |
required | boolean | |
disabled | boolean | |
clearable | boolean | Show the clear button. Default true. |
size | ComboboxSize | Default md. |
maxHeight | number | Max height of the list in the tablet popover, in dp. Default 280. A phone sheet uses the screen. |
presentation | "auto" | "sheet" | "popover" | auto (default): bottom sheet on a phone, popover on a tablet. |
emptyMessage | ReactNode | Shown when nothing matches the search. Defaults to the locale text. |
style | Styleable<ViewStyle> | |
styles | ComboboxStyles | |
localeText | Partial<ComboboxLocaleText> | |
onOpenChange | (open: boolean) => void |
Native and web
presentation:"auto"(sheet below 600dp, popover above),"sheet"or"popover".- Change events are
onChange, as on the web. No keyboard navigation or type-ahead on touch. - Web version: Select & MultiSelect on the web.