Feature summary
Add a DataTable component to packages/ui for exact value display with typed columns,
formatting, and sorting — the complement to the visual charts, providing precise value
lookup. Pure Tamagui layout (no SVG). Fourth and final of the remaining analytics chart
components completing GoodWidget's analytics visualization layer (Scorecard already
shipped in PR #142).
Problem or opportunity
Charts are fast at showing patterns but bad at exact lookups ("what was Celo's claim count
on Tuesday?"). GoodWidget has no reusable, themeable table primitive for multi-attribute
comparisons (address + volume + tx count + date) or sorted leaderboards — each widget
currently improvises its own layout for this.
Proposed solution
Follow Scorecard.tsx's patterns (createComponent, useTheme, golden-ratio spacing,
formatMetricValue). Aesthetic reference: the StatCell pattern / stat grid in
ai-credits-widget's CreditsManagementCard.tsx.
Props / API surface (MVP)
| Prop |
Type |
Required |
Default |
Description |
| data |
Array<Record<string, unknown>> |
Yes |
- |
Row data |
| columns |
Array<ColumnDef> |
Yes |
- |
Column configuration |
| title |
string |
No |
undefined |
Table heading |
| striped |
boolean |
No |
true |
Alternating row backgrounds |
| compact |
boolean |
No |
false |
Reduced row padding |
| stickyHeader |
boolean |
No |
true |
Fixed header on scroll |
| maxHeight |
number |
No |
undefined |
Max height before scroll |
| defaultSort |
{ key, direction } |
No |
undefined |
Initial sort |
| onSort |
(key, direction) => void |
No |
undefined |
Sort callback |
| emptyMessage |
string |
No |
"No data" |
Empty state text |
| onRowPress |
(row, index) => void |
No |
undefined |
Row tap callback |
| variant |
'bare' | 'card' |
No |
'bare' |
Visual variant |
| testID |
string |
No |
undefined |
Testing identifier |
| accessibilityLabel |
string |
No |
auto-generated |
Screen reader description |
ColumnDef
| Field |
Type |
Required |
Default |
Description |
| key |
string |
Yes |
- |
Data field accessor |
| label |
string |
Yes |
- |
Header text |
| type |
'text' | 'number' | 'date' | 'currency' |
No |
'text' |
Formatting hint |
| align |
'left' | 'center' | 'right' |
No |
'center' |
Cell alignment |
| width |
number | string |
No |
'auto' |
Column width |
| minWidth |
number |
No |
60 |
Minimum width |
| formatter |
(value, row) => string |
No |
type-based |
Custom formatting |
| sortable |
boolean |
No |
false |
Enable column sorting |
| truncate |
boolean |
No |
true |
Truncate with ellipsis |
Behavioral rules
- Default column alignment is 'center'; explicit
align overrides.
- Default formatters by type — text:
String(value) (truncate if needed); number: formatMetricValue (integers show no decimal); date: displayed as-is (consumer pre-formats); currency: formatMetricValue with consumer-provided prefix.
- Sorting: client-side when
sortable=true; tapping a header cycles ascending → descending → clear, with a unicode arrow indicator; defaultSort applies on first render.
- Striped rows: even rows
$backgroundHover, odd transparent; header fontWeight: 700.
- Sticky header stays fixed on vertical scroll.
- Horizontal overflow enables a horizontal
ScrollView when columns exceed container width.
- Empty state: header row + single merged cell with
emptyMessage.
- Null/undefined cell values render as
"--".
- Rows are tappable (with press feedback) when
onRowPress is provided.
- Compact mode reduces padding and font size.
Shared constraints (apply to all 4 remaining chart components — see Pie/Donut issue #143 for full text): Tamagui + useTheme() for layout/color (no react-native-svg needed here — table is pure layout), createComponent(), Scorecard spacing/type-scale constants, no animation, no new dependencies.
Acceptance criteria
Additional context
DO NOT:
- Use SVG — pure Tamagui layout (
YStack, XStack, Text, ScrollView)
- Add
FlatList/VirtualizedList
- Use
Icon.tsx for sort arrows — use unicode characters
- Add pagination
- Modify
Card.ts, Text.ts, Icon.tsx, theme.ts, presets.ts, config.ts, or governance-widget
Scope boundary — only create/modify:
packages/ui/src/components/DataTable.tsx
packages/ui/src/index.ts (add export under // Analytics)
examples/storybook/src/stories/design-system/DataTable.stories.tsx
tests/design-system/smoke.spec.ts (add cases)
Mock data: multi-column top-wallets table, compact metrics summary, empty, single
column, and a 150-row stress test with maxHeight/scroll — full snippets in the spec
(link below).
References:
Create the plan
Based on the above description create an execution plan in a new sub-issue and preserve the original issue content unchanged.
Do not execute the plan until given instructions to do so.
Sub-issue requirements:
- Title format:
[DRAFT][PLAN] <what issue is being planned>
- Type:
Task
- Description must start with:
<sub-issue title>
- Link the sub-issue back to this parent issue.
Instructions when creating the plan:
- Map relevant files that are too be used as reference from all repos mentioned
- import existing @GoodDollar packages
- Map new components that should be created. Assess when a new component should be created in the new savings widget package or made part of the reusable packages/ui
Plan specification required sections:
- Required states, flows, and behaviors
- Execution plan
- acceptance criteria
- human-reviewer checklist
Feature summary
Add a
DataTablecomponent topackages/uifor exact value display with typed columns,formatting, and sorting — the complement to the visual charts, providing precise value
lookup. Pure Tamagui layout (no SVG). Fourth and final of the remaining analytics chart
components completing GoodWidget's analytics visualization layer (Scorecard already
shipped in PR #142).
Problem or opportunity
Charts are fast at showing patterns but bad at exact lookups ("what was Celo's claim count
on Tuesday?"). GoodWidget has no reusable, themeable table primitive for multi-attribute
comparisons (address + volume + tx count + date) or sorted leaderboards — each widget
currently improvises its own layout for this.
Proposed solution
Follow
Scorecard.tsx's patterns (createComponent,useTheme, golden-ratio spacing,formatMetricValue). Aesthetic reference: theStatCellpattern / stat grid inai-credits-widget'sCreditsManagementCard.tsx.Props / API surface (MVP)
Array<Record<string, unknown>>Array<ColumnDef>{ key, direction }(key, direction) => void(row, index) => voidColumnDef
(value, row) => stringBehavioral rules
alignoverrides.String(value)(truncate if needed); number:formatMetricValue(integers show no decimal); date: displayed as-is (consumer pre-formats); currency:formatMetricValuewith consumer-provided prefix.sortable=true; tapping a header cycles ascending → descending → clear, with a unicode arrow indicator;defaultSortapplies on first render.$backgroundHover, odd transparent; headerfontWeight: 700.ScrollViewwhen columns exceed container width.emptyMessage."--".onRowPressis provided.Shared constraints (apply to all 4 remaining chart components — see Pie/Donut issue #143 for full text): Tamagui +
useTheme()for layout/color (noreact-native-svgneeded here — table is pure layout),createComponent(), Scorecard spacing/type-scale constants, no animation, no new dependencies.Acceptance criteria
formatMetricValueapplied to number/currency columnsemptyMessagevariant="card"workstestID+data-testidpresentonRowPressfires correctlyAdditional context
DO NOT:
YStack,XStack,Text,ScrollView)FlatList/VirtualizedListIcon.tsxfor sort arrows — use unicode charactersCard.ts,Text.ts,Icon.tsx,theme.ts,presets.ts,config.ts, orgovernance-widgetScope boundary — only create/modify:
packages/ui/src/components/DataTable.tsxpackages/ui/src/index.ts(add export under// Analytics)examples/storybook/src/stories/design-system/DataTable.stories.tsxtests/design-system/smoke.spec.ts(add cases)Mock data: multi-column top-wallets table, compact metrics summary, empty, single
column, and a 150-row stress test with
maxHeight/scroll — full snippets in the spec(link below).
References:
CreditsManagementCard.tsx(StatCell/stat-grid pattern) inai-credits-widgetfeat/analytics-components)Create the plan
Based on the above description create an execution plan in a new sub-issue and preserve the original issue content unchanged.
Do not execute the plan until given instructions to do so.
Sub-issue requirements:
[DRAFT][PLAN] <what issue is being planned>Task<sub-issue title>Instructions when creating the plan:
Plan specification required sections: