# rn-pivot-filter URL: /docs/native/docyrus/pivot-filter Horizontal or vertical strip of count-tagged pills used as a one-dimension quick filter above a grid, calendar or map, with refresh and a settings bottom sheet. API-aligned with the web PivotFilter. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-pivot-filter ``` **Dependencies:** - [react-native-reanimated](https://www.npmjs.com/package/react-native-reanimated) - [react-native-gesture-handler](https://www.npmjs.com/package/react-native-gesture-handler) - [tailwind-variants](https://www.npmjs.com/package/tailwind-variants) `PivotFilter` only handles presentation. To connect it to a Docyrus data source, which covers the pivot query, pill items, date buckets, filter rules and settings content, use [`useDocyrusPivotFilter`](/docs/native/hooks/use-docyrus-pivot-filter) or `DocyrusPivotFilterGroup` for several strips that cross-filter each other. ## Usage ```tsx import { useState } from 'react'; import { PivotFilter, type PivotFilterItem } from '@/components/docyrus-native/pivot-filter'; const items: PivotFilterItem[] = [ { id: 'open', name: 'Open', stat: 24, color: 'sky', icon: 'fal circle' }, { id: 'done', name: 'Done', stat: 1280, color: 'emerald', icon: 'fal circle-check' }, { id: 'EMPTY', name: 'Not Set', stat: 3, isEmpty: true } ]; export function StatusFilter() { const [selected, setSelected] = useState(null); return ( setSelected(item?.id ?? null)} onRefresh={() => refetch()} /> ); } ``` ### Settings sheet When you pass `settingsContent`, a gear button appears. Pressing it opens the content in a bottom sheet (`ActionSheet`, medium detent that expands to large). This replaces the web popover. The exported `PivotFilterSettings` panel holds the date-bucket and calculation controls that `useDocyrusPivotFilter` supplies on its own: ```tsx import { PivotFilter, PivotFilterSettings } from '@/components/docyrus-native/pivot-filter'; )} /> ``` ### Mobile behaviour - **Horizontal** (default): a horizontally scrolling strip without a scroll indicator. The selected pill is scrolled to the centre whenever the selection or the content changes. The refresh and gear buttons sit at the start of the strip. - **Vertical**: a full-width bordered column. The selected row gets an accent background and a left accent bar, and the toolbar sits along the top edge. On phones, prefer horizontal strips. - The accent colour comes from `item.color`, a Tailwind family such as `sky`, a token such as `emerald-500`, or a hex value, resolved with `resolveColorHex`. Users render with `Avatar`, icons with `DocyrusIcon`, and loading renders `SkeletonBase` pills. - Stats of 1000 or more render as `1.3k`, and non-integers render with 2 decimals. ## API Reference ### PivotFilterProps | Prop | Type | Default | Description | |------|------|---------|-------------| | `items` | `PivotFilterItem[]` | — | Items rendered in the strip (required). | | `selectedItemId` | `string \| null` | — | Selected item id; `null` = the "All" pill (required). | | `onSelect` | `(item: PivotFilterItem \| null) => void` | — | Fired when a pill is pressed; "All" fires with `null` (required). | | `total` | `number` | — | Aggregate stat shown on the "All" pill. | | `totalLabel` | `string` | `t('ui.pivotFilter.all', 'All')` | Label of the "All" pill. | | `loading` | `boolean` | `false` | Render skeleton pills instead of items. | | `vertical` | `boolean` | `false` | Vertical column layout. | | `compact` | `boolean` | `false` | Pills shrink to their content width (no 96pt minimum). | | `hideZeroValues` | `boolean` | `false` | Hide items with `stat === 0` (the selected item is always kept). `isEmpty` items with `stat === 0` are always hidden. | | `hideAllPill` | `boolean` | `false` | Hide the "All" pill. | | `onRefresh` | `() => void` | — | Refresh button handler. When omitted, the button is hidden. | | `settingsContent` | `ReactNode` | — | Body of the settings bottom sheet. When omitted, the gear button is hidden. | | `settingsOpen` | `boolean` | — | Controlled sheet open state. | | `onSettingsOpenChange` | `(open: boolean) => void` | — | Sheet open-state handler. | | `emptyState` | `ReactNode` | — | Placeholder shown when there are no items and no "All" pill. | | `className` | `string` | — | Root `View` classes. | | `ref` | `Ref` | — | Ref forwarded to the root `View`. | ### PivotFilterItem | Field | Type | Description | |-------|------|-------------| | `id` | `string` | Stable id, matched against `selectedItemId`. | | `name` | `string` | Pill label. | | `stat` | `number` | Value shown on the right of the pill. | | `secondary` | `string` | Muted secondary text after the name (e.g. `14 Sep`). | | `color` | `string` | Tailwind colour family or token, or a hex value, for the selected accent. | | `icon` | `string` | `DocyrusIcon` identifier (e.g. `fal star`). | | `user` | `PivotFilterUser` | Renders an avatar instead of the icon. | | `isEmpty` | `boolean` | Synthetic "Not Set" bucket: muted, and hidden when its stat is 0. | | `meta` | `Record` | Arbitrary payload passed back to `onSelect`. | ### PivotFilterUser | Field | Type | Description | |-------|------|-------------| | `id` | `string` | User id. | | `name` | `string` | Display name, also used for the initials fallback. | | `picturePath` | `string \| null` | Avatar image URL. | ### PivotFilterSettingsProps | Prop | Type | Description | |------|------|-------------| | `fieldType` | `PivotFilterFieldType` | The date-bucket radio grid is shown only for `'date'`. | | `dateBucket` | `PivotFilterDateBucket` | Current bucket. | | `onDateBucketChange` | `(next: PivotFilterDateBucket) => void` | Bucket change handler. | | `calculation` | `PivotFilterCalculation \| null` | Current calculation (`null` = COUNT of records). | | `onCalculationChange` | `(next: PivotFilterCalculation \| null) => void` | Calculation change handler. | | `aggregateFields` | `PivotFilterAggregateField[]` | Numeric / money / duration fields offered as measures (`{ slug, name }`). | The web panel uses two `Select`s for the aggregate function and the measure field. On native these are chip rows (SUM / AVG / MIN / MAX, then one chip per field), so a second sheet never opens on top of the settings sheet. ### PivotFilterPillProps | Prop | Type | Description | |------|------|-------------| | `item` | `PivotFilterItem \| null` | The item; `null` for the "All" pill. | | `isAll` | `boolean` | Renders the "All" pill. | | `label` | `string` | Pill label. | | `stat` | `number \| undefined` | Stat value. | | `selected` | `boolean` | Selected state. | | `vertical` | `boolean` | Row style (vertical strip). | | `compact` | `boolean` | Compact width. | | `onClick` | `() => void` | Press handler (web name kept for API parity). | | `ref` | `Ref` | Ref to the `Pressable`. | ## Translation keys The component reads these through `useUiTranslation()`, using the same keys as web: `ui.pivotFilter.all`, `ui.pivotFilter.refresh`, `ui.pivotFilter.settings`, `ui.pivotFilter.empty`. The settings panel uses `ui.pivotFilter.dateFilterType`, `ui.pivotFilter.calculation`, `ui.pivotFilter.countOfRecords`, `ui.pivotFilter.function`, `ui.pivotFilter.field`, `ui.pivotFilter.noNumericFields`, plus `ui.pivotFilter.bucket.hoursOfToday | daysOfWeek | daysOfMonth | weeksOfMonth | weeksOfQuarter | monthsOfQuarter | monthsOfYear | quartersOfYear`. ## Components | Component | Description | |-----------|-------------| | `PivotFilter` | The strip (horizontal scroll or vertical column) with toolbar and settings sheet. | | `PivotFilterPill` | A single pill or row. | | `PivotFilterSettings` | Date-bucket and calculation controls for the settings sheet. | ## Type Exports | Type | Description | |------|-------------| | `PivotFilterProps` | Props for `PivotFilter`. | | `PivotFilterItem` | A pill item. | | `PivotFilterUser` | User payload of an item. | | `PivotFilterPillProps` | Props for `PivotFilterPill`. | | `PivotFilterSettingsProps` | Props for `PivotFilterSettings`. | | `PivotFilterAggregateFunc` | `'count' \| 'sum' \| 'avg' \| 'min' \| 'max'`. | | `PivotFilterCalculation` | `{ func, field }`. | | `PivotFilterDateBucket` | The 8 date buckets. | | `PivotFilterFieldType` | `'list' \| 'user' \| 'user-multi' \| 'date' \| 'unsupported'`. | | `PivotFilterAggregateField` | `{ slug, name }` measure field. |