diff --git a/packages/apollo-wind/.storybook/preview.tsx b/packages/apollo-wind/.storybook/preview.tsx index 26aa1c574..150186555 100644 --- a/packages/apollo-wind/.storybook/preview.tsx +++ b/packages/apollo-wind/.storybook/preview.tsx @@ -81,6 +81,7 @@ const preview: Preview = { 'Layout', 'Navigation', 'Overlays', + 'Charts', [ 'UiPath', [ diff --git a/packages/apollo-wind/src/components/ui/chart-area.stories.tsx b/packages/apollo-wind/src/components/ui/chart-area.stories.tsx new file mode 100644 index 000000000..27d925275 --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-area.stories.tsx @@ -0,0 +1,156 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + type ChartConfig, + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { chartTheme } from './chart-palette'; + +const meta = { + title: 'Components/Charts/Area Chart', + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// ============================================================================ +// Stacked +// ============================================================================ + +const stackedData = [ + { month: 'Jan', desktop: 186, mobile: 80 }, + { month: 'Feb', desktop: 305, mobile: 200 }, + { month: 'Mar', desktop: 237, mobile: 120 }, + { month: 'Apr', desktop: 73, mobile: 190 }, + { month: 'May', desktop: 209, mobile: 130 }, + { month: 'Jun', desktop: 214, mobile: 140 }, +]; + +const stackedConfig = { + desktop: { label: 'Desktop', theme: chartTheme[1] }, + mobile: { label: 'Mobile', theme: chartTheme[2] }, +} satisfies ChartConfig; + +export const Stacked: Story = { + render: () => ( + + + Stacked area chart + + Total visitors by device — January to June 2026 + + + + + + + + + } /> + } /> + + + + + + + + + + + + + + + + + ), +}; + +// ============================================================================ +// Step +// ============================================================================ + +const stepData = [ + { month: 'Jan', pageViews: 1200, sessions: 820 }, + { month: 'Feb', pageViews: 1450, sessions: 960 }, + { month: 'Mar', pageViews: 1100, sessions: 740 }, + { month: 'Apr', pageViews: 1680, sessions: 1100 }, + { month: 'May', pageViews: 1520, sessions: 980 }, + { month: 'Jun', pageViews: 1790, sessions: 1250 }, +]; + +const stepConfig = { + pageViews: { label: 'Page views', theme: chartTheme[1] }, + sessions: { label: 'Sessions', theme: chartTheme[4] }, +} satisfies ChartConfig; + +export const Step: Story = { + render: () => ( + + + Step area chart + + Page views and sessions with step interpolation + + + + + + + + + } /> + } /> + + + + + + + + + + + + + + + + + ), +}; diff --git a/packages/apollo-wind/src/components/ui/chart-bar.stories.tsx b/packages/apollo-wind/src/components/ui/chart-bar.stories.tsx new file mode 100644 index 000000000..82984a8d3 --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-bar.stories.tsx @@ -0,0 +1,212 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + type ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { chartTheme } from './chart-palette'; + +const meta = { + title: 'Components/Charts/Bar Chart', + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// ============================================================================ +// Vertical +// ============================================================================ + +const verticalData = [ + { month: 'Jan', desktop: 186, mobile: 80 }, + { month: 'Feb', desktop: 305, mobile: 200 }, + { month: 'Mar', desktop: 237, mobile: 120 }, + { month: 'Apr', desktop: 73, mobile: 190 }, + { month: 'May', desktop: 209, mobile: 130 }, + { month: 'Jun', desktop: 214, mobile: 140 }, +]; + +const verticalConfig = { + desktop: { label: 'Desktop', theme: chartTheme[1] }, + mobile: { label: 'Mobile', theme: chartTheme[2] }, +} satisfies ChartConfig; + +export const Vertical: Story = { + render: () => ( + + + Bar chart + + Visitors by device — January to June 2026 + + + + + + + + + } /> + + + + + + + ), +}; + +// ============================================================================ +// Horizontal +// ============================================================================ + +const horizontalData = [ + { category: 'React', value: 42 }, + { category: 'Vue', value: 28 }, + { category: 'Angular', value: 19 }, + { category: 'Svelte', value: 15 }, + { category: 'Solid', value: 8 }, +]; + +const horizontalConfig = { + value: { label: 'Popularity %', theme: chartTheme[2] }, +} satisfies ChartConfig; + +export const Horizontal: Story = { + render: () => ( + + + Horizontal bar chart + + Framework popularity — developer survey 2026 + + + + + + + + + } /> + + + + + + ), +}; + +// ============================================================================ +// Interactive +// ============================================================================ + +const interactiveData = [ + { date: '2026-01-01', desktop: 222, mobile: 150 }, + { date: '2026-01-02', desktop: 97, mobile: 180 }, + { date: '2026-01-03', desktop: 167, mobile: 120 }, + { date: '2026-01-04', desktop: 242, mobile: 260 }, + { date: '2026-01-05', desktop: 373, mobile: 290 }, + { date: '2026-01-06', desktop: 301, mobile: 340 }, + { date: '2026-01-07', desktop: 245, mobile: 180 }, + { date: '2026-01-08', desktop: 409, mobile: 320 }, + { date: '2026-01-09', desktop: 59, mobile: 110 }, + { date: '2026-01-10', desktop: 261, mobile: 190 }, + { date: '2026-01-11', desktop: 327, mobile: 350 }, + { date: '2026-01-12', desktop: 292, mobile: 210 }, +]; + +const interactiveConfig = { + desktop: { label: 'Desktop', theme: chartTheme[1] }, + mobile: { label: 'Mobile', theme: chartTheme[2] }, +} satisfies ChartConfig; + +function InteractiveBarChart() { + const [activeKey, setActiveKey] = React.useState<'desktop' | 'mobile'>('desktop'); + const total = React.useMemo( + () => ({ + desktop: interactiveData.reduce((acc, d) => acc + d.desktop, 0), + mobile: interactiveData.reduce((acc, d) => acc + d.mobile, 0), + }), + [] + ); + + return ( + + +
+ Interactive bar chart + + Daily visitors — click a toggle to filter + +
+
+ {(['desktop', 'mobile'] as const).map((key) => ( + + ))} +
+
+ + + + + { + const d = new Date(v); + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + }} + /> + + new Date(v).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }) + } + /> + } + /> + + + + +
+ ); +} + +export const Interactive: Story = { + render: () => , +}; diff --git a/packages/apollo-wind/src/components/ui/chart-line.stories.tsx b/packages/apollo-wind/src/components/ui/chart-line.stories.tsx new file mode 100644 index 000000000..f560e7f7d --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-line.stories.tsx @@ -0,0 +1,145 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; +import { CartesianGrid, Line, LineChart, XAxis, YAxis } from 'recharts'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + type ChartConfig, + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { chartTheme } from './chart-palette'; + +const meta = { + title: 'Components/Charts/Line Chart', + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// ============================================================================ +// Multi-line +// ============================================================================ + +const multiData = [ + { month: 'Jan', desktop: 186, mobile: 80, tablet: 45 }, + { month: 'Feb', desktop: 305, mobile: 200, tablet: 98 }, + { month: 'Mar', desktop: 237, mobile: 120, tablet: 65 }, + { month: 'Apr', desktop: 73, mobile: 190, tablet: 140 }, + { month: 'May', desktop: 209, mobile: 130, tablet: 87 }, + { month: 'Jun', desktop: 214, mobile: 140, tablet: 112 }, +]; + +const multiConfig = { + desktop: { label: 'Desktop', theme: chartTheme[1] }, + mobile: { label: 'Mobile', theme: chartTheme[2] }, + tablet: { label: 'Tablet', theme: chartTheme[3] }, +} satisfies ChartConfig; + +export const MultiLine: Story = { + render: () => ( + + + Multi-line chart + + Visitors across three device types — January to June 2026 + + + + + + + + + } /> + } /> + + + + + + + + ), +}; + +// ============================================================================ +// Curved +// ============================================================================ + +const curvedData = [ + { month: 'Jan', revenue: 2400, profit: 1200 }, + { month: 'Feb', revenue: 1398, profit: 900 }, + { month: 'Mar', revenue: 3800, profit: 2100 }, + { month: 'Apr', revenue: 3908, profit: 2400 }, + { month: 'May', revenue: 4800, profit: 2800 }, + { month: 'Jun', revenue: 3490, profit: 1900 }, +]; + +const curvedConfig = { + revenue: { label: 'Revenue', theme: chartTheme[4] }, + profit: { label: 'Profit', theme: chartTheme[3] }, +} satisfies ChartConfig; + +export const Curved: Story = { + render: () => ( + + + Curved line chart + + Revenue vs profit with natural curve interpolation + + + + + + + + + } /> + } /> + + + + + + + ), +}; diff --git a/packages/apollo-wind/src/components/ui/chart-palette.ts b/packages/apollo-wind/src/components/ui/chart-palette.ts new file mode 100644 index 000000000..47cf4a34e --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-palette.ts @@ -0,0 +1,44 @@ +// Shared color palette for chart stories. +// +// Core theme hex values come from apollo-core theme-variables.css: +// slot 1 → --color-chart-light-blue +// slot 2 → --color-chart-blue-secondary +// slot 3 → --color-chart-green +// slot 4 → --color-chart-yellow +// slot 5 → --color-chart-pink +// +// Future theme values reference --chart-1 through --chart-5 CSS custom +// properties defined in tailwind.consumer.css for future-light / future-dark. + +export const chartTheme = { + 1: { + light: 'var(--color-chart-light-blue, #38c6f4)', + dark: 'var(--color-chart-light-blue, #44cffc)', + 'future-light': 'var(--chart-1)', + 'future-dark': 'var(--chart-1)', + }, + 2: { + light: 'var(--color-chart-blue-secondary, #42a1ff)', + dark: 'var(--color-chart-blue-secondary, #42a1ff)', + 'future-light': 'var(--chart-2)', + 'future-dark': 'var(--chart-2)', + }, + 3: { + light: 'var(--color-chart-green, #6eb84a)', + dark: 'var(--color-chart-green, #74c94b)', + 'future-light': 'var(--chart-3)', + 'future-dark': 'var(--chart-3)', + }, + 4: { + light: 'var(--color-chart-yellow, #ffb40e)', + dark: 'var(--color-chart-yellow, #ffbb27)', + 'future-light': 'var(--chart-4)', + 'future-dark': 'var(--chart-4)', + }, + 5: { + light: 'var(--color-chart-pink, #ed145b)', + dark: 'var(--color-chart-pink, #f25a8c)', + 'future-light': 'var(--chart-5)', + 'future-dark': 'var(--chart-5)', + }, +} as const; diff --git a/packages/apollo-wind/src/components/ui/chart-pie.stories.tsx b/packages/apollo-wind/src/components/ui/chart-pie.stories.tsx new file mode 100644 index 000000000..22fdfe237 --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-pie.stories.tsx @@ -0,0 +1,157 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; +import { Label, Pie, PieChart } from 'recharts'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + type ChartConfig, + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { chartTheme } from './chart-palette'; + +const meta = { + title: 'Components/Charts/Pie Chart', + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// ============================================================================ +// Donut +// ============================================================================ + +const donutData = [ + { browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' }, + { browser: 'safari', visitors: 200, fill: 'var(--color-safari)' }, + { browser: 'firefox', visitors: 287, fill: 'var(--color-firefox)' }, + { browser: 'edge', visitors: 173, fill: 'var(--color-edge)' }, + { browser: 'other', visitors: 190, fill: 'var(--color-other)' }, +]; + +const donutConfig = { + visitors: { label: 'Visitors' }, + chrome: { label: 'Chrome', theme: chartTheme[1] }, + safari: { label: 'Safari', theme: chartTheme[2] }, + firefox: { label: 'Firefox', theme: chartTheme[3] }, + edge: { label: 'Edge', theme: chartTheme[4] }, + other: { label: 'Other', theme: chartTheme[5] }, +} satisfies ChartConfig; + +function DonutChart() { + const totalVisitors = React.useMemo(() => donutData.reduce((acc, d) => acc + d.visitors, 0), []); + + return ( + + + Donut chart + + Browser market share — January to June 2026 + + + + + + } /> + + + } /> + + + + + ); +} + +export const Donut: Story = { + render: () => , +}; + +// ============================================================================ +// Simple +// ============================================================================ + +const simpleData = [ + { segment: 'direct', value: 420, fill: 'var(--color-direct)' }, + { segment: 'organic', value: 315, fill: 'var(--color-organic)' }, + { segment: 'referral', value: 180, fill: 'var(--color-referral)' }, + { segment: 'social', value: 135, fill: 'var(--color-social)' }, +]; + +const simpleConfig = { + value: { label: 'Traffic' }, + direct: { label: 'Direct', theme: chartTheme[1] }, + organic: { label: 'Organic', theme: chartTheme[3] }, + referral: { label: 'Referral', theme: chartTheme[4] }, + social: { label: 'Social', theme: chartTheme[5] }, +} satisfies ChartConfig; + +export const Simple: Story = { + render: () => ( + + + Pie chart + + Traffic sources — January to June 2026 + + + + + + } /> + + `${simpleConfig[segment as keyof typeof simpleConfig]?.label ?? segment} ${(percent * 100).toFixed(0)}%` + } + /> + } /> + + + + + ), +}; diff --git a/packages/apollo-wind/src/components/ui/chart-radar.stories.tsx b/packages/apollo-wind/src/components/ui/chart-radar.stories.tsx new file mode 100644 index 000000000..ea2114678 --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-radar.stories.tsx @@ -0,0 +1,122 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; +import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + type ChartConfig, + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { chartTheme } from './chart-palette'; + +const meta = { + title: 'Components/Charts/Radar Chart', + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// ============================================================================ +// Multi-series +// ============================================================================ + +const multiData = [ + { metric: 'Speed', desktop: 86, mobile: 65 }, + { metric: 'Reliability', desktop: 92, mobile: 78 }, + { metric: 'Scalability', desktop: 78, mobile: 60 }, + { metric: 'Security', desktop: 95, mobile: 88 }, + { metric: 'Usability', desktop: 72, mobile: 90 }, + { metric: 'Support', desktop: 81, mobile: 75 }, +]; + +const multiConfig = { + desktop: { label: 'Desktop', theme: chartTheme[1] }, + mobile: { label: 'Mobile', theme: chartTheme[2] }, +} satisfies ChartConfig; + +export const MultiSeries: Story = { + render: () => ( + + + Radar chart + + Platform performance across 6 dimensions + + + + + + } /> + + + + + } /> + + + + + ), +}; + +// ============================================================================ +// Dots +// ============================================================================ + +const dotsData = [ + { skill: 'HTML', level: 95 }, + { skill: 'CSS', level: 88 }, + { skill: 'JS', level: 92 }, + { skill: 'React', level: 85 }, + { skill: 'Node', level: 70 }, + { skill: 'SQL', level: 65 }, +]; + +const dotsConfig = { + level: { label: 'Proficiency', theme: chartTheme[3] }, +} satisfies ChartConfig; + +export const Dots: Story = { + render: () => ( + + + Radar chart — dots + + Developer skill proficiency with dot markers + + + + + + } /> + + + + + + + + ), +}; diff --git a/packages/apollo-wind/src/components/ui/chart-radial.stories.tsx b/packages/apollo-wind/src/components/ui/chart-radial.stories.tsx new file mode 100644 index 000000000..ac7488902 --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-radial.stories.tsx @@ -0,0 +1,145 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; +import { Cell, Label, RadialBar, RadialBarChart } from 'recharts'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + type ChartConfig, + ChartContainer, + ChartLegend, + ChartLegendContent, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { chartTheme } from './chart-palette'; + +const meta = { + title: 'Components/Charts/Radial Chart', + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// ============================================================================ +// Bar +// ============================================================================ + +const barData = [ + { name: 'safari', visitors: 200, fill: 'var(--color-safari)' }, + { name: 'firefox', visitors: 287, fill: 'var(--color-firefox)' }, + { name: 'edge', visitors: 173, fill: 'var(--color-edge)' }, + { name: 'chrome', visitors: 275, fill: 'var(--color-chrome)' }, +]; + +const barConfig = { + visitors: { label: 'Visitors' }, + chrome: { label: 'Chrome', theme: chartTheme[1] }, + safari: { label: 'Safari', theme: chartTheme[2] }, + firefox: { label: 'Firefox', theme: chartTheme[3] }, + edge: { label: 'Edge', theme: chartTheme[4] }, +} satisfies ChartConfig; + +export const Bar: Story = { + render: () => ( + + + Radial bar chart + + Browser visitors as stacked radial segments + + + + + + } /> + + {barData.map((entry) => ( + + ))} + + } + className="-translate-y-2 flex-wrap gap-2 [&>*]:basis-1/4 [&>*]:justify-center" + /> + + + + + ), +}; + +// ============================================================================ +// Gauge +// ============================================================================ + +const GAUGE_VALUE = 72; +const gaugeData = [{ name: 'progress', value: GAUGE_VALUE, fill: 'var(--color-progress)' }]; + +const gaugeConfig = { + progress: { label: 'Completion', theme: chartTheme[1] }, +} satisfies ChartConfig; + +export const Gauge: Story = { + render: () => ( + + + Radial gauge + + Sprint completion progress — {GAUGE_VALUE}% + + + + + + + + + + + + + ), +}; diff --git a/packages/apollo-wind/src/components/ui/chart-tooltip.stories.tsx b/packages/apollo-wind/src/components/ui/chart-tooltip.stories.tsx new file mode 100644 index 000000000..3a4a9ad0c --- /dev/null +++ b/packages/apollo-wind/src/components/ui/chart-tooltip.stories.tsx @@ -0,0 +1,128 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import * as React from 'react'; +import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { + type ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from '@/components/ui/chart'; +import { chartTheme } from './chart-palette'; + +const meta = { + title: 'Components/Charts/Tooltips', + tags: ['autodocs'], + parameters: { layout: 'padded' }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const data = [ + { month: 'Jan', revenue: 4500, expenses: 2800 }, + { month: 'Feb', revenue: 5200, expenses: 3100 }, + { month: 'Mar', revenue: 4800, expenses: 2900 }, + { month: 'Apr', revenue: 6100, expenses: 3400 }, + { month: 'May', revenue: 5800, expenses: 3200 }, + { month: 'Jun', revenue: 7200, expenses: 3800 }, +]; + +// ============================================================================ +// Dot indicator +// ============================================================================ + +const dotConfig = { + revenue: { label: 'Revenue', theme: chartTheme[3] }, + expenses: { label: 'Expenses', theme: chartTheme[5] }, +} satisfies ChartConfig; + +export const DotIndicator: Story = { + render: () => ( + + + Dot indicator + + Default dot-style tooltip indicator + + + + + + + + } /> + + + + + + + ), +}; + +// ============================================================================ +// Line indicator +// ============================================================================ + +const lineConfig = { + revenue: { label: 'Revenue', theme: chartTheme[1] }, + expenses: { label: 'Expenses', theme: chartTheme[4] }, +} satisfies ChartConfig; + +export const LineIndicator: Story = { + render: () => ( + + + Line indicator + + Vertical line-style tooltip indicator + + + + + + + + } /> + + + + + + + ), +}; + +// ============================================================================ +// Dashed indicator +// ============================================================================ + +const dashedConfig = { + revenue: { label: 'Revenue', theme: chartTheme[2] }, + expenses: { label: 'Expenses', theme: chartTheme[5] }, +} satisfies ChartConfig; + +export const DashedIndicator: Story = { + render: () => ( + + + Dashed indicator + + Dashed border-style tooltip indicator + + + + + + + + } /> + + + + + + + ), +}; diff --git a/packages/apollo-wind/src/components/ui/chart.tsx b/packages/apollo-wind/src/components/ui/chart.tsx index 06c229171..bf95dc542 100644 --- a/packages/apollo-wind/src/components/ui/chart.tsx +++ b/packages/apollo-wind/src/components/ui/chart.tsx @@ -4,10 +4,13 @@ import * as RechartsPrimitive from 'recharts'; import { cn } from '@/lib'; // Format: { THEME_NAME: CSS_SELECTOR } -// Matches ALL light themes and ALL dark themes using :is() selector +// Core and Future themes are split so each can use their own color tokens. +// wireframe/vertex/canvas are dark-adjacent and inherit core dark colors. const THEMES = { - light: ':is(.light, .light-hc, .future-light)', - dark: ':is(.dark, .dark-hc, .future-dark, .wireframe, .vertex, .canvas)', + light: ':is(.light, .light-hc)', + dark: ':is(.dark, .dark-hc, .wireframe, .vertex, .canvas)', + 'future-light': '.future-light', + 'future-dark': '.future-dark', } as const; export type ChartConfig = { diff --git a/packages/apollo-wind/src/components/ui/component-gallery.stories.tsx b/packages/apollo-wind/src/components/ui/component-gallery.stories.tsx index 1a5aedef5..b252c1c96 100644 --- a/packages/apollo-wind/src/components/ui/component-gallery.stories.tsx +++ b/packages/apollo-wind/src/components/ui/component-gallery.stories.tsx @@ -1,5 +1,21 @@ import type { Meta } from '@storybook/react-vite'; import { useState, useMemo } from 'react'; +import { + Area, + AreaChart, + Bar, + BarChart, + Line, + LineChart, + Pie, + PieChart, + PolarAngleAxis, + PolarGrid, + Radar, + RadarChart, + RadialBar, + RadialBarChart, +} from 'recharts'; import { Card, CardDescription, CardHeader, CardTitle } from './card'; import { Badge } from './badge'; import { Button } from './button'; @@ -16,6 +32,7 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './ import { Tabs, TabsList, TabsTrigger } from './tabs'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table'; import { Calendar } from './calendar'; +import { type ChartConfig, ChartContainer } from './chart'; import { Search } from 'lucide-react'; const meta = { @@ -34,6 +51,7 @@ enum Category { Navigation = 'Navigation', Overlays = 'Overlays', Feedback = 'Feedback', + Charts = 'Charts', } const CATEGORY_ORDER: Category[] = [ @@ -43,8 +61,63 @@ const CATEGORY_ORDER: Category[] = [ Category.Navigation, Category.Overlays, Category.Feedback, + Category.Charts, ]; +// ============================================================================ +// Mini chart data + configs (for gallery previews) +// ============================================================================ + +const miniSparkData = [ + { x: 'A', a: 30, b: 20 }, + { x: 'B', a: 55, b: 35 }, + { x: 'C', a: 40, b: 50 }, + { x: 'D', a: 70, b: 30 }, + { x: 'E', a: 50, b: 60 }, +]; + +const miniPieData = [ + { name: 'a', value: 40, fill: '#22d3ee' }, + { name: 'b', value: 30, fill: '#818cf8' }, + { name: 'c', value: 20, fill: '#34d399' }, + { name: 'd', value: 10, fill: '#fbbf24' }, +]; + +const miniRadarData = [ + { m: 'A', v: 80 }, + { m: 'B', v: 65 }, + { m: 'C', v: 90 }, + { m: 'D', v: 75 }, + { m: 'E', v: 85 }, +]; + +const miniRadialData = [ + { name: 'a', value: 80, fill: '#22d3ee' }, + { name: 'b', value: 60, fill: '#818cf8' }, + { name: 'c', value: 40, fill: '#34d399' }, +]; + +const miniDualConfig = { + a: { color: '#22d3ee' }, + b: { color: '#818cf8' }, +} satisfies ChartConfig; + +const miniSingleConfig = { + a: { color: '#22d3ee' }, +} satisfies ChartConfig; + +const miniPieConfig = { + value: { color: '#22d3ee' }, +} satisfies ChartConfig; + +const miniRadarConfig = { + v: { color: '#22d3ee' }, +} satisfies ChartConfig; + +const miniRadialConfig = { + value: { color: '#22d3ee' }, +} satisfies ChartConfig; + interface ComponentInfo { name: string; description: string; @@ -657,6 +730,133 @@ const components: ComponentInfo[] = [ ), }, + // ============================================================================ + // Charts + // ============================================================================ + { + name: 'Area Chart', + description: 'Stacked and step area charts', + storyPath: 'components-charts-area-chart--docs', + category: Category.Charts, + preview: ( + + + + + + + ), + }, + { + name: 'Bar Chart', + description: 'Vertical, horizontal, and interactive bars', + storyPath: 'components-charts-bar-chart--docs', + category: Category.Charts, + preview: ( + + + + + + + ), + }, + { + name: 'Line Chart', + description: 'Multi-line and curved line charts', + storyPath: 'components-charts-line-chart--docs', + category: Category.Charts, + preview: ( + + + + + + + ), + }, + { + name: 'Pie Chart', + description: 'Donut and simple pie charts', + storyPath: 'components-charts-pie-chart--docs', + category: Category.Charts, + preview: ( + + + + + + ), + }, + { + name: 'Radar Chart', + description: 'Multi-series and dot radar charts', + storyPath: 'components-charts-radar-chart--docs', + category: Category.Charts, + preview: ( + + + + + + + + ), + }, + { + name: 'Radial Chart', + description: 'Radial bar and gauge charts', + storyPath: 'components-charts-radial-chart--docs', + category: Category.Charts, + preview: ( + + + + + + ), + }, + { + name: 'Tooltips', + description: 'Dot, line, and dashed tooltip styles', + storyPath: 'components-charts-tooltips--docs', + category: Category.Charts, + preview: ( + + + + + + ), + }, ]; function ComponentGallery() { @@ -682,6 +882,7 @@ function ComponentGallery() { [Category.Navigation]: [], [Category.Overlays]: [], [Category.Feedback]: [], + [Category.Charts]: [], }; filteredComponents.forEach((component) => { grouped[component.category].push(component); @@ -702,7 +903,7 @@ function ComponentGallery() {

Explore the collection of {components.length} beautifully crafted, accessible - components built with React and Tailwind CSS. + components and charts built with React and Tailwind CSS.

diff --git a/packages/apollo-wind/src/styles/tailwind.consumer.css b/packages/apollo-wind/src/styles/tailwind.consumer.css index e1dd1e0b4..49c07112b 100644 --- a/packages/apollo-wind/src/styles/tailwind.consumer.css +++ b/packages/apollo-wind/src/styles/tailwind.consumer.css @@ -494,6 +494,12 @@ body.future-dark, .future-dark { --chart-3: var(--color-emerald-400); --chart-4: var(--color-amber-400); --chart-5: var(--color-rose-400); + /* Bridge apollo-core chart token names → future chart vars */ + --color-chart-light-blue: var(--chart-1); + --color-chart-blue-secondary: var(--chart-2); + --color-chart-green: var(--chart-3); + --color-chart-yellow: var(--chart-4); + --color-chart-pink: var(--chart-5); /* ── shadcn aliases — maps to standard names for shadcn component compat ── */ --background: var(--surface); @@ -661,6 +667,12 @@ body.future-light, .future-light { --chart-3: var(--color-emerald-600); --chart-4: var(--color-amber-600); --chart-5: var(--color-rose-600); + /* Bridge apollo-core chart token names → future chart vars */ + --color-chart-light-blue: var(--chart-1); + --color-chart-blue-secondary: var(--chart-2); + --color-chart-green: var(--chart-3); + --color-chart-yellow: var(--chart-4); + --color-chart-pink: var(--chart-5); /* ── shadcn aliases ──────────────────────────────────────────────────── */ --background: var(--surface); diff --git a/packages/apollo-wind/src/templates/Maestro/visualization.stories.tsx b/packages/apollo-wind/src/templates/Maestro/visualization.stories.tsx index c6b5166ba..53c5ffbc5 100644 --- a/packages/apollo-wind/src/templates/Maestro/visualization.stories.tsx +++ b/packages/apollo-wind/src/templates/Maestro/visualization.stories.tsx @@ -52,17 +52,57 @@ type Story = StoryObj; // ============================================================================ const palette = { - cyan: { dark: '#22d3ee', light: '#0891b2' }, - indigo: { dark: '#818cf8', light: '#4f46e5' }, - emerald: { dark: '#34d399', light: '#059669' }, - amber: { dark: '#fbbf24', light: '#d97706' }, - rose: { dark: '#fb7185', light: '#e11d48' }, - violet: { dark: '#a78bfa', light: '#7c3aed' }, - sky: { dark: '#38bdf8', light: '#0284c7' }, + cyan: { + light: 'var(--color-chart-light-blue, #38c6f4)', + dark: 'var(--color-chart-light-blue, #44cffc)', + 'future-light': 'var(--chart-1)', + 'future-dark': 'var(--chart-1)', + }, + indigo: { + light: 'var(--color-chart-blue-secondary, #42a1ff)', + dark: 'var(--color-chart-blue-secondary, #42a1ff)', + 'future-light': 'var(--chart-2)', + 'future-dark': 'var(--chart-2)', + }, + emerald: { + light: 'var(--color-chart-green, #6eb84a)', + dark: 'var(--color-chart-green, #74c94b)', + 'future-light': 'var(--chart-3)', + 'future-dark': 'var(--chart-3)', + }, + amber: { + light: 'var(--color-chart-yellow, #ffb40e)', + dark: 'var(--color-chart-yellow, #ffbb27)', + 'future-light': 'var(--chart-4)', + 'future-dark': 'var(--chart-4)', + }, + rose: { + light: 'var(--color-chart-pink, #ed145b)', + dark: 'var(--color-chart-pink, #f25a8c)', + 'future-light': 'var(--chart-5)', + 'future-dark': 'var(--chart-5)', + }, + violet: { + light: 'var(--color-chart-blue-secondary, #42a1ff)', + dark: 'var(--color-chart-blue-secondary, #42a1ff)', + 'future-light': 'var(--chart-2)', + 'future-dark': 'var(--chart-2)', + }, + sky: { + light: 'var(--color-chart-light-blue, #38c6f4)', + dark: 'var(--color-chart-light-blue, #44cffc)', + 'future-light': 'var(--chart-1)', + 'future-dark': 'var(--chart-1)', + }, } as const; function themed(key: keyof typeof palette) { - return { dark: palette[key].dark, light: palette[key].light }; + return { + light: palette[key].light, + dark: palette[key].dark, + 'future-light': palette[key]['future-light'], + 'future-dark': palette[key]['future-dark'], + }; } // ============================================================================