Clawscale

Select

Select picks one item from a list. Its child, usually a button, opens a popover with a filter input and the list.

Usage#

import { Select } from "@clawscale/react/select";
<Select<Dataset>
  items={datasets}
  itemPredicate={filterDataset}
  itemRenderer={renderDataset}
  onItemSelect={setDataset}
>
  <Button endIcon="caret-down" text={dataset?.name ?? "Select a dataset"} />
</Select>

The item model explains how items, itemRenderer and itemPredicate work together.

Examples#

Basic#

Select does not store the choice. Keep it in state from onItemSelect and show it in the button text. noResults renders when the query matches nothing.

import { Button, MenuItem } from "@clawscale/react";
import { type ItemRenderer, Select } from "@clawscale/react/select";
import { useState } from "react";
import { DATASETS, type Dataset, filterDataset } from "../_data";

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}
      disabled={modifiers.disabled}
      label={dataset.owner}
      onClick={handleClick}
      onFocus={handleFocus}
      roleStructure="listoption"
      text={dataset.name}
    />
  );
};

export default function SelectBasic() {
  const [dataset, setDataset] = useState<Dataset>();
  return (
    <Select<Dataset>
      items={DATASETS}
      itemPredicate={filterDataset}
      itemRenderer={renderDataset}
      noResults={<MenuItem disabled text="No datasets match." roleStructure="listoption" />}
      onItemSelect={setDataset}
    >
      <Button endIcon="caret-down" icon="database" text={dataset?.name ?? "Select a dataset"} />
    </Select>
  );
}

Without filtering#

filterable={false} drops the filter input. Use it for short, fixed lists. The selected prop of MenuItem ticks the current choice.

import { Button, MenuItem } from "@clawscale/react";
import { type ItemRenderer, Select } from "@clawscale/react/select";
import { useState } from "react";
import { REGIONS } from "../_data";

export default function SelectFilterable() {
  const [region, setRegion] = useState("us-east-1");
  const renderRegion: ItemRenderer<string> = (item, { handleClick, handleFocus, id, modifiers, ref }) => (
    <MenuItem
      key={item}
      ref={ref}
      id={id}
      active={modifiers.active}
      onClick={handleClick}
      onFocus={handleFocus}
      roleStructure="listoption"
      selected={item === region}
      text={item}
    />
  );
  return (
    <Select<string> filterable={false} items={REGIONS} itemRenderer={renderRegion} onItemSelect={setRegion}>
      <Button endIcon="caret-down" icon="globe" text={region} />
    </Select>
  );
}

Create new items#

createNewItemFromQuery turns the query into a new item, and createNewItemRenderer renders the option that creates it. Add the new item to items in onItemSelect. itemsEqual="name" hides the option when the name already exists.

import { Button, MenuItem } from "@clawscale/react";
import { Select } from "@clawscale/react/select";
import { useState } from "react";
import { DATASETS, type Dataset, filterDataset } from "../_data";
import { renderDataset } from "../_renderers";

function createDataset(name: string): Dataset {
  return { id: `new-${name}`, name, owner: "you", region: "us-east-1", rows: 0 };
}

export default function SelectCreate() {
  const [items, setItems] = useState(DATASETS);
  const [dataset, setDataset] = useState<Dataset>();
  const handleSelect = (item: Dataset) => {
    if (!items.includes(item)) setItems([...items, item]);
    setDataset(item);
  };
  return (
    <Select<Dataset>
      items={items}
      itemsEqual="name"
      itemPredicate={filterDataset}
      itemRenderer={renderDataset}
      createNewItemFromQuery={createDataset}
      createNewItemRenderer={(query, active, handleClick) => (
        <MenuItem
          active={active}
          icon="add"
          onClick={handleClick}
          roleStructure="listoption"
          shouldDismissPopover={false}
          text={`Create "${query}"`}
        />
      )}
      onItemSelect={handleSelect}
    >
      <Button endIcon="caret-down" icon="database" text={dataset?.name ?? "Choose a destination"} />
    </Select>
  );
}

Disabled items#

itemDisabled takes a property name or a function. Here archived datasets are dimmed, and the arrow keys skip them. To disable the whole control, set disabled on Select and on its button.

import { Button } from "@clawscale/react";
import { Select } from "@clawscale/react/select";
import { useState } from "react";
import { DATASETS, type Dataset, filterDataset } from "../_data";
import { renderDataset } from "../_renderers";

export default function SelectDisabled() {
  const [dataset, setDataset] = useState<Dataset>();
  return (
    <Select<Dataset>
      items={DATASETS}
      itemDisabled="archived"
      itemPredicate={filterDataset}
      itemRenderer={renderDataset}
      onItemSelect={setDataset}
    >
      <Button endIcon="caret-down" icon="database" text={dataset?.name ?? "Select a dataset"} />
    </Select>
  );
}

Interactive playground#

Props
Popover
View source on GitHub

Props interface#

interface SelectProps extends ListItemsProps<T>, SelectPopoverProps@clawscale/react/select
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.
Inherited from ListItemsProps
children
React.ReactNode
Element which triggers the select popover. In most cases, you should display the name or label of the curently selected item here.
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.
Inherited from ListItemsProps
createNewItemPosition
"first" | "last" = 'last'
Determines the position of the createNewItem within the list: first or last. Only relevant when createNewItemRenderer is defined.
Inherited from ListItemsProps
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.
Inherited from ListItemsProps
disabled
boolean = false
Whether the component is non-interactive. If true, the list's item renderer will not be called. Note that you'll also need to disable the component's children, if appropriate.
fill
boolean
Whether the component should take up the full width of its container. You also have to ensure that the child component has fill set to true or is styled appropriately.
filterable
boolean = true
Whether the dropdown list can be filtered. Disabling this option will remove the InputGroup and ignore inputProps.
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.
Inherited from ListItemsProps
inputProps
Partial<Omit<InputGroupProps, "value" | "onChange">>
Props to pass to the query InputGroup component.
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.
Inherited from ListItemsProps
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.)
Inherited from ListItemsProps
itemListRenderer
ItemListRenderer<T>
Custom renderer for the contents of the dropdown.
Inherited from ListItemsProps
itemPredicate
ItemPredicate<T>
Customize querying of individual items.
Inherited from ListItemsProps
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.
Inherited from ListItemsProps
itemsrequired
T[]
Array of items in the list.
Inherited from ListItemsProps
itemsEqual
ItemsEqualProp<T>
Specifies how to test if two items are equal. By default, simple strict equality (===) is used to compare two items.
Inherited from ListItemsProps
menuProps
React.HTMLAttributes<HTMLUListElement>
HTML attributes to add to the Menu listbox containing the selectable options.
noResults
React.ReactNode
React content to render when filtering items returns zero results. If omitted, nothing will be rendered in this case.
Inherited from ListItemsProps
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.
Inherited from ListItemsProps
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.
Inherited from ListItemsProps
onItemsPaste
(items: T[]) => void
Callback invoked when multiple items are selected at once via pasting.
Inherited from ListItemsProps
onQueryChange
(query: string, event?: React.ChangeEvent<HTMLInputElement>) => void
Callback invoked when the query string changes.
Inherited from ListItemsProps
placeholder
string = "Filter..."
A placeholder string passed to the filter text input. Applicable only when filterable is true.
popoverContentProps
React.HTMLAttributes<HTMLDivElement>
HTML attributes to spread to the popover content container element.
Inherited from SelectPopoverProps
popoverProps
Partial<Omit<PopoverProps, "content" | "defaultIsOpen" | "fill" | "renderTarget">>
Props to spread to the popover.
Inherited from SelectPopoverProps
popoverRef
React.RefObject<PopoverNextRef>
Optional ref to the popover. popoverRef.current?.reposition() re-runs position calculation, useful when the target moves in a way the popover doesn't observe automatically (e.g. a parent layout change that doesn't trigger a resize).
Inherited from SelectPopoverProps
popoverTargetProps
React.HTMLAttributes<HTMLElement>
HTML attributes to add to the popover target element.
Inherited from SelectPopoverProps
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.
Inherited from ListItemsProps
resetOnClose
boolean = false
Whether the active item should be reset to the first matching item _when the popover closes_. The query will also be reset to the empty string.
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).
Inherited from ListItemsProps
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.
Inherited from ListItemsProps
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).
Inherited from ListItemsProps