Acronis UIKit
Components

FilterSearch

A composable data-table toolbar — search, a tenant switcher, a filter popover, and applied-filter chips.

Usage

import {
  FilterSearch,
  FilterSearchActions,
  FilterSearchFilters,
  FilterSearchAppliedFilters,
  useFilterSearchFilters,
} from '@acronis-platform/ui-react';

FilterSearch is a composable toolbar for data tables: a horizontal row that arranges a search field, an optional tenant/scope switcher, an optional filter popover, and trailing action buttons. It's a pure layout wrapper — you place Search, Select, FilterSearchFilters, and FilterSearchActions as direct children.

FilterSearchFilters is the real filter popover: a trigger button that opens a form (Reset filters / Cancel / Apply) over a draft copy of the applied filters. It snapshots value on open, lets field children mutate the draft, and only commits it back through onValueChange (plus the optional onApply) when Apply is pressed — disabled until the draft actually differs from the applied value. That comparison is structural (a deep compare, not reference identity), so re-picking the same array or object value leaves Apply disabled. Cancel, Escape, and outside-press revert the draft. Fields that don't fit the popover's height scroll internally.

Any prop it doesn't consume itself is forwarded to the trigger button, so data-testid, id, disabled, and aria-* land on the real <button> (className is merged onto it too). The footer labels (resetFiltersLabel, cancelLabel, applyLabel) and the trigger label are overridable for localization. side, align, sideOffset, contentClassName, portalContainer, collisionBoundary, and positionMethod are forwarded to the underlying PopoverContent for consumers who need to configure the popover directly — portalContainer/positionMethod matter mainly when the toolbar renders inside a constrained MFE/Shadow DOM mount (see the Popover docs).

FilterSearchAppliedFilters renders the currently applied filters as removable chips, plus a top-level Reset filters action that clears everything immediately (no popover involved). It's a separate component, rendered as a sibling row below FilterSearch, and renders nothing when there are no applied filters. Chip labels default to "<key>: <value>" — array values are joined with ", " and object values are JSON-stringified. Pass getFilterChipLabel(key, value) whenever the raw key or a stringified object isn't presentable (a localized field name, a formatted date range). Each chip's remove-control accessible label defaults to "Remove <key> filter" — override via getRemoveFilterLabel(key) to localize it. Its resetFiltersLabel is independent from FilterSearchFilters's own prop of the same name — set both if you localize.

Filter fields are supplied as plain children inside FilterSearchFilters. Each field wires itself to the draft with the useFilterSearchFilters() context hook, which returns the current draft filters map and a per-key setFilter setter:

function StatusField() {
  const { filters, setFilter } = useFilterSearchFilters();
  return (
    <InputSelect
      value={(filters.status as string) ?? 'all'}
      onValueChange={(value) =>
        setFilter('status', value === 'all' ? undefined : value)
      }
    >
      {/* … */}
    </InputSelect>
  );
}

Calling setFilter(key, undefined) deletes that key from the draft instead of storing a literal undefined. Route a field's "all"/"any" option through it, as above — an unset filter is the absence of a key, so it stays out of the applied set and out of the chip row rather than showing a status: undefined chip that can't be cleared.

Group fields with a Separator as a layout convention if desired. Calling useFilterSearchFilters() outside a <FilterSearchFilters> popover throws.

Need a filter popover outside a FilterSearch toolbar? See the Filter popover pattern for the hand-rolled Button + Popover + InputSelect composition.

Design-pending. FilterSearch/FilterSearchActions have a real Figma node (see the API reference below), and FilterSearchAppliedFilters's chip row now does too — it landed as its own FilterChips component, which FilterSearchAppliedFilters renders through. FilterSearchFilters still has no node: it themes entirely from shared semantic tokens plus its composed components' own tiers (Button, Popover), and will be reconciled against the real design once a mockup lands.

Examples

A full toolbar — tenant switcher, search, filter popover, and applied-filter chips:

function DeviceFilterFields() {
  return (
    <>
      <TypeField />
      <PricingModeField />
      <Separator />
      <StatusField />
    </>
  );
}

function Toolbar() {
  const [filters, setFilters] = useState<Record<string, unknown>>({});

  return (
    <div className="flex flex-col gap-3">
      <FilterSearch>
        <Select defaultValue="acme">
          <SelectTrigger className="w-56">
            <SelectValue placeholder="All customers" />
          </SelectTrigger>
          <SelectContent>{/* … */}</SelectContent>
        </Select>
        <InputSearch
          placeholder="Enter text to filter"
          aria-label="Search"
          className="w-56"
        />
        <FilterSearchFilters
          value={filters}
          onValueChange={setFilters}
          label="Table filters"
        >
          <DeviceFilterFields />
        </FilterSearchFilters>
      </FilterSearch>
      <FilterSearchAppliedFilters
        filters={filters}
        onValueChange={setFilters}
      />
    </div>
  );
}

Search-only, no tenant switcher or filters:

<FilterSearch>
  <InputSearch
    placeholder="Search table"
    aria-label="Search"
    className="w-56"
  />
</FilterSearch>

With trailing action buttons:

<FilterSearch>
  <InputSearch
    placeholder="Search table"
    aria-label="Search"
    className="w-56"
  />
  <FilterSearchFilters value={filters} onValueChange={setFilters} />
  <FilterSearchActions>
    <Button>Export</Button>
  </FilterSearchActions>
</FilterSearch>

API Reference

Prop

Type

Prop

Type

Prop

Type

Prop

Type

The useFilterSearchFilters() hook returns the current draft filters map and a setFilter(key, value) setter for wiring filter-field children to the popover draft — passing undefined as the value drops the key from the draft.

Edit on GitHub

On this page