Clawscale

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#

ComponentUse it to
SelectPick one item from a button.
SuggestPick one item by typing in a text input.
Multi selectPick several items, shown as tags.
OmnibarSearch or run commands from an overlay.
Query listBuild 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:

PropRole
itemsThe array of T to pick from.
itemRendererRenders one item, usually as a MenuItem.
itemPredicateDecides whether one item matches the query.
itemListPredicateFilters the whole array at once. It can also rank. It wins over itemPredicate.
onItemSelectReceives 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 MenuItem with roleStructure="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 null when modifiers.matchesPredicate is 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/select

Reusable generic props for a component that operates on a filterable, selectable list of items.

PropsDescription
activeItem
T | CreateNewItem | null
The 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
string
A 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 | undefined
Custom 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 | null
React 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.ReactNode
React content to render when filtering items returns zero results. If omitted, nothing will be rendered in this case.
onActiveItemChange
(activeItem: T | null, isCreateNewItem: boolean) => void
Invoked 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>) => void
Callback invoked when an item from the list is selected, typically by clicking or pressing enter key.
onItemsPaste
(items: T[]) => void
Callback invoked when multiple items are selected at once via pasting.
onQueryChange
(query: string, event?: React.ChangeEvent<HTMLInputElement>) => void
Callback invoked when the query string changes.
query
string
Query 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 = true
Whether the active item should be reset to the first matching item _every time the query changes_ (via prop or by user input).
resetOnSelect
boolean = false
Whether 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 = true
When 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/select

An object describing how to render a particular item. An itemRenderer receives the item as its first argument, and this object as its second argument.

PropsDescription
handleClickrequired
React.MouseEventHandler<HTMLElement>
Click event handler to select this item.
handleFocus
() => void
Focus event handler to set this as the "active" item.
id
string
Unique identifier for this item, used for accessibility.
indexrequired
number
Index of the item in the QueryList items array.
modifiersrequired
ItemModifiers
Modifiers that describe how to render this item, such as active or disabled.
queryrequired
string
The current query string used to filter the items.
ref
React.Ref<T>
A ref attached the native HTML element rendered by this item.