Docyrus

PivotFilter

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.

iOSAndroid
Preview PivotFilter on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-pivot-filter
Required Packages(3 packages)
pnpm add react-native-reanimated react-native-gesture-handler 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 or DocyrusPivotFilterGroup for several strips that cross-filter each other.

Usage

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<string | null>(null);

  return (
    <PivotFilter
      items={items}
      total={1307}
      selectedItemId={selected}
      onSelect={item => 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:

import { PivotFilter, PivotFilterSettings } from '@/components/docyrus-native/pivot-filter';

<PivotFilter
  {...props}
  settingsContent={(
    <PivotFilterSettings
      fieldType="date"
      dateBucket={bucket}
      onDateBucketChange={setBucket}
      calculation={calculation}
      onCalculationChange={setCalculation}
      aggregateFields={[{ slug: 'budget', name: 'Budget' }]} />
  )} />

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

PropTypeDefaultDescription
itemsPivotFilterItem[]—Items rendered in the strip (required).
selectedItemIdstring | 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).
totalnumber—Aggregate stat shown on the "All" pill.
totalLabelstringt('ui.pivotFilter.all', 'All')Label of the "All" pill.
loadingbooleanfalseRender skeleton pills instead of items.
verticalbooleanfalseVertical column layout.
compactbooleanfalsePills shrink to their content width (no 96pt minimum).
hideZeroValuesbooleanfalseHide items with stat === 0 (the selected item is always kept). isEmpty items with stat === 0 are always hidden.
hideAllPillbooleanfalseHide the "All" pill.
onRefresh() => void—Refresh button handler. When omitted, the button is hidden.
settingsContentReactNode—Body of the settings bottom sheet. When omitted, the gear button is hidden.
settingsOpenboolean—Controlled sheet open state.
onSettingsOpenChange(open: boolean) => void—Sheet open-state handler.
emptyStateReactNode—Placeholder shown when there are no items and no "All" pill.
classNamestring—Root View classes.
refRef<View>—Ref forwarded to the root View.

PivotFilterItem

FieldTypeDescription
idstringStable id, matched against selectedItemId.
namestringPill label.
statnumberValue shown on the right of the pill.
secondarystringMuted secondary text after the name (e.g. 14 Sep).
colorstringTailwind colour family or token, or a hex value, for the selected accent.
iconstringDocyrusIcon identifier (e.g. fal star).
userPivotFilterUserRenders an avatar instead of the icon.
isEmptybooleanSynthetic "Not Set" bucket: muted, and hidden when its stat is 0.
metaRecord<string, unknown>Arbitrary payload passed back to onSelect.

PivotFilterUser

FieldTypeDescription
idstringUser id.
namestringDisplay name, also used for the initials fallback.
picturePathstring | nullAvatar image URL.

PivotFilterSettingsProps

PropTypeDescription
fieldTypePivotFilterFieldTypeThe date-bucket radio grid is shown only for 'date'.
dateBucketPivotFilterDateBucketCurrent bucket.
onDateBucketChange(next: PivotFilterDateBucket) => voidBucket change handler.
calculationPivotFilterCalculation | nullCurrent calculation (null = COUNT of records).
onCalculationChange(next: PivotFilterCalculation | null) => voidCalculation change handler.
aggregateFieldsPivotFilterAggregateField[]Numeric / money / duration fields offered as measures ({ slug, name }).

The web panel uses two Selects 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

PropTypeDescription
itemPivotFilterItem | nullThe item; null for the "All" pill.
isAllbooleanRenders the "All" pill.
labelstringPill label.
statnumber | undefinedStat value.
selectedbooleanSelected state.
verticalbooleanRow style (vertical strip).
compactbooleanCompact width.
onClick() => voidPress handler (web name kept for API parity).
refRef<View>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

ComponentDescription
PivotFilterThe strip (horizontal scroll or vertical column) with toolbar and settings sheet.
PivotFilterPillA single pill or row.
PivotFilterSettingsDate-bucket and calculation controls for the settings sheet.

Type Exports

TypeDescription
PivotFilterPropsProps for PivotFilter.
PivotFilterItemA pill item.
PivotFilterUserUser payload of an item.
PivotFilterPillPropsProps for PivotFilterPill.
PivotFilterSettingsPropsProps for PivotFilterSettings.
PivotFilterAggregateFunc'count' | 'sum' | 'avg' | 'min' | 'max'.
PivotFilterCalculation{ func, field }.
PivotFilterDateBucketThe 8 date buckets.
PivotFilterFieldType'list' | 'user' | 'user-multi' | 'date' | 'unsupported'.
PivotFilterAggregateField{ slug, name } measure field.

On this page