feat(chart): add @hm-design/chart package with Bar, Line, Donut, Pie, Radar, Area and Radial charts

- new workspace package packages/chart (shadcn/ui Chart + Recharts, HM Theme tokens), examples entry with sample data
- Storybook Chart group (src/chart), consumer example (examples/chart), build:chart / build:chart-example scripts
- README rewritten as the AI usage guide (install, CSS wiring, chart selection, data rules, verification)
- docs: package count/list, Submodule guide (chart workspace, React dedupe), shared AI_GUIDE row, MAINTAINING, story inventory
This commit is contained in:
shkim4 committed 2026-11-06 15:52:47 +09:00
1 parent 228944a06b
commit dc9258599c
56 files changed
+3979 -28

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
AreaCurveDotLabelExample,
AreaCurveExample,
AreaExpandedExample,
AreaStackedExample,
AreaStepDotLabelExample,
AreaStepExample,
AreaStraightColorLabelExample,
AreaStraightExample,
} from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Sample data only. The eight examples share the Line Chart data: connection styles as a plain area (left) and with Dots and
// value Labels (right), then Stacked (running total) and Expanded (each month normalized to 100%).
const meta = {
title: "Chart/Area Chart",
parameters: { layout: "fullscreen", controls: { disable: true } },
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Area: Story = {
name: "Area Chart",
render: () => (
<StoryPage>
<StoryPageHeader title="Area Chart" />
{/* Same grid and order as Line Chart, plus a Stacked | Expanded row. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Curve">
<AreaCurveExample />
</StorySection>
<StorySection title="Curve · Dot + Label">
<AreaCurveDotLabelExample />
</StorySection>
<StorySection title="Straight">
<AreaStraightExample />
</StorySection>
<StorySection title="Straight · Color Dot + Label">
<AreaStraightColorLabelExample />
</StorySection>
<StorySection title="Step">
<AreaStepExample />
</StorySection>
<StorySection title="Step · Dot + Label">
<AreaStepDotLabelExample />
</StorySection>
<StorySection title="Stacked">
<AreaStackedExample />
</StorySection>
<StorySection title="Expanded">
<AreaExpandedExample />
</StorySection>
</div>
</StoryPage>
),
}
+43
View File
@@ -0,0 +1,43 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
BarChartExample,
HorizontalBarChartExample,
SignedBarChartExample,
StackedBarChartExample,
} from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// shadcn/ui Chart (Recharts) with HM tokens, from the public @hm-design/chart entries. All values are sample data.
// Narrow widths: use the toolbar Viewport presets (HM Mobile · 390). The chart is fluid.
const meta = {
title: "Chart/Bar Chart",
parameters: { layout: "fullscreen", controls: { disable: true } },
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Bar: Story = {
name: "Bar Chart",
render: () => (
<StoryPage>
<StoryPageHeader title="Bar Chart" />
{/* Two sections per row from 1024px (HM desktop boundary), one below. Source order is the mobile order; on desktop it reads
row by row: Grouped | Horizontal, then Stacked | Positive / Negative. On desktop every row has the same height and each
card fills its section, so card tops, bottoms and chart areas line up. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Grouped">
<BarChartExample />
</StorySection>
<StorySection title="Horizontal">
<HorizontalBarChartExample />
</StorySection>
<StorySection title="Stacked">
<StackedBarChartExample />
</StorySection>
<StorySection title="Positive / Negative">
<SignedBarChartExample />
</StorySection>
</div>
</StoryPage>
),
}
+40
View File
@@ -0,0 +1,40 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
DonutActiveExample,
DonutCenterExample,
DonutInteractiveExample,
DonutPlainExample,
} from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Sample data only. The four examples share one data set, color mapping and item order.
const meta = {
title: "Chart/Donut Chart",
parameters: { layout: "fullscreen", controls: { disable: true } },
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Donut: Story = {
name: "Donut Chart",
render: () => (
<StoryPage>
<StoryPageHeader title="Donut Chart" />
{/* Same grid as Bar and Line Chart: two per row from 1024px, one below; source order is the mobile order. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="기본형">
<DonutPlainExample />
</StorySection>
<StorySection title="기본형 + 텍스트">
<DonutCenterExample />
</StorySection>
<StorySection title="Active">
<DonutActiveExample />
</StorySection>
<StorySection title="Interactive">
<DonutInteractiveExample />
</StorySection>
</div>
</StoryPage>
),
}
+50
View File
@@ -0,0 +1,50 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
LineCurveDotLabelExample,
LineCurveExample,
LineStepDotLabelExample,
LineStepExample,
LineStraightColorLabelExample,
LineStraightExample,
} from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Sample data only. All six examples share one data set and axis range, so connection styles compare directly: curve, straight
// and step, each as a plain line (left) and with Dots and value labels (right).
const meta = {
title: "Chart/Line Chart",
parameters: { layout: "fullscreen", controls: { disable: true } },
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Line: Story = {
name: "Line Chart",
render: () => (
<StoryPage>
<StoryPageHeader title="Line Chart" />
{/* Same grid as Bar Chart: two per row from 1024px, one below. Source order is the mobile order; desktop reads row by row:
Curve | Curve + labels, Straight | Straight + colored labels, Step | Step + labels. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Curve">
<LineCurveExample />
</StorySection>
<StorySection title="Curve · Dot + Label">
<LineCurveDotLabelExample />
</StorySection>
<StorySection title="Straight">
<LineStraightExample />
</StorySection>
<StorySection title="Straight · Color Dot + Label">
<LineStraightColorLabelExample />
</StorySection>
<StorySection title="Step">
<LineStepExample />
</StorySection>
<StorySection title="Step · Dot + Label">
<LineStepDotLabelExample />
</StorySection>
</div>
</StoryPage>
),
}
+35
View File
@@ -0,0 +1,35 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import { PieLabelExample, PieLabelListExample, PiePlainExample, PieStackedExample } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Sample data only. All pies share one item list and color mapping; the stacked pie draws two series as concentric rings.
const meta = {
title: "Chart/Pie Chart",
parameters: { layout: "fullscreen", controls: { disable: true } },
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Pie: Story = {
name: "Pie Chart",
render: () => (
<StoryPage>
<StoryPageHeader title="Pie Chart" />
{/* Same grid as Bar, Line and Donut Chart: two per row from 1024px, one below; source order is the mobile order. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="기본 Pie">
<PiePlainExample />
</StorySection>
<StorySection title="Pie + Label">
<PieLabelExample />
</StorySection>
<StorySection title="Pie + Label List">
<PieLabelListExample />
</StorySection>
<StorySection title="Pie + Stacked">
<PieStackedExample />
</StorySection>
</div>
</StoryPage>
),
}
+35
View File
@@ -0,0 +1,35 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import { RadarCustomLabelExample, RadarDotExample, RadarLinesOnlyExample, RadarPlainExample } from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Sample data only. All radars share one data set (six axes, two series) and the 0-100 axis range.
const meta = {
title: "Chart/Radar Chart",
parameters: { layout: "fullscreen", controls: { disable: true } },
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Radar: Story = {
name: "Radar Chart",
render: () => (
<StoryPage>
<StoryPageHeader title="Radar Chart" />
{/* Same grid as the other Chart pages: two per row from 1024px, one below. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Radar">
<RadarPlainExample />
</StorySection>
<StorySection title="Radar + Dot">
<RadarDotExample />
</StorySection>
<StorySection title="Radar + Lines Only">
<RadarLinesOnlyExample />
</StorySection>
<StorySection title="Radar + Custom Label">
<RadarCustomLabelExample />
</StorySection>
</div>
</StoryPage>
),
}
+48
View File
@@ -0,0 +1,48 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
RadialGridExample,
RadialLabelExample,
RadialShapeExample,
RadialSimpleExample,
RadialStackedExample,
RadialTextExample,
} from "@hm-design/chart/examples"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
// Sample data only. One example per official shadcn/ui radial chart (radial-simple, -label, -grid, -text, -shape, -stacked).
const meta = {
title: "Chart/Radial Chart",
parameters: { layout: "fullscreen", controls: { disable: true } },
} satisfies Meta
export default meta
type Story = StoryObj<typeof meta>
export const Radial: Story = {
name: "Radial Chart",
render: () => (
<StoryPage>
<StoryPageHeader title="Radial Chart" />
{/* Same grid as the other Chart pages: two per row from 1024px, one below; source order is the mobile order. */}
<div className="grid grid-cols-1 gap-x-6 lg:auto-rows-fr lg:grid-cols-2 *:flex *:min-w-0 *:flex-col [&_.sb-page-section-body]:flex-1">
<StorySection title="Radial Chart">
<RadialSimpleExample />
</StorySection>
<StorySection title="Radial Chart + Label">
<RadialLabelExample />
</StorySection>
<StorySection title="Radial Chart - Grid">
<RadialGridExample />
</StorySection>
<StorySection title="Radial Chart + Text">
<RadialTextExample />
</StorySection>
<StorySection title="Radial Chart - Shape">
<RadialShapeExample />
</StorySection>
<StorySection title="Radial Chart + Stacked">
<RadialStackedExample />
</StorySection>
</div>
</StoryPage>
),
}
+1
View File
@@ -1,4 +1,5 @@
@import "tailwindcss" source("./");
@import "@hm-design/ui/tailwind.css";
@import "@hm-design/chart/tailwind.css";
/* Keep utility detection relative to this entry in both the repo and extracted ZIP. */
@source "../.storybook";