Graph Widgets

12 blocks

Graph Widget 01

Preview
npx nateui@latest add GraphWidgetRevenueComparison
Dark
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 GraphWidgetMessageVolume
Dark
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 GraphWidgetActivitySummary
Dark
'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 GraphWidgetRunActivity
Dark
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 GraphWidgetPipelineFunnel
Dark
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 GraphWidgetBillingUsage
Dark
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 GraphWidgetTicketVolume
Dark
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 GraphWidgetCashRunway
Dark
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 &amp; 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 GraphWidgetSubscriptionForecast
Dark
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 GraphWidgetUserTrend
Dark
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 GraphWidgetCostBreakdown
Dark
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 GraphWidgetSalesChannels
Dark
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>
    )
}