@reopt-ai/opt-ui 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/COMPONENT_CATALOG.md +3000 -0
  3. package/LICENSE +21 -0
  4. package/README.md +244 -0
  5. package/dist/chunk-3GWWZKX7.js +38 -0
  6. package/dist/chunk-AFF2HPE5.cjs +5008 -0
  7. package/dist/chunk-ELWICXYY.js +4745 -0
  8. package/dist/chunk-N4NDU5ET.cjs +4745 -0
  9. package/dist/chunk-ONE3C5RV.cjs +38 -0
  10. package/dist/chunk-QWBHD54V.js +3218 -0
  11. package/dist/chunk-RBM2RNC2.js +5008 -0
  12. package/dist/chunk-X5WCXJAF.cjs +3218 -0
  13. package/dist/core/index.cjs +324 -0
  14. package/dist/core/index.d.cts +5 -0
  15. package/dist/core/index.d.ts +5 -0
  16. package/dist/core/index.js +324 -0
  17. package/dist/docs/01-getting-started.md +129 -0
  18. package/dist/docs/02-components/01-core.md +1841 -0
  19. package/dist/docs/02-components/02-visuals.md +11 -0
  20. package/dist/docs/02-components/03-shells.md +1361 -0
  21. package/dist/docs/02-components/04-surfaces.md +11 -0
  22. package/dist/docs/02-components/index.md +106 -0
  23. package/dist/docs/03-recipes/01-forms.md +471 -0
  24. package/dist/docs/03-recipes/02-dashboards.md +397 -0
  25. package/dist/docs/03-recipes/03-layouts.md +424 -0
  26. package/dist/docs/04-theming.md +232 -0
  27. package/dist/docs/05-migration/01-breaking-changes.md +117 -0
  28. package/dist/docs/05-migration/02-formstore.md +336 -0
  29. package/dist/docs/06-troubleshooting.md +119 -0
  30. package/dist/docs/index.md +71 -0
  31. package/dist/id-registry.cjs +1875 -0
  32. package/dist/id-registry.d.cts +27 -0
  33. package/dist/id-registry.d.ts +27 -0
  34. package/dist/id-registry.js +1875 -0
  35. package/dist/id-registry.json +3799 -0
  36. package/dist/index-BZ_lBlO1.d.ts +474 -0
  37. package/dist/index-BuvxoWHf.d.cts +474 -0
  38. package/dist/index-DlAcuvQp.d.cts +1686 -0
  39. package/dist/index-Slu5hOj1.d.ts +1686 -0
  40. package/dist/index.cjs +9959 -0
  41. package/dist/index.d.cts +2351 -0
  42. package/dist/index.d.ts +2351 -0
  43. package/dist/index.js +9959 -0
  44. package/dist/meta.cjs +6898 -0
  45. package/dist/meta.d.cts +36 -0
  46. package/dist/meta.d.ts +36 -0
  47. package/dist/meta.js +6869 -0
  48. package/dist/shells/index.cjs +65 -0
  49. package/dist/shells/index.d.cts +5 -0
  50. package/dist/shells/index.d.ts +5 -0
  51. package/dist/shells/index.js +65 -0
  52. package/dist/tailwind.css +401 -0
  53. package/dist/theme/presets/default.css +355 -0
  54. package/dist/theme/presets/minimal.css +354 -0
  55. package/dist/theme/presets/mono-dark.css +354 -0
  56. package/dist/theme/presets/natural.css +181 -0
  57. package/dist/theme/presets/pro.css +354 -0
  58. package/dist/types-D4-0lwaE.d.cts +298 -0
  59. package/dist/types-D4-0lwaE.d.ts +298 -0
  60. package/dist/visuals/index.cjs +4 -0
  61. package/dist/visuals/index.d.cts +1 -0
  62. package/dist/visuals/index.d.ts +1 -0
  63. package/dist/visuals/index.js +4 -0
  64. package/package.json +165 -0
@@ -0,0 +1,397 @@
1
+ ---
2
+ title: "Dashboard Recipes"
3
+ description: "opt-ui 대시보드 조합 패턴 3종 — KPI, Analytics, Activity"
4
+ related:
5
+ links:
6
+ - ../02-components/02-visuals.md
7
+ - ../02-components/03-shells.md
8
+ ---
9
+
10
+ <!-- AI agent hint: 차트 컴포넌트는 반드시 ChartContainer로 감쌀 것. ChartDataPoint[]의 name 필드는 필수. -->
11
+
12
+ # Dashboard Recipes
13
+
14
+ Copy-paste-ready dashboard composition patterns.
15
+ All imports from `@reopt-ai/opt-ui`.
16
+
17
+ ## 1. KPI Dashboard
18
+
19
+ SummaryRow for top-level stats, TrendChart for time series, DataTable for details.
20
+
21
+ ```tsx
22
+ "use client";
23
+
24
+ import { useState } from "react";
25
+ import {
26
+ SurfaceLayout,
27
+ PageHeader,
28
+ SummaryRow,
29
+ TrendChart,
30
+ DataTable,
31
+ Button,
32
+ type StatCard as StatCardType,
33
+ type ChartDataPoint,
34
+ type ChartSeriesDef,
35
+ type ColumnDef,
36
+ } from "@reopt-ai/opt-ui";
37
+
38
+ interface SalesRow {
39
+ id: string;
40
+ product: string;
41
+ revenue: number;
42
+ units: number;
43
+ growth: string;
44
+ }
45
+
46
+ const stats: StatCardType[] = [
47
+ {
48
+ id: "revenue",
49
+ title: "매출",
50
+ value: "₩45.2M",
51
+ change: "+12.5%",
52
+ trend: "up",
53
+ },
54
+ { id: "orders", title: "주문", value: "1,234", change: "+8.2%", trend: "up" },
55
+ {
56
+ id: "aov",
57
+ title: "객단가",
58
+ value: "₩36,600",
59
+ change: "-2.1%",
60
+ trend: "down",
61
+ },
62
+ {
63
+ id: "refund",
64
+ title: "환불률",
65
+ value: "3.2%",
66
+ change: "-0.5%",
67
+ trend: "up",
68
+ },
69
+ ];
70
+
71
+ const trendData: ChartDataPoint[] = [
72
+ { name: "1월", revenue: 32000, orders: 890 },
73
+ { name: "2월", revenue: 35000, orders: 920 },
74
+ { name: "3월", revenue: 38000, orders: 1010 },
75
+ { name: "4월", revenue: 41000, orders: 1100 },
76
+ { name: "5월", revenue: 45200, orders: 1234 },
77
+ ];
78
+
79
+ const trendSeries: ChartSeriesDef[] = [
80
+ { dataKey: "revenue", name: "매출" },
81
+ { dataKey: "orders", name: "주문" },
82
+ ];
83
+
84
+ const rows: SalesRow[] = [
85
+ {
86
+ id: "1",
87
+ product: "Pro Plan",
88
+ revenue: 18200000,
89
+ units: 340,
90
+ growth: "+15%",
91
+ },
92
+ { id: "2", product: "Starter", revenue: 12500000, units: 520, growth: "+8%" },
93
+ {
94
+ id: "3",
95
+ product: "Enterprise",
96
+ revenue: 14500000,
97
+ units: 90,
98
+ growth: "+22%",
99
+ },
100
+ ];
101
+
102
+ const columns: ColumnDef<SalesRow>[] = [
103
+ { id: "product", header: "상품", accessor: "product" },
104
+ {
105
+ id: "revenue",
106
+ header: "매출",
107
+ accessor: (r) => `₩${(r.revenue / 1e6).toFixed(1)}M`,
108
+ },
109
+ { id: "units", header: "판매", accessor: "units", align: "right" },
110
+ { id: "growth", header: "성장률", accessor: "growth", align: "right" },
111
+ ];
112
+
113
+ export function KpiDashboard() {
114
+ return (
115
+ <SurfaceLayout>
116
+ <PageHeader
117
+ title="매출 대시보드"
118
+ actions={<Button variant="secondary">리포트 다운로드</Button>}
119
+ />
120
+
121
+ <SummaryRow stats={stats} columns={4} />
122
+
123
+ <TrendChart
124
+ title="월별 추세"
125
+ description="매출 및 주문 수 추이"
126
+ data={trendData}
127
+ series={trendSeries}
128
+ height={280}
129
+ />
130
+
131
+ <DataTable
132
+ data={rows}
133
+ columns={columns}
134
+ keyExtractor={(r) => r.id}
135
+ title="상품별 실적"
136
+ sortable
137
+ />
138
+ </SurfaceLayout>
139
+ );
140
+ }
141
+ ```
142
+
143
+ ## 2. Analytics Overview
144
+
145
+ TimeRangeSelector for date filtering, LineChart and BarChart side by side, FilterBar for segmentation.
146
+
147
+ ```tsx
148
+ "use client";
149
+
150
+ import { useState } from "react";
151
+ import {
152
+ SurfaceLayout,
153
+ PageHeader,
154
+ TimeRangeSelector,
155
+ ChartContainer,
156
+ LineChart,
157
+ BarChart,
158
+ FilterBar,
159
+ type ChartDataPoint,
160
+ type ChartSeriesDef,
161
+ type FilterGroupDef,
162
+ type DateRange,
163
+ } from "@reopt-ai/opt-ui";
164
+
165
+ const pageViewData: ChartDataPoint[] = [
166
+ { name: "Mon", views: 1200, visitors: 800 },
167
+ { name: "Tue", views: 1400, visitors: 950 },
168
+ { name: "Wed", views: 1100, visitors: 750 },
169
+ { name: "Thu", views: 1600, visitors: 1100 },
170
+ { name: "Fri", views: 1800, visitors: 1250 },
171
+ { name: "Sat", views: 900, visitors: 600 },
172
+ { name: "Sun", views: 700, visitors: 450 },
173
+ ];
174
+
175
+ const lineSeries: ChartSeriesDef[] = [
176
+ { dataKey: "views", name: "페이지뷰" },
177
+ { dataKey: "visitors", name: "방문자" },
178
+ ];
179
+
180
+ const channelData: ChartDataPoint[] = [
181
+ { name: "Organic", sessions: 4500 },
182
+ { name: "Direct", sessions: 3200 },
183
+ { name: "Social", sessions: 2100 },
184
+ { name: "Referral", sessions: 1800 },
185
+ { name: "Paid", sessions: 1200 },
186
+ ];
187
+
188
+ const barSeries: ChartSeriesDef[] = [{ dataKey: "sessions", name: "세션" }];
189
+
190
+ const filters: FilterGroupDef[] = [
191
+ {
192
+ id: "channel",
193
+ label: "채널",
194
+ type: "select",
195
+ options: [
196
+ { id: "all", label: "전체", value: "all" },
197
+ { id: "organic", label: "Organic", value: "organic" },
198
+ { id: "paid", label: "Paid", value: "paid" },
199
+ ],
200
+ value: "all",
201
+ },
202
+ {
203
+ id: "device",
204
+ label: "디바이스",
205
+ type: "multi-select",
206
+ options: [
207
+ { id: "desktop", label: "Desktop", value: "desktop" },
208
+ { id: "mobile", label: "Mobile", value: "mobile" },
209
+ { id: "tablet", label: "Tablet", value: "tablet" },
210
+ ],
211
+ value: [],
212
+ },
213
+ ];
214
+
215
+ export function AnalyticsOverview() {
216
+ const [timeRange, setTimeRange] = useState<DateRange>({
217
+ start: null,
218
+ end: null,
219
+ });
220
+
221
+ return (
222
+ <SurfaceLayout>
223
+ <PageHeader
224
+ title="분석 개요"
225
+ description="트래픽 및 채널 분석"
226
+ actions={
227
+ <TimeRangeSelector value={timeRange} onChange={setTimeRange} />
228
+ }
229
+ />
230
+
231
+ <FilterBar
232
+ filters={filters}
233
+ onFilterChange={(id, value) => console.log(id, value)}
234
+ />
235
+
236
+ <div className="gap-group grid grid-cols-1 lg:grid-cols-2">
237
+ <div className="gap-element flex flex-col">
238
+ <h3 className="text-text-primary text-sm font-medium">일별 트래픽</h3>
239
+ <ChartContainer height={280}>
240
+ <LineChart data={pageViewData} series={lineSeries} />
241
+ </ChartContainer>
242
+ </div>
243
+
244
+ <div className="gap-element flex flex-col">
245
+ <h3 className="text-text-primary text-sm font-medium">채널별 세션</h3>
246
+ <ChartContainer height={280}>
247
+ <BarChart data={channelData} series={barSeries} />
248
+ </ChartContainer>
249
+ </div>
250
+ </div>
251
+ </SurfaceLayout>
252
+ );
253
+ }
254
+ ```
255
+
256
+ ## 3. Activity Dashboard
257
+
258
+ ActivityFeed for recent events, TaskList for action items, MetricsCard grid for quick stats.
259
+
260
+ ```tsx
261
+ "use client";
262
+
263
+ import {
264
+ SurfaceLayout,
265
+ PageHeader,
266
+ ActivityFeed,
267
+ TaskList,
268
+ MetricsCard,
269
+ Button,
270
+ type ActivityDef,
271
+ type TaskDef,
272
+ type TeamMemberDef,
273
+ } from "@reopt-ai/opt-ui";
274
+
275
+ const actor: TeamMemberDef = {
276
+ id: "u1",
277
+ name: "김개발",
278
+ email: "dev@example.com",
279
+ role: "Engineer",
280
+ status: "online",
281
+ };
282
+
283
+ const activities: ActivityDef[] = [
284
+ {
285
+ id: "a1",
286
+ type: "deployed",
287
+ title: "v2.4.0 프로덕션 배포",
288
+ actor,
289
+ timestamp: new Date(Date.now() - 1000 * 60 * 30).toISOString(),
290
+ },
291
+ {
292
+ id: "a2",
293
+ type: "merged",
294
+ title: "feat: 대시보드 리팩토링 PR 병합",
295
+ actor,
296
+ timestamp: new Date(Date.now() - 1000 * 60 * 120).toISOString(),
297
+ },
298
+ {
299
+ id: "a3",
300
+ type: "completed",
301
+ title: "API 마이그레이션 완료",
302
+ description: "REST → GraphQL 전환 작업",
303
+ actor,
304
+ timestamp: new Date(Date.now() - 1000 * 60 * 300).toISOString(),
305
+ },
306
+ ];
307
+
308
+ const tasks: TaskDef[] = [
309
+ {
310
+ id: "t1",
311
+ title: "인증 토큰 만료 처리",
312
+ status: "in-progress",
313
+ priority: "high",
314
+ assignee: actor,
315
+ dueDate: "2026-04-05",
316
+ },
317
+ {
318
+ id: "t2",
319
+ title: "대시보드 성능 최적화",
320
+ status: "todo",
321
+ priority: "medium",
322
+ tags: ["performance"],
323
+ },
324
+ {
325
+ id: "t3",
326
+ title: "E2E 테스트 추가",
327
+ description: "핵심 플로우 커버리지 확보",
328
+ status: "todo",
329
+ priority: "low",
330
+ },
331
+ ];
332
+
333
+ export function ActivityDashboard() {
334
+ return (
335
+ <SurfaceLayout>
336
+ <PageHeader
337
+ title="활동 대시보드"
338
+ actions={<Button variant="secondary">전체 보기</Button>}
339
+ />
340
+
341
+ <div className="gap-group grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
342
+ <MetricsCard
343
+ title="오늘 배포"
344
+ value={3}
345
+ change="+2"
346
+ trend="up"
347
+ sparklineData={[1, 0, 2, 1, 3]}
348
+ />
349
+ <MetricsCard
350
+ title="열린 PR"
351
+ value={7}
352
+ change="-1"
353
+ trend="down"
354
+ sparklineData={[9, 8, 10, 8, 7]}
355
+ />
356
+ <MetricsCard
357
+ title="완료 태스크"
358
+ value={12}
359
+ change="+4"
360
+ trend="up"
361
+ sparklineData={[5, 6, 8, 10, 12]}
362
+ />
363
+ <MetricsCard
364
+ title="미해결 이슈"
365
+ value={23}
366
+ trend="neutral"
367
+ sparklineData={[25, 24, 23, 24, 23]}
368
+ />
369
+ </div>
370
+
371
+ <div className="gap-section grid grid-cols-1 lg:grid-cols-2">
372
+ <ActivityFeed activities={activities} maxItems={5} />
373
+ <TaskList
374
+ tasks={tasks}
375
+ onTaskToggle={(id) => console.log("toggle", id)}
376
+ />
377
+ </div>
378
+ </SurfaceLayout>
379
+ );
380
+ }
381
+ ```
382
+
383
+ ## Composition Notes
384
+
385
+ | Pattern | Key Components | Data Types |
386
+ | ------------------- | ---------------------------------------- | ---------------------------------------- |
387
+ | KPI stats row | `SummaryRow` | `StatCard[]` (aliased as `StatCardType`) |
388
+ | Trend visualization | `TrendChart` or `ChartContainer` + chart | `ChartDataPoint[]` + `ChartSeriesDef[]` |
389
+ | Tabular detail | `DataTable` | `ColumnDef<T>[]` + data array |
390
+ | Date filtering | `TimeRangeSelector` | `DateRange` |
391
+ | Segment filtering | `FilterBar` | `FilterGroupDef[]` |
392
+ | Quick metrics | `MetricsCard` | inline props + `sparklineData: number[]` |
393
+ | Activity timeline | `ActivityFeed` | `ActivityDef[]` |
394
+ | Task tracking | `TaskList` | `TaskDef[]` |
395
+
396
+ All page-level layouts use `SurfaceLayout` for consistent `gap-section` spacing.
397
+ Use `gap-group` for grid gaps and `gap-element` for tightly spaced items.