# rn-data-table-filter URL: /docs/native/docyrus/data-table-filter Typed filter builder with 7 data types, ~70 operators including relative dates, async option loading, faceted counts and a mobile step wizard. The native port of the web `data-table-filter` engine. The DOM-free core (column model, operators, faceting, `useDataTableFilters`, `createColumnConfigHelper`) is copied from `@docyrus/ui`, so filter state (`FiltersState`) is interchangeable between platforms. The UI is re-skinned for phones: a **Filter** button opens an ActionSheet wizard (column → operator → value) and active filters show as pills — tap the operator or value of a pill to edit it, × to remove it. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-data-table-filter ``` **Dependencies:** - [date-fns](https://www.npmjs.com/package/date-fns) - [@react-native-community/datetimepicker](https://www.npmjs.com/package/@react-native-community/datetimepicker) ## Usage ```tsx import { useState } from 'react'; import { DataTableFilter, createColumnConfigHelper, filterData, useDataTableFilters, type FiltersState } from '@/components/docyrus-native/data-table-filter'; type Deal = { name: string; stage: string; amount: number; closeDate: Date; won: boolean }; const dtf = createColumnConfigHelper(); const columnsConfig = [ dtf.text().id('name').accessor(row => row.name).displayName('Name').icon('fal text').build(), dtf.option().id('stage').accessor(row => row.stage).displayName('Stage') .options([ { label: 'Lead', value: 'lead', color: '#94a3b8' }, { label: 'Won', value: 'won', color: '#22c55e' } ]) .build(), dtf.number().id('amount').accessor(row => row.amount).displayName('Amount').build(), dtf.date().id('closeDate').accessor(row => row.closeDate).displayName('Close date').build() ]; export function DealsFilter({ deals }: { deals: Deal[] }) { const [filters, setFilters] = useState([]); const { columns, actions, strategy } = useDataTableFilters({ strategy: 'client', data: deals, columnsConfig, filters, onFiltersChange: setFilters }); const visibleDeals = filterData(deals, columnsConfig, filters); // render these rows return ( <> ); } ``` ### Server strategy (Docyrus items endpoint) ```tsx import { filtersToServerFilter } from '@/components/docyrus-native/data-table-filter'; const { columns, filters, actions } = useDataTableFilters({ strategy: 'server', data: [], columnsConfig, calculateFacets: false, options: { stage: stageOptions }, // server strategy needs static options… faceted: { stage: stageCounts } // …and (optionally) counts from the server }); // Backend-safe payload: `like %x%`, `between`, `not between` → OR group, `contains any` for array columns, // snake_case relative dates (`last_7_days`, `in_last_x_days` + N). Unsendable rules are dropped. const params = { filters: filtersToServerFilter(filters, { columns: columnsConfig }) }; ``` ### Async options (relation / user columns) ```tsx const ownerColumn = { ...dtf.option().id('owner').accessor(row => row.ownerId).displayName('Owner').build(), asyncOptions: { pageSize: 25, debounceMs: 300, load: async ({ search, page, pageSize, signal }) => { const res = await client.get('/v1/users', { params: { search, offset: page * pageSize, limit: pageSize }, signal }); return { items: res.data.map(u => ({ value: u.id, label: `${u.firstname} ${u.lastname}`, imageUrl: u.photo })), hasMore: res.data.length === pageSize }; } } }; ``` Loaded labels are HTML-entity decoded (`&` → `&`), loads are aborted when the search changes or the sheet closes, and "Load more" fetches the next page. ## API Reference ### DataTableFilterProps | Prop | Type | Default | Description | |------|------|---------|-------------| | `columns` | `Column[]` | — | **Required.** Columns from `useDataTableFilters().columns`. | | `filters` | `FiltersState` | — | **Required.** Current filter state (`FilterModel[]`). | | `actions` | `DataTableFilterActions` | — | **Required.** Actions from `useDataTableFilters().actions`. | | `strategy` | `'client' \| 'server'` | — | **Required.** `client` computes options / facets from `data`; `server` expects them from the host and filters nothing locally. | | `locale` | `'en' \| 'de' \| 'fr' \| 'nl' \| 'zh_CN' \| 'zh_TW'` | `'en'` | Bundled string catalog (web parity). An app `UiTranslationProvider` entry under `ui.dataTableFilter.` wins. | | `combinator` | `'and' \| 'or'` | `'and'` | How the host combines filters — the value shown by the AND / OR toggle. `useDataTableFilters` and `filterData` default to AND. | | `onCombinatorChange` | `(combinator: 'and' \| 'or') => void` | — | Shows an interactive AND / OR toggle when two or more filters are active. | | `className` | `string` | — | Additional classes for the root view. | ### useDataTableFilters(options) | Option | Type | Default | Description | |--------|------|---------|-------------| | `strategy` | `'client' \| 'server'` | — | **Required.** Filtering strategy. | | `data` | `TData[]` | — | **Required.** Rows used for option / facet / min-max derivation (pass `[]` for server-paged data). | | `columnsConfig` | `ColumnConfig[]` | — | **Required.** Column definitions (hand-written or built with `createColumnConfigHelper`). | | `defaultFilters` | `FiltersState` | `[]` | Initial state (uncontrolled). | | `filters` | `FiltersState` | — | Controlled state — must be paired with `onFiltersChange`. | | `onFiltersChange` | `Dispatch>` | — | Controlled setter. | | `calculateFacets` | `boolean` | `true` | Compute faceted counts from `data`. Disable when you supply `faceted` yourself. | | `options` | `Partial>` | — | Override option lists per option / multiOption column. | | `faceted` | `Partial \| [number, number]>>` | — | Faceted counts (option columns) or `[min, max]` (number columns). | Returns `{ columns, filters, actions, strategy }`. ### DataTableFilterActions | Action | Signature | Description | |--------|-----------|-------------| | `addFilterValue` | `(column, values) => void` | Add values to an option / multiOption filter (operator auto-switches `is` → `is any of`). | | `removeFilterValue` | `(column, values) => void` | Remove values; the filter is dropped when empty. | | `setFilterValue` | `(column, values) => void` | Replace the values of any filter type. | | `setFilterOperator` | `(columnId, operator) => void` | Change the operator. | | `removeFilter` | `(columnId) => void` | Remove one filter. | | `removeAllFilters` | `() => void` | Clear everything. | | `upsertFilter` | `(filter: FilterModel) => void` | **Native addition (optional).** Insert / replace a whole filter in one step — how the wizard creates value-less filters (`is empty`, `today`). | ### ColumnConfig<TData> | Field | Type | Description | |-------|------|-------------| | `id` | `string` | **Required.** Column id (becomes `FilterModel.columnId`). | | `accessor` | `(row: TData) => unknown` | **Required.** Reads the cell value. | | `displayName` | `string` | **Required.** Label. | | `type` | `ColumnDataType` | **Required.** `'text' \| 'number' \| 'date' \| 'option' \| 'multiOption' \| 'boolean' \| 'uuid'`. | | `icon` | `string \| ReactNode` | DocyrusIcon name (`'fal user'`) or a node. Optional on native — a type default is used. (Web: `LucideIcon`, required.) | | `options` | `ColumnOption[]` | Static options (option / multiOption). | | `facetedOptions` | `Map` | Server-supplied counts. | | `min` / `max` | `number` | Number bounds (shown as input hints). | | `transformOptionFn` | `(value) => ColumnOption` | Derive options from raw values. | | `orderFn` | `(a, b) => number` | Custom option ordering. | | `asyncOptions` | `AsyncOptionsConfig` | Paged, debounced, abortable remote option loader. | | `trueLabel` / `falseLabel` | `string` | Boolean choice labels. | | `includeTime` | `boolean` | Date columns backed by a timestamp: datetime pickers, minute-precision comparison, day-range `is`. | | `group` | `string` | Section header in the column picker (e.g. the relation a borrowed column comes through). | | `allowedOperators` | `FilterOperators[type][]` | Restrict the operator list. | | `operatorNote` | `string` | One line shown under a restricted operator list (already translated). | ### ColumnOption | Field | Type | Description | |-------|------|-------------| | `label` | `string` | **Required.** Display label. | | `value` | `string` | **Required.** Stored value. | | `secondaryLabel` | `string` | Dimmer second line. | | `icon` | `string \| ReactElement \| ElementType` | DocyrusIcon name, element or component. | | `imageUrl` | `string` | Avatar image. | | `color` | `string` | Color dot (status / enum options). | ### AsyncOptionsConfig | Field | Type | Default | Description | |-------|------|---------|-------------| | `load` | `({ search, page, pageSize, signal }) => Promise<{ items: ColumnOption[]; hasMore?: boolean }>` | — | **Required.** Page loader; forward `signal` to your HTTP client. | | `pageSize` | `number` | `25` | Items per page. | | `debounceMs` | `number` | `300` | Search debounce. | ### FilterModel | Field | Type | Description | |-------|------|-------------| | `columnId` | `string` | Column id. | | `type` | `ColumnDataType` | Column data type. | | `operator` | `FilterOperators[type]` | Operator (see below). | | `values` | `FilterValues` | Values: strings (text / option / uuid), numbers (number, or `[N]` for X-days operators), `Date`s (date), booleans. | ### Operators | Type | Operators | |------|-----------| | `text` | `contains`, `does not contain`, `is`, `is not`, `starts with`, `ends with`, `is empty`, `is not empty` | | `number` | `is`, `is not`, `is less than`, `is less than or equal to`, `is greater than`, `is greater than or equal to`, `is between`, `is not between`, `is empty`, `is not empty` | | `date` (calendar) | `is`, `is not`, `is before`, `is on or after`, `is after`, `is on or before`, `is between`, `is not between`, `is empty`, `is not empty` | | `date` (relative) | `today`, `tomorrow`, `yesterday`, `last7/15/30/60/90/120Days`, `next7/15/30/60/90/120Days`, `lastWeek`, `thisWeek`, `nextWeek`, `lastMonth`, `thisMonth`, `nextMonth`, `beforeToday`, `afterToday`, `lastYear`, `thisYear`, `nextYear`, `first/second/third/fourthQuarter`, `last3Months`, `last6Months`, and the N-day family `xDaysAgo`, `xDaysLater`, `beforeLastXDays`, `inLastXDays`, `afterLastXDays`, `inNextXDays` | | `option` | `is`, `is not`, `is any of`, `is none of`, `is empty`, `is not empty` | | `multiOption` | `include`, `exclude`, `include any of`, `include all of`, `exclude if any of`, `exclude if all`, `is empty`, `is not empty` | | `boolean` | `is`, `is not`, `is empty`, `is not empty` | | `uuid` | `is`, `is not`, `is empty`, `is not empty` (exact match only; a rule is only sent for a complete UUID) | ## Wizard behaviour | Step | Native control | |------|----------------| | Column | Searchable list sectioned by `group`; typing 2+ characters also lists matching option values across option columns, which toggle directly (web "quick search"). | | Operator | Every operator of the type (date: Calendar / Relative sections), honouring `allowedOperators` + `operatorNote`. Value-less operators apply immediately. | | Value — text / uuid | Text input (uuid shows an "incomplete UUID" hint). | | Value — number | One input, or Min / Max inputs for `is between` / `is not between`. Faceted min / max shown as placeholders. | | Value — date | `DateTimePicker` (`date` or `datetime` by `includeTime`); From / To pickers (or `DateTimeRangePicker` with time) for ranges; preset chips (Today, Yesterday, Last 7 / 30 days, This week / month / year) switch `is` → `is between`; N input for X-days operators. | | Value — option / multiOption | Searchable check list with faceted counts, color dots, icons, avatars; async columns load pages with "Load more". Several picks under `is` become `is any of`. | | Value — boolean | True / False rows (`trueLabel` / `falseLabel`) that apply on tap. | ## Utilities | Export | Description | |--------|-------------| | `createColumnConfigHelper()` | Fluent builder: `.text() .number() .date() .option() .multiOption() .uuid()` → `.id() .accessor() .displayName() .icon() .min() .max() .options() .transformOptionFn() .orderFn() .build()`. | | `filterData(data, columns, filters, { combinator?, now?, weekStartsOn? })` | Native addition. Client-side filtering applying every operator (relative dates resolved against local time). | | `evaluateFilter(cellValue, filter, options?)` | Native addition. Test one value against one `FilterModel`. | | `filtersToServerFilter(filters, { columns?, combinator?, getRuleOptions? })` | Native addition. Backend-safe Docyrus filter group; `getRuleOptions(columnId)` sets `field`, `arrayColumn`, `includeTime`. | | `filterToServerRule(filter, options?)` | One rule (or OR group) / `null` when unsendable. | | `resolveRelativeDateWindow(operator, days?, options?)` | Inclusive `{ from?, to? }` window for a relative-date operator. | | `decodeHtmlEntities(text)` / `decodeOptionLabels(items)` | DOM-free entity decoding. | | `determineNewOperator`, `filterTypeOperatorDetails`, `isRelativeDateOperator`, `isXDaysRelativeOperator`, `DEFAULT_OPERATORS` | Operator metadata (web parity). | | `useFilterTranslation(locale)` | Translation function used by the component (`ui.dataTableFilter.` → locale catalog → English). | ## i18n Strings come from the bundled catalogs selected by `locale` (the web JSON files, verbatim). An app `UiTranslationProvider` overrides any of them under `ui.dataTableFilter.` — e.g. `ui.dataTableFilter.filter`, `ui.dataTableFilter.filters.date.today`. Native-only strings: `ui.dataTableFilter.apply` (Apply), `ui.dataTableFilter.back` (Back), `ui.dataTableFilter.removeFilter` (Remove filter), `ui.dataTableFilter.addFilter` (Add filter), `ui.dataTableFilter.or` (or). ## Data grid integration The native `DataGrid` toolbar's filter sheet is built on this engine (web `DataGridFilterMenu` parity): cell variants map to filter types, `FilterModel`s are written to TanStack `columnFilters` as `{ operator, value, endValue }` (web grid operator names such as `isBetween`, `includesAny`, `last7Days`), and the grid's default `filterFn` applies every operator in memory, relative dates included. Array-backed variants (`multi-select`, `tag-select`, `user-multi-select`) use the array operators; `duration` columns are filtered in hours. ## Migration from 0.x | Before | Now | |--------|-----| | `columns: FilterColumn[]` (`{ id, label, type, options }`) | `columns` from `useDataTableFilters({ columnsConfig })` (`ColumnConfig`: `id`, `accessor`, `displayName`, `type`, `options`). | | `filters: FilterItem[]` (`{ id, columnId, operator, value }`) | `filters: FiltersState` (`{ columnId, type, operator, values[] }`). | | `onFiltersChange` | `actions` (from the hook) — or pass `filters` + `onFiltersChange` to the hook. | | `strategy: 'and' \| 'or'` | `combinator` + `onCombinatorChange`. `strategy` now means `'client' \| 'server'` (web). | | type `select` | `option` (single) / `multiOption` (multiple). | | operators `equals`, `contains`, `gt`, `lt`, `between`, `is_empty`, `is_not_empty` | Web operator names (`is`, `contains`, `is greater than`, `is less than`, `is between`, `is empty`, `is not empty`, …). | | `style` | Removed — use `className`. | ## Components | Component | Description | |-----------|-------------| | `DataTableFilter` | Filter button, AND / OR toggle, active-filter pills and the wizard sheet. | ## Type Exports | Type | Description | |------|-------------| | `DataTableFilterProps` | Component props. | | `DataTableFilterCombinator` | `'and' \| 'or'`. | | `DataTableFiltersOptions` | `useDataTableFilters` options. | | `DataTableFilterActions` | Filter actions. | | `Column` / `ColumnConfig` / `ColumnConfigHelper` / `DataTableFilterConfig` | Column model. | | `ColumnDataType` / `OptionBasedColumnDataType` | Column data types. | | `ColumnOption` / `ColumnOptionExtended` | Option model (extended adds `selected`, `count`). | | `AsyncOptionsConfig` | Async option loader config. | | `FilterModel` / `FiltersState` / `FilterValues` / `FilterStrategy` | Filter state. | | `FilterOperators` and `TextFilterOperator`, `NumberFilterOperator`, `DateFilterOperator`, `OptionFilterOperator`, `MultiOptionFilterOperator`, `BooleanFilterOperator`, `UuidFilterOperator` | Operator unions. | | `FilterIcon` | `string \| ReactNode`. | | `Locale` / `FilterTranslateFn` | i18n. | | `EvaluateFilterOptions` / `RelativeDateOptions` / `RelativeDateWindow` | Client filtering. | | `DocyrusFilterRule` / `DocyrusFilterGroup` / `ServerRuleOptions` / `ServerFilterOptions` | Server serialization. |