Data table toolbar
The control strip that sits above a table or card grid: a search
field, a filter menu, a grid/list view toggle, and the page’s primary
action. It’s fully controlled — you own query and view state and
this block just renders the controls and calls back. The search box
flexes to fill space while the right-hand cluster stays fixed.
'use client';
import { Button, Flex, Menu, MenuItem, MenuTrigger, Segment, SegmentedControl, TextInput,} from '@helix-ui/core';import { Filter, LayoutGrid2, Plus, Rows, Search } from '@helix-ui/icons';
export type TableView = 'grid' | 'list';
export interface DataTableToolbarProps { query: string; onQueryChange: (value: string) => void; view: TableView; onViewChange: (view: TableView) => void; filters?: { label: string; onSelect: () => void }[]; primaryLabel?: string; onPrimary?: () => void;}
export function DataTableToolbar({ query, onQueryChange, view, onViewChange, filters = [], primaryLabel = 'New', onPrimary,}: DataTableToolbarProps) { return ( <Flex align="center" justify="between" gap={3} wrap> <Flex align="center" gap={2} style={{ flex: '1 1 260px' }}> <div style={{ flex: '1 1 220px', maxWidth: 340 }}> <TextInput aria-label="Search" size="sm" placeholder="Search…" prefix={<Search size={16} />} value={query} onChange={onQueryChange} /> </div> {filters.length > 0 ? ( <MenuTrigger> <Button variant="outline" tone="neutral" size="sm"> <Filter size={16} /> Filter </Button> <Menu> {filters.map((filter) => ( <MenuItem key={filter.label} onAction={filter.onSelect}> {filter.label} </MenuItem> ))} </Menu> </MenuTrigger> ) : null} </Flex>
<Flex align="center" gap={2}> <SegmentedControl size="sm" aria-label="View" selectedKeys={new Set([view])} onSelectionChange={(keys) => onViewChange([...keys][0] as TableView)} > <Segment id="grid" aria-label="Grid view"> <LayoutGrid2 size={16} /> </Segment> <Segment id="list" aria-label="List view"> <Rows size={16} /> </Segment> </SegmentedControl> <Button size="sm" onClick={onPrimary}> <Plus size={16} /> {primaryLabel} </Button> </Flex> </Flex> );}Usage
const [query, setQuery] = useState('');const [view, setView] = useState<TableView>('list');
<DataTableToolbar query={query} onQueryChange={setQuery} view={view} onViewChange={setView} filters={[ { label: 'Active only', onSelect: () => setStatus('active') }, { label: 'Archived', onSelect: () => setStatus('archived') }, ]} primaryLabel="New project" onPrimary={createProject}/>;Notes
onSelectionChangehands back aSet; the block reads the single selected key off it.disallowEmptySelection(on by default) keeps a view always active.- For more than a couple of filter dimensions, promote the
Menuto aSheetor a filter bar — a dropdown gets unwieldy past ~6 items.