Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
b700b65
feat(ui): add Scorecard KPI card component
goodbounties-nanoclaw-agent[bot] Aug 5, 2026
53b9ae8
Address review feedback on Scorecard: rounding, testID, a11y, peer dep
goodbounties-nanoclaw-agent[bot] Aug 5, 2026
9e0b153
Fix whole-number formatting, card centering, and apply Scorecard styl…
goodbounties-nanoclaw-agent[bot] Aug 5, 2026
053723d
feat(ui): extract shared resolveThemeColor util
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
afdb874
Add PieDonutChart analytics component (#143)
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
f88bf8d
Add BarChart analytics component (#144)
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
9441c03
Add LineAreaChart component (#145)
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
e6d2849
Design QA fixes across Pie/Donut, Bar, Line/Area charts (#143-145)
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
81e21f0
Fix PieDonutChart center-value truncation (inverted maxWidth formula)
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
e2434a9
Add DataTable component (#146): 4th and final analytics component
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
582432d
fix(ui): format whole-number K/M/B/T compact values without decimals
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
51c43cc
test(design-system): capture full-page Storybook screenshots, not cro…
goodbounties-nanoclaw-agent[bot] Aug 7, 2026
953a7d1
fix(examples/react-web): add missing react-native-svg shim exports
goodbounties-nanoclaw-agent[bot] Aug 10, 2026
9bbb203
fix(examples/html, apps): add missing react-native-svg shim exports
goodbounties-nanoclaw-agent[bot] Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions apps/ai-credits-web/src/reactNativeSvgWeb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,37 @@ type SvgProps = React.SVGProps<SVGSVGElement> & {
accessibilityRole?: string
}

type SvgGroupProps = React.SVGProps<SVGGElement> & {
rotation?: string | number
origin?: string
}

type SvgTextProps = React.SVGProps<SVGTextElement> & {
rotation?: string | number
origin?: string
}

export function Svg({ accessibilityRole, ...props }: SvgProps) {
void accessibilityRole
return <svg {...props} />
}

/** Mirrors react-native-svg's G transform props with a standard SVG transform; used by PieDonutChart/BarChart/LineAreaChart to group arc/bar/line elements. */
export function G({ rotation, origin, transform, ...props }: SvgGroupProps) {
const rotationTransform = rotation ? `rotate(${rotation} ${origin ?? ''})`.trim() : undefined
const combinedTransform = [transform, rotationTransform].filter(Boolean).join(' ')

return <g {...props} transform={combinedTransform || undefined} />
}

/** Mirrors react-native-svg's Text rotation/origin props with a standard SVG transform; used by BarChart/LineAreaChart for in-chart axis/tick/value labels. */
export function Text({ rotation, origin, transform, ...props }: SvgTextProps) {
const rotationTransform = rotation ? `rotate(${rotation} ${origin ?? ''})`.trim() : undefined
const combinedTransform = [transform, rotationTransform].filter(Boolean).join(' ')

return <text {...props} transform={combinedTransform || undefined} />
}

export function Path(props: React.SVGProps<SVGPathElement>) {
return <path {...props} />
}
Expand Down Expand Up @@ -37,4 +63,21 @@ export function Ellipse(props: React.SVGProps<SVGEllipseElement>) {
return <ellipse {...props} />
}

/**
* Defs/LinearGradient/Stop passthroughs — used by LineAreaChart's area-fill
* gradients. Plain SVG already understands these tags natively, so (unlike
* G/Text above) no react-native-specific prop translation is needed.
*/
export function Defs(props: React.SVGProps<SVGDefsElement>) {
return <defs {...props} />
}

export function LinearGradient(props: React.SVGProps<SVGLinearGradientElement>) {
return <linearGradient {...props} />
}

export function Stop(props: React.SVGProps<SVGStopElement>) {
return <stop {...props} />
}

export default Svg
43 changes: 43 additions & 0 deletions apps/superfluid-campaign-web/src/reactNativeSvgWeb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,37 @@ type SvgProps = React.SVGProps<SVGSVGElement> & {
accessibilityRole?: string
}

type SvgGroupProps = React.SVGProps<SVGGElement> & {
rotation?: string | number
origin?: string
}

type SvgTextProps = React.SVGProps<SVGTextElement> & {
rotation?: string | number
origin?: string
}

export function Svg({ accessibilityRole, ...props }: SvgProps) {
void accessibilityRole
return <svg {...props} />
}

/** Mirrors react-native-svg's G transform props with a standard SVG transform; used by PieDonutChart/BarChart/LineAreaChart to group arc/bar/line elements. */
export function G({ rotation, origin, transform, ...props }: SvgGroupProps) {
const rotationTransform = rotation ? `rotate(${rotation} ${origin ?? ''})`.trim() : undefined
const combinedTransform = [transform, rotationTransform].filter(Boolean).join(' ')

return <g {...props} transform={combinedTransform || undefined} />
}

/** Mirrors react-native-svg's Text rotation/origin props with a standard SVG transform; used by BarChart/LineAreaChart for in-chart axis/tick/value labels. */
export function Text({ rotation, origin, transform, ...props }: SvgTextProps) {
const rotationTransform = rotation ? `rotate(${rotation} ${origin ?? ''})`.trim() : undefined
const combinedTransform = [transform, rotationTransform].filter(Boolean).join(' ')

return <text {...props} transform={combinedTransform || undefined} />
}

export function Path(props: React.SVGProps<SVGPathElement>) {
return <path {...props} />
}
Expand Down Expand Up @@ -37,4 +63,21 @@ export function Ellipse(props: React.SVGProps<SVGEllipseElement>) {
return <ellipse {...props} />
}

/**
* Defs/LinearGradient/Stop passthroughs — used by LineAreaChart's area-fill
* gradients. Plain SVG already understands these tags natively, so (unlike
* G/Text above) no react-native-specific prop translation is needed.
*/
export function Defs(props: React.SVGProps<SVGDefsElement>) {
return <defs {...props} />
}

export function LinearGradient(props: React.SVGProps<SVGLinearGradientElement>) {
return <linearGradient {...props} />
}

export function Stop(props: React.SVGProps<SVGStopElement>) {
return <stop {...props} />
}

export default Svg
23 changes: 23 additions & 0 deletions examples/html/src/shims/reactNativeSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ type SvgCircleProps = React.SVGProps<SVGCircleElement> & {
onPress?: () => void
}

type SvgTextProps = React.SVGProps<SVGTextElement> & {
rotation?: string | number
origin?: string
}

/** Web implementation for the react-native-svg primitives used by GoodWidget dependencies. */
function Svg({ accessibilityRole: _accessibilityRole, ...props }: SvgElementProps) {
return <svg {...props} />
Expand All @@ -31,6 +36,14 @@ export function Circle({ onPress, ...props }: SvgCircleProps) {
return <circle {...props} onClick={onPress} />
}

/** Mirrors react-native-svg's Text rotation/origin props with a standard SVG transform; used by BarChart/LineAreaChart for in-chart axis/tick/value labels. */
export function Text({ rotation, origin, transform, ...props }: SvgTextProps) {
const rotationTransform = rotation ? `rotate(${rotation} ${origin ?? ''})`.trim() : undefined
const combinedTransform = [transform, rotationTransform].filter(Boolean).join(' ')

return <text {...props} transform={combinedTransform || undefined} />
}

/** Static SVG primitives used by @tamagui/lucide-icons need no prop translation. */
function createPassthroughSvgPrimitive(tag: string) {
return function SvgPrimitive(props: React.SVGProps<SVGElement>) {
Expand All @@ -45,5 +58,15 @@ export const Polygon = createPassthroughSvgPrimitive('polygon')
export const Polyline = createPassthroughSvgPrimitive('polyline')
export const Ellipse = createPassthroughSvgPrimitive('ellipse')

/**
* Defs/LinearGradient/Stop passthroughs — used by LineAreaChart's area-fill
* gradients. Plain SVG already understands these tags natively via
* React.createElement, so (unlike Circle/G/Text above) no react-native-specific
* prop translation is needed.
*/
export const Defs = createPassthroughSvgPrimitive('defs')
export const LinearGradient = createPassthroughSvgPrimitive('linearGradient')
export const Stop = createPassthroughSvgPrimitive('stop')

export { Svg }
export default Svg
23 changes: 23 additions & 0 deletions examples/react-web/src/shims/reactNativeSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ type SvgCircleProps = React.SVGProps<SVGCircleElement> & {
onPress?: () => void
}

type SvgTextProps = React.SVGProps<SVGTextElement> & {
rotation?: string | number
origin?: string
}

/** Web implementation for the react-native-svg primitives used by GoodWidget dependencies. */
function Svg({ accessibilityRole: _accessibilityRole, ...props }: SvgElementProps) {
return <svg {...props} />
Expand All @@ -31,6 +36,14 @@ export function Circle({ onPress, ...props }: SvgCircleProps) {
return <circle {...props} onClick={onPress} />
}

/** Mirrors react-native-svg's Text rotation/origin props with a standard SVG transform; used by BarChart/LineAreaChart for in-chart axis/tick/value labels. */
export function Text({ rotation, origin, transform, ...props }: SvgTextProps) {
const rotationTransform = rotation ? `rotate(${rotation} ${origin ?? ''})`.trim() : undefined
const combinedTransform = [transform, rotationTransform].filter(Boolean).join(' ')

return <text {...props} transform={combinedTransform || undefined} />
}

/** Static SVG primitives used by @tamagui/lucide-icons need no prop translation. */
function createPassthroughSvgPrimitive(tag: string) {
return function SvgPrimitive(props: React.SVGProps<SVGElement>) {
Expand All @@ -45,5 +58,15 @@ export const Polygon = createPassthroughSvgPrimitive('polygon')
export const Polyline = createPassthroughSvgPrimitive('polyline')
export const Ellipse = createPassthroughSvgPrimitive('ellipse')

/**
* Defs/LinearGradient/Stop passthroughs — used by LineAreaChart's area-fill
* gradients. Plain SVG already understands these tags natively via
* React.createElement, so (unlike Circle/G/Text above) no react-native-specific
* prop translation is needed.
*/
export const Defs = createPassthroughSvgPrimitive('defs')
export const LinearGradient = createPassthroughSvgPrimitive('linearGradient')
export const Stop = createPassthroughSvgPrimitive('stop')

export { Svg }
export default Svg
23 changes: 23 additions & 0 deletions examples/storybook/src/shims/reactNativeSvg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ type SvgCircleProps = React.SVGProps<SVGCircleElement> & {
onPress?: () => void
}

type SvgTextProps = React.SVGProps<SVGTextElement> & {
rotation?: string | number
origin?: string
}

/** Storybook web shim for the react-native-svg primitives used by the donut chart and @tamagui/lucide-icons. */
function Svg({ accessibilityRole: _accessibilityRole, ...props }: SvgElementProps) {
return <svg {...props} />
Expand All @@ -31,6 +36,14 @@ export function Circle({ onPress, ...props }: SvgCircleProps) {
return <circle {...props} onClick={onPress} />
}

/** Mirrors react-native-svg's Text rotation/origin props with a standard SVG transform; used by BarChart for in-chart axis/tick/value labels. */
export function Text({ rotation, origin, transform, ...props }: SvgTextProps) {
const rotationTransform = rotation ? `rotate(${rotation} ${origin ?? ''})`.trim() : undefined
const combinedTransform = [transform, rotationTransform].filter(Boolean).join(' ')
Comment on lines +41 to +42

return <text {...props} transform={combinedTransform || undefined} />
}

/**
* @tamagui/lucide-icons' generated icon components import Path/Line/Rect/Polygon/
* Polyline/Ellipse by name from react-native-svg purely to render static shapes —
Expand All @@ -50,5 +63,15 @@ export const Polygon = createPassthroughSvgPrimitive('polygon')
export const Polyline = createPassthroughSvgPrimitive('polyline')
export const Ellipse = createPassthroughSvgPrimitive('ellipse')

/**
* Defs/LinearGradient/Stop passthroughs — used by LineAreaChart's area-fill
* gradients. Plain SVG already understands these tags natively via
* React.createElement, so (unlike Circle/G/Text above) no react-native-specific
* prop translation is needed.
*/
export const Defs = createPassthroughSvgPrimitive('defs')
export const LinearGradient = createPassthroughSvgPrimitive('linearGradient')
export const Stop = createPassthroughSvgPrimitive('stop')

export { Svg }
export default Svg
119 changes: 119 additions & 0 deletions examples/storybook/src/stories/design-system/BarChart.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* BarChart — discrete categorical comparison via bar length, vertical or
* horizontal. Mock datasets are the 5 fixtures from #144.
*/
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import { BarChart, XStack, YStack } from '@goodwidget/ui'
import { withDefaultPreset } from '../helpers/withDefaultPreset'

const chains = [
{ category: 'Celo', value: 45200 },
{ category: 'Fuse', value: 32100 },
{ category: 'Ethereum', value: 8500 },
]

const houses = [
{ category: 'House of Alignment', value: 450000 },
{ category: 'House of Innovation', value: 320000 },
{ category: 'House of Community', value: 180000 },
]

const single = [{ category: 'Total Claims', value: 85800 }]

const empty: Array<{ category: string; value: number }> = []

/** 150 categories — bars become sub-pixel-narrow; must clip/degrade gracefully, not crash. */
const stress = Array.from({ length: 150 }, (_, i) => ({
category: `Wallet ${String(i + 1).padStart(3, '0')}`,
value: Math.floor(Math.random() * 100000),
}))

const meta: Meta<typeof BarChart> = {
title: 'Design System/Primitives/BarChart',
component: BarChart,
tags: ['autodocs', 'showcase'],
parameters: { layout: 'padded' },
decorators: [withDefaultPreset],
argTypes: {
layout: {
control: 'select',
options: ['vertical', 'horizontal'],
description: 'Bar orientation',
},
showGrid: { control: 'boolean' },
showValueLabels: { control: 'boolean' },
barCornerRadius: { control: 'number' },
variant: {
control: 'select',
options: ['bare', 'card'],
description: 'Chrome-less vs. card-wrapped face',
},
},
}
export default meta
type Story = StoryObj<typeof BarChart>

/** Claims-by-chain rendered vertical (bare + card) and horizontal, with value labels. */
export const Default: Story = {
render: () => (
<YStack testID="BarChart-default" data-testid="BarChart-default" gap="$6">
<XStack flexWrap="wrap" gap="$5">
<BarChart
data={chains}
title="Claims by Chain"
width={320}
testID="BarChart-chains-vertical-bare"
showValueLabels
/>
<BarChart
data={chains}
title="Claims by Chain"
width={320}
variant="card"
testID="BarChart-chains-vertical-card"
showValueLabels
/>
</XStack>
</YStack>
),
}

/** Long category labels — the case horizontal layout exists for. Wider left padding gives labels room. */
export const HorizontalLongLabels: Story = {
render: () => (
<BarChart
data={houses}
title="Funding by House"
layout="horizontal"
width={480}
height={220}
padding={{ top: 16, right: 16, bottom: 32, left: 140 }}
testID="BarChart-houses-horizontal"
showValueLabels
/>
),
}

export const EmptyState: Story = {
render: () => <BarChart data={empty} title="No Data Yet" width={320} testID="BarChart-empty" />,
}

export const SinglePoint: Story = {
render: () => <BarChart data={single} title="Total Claims" width={320} testID="BarChart-single" />,
}

/** 150 categories, maxSlices has no equivalent here — exercises sub-pixel bar clipping and label-truncation safety. */
export const StressTest: Story = {
render: () => <BarChart data={stress} title="Wallet Activity" width={800} showGrid={false} testID="BarChart-stress" />,
}

/** Controllable instance — edit args in the Controls panel. */
export const Controllable: Story = {
args: {
data: chains,
title: 'Claims by Chain',
width: 320,
variant: 'card',
},
}
Loading
Loading