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

@@ -0,0 +1,205 @@
import { useState, type ReactNode } from "react"
import { cn } from "cn"
import {
Badge,
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@hm-design/ui"
import { HMBarChart, HMDonutChart, HMHorizontalBarChart, HMLineChart, HMSignedBarChart } from "../index"
import { sampleBar, sampleDataNotice, sampleDonut, sampleDonutStates, sampleHorizontalBar, sampleLine, sampleLineCompare, sampleLinePointColors, sampleSignedBar } from "./sample-data"
/** Marks a chart as sample data. Use it next to every chart that is fed with `sample-data`. */
export function SampleDataBadge() {
return <Badge variant="outline" data-slot="chart-sample-badge" title={sampleDataNotice}>예시 데이터</Badge>
}
export function ExampleCard({ title, description, className, toolbar, children }: { title: string; description: string; className?: string; toolbar?: ReactNode; children: ReactNode }) {
return (
// h-full lets a card fill its grid cell; the notice sits at the card bottom so cards of one row line up.
<Card className={cn("h-full", className)} data-chart-example={title}>
<CardHeader>
<CardTitle className="break-keep">{title}</CardTitle>
<CardDescription className="break-keep">{description}</CardDescription>
<CardAction><SampleDataBadge /></CardAction>
</CardHeader>
<CardContent className="flex flex-1 flex-col">
{toolbar !== undefined ? <div data-slot="chart-example-toolbar" className="mb-3 flex min-h-[34px] items-center text-sm text-muted-foreground">{toolbar}</div> : null}
{children}
<p data-slot="chart-sample-notice" className="mt-auto pt-3">{sampleDataNotice}</p>
</CardContent>
</Card>
)
}
export function BarChartExample({ className }: { className?: string }) {
return (
<ExampleCard title="월별 접수·완료 현황" description="막대 차트 · 상반기 월별 건수" className={className}>
<HMBarChart {...sampleBar} label="월별 접수·완료 건수 막대 차트(예시 데이터)" />
</ExampleCard>
)
}
export function StackedBarChartExample({ className }: { className?: string }) {
return (
<ExampleCard title="월별 접수·완료 현황(누적)" description="막대 차트 · stacked" className={className}>
<HMBarChart {...sampleBar} stacked label="월별 접수·완료 건수 누적 막대 차트(예시 데이터)" />
</ExampleCard>
)
}
export function HorizontalBarChartExample({ className }: { className?: string }) {
return (
<ExampleCard title="항목별 처리 건수" description="가로 막대 차트 · 항목별 건수" className={className}>
<HMHorizontalBarChart {...sampleHorizontalBar} label="항목별 처리 건수 가로 막대 차트(예시 데이터)" />
</ExampleCard>
)
}
export function SignedBarChartExample({ className }: { className?: string }) {
return (
<ExampleCard title="월별 순증감" description="막대 차트 · 0 기준 양수·음수" className={className}>
<HMSignedBarChart {...sampleSignedBar} label="월별 순증감 양수·음수 막대 차트(예시 데이터)" />
</ExampleCard>
)
}
export function LineChartExample({ className }: { className?: string }) {
return (
<ExampleCard title="월별 처리 시간 추이" description="선 차트 · 단위: 시간" className={className}>
<HMLineChart {...sampleLine} label="월별 평균 처리 시간 선 차트(예시 데이터)" />
</ExampleCard>
)
}
export function LineCurveExample({ className }: { className?: string }) {
return (
<ExampleCard title="곡선형" description="monotone · Dot 없음" className={className}>
<HMLineChart {...sampleLineCompare} curve="monotone" dots={false} label="월별 접수·완료 곡선형 선 차트(예시 데이터)" />
</ExampleCard>
)
}
export function LineCurveDotLabelExample({ className }: { className?: string }) {
return (
<ExampleCard title="곡선형 · Dot + Label" description="monotone · Dot과 값 Label" className={className}>
<HMLineChart {...sampleLineCompare} curve="monotone" pointLabels label="월별 접수·완료 곡선형 Dot·값 라벨 선 차트(예시 데이터)" />
</ExampleCard>
)
}
export function LineStraightExample({ className }: { className?: string }) {
return (
<ExampleCard title="직선형" description="linear · Dot 없음" className={className}>
<HMLineChart {...sampleLineCompare} curve="linear" dots={false} label="월별 접수·완료 직선형 선 차트(예시 데이터)" />
</ExampleCard>
)
}
export function LineStraightColorLabelExample({ className }: { className?: string }) {
return (
<ExampleCard title="직선형 · Color Dot" description="linear · Dot 개별 색상 + 값 Label" className={className}>
<HMLineChart {...sampleLinePointColors} curve="linear" pointLabels label="월별 접수·완료 직선형 포인트별 색상 선 차트(예시 데이터)" />
</ExampleCard>
)
}
export function LineStepExample({ className }: { className?: string }) {
return (
<ExampleCard title="계단형" description="step · Dot 없음" className={className}>
<HMLineChart {...sampleLineCompare} curve="step" dots={false} label="월별 접수·완료 계단형 선 차트(예시 데이터)" />
</ExampleCard>
)
}
export function LineStepDotLabelExample({ className }: { className?: string }) {
return (
<ExampleCard title="계단형 · Dot + Label" description="step · Dot과 값 Label" className={className}>
<HMLineChart {...sampleLineCompare} curve="step" pointLabels label="월별 접수·완료 계단형 Dot·값 라벨 선 차트(예시 데이터)" />
</ExampleCard>
)
}
export function DonutChartExample({ className }: { className?: string }) {
return (
<ExampleCard title="업무 상태 비중" description="도넛 차트 · 상태별 건수" className={className}>
<HMDonutChart {...sampleDonut} label="업무 상태별 비중 도넛 차트(예시 데이터)" />
</ExampleCard>
)
}
/** Bar, line and donut examples: two per row from 1024px (HM desktop boundary), one per row below. With an odd count the last card keeps one column's width. */
export function ChartExamples() {
return (
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2" data-chart-examples="">
<BarChartExample className="min-w-0" />
<LineChartExample className="min-w-0" />
<DonutChartExample className="min-w-0" />
</div>
)
}
// Four donut states on one data set: plain, center total, active (fixed selection) and interactive (Select + slice click).
// The toolbar row has the Select height in every card, so the donuts of a row start at the same height.
export function DonutPlainExample({ className }: { className?: string }) {
return (
<ExampleCard title="기본형" description="도넛 차트 · 기본" className={className} toolbar="중앙 표시 없음">
<HMDonutChart data={sampleDonutStates.data} label="업무 상태 도넛 차트, 가운데 값 없음(예시 데이터)" />
</ExampleCard>
)
}
export function DonutCenterExample({ className }: { className?: string }) {
return (
<ExampleCard title="기본형 + 텍스트" description="도넛 차트 · 전체 합계" className={className} toolbar="중앙 표시: 전체 합계">
<HMDonutChart data={sampleDonutStates.data} totalLabel={sampleDonutStates.totalLabel} label="업무 상태 도넛 차트, 가운데 전체 합계(예시 데이터)" />
</ExampleCard>
)
}
export function DonutActiveExample({ className }: { className?: string }) {
const selected = sampleDonutStates.data.find(item => item.id === sampleDonutStates.activeId)
return (
<ExampleCard title="Active 상태" description="도넛 차트 · 선택된 항목" className={className} toolbar={`선택 항목: ${selected?.label ?? ""}`}>
<HMDonutChart data={sampleDonutStates.data} activeId={sampleDonutStates.activeId} label="업무 상태 도넛 차트, 선택된 항목 강조(예시 데이터)" />
</ExampleCard>
)
}
export function DonutInteractiveExample({ className }: { className?: string }) {
const [activeId, setActiveId] = useState(sampleDonutStates.activeId)
return (
<ExampleCard
title="Interactive"
description="도넛 차트 · 선택 또는 클릭"
className={className}
toolbar={
<Select value={activeId} onValueChange={setActiveId}>
<SelectTrigger size="medium" aria-label="강조할 업무 상태 선택" className="w-full max-w-64">
<SelectValue />
</SelectTrigger>
<SelectContent>
{sampleDonutStates.data.map(item => (
<SelectItem key={item.id} value={item.id}>{item.label}</SelectItem>
))}
</SelectContent>
</Select>
}
>
<HMDonutChart
data={sampleDonutStates.data}
activeId={activeId}
onActiveChange={setActiveId}
label="업무 상태 도넛 차트, 항목을 선택하거나 조각을 클릭해 강조(예시 데이터)"
/>
</ExampleCard>
)
}