# Filter

> Source: https://design.nanatec.co.ke/components/filter/
> Collects a set of filter fields into one value, renders the active ones as removable chips, and publishes the result — the toolbar that sits above a data table.
> Status: stable
> Since: v0.0.20

Collects a set of filter fields into one value, renders the active ones as
removable chips, and publishes the result — the toolbar that usually sits above
a data table.

It composes rather than replaces. Each filter is a
[`nana-filter-item`](/components/filter-item/) wrapping whichever field suits
it — a search input, a status select, a date range, a column picker — so every
filter behaves the same from your side no matter which control drives it. The
aggregated value is a plain object keyed by each item's `name`, ready to hand
to a query string or a `fetch`.

## Preview

```html
<nana-filter label="Filters">
  <nana-filter-item name="q" label="Search" grow live debounce="250">
    <nana-input placeholder="Order ref or customer" clearable></nana-input>
  </nana-filter-item>
  <nana-filter-item name="status" label="Status" operator="is" default-value="all">
    <nana-select value="all">
      <nana-option value="all">All</nana-option>
      <nana-option value="open">Open</nana-option>
      <nana-option value="pending">Pending</nana-option>
      <nana-option value="closed">Closed</nana-option>
    </nana-select>
  </nana-filter-item>
  <nana-filter-item name="created" label="Created" operator="between">
    <nana-date-range clearable></nana-date-range>
  </nana-filter-item>
</nana-filter>
```

## Installation

```bash
npm install @nana-tec/ui-components
```

## Import

```js
import "@nana-tec/ui-components/filter";
```

That one import registers `nana-filter-item`, `nana-tag` and `nana-popup` too,
since a filter surface is not usable without them. The fields you slot in are
imported separately.

## Reading the value

`nana-filter-change` carries the aggregated value — active filters only, keyed
by each item's `name`.

```js
const filter = document.querySelector("nana-filter");

filter.addEventListener("nana-filter-change", (e) => {
  const { value } = e.detail;      // { q: "acme", status: "open" }
  loadOrders(new URLSearchParams(value));
});
```

The same object is readable at any time via the `value` property, and assigning
to it writes each key back through to its field:

```js
// Restore from the URL on load — this does not emit an event back at you.
filter.value = Object.fromEntries(new URLSearchParams(location.search));
```

## The four common filters

### Search

Give the search field `grow` so it absorbs the leftover width in a bar layout,
and `live` with a `debounce` so it filters as the user types without a request
per keystroke.

```html
<nana-filter label="Filters">
  <nana-filter-item name="q" label="Search" grow live debounce="250">
    <nana-input placeholder="Search orders" clearable></nana-input>
  </nana-filter-item>
</nana-filter>
```

### Status

Set `default-value` to whatever means "no filter". Without it a select sitting
on **All** counts as an active filter and shows a chip forever.

```html
<nana-filter label="Filters">
  <nana-filter-item name="status" label="Status" operator="is" default-value="all">
    <nana-select value="all">
      <nana-option value="all">All</nana-option>
      <nana-option value="open">Open</nana-option>
      <nana-option value="pending">Pending</nana-option>
      <nana-option value="closed">Closed</nana-option>
    </nana-select>
  </nana-filter-item>
</nana-filter>
```

A segmented control reads faster when there are five or fewer statuses and they
are the primary cut through the data:

```html
<nana-filter label="Filters">
  <nana-filter-item name="status" label="Status" default-value="all" no-label>
    <nana-radio-group value="all" orientation="horizontal">
      <nana-radio-button value="all">All</nana-radio-button>
      <nana-radio-button value="open">Open</nana-radio-button>
      <nana-radio-button value="pending">Pending</nana-radio-button>
      <nana-radio-button value="closed">Closed</nana-radio-button>
    </nana-radio-group>
  </nana-filter-item>
</nana-filter>
```

For several statuses at once, slot a checkbox group. The value becomes an array
and the chip collapses the whole selection into one removable token.

```html
<nana-filter label="Filters">
  <nana-filter-item name="status" label="Status" operator="is any of">
    <nana-checkbox-group orientation="horizontal">
      <nana-checkbox value="open">Open</nana-checkbox>
      <nana-checkbox value="pending">Pending</nana-checkbox>
      <nana-checkbox value="closed">Closed</nana-checkbox>
    </nana-checkbox-group>
  </nana-filter-item>
</nana-filter>
```

### Dates

A date range serialises as `start/end`. The chip splits it back into two
readable ends rather than showing the raw value.

```html
<nana-filter label="Filters">
  <nana-filter-item name="created" label="Created" operator="between">
    <nana-date-range clearable presets></nana-date-range>
  </nana-filter-item>
</nana-filter>
```

### Columns

A column picker belongs in the query but is not a filter — it does not narrow
the result set. Mark it `no-chip` and it stays out of the chip row, out of the
count, and out of **Clear all**, while still contributing its value.

```html
<nana-filter label="Filters" variant="panel">
  <nana-filter-item name="status" label="Status" default-value="all">
    <nana-select value="all">
      <nana-option value="all">All</nana-option>
      <nana-option value="open">Open</nana-option>
    </nana-select>
  </nana-filter-item>
  <nana-filter-item name="columns" label="Visible columns" no-chip>
    <nana-checkbox-group orientation="horizontal">
      <nana-checkbox value="ref" checked>Ref</nana-checkbox>
      <nana-checkbox value="customer" checked>Customer</nana-checkbox>
      <nana-checkbox value="status" checked>Status</nana-checkbox>
      <nana-checkbox value="total">Total</nana-checkbox>
    </nana-checkbox-group>
  </nana-filter-item>
</nana-filter>
```

## Variants

`bar` is a bare wrapping row for sitting directly above a table. `panel` adds
card chrome and lays the items out in a responsive grid. `popover` puts the
whole panel behind a trigger button carrying the active count.

```html
<nana-filter variant="panel" label="Filter orders">
  <nana-filter-item name="q" label="Search" grow>
    <nana-input placeholder="Search orders" clearable></nana-input>
  </nana-filter-item>
  <nana-filter-item name="status" label="Status" default-value="all">
    <nana-select value="all">
      <nana-option value="all">All</nana-option>
      <nana-option value="open">Open</nana-option>
    </nana-select>
  </nana-filter-item>
</nana-filter>
```

```html
<div style="min-height: 22rem">
  <nana-filter variant="popover" label="Filters">
    <nana-filter-item name="status" label="Status" default-value="all">
      <nana-select value="all">
        <nana-option value="all">All</nana-option>
        <nana-option value="open">Open</nana-option>
        <nana-option value="closed">Closed</nana-option>
      </nana-select>
    </nana-filter-item>
    <nana-filter-item name="created" label="Created" operator="between">
      <nana-date-range clearable></nana-date-range>
    </nana-filter-item>
  </nana-filter>
</div>
```

## Live and manual modes

`live` (the default) publishes on every field change. `mode="manual"` buffers
edits and adds an Apply / Reset footer — the right default when each change
costs a round trip. `value` always reflects what is in the fields; in manual
mode those changes simply are not published until Apply.

```html
<nana-filter variant="panel" mode="manual" label="Filter orders">
  <nana-filter-item name="q" label="Search" grow>
    <nana-input placeholder="Search orders" clearable></nana-input>
  </nana-filter-item>
  <nana-filter-item name="status" label="Status" default-value="all">
    <nana-select value="all">
      <nana-option value="all">All</nana-option>
      <nana-option value="open">Open</nana-option>
    </nana-select>
  </nana-filter-item>
</nana-filter>
```

## Sizes

```html
<nana-filter size="sm" variant="panel" label="Small">
  <nana-filter-item name="status" label="Status" default-value="all">
    <nana-select value="all" size="sm">
      <nana-option value="all">All</nana-option>
      <nana-option value="open">Open</nana-option>
    </nana-select>
  </nana-filter-item>
</nana-filter>
```

## API

## Accessibility

- The popover trigger carries `aria-haspopup="dialog"` and a live
  `aria-expanded`; the panel is a labelled `dialog`.
- Each `nana-filter-item` forwards its `label` to the slotted field as an
  `aria-label`, but only when that field has no accessible name of its own — so
  a field with its own `label` keeps it.
- The active count is decorative (`aria-hidden`); the chip row carries the same
  information as text.
- Chips are `nana-tag` elements with real remove buttons, so every filter can be
  dropped from the keyboard.
