# Filter Params

A URL-backed filter bar with searchable filter selection.

---

## Default

```tsx
'use client';

import {
  decodeDateRangeValue,
  encodeDateRangeValue,
  FilterParamsBar,
  type DateRangePreset,
  type DateRangeValue,
  type FilterParam,
} from '@vercel/geistcn/components/filter-params';
import type { JSX } from 'react';

const createdPresets: DateRangePreset[] = [
  {
    label: 'Last 7 Days',
    range: () => {
      const to = new Date();
      const from = new Date(to);
      from.setDate(to.getDate() - 7);
      return { from, to };
    },
  },
];

const filters: FilterParam<any>[] = [
  {
    key: 'Environment',
    type: 'select',
    label: (value) => value ?? 'All Environments',
    encode: (value) => ({ environment: value }),
    decode: (params) =>
      typeof params.environment === 'string' ? params.environment : undefined,
    options: ['Production', 'Preview', 'Development'],
  },
  {
    key: 'Branch',
    type: 'select',
    label: (value) => value ?? 'All Branches',
    encode: (value) => ({ branch: value }),
    decode: (params) =>
      typeof params.branch === 'string' ? params.branch : undefined,
    options: ['main', 'canary', 'feature/filter-bar'],
  },
  {
    key: 'Created',
    type: 'date-range',
    label: (value: DateRangeValue | undefined) =>
      value?.kind === 'preset' ? value.preset.label : 'Any Time',
    encode: (value: DateRangeValue | undefined) => ({
      created: encodeDateRangeValue(value),
    }),
    decode: (params) => decodeDateRangeValue(params.created, createdPresets),
    presets: createdPresets,
  },
];

export function FilterParamsDefault(): JSX.Element {
  return <FilterParamsBar params={filters} />;
}
```

## Suggestions

Provide a `suggestion` for filters that have a useful starting value. Suggestions
remain optional until users select them.

```tsx
'use client';

import {
  FilterParamsBar,
  type FilterParam,
} from '@vercel/geistcn/components/filter-params';
import type { JSX } from 'react';

const filters: FilterParam<any>[] = [
  {
    key: 'Environment',
    type: 'select',
    label: (value: string | undefined) => value ?? 'All Environments',
    encode: (value: string | undefined) => ({
      suggestedEnvironment: value,
    }),
    decode: (params) =>
      typeof params.suggestedEnvironment === 'string'
        ? params.suggestedEnvironment
        : undefined,
    options: ['Production', 'Preview', 'Development'],
    suggestion: () => 'Production',
  },
  {
    key: 'Status',
    type: 'multi-select',
    label: (value: string[] | undefined) =>
      value?.length ? `${value.length} Statuses` : 'All Statuses',
    optionLabel: (value) => value,
    encode: (value: string[] | undefined) => ({
      suggestedStatus: value?.join(','),
    }),
    decode: (params) => {
      const value = params.suggestedStatus;
      return typeof value === 'string' ? value.split(',') : undefined;
    },
    options: ['Ready', 'Building', 'Error'],
    suggestion: () => ['Error'],
  },
];

export function FilterParamsSuggestions(): JSX.Element {
  return <FilterParamsBar params={filters} />;
}
```

## AI

Pass an `ai` callback to offer natural-language filter suggestions. This example
simulates a request and always applies the Production environment filter.

```tsx
'use client';

import {
  FilterParamsBar,
  useSearchParamState,
  type FilterParam,
} from '@vercel/geistcn/components/filter-params';
import { useCallback, useRef, useState, type JSX } from 'react';

const filters: FilterParam<string>[] = [
  {
    key: 'Environment',
    type: 'select',
    label: (value) => value ?? 'All Environments',
    encode: (value) => ({ aiEnvironment: value }),
    decode: (params) =>
      typeof params.aiEnvironment === 'string'
        ? params.aiEnvironment
        : undefined,
    options: ['Production', 'Preview', 'Development'],
  },
];

export function FilterParamsAi(): JSX.Element {
  const [searchParams, setSearchParams] = useSearchParamState();
  const [isThinking, setIsThinking] = useState(false);
  const requestId = useRef(0);

  const onAbort = useCallback(() => {
    requestId.current += 1;
    setIsThinking(false);
  }, []);

  const onQuery = useCallback(async (): Promise<number | void> => {
    const id = ++requestId.current;
    setIsThinking(true);
    await new Promise((resolve) => setTimeout(resolve, 3000));

    if (id !== requestId.current) return;

    const nextSearchParams = new URLSearchParams(searchParams);
    nextSearchParams.set('aiEnvironment', 'Production');
    setSearchParams(nextSearchParams);
    setIsThinking(false);
    return 1;
  }, [searchParams, setSearchParams]);

  return (
    <FilterParamsBar ai={{ isThinking, onAbort, onQuery }} params={filters} />
  );
}
```

## Best Practices

### When to use

* Use `FilterParamsBar` for list and search pages where the active constraints
  should be visible, linkable, and preserved on refresh.
* Keep filter names short and use option labels that explain the selected value
  without requiring users to reopen the menu.

### Behavior

* Encode only the URL parameters owned by each filter so removing a pill does
  not clear unrelated page state.
* Provide suggestions only when they are useful defaults, not as replacements
  for filters users have explicitly selected.

### Accessibility

* Make filter labels and option labels specific enough to identify the
  constraint and selected value when announced by a screen reader.
