Select
@clawscale/react/select holds the components that pick items from a list. They share one item model, so a prop works the same way in each.
Usage#
import { MultiSelect, Omnibar, QueryList, Select, Suggest } from "@clawscale/react/select";The styles ship in @clawscale/react/styles.css. There is no extra stylesheet to load.
Components#
| Component | Use it to |
|---|---|
| Select | Pick one item from a button. |
| Suggest | Pick one item by typing in a text input. |
| Multi select | Pick several items, shown as tags. |
| Omnibar | Search or run commands from an overlay. |
| Query list | Build your own picker on the same logic. |
Item model#
Every component is generic. T is the type of one item, and these props describe the list:
| Prop | Role |
|---|---|
items | The array of T to pick from. |
itemRenderer | Renders one item, usually as a MenuItem. |
itemPredicate | Decides whether one item matches the query. |
itemListPredicate | Filters the whole array at once. It can also rank. It wins over itemPredicate. |
onItemSelect | Receives the chosen item. |
Here one set of props drives a Select and a Suggest. Both write to the same state, so they stay in sync.
import { Button, MenuItem } from "@clawscale/react";
import { type ItemPredicate, type ItemRenderer, Select, Suggest } from "@clawscale/react/select";
import { useState } from "react";
import { DATASETS, type Dataset } from "../_data";
const filterDataset: ItemPredicate<Dataset> = (query, dataset) => dataset.name.includes(query.trim().toLowerCase());
const renderDataset: ItemRenderer<Dataset> = (dataset, { handleClick, handleFocus, id, modifiers, ref }) => {
if (!modifiers.matchesPredicate) return null;
return (
<MenuItem
key={dataset.id}
ref={ref}
id={id}
active={modifiers.active}
label={dataset.owner}
onClick={handleClick}
onFocus={handleFocus}
roleStructure="listoption"
text={dataset.name}
/>
);
};
export default function SelectItemModel() {
const [dataset, setDataset] = useState<Dataset | null>(null);
const list = { items: DATASETS, itemPredicate: filterDataset, itemRenderer: renderDataset, onItemSelect: setDataset };
return (
<>
<Select<Dataset> {...list}>
<Button endIcon="caret-down" icon="database" text={dataset?.name ?? "Select a dataset"} />
</Select>
<Suggest<Dataset> {...list} inputValueRenderer={(item) => item.name} selectedItem={dataset} />
</>
);
}Item renderer#
itemRenderer receives the item and an object with handleClick, handleFocus, modifiers, ref and id.
- Return a
MenuItemwithroleStructure="listoption". The list is a listbox, so each item is an option. - Set
active={modifiers.active}to highlight the item that the arrow keys point at. - Forward
ref, so the list scrolls to the active item. - Pass
id, so the filter input can point screen readers to the active item. - Return
nullwhenmodifiers.matchesPredicateis false. A custom list renderer may call the renderer for every item.
Filtering#
itemPredicate runs once per item and suits most lists. Use itemListPredicate for fuzzy search or ranking across the whole list. With neither, every item shows. Set noResults for an empty result and initialContent for an empty query.
Controlled state#
Control the query with query and onQueryChange, and the highlighted item with activeItem and onActiveItemChange.
Props interface#
Every component in the package accepts these props.
interface ListItemsProps extends Props@clawscale/react/selectReusable generic props for a component that operates on a filterable, selectable list of items.
| Props | Description |
|---|---|
activeItem | T | CreateNewItem | nullThe currently focused item for keyboard interactions, or null to indicate that no item is active. If omitted or undefined, this prop will be uncontrolled (managed by the component's state). Use onActiveItemChange to listen for updates. |
className | stringA space-delimited list of class names to pass along to a child element. Inherited from Props |
createNewItemFromQuery | (query: string) => T | T[]If provided, allows new items to be created using the current query string. This is invoked when user interaction causes one or many items to be created, either by pressing the Enter key or by clicking on the "Create Item" option. It transforms a query string into one or many items type. |
createNewItemPosition | "first" | "last" = 'last'Determines the position of the createNewItem within the list: first or last. Only relevant when createNewItemRenderer is defined. |
createNewItemRenderer | (query: string, active: boolean, handleClick: React.MouseEventHandler<HTMLElement>) => React.JSX.Element | undefinedCustom renderer to transform the current query string into a selectable "Create Item" option. If this function is provided, a "Create Item" option will be rendered at the end of the list of items. If this function is not provided, a "Create Item" option will not be displayed. |
initialContent | React.ReactNode | nullReact content to render when query is empty. If omitted, all items will be rendered (or result of itemListPredicate with empty query). If explicit null, nothing will be rendered when query is empty. |
itemDisabled | keyof T | ((item: T, index: number) => boolean)Determine if the given item is disabled. Provide a callback function, or simply provide the name of a boolean property on the item that exposes its disabled state. |
itemListPredicate | ItemListPredicate<T>Customize querying of entire items array. Return new list of items. This method can reorder, add, or remove items at will. (Supports filter algorithms that operate on the entire set, rather than individual items.) |
itemListRenderer | ItemListRenderer<T>Custom renderer for the contents of the dropdown. |
itemPredicate | ItemPredicate<T>Customize querying of individual items. |
itemRendererrequired | ItemRenderer<T>Custom renderer for an item in the dropdown list. Receives a boolean indicating whether this item is active (selected by keyboard arrows) and an onClick event handler that should be attached to the returned element. |
itemsrequired | T[]Array of items in the list. |
itemsEqual | ItemsEqualProp<T>Specifies how to test if two items are equal. By default, simple strict equality ( ===) is used to compare two items. |
noResults | React.ReactNodeReact content to render when filtering items returns zero results. If omitted, nothing will be rendered in this case. |
onActiveItemChange | (activeItem: T | null, isCreateNewItem: boolean) => voidInvoked when user interaction should change the active item: arrow keys move it up/down in the list, selecting an item makes it active, and changing the query may reset it to the first item in the list if it no longer matches the filter. |
onItemSelectrequired | (item: T, event?: React.SyntheticEvent<HTMLElement>) => voidCallback invoked when an item from the list is selected, typically by clicking or pressing enter key. |
onItemsPaste | (items: T[]) => voidCallback invoked when multiple items are selected at once via pasting. |
onQueryChange | (query: string, event?: React.ChangeEvent<HTMLInputElement>) => voidCallback invoked when the query string changes. |
query | stringQuery string passed to itemListPredicate or itemPredicate to filter items. This value is controlled: its state must be managed externally by attaching an onChange handler to the relevant element in your renderer implementation. |
resetOnQuery | boolean = trueWhether the active item should be reset to the first matching item _every time the query changes_ (via prop or by user input). |
resetOnSelect | boolean = falseWhether the active item should be reset to the first matching item _when an item is selected_. The query will also be reset to the empty string. |
scrollToActiveItem | boolean = trueWhen activeItem is controlled, whether the active item should _always_ be scrolled into view when the prop changes. If false, only changes that result from built-in interactions (clicking, querying, or using arrow keys) will scroll the active item into view. Ignored if the activeItem prop is omitted (uncontrolled behavior). |
itemRenderer receives these props for each item.
interface ItemRendererProps@clawscale/react/selectAn object describing how to render a particular item. An itemRenderer receives the item as its first argument, and this object as its second argument.
| Props | Description |
|---|---|
handleClickrequired | React.MouseEventHandler<HTMLElement>Click event handler to select this item. |
handleFocus | () => voidFocus event handler to set this as the "active" item. |
id | stringUnique identifier for this item, used for accessibility. |
indexrequired | numberIndex of the item in the QueryList items array. |
modifiersrequired | ItemModifiersModifiers that describe how to render this item, such as active or disabled. |
queryrequired | stringThe current query string used to filter the items. |
ref | React.Ref<T>A ref attached the native HTML element rendered by this item. |