Docyrus

AwesomeStats

KPI stat cards with number formatting, period comparison, SVG mini charts, pressable cards, card menus, reordering and flex / grid / tabs layouts.

iOSAndroid
Preview AwesomeStats 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-awesome-stats
Required Packages(2 packages)
pnpm add @shopify/flash-list react-native-svg

Usage

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" />

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

PropTypeDefaultDescription
itemsAwesomeStatItem[]-Stat items (required)
layoutAwesomeStatsLayout-Layout configuration (required)
cardVariant'default' | 'awesome''default'Plain card or AwesomeCard chrome
awesomeCardPropsAwesomeStatsAwesomeCardProps-AwesomeCard options when cardVariant="awesome"
sortablebooleanfalseReorder 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
refRef<View>-Ref to the root View
classNamestring-Classes for the root View

AwesomeStatsLayout

Flex — { type: 'flex' }

FieldTypeDefaultDescription
cardWidthnumber | string-Card width: dp number or '<n>px' / '<n>%' (of the container) / '<n>rem'
behavior'wrap' | 'scroll'-Wrap into rows, or scroll horizontally (FlashList)
gapnumber | string16Gap between cards

Grid — { type: 'grid' }

FieldTypeDefaultDescription
columnsnumber-Number of columns
maxCardWidthnumber | string-Max card width
gapnumber | string16Gap between cards

Tabs — { type: 'tabs' }

FieldTypeDefaultDescription
defaultTabIdstringfirst itemInitially selected tab

Scroll (deprecated) — { type: 'scroll', cardWidth?, behavior?, gap? }, an alias of flex (behavior defaults to 'scroll', cardWidth to 240).

AwesomeStatItem

FieldTypeDefaultDescription
idstring-Unique identifier
eyebrowReactNode-Small uppercase caption above the title
titleReactNode-Title
subtitleReactNode-Text below the title
iconReactNode | string-DocyrusIcon string or node, shown in an accent-tinted box
colorstringtheme primaryAccent colour: Tailwind name ('indigo-500', 'blue'), hex, rgb or hsl
valuenumber-Value
formatAwesomeStatValueFormat-Number formatting
unitLabelReactNode-Unit shown after the value
comparisonAwesomeStatComparison-Comparison with a previous period
miniChartAwesomeStatMiniChart-Mini chart
menuItemsAwesomeStatsCardMenuItem[]-Card menu items
onClick(item: AwesomeStatItem) => void-Per-item press handler

AwesomeStatValueFormat

FieldTypeDefaultDescription
localestringdeviceIntl locale
style'number' | 'currency' | 'percent''number'Format style
currencystring'USD'Currency code
currencyDisplay'symbol' | 'code' | 'name' | 'narrowSymbol''symbol'Currency display
notation'standard' | 'scientific' | 'engineering' | 'compact'-Notation
minimumFractionDigitsnumber-Minimum fraction digits
maximumFractionDigitsnumber-Maximum fraction digits
signDisplayIntl.NumberFormatOptions['signDisplay']-Sign display
percentScale'whole' | 'fraction''whole''whole' treats 12 as 12%

AwesomeStatComparison

FieldTypeDefaultDescription
previousValuenumber-Previous period value
period'yesterday' | 'last-week' | 'last-month' | 'last-quarter' | 'last-year'-Comparison period
positiveDirection'up' | 'down''up'Which direction is good

AwesomeStatMiniChart

FieldTypeDefaultDescription
type'sparkline' | 'bar' | 'area'-Chart type (SVG paths)
datanumber[] | Array<Record<string, unknown>>-Values or rows
dataKeystring'value'Row key to plot
position'right' | 'bottom''right'Beside the value (96×80) or full-width below (96 high)

AwesomeStatsCardMenuItem

FieldTypeDefaultDescription
idstring-Identifier
labelReactNode-Row label (flattened to text)
iconReactNode-DocyrusIcon string; non-string icons are omitted
shortcutReactNode-Web parity; not shown
disabledboolean-Disabled row
variant'default' | 'destructive''default'Destructive styling
onSelect(item: AwesomeStatItem) => void-Called with the stat

AwesomeStatsAwesomeCardProps

FieldTypeDefaultDescription
patternbooleantruePattern background
patternStylePatternStyle'stripes'Pattern motif
collapsiblebooleanfalseCollapsible card
collapsedbooleanfalseInitially collapsed
chevronPosition'left' | 'right''left'Chevron position
classNamestring-Card classes

Type Exports

TypeDescription
AwesomeStatsPropsComponent props
AwesomeStatItemStat item
AwesomeStatValueFormatNumber formatting options
AwesomeStatComparisonComparison configuration
AwesomeStatComparisonPeriodComparison period union
AwesomeStatMiniChartMini chart configuration
AwesomeStatsCardMenuItemCard menu item
AwesomeStatsAwesomeCardPropsAwesomeCard options
AwesomeStatsClickTarget'title' | 'card'
AwesomeStatsLayoutLayout union

On this page