@iamfiro/design 0.0.0-stage → 0.1.0

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 (264) hide show
  1. package/AGENTS.md +81 -0
  2. package/README.md +15 -2
  3. package/agents/INDEX.md +156 -0
  4. package/agents/accordion/AGENTS.md +279 -0
  5. package/agents/action-bar/AGENTS.md +90 -0
  6. package/agents/alert/AGENTS.md +212 -0
  7. package/agents/alert-dialog/AGENTS.md +193 -0
  8. package/agents/app-shell/AGENTS.md +409 -0
  9. package/agents/area-chart/AGENTS.md +183 -0
  10. package/agents/aspect-ratio/AGENTS.md +143 -0
  11. package/agents/avatar/AGENTS.md +130 -0
  12. package/agents/badge/AGENTS.md +217 -0
  13. package/agents/bar-chart/AGENTS.md +386 -0
  14. package/agents/bar-list/AGENTS.md +223 -0
  15. package/agents/brand-icon/AGENTS.md +82 -0
  16. package/agents/breadcrumb/AGENTS.md +133 -0
  17. package/agents/button/AGENTS.md +195 -0
  18. package/agents/card/AGENTS.md +141 -0
  19. package/agents/card-frame/AGENTS.md +227 -0
  20. package/agents/chat-bubble/AGENTS.md +266 -0
  21. package/agents/chat-composer/AGENTS.md +251 -0
  22. package/agents/checkbox/AGENTS.md +113 -0
  23. package/agents/code-block/AGENTS.md +144 -0
  24. package/agents/color-dot/AGENTS.md +120 -0
  25. package/agents/color-picker/AGENTS.md +224 -0
  26. package/agents/colors/AGENTS.md +168 -0
  27. package/agents/combobox/AGENTS.md +218 -0
  28. package/agents/command-menu/AGENTS.md +169 -0
  29. package/agents/comment/AGENTS.md +206 -0
  30. package/agents/context-menu/AGENTS.md +303 -0
  31. package/agents/copy-button/AGENTS.md +84 -0
  32. package/agents/count-badge/AGENTS.md +115 -0
  33. package/agents/countdown/AGENTS.md +101 -0
  34. package/agents/data-grid/AGENTS.md +460 -0
  35. package/agents/date-picker/AGENTS.md +197 -0
  36. package/agents/delta/AGENTS.md +114 -0
  37. package/agents/description-list/AGENTS.md +251 -0
  38. package/agents/dialog/AGENTS.md +391 -0
  39. package/agents/drawer/AGENTS.md +471 -0
  40. package/agents/dropdown-menu/AGENTS.md +322 -0
  41. package/agents/dropzone/AGENTS.md +116 -0
  42. package/agents/elapsed/AGENTS.md +160 -0
  43. package/agents/empty-state/AGENTS.md +203 -0
  44. package/agents/file-chip/AGENTS.md +79 -0
  45. package/agents/file-icon/AGENTS.md +66 -0
  46. package/agents/file-upload/AGENTS.md +168 -0
  47. package/agents/filter-chips/AGENTS.md +167 -0
  48. package/agents/flag/AGENTS.md +67 -0
  49. package/agents/funnel-chart/AGENTS.md +131 -0
  50. package/agents/highlight/AGENTS.md +119 -0
  51. package/agents/hover-card/AGENTS.md +271 -0
  52. package/agents/icon-stack/AGENTS.md +120 -0
  53. package/agents/icon-tile/AGENTS.md +94 -0
  54. package/agents/inline-diff/AGENTS.md +211 -0
  55. package/agents/input/AGENTS.md +261 -0
  56. package/agents/kanban/AGENTS.md +352 -0
  57. package/agents/kbd/AGENTS.md +160 -0
  58. package/agents/layout/AGENTS.md +351 -0
  59. package/agents/line-chart/AGENTS.md +393 -0
  60. package/agents/link/AGENTS.md +53 -0
  61. package/agents/list/AGENTS.md +392 -0
  62. package/agents/marker/AGENTS.md +142 -0
  63. package/agents/masked-text/AGENTS.md +127 -0
  64. package/agents/menubar/AGENTS.md +276 -0
  65. package/agents/multi-select/AGENTS.md +159 -0
  66. package/agents/navigation-menu/AGENTS.md +315 -0
  67. package/agents/notice-stack/AGENTS.md +101 -0
  68. package/agents/number-stepper/AGENTS.md +96 -0
  69. package/agents/otp-input/AGENTS.md +162 -0
  70. package/agents/pagination/AGENTS.md +189 -0
  71. package/agents/phone-input/AGENTS.md +131 -0
  72. package/agents/pie-chart/AGENTS.md +243 -0
  73. package/agents/popover/AGENTS.md +190 -0
  74. package/agents/progress/AGENTS.md +273 -0
  75. package/agents/progress-tracker/AGENTS.md +305 -0
  76. package/agents/rating/AGENTS.md +165 -0
  77. package/agents/relative-time/AGENTS.md +103 -0
  78. package/agents/resizable/AGENTS.md +390 -0
  79. package/agents/scaled-preview/AGENTS.md +111 -0
  80. package/agents/scatter-chart/AGENTS.md +160 -0
  81. package/agents/scroll-area/AGENTS.md +271 -0
  82. package/agents/search-input/AGENTS.md +105 -0
  83. package/agents/segmented-control/AGENTS.md +153 -0
  84. package/agents/select/AGENTS.md +159 -0
  85. package/agents/selector-button/AGENTS.md +122 -0
  86. package/agents/sidebar/AGENTS.md +201 -0
  87. package/agents/skeleton/AGENTS.md +166 -0
  88. package/agents/slider/AGENTS.md +181 -0
  89. package/agents/spark-chart/AGENTS.md +257 -0
  90. package/agents/sparkbar/AGENTS.md +92 -0
  91. package/agents/spinner/AGENTS.md +123 -0
  92. package/agents/split-view/AGENTS.md +290 -0
  93. package/agents/stat/AGENTS.md +138 -0
  94. package/agents/status-icon/AGENTS.md +243 -0
  95. package/agents/step-list/AGENTS.md +313 -0
  96. package/agents/table/AGENTS.md +286 -0
  97. package/agents/tabs/AGENTS.md +305 -0
  98. package/agents/tag-input/AGENTS.md +246 -0
  99. package/agents/time-picker/AGENTS.md +228 -0
  100. package/agents/timeline/AGENTS.md +1018 -0
  101. package/agents/toast/AGENTS.md +266 -0
  102. package/agents/toggle-group/AGENTS.md +216 -0
  103. package/agents/toolbar/AGENTS.md +277 -0
  104. package/agents/tooltip/AGENTS.md +154 -0
  105. package/agents/tracker/AGENTS.md +158 -0
  106. package/agents/tree-diagram/AGENTS.md +260 -0
  107. package/agents/tree-view/AGENTS.md +329 -0
  108. package/agents/typography/AGENTS.md +396 -0
  109. package/lib/index.js +17914 -0
  110. package/lib/style.css +2 -0
  111. package/lib/types/components/Accordion/Accordion.d.ts +57 -0
  112. package/lib/types/components/ActionBar/ActionBar.d.ts +24 -0
  113. package/lib/types/components/Alert/Alert.d.ts +36 -0
  114. package/lib/types/components/AppShell/AppShell.d.ts +38 -0
  115. package/lib/types/components/AreaChart/AreaChart.d.ts +17 -0
  116. package/lib/types/components/BarChart/BarChart.d.ts +57 -0
  117. package/lib/types/components/BarList/BarList.d.ts +56 -0
  118. package/lib/types/components/Breadcrumb/Breadcrumb.d.ts +15 -0
  119. package/lib/types/components/Calendar/Calendar.d.ts +34 -0
  120. package/lib/types/components/Calendar/DatePicker.d.ts +45 -0
  121. package/lib/types/components/Calendar/date.d.ts +32 -0
  122. package/lib/types/components/Card/Card.d.ts +64 -0
  123. package/lib/types/components/CardFrame/CardFrame.d.ts +15 -0
  124. package/lib/types/components/Chart/ChartParts.d.ts +38 -0
  125. package/lib/types/components/Chart/chart.d.ts +46 -0
  126. package/lib/types/components/Chart/useChartInteraction.d.ts +39 -0
  127. package/lib/types/components/ChatBubble/ChatBubble.d.ts +61 -0
  128. package/lib/types/components/ChatBubble/formatChatTime.d.ts +2 -0
  129. package/lib/types/components/ChatComposer/ChatComposer.d.ts +42 -0
  130. package/lib/types/components/CodeBlock/CodeBlock.d.ts +18 -0
  131. package/lib/types/components/CodeBlock/highlight.d.ts +10 -0
  132. package/lib/types/components/ColorPicker/ColorPicker.d.ts +61 -0
  133. package/lib/types/components/ColorPicker/color.d.ts +48 -0
  134. package/lib/types/components/ColorPicker/presets.d.ts +7 -0
  135. package/lib/types/components/Combobox/Combobox.d.ts +48 -0
  136. package/lib/types/components/CommandMenu/CommandMenu.d.ts +51 -0
  137. package/lib/types/components/Comment/Comment.d.ts +45 -0
  138. package/lib/types/components/ContextMenu/ContextMenu.d.ts +40 -0
  139. package/lib/types/components/DataGrid/DataGrid.d.ts +43 -0
  140. package/lib/types/components/DataGrid/GridPopover.d.ts +43 -0
  141. package/lib/types/components/DataGrid/anchor.d.ts +5 -0
  142. package/lib/types/components/DataGrid/model.d.ts +61 -0
  143. package/lib/types/components/DataGrid/panels.d.ts +63 -0
  144. package/lib/types/components/DataGrid/typeMeta.d.ts +6 -0
  145. package/lib/types/components/DataGrid/types.d.ts +77 -0
  146. package/lib/types/components/DescriptionList/DescriptionList.d.ts +77 -0
  147. package/lib/types/components/Dialog/AlertDialog.d.ts +26 -0
  148. package/lib/types/components/Dialog/Dialog.d.ts +42 -0
  149. package/lib/types/components/Dialog/modal.d.ts +38 -0
  150. package/lib/types/components/Drawer/Drawer.d.ts +62 -0
  151. package/lib/types/components/DropdownMenu/DropdownMenu.d.ts +97 -0
  152. package/lib/types/components/DropdownMenu/menuCore.d.ts +23 -0
  153. package/lib/types/components/Dropzone/Dropzone.d.ts +30 -0
  154. package/lib/types/components/EmptyState/EmptyState.d.ts +26 -0
  155. package/lib/types/components/FileChip/FileChip.d.ts +32 -0
  156. package/lib/types/components/FileUpload/FileUpload.d.ts +52 -0
  157. package/lib/types/components/FilterChips/FilterChips.d.ts +37 -0
  158. package/lib/types/components/FunnelChart/FunnelChart.d.ts +38 -0
  159. package/lib/types/components/FunnelChart/funnel.d.ts +25 -0
  160. package/lib/types/components/Highlight/Highlight.d.ts +35 -0
  161. package/lib/types/components/HoverCard/HoverCard.d.ts +47 -0
  162. package/lib/types/components/InlineDiff/InlineDiff.d.ts +36 -0
  163. package/lib/types/components/InlineDiff/diff.d.ts +42 -0
  164. package/lib/types/components/InlineDiff/useDiffReview.d.ts +57 -0
  165. package/lib/types/components/Kanban/Kanban.d.ts +64 -0
  166. package/lib/types/components/Kanban/KanbanCard.d.ts +35 -0
  167. package/lib/types/components/Kanban/move.d.ts +25 -0
  168. package/lib/types/components/LineChart/LineChart.d.ts +85 -0
  169. package/lib/types/components/List/List.d.ts +62 -0
  170. package/lib/types/components/Listbox/Listbox.d.ts +60 -0
  171. package/lib/types/components/Marker/Marker.d.ts +28 -0
  172. package/lib/types/components/Menubar/Menubar.d.ts +50 -0
  173. package/lib/types/components/MultiSelect/MultiSelect.d.ts +38 -0
  174. package/lib/types/components/NavigationMenu/NavigationMenu.d.ts +101 -0
  175. package/lib/types/components/NoticeStack/NoticeStack.d.ts +41 -0
  176. package/lib/types/components/NumberStepper/NumberStepper.d.ts +32 -0
  177. package/lib/types/components/OtpInput/OtpInput.d.ts +31 -0
  178. package/lib/types/components/Pagination/Pagination.d.ts +54 -0
  179. package/lib/types/components/Pagination/paginationRange.d.ts +7 -0
  180. package/lib/types/components/PhoneInput/PhoneInput.d.ts +47 -0
  181. package/lib/types/components/PieChart/PieChart.d.ts +67 -0
  182. package/lib/types/components/PieChart/arc.d.ts +26 -0
  183. package/lib/types/components/PieChart/useArcTween.d.ts +15 -0
  184. package/lib/types/components/Popover/Popover.d.ts +28 -0
  185. package/lib/types/components/ProgressTracker/ProgressTracker.d.ts +64 -0
  186. package/lib/types/components/Rating/Rating.d.ts +42 -0
  187. package/lib/types/components/Resizable/Resizable.d.ts +50 -0
  188. package/lib/types/components/Resizable/layout.d.ts +31 -0
  189. package/lib/types/components/ScaledPreview/ScaledPreview.d.ts +38 -0
  190. package/lib/types/components/ScatterChart/ScatterChart.d.ts +81 -0
  191. package/lib/types/components/ScatterChart/scatter.d.ts +14 -0
  192. package/lib/types/components/ScrollArea/ScrollArea.d.ts +34 -0
  193. package/lib/types/components/Select/Select.d.ts +39 -0
  194. package/lib/types/components/Sidebar/Sidebar.d.ts +39 -0
  195. package/lib/types/components/Skeleton/Skeleton.d.ts +47 -0
  196. package/lib/types/components/Slider/Slider.d.ts +36 -0
  197. package/lib/types/components/SparkChart/SparkChart.d.ts +55 -0
  198. package/lib/types/components/SparkChart/spark.d.ts +16 -0
  199. package/lib/types/components/Sparkbar/Sparkbar.d.ts +39 -0
  200. package/lib/types/components/SplitView/SplitView.d.ts +31 -0
  201. package/lib/types/components/Stat/Stat.d.ts +58 -0
  202. package/lib/types/components/StepList/StepList.d.ts +61 -0
  203. package/lib/types/components/Table/Table.d.ts +39 -0
  204. package/lib/types/components/Tabs/Tabs.d.ts +47 -0
  205. package/lib/types/components/TagInput/TagInput.d.ts +46 -0
  206. package/lib/types/components/TimePicker/TimePicker.d.ts +43 -0
  207. package/lib/types/components/TimePicker/time.d.ts +15 -0
  208. package/lib/types/components/Timeline/Timeline.d.ts +154 -0
  209. package/lib/types/components/Timeline/scale.d.ts +22 -0
  210. package/lib/types/components/Toast/Toaster.d.ts +19 -0
  211. package/lib/types/components/Toast/index.d.ts +4 -0
  212. package/lib/types/components/Toast/store.d.ts +75 -0
  213. package/lib/types/components/ToggleGroup/ToggleGroup.d.ts +28 -0
  214. package/lib/types/components/Toolbar/Toolbar.d.ts +70 -0
  215. package/lib/types/components/Tracker/Tracker.d.ts +43 -0
  216. package/lib/types/components/TreeDiagram/TreeDiagram.d.ts +68 -0
  217. package/lib/types/components/TreeView/TreeView.d.ts +107 -0
  218. package/lib/types/components/index.d.ts +186 -0
  219. package/lib/types/hooks/index.d.ts +17 -0
  220. package/lib/types/hooks/timing.d.ts +29 -0
  221. package/lib/types/hooks/useControllableState.d.ts +5 -0
  222. package/lib/types/hooks/useCountdown.d.ts +19 -0
  223. package/lib/types/hooks/useDigitInput.d.ts +24 -0
  224. package/lib/types/hooks/useDismiss.d.ts +3 -0
  225. package/lib/types/hooks/useElapsed.d.ts +28 -0
  226. package/lib/types/hooks/useFloating.d.ts +21 -0
  227. package/lib/types/hooks/useFocusTrap.d.ts +7 -0
  228. package/lib/types/hooks/usePresence.d.ts +8 -0
  229. package/lib/types/hooks/usePressToOpen.d.ts +20 -0
  230. package/lib/types/hooks/useScrollLock.d.ts +2 -0
  231. package/lib/types/hooks/useSelection.d.ts +29 -0
  232. package/lib/types/index.d.ts +5 -0
  233. package/lib/types/primitives/AspectRatio.d.ts +14 -0
  234. package/lib/types/primitives/BrandIcon.d.ts +12 -0
  235. package/lib/types/primitives/ColorDot.d.ts +20 -0
  236. package/lib/types/primitives/CopyButton.d.ts +12 -0
  237. package/lib/types/primitives/CountBadge.d.ts +28 -0
  238. package/lib/types/primitives/Countdown.d.ts +21 -0
  239. package/lib/types/primitives/Delta.d.ts +33 -0
  240. package/lib/types/primitives/Elapsed.d.ts +23 -0
  241. package/lib/types/primitives/FileIcon.d.ts +17 -0
  242. package/lib/types/primitives/Flag.d.ts +15 -0
  243. package/lib/types/primitives/IconStack.d.ts +27 -0
  244. package/lib/types/primitives/IconTile.d.ts +24 -0
  245. package/lib/types/primitives/Kbd.d.ts +19 -0
  246. package/lib/types/primitives/Layout.d.ts +64 -0
  247. package/lib/types/primitives/Link.d.ts +12 -0
  248. package/lib/types/primitives/MaskedText.d.ts +28 -0
  249. package/lib/types/primitives/Progress.d.ts +52 -0
  250. package/lib/types/primitives/RelativeTime.d.ts +22 -0
  251. package/lib/types/primitives/SearchInput.d.ts +8 -0
  252. package/lib/types/primitives/SelectorButton.d.ts +23 -0
  253. package/lib/types/primitives/Spinner.d.ts +15 -0
  254. package/lib/types/primitives/StatusIcon.d.ts +40 -0
  255. package/lib/types/primitives/Swatch.d.ts +11 -0
  256. package/lib/types/primitives/Tooltip.d.ts +17 -0
  257. package/lib/types/primitives/Typography.d.ts +48 -0
  258. package/lib/types/primitives/formatCountdown.d.ts +2 -0
  259. package/lib/types/primitives/formatElapsed.d.ts +8 -0
  260. package/lib/types/primitives/formatRelativeTime.d.ts +16 -0
  261. package/lib/types/primitives/hotkey.d.ts +10 -0
  262. package/lib/types/primitives/index.d.ts +188 -0
  263. package/lib/types/primitives/maskValue.d.ts +24 -0
  264. package/package.json +54 -4
@@ -0,0 +1,386 @@
1
+ # BarChart
2
+
3
+ > 차트 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 항목끼리 크기를 비교하는 막대 차트. LineChart와 같은 축·툴팁·범례를 써요. 마우스를 올린 칸은 옅은 회색으로 채워지고 나머지 막대는 흐려지며, 막대는 바닥에서 자라고 데이터가 바뀌면 새 높이로 미끄러져요.
6
+
7
+ ```tsx
8
+ import { BarChart, type BarSeries } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 차트: 시리즈 색은 `--chart-1…6`을 순서대로, 그리드는 `--chart-grid`로 아주 옅게(세로선·축선 없음). 범례·툴팁의 색 표시는 `ColorDot`, 툴팁은 `Card raised` + `Separator`로 조립. 축·툴팁·범례·호버/키보드는 `components/Chart/`의 공용 조각(`ChartTooltip`, `ChartLegend`, `useChartInteraction`)을 쓴다. → `LineChart` · `BarChart` · `PieChart` · `Sparkbar`
14
+ - 차트에서 한 칸을 고르는 화면(피벗)은 `BarChart selected` + `Table selectedKey`를 같은 상태로 묶는다. 고르지 않은 칸은 옅은 색, 고른 칸의 x축 아이콘(`xIcon`)은 흰 칩으로 펼친다. 임계치·이상 구간은 `LineChart referenceLines` · `bands`, 실시간 데이터는 `streaming`(모양 변형 대신 한 칸 미끄러짐) + `endDot="pulse"`.
15
+
16
+ ## API
17
+
18
+ ### BarChart
19
+
20
+ | prop | 타입 | 기본값 | 설명 |
21
+ | --- | --- | --- | --- |
22
+ | `data` (필수) | `BarChartRow[]` | | 행 목록. 한 행 = 막대 한 칸. |
23
+ | `xKey` (필수) | `string` | | x축 라벨이 든 필드. |
24
+ | `series` (필수) | `BarSeries[]` | | 시리즈. color를 생략하면 --chart-1부터 순서대로. |
25
+ | `stacked` | `boolean` | `false` | 한 막대에 쌓아요. |
26
+ | `height` | `number` | `240` | 축 라벨을 포함한 높이(px). |
27
+ | `yTickCount` | `number` | `3` | y축 라벨 개수(0 포함). |
28
+ | `yMax` | `number` | | y축 위 끝을 고정. |
29
+ | `formatValue` | `(value, series) => string` | `'1,234'` | 툴팁 값 형식. |
30
+ | `formatAxisValue` | `(value) => string` | `'6천'` | y축 라벨 형식. |
31
+ | `formatX / formatTitle` | `(row, index) => …` | | x축 라벨 · 툴팁 제목 형식. |
32
+ | `showTotal` | `boolean` | `stacked` | 툴팁 아래 합계 행. |
33
+ | `legend` | `boolean` | `true` | 아래 범례. 마우스를 올리면 해당 시리즈 강조. |
34
+ | `activeIndex / defaultActiveIndex / onActiveIndexChange` | `number \| null` | | 하이라이트한 칸(제어/비제어). |
35
+ | `selected / defaultSelected / onSelectedChange` | `number \| null` | | 고른 칸(제어/비제어). 고른 칸과 마우스를 올린 칸만 진하고 나머지는 옅어져요. |
36
+ | `selectable` | `boolean` | | 클릭 · Enter/Space로 칸을 골라요. selected 관련 prop을 넘기면 자동으로 켜져요. |
37
+ | `xIcon` | `(row, index) => ReactNode` | | x축 라벨 대신 아이콘. 고른 칸은 아이콘 + formatX 라벨의 흰 칩(--shadow-chip)으로 펼쳐져요. |
38
+ | `yAxis` | `'left' \| 'right'` | `'left'` | 값 축 위치. |
39
+ | `yTicks` | `number[]` | | y축 값을 직접 지정(yTickCount · yMax 무시). |
40
+ | `segmentGap` | `number` | `0` | 누적 조각 사이 흰 틈(px). |
41
+ | `tooltipLeading` | `(row, index) => ReactNode` | | 툴팁 제목 앞 요소(아이콘, Checkbox). |
42
+ | `totalPlacement` | `'footer' \| 'header'` | `'footer'` | 합계 위치. header면 제목 줄 오른쪽에 두고 Separator 아래에 시리즈 행이 와요. |
43
+ | `tooltipSort` | `'series' \| 'reverse' \| 'value'` | `'series'` | 툴팁 행 순서. reverse는 누적 막대의 위 조각부터. |
44
+ | `aria-label` | `string` | `'막대 차트'` | 차트 이름. |
45
+
46
+ ## 예제
47
+
48
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
49
+
50
+ ### 묶음
51
+
52
+ 시리즈를 나란히 세워 칸마다 비교해요. ←/→, Home/End, Esc로도 움직여요.
53
+
54
+ ```tsx
55
+ import { BarChart, type BarSeries } from '@iamfiro/design'
56
+
57
+ const SERIES: BarSeries[] = [
58
+ { key: 'thisYear', label: '올해', color: '--chart-2' },
59
+ { key: 'lastYear', label: '작년', color: '--chart-5' },
60
+ ]
61
+
62
+ const DATA = [
63
+ { q: '1분기', thisYear: 4200, lastYear: 3100 },
64
+ { q: '2분기', thisYear: 5100, lastYear: 3900 },
65
+ { q: '3분기', thisYear: 4700, lastYear: 4400 },
66
+ { q: '4분기', thisYear: 6200, lastYear: 4800 },
67
+ ]
68
+
69
+ export default function Example() {
70
+ return (
71
+ <BarChart
72
+ data={DATA}
73
+ xKey="q"
74
+ series={SERIES}
75
+ formatValue={(v) => `${v.toLocaleString('ko-KR')}만 원`}
76
+ aria-label="분기별 매출"
77
+ style={{ width: '100%', maxWidth: 560 }}
78
+ />
79
+ )
80
+ }
81
+ ```
82
+
83
+ ### 누적
84
+
85
+ stacked는 한 막대에 쌓고, 툴팁 아래에 합계를 보여줘요. 맨 위 조각만 모서리가 둥글어요.
86
+
87
+ ```tsx
88
+ import { BarChart, type BarSeries } from '@iamfiro/design'
89
+
90
+ const SERIES: BarSeries[] = [
91
+ { key: 'bug', label: '버그' },
92
+ { key: 'feature', label: '기능' },
93
+ { key: 'chore', label: '정리' },
94
+ ]
95
+
96
+ const DATA = [
97
+ { day: '월', bug: 8, feature: 12, chore: 3 },
98
+ { day: '화', bug: 5, feature: 15, chore: 6 },
99
+ { day: '수', bug: 11, feature: 9, chore: 4 },
100
+ { day: '목', bug: 6, feature: 18, chore: 2 },
101
+ { day: '금', bug: 4, feature: 14, chore: 9 },
102
+ { day: '토', bug: 2, feature: 5, chore: 1 },
103
+ { day: '일', bug: 1, feature: 3, chore: 2 },
104
+ ]
105
+
106
+ export default function Example() {
107
+ return (
108
+ <BarChart
109
+ data={DATA}
110
+ xKey="day"
111
+ series={SERIES}
112
+ stacked
113
+ formatValue={(v) => `${v}건`}
114
+ formatTitle={(row) => `${row.day}요일`}
115
+ aria-label="요일별 처리한 이슈"
116
+ style={{ width: '100%', maxWidth: 560 }}
117
+ />
118
+ )
119
+ }
120
+ ```
121
+
122
+ ### 단일 시리즈
123
+
124
+ 시리즈가 하나면 범례를 끄고 제목만으로 충분해요.
125
+
126
+ ```tsx
127
+ import { BarChart, Stack, Text } from '@iamfiro/design'
128
+
129
+ const DATA = [
130
+ { hour: '9시', visits: 120 },
131
+ { hour: '10시', visits: 340 },
132
+ { hour: '11시', visits: 410 },
133
+ { hour: '12시', visits: 290 },
134
+ { hour: '13시', visits: 260 },
135
+ { hour: '14시', visits: 380 },
136
+ { hour: '15시', visits: 450 },
137
+ { hour: '16시', visits: 400 },
138
+ { hour: '17시', visits: 310 },
139
+ { hour: '18시', visits: 180 },
140
+ ]
141
+
142
+ export default function Example() {
143
+ return (
144
+ <Stack gap={3} style={{ width: '100%', maxWidth: 560 }}>
145
+ <Text variant="heading-xs" as="h3">
146
+ 시간대별 방문
147
+ </Text>
148
+ <BarChart data={DATA} xKey="hour" series={[{ key: 'visits', label: '방문' }]} legend={false} height={200} formatValue={(v) => `${v}회`} />
149
+ </Stack>
150
+ )
151
+ }
152
+ ```
153
+
154
+ ### 선택 · 아이콘 축
155
+
156
+ selected/onSelectedChange로 한 칸을 고르면 나머지는 옅은 색으로 바뀌어요. 클릭하거나 화살표로 옮겨 Enter/Space로 골라요. xIcon은 x축에 아이콘을 두고, 고른 칸은 아이콘 + 이름이 든 흰 칩으로 펼쳐져요. yAxis="right", segmentGap, 시리즈별 color, 툴팁 제목 줄의 tooltipLeading · totalPlacement="header"도 함께 보여요.
157
+
158
+ ```tsx
159
+ import { useState } from 'react'
160
+ import { CreditCard, Globe, Search, Server, Smartphone } from 'lucide-react'
161
+ import { BarChart, type BarSeries, Stack, Text } from '@iamfiro/design'
162
+
163
+ const SERIES: BarSeries[] = [
164
+ { key: 'success', label: '성공', color: '--chart-3' },
165
+ { key: 'rollback', label: '롤백', color: '--warning' },
166
+ { key: 'failed', label: '실패', color: '--danger' },
167
+ ]
168
+
169
+ const DATA = [
170
+ { service: '웹', success: 42, rollback: 3, failed: 2 },
171
+ { service: '모바일', success: 18, rollback: 1, failed: 1 },
172
+ { service: 'API', success: 57, rollback: 5, failed: 4 },
173
+ { service: '결제', success: 12, rollback: 2, failed: 0 },
174
+ { service: '검색', success: 25, rollback: 0, failed: 1 },
175
+ ]
176
+
177
+ const ICONS = [Globe, Smartphone, Server, CreditCard, Search]
178
+
179
+ export default function Example() {
180
+ const [selected, setSelected] = useState<number | null>(2)
181
+ const row = selected === null ? null : DATA[selected]
182
+
183
+ return (
184
+ <Stack gap={4} style={{ width: '100%', maxWidth: 560 }}>
185
+ <BarChart
186
+ data={DATA}
187
+ xKey="service"
188
+ series={SERIES}
189
+ stacked
190
+ segmentGap={2}
191
+ yAxis="right"
192
+ height={260}
193
+ xIcon={(_, i) => { const Icon = ICONS[i]; return <Icon size={16} /> }}
194
+ selected={selected}
195
+ onSelectedChange={setSelected}
196
+ tooltipLeading={(_, i) => { const Icon = ICONS[i]; return <Icon size={16} /> }}
197
+ totalPlacement="header"
198
+ tooltipSort="reverse"
199
+ formatValue={(v) => `${v}건`}
200
+ aria-label="서비스별 이번 주 배포"
201
+ />
202
+ <Text variant="body-sm" tone="secondary">
203
+ {row ? `${row.service}: 배포 ${row.success + row.rollback + row.failed}건 중 ${row.success}건 성공` : '막대를 눌러 서비스를 골라 보세요.'}
204
+ </Text>
205
+ </Stack>
206
+ )
207
+ }
208
+ ```
209
+
210
+ ### 표와 연동 (피벗)
211
+
212
+ 스프린트 작업을 상태 × 우선순위로 나눠요. 막대를 고르면 표의 행이, 표의 행을 누르면 막대가 강조돼요. 위의 Select로 측정값·분류·세분화를 바꾸면 막대가 새 높이로 미끄러지고, 범례는 표 아래에 ChartLegend로 따로 둬요.
213
+
214
+ ```tsx
215
+ import { useState, type ReactNode } from 'react'
216
+ import { CircleSlash, Clock, Eye, Inbox, Square, SquareCheck } from 'lucide-react'
217
+ import { BarChart, Card, CardBody, CardHeader, ChartLegend, Select, Table, type BarChartRow, type BarSeries, type TableColumn, Avatar, ColorDot, Field, Grid, Inline, Stack, Text } from '@iamfiro/design'
218
+
219
+ type Measure = 'count' | 'points'
220
+ type Slice = 'status' | 'assignee'
221
+ type Segment = 'priority' | 'none'
222
+
223
+ const PRIORITIES = [
224
+ { key: 'urgent', label: '긴급', color: '--danger', points: 5 },
225
+ { key: 'high', label: '높음', color: '--warning', points: 3 },
226
+ { key: 'medium', label: '보통', color: '--accent', points: 3 },
227
+ { key: 'low', label: '낮음', color: '--chart-seq-3', points: 2 },
228
+ { key: 'none', label: '없음', color: '--chart-5', points: 1 },
229
+ ]
230
+
231
+ interface Group {
232
+ id: string
233
+ label: string
234
+ icon: ReactNode
235
+ /** Task count per priority, in PRIORITIES order. */
236
+ counts: number[]
237
+ }
238
+
239
+ const GROUPS: Record<Slice, Group[]> = {
240
+ status: [
241
+ { id: 'backlog', label: '백로그', icon: <Inbox size={16} color="var(--text-3)" />, counts: [3, 6, 6, 8, 3] },
242
+ { id: 'todo', label: '할 일', icon: <Square size={16} color="var(--text-3)" />, counts: [1, 7, 12, 11, 5] },
243
+ { id: 'progress', label: '진행 중', icon: <Clock size={16} color="var(--accent)" />, counts: [1, 9, 12, 7, 5] },
244
+ { id: 'review', label: '검토 중', icon: <Eye size={16} color="var(--chart-4)" />, counts: [0, 1, 6, 4, 2] },
245
+ { id: 'done', label: '완료', icon: <SquareCheck size={16} color="var(--accent)" />, counts: [7, 14, 23, 25, 27] },
246
+ { id: 'canceled', label: '취소', icon: <CircleSlash size={16} color="var(--text-3)" />, counts: [0, 1, 3, 1, 2] },
247
+ ],
248
+ assignee: [
249
+ { id: 'minji', label: '김민지', icon: <Avatar name="김민지" size={18} />, counts: [4, 11, 17, 14, 9] },
250
+ { id: 'dohyun', label: '이도현', icon: <Avatar name="이도현" size={18} />, counts: [3, 9, 15, 12, 10] },
251
+ { id: 'seoyeon', label: '박서연', icon: <Avatar name="박서연" size={18} />, counts: [2, 10, 16, 18, 13] },
252
+ { id: 'junho', label: '최준호', icon: <Avatar name="최준호" size={18} />, counts: [3, 8, 14, 12, 12] },
253
+ ],
254
+ }
255
+
256
+ const MEASURE_LABEL: Record<Measure, string> = { count: '작업 수', points: '스토리 포인트' }
257
+ const SLICE_LABEL: Record<Slice, string> = { status: '상태', assignee: '담당자' }
258
+
259
+ /** One row per group: the total plus a field per priority. */
260
+ function pivot(slice: Slice, measure: Measure): BarChartRow[] {
261
+ return GROUPS[slice].map((g) => {
262
+ const values = g.counts.map((c, i) => (measure === 'points' ? c * PRIORITIES[i].points : c))
263
+ return {
264
+ id: g.id,
265
+ label: g.label,
266
+ total: values.reduce((a, b) => a + b, 0),
267
+ ...Object.fromEntries(PRIORITIES.map((p, i) => [p.key, values[i]])),
268
+ }
269
+ })
270
+ }
271
+
272
+ export default function Example() {
273
+ const [measure, setMeasure] = useState<Measure>('count')
274
+ const [slice, setSlice] = useState<Slice>('status')
275
+ const [segment, setSegment] = useState<Segment>('priority')
276
+ const [selectedId, setSelectedId] = useState<string | null>('done')
277
+
278
+ const rows = pivot(slice, measure)
279
+ const icons = Object.fromEntries(GROUPS[slice].map((g) => [g.id, g.icon]))
280
+ const series: BarSeries[] =
281
+ segment === 'priority' ? PRIORITIES.map((p) => ({ key: p.key, label: p.label, color: p.color })) : [{ key: 'total', label: MEASURE_LABEL[measure], color: '--accent' }]
282
+ const selectedIndex = rows.findIndex((r) => r.id === selectedId)
283
+ const grandTotal = rows.reduce((a, r) => a + Number(r.total), 0)
284
+ const unit = measure === 'count' ? '개' : '점'
285
+
286
+ const columns: TableColumn<BarChartRow>[] = [
287
+ { key: 'label', header: SLICE_LABEL[slice], leading: (r) => icons[String(r.id)] },
288
+ { key: 'total', header: MEASURE_LABEL[measure], numeric: true },
289
+ ...(segment === 'priority'
290
+ ? PRIORITIES.map((p): TableColumn<BarChartRow> => ({ key: p.key, header: p.label, headerLeading: <ColorDot color={p.color} />, numeric: true }))
291
+ : []),
292
+ ]
293
+
294
+ return (
295
+ <Card style={{ width: '100%', maxWidth: 620 }}>
296
+ <CardHeader
297
+ title="스프린트 24"
298
+ actions={
299
+ <Inline gap={1} align="baseline" wrap={false}>
300
+ <Text variant="number-sm">{grandTotal.toLocaleString('ko-KR')}</Text>
301
+ <Text variant="body-sm" tone="tertiary">
302
+ {unit}
303
+ </Text>
304
+ </Inline>
305
+ }
306
+ />
307
+ <CardBody>
308
+ <Stack gap={5}>
309
+ <Grid columns={3} gap={2}>
310
+ <Field label="측정값" htmlFor="pivot-measure">
311
+ <Select<Measure>
312
+ id="pivot-measure"
313
+ size="sm"
314
+ value={measure}
315
+ onValueChange={setMeasure}
316
+ options={[
317
+ { value: 'count', label: '작업 수' },
318
+ { value: 'points', label: '스토리 포인트' },
319
+ ]}
320
+ />
321
+ </Field>
322
+ <Field label="분류" htmlFor="pivot-slice">
323
+ <Select<Slice>
324
+ id="pivot-slice"
325
+ size="sm"
326
+ value={slice}
327
+ onValueChange={(v) => {
328
+ setSlice(v)
329
+ setSelectedId(null)
330
+ }}
331
+ options={[
332
+ { value: 'status', label: '상태' },
333
+ { value: 'assignee', label: '담당자' },
334
+ ]}
335
+ />
336
+ </Field>
337
+ <Field label="세분화" htmlFor="pivot-segment">
338
+ <Select<Segment>
339
+ id="pivot-segment"
340
+ size="sm"
341
+ value={segment}
342
+ onValueChange={setSegment}
343
+ options={[
344
+ { value: 'priority', label: '우선순위' },
345
+ { value: 'none', label: '없음' },
346
+ ]}
347
+ />
348
+ </Field>
349
+ </Grid>
350
+
351
+ <BarChart
352
+ data={rows}
353
+ xKey="label"
354
+ series={series}
355
+ stacked
356
+ segmentGap={2}
357
+ height={280}
358
+ yAxis="right"
359
+ yTickCount={6}
360
+ xIcon={(r) => icons[String(r.id)]}
361
+ selected={selectedIndex < 0 ? null : selectedIndex}
362
+ onSelectedChange={(i) => setSelectedId(i === null ? null : String(rows[i].id))}
363
+ tooltipLeading={(r) => icons[String(r.id)]}
364
+ totalPlacement="header"
365
+ tooltipSort="reverse"
366
+ legend={false}
367
+ aria-label={`${SLICE_LABEL[slice]}별 ${MEASURE_LABEL[measure]}`}
368
+ />
369
+
370
+ <Table
371
+ columns={columns}
372
+ rows={rows}
373
+ rowKey={(r) => String(r.id)}
374
+ headerVariant="overline"
375
+ striped={false}
376
+ selectedKey={selectedId}
377
+ onRowClick={(r) => setSelectedId(String(r.id))}
378
+ />
379
+
380
+ {segment === 'priority' && <ChartLegend items={PRIORITIES.map((p) => ({ key: p.key, label: p.label, color: p.color }))} />}
381
+ </Stack>
382
+ </CardBody>
383
+ </Card>
384
+ )
385
+ }
386
+ ```
@@ -0,0 +1,223 @@
1
+ # BarList
2
+
3
+ > 차트 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 많이 본 페이지 · 유입 경로 · 국가처럼 순위를 매긴 가로 막대 목록. 이름 뒤로 값에 비례한 옅은 막대가 깔리고, 값은 오른쪽에 tabular 숫자로 맞춰요. 처음엔 막대가 왼쪽에서 자라고, 데이터가 바뀌면 새 폭으로 미끄러지며 순서가 바뀐 행은 새 자리로 움직여요. 긴 목록은 limit으로 접어 "더 보기"로 펼쳐요.
6
+
7
+ ```tsx
8
+ import { BarList } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 순위 목록(많이 본 페이지 · 유입 경로 · 국가)은 `BarList`: 이름 뒤로 옅은 막대(`tone` 또는 `color` 16% 섞기), 값은 오른쪽 tabular. 긴 목록은 `limit` + "더 보기"(0fr ↔ 1fr), 순위가 바뀌면 행이 새 자리로 미끄러진다. 행 진행률 하나면 `ListItem progress`.
14
+
15
+ ## API
16
+
17
+ ### BarList
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `data` (필수) | `BarListItem[]` | | { key?, name, value, icon?, href?, color? } 목록. |
22
+ | `sortOrder` | `'descending' \| 'ascending' \| 'none'` | `'descending'` | 행 순서. none은 data 순서 그대로. |
23
+ | `tone` | `'accent' \| 'neutral' \| 'success' \| 'warning' \| 'danger'` | `'accent'` | 막대의 옅은 채움 색. |
24
+ | `color` | `string` | | tone 대신 쓸 색. CSS 변수 이름('--chart-1') 또는 색, 옅게 섞어 칠해요. 행마다 item.color로도. |
25
+ | `formatValue` | `(value, item) => string` | `'1,234'` | 값 형식. |
26
+ | `onValueChange` | `(item, index) => void` | | 주면 행이 버튼이 되고, 누른 행을 알려 줘요. |
27
+ | `showAnimation` | `boolean` | `true` | 처음 자라기 · 폭 미끄러짐 · 순서 바뀜 이동. |
28
+ | `max` | `number` | | 막대가 꽉 차는 값. 기본은 가장 큰 값. |
29
+ | `limit` | `number` | | 접기 전에 보일 행 수. 넘치면 "더 보기" 버튼. |
30
+ | `expanded / defaultExpanded / onExpandedChange` | `boolean` | `false` | 나머지 행 펼침(제어/비제어). |
31
+ | `moreLabel / lessLabel` | `(hidden) => ReactNode / ReactNode` | `'더 보기 (7)' / '접기'` | 펼침 버튼 문구. |
32
+ | `nameLabel / valueLabel` | `ReactNode` | | 오버라인 머리줄("페이지" / "방문수"). |
33
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 행 높이 28 · 32 · 36px. |
34
+ | `aria-label` | `string` | | 목록 이름. |
35
+
36
+ ## 예제
37
+
38
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
39
+
40
+ ### 기본 · 머리줄
41
+
42
+ nameLabel · valueLabel을 주면 위에 오버라인 머리줄이 붙어요. href가 있는 행은 이름이 링크가 되고, 긴 이름은 말줄임돼요.
43
+
44
+ ```tsx
45
+ import { BarList } from '@iamfiro/design'
46
+
47
+ const PAGES = [
48
+ { name: '/', value: 8420, href: '#' },
49
+ { name: '/요금제', value: 3910, href: '#' },
50
+ { name: '/블로그/디자인-시스템-만들기', value: 2875, href: '#' },
51
+ { name: '/문서/시작하기', value: 2140, href: '#' },
52
+ { name: '/고객-사례', value: 1260, href: '#' },
53
+ { name: '/채용', value: 640, href: '#' },
54
+ ]
55
+
56
+ export default function Example() {
57
+ return (
58
+ <div style={{ width: '100%', maxWidth: 420 }}>
59
+ <BarList data={PAGES} nameLabel="페이지" valueLabel="방문수" aria-label="많이 본 페이지" />
60
+ </div>
61
+ )
62
+ }
63
+ ```
64
+
65
+ ### 톤 · 아이콘 · 색
66
+
67
+ tone으로 막대 채움을 바꾸고(accent · neutral · success · warning · danger), 행마다 color에 차트 색('--chart-2')을 주면 그 색의 옅은 톤으로 칠해요. icon은 이름 앞에 붙어요.
68
+
69
+ ```tsx
70
+ import { Globe, Mail, Search, Link } from 'lucide-react'
71
+ import { Grid, Stack, Text, BarList } from '@iamfiro/design'
72
+
73
+ const SOURCES = [
74
+ { name: '검색', value: 4820, icon: <Search size={14} /> },
75
+ { name: '직접 방문', value: 3150, icon: <Globe size={14} /> },
76
+ { name: '외부 링크', value: 1730, icon: <Link size={14} /> },
77
+ { name: '뉴스레터', value: 960, icon: <Mail size={14} /> },
78
+ ]
79
+
80
+ const STATUS = [
81
+ { name: '200 성공', value: 18240 },
82
+ { name: '304 변경 없음', value: 4120 },
83
+ { name: '404 없음', value: 310 },
84
+ ]
85
+
86
+ const ERRORS = [
87
+ { name: '시간 초과', value: 128 },
88
+ { name: '권한 없음', value: 74 },
89
+ { name: '잘못된 요청', value: 31 },
90
+ ]
91
+
92
+ const DEVICES = [
93
+ { name: '데스크톱', value: 61, color: '--chart-2' },
94
+ { name: '휴대폰', value: 34, color: '--chart-1' },
95
+ { name: '태블릿', value: 5, color: '--chart-3' },
96
+ ]
97
+
98
+ export default function Example() {
99
+ return (
100
+ <Grid minItemWidth={240} gap={8} style={{ width: '100%' }}>
101
+ <Stack gap={2}>
102
+ <Text variant="label-sm">유입 경로 · 아이콘</Text>
103
+ <BarList data={SOURCES} tone="neutral" />
104
+ </Stack>
105
+ <Stack gap={2}>
106
+ <Text variant="label-sm">응답 코드 · success</Text>
107
+ <BarList data={STATUS} tone="success" />
108
+ </Stack>
109
+ <Stack gap={2}>
110
+ <Text variant="label-sm">오류 · danger</Text>
111
+ <BarList data={ERRORS} tone="danger" formatValue={(v) => `${v}건`} />
112
+ </Stack>
113
+ <Stack gap={2}>
114
+ <Text variant="label-sm">기기 · 행마다 차트 색</Text>
115
+ <BarList data={DEVICES} max={100} formatValue={(v) => `${v}%`} />
116
+ </Stack>
117
+ </Grid>
118
+ )
119
+ }
120
+ ```
121
+
122
+ ### 누를 수 있는 행 · 정렬 · 데이터 바뀜
123
+
124
+ onValueChange를 주면 행이 버튼이 돼요(hover 채움, Tab · ↑/↓ · Home/End). 기간을 바꾸면 막대가 새 폭으로, 순위가 바뀐 행은 새 자리로 미끄러져요.
125
+
126
+ ```tsx
127
+ import { useState } from 'react'
128
+ import { Inline, SegmentedControl, Stack, Text, BarList, type BarListSortOrder } from '@iamfiro/design'
129
+
130
+ const WEEK = [
131
+ { key: 'seoul', name: '서울', value: 5240 },
132
+ { key: 'busan', name: '부산', value: 1820 },
133
+ { key: 'incheon', name: '인천', value: 1460 },
134
+ { key: 'daegu', name: '대구', value: 980 },
135
+ { key: 'gwangju', name: '광주', value: 610 },
136
+ ]
137
+
138
+ const MONTH = [
139
+ { key: 'seoul', name: '서울', value: 19800 },
140
+ { key: 'busan', name: '부산', value: 6100 },
141
+ { key: 'incheon', name: '인천', value: 7350 },
142
+ { key: 'daegu', name: '대구', value: 4120 },
143
+ { key: 'gwangju', name: '광주', value: 4380 },
144
+ ]
145
+
146
+ export default function Example() {
147
+ const [period, setPeriod] = useState<'week' | 'month'>('week')
148
+ const [sortOrder, setSortOrder] = useState<BarListSortOrder>('descending')
149
+ const [picked, setPicked] = useState<string | null>(null)
150
+
151
+ return (
152
+ <Stack gap={4} style={{ width: '100%', maxWidth: 420 }}>
153
+ <Inline justify="between" gap={3}>
154
+ <SegmentedControl
155
+ ariaLabel="기간"
156
+ value={period}
157
+ onChange={setPeriod}
158
+ options={[
159
+ { value: 'week', label: '7일' },
160
+ { value: 'month', label: '30일' },
161
+ ]}
162
+ />
163
+ <SegmentedControl
164
+ ariaLabel="정렬"
165
+ variant="text"
166
+ value={sortOrder}
167
+ onChange={setSortOrder}
168
+ options={[
169
+ { value: 'descending', label: '많은 순' },
170
+ { value: 'ascending', label: '적은 순' },
171
+ { value: 'none', label: '원래 순서' },
172
+ ]}
173
+ />
174
+ </Inline>
175
+ <BarList
176
+ data={period === 'week' ? WEEK : MONTH}
177
+ sortOrder={sortOrder}
178
+ nameLabel="지역"
179
+ valueLabel="방문수"
180
+ onValueChange={(item) => setPicked(item.name)}
181
+ aria-label="지역별 방문수"
182
+ />
183
+ <Text variant="body-sm" tone="secondary">
184
+ {picked ? `${picked}을(를) 골랐어요.` : '행을 눌러 지역을 고르세요. Tab으로 들어와 ↑/↓로도 움직여요.'}
185
+ </Text>
186
+ </Stack>
187
+ )
188
+ }
189
+ ```
190
+
191
+ ### 더 보기 · 카드 안
192
+
193
+ limit개만 보이고 나머지는 접혀요. "더 보기"를 누르면 실제 높이까지 펼쳐져요. expanded · onExpandedChange로 제어할 수도 있어요.
194
+
195
+ ```tsx
196
+ import { Card, CardBody, CardHeader, BarList } from '@iamfiro/design'
197
+
198
+ const COUNTRIES = [
199
+ { name: '대한민국', value: 48210 },
200
+ { name: '미국', value: 12840 },
201
+ { name: '일본', value: 9630 },
202
+ { name: '베트남', value: 4210 },
203
+ { name: '독일', value: 3180 },
204
+ { name: '캐나다', value: 2470 },
205
+ { name: '영국', value: 2210 },
206
+ { name: '싱가포르', value: 1650 },
207
+ { name: '호주', value: 1320 },
208
+ { name: '프랑스', value: 980 },
209
+ { name: '태국', value: 760 },
210
+ { name: '브라질', value: 420 },
211
+ ]
212
+
213
+ export default function Example() {
214
+ return (
215
+ <Card style={{ width: '100%', maxWidth: 420 }}>
216
+ <CardHeader title="국가별 방문" description="최근 30일" />
217
+ <CardBody>
218
+ <BarList data={COUNTRIES} limit={5} nameLabel="국가" valueLabel="방문수" aria-label="국가별 방문" />
219
+ </CardBody>
220
+ </Card>
221
+ )
222
+ }
223
+ ```