Graph Widgets
12 blocksGraph Widget 01
Preview
npx nateui@latest add GraphWidgetRevenueComparisonDark
import Card from '@/components/ui/Card'
import Tag from '@/components/ui/Tag'
import { BarChart } from '@/components/composites/Chart'
const monthlyRevenue = [
{ month: 'Jan', currentPeriod: 32000, priorPeriod: 68000 },
{ month: 'Feb', currentPeriod: 36000, priorPeriod: 71000 },
{ month: 'Mar', currentPeriod: 70000, priorPeriod: 82000 },
{ month: 'Apr', currentPeriod: 48000, priorPeriod: 57000 },
{ month: 'May', currentPeriod: 83000, priorPeriod: 59000 },
{ month: 'Jun', currentPeriod: 88000, priorPeriod: 102000 },
{ month: 'Jul', currentPeriod: 50000, priorPeriod: 88000 },
{ month: 'Aug', currentPeriod: 71000, priorPeriod: 82000 },
{ month: 'Sep', currentPeriod: 70000, priorPeriod: 77000 },
{ month: 'Oct', currentPeriod: 64000, priorPeriod: 72000 },
{ month: 'Nov', currentPeriod: 103000, priorPeriod: 61000 },
{ month: 'Dec', currentPeriod: 50000, priorPeriod: 65000 },
]
const revenueTicks = [0, 25000, 50000, 75000, 100000, 125000]
const formatRevenueTick = (value: number) => {
if (value === 0) {
return '$0'
}
return `$${value / 1000}K`
}
export default function GraphWidgetRevenueComparison() {
return (
<section
aria-labelledby="graph-widget-revenue-comparison-title"
className="w-full"
>
<Card className="w-full">
<div className="space-y-8">
<header>
<h5
id="graph-widget-revenue-comparison-title"
className="text-lg font-semibold text-foreground"
>
Revenue overview
</h5>
<p className="text-muted-foreground">
Monthly revenue across two comparable periods
</p>
</header>
<dl className="grid grid-cols-2 gap-4">
<div className="min-w-0">
<dt className="flex items-center gap-2 text-muted-foreground">
<span
aria-hidden="true"
className="h-2 w-2 rounded-sm"
style={{
backgroundColor:
'var(--color-chart-1)',
}}
/>
Current period
</dt>
<dd className="mt-2 flex flex-wrap items-center gap-2 text-xl font-semibold tabular-nums text-foreground">
$0.86M
<Tag
size="md"
className="border-0 bg-success-soft text-success"
>
+17.8%
</Tag>
</dd>
</div>
<div className="min-w-0">
<dt className="flex items-center gap-2 text-muted-foreground">
<span
aria-hidden="true"
className="h-2 w-2 rounded-sm"
style={{
backgroundColor:
'var(--color-chart-2)',
}}
/>
Prior period
</dt>
<dd className="mt-2 text-xl font-semibold tabular-nums text-foreground">
$0.73M
</dd>
</div>
</dl>
<div className="min-w-0 overflow-hidden">
<BarChart
data={monthlyRevenue}
height={232}
xAxisConfig={{
dataKey: 'month',
interval: 1,
tickMargin: 12,
}}
yAxisConfig={{
hide: false,
domain: [0, 125000],
ticks: revenueTicks,
width: 48,
tickLine: false,
axisLine: false,
tickFormatter: formatRevenueTick,
}}
cartesianGridConfig={{
vertical: false,
}}
barConfig={[
{
dataKey: 'currentPeriod',
name: 'Current period',
barSize: 12,
radius: 0,
isAnimationActive: false,
},
{
dataKey: 'priorPeriod',
name: 'Prior period',
barSize: 12,
radius: 0,
isAnimationActive: false,
},
]}
chartHorizontalSpace={0}
chartVerticalSpace={0}
/>
</div>
</div>
</Card>
</section>
)
}
Graph Widget 02
Preview
npx nateui@latest add GraphWidgetMessageVolumeDark
import IconFrame from '@/components/composites/IconFrame'
import { LineChart } from '@/components/composites/Chart'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import Dropdown from '@/components/ui/Dropdown'
import Tag from '@/components/ui/Tag'
import {
PiArrowSquareOut,
PiChatText,
PiDownloadSimple,
PiDotsThreeVerticalBold,
} from 'react-icons/pi'
const messageActivity = [
{ day: 'Jan 01', current: 16000, previous: 11000 },
{ day: 'Jan 02', current: 13000, previous: 17000 },
{ day: 'Jan 03', current: 26000, previous: 8000 },
{ day: 'Jan 04', current: 12000, previous: 24000 },
{ day: 'Jan 05', current: 38000, previous: 22000 },
{ day: 'Jan 06', current: 23000, previous: 18000 },
{ day: 'Jan 07', current: 26000, previous: 12000 },
{ day: 'Jan 08', current: 17000, previous: 15000 },
{ day: 'Jan 09', current: 24000, previous: 5000 },
{ day: 'Jan 10', current: 11000, previous: 8000 },
{ day: 'Jan 11', current: 38000, previous: 20000 },
{ day: 'Jan 12', current: 34000, previous: 30000 },
{ day: 'Jan 13', current: 39000, previous: 22000 },
{ day: 'Jan 14', current: 28000, previous: 17000 },
{ day: 'Jan 15', current: 30000, previous: 5000 },
]
const volumeTicks = [0, 10000, 20000, 30000, 40000, 50000]
const formatVolumeTick = (value: number) => `${value / 1000}k`
export default function GraphWidgetMessageVolume() {
return (
<section
aria-labelledby="graph-widget-message-volume-title"
className="w-full"
>
<Card className="w-full">
<div className="space-y-8">
<header className="flex flex-col justify-between gap-4 sm:flex-row sm:items-start">
<div className="flex min-w-0 items-start gap-3">
<IconFrame
variant="layered"
size={28}
className="shrink-0 text-primary"
>
<PiChatText aria-hidden="true" className="text-base" />
</IconFrame>
<div className="min-w-0">
<h5
id="graph-widget-message-volume-title"
className="font-semibold text-foreground"
>
Message volume
</h5>
<p className="mt-1 text-sm text-muted-foreground">
Daily message activity across a selected period
</p>
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
<Dropdown
placement="bottom-end"
renderTitle={
<Button
variant="ghost"
size="sm"
icon={<PiDotsThreeVerticalBold />}
aria-label="Message volume options"
/>
}
>
<Dropdown.Item>
<span className="inline-flex items-center gap-2">
<PiDownloadSimple className="text-base" />
Export data
</span>
</Dropdown.Item>
<Dropdown.Item>
<span className="inline-flex items-center gap-2">
<PiArrowSquareOut className="text-base" />
View details
</span>
</Dropdown.Item>
</Dropdown>
</div>
</header>
<div className="flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
<dl>
<dt className="sr-only">Messages sent</dt>
<dd className="flex flex-wrap items-center gap-2 text-2xl font-semibold tabular-nums text-foreground">
227,563
<Tag
size="md"
className="border-0 bg-success-soft text-success"
>
+1,246
</Tag>
<span className="text-sm font-normal text-muted-foreground">
vs same period
</span>
</dd>
</dl>
<div
aria-label="Chart series"
className="flex items-center gap-4 text-sm text-muted-foreground"
>
<span className="flex items-center gap-2">
<span
aria-hidden="true"
className="h-2 w-2 rounded-full"
style={{
backgroundColor: 'var(--color-chart-1)',
}}
/>
Current
</span>
<span className="flex items-center gap-2">
<span
aria-hidden="true"
className="h-2 w-2 rounded-full"
style={{
backgroundColor: 'var(--color-chart-7)',
}}
/>
Previous
</span>
</div>
</div>
<div className="min-w-0 overflow-hidden">
<LineChart
data={messageActivity}
xAxisConfig={{ dataKey: 'day' }}
yAxisConfig={{
hide: false,
domain: [0, 50000],
ticks: volumeTicks,
width: 42,
tickFormatter: formatVolumeTick,
}}
lineConfig={[
{ dataKey: 'current', name: 'Current' },
{
dataKey: 'previous',
name: 'Previous',
stroke: 'var(--color-chart-7)',
},
]}
/>
</div>
</div>
</Card>
</section>
)
}
Graph Widget 03
Preview
npx nateui@latest add GraphWidgetActivitySummaryDark
'use client'
import { useState } from 'react'
import { LineChart } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
import Switcher from '@/components/ui/Switcher'
import Tag from '@/components/ui/Tag'
import Tabs from '@/components/ui/Tabs'
const { TabList, TabNav } = Tabs
type ActivityMetric = 'requests' | 'processing' | 'cost' | 'outputs'
type ActivityPoint = {
day: string
current: number
baseline: number
}
const activityDataByMetric: Record<ActivityMetric, ActivityPoint[]> = {
requests: [
{ day: 'Mar 03', current: 58, baseline: 60 },
{ day: 'Mar 05', current: 61, baseline: 58 },
{ day: 'Mar 07', current: 60, baseline: 62 },
{ day: 'Mar 09', current: 65, baseline: 59 },
{ day: 'Mar 11', current: 63, baseline: 63 },
{ day: 'Mar 13', current: 67, baseline: 64 },
{ day: 'Mar 15', current: 65, baseline: 61 },
{ day: 'Mar 17', current: 64, baseline: 63 },
{ day: 'Mar 19', current: 62, baseline: 60 },
{ day: 'Mar 21', current: 70, baseline: 65 },
{ day: 'Mar 23', current: 66, baseline: 62 },
{ day: 'Mar 25', current: 68, baseline: 65 },
{ day: 'Mar 27', current: 62, baseline: 61 },
{ day: 'Mar 29', current: 64, baseline: 63 },
{ day: 'Mar 31', current: 60, baseline: 59 },
],
processing: [
{ day: 'Mar 03', current: 112, baseline: 115 },
{ day: 'Mar 05', current: 118, baseline: 111 },
{ day: 'Mar 07', current: 116, baseline: 118 },
{ day: 'Mar 09', current: 124, baseline: 114 },
{ day: 'Mar 11', current: 122, baseline: 120 },
{ day: 'Mar 13', current: 128, baseline: 123 },
{ day: 'Mar 15', current: 119, baseline: 117 },
{ day: 'Mar 17', current: 121, baseline: 121 },
{ day: 'Mar 19', current: 120, baseline: 116 },
{ day: 'Mar 21', current: 132, baseline: 125 },
{ day: 'Mar 23', current: 126, baseline: 120 },
{ day: 'Mar 25', current: 130, baseline: 124 },
{ day: 'Mar 27', current: 122, baseline: 118 },
{ day: 'Mar 29', current: 125, baseline: 121 },
{ day: 'Mar 31', current: 120, baseline: 115 },
],
cost: [
{ day: 'Mar 03', current: 42, baseline: 44 },
{ day: 'Mar 05', current: 41, baseline: 43 },
{ day: 'Mar 07', current: 43, baseline: 45 },
{ day: 'Mar 09', current: 42, baseline: 43 },
{ day: 'Mar 11', current: 45, baseline: 46 },
{ day: 'Mar 13', current: 44, baseline: 45 },
{ day: 'Mar 15', current: 43, baseline: 44 },
{ day: 'Mar 17', current: 44, baseline: 45 },
{ day: 'Mar 19', current: 42, baseline: 43 },
{ day: 'Mar 21', current: 46, baseline: 47 },
{ day: 'Mar 23', current: 45, baseline: 44 },
{ day: 'Mar 25', current: 47, baseline: 46 },
{ day: 'Mar 27', current: 44, baseline: 45 },
{ day: 'Mar 29', current: 45, baseline: 44 },
{ day: 'Mar 31', current: 43, baseline: 44 },
],
outputs: [
{ day: 'Mar 03', current: 18, baseline: 20 },
{ day: 'Mar 05', current: 21, baseline: 19 },
{ day: 'Mar 07', current: 20, baseline: 22 },
{ day: 'Mar 09', current: 24, baseline: 20 },
{ day: 'Mar 11', current: 23, baseline: 23 },
{ day: 'Mar 13', current: 26, baseline: 24 },
{ day: 'Mar 15', current: 22, baseline: 21 },
{ day: 'Mar 17', current: 24, baseline: 23 },
{ day: 'Mar 19', current: 23, baseline: 21 },
{ day: 'Mar 21', current: 28, baseline: 25 },
{ day: 'Mar 23', current: 25, baseline: 23 },
{ day: 'Mar 25', current: 27, baseline: 25 },
{ day: 'Mar 27', current: 22, baseline: 22 },
{ day: 'Mar 29', current: 24, baseline: 23 },
{ day: 'Mar 31', current: 21, baseline: 20 },
],
}
const summaryMetrics: Array<{
key: ActivityMetric
label: string
value: string
change: string
description: string
}> = [
{
key: 'requests',
label: 'Requests',
value: '4,820',
change: '+6.4%',
description: 'vs prior run',
},
{
key: 'processing',
label: 'Processing',
value: '1.76M',
change: '+2.1%',
description: '1.12M queued · 640K completed',
},
{
key: 'cost',
label: 'Run cost',
value: '$9.80',
change: '-1.3%',
description: 'vs prior run',
},
{
key: 'outputs',
label: 'Outputs',
value: '128',
change: '+4.8%',
description: 'vs prior run',
},
]
export default function GraphWidgetActivitySummary() {
const [activeMetric, setActiveMetric] = useState<ActivityMetric>(
summaryMetrics[0].key,
)
const [showBaseline, setShowBaseline] = useState(true)
return (
<section
aria-labelledby="graph-widget-activity-summary-title"
className="w-full"
>
<Card className="w-full">
<div className="space-y-6">
<h5
id="graph-widget-activity-summary-title"
className="sr-only"
>
Activity summary
</h5>
<Tabs
variant="pill"
value={activeMetric}
onChange={(value) => {
const nextMetric = summaryMetrics.find(
(metric) => metric.key === value,
)
if (nextMetric) {
setActiveMetric(nextMetric.key)
}
}}
>
<TabList
className="grid w-full grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-4 lg:gap-4"
indicatorClass="bg-muted"
>
{summaryMetrics.map((metric, index) => (
<TabNav
key={metric.key}
value={metric.key}
className={
index !== summaryMetrics.length - 1
? "relative min-w-0 justify-start p-0 data-[state=active]:text-foreground lg:after:absolute lg:after:top-0 lg:after:-right-2 lg:after:h-full lg:after:w-px lg:after:bg-border lg:after:content-['']"
: 'min-w-0 justify-start p-0 data-[state=active]:text-foreground'
}
>
<span className="w-full space-y-1 px-4 py-4 text-start transition-colors">
<span className="block font-medium text-muted-foreground">
{metric.label}
</span>
<span className="flex flex-wrap items-center gap-2">
<span className="text-xl font-semibold tabular-nums">
{metric.value}
</span>
<Tag
size="md"
className="border-0 bg-success-soft text-success"
>
{metric.change}
</Tag>
</span>
<span className="block text-xs text-muted-foreground">
{metric.description}
</span>
</span>
</TabNav>
))}
</TabList>
</Tabs>
<div className="min-w-0 overflow-hidden">
<LineChart
data={activityDataByMetric[activeMetric]}
xAxisConfig={{ dataKey: 'day' }}
lineConfig={[
{
dataKey: 'current',
name: 'Current run',
type: 'linear' as const,
},
...(showBaseline
? [
{
dataKey: 'baseline',
name: 'Baseline',
type: 'linear' as const,
},
]
: []),
]}
/>
</div>
<div className="flex flex-col gap-4 border-t pt-4 sm:flex-row sm:items-center sm:justify-between">
<div
aria-label="Chart series"
className="flex flex-wrap items-center gap-4 text-xs text-muted-foreground"
>
<span className="flex items-center gap-2">
<span
aria-hidden="true"
className="h-0.5 w-3 rounded"
style={{
backgroundColor: 'var(--color-chart-1)',
}}
/>
Current run
</span>
{showBaseline ? (
<span className="flex items-center gap-2">
<span
aria-hidden="true"
className="h-0.5 w-3 rounded"
style={{
backgroundColor:
'var(--color-chart-2)',
}}
/>
Baseline
</span>
) : null}
</div>
<div className="flex items-center gap-2">
<span
id="graph-widget-activity-summary-baseline"
className="text-sm font-medium"
>
Include baseline
</span>
<Switcher
aria-labelledby="graph-widget-activity-summary-baseline"
checked={showBaseline}
onChange={setShowBaseline}
/>
</div>
</div>
</div>
</Card>
</section>
)
}
Graph Widget 04
Preview
npx nateui@latest add GraphWidgetRunActivityDark
import { BarChart } from '@/components/composites/Chart'
import IconFrame from '@/components/composites/IconFrame'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import Dropdown from '@/components/ui/Dropdown'
import Tooltip from '@/components/ui/Tooltip'
import classNames from '@/utils/classNames'
import {
PiCaretDown,
PiCheckCircle,
PiClockCountdown,
PiGauge,
PiInfo,
PiXCircle,
} from 'react-icons/pi'
type ActivityPoint = {
date: string
value: number
}
const runActivity: ActivityPoint[] = [
{ date: 'Jan 1', value: 148 },
{ date: 'Jan 10', value: 152 },
{ date: 'Jan 20', value: 160 },
{ date: 'Feb 1', value: 166 },
{ date: 'Feb 10', value: 172 },
{ date: 'Feb 20', value: 182 },
{ date: 'Mar 1', value: 200 },
{ date: 'Mar 10', value: 250 },
{ date: 'Mar 20', value: 300 },
{ date: 'Apr 1', value: 265 },
{ date: 'Apr 10', value: 240 },
{ date: 'Apr 20', value: 222 },
{ date: 'May 1', value: 214 },
{ date: 'May 10', value: 218 },
{ date: 'May 20', value: 224 },
{ date: 'Jun 1', value: 236 },
{ date: 'Jun 10', value: 244 },
{ date: 'Jun 20', value: 258 },
{ date: 'Jul 1', value: 285 },
{ date: 'Jul 10', value: 310 },
{ date: 'Jul 20', value: 330 },
{ date: 'Aug 1', value: 290 },
{ date: 'Aug 10', value: 260 },
{ date: 'Aug 20', value: 240 },
{ date: 'Sep 1', value: 228 },
{ date: 'Sep 10', value: 220 },
{ date: 'Sep 20', value: 212 },
{ date: 'Oct 1', value: 206 },
{ date: 'Oct 10', value: 204 },
{ date: 'Oct 20', value: 210 },
{ date: 'Nov 1', value: 216 },
{ date: 'Nov 10', value: 222 },
{ date: 'Nov 20', value: 214 },
{ date: 'Dec 1', value: 200 },
{ date: 'Dec 10', value: 192 },
{ date: 'Dec 20', value: 178 },
]
const formatMonthTick = (value: string) =>
value.endsWith(' 1') ? value.split(' ')[0] : ''
const footerStats: Array<{
label: string
value: string
delta: string
iconClass: string
icon: typeof PiCheckCircle
}> = [
{
label: 'Completed runs',
value: '3,248',
delta: '+5.2% vs last month',
iconClass: 'bg-palette-blue',
icon: PiCheckCircle,
},
{
label: 'Failed runs',
value: '86',
delta: '+3.5% vs last month',
iconClass: 'bg-palette-red',
icon: PiXCircle,
},
{
label: 'Error rate',
value: '2.4%',
delta: '+0.3% vs last month',
iconClass: 'bg-palette-orange',
icon: PiGauge,
},
{
label: 'Time saved',
value: '18.4 hrs',
delta: '-1.1% vs last month',
iconClass: 'bg-palette-emerald',
icon: PiClockCountdown,
},
]
export default function GraphWidgetRunActivity() {
return (
<section
aria-labelledby="graph-widget-run-activity-title"
className="w-full"
>
<Card className="w-full">
<div className="space-y-8">
<header className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex items-center gap-1.5">
<h5
id="graph-widget-run-activity-title"
className="text-lg font-semibold text-foreground"
>
Run activity
</h5>
<Tooltip title="Run volume across the year">
<PiInfo
aria-label="About this chart"
tabIndex={0}
className="text-base text-muted-foreground outline-none focus-visible:ring-2 focus-visible:ring-primary"
/>
</Tooltip>
</div>
<Dropdown
placement="bottom-end"
renderTitle={
<Button
variant="default"
icon={<PiCaretDown aria-hidden="true" />}
iconAlignment="end"
>
Runs
</Button>
}
>
<Dropdown.Item>
<span className="inline-flex items-center gap-2">
<PiCheckCircle className="text-base" />
Success rate
</span>
</Dropdown.Item>
<Dropdown.Item>
<span className="inline-flex items-center gap-2">
<PiXCircle className="text-base" />
Failed runs
</span>
</Dropdown.Item>
<Dropdown.Item>
<span className="inline-flex items-center gap-2">
<PiClockCountdown className="text-base" />
Time saved
</span>
</Dropdown.Item>
</Dropdown>
</header>
<div className="min-w-0 overflow-hidden">
<BarChart
data={runActivity}
height={240}
xAxisConfig={{
dataKey: 'date',
interval: 0,
tickFormatter: formatMonthTick,
}}
yAxisConfig={{
hide: false,
domain: [0, 400],
ticks: [100, 200, 300, 400],
width: 36,
}}
cartesianGridConfig={{ vertical: false, horizontal: false }}
barConfig={[
{
dataKey: 'value',
fill: 'var(--color-chart-1)',
background: {
fill: 'var(--color-muted)',
},
radius: [2, 2, 0, 0],
isAnimationActive: false,
},
]}
tooltipContentConfig={{
hideLabel: false,
valueFormatter: (value) => `${value} runs`,
}}
/>
</div>
<div className="flex flex-col divide-y border-t pt-4 sm:flex-row sm:divide-x sm:divide-y-0">
{footerStats.map((stat) => {
const Icon = stat.icon
return (
<div
key={stat.label}
className="min-w-0 flex-1 space-y-2 py-4 first:pt-0 sm:px-4 sm:py-0 sm:first:pl-0 sm:last:pr-0"
>
<div className="flex items-center gap-2">
<IconFrame
size={28}
className={classNames(
'border-0 text-white',
stat.iconClass,
)}
>
<Icon className="text-base" />
</IconFrame>
<span className="text-sm font-medium text-muted-foreground">
{stat.label}
</span>
</div>
<p className="text-xl font-semibold tabular-nums text-foreground">
{stat.value}
</p>
<p className="text-xs text-muted-foreground font-medium">
{stat.delta}
</p>
</div>
)
})}
</div>
</div>
</Card>
</section>
)
}
Graph Widget 05
Preview
npx nateui@latest add GraphWidgetPipelineFunnelDark
import { ChartContainer } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
import Tag from '@/components/ui/Tag'
import { Area, ComposedChart, XAxis } from 'recharts'
import {
PiCheckCircle,
PiHandshake,
PiMagnifyingGlass,
PiTrophy,
PiCaretDownFill,
PiCaretUpFill,
} from 'react-icons/pi'
type Stage = {
stage: string
description: string
count: number
growth?: number
trend?: 'up' | 'down'
icon: typeof PiMagnifyingGlass
}
const stages: Stage[] = [
{
stage: 'Prospecting',
description: 'New leads entering the pipeline.',
count: 1623,
growth: 210,
trend: 'down',
icon: PiMagnifyingGlass,
},
{
stage: 'Qualified',
description: 'Leads confirmed as sales-ready.',
count: 981,
growth: 127,
trend: 'up',
icon: PiCheckCircle,
},
{
stage: 'Negotiation',
description: 'Deals in active discussion.',
count: 590,
growth: 65,
trend: 'up',
icon: PiHandshake,
},
{
stage: 'Closed Won',
description: 'Deals successfully closed.',
count: 310,
growth: 42,
trend: 'down',
icon: PiTrophy,
},
]
const gradientStops = stages.map((_, index) => ({
offset: `${(index / (stages.length - 1)) * 100}%`,
opacity: 0.7 - (0.6 / (stages.length - 1)) * index,
}))
export default function GraphWidgetPipelineFunnel() {
return (
<section
aria-labelledby="graph-widget-pipeline-funnel-title"
className="w-full"
>
<Card className="flex w-full flex-col">
<div className="mb-4 flex items-center">
<h5
id="graph-widget-pipeline-funnel-title"
className="text-lg font-semibold text-foreground"
>
Pipeline funnel
</h5>
</div>
<div className="relative min-h-[200px] flex-1 overflow-hidden">
<div className="absolute top-0 left-0 z-10 h-full w-full">
<div
className="grid h-full divide-x"
style={{
gridTemplateColumns: `repeat(${stages.length}, minmax(0, 1fr))`,
}}
>
{stages.map((stageItem, index) => {
const Icon = stageItem.icon
return (
<div
key={stageItem.stage}
className="relative border-4 border-card"
>
<div className="space-y-1.5 px-4 pt-4">
<Icon
aria-hidden="true"
className="text-xl"
/>
<div className="text-sm font-medium text-foreground">
{stageItem.stage}
</div>
<div className="flex flex-wrap items-center gap-2">
<h4 className="text-xl font-semibold text-foreground">
{stageItem.count.toLocaleString()}
</h4>
<Tag className={
`border-0 gap-1 ${stageItem.trend === 'up' ? 'bg-success-soft text-success' : 'bg-warning-soft text-warning-foreground'}`
}>
{stageItem.trend === 'up' ? (
<PiCaretUpFill aria-hidden="true" />
) : (
<PiCaretDownFill aria-hidden="true" />
)}
{
stageItem.growth?.toLocaleString()
}
</Tag>
</div>
<p className="text-xs text-muted-foreground">
{stageItem.description}
</p>
</div>
{index !== stages.length - 1 && (
<div className="absolute top-0 -right-1.25 z-10 h-full w-0.25 bg-border" />
)}
</div>
)
})}
</div>
</div>
<ChartContainer className="pt-30" height={385}>
<ComposedChart data={stages}>
<defs>
<linearGradient
id="pipeline-funnel-gradient"
x1="0"
y1="0"
x2="1"
y2="0"
>
{gradientStops.map((stop) => (
<stop
key={stop.offset}
offset={stop.offset}
style={{
stopColor:
'var(--color-chart-1)',
stopOpacity: stop.opacity,
}}
/>
))}
</linearGradient>
</defs>
<XAxis dataKey="stage" hide />
<Area
type="monotone"
dataKey="count"
stroke="var(--color-chart-1)"
strokeWidth={2}
fill="url(#pipeline-funnel-gradient)"
fillOpacity={1}
isAnimationActive={false}
/>
</ComposedChart>
</ChartContainer>
</div>
</Card>
</section>
)
}
Graph Widget 06
Preview
npx nateui@latest add GraphWidgetBillingUsageDark
import { BarChart } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
import Divider from '@/components/composites/Divider'
const dailyTotals = [
11500, 17500, 17800, 18800, 17600, 17500, 17900, 16300, 16800, 16400,
18300, 14700, 14100, 9700, 9600, 11700, 12500, 17300, 16700, 16600,
18300, 9200, 9700, 13300, 14700, 14500, 15400, 15600, 17700, 16700,
18700,
]
const dailyUsage = dailyTotals.map((total, index) => {
const actual = Math.round(total * 0.6)
return {
date: `Aug ${String(index + 1).padStart(2, '0')}`,
actual,
forecast: total - actual,
}
})
const usageTicks = [0, 5000, 10000, 15000, 20000]
const formatUsageTick = (value: number) => `$${value.toLocaleString('en-US')}`
export default function GraphWidgetBillingUsage() {
return (
<section
aria-labelledby="graph-widget-billing-usage-title"
className="w-full"
>
<Card className="w-full">
<div className="space-y-4">
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between pb-4 border-b">
<div>
<h5
id="graph-widget-billing-usage-title"
className="text-lg font-semibold text-foreground"
>
Usage Billing
</h5>
<p className="text-sm text-muted-foreground">
Started Aug 1, 2026 (billed on the 28th)
</p>
</div>
<div className="flex items-start gap-12">
<div>
<p className="text-sm text-muted-foreground">
Actual
</p>
<p className="text-base font-semibold text-foreground">
$8,412.30
</p>
</div>
<Divider orientation="vertical" className="h-10" />
<div>
<p className="text-sm text-muted-foreground">
Forecasted
</p>
<p className="text-base font-semibold text-foreground">
$10,890.50
</p>
</div>
</div>
</div>
<div className="min-w-0 overflow-hidden">
<BarChart
data={dailyUsage}
height={280}
xAxisConfig={{
dataKey: 'date',
interval: 1,
tickMargin: 12,
}}
yAxisConfig={{
hide: false,
domain: [0, 20000],
ticks: usageTicks,
width: 40,
tickLine: false,
axisLine: false,
tickFormatter: formatUsageTick,
}}
barConfig={[
{
dataKey: 'actual',
name: 'Actual',
stackId: 'usage',
fill: 'var(--color-chart-1)',
barSize: 16,
radius: [0, 0, 0, 0],
isAnimationActive: false,
},
{
dataKey: 'forecast',
name: 'Forecasted',
stackId: 'usage',
fill: 'var(--color-chart-7)',
barSize: 16,
radius: [4, 4, 0, 0],
isAnimationActive: false,
},
]}
tooltipContentConfig={{
hideLabel: false,
valueFormatter: (value) =>
`$${Number(value).toLocaleString('en-US')}`,
}}
/>
</div>
</div>
</Card>
</section>
)
}
Graph Widget 07
Preview
npx nateui@latest add GraphWidgetTicketVolumeDark
import { BarChart } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
const monthlyTickets = [
{ month: 'Jan 23', resolved: 18, escalated: 0, reopened: 0 },
{ month: 'Feb 23', resolved: 26, escalated: 2, reopened: 1 },
{ month: 'Mar 23', resolved: 44, escalated: 5, reopened: 3 },
{ month: 'Apr 23', resolved: 24, escalated: 0, reopened: 0 },
{ month: 'May 23', resolved: 40, escalated: 4, reopened: 2 },
{ month: 'Jun 23', resolved: 27, escalated: 2, reopened: 0 },
{ month: 'Jul 23', resolved: 16, escalated: 0, reopened: 0 },
{ month: 'Aug 23', resolved: 39, escalated: 4, reopened: 2 },
{ month: 'Sep 23', resolved: 23, escalated: 3, reopened: 2 },
{ month: 'Oct 23', resolved: 20, escalated: 0, reopened: 0 },
{ month: 'Nov 23', resolved: 25, escalated: 0, reopened: 0 },
{ month: 'Dec 23', resolved: 44, escalated: 4, reopened: 2 },
]
const legend = [
{
label: 'Resolved',
value: monthlyTickets.reduce((sum, item) => sum + item.resolved, 0),
color: 'var(--color-chart-1)',
},
{
label: 'Escalated',
value: monthlyTickets.reduce((sum, item) => sum + item.escalated, 0),
color: 'var(--color-chart-2)',
},
{
label: 'Reopened',
value: monthlyTickets.reduce((sum, item) => sum + item.reopened, 0),
color: 'var(--color-chart-8)',
},
]
export default function GraphWidgetTicketVolume() {
return (
<section
aria-labelledby="graph-widget-ticket-volume-title"
className="w-full"
>
<Card className="w-full">
<div className="space-y-6">
<h5
id="graph-widget-ticket-volume-title"
className="text-lg font-semibold text-foreground"
>
Support tickets
</h5>
<div className="min-w-0 overflow-hidden">
<BarChart
data={monthlyTickets}
height={220}
xAxisConfig={{
dataKey: 'month',
ticks: ['Jan 23', 'Dec 23'],
tickLine: false,
axisLine: false,
tickMargin: 12,
}}
barConfig={[
{
dataKey: 'resolved',
name: 'Resolved',
stackId: 'tickets',
fill: 'var(--color-chart-1)',
barSize: 16,
radius: [0, 0, 0, 0],
isAnimationActive: false,
},
{
dataKey: 'escalated',
name: 'Escalated',
stackId: 'tickets',
fill: 'var(--color-chart-2)',
barSize: 16,
radius: [0, 0, 0, 0],
isAnimationActive: false,
},
{
dataKey: 'reopened',
name: 'Reopened',
stackId: 'tickets',
fill: 'var(--color-chart-8)',
barSize: 16,
radius: [4, 4, 0, 0],
isAnimationActive: false,
},
]}
tooltipContentConfig={{ hideLabel: false }}
/>
</div>
<div className="divide-y">
{legend.map((item) => (
<div
key={item.label}
className="flex items-center justify-between py-2.5"
>
<div className="flex items-center gap-2">
<span
aria-hidden="true"
className="size-2.5 shrink-0 rounded-full"
style={{
backgroundColor: item.color,
}}
/>
<span className="text-sm text-foreground">
{item.label}
</span>
</div>
<span className="text-sm font-semibold tabular-nums text-foreground">
{item.value}
</span>
</div>
))}
</div>
</div>
</Card>
</section>
)
}
Graph Widget 08
Preview
npx nateui@latest add GraphWidgetCashRunwayDark
import { BarChart } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
const weekTicks = ['Jul 28', 'Jul 30', 'Aug 1', 'Aug 3']
const cashOnHand = [
{ day: 'Jul 28', value: 70 },
{ day: 'Jul 29', value: 150 },
{ day: 'Jul 30', value: 210 },
{ day: 'Jul 31', value: 190 },
{ day: 'Aug 1', value: 320 },
{ day: 'Aug 2', value: 410 },
{ day: 'Aug 3', value: 450 },
]
const monthsRunway = [
{ day: 'Jul 28', value: 2.1 },
{ day: 'Jul 29', value: 3.8 },
{ day: 'Jul 30', value: 4.2 },
{ day: 'Jul 31', value: 11.8 },
{ day: 'Aug 1', value: 12.7 },
{ day: 'Aug 2', value: 12.9 },
{ day: 'Aug 3', value: 12.9 },
]
export default function GraphWidgetCashRunway() {
return (
<section
aria-labelledby="graph-widget-cash-runway-title"
className="w-full"
>
<Card className="w-full">
<h5
id="graph-widget-cash-runway-title"
className="text-lg font-semibold text-foreground"
>
Cash & Runway
</h5>
<div className="mt-4 divide-y">
<div className="flex items-center justify-between gap-8 pb-4">
<div className="min-w-0 w-64 xl:w-40 2xl:w-64">
<p className="text-2xl font-semibold text-foreground">
450k
</p>
<p className="mt-1 text-sm text-muted-foreground">
Cash on hand (7 days)
</p>
<p className="mt-2 text-sm text-muted-foreground">
Balance increased by{' '}
<span className="font-medium text-success">
1k
</span>{' '}
this week compared to last week.
</p>
</div>
<div className="flex-1 min-w-0 shrink-0 overflow-hidden">
<BarChart
data={cashOnHand}
height={150}
chartHorizontalSpace={0}
chartVerticalSpace={0}
xAxisConfig={{
dataKey: 'day',
ticks: weekTicks,
interval: 0,
tickLine: false,
axisLine: false,
tickMargin: 8,
}}
cartesianGridConfig={{ horizontal: false }}
barConfig={[
{
dataKey: 'value',
name: 'Cash on hand',
fill: 'var(--color-chart-3)',
barSize: 20,
radius: [3, 3, 0, 0],
isAnimationActive: false,
},
]}
tooltipContentConfig={{
hideLabel: false,
valueFormatter: (value) => `$${value}k`,
}}
/>
</div>
</div>
<div className="flex items-center justify-between gap-8 pt-4">
<div className="min-w-0 w-64 xl:w-40 2xl:w-64">
<p className="text-2xl font-semibold text-foreground">
12.9
</p>
<p className="mt-1 text-sm text-muted-foreground">
Months runway (7 days)
</p>
<p className="mt-2 text-sm text-muted-foreground">
Runway increased by{' '}
<span className="font-medium text-success">
0.3 months
</span>{' '}
this week compared to last week.
</p>
</div>
<div className="flex-1 min-w-0 shrink-0 overflow-hidden">
<BarChart
data={monthsRunway}
height={150}
chartHorizontalSpace={0}
chartVerticalSpace={0}
xAxisConfig={{
dataKey: 'day',
ticks: weekTicks,
interval: 0,
tickLine: false,
axisLine: false,
tickMargin: 8,
}}
cartesianGridConfig={{ horizontal: false }}
barConfig={[
{
dataKey: 'value',
name: 'Months runway',
fill: 'var(--color-chart-7)',
barSize: 20,
radius: [3, 3, 0, 0],
isAnimationActive: false,
},
]}
tooltipContentConfig={{
hideLabel: false,
valueFormatter: (value) =>
`${value} months`,
}}
/>
</div>
</div>
</div>
</Card>
</section>
)
}
Graph Widget 09
Preview
npx nateui@latest add GraphWidgetSubscriptionForecastDark
import { BarChart, ChartLegendContent } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
import Tag from '@/components/ui/Tag'
import { PiArrowUp } from 'react-icons/pi'
const subscriptionForecast = [
{ month: '2026-08-31', free: 883, pro: 441, enterprise: 147 },
{ month: '2026-09-30', free: 936, pro: 468, enterprise: 156 },
{ month: '2026-10-31', free: 992, pro: 496, enterprise: 166 },
{ month: '2026-11-30', free: 1052, pro: 526, enterprise: 175 },
{ month: '2026-12-31', free: 1115, pro: 557, enterprise: 186 },
{ month: '2027-01-31', free: 1182, pro: 591, enterprise: 197 },
]
const planMix = [
{ key: 'free', label: 'Free', value: 60, color: 'var(--color-chart-7)' },
{ key: 'pro', label: 'Pro', value: 30, color: 'var(--color-chart-6)' },
{
key: 'enterprise',
label: 'Enterprise',
value: 10,
color: 'var(--color-chart-4)',
},
]
const parameters = [
{ label: 'Conversion Rate', value: 15, tone: 'up' as const },
{ label: 'Upgrade Rate', value: 8, tone: 'up' as const },
{ label: 'Downgrade Rate', value: 3, tone: 'down' as const },
]
export default function GraphWidgetSubscriptionForecast() {
return (
<section
aria-labelledby="graph-widget-subscription-forecast-title"
className="w-full"
>
<Card className="w-full">
<div className="flex flex-col gap-8 lg:flex-row">
<div className="flex flex-col gap-6 lg:w-80 lg:shrink-0">
<div>
<h5
id="graph-widget-subscription-forecast-title"
className="text-lg font-semibold text-foreground"
>
Subscription Plans Forecast
</h5>
<p className="mt-1 text-sm text-muted-foreground">
Plan distribution and subscription growth
projections
</p>
</div>
<div>
<p className="text-2xl font-semibold text-foreground">
1,471
</p>
<p className="mt-1 text-sm text-muted-foreground">
Total Subscriptions
</p>
</div>
<div className="divide-y">
{parameters.map((parameter) => (
<div
key={parameter.label}
className="flex items-center justify-between py-2.5"
>
<span>
{parameter.label}
</span>
<Tag
className={
parameter.tone === 'up'
? 'gap-1 bg-transparent text-success'
: 'bg-transparent text-destructive'
}
>
{parameter.tone === 'up' && (
<PiArrowUp aria-hidden="true" />
)}
{parameter.value}%
</Tag>
</div>
))}
</div>
<div className="space-y-2">
<div className="flex h-1 items-center gap-1">
{planMix.map((segment) => (
<div
key={segment.key}
className="h-full rounded-full"
style={{
width: `${segment.value}%`,
backgroundColor: segment.color,
}}
/>
))}
</div>
<div className="flex flex-wrap items-center gap-4">
{planMix.map((segment) => (
<div
key={segment.key}
className="flex items-center gap-1.5"
>
<span
aria-hidden="true"
className="size-2 rounded-full"
style={{
backgroundColor:
segment.color,
}}
/>
<span className="text-sm text-muted-foreground">
{segment.label}:{' '}
<span className="font-medium text-foreground">
{segment.value}%
</span>
</span>
</div>
))}
</div>
</div>
</div>
<div className="min-w-0 flex-1 self-end overflow-hidden">
<BarChart
data={subscriptionForecast}
height={300}
xAxisConfig={{ dataKey: 'month' }}
barConfig={[
{
dataKey: 'free',
name: 'Free Plan',
stackId: 'plans',
fill: 'var(--color-chart-7)',
barSize: 25,
radius: [0, 0, 0, 0],
},
{
dataKey: 'pro',
name: 'Pro Plan',
stackId: 'plans',
fill: 'var(--color-chart-6)',
barSize: 25,
radius: [0, 0, 0, 0],
},
{
dataKey: 'enterprise',
name: 'Enterprise Plan',
stackId: 'plans',
fill: 'var(--color-chart-4)',
barSize: 25,
radius: [4, 4, 0, 0],
},
]}
>
<ChartLegendContent
verticalAlign="top"
className="!justify-end pb-6"
/>
</BarChart>
</div>
</div>
</Card>
</section>
)
}
Graph Widget 10
Preview
npx nateui@latest add GraphWidgetUserTrendDark
import { AreaChart } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
import Segment from '@/components/ui/Segment'
import GrowShrinkTag from '@/components/composites/GrowShrinkTag'
import { ReferenceDot } from 'recharts'
import { useState } from 'react'
import {
PiArrowClockwise,
PiArrowDownLeft,
PiClock,
PiUserPlus,
} from 'react-icons/pi'
import type { ReactElement } from 'react'
const thirtyDayUsers = [
{ day: 'JUL 14', value: 50 },
{ day: 'JUL 15', value: 65 },
{ day: 'JUL 16', value: 55 },
{ day: 'JUL 17', value: 80 },
{ day: 'JUL 18', value: 60 },
{ day: 'JUL 19', value: 75 },
{ day: 'JUL 20', value: 90 },
{ day: 'JUL 21', value: 65 },
{ day: 'JUL 22', value: 85 },
{ day: 'JUL 23', value: 70 },
{ day: 'JUL 24', value: 95 },
{ day: 'JUL 25', value: 80 },
{ day: 'JUL 26', value: 100 },
{ day: 'JUL 27', value: 75 },
{ day: 'JUL 28', value: 110 },
{ day: 'JUL 29', value: 90 },
{ day: 'JUL 30', value: 105 },
{ day: 'JUL 31', value: 70 },
{ day: 'AUG 1', value: 60 },
{ day: 'AUG 2', value: 95 },
{ day: 'AUG 3', value: 80 },
{ day: 'AUG 4', value: 110 },
{ day: 'AUG 5', value: 90 },
{ day: 'AUG 6', value: 70 },
{ day: 'AUG 7', value: 100 },
{ day: 'AUG 8', value: 75 },
{ day: 'AUG 9', value: 105 },
{ day: 'AUG 10', value: 85 },
{ day: 'AUG 11', value: 120 },
{ day: 'AUG 12', value: 150 },
]
const twelveDayUsers = thirtyDayUsers.slice(-12)
const sevenDayUsers = thirtyDayUsers.slice(-7)
type RangeKey = '7d' | '12d' | '30d'
const ranges: Record<
RangeKey,
{
data: typeof thirtyDayUsers
ticks: string[]
total: string
delta: number
}
> = {
'7d': {
data: sevenDayUsers,
ticks: ['AUG 6', 'AUG 9', 'AUG 12'],
total: '690',
delta: 6.4,
},
'12d': {
data: twelveDayUsers,
ticks: ['AUG 1', 'AUG 6', 'AUG 12'],
total: '1,240',
delta: 12.5,
},
'30d': {
data: thirtyDayUsers,
ticks: ['JUL 14', 'JUL 31', 'AUG 12'],
total: '2,540',
delta: 18.3,
},
}
const metrics = [
{ label: 'New users', value: '980', icon: PiUserPlus },
{ label: 'Returning users', value: '260', icon: PiArrowClockwise },
{ label: 'Avg. session', value: '3m 42s', icon: PiClock },
{ label: 'Bounce rate', value: '43.5%', icon: PiArrowDownLeft },
]
const formatPeakDay = (day: string) =>
day.charAt(0) + day.slice(1).toLowerCase()
const createPeakLabelRenderer = (text: string) => {
const renderPeakLabel = (props: unknown): ReactElement => {
const { viewBox } = props as { viewBox?: { x: number; y: number } }
const x = viewBox?.x ?? 0
const y = viewBox?.y ?? 0
return (
<g transform={`translate(${x}, ${y - 30})`}>
<rect
x={-22}
y={-11}
width={44}
height={22}
rx={11}
fill="var(--color-card)"
stroke="var(--color-border)"
/>
<text
textAnchor="middle"
dominantBaseline="central"
className="text-xs font-medium"
fill="var(--color-foreground)"
>
{text}
</text>
</g>
)
}
return renderPeakLabel
}
export default function GraphWidgetUserTrend() {
const [range, setRange] = useState<RangeKey>('12d')
const activeRange = ranges[range]
const peakPoint = activeRange.data[activeRange.data.length - 1]
return (
<section
aria-labelledby="graph-widget-user-trend-title"
className="w-full"
>
<Card>
<p
id="graph-widget-user-trend-title"
className="text-xs font-semibold tracking-wide uppercase"
>
Users
</p>
<Segment
value={range}
onChange={(value) => setRange(value as RangeKey)}
size="sm"
className="mt-4 w-full"
>
<Segment.Item value="7d">7d</Segment.Item>
<Segment.Item value="12d">12d</Segment.Item>
<Segment.Item value="30d">30d</Segment.Item>
</Segment>
<div className="mt-6 flex items-center gap-2">
<p className="text-2xl font-semibold text-foreground">
{activeRange.total}
</p>
<GrowShrinkTag value={activeRange.delta} suffix="%" />
</div>
<div className="mt-2 min-w-0 overflow-hidden">
<AreaChart
data={activeRange.data}
height={150}
chartHorizontalSpace={8}
xAxisConfig={{
dataKey: 'day',
ticks: activeRange.ticks,
interval: 0,
tickLine: false,
axisLine: false,
tickMargin: 8,
}}
cartesianGridConfig={{
vertical: false,
strokeDasharray: '4 4',
}}
areaConfig={[
{
dataKey: 'value',
stroke: 'var(--color-chart-3)',
fill: 'url(#user-trend-gradient)',
strokeWidth: 2,
type: 'linear',
},
]}
tooltipContentConfig={{
hideLabel: false,
valueFormatter: (value) => `${value} users`,
}}
>
<defs>
<linearGradient
id="user-trend-gradient"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
style={{
stopColor: 'var(--color-chart-3)',
stopOpacity: 0.35,
}}
/>
<stop
offset="100%"
style={{
stopColor: 'var(--color-chart-3)',
stopOpacity: 0,
}}
/>
</linearGradient>
</defs>
<ReferenceDot
x={peakPoint.day}
y={peakPoint.value}
r={5}
fill="var(--color-chart-3)"
stroke="var(--color-card)"
strokeWidth={2}
label={createPeakLabelRenderer(
formatPeakDay(peakPoint.day),
)}
/>
</AreaChart>
</div>
<div className="mt-4 divide-y">
{metrics.map((metric) => (
<div
key={metric.label}
className="flex items-center justify-between py-2.5"
>
<span className="flex items-center gap-2">
<metric.icon
aria-hidden="true"
className="text-base"
/>
{metric.label}
</span>
<span className="text-sm font-semibold text-foreground">
{metric.value}
</span>
</div>
))}
</div>
</Card>
</section>
)
}
Graph Widget 11
Preview
npx nateui@latest add GraphWidgetCostBreakdownDark
import { PieChart } from '@/components/composites/Chart'
import Card from '@/components/ui/Card'
import Select from '@/components/ui/Select'
const pieSlices = [
{ name: 'Sports Equipment Sales', value: 35, color: 'var(--color-chart-2)' },
{ name: 'Sponsored Events', value: 13, color: 'var(--color-chart-4)' },
{ name: 'Fitness Merchandise Sales', value: 21, color: 'var(--color-chart-7)' },
{ name: 'Athlete Endorsements', value: 5, color: 'var(--color-chart-8)' },
{ name: 'Virtual Training Sessions', value: 12, color: 'var(--color-chart-6)' },
]
const legend = [
{ name: 'Sports Equipment Sales', value: '$35K', color: 'var(--color-chart-2)' },
{ name: 'Virtual Training Sessions', value: '$12K', color: 'var(--color-chart-6)' },
{ name: 'Athlete Endorsements', value: '$5K', color: 'var(--color-chart-8)' },
{ name: 'Fitness Merchandise Sales', value: '$21K', color: 'var(--color-chart-7)' },
{ name: 'Sponsored Events', value: '$13K', color: 'var(--color-chart-4)' },
]
const years = [
{ value: '2024', label: '2024' },
{ value: '2023', label: '2023' },
{ value: '2022', label: '2022' },
]
export default function GraphWidgetCostBreakdown() {
return (
<section
aria-labelledby="graph-widget-cost-breakdown-title"
className="w-full"
>
<Card className="w-full">
<div className="flex items-center justify-between">
<h5
id="graph-widget-cost-breakdown-title"
className="text-lg font-semibold text-foreground"
>
Cost breakdown
</h5>
<Select
className="w-24"
size="sm"
isSearchable={false}
options={years}
defaultValue={years[0]}
/>
</div>
<div className="mt-8 flex flex-col items-center gap-8 sm:flex-row">
<div className="relative shrink-0 overflow-hidden">
<PieChart
data={pieSlices}
height={220}
width={220}
pieConfig={{
dataKey: 'value',
nameKey: 'name',
cx: '50%',
cy: '50%',
innerRadius: 60,
outerRadius: 92,
startAngle: 90,
endAngle: -270,
paddingAngle: 2,
cornerRadius: 4,
isAnimationActive: false,
}}
cellConfig={pieSlices.map((slice) => ({
fill: slice.color,
}))}
tooltipContentConfig={{
hideLabel: true,
}}
/>
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
<span className="text-sm text-muted-foreground">
Total
</span>
<span className="text-2xl font-semibold text-foreground">
$86K
</span>
</div>
</div>
<div className="w-full min-w-0 divide-y">
{legend.map((item) => (
<div
key={item.name}
className="flex items-center justify-between gap-4 py-2.5"
>
<span className="flex items-center gap-2">
<span
aria-hidden="true"
className="size-2.5 shrink-0 rounded-full"
style={{
backgroundColor: item.color,
}}
/>
<span className="text-sm text-foreground">
{item.name}
</span>
</span>
<span className="text-sm font-semibold text-foreground">
{item.value}
</span>
</div>
))}
</div>
</div>
</Card>
</section>
)
}
Graph Widget 12
Preview
npx nateui@latest add GraphWidgetSalesChannelsDark
import { PieChart } from '@/components/composites/Chart'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import Dropdown from '@/components/ui/Dropdown'
import GrowShrinkTag from '@/components/composites/GrowShrinkTag'
import {
PiArrowDown,
PiArrowSquareOut,
PiArrowUp,
PiDotsThreeVerticalBold,
PiDownloadSimple,
} from 'react-icons/pi'
const salesChannels = [
{
name: 'Organic Search',
value: 3200,
color: 'var(--color-chart-1)',
},
{ name: 'Direct', value: 3450, color: 'var(--color-chart-2)' },
{ name: 'Dribbble', value: 3100, color: 'var(--color-chart-3)' },
{ name: 'Facebook', value: 2500, color: 'var(--color-chart-4)' },
{ name: 'Other', value: 1811, color: 'var(--color-chart-5)' },
]
const totalSales = salesChannels.reduce((sum, item) => sum + item.value, 0)
const monthlySales = [
{ month: 'May', count: '10,988', delta: 11.02 },
{ month: 'April', count: '9,899', delta: -44.01 },
{ month: 'March', count: '17,678', delta: -41.91 },
{ month: 'February', count: '30,437', delta: 50.01 },
{ month: 'January', count: '20,285', delta: 2.32 },
]
export default function GraphWidgetSalesChannels() {
return (
<section
aria-labelledby="graph-widget-sales-channels-title"
className="w-full mx-auto max-w-[480px]"
>
<Card className="w-full">
<div className="flex items-start justify-between">
<div>
<h5
id="graph-widget-sales-channels-title"
className="text-lg font-semibold text-foreground"
>
Sales
</h5>
<p className="mt-1 text-muted-foreground">
Sales performance for the period, broken down by
channel and month.
</p>
</div>
<Dropdown
placement="bottom-end"
renderTitle={
<Button
variant="ghost"
size="sm"
icon={<PiDotsThreeVerticalBold />}
aria-label="Sales report options"
/>
}
>
<Dropdown.Item>
<span className="inline-flex items-center gap-2">
<PiDownloadSimple className="text-base" />
Export data
</span>
</Dropdown.Item>
<Dropdown.Item>
<span className="inline-flex items-center gap-2">
<PiArrowSquareOut className="text-base" />
View details
</span>
</Dropdown.Item>
</Dropdown>
</div>
<div className="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:items-center justify-between">
<div className="relative shrink-0 overflow-hidden">
<PieChart
data={salesChannels}
height={220}
width={220}
pieConfig={{
dataKey: 'value',
nameKey: 'name',
cx: '50%',
cy: '50%',
innerRadius: 52,
outerRadius: 78,
startAngle: 90,
endAngle: -270,
paddingAngle: 2,
cornerRadius: 4,
isAnimationActive: false,
}}
cellConfig={salesChannels.map((channel) => ({
fill: channel.color,
}))}
tooltipContentConfig={{
hideLabel: true,
}}
/>
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
<span className="text-muted-foreground">
Total sales
</span>
<span className="text-2xl font-semibold text-foreground">
{totalSales.toLocaleString()}
</span>
</div>
</div>
<div className="w-full min-w-0 max-w-45">
<p className="text-muted-foreground">
Revenue
</p>
<h4 className="mt-1 text-xl font-semibold text-foreground">
$56,734.36
</h4>
<GrowShrinkTag
value={12.76}
suffix="%"
className="mt-1"
/>
<div className="mt-4 space-y-2.5">
{salesChannels.map((channel) => (
<div
key={channel.name}
className="flex items-center gap-2"
>
<span
aria-hidden="true"
className="size-2.5 shrink-0 rounded-full"
style={{
backgroundColor: channel.color,
}}
/>
<span>
{channel.name}
</span>
</div>
))}
</div>
</div>
</div>
<div className="mt-8 divide-y border-t">
{monthlySales.map((item) => {
const isPositive = item.delta >= 0
return (
<div
key={item.month}
className="flex items-center justify-between gap-4 py-3"
>
<span>
{item.month}
</span>
<span className="font-medium text-foreground">
{item.count}
</span>
<span
className={`inline-flex items-center gap-1 font-medium ${
isPositive
? 'text-success'
: 'text-destructive'
}`}
>
{isPositive ? (
<PiArrowUp aria-hidden="true" />
) : (
<PiArrowDown aria-hidden="true" />
)}
{Math.abs(item.delta).toFixed(2)}%
</span>
</div>
)
})}
</div>
</Card>
</section>
)
}