# rn-data-table-side-filters URL: /docs/native/docyrus/data-table-side-filters E-commerce-style facet filters for React Native — a "Filters (n)" trigger that opens a large bottom sheet (or an inline panel on tablets), covering every column type and emitting the same RuleGroupType JSON as the Query Builder. API-aligned with the web DataTableSideFilters. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-data-table-side-filters ``` **Dependencies:** - [@react-querybuilder/core](https://www.npmjs.com/package/@react-querybuilder/core) - [date-fns](https://www.npmjs.com/package/date-fns) - [tailwind-variants](https://www.npmjs.com/package/tailwind-variants) `DataTableSideFilters` is built on the same `ColumnConfig` schema as [rn-data-table-filter](/docs/native/docyrus/data-table-filter), so one column definition powers both the filter wizard and the facet panel. `useDataTableSideFilters` wraps `useDataTableFilters` and translates the internal `FiltersState` to and from a `react-querybuilder` `RuleGroupType`, so the emitted query feeds the Query Builder, saved views and your back end unchanged. ## Usage ```tsx import { useState } from 'react'; import { DataTableSideFilters, useDataTableSideFilters, type RuleGroupType } from '@/components/docyrus-native/data-table-side-filters'; const columnsConfig = [ { id: 'category', displayName: 'Category', icon: 'fal bag-shopping', type: 'option' as const, accessor: (row: Product) => row.category, options: categoryOptions }, { id: 'vendorId', displayName: 'Vendor', icon: 'fal user', type: 'multiOption' as const, accessor: (row: Product) => [row.vendorId], asyncOptions: { load: async ({ search, page, pageSize, signal }) => { const res = await fetch(`${API}/vendors?q=${search}&page=${page}&size=${pageSize}`, { signal }); return res.json(); // → { items: ColumnOption[], hasMore: boolean } } } }, { id: 'price', displayName: 'Price', icon: 'fal dollar-sign', type: 'number' as const, accessor: (row: Product) => row.price, min: 0, max: 500 }, { id: 'releasedAt', displayName: 'Released', icon: 'fal calendar', type: 'date' as const, accessor: (row: Product) => row.releasedAt }, { id: 'inStock', displayName: 'In stock', icon: 'fal circle-check', type: 'boolean' as const, accessor: (row: Product) => row.inStock } ] as const; export function ProductFilters({ products }: { products: Product[] }) { const [query, setQuery] = useState({ combinator: 'and', rules: [] }); const { columns, filters, actions, strategy } = useDataTableSideFilters({ strategy: 'client', data: products, columnsConfig, query, onQueryChange: setQuery }); return ( ); } ``` ### Presentation - **`presentation="sheet"`** (default): renders an outline `Filters (n)` button. Pressing it opens a large-detent `ActionSheet` with **Clear all** on the left and **Done** on the right. The search input and the active-filter chips sit in the sheet's fixed header, and the sections scroll below them. Filters apply live, so **Done** only closes the sheet. Replace the button with `renderTrigger`, and control visibility with `open` / `defaultOpen` / `onOpenChange`. - **`presentation="inline"`**: renders the panel in place (tablets, a dedicated filter screen). With `collapsible`, the header gets a collapse button and the collapsed state is a full-width bar (`Filters · n`) that expands on press. ```tsx ``` ## Render mode resolution Each column picks its UI by `column.type`. You can override the mode with `defaults[columnId].mode`: | Column type | Default render mode | Native UI | |-------------|---------------------|-----------| | `text` | `text-input` | Debounced input (300 ms), applies `contains`. | | `number` | `numeric-range` | Min / Max inputs, clamped to the faceted or `column.min` / `column.max` bounds. Shows an error when min > max. | | `date` | `date-range` | Preset chips (Today, Yesterday, Last 7 days, Last 30 days, This week, This month, This year) plus From / To `DateTimePicker`s. | | `boolean` | `boolean` | Any / True / False `ToggleGroup`. "Any" removes the filter. | | `option` / `multiOption` | `inline-checkbox` | Checkbox rows with faceted counts. Past `showMoreThreshold` (default `8`) the list collapses behind **Show more**, with selected options pinned above the fold. | | `option` / `multiOption` with `asyncOptions` | `dropdown-chips` | A trigger that opens a searchable, paginated picker sheet, with the selection shown as chips. | Set `defaults[columnId].mode = 'dropdown-chips'` to move a long static option list into a picker sheet. ## Sections, sticky filters and Show more ```tsx ``` Without `sections`, sticky columns render first and the rest follow. With `sections`, columns you did not list fall into an **Other** group at the end. ## Query JSON output `useDataTableSideFilters` emits a `RuleGroupType` through `onQueryChange` whenever a filter changes, and returns it as `query`. Operators are mapped through `DEFAULT_OPERATOR_MAP` (for example `contains` → `like`, `is between` → `between`, `is any of` → `in`, `include any of` → `contains any`). Pass `operatorMap` to use a different vocabulary. Date values are serialized as timezone-naive `yyyy-MM-dd'T'HH:mm:ss` strings. For date-only columns, the start side is snapped to `00:00:00` and the end side to `23:59:59`, so the whole boundary day is included. ## API Reference ### DataTableSideFiltersProps | Prop | Type | Default | Description | |------|------|---------|-------------| | `columns` | `Column[]` | — | Columns from `useDataTableSideFilters()` (required). | | `filters` | `FiltersState` | — | Current filter state (required). | | `actions` | `DataTableFilterActions` | — | Filter actions from the hook (required). | | `strategy` | `FilterStrategy` | — | `'client'` or `'server'` (required). | | `defaults` | `SideFilterDefaults` | — | Per-column UI hints (mode, threshold, collapsed, sticky, title, hidden). | | `sections` | `ReadonlyArray` | — | Group sections under named headings. | | `locale` | `Locale` | `'en'` | Bundled filter-core string catalog. `UiTranslationProvider` keys win. | | `variant` | `'default' \| 'bordered' \| 'compact'` | `'default'` | Panel style (`bordered` = rounded card with padding). | | `className` | `string` | — | Classes for the trigger button (sheet), the panel (inline) or the collapsed bar. | | `title` | `ReactNode` | `t('ui.dataTableSideFilters.title', 'Filters')` | Header title. Pass `null` to hide it. The sheet title only shows string titles. | | `showActiveChips` | `boolean` | `true` | Show removable chips for the active filters. | | `showClearAll` | `boolean` | `true` | Show **Clear all (n)** while any filter is active. | | `searchable` | `boolean \| string` | `false` | `true` drives the first `text` column with a search input; a column id drives that column. The searched column is removed from the sections. | | `clearAllLabel` | `string` | `t('ui.dataTableSideFilters.clearAll', 'Clear all')` | "Clear all" label. | | `clearLabel` | `string` | `t('ui.dataTableSideFilters.clear', 'Clear')` | Per-section "Clear" label. | | `presentation` | `'sheet' \| 'inline'` | `'sheet'` | Native: trigger + bottom sheet, or the panel in place. | | `triggerLabel` | `string` | `title`, then `'Filters'` | Native (sheet): trigger label. The active count is appended as `(n)`. | | `renderTrigger` | `(props: { open: () => void; activeCount: number; label: string }) => ReactNode` | — | Native (sheet): replace the trigger button. | | `open` | `boolean` | — | Native (sheet): controlled sheet visibility. | | `defaultOpen` | `boolean` | `false` | Native (sheet): initial sheet visibility (uncontrolled). | | `onOpenChange` | `(open: boolean) => void` | — | Native (sheet): called when the sheet opens or closes. | | `collapsible` | `boolean` | `false` | Inline: the panel can collapse to a compact bar. | | `expanded` | `boolean` | — | Inline: controlled expanded state. | | `defaultExpanded` | `boolean` | `true` | Inline: uncontrolled initial expanded state. | | `onExpandedChange` | `(expanded: boolean) => void` | — | Inline: called when the user collapses or expands the panel. | | `collapsedWidth` | `number \| string` | — | Accepted for web parity. The collapsed bar is full-width on native. | | `expandLabel` | `ReactNode` | `title`, then `'Filters'` | Inline: label of the collapsed bar. | | `collapseAriaLabel` | `string` | `t('ui.dataTableSideFilters.collapse', 'Collapse filters')` | Accessibility label of the collapse button. | | `expandAriaLabel` | `string` | `t('ui.dataTableSideFilters.expand', 'Expand filters')` | Accessibility label of the collapsed bar. | ### useDataTableSideFilters options (`DataTableSideFiltersOptions`) Extends the `useDataTableFilters` options (without `defaultFilters` / `filters` / `onFiltersChange`, which the hook owns). | Option | Type | Default | Description | |--------|------|---------|-------------| | `strategy` | `FilterStrategy` | — | `'client'` (in-memory facets) or `'server'` (required). | | `data` | `TData[]` | — | Rows used for faceting (required; pass `[]` for server strategy). | | `columnsConfig` | `ReadonlyArray>` | — | Column definitions (required). | | `calculateFacets` | `boolean` | `true` | Compute faceted counts / min-max from `data`. | | `options` | `Partial>` | — | External options per option column. | | `faceted` | `Partial \| [number, number]>>` | — | External facets per column. | | `defaultQuery` | `RuleGroupType` | — | Uncontrolled initial query, translated to `FiltersState` on mount. | | `query` | `RuleGroupType` | — | Controlled query. Pair it with `onQueryChange`. | | `onQueryChange` | `(query: RuleGroupType) => void` | — | Receives the translated query on every change. | | `combinator` | `'and' \| 'or'` | `'and'` | Top-level combinator of the emitted group. | | `operatorMap` | `SideFilterOperatorMap` | `DEFAULT_OPERATOR_MAP` | Filter operator → query-builder operator vocabulary. | | `defaults` | `SideFilterDefaults` | — | Passed through in the result so you can spread it onto the component. | | `sections` | `ReadonlyArray` | — | Passed through in the result. | The hook returns `{ columns, filters, actions, strategy, query, defaults, sections, reset }`. `reset()` clears every filter. ### SideFilterColumnDefaults | Field | Type | Description | |-------|------|-------------| | `mode` | `SideFilterRenderMode` | `'auto' \| 'text-input' \| 'inline-checkbox' \| 'dropdown-chips' \| 'date' \| 'date-range' \| 'numeric-range' \| 'boolean'`. | | `showMoreThreshold` | `number` | Options shown before **Show more** (default `8`). | | `collapsed` | `boolean` | The section starts collapsed. | | `sticky` | `boolean` | Pin the section above the unpinned sections. | | `title` | `ReactNode` | Override the section title (default `column.displayName`). | | `hidden` | `boolean` | Remove the column from the panel. | ### SideFilterSectionGroup | Field | Type | Description | |-------|------|-------------| | `id` | `string` | Group id. | | `title` | `ReactNode` | Group heading. | | `columnIds` | `ReadonlyArray` | Columns in this group, in order. | | `withDivider` | `boolean` | Divider above the heading (default `true`). | ### Building blocks | Component | Props | |-----------|-------| | `SideFilterSection` | `id`, `title`, `icon?`, `activeCount?` (`0`), `defaultCollapsed?` (`false`), `onClear?`, `clearLabel?` (`'Clear'`), `children` | | `SideFilterActiveChips` | `filters`, `columns`, `actions`, `clearAllLabel?`, `layout?` (`'scroll' \| 'wrap'`, default `'scroll'`) | | `SideFilterClear` | `count`, `onClick`, `label?` (`'Clear all'`), `className?`. Renders nothing while `count` is `0`. | | `SideFilterSearch` | `columnId`, `columns`, `filters`, `actions`, `locale?`, `placeholder?` | | `SideFilterText` | `column`, `filter?`, `actions`, `locale?`, `placeholder?` | | `SideFilterNumericRange` | `column`, `filter?`, `actions`, `locale?` | | `SideFilterDateRange` | `column`, `filter?`, `actions`, `locale?`, `withPresets?` (default `true`) | | `SideFilterBoolean` | `column`, `filter?`, `actions`, `locale?` | | `SideFilterCheckboxList` | `column`, `filter?`, `actions`, `locale?`, `threshold?` | | `SideFilterOptionsDropdown` | `column`, `filter?`, `actions`, `locale?`, `placeholder?` | | `SideFilterAsyncOptions` | `column`, `filter?`, `actions`, `locale?`, `placeholder?` | ## Native differences - A persistent side column does not fit a phone, so the default presentation is a trigger plus a large bottom sheet. The web layout is available as `presentation="inline"`. - Numeric ranges use Min / Max inputs without a slider. - Option pickers (`dropdown-chips` and async options) open a picker sheet instead of a popover. - New native-only props: `presentation`, `triggerLabel`, `renderTrigger`, `open`, `defaultOpen`, `onOpenChange`, and `layout` on `SideFilterActiveChips`. `collapsedWidth` is accepted but ignored. - Column `icon`s are `DocyrusIcon` names (for example `'fal tag'`), elements or components, instead of Lucide icons. ## Translation keys Panel strings use `ui.dataTableSideFilters.*` with English fallbacks: `title`, `clearAll`, `clear`, `other`, `done`, `expand`, `collapse`, `any`, `selected`, `showMore`, `showLess`, `minMaxOrder`, `asyncNotConfigured`. Filter-core strings (search, min / max, true / false, date presets) come from the `ui.dataTableFilter.*` keys shared with [rn-data-table-filter](/docs/native/docyrus/data-table-filter). ## Components | Component | Description | |-----------|-------------| | `DataTableSideFilters` | Trigger + sheet, or the inline panel. | | `SideFilterSection` | Collapsible per-column section with active count and Clear. | | `SideFilterActiveChips` | Removable chips for every active filter (usable outside the panel). | | `SideFilterClear` | "Clear all (n)" link. | | `SideFilterSearch` | Debounced search bound to one `text` column. | | `SideFilterText` / `SideFilterNumericRange` / `SideFilterDateRange` / `SideFilterBoolean` / `SideFilterCheckboxList` / `SideFilterOptionsDropdown` / `SideFilterAsyncOptions` | Per-mode controllers. | ## Helpers | Export | Description | |--------|-------------| | `useDataTableSideFilters(options)` | Filter state + query translation. | | `filtersStateToRuleGroup(filters, combinator, operatorMap, columns)` | `FiltersState` → `RuleGroupType`. | | `ruleGroupToFiltersState(query, columns, operatorMap)` | `RuleGroupType` → `FiltersState`. | | `DEFAULT_OPERATOR_MAP` | Default operator vocabulary. | | `dataTableSideFiltersVariants` | `tv()` variants of the panel. | ## Type Exports | Type | Description | |------|-------------| | `DataTableSideFiltersProps` | Component props. | | `DataTableSideFiltersPresentation` | `'sheet' \| 'inline'`. | | `DataTableSideFiltersOptions` | Hook options. | | `UseDataTableSideFiltersReturn` | Hook result. | | `SideFilterRenderMode` | Render modes. | | `SideFilterColumnDefaults` / `SideFilterDefaults` | Per-column hints. | | `SideFilterSectionGroup` | Section grouping. | | `SideFilterCombinator` | `'and' \| 'or'`. | | `SideFilterOperatorMap` | Operator vocabulary map. | | `RuleGroupType` | Re-exported from `@react-querybuilder/core`. |