# rn-pivot-calendar URL: /docs/native/docyrus/pivot-calendar Date-bucketed pivot for React Native — a month calendar with per-day measures and a tap-to-expand day sheet, plus week, days-of-month and year grids with a sticky group column. API-aligned with the web PivotCalendar. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-pivot-calendar ``` **Dependencies:** - [date-fns](https://www.npmjs.com/package/date-fns) - [react-native-gesture-handler](https://www.npmjs.com/package/react-native-gesture-handler) - [react-native-reanimated](https://www.npmjs.com/package/react-native-reanimated) - [tailwind-variants](https://www.npmjs.com/package/tailwind-variants) The public API (`PivotCalendar`, `pivotCalendarVariants`, `usePivotCalendarController`, the helpers and every type) matches the web component. `helpers.ts`, `types.ts` and the controller hook are synced copies of the web source. The layout is redesigned for phones: - **Toolbar**: group-filter icon · ‹ title › · **Today** on one line, with a horizontally scrollable view switcher (`Month` / `Week` / `Days` / `Year`) underneath. - **Month view**: a 7-column grid. Each day shows the first measure (the measure's `formatValue`, else a compact number such as `1.2K`) and a coloured dot for each further non-zero measure. Tapping a day opens a bottom sheet that lists **every** measure, plus a per-group breakdown when `groupBy` is set. Each non-zero row fires `onCellClick`. - **Grid views** (`days-of-week`, `days-of-month`, `months-of-year`): the group column stays fixed on the left while the date columns scroll horizontally. The view scrolls to today / the current month. A **Totals** row closes the grid. Each non-zero value cell fires `onCellClick`. - **Groups**: the web side panel becomes a bottom sheet with a checkbox list (`AvatarThumbnail` per group) and an All / Clear toggle. The selection applies to every view. ## Usage ```tsx import { PivotCalendar, type IPivotCalendarGroupBy, type IPivotCalendarMeasure } from '@/components/docyrus-native/pivot-calendar'; type Sale = { date: string; region: string; amount: number }; const measures: IPivotCalendarMeasure[] = [ { id: 'revenue', label: 'Revenue', aggregate: 'sum', getValue: row => row.amount, formatValue: v => `$${(v / 1000).toFixed(1)}K`, color: '#10b981' }, { id: 'orders', label: 'Orders', aggregate: 'count', color: '#6366f1' } ]; const groupBy: IPivotCalendarGroupBy = { id: 'region', label: 'Region', getId: row => row.region }; export function SalesCalendar({ sales }: { sales: Sale[] }) { return ( mode="local" data={sales} getDate={row => row.date} groupBy={groupBy} measures={measures} onCellClick={info => console.log(info.bucket, info.groupId, info.value)} /> ); } ``` For a Docyrus data source, use [`useDocyrusPivotCalendar`](/docs/native/hooks/use-docyrus-pivot-calendar) and spread its `pivotCalendarProps`. ## API Reference ### PivotCalendar | Prop | Type | Default | Description | |------|------|---------|-------------| | `measures` | `IPivotCalendarMeasure[]` | — | **Required.** Measures to aggregate and display. | | `mode` | `'local' \| 'remote'` | `'local'` | `'local'` aggregates `data`; `'remote'` reads pre-aggregated `cells`. | | `data` | `TData[]` | — | Raw rows. Required when `mode='local'`. | | `cells` | `IPivotCalendarRemoteCell[]` | — | Pre-aggregated cells. Required when `mode='remote'`. | | `getDate` | `(row: TData) => Date \| string` | — | Reads a row's date. Required when `mode='local'`. | | `groupBy` | `IPivotCalendarGroupBy` | — | Optional grouping dimension. Enables the group filter sheet and the grid rows. | | `groups` | `IPivotCalendarGroup[]` | inferred | Group catalog. Required for `mode='remote'` with `groupBy`; inferred from `data` in local mode. | | `defaultView` | `TPivotCalendarView` | `'month-calendar'` | Initial view. | | `view` | `TPivotCalendarView` | — | Controlled view. | | `onViewChange` | `(view: TPivotCalendarView) => void` | — | Called when the view changes. | | `defaultDate` | `Date` | `new Date()` | Initial reference date. | | `date` | `Date` | — | Controlled reference date. | | `onDateChange` | `(date: Date) => void` | — | Called when the reference date changes. | | `defaultSelectedGroupIds` | `string[]` | all groups | Initially selected groups. | | `selectedGroupIds` | `string[]` | — | Controlled group selection. | | `onSelectedGroupIdsChange` | `(ids: string[]) => void` | — | Called when the group selection changes. | | `hideViewSwitcher` | `boolean` | `false` | Hide the view switcher. | | `hideSidebar` | `boolean` | `false` | Hide the group filter button and sheet (the web side panel). | | `visibleViews` | `TPivotCalendarView[]` | all 4 views | Views exposed in the switcher. | | `title` | `ReactNode` | range label | Custom toolbar title. | | `onCellClick` | `(info: IPivotCalendarCellClick) => void` | — | Fires when a measure value is tapped: a day-sheet row or a grid value cell. Use it for drilldown. | | `maxCellMeasures` | `number` | `2` | **Native only.** How many measures the month cells (value + dots) and the grid views render. The day sheet always lists every measure. | | `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Day-cell height (52 / 44 / 64 pt) and grid-row height (44 / 36 / 48 pt). On web this sets the container's `min-h`. | | `className` | `string` | — | Container classes. | ### IPivotCalendarMeasure | Field | Type | Description | |-------|------|-------------| | `id` | `string` | Stable id. | | `label` | `string` | Visible label. | | `shortLabel` | `string` | Compact label for the legend and the grid headers. | | `aggregate` | `'sum' \| 'count' \| 'avg' \| 'min' \| 'max'` | Aggregation (local mode). | | `getValue` | `(row: TData) => number \| null \| undefined` | Value reader (local mode). `count` without `getValue` counts rows. | | `formatValue` | `(value: number) => string` | Display formatter. | | `color` | `string` | Accent for dots and legend (hex or Tailwind `family-shade`). | | `description` | `string` | Shown in the day sheet. | ### IPivotCalendarGroupBy / IPivotCalendarGroup | Field | Type | Description | |-------|------|-------------| | `groupBy.id` | `string` | Dimension id. | | `groupBy.label` | `string` | Label for the filter sheet and the grid's group column. | | `groupBy.getId` | `(row: TData) => string \| null \| undefined` | Group id of a row. | | `groupBy.getGroup` | `(row: TData) => IPivotCalendarGroup \| null \| undefined` | Optional group descriptor (label, avatar). | | `group.id` / `group.label` | `string` | Identity and label. | | `group.color` / `group.icon` / `group.image` | `string \| null` / `string \| null` / `{ signed_url?, file_name? } \| null` | Rendered with `AvatarThumbnail`. | | `group.description` | `string` | Optional description. | ### IPivotCalendarCellClick | Field | Type | Description | |-------|------|-------------| | `bucket` | `string` | `yyyy-MM-dd` (day) or `yyyy-MM` (months-of-year). | | `bucketStart` / `bucketEnd` | `Date` | Inclusive local-time range of the bucket. | | `view` | `TPivotCalendarView` | Active view. | | `groupId` | `string \| null` | Group of the tapped value. Month totals carry a group only when exactly one group is selected. | | `group` | `IPivotCalendarGroup` | Group entry, when available. | | `measure` | `IPivotCalendarMeasure` | Tapped measure. | | `value` / `formattedValue` | `number` / `string` | Raw and formatted value. | ### IPivotCalendarRemoteCell | Field | Type | Description | |-------|------|-------------| | `bucket` | `string` | `YYYY-MM-DD` for days, `YYYY-MM` for months. | | `groupId` | `string \| null` | Group id, or omitted for ungrouped totals. | | `values` | `{ measureId: string; value: number }[]` | Aggregated values. | ### Helpers | Export | Description | |--------|-------------| | `usePivotCalendarController(props)` | Headless controller (`IPivotCalendarController`): view / date / group state, `getCellValues`, `formatValue`, navigation, `rangeLabel`, `emitCellClick`. | | `bucketKeyForDate(date, view)` | Bucket key for a date in a view. | | `buildLocalCellMap({ data, getDate, view, measures, groupBy })` | Aggregates raw rows into a `bucket::groupId` map. | | `buildRemoteCellMap(cells)` | Indexes remote cells the same way. | | `getRangeForView(date, view)` / `getRangeForBucket(bucket, view)` | Date range of a view or a bucket. | | `navigateDate(date, view, 'previous' \| 'next')` | Steps a week, a month or a year. | | `rangeLabelForView(date, view)` | Toolbar title (`May 2026`, `May 3 – 9, 2026`, `2026`). | | `pivotCalendarVariants` | `tv()` slots `base` / `dayCell` / `gridRow` with the `size` variant. | ## Components | Component | Description | |-----------|-------------| | `PivotCalendar` | Toolbar, active view, group filter sheet and day sheet. | ## Type Exports | Type | Description | |------|-------------| | `PivotCalendarProps` | Component props (without `size`). | | `PivotCalendarRootProps` | `PivotCalendarProps` + `size` variant. | | `TPivotCalendarView` | `'month-calendar' \| 'days-of-week' \| 'days-of-month' \| 'months-of-year'` | | `TPivotCalendarMode` | `'local' \| 'remote'` | | `TPivotCalendarAggregate` | `'sum' \| 'count' \| 'avg' \| 'min' \| 'max'` | | `TPivotCalendarBucketKind` | `'day' \| 'weekday' \| 'month'` | | `IPivotCalendarMeasure` | Measure descriptor. | | `IPivotCalendarGroup` | Group descriptor. | | `IPivotCalendarGroupBy` | Grouping dimension. | | `IPivotCalendarMeasureValue` | `{ measureId, value }`. | | `IPivotCalendarCellClick` | `onCellClick` payload. | | `IPivotCalendarRemoteCell` | Pre-aggregated cell. | | `IPivotCalendarController` | Return type of `usePivotCalendarController`. | ## Translations Copy uses `useUiTranslation()` with English fallbacks: `ui.pivotCalendar.today`, `previousPeriod`, `nextPeriod`, `filterGroups`, `views`, `viewMonth`, `viewWeek`, `viewDays`, `viewYear`, `groups`, `all`, `clear`, `noGroups`, `totals`, `total`, `byGroup` (`By {{group}}`), `tapToDrillDown`, and `weekdaySun` … `weekdaySat`.