Skip to content
v0.2.10

Filter bar

Compose search and filters with a host-owned data table.

Compose existing Field, SearchInput, Select and Button. Filtering belongs to the host collection, not to DataTable. This recipe filters the full local data set before handing it to the table and resets page and selection when filters change.

TSX
"use client";
import {useState} from "react";
import {Button, DataTable, Field, Inline, SearchInput, Select, Stack} from "@flood-md/react";

const projects = [
  {id: "design", name: "Design system", status: "Active", members: 12},
  {id: "website", name: "Website", status: "Active", members: 4},
  {id: "research", name: "Research", status: "Draft", members: 2},
];

export function ProjectTable() {
  const [query, setQuery] = useState("");
  const [status, setStatus] = useState("all");
  const [page, setPage] = useState(1);
  const [selected, setSelected] = useState<readonly string[]>([]);
  function resetResults() { setPage(1); setSelected([]); }
  const visible = projects.filter(project =>
    project.name.toLowerCase().includes(query.trim().toLowerCase()) &&
    (status === "all" || project.status === status));

  return <Stack gap="4">
    <Inline gap="3" align="end">
      <Field label="Search projects">{props => <SearchInput {...props}
        value={query} onChange={event => {setQuery(event.currentTarget.value); resetResults();}} />}</Field>
      <Field label="Status">{props => <Select {...props} value={status}
        onChange={event => {setStatus(event.currentTarget.value); resetResults();}}>
        <option value="all">All statuses</option>
        <option value="Active">Active</option>
        <option value="Draft">Draft</option>
      </Select>}</Field>
      {(query || status !== "all") && <Button variant="subtle"
        onClick={() => {setQuery(""); setStatus("all"); resetResults();}}>Clear filters</Button>}
    </Inline>
    <DataTable caption="Projects" selectable pageSize={5}
      page={page} onPageChange={setPage} selectedIds={selected} onSelectionChange={setSelected}
      columns={[{id:"name",label:"Project",sortable:true},
        {id:"status",label:"Status"}, {id:"members",label:"Members",sortable:true,align:"end"}]}
      rows={visible.map(project => ({id:project.id,label:project.name,cells:{
        name:project.name,status:project.status,members:project.members}}))} />
  </Stack>;
}

Place inside FloodRoot and import the shared stylesheet once. Keep visible labels for filters, including when defaults are active. A clear action resets actual values. Expose active filters as removable Tag components when the filter set gets larger; do not add a duplicate generic FilterBar component just for layout.

For remote data, debounce or submit at the host boundary, cancel obsolete requests and map loading/error states explicitly. Do not pass an individual server page to this client-paginated DataTable or interpret the page aggregate as server-wide selection. Use Table + Pagination for a remote query contract.