AwesomeStats
KPI stat cards with number formatting, period comparison, SVG mini charts, pressable cards, card menus, reordering and flex / grid / tabs layouts.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-awesome-statspnpm add @shopify/flash-list react-native-svgUsage
import { AwesomeStats, type AwesomeStatItem } from '@/components/docyrus-native/awesome-stats';
const items: AwesomeStatItem[] = [
{
id: 'revenue',
title: 'Revenue',
value: 48500,
icon: 'fal dollar-sign',
color: 'emerald-500',
format: { style: 'currency', currency: 'USD', notation: 'compact' },
comparison: { previousValue: 42100, period: 'last-month' },
miniChart: { type: 'area', data: [32, 35, 31, 38, 42, 48] },
},
{
id: 'users',
title: 'Users',
value: 1250,
comparison: { previousValue: 1100, period: 'last-week' },
},
];
<AwesomeStats items={items} layout={{ type: 'grid', columns: 2 }} />Layouts
<AwesomeStats items={items} layout={{ type: 'flex', cardWidth: 260, behavior: 'scroll' }} /> {/* horizontal FlashList */}
<AwesomeStats items={items} layout={{ type: 'flex', cardWidth: '48%', behavior: 'wrap', gap: 12 }} />
<AwesomeStats items={items} layout={{ type: 'grid', columns: 2, maxCardWidth: 320 }} />
<AwesomeStats items={items} layout={{ type: 'tabs', defaultTabId: 'users' }} />Pressable cards
item.onClick takes precedence over the global onItemClick. clickTarget="title" makes only the title pressable (underlined).
<AwesomeStats items={items} layout={layout} onItemClick={item => router.push(`/kpi/${item.id}`)} clickTarget="card" />Menus and reordering
Card menus open in a native ActionSheet (string icon, destructive and disabled rows). With sortable, long-pressing a card (or a tab) opens the menu with Move left / Move right actions; onItemsOrderChange receives the new order.
<AwesomeStats
items={items}
layout={layout}
sortable
onItemsOrderChange={next => saveOrder(next.map(item => item.id))}
getCardMenuItems={item => [
{ id: 'open', label: 'Open', icon: 'fal eye', onSelect: openStat },
{ id: 'remove', label: 'Remove', icon: 'fal trash', variant: 'destructive', onSelect: removeStat },
]} />Awesome card variant
<AwesomeStats items={items} layout={layout} cardVariant="awesome" awesomeCardProps={{ patternStyle: 'dots', collapsible: true }} />Localisation and number formatting
Comparison copy is translated through useUiTranslation() (ui.awesome.periodYesterday / periodLastWeek / periodLastMonth / periodLastQuarter / periodLastYear, ui.awesome.vs, ui.awesome.noChange, ui.awesome.moveLeft, ui.awesome.moveRight, ui.awesome.openMenuFor). When a NumberFormatProvider is mounted (e.g. by DocyrusTenantProvider), plain number and currency values use the tenant formatter; percent values, explicit locale, non-standard notation, currencyDisplay or signDisplay keep Intl.NumberFormat.
Native differences from web: reordering is menu-driven (long-press → Move left / right) instead of drag; AwesomeStatsCardMenuItem.shortcut is not shown and non-string icons are omitted in the action sheet (non-text labels are flattened to text); ref targets a View. The deprecated { type: 'scroll' } layout is still accepted (scrolls unless behavior: 'wrap', default card width 240).
API Reference
AwesomeStatsProps
| Prop | Type | Default | Description |
|---|---|---|---|
items | AwesomeStatItem[] | - | Stat items (required) |
layout | AwesomeStatsLayout | - | Layout configuration (required) |
cardVariant | 'default' | 'awesome' | 'default' | Plain card or AwesomeCard chrome |
awesomeCardProps | AwesomeStatsAwesomeCardProps | - | AwesomeCard options when cardVariant="awesome" |
sortable | boolean | false | Reorder via long-press → Move left / Move right |
onItemsOrderChange | (items: AwesomeStatItem[]) => void | - | Called with the reordered items |
getCardMenuItems | (item: AwesomeStatItem) => AwesomeStatsCardMenuItem[] | - | Menu items per card (item.menuItems wins) |
onItemClick | (item: AwesomeStatItem) => void | - | Global press handler (item.onClick wins) |
clickTarget | 'title' | 'card' | 'card' | Which part of the card is pressable |
ref | Ref<View> | - | Ref to the root View |
className | string | - | Classes for the root View |
AwesomeStatsLayout
Flex — { type: 'flex' }
| Field | Type | Default | Description |
|---|---|---|---|
cardWidth | number | string | - | Card width: dp number or '<n>px' / '<n>%' (of the container) / '<n>rem' |
behavior | 'wrap' | 'scroll' | - | Wrap into rows, or scroll horizontally (FlashList) |
gap | number | string | 16 | Gap between cards |
Grid — { type: 'grid' }
| Field | Type | Default | Description |
|---|---|---|---|
columns | number | - | Number of columns |
maxCardWidth | number | string | - | Max card width |
gap | number | string | 16 | Gap between cards |
Tabs — { type: 'tabs' }
| Field | Type | Default | Description |
|---|---|---|---|
defaultTabId | string | first item | Initially selected tab |
Scroll (deprecated) — { type: 'scroll', cardWidth?, behavior?, gap? }, an alias of flex (behavior defaults to 'scroll', cardWidth to 240).
AwesomeStatItem
| Field | Type | Default | Description |
|---|---|---|---|
id | string | - | Unique identifier |
eyebrow | ReactNode | - | Small uppercase caption above the title |
title | ReactNode | - | Title |
subtitle | ReactNode | - | Text below the title |
icon | ReactNode | string | - | DocyrusIcon string or node, shown in an accent-tinted box |
color | string | theme primary | Accent colour: Tailwind name ('indigo-500', 'blue'), hex, rgb or hsl |
value | number | - | Value |
format | AwesomeStatValueFormat | - | Number formatting |
unitLabel | ReactNode | - | Unit shown after the value |
comparison | AwesomeStatComparison | - | Comparison with a previous period |
miniChart | AwesomeStatMiniChart | - | Mini chart |
menuItems | AwesomeStatsCardMenuItem[] | - | Card menu items |
onClick | (item: AwesomeStatItem) => void | - | Per-item press handler |
AwesomeStatValueFormat
| Field | Type | Default | Description |
|---|---|---|---|
locale | string | device | Intl locale |
style | 'number' | 'currency' | 'percent' | 'number' | Format style |
currency | string | 'USD' | Currency code |
currencyDisplay | 'symbol' | 'code' | 'name' | 'narrowSymbol' | 'symbol' | Currency display |
notation | 'standard' | 'scientific' | 'engineering' | 'compact' | - | Notation |
minimumFractionDigits | number | - | Minimum fraction digits |
maximumFractionDigits | number | - | Maximum fraction digits |
signDisplay | Intl.NumberFormatOptions['signDisplay'] | - | Sign display |
percentScale | 'whole' | 'fraction' | 'whole' | 'whole' treats 12 as 12% |
AwesomeStatComparison
| Field | Type | Default | Description |
|---|---|---|---|
previousValue | number | - | Previous period value |
period | 'yesterday' | 'last-week' | 'last-month' | 'last-quarter' | 'last-year' | - | Comparison period |
positiveDirection | 'up' | 'down' | 'up' | Which direction is good |
AwesomeStatMiniChart
| Field | Type | Default | Description |
|---|---|---|---|
type | 'sparkline' | 'bar' | 'area' | - | Chart type (SVG paths) |
data | number[] | Array<Record<string, unknown>> | - | Values or rows |
dataKey | string | 'value' | Row key to plot |
position | 'right' | 'bottom' | 'right' | Beside the value (96×80) or full-width below (96 high) |
AwesomeStatsCardMenuItem
| Field | Type | Default | Description |
|---|---|---|---|
id | string | - | Identifier |
label | ReactNode | - | Row label (flattened to text) |
icon | ReactNode | - | DocyrusIcon string; non-string icons are omitted |
shortcut | ReactNode | - | Web parity; not shown |
disabled | boolean | - | Disabled row |
variant | 'default' | 'destructive' | 'default' | Destructive styling |
onSelect | (item: AwesomeStatItem) => void | - | Called with the stat |
AwesomeStatsAwesomeCardProps
| Field | Type | Default | Description |
|---|---|---|---|
pattern | boolean | true | Pattern background |
patternStyle | PatternStyle | 'stripes' | Pattern motif |
collapsible | boolean | false | Collapsible card |
collapsed | boolean | false | Initially collapsed |
chevronPosition | 'left' | 'right' | 'left' | Chevron position |
className | string | - | Card classes |
Type Exports
| Type | Description |
|---|---|
AwesomeStatsProps | Component props |
AwesomeStatItem | Stat item |
AwesomeStatValueFormat | Number formatting options |
AwesomeStatComparison | Comparison configuration |
AwesomeStatComparisonPeriod | Comparison period union |
AwesomeStatMiniChart | Mini chart configuration |
AwesomeStatsCardMenuItem | Card menu item |
AwesomeStatsAwesomeCardProps | AwesomeCard options |
AwesomeStatsClickTarget | 'title' | 'card' |
AwesomeStatsLayout | Layout union |
AwesomeDialog
Compound dialog rendered as a centred modal, an edge sheet or a swipe-to-dismiss drawer, with pattern chrome, header avatar / icon, toolbar menus, fullscreen and minimise-to-bar.
BulkUpdateDialog
Bottom-sheet dialog that overwrites one or more fields on every selected record of a Docyrus data source in a single bulk PATCH. Field values are edited with the native form fields. API-aligned with the web BulkUpdateDialog.