@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,315 @@
1
+ # NavigationMenu
2
+
3
+ > 내비게이션 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 사이트 헤더의 메뉴(제품 · 보안 · 요금). 메뉴 이름에 마우스를 잠깐 올려 두면 열리고, 열린 동안 옆 이름으로 옮기면 바로 바뀌어요. 메뉴들은 패널 하나를 함께 쓰기 때문에 메뉴를 바꾸면 패널이 새로 뜨지 않고 새 크기로 늘거나 줄며, 내용은 옮겨 간 방향에서 미끄러져 들어와요. 메뉴 이름 뒤의 회색 채움도 따라 미끄러져요. 패널 안은 Grid로 자유롭게 짜고, NavigationMenuGroup(제목 + 링크 열) · NavigationMenuLink(이름 + 설명) · NavigationMenuFeature(소개 카드)를 끼워 넣어요. 링크는 as로 라우터 링크를 쓸 수 있어요. 키보드는 ←/→로 메뉴 사이 이동, ↓ · Enter로 열어 첫 링크로, 패널 안 ↑/↓, Esc로 메뉴 이름으로 돌아와요.
6
+
7
+ ```tsx
8
+ import { NavigationMenu, NavigationMenuItem, NavigationMenuLink, NavigationMenuGroup, NavigationMenuFeature } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 사이트 헤더 메뉴(제품 · 보안 · 요금)는 `NavigationMenu`: 메뉴들이 **패널 하나를 함께** 쓰고, 메뉴를 바꾸면 패널이 새로 뜨지 않고 크기(· `panelAlign="trigger"`면 위치)를 바꾸며 내용은 옮긴 방향에서 미끄러져 들어온다. 처음 열기는 `TIMING.hoverIntent`, 열린 동안 옆 메뉴는 즉시, 벗어나면 `hoverGrace`. 패널 안은 Grid + `NavigationMenuGroup` · `NavigationMenuLink` · `NavigationMenuFeature`로 조립. 헤더 전체 폭은 `viewportAnchor`.
14
+
15
+ ## API
16
+
17
+ ### NavigationMenu
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `children` (필수) | `ReactNode` | | NavigationMenuItem(패널을 여는 메뉴)과 NavigationMenuLink(바로 가는 링크), 순서대로. |
22
+ | `value / onValueChange` | `string \| null / (value) => void` | | 열린 메뉴의 value(null = 닫힘). 밖에서 제어할 때. |
23
+ | `defaultValue` | `string \| null` | `null` | 처음 열린 메뉴(비제어). |
24
+ | `openOn` | `'hover' \| 'click'` | `'hover'` | hover: 머물면 열리고 열린 동안 옆 메뉴로 바로 전환 · click: 눌러야 열림. |
25
+ | `panelAlign` | `'stretch' \| 'trigger'` | `'stretch'` | stretch: viewportAnchor 폭 전체(메가 메뉴) · trigger: 내용 폭, 열린 메뉴 아래 가운데. |
26
+ | `viewportAnchor` | `RefObject<HTMLElement>` | | 패널이 폭과 아래 가장자리를 맞출 요소(보통 헤더 전체). 없으면 nav 자신. |
27
+ | `tone` | `'default' \| 'inverse'` | `'default'` | inverse: 어두운 헤더용 밝은 메뉴 이름. |
28
+ | `aria-label` | `string` | `'주 메뉴'` | nav의 접근성 이름. |
29
+
30
+ ### NavigationMenuItem
31
+
32
+ 메뉴 이름 버튼 + 패널에 보일 내용. 내용은 어떤 레이아웃이든 돼요(보통 Grid).
33
+
34
+ | prop | 타입 | 기본값 | 설명 |
35
+ | --- | --- | --- | --- |
36
+ | `label` (필수) | `ReactNode` | | 메뉴 이름(예: 제품). |
37
+ | `children` (필수) | `ReactNode` | | 패널 내용. |
38
+ | `value` | `string` | | NavigationMenu value에서 이 메뉴를 가리키는 값. 없으면 내부 id. |
39
+ | `icon` | `ReactNode` | | 이름 앞 16px 아이콘. |
40
+ | `disabled` | `boolean` | | 비활성. 열리지 않고 키보드 이동에서 건너뛰어요. |
41
+ | `width` | `number \| string` | | panelAlign="trigger"일 때 내용 폭. 없으면 내용 자체 폭. |
42
+
43
+ ### NavigationMenuLink
44
+
45
+ NavigationMenu 바로 아래면 막대 항목, 패널 안이면 이름 + 설명 행. 누르면 패널이 닫혀요(onClick에서 preventDefault 하는 라우터 링크도).
46
+
47
+ | prop | 타입 | 기본값 | 설명 |
48
+ | --- | --- | --- | --- |
49
+ | `label` (필수) | `ReactNode` | | 링크 이름. |
50
+ | `description` | `ReactNode` | | 이름 아래 옅은 한 줄(패널 안). |
51
+ | `icon` | `ReactNode` | | 이름 앞 아이콘 · IconTile. |
52
+ | `badge` | `ReactNode` | | 이름 뒤 Badge 등. |
53
+ | `active` | `boolean` | | 현재 페이지. aria-current="page". |
54
+ | `as` | `ElementType` | `'a'` | 라우터 링크 컴포넌트. 나머지 props(href, to …)는 그대로 전달. |
55
+
56
+ ### NavigationMenuGroup
57
+
58
+ | prop | 타입 | 기본값 | 설명 |
59
+ | --- | --- | --- | --- |
60
+ | `title` | `ReactNode` | | 링크 열 위의 작은 제목(role="group" 이름). |
61
+ | `children` (필수) | `ReactNode` | | NavigationMenuLink들. |
62
+
63
+ ### NavigationMenuFeature
64
+
65
+ 패널 마지막 칸의 소개 카드. 칸 높이를 꽉 채워요.
66
+
67
+ | prop | 타입 | 기본값 | 설명 |
68
+ | --- | --- | --- | --- |
69
+ | `title` (필수) | `ReactNode` | | 카드 제목. |
70
+ | `eyebrow / description` | `ReactNode` | | 위 작은 줄 / 제목 아래 설명. |
71
+ | `action` | `ReactNode` | | 아래 버튼 · 링크. |
72
+ | `media` | `ReactNode` | | 글 뒤를 채우는 이미지 · 일러스트(cover). |
73
+ | `background` | `string` | | media가 없을 때 배경(CSS). 기본은 옅은 회색 판. |
74
+ | `tone` | `'default' \| 'inverse'` | `'default'` | inverse: 어두운 배경 위 흰 글자. |
75
+
76
+ ## 예제
77
+
78
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
79
+
80
+ ### 메가 메뉴 헤더
81
+
82
+ viewportAnchor로 헤더 전체를 넘기면 패널이 헤더 폭에 맞춰 펼쳐져요. 제품을 연 채 보안으로 옮겨 보세요 — 패널 높이가 바뀌고 내용이 오른쪽에서 들어와요. 열린 패널로 가는 동안 잠깐 벗어나도 유예 시간 안이면 닫히지 않아요.
83
+
84
+ ```tsx
85
+ import { useRef } from 'react'
86
+ import { ChevronRight } from 'lucide-react'
87
+ import { Button, Grid, Inline, Text, NavigationMenu, NavigationMenuFeature, NavigationMenuGroup, NavigationMenuItem, NavigationMenuLink } from '@iamfiro/design'
88
+
89
+ export default function Example() {
90
+ // The panel spans the whole header, not just the menu triggers.
91
+ const headerRef = useRef<HTMLElement>(null)
92
+
93
+ return (
94
+ <Inline as="header" ref={headerRef} justify="between" wrap={false} style={{ width: '100%', padding: '8px 4px' }}>
95
+ <Text variant="heading-sm">Paged</Text>
96
+ <NavigationMenu viewportAnchor={headerRef}>
97
+ <NavigationMenuItem value="product" label="제품">
98
+ <Grid columns="1fr 1fr 300px" gap={4}>
99
+ <NavigationMenuGroup title="제품">
100
+ <NavigationMenuLink href="#hire" label="Hire" description="실제 장애 환경에서 보는 채용 실기" />
101
+ <NavigationMenuLink href="#train" label="Train" description="지난 포스트모템이 다음 온콜 훈련으로" />
102
+ <NavigationMenuLink href="#sandbox" label="시험 환경" description="BYOC 또는 설치 없는 Paged 샌드박스" />
103
+ </NavigationMenuGroup>
104
+ <NavigationMenuGroup title="활용">
105
+ <NavigationMenuLink href="#cohort" label="기수 채용" description="같은 기수를 같은 주에, 같은 난이도로" />
106
+ <NavigationMenuLink href="#career" label="경력 채용" description="면접 전에 실제 대응 과정을 확인" />
107
+ <NavigationMenuLink href="#open" label="공채" description="정해진 회차에, 1차는 읽기형으로" />
108
+ <NavigationMenuLink href="#oncall" label="온콜 조직" description="팀의 장애 대응을 정기적으로 훈련" />
109
+ <NavigationMenuLink href="#edu" label="교육기관" description="실무형 장애 대응 과제 운영" />
110
+ </NavigationMenuGroup>
111
+ <NavigationMenuFeature
112
+ eyebrow="제품 둘러보기"
113
+ title="Hire · Train이 같은 장애 하나로 이어져요"
114
+ tone="inverse"
115
+ background="linear-gradient(180deg, #141a4a 0%, #6b3f78 55%, #f08a4b 100%)"
116
+ action={
117
+ <Button size="sm" iconRight={<ChevronRight />}>
118
+ 흐름 보기
119
+ </Button>
120
+ }
121
+ />
122
+ </Grid>
123
+ </NavigationMenuItem>
124
+ <NavigationMenuItem value="security" label="보안">
125
+ <Grid columns="1fr 1fr 300px" gap={4}>
126
+ <NavigationMenuGroup title="보안">
127
+ <NavigationMenuLink href="#trust" label="보안 센터" description="인증 현황과 감사 보고서" />
128
+ <NavigationMenuLink href="#data" label="데이터 처리" description="후보자 데이터를 어디에, 얼마나 두는지" />
129
+ </NavigationMenuGroup>
130
+ <NavigationMenuGroup title="문서">
131
+ <NavigationMenuLink href="#dpa" label="데이터 처리 부속서" description="DPA 서명본 받기" />
132
+ <NavigationMenuLink href="#sub" label="하위 처리자" description="함께 일하는 처리자 목록" />
133
+ </NavigationMenuGroup>
134
+ <NavigationMenuFeature
135
+ eyebrow="ISMS-P"
136
+ title="2026년 10월 인증을 갱신했어요"
137
+ action={
138
+ <Button size="sm" iconRight={<ChevronRight />}>
139
+ 인증서 보기
140
+ </Button>
141
+ }
142
+ />
143
+ </Grid>
144
+ </NavigationMenuItem>
145
+ <NavigationMenuLink href="#pricing" label="요금" />
146
+ </NavigationMenu>
147
+ <Button size="sm" variant="primary">
148
+ 데모 신청
149
+ </Button>
150
+ </Inline>
151
+ )
152
+ }
153
+ ```
154
+
155
+ ### 어두운 헤더
156
+
157
+ tone="inverse"면 메뉴 이름이 밝은 글자와 반투명 채움으로 바뀌어요. 패널은 그대로 흰색이에요.
158
+
159
+ ```tsx
160
+ import { useRef } from 'react'
161
+ import { ChevronRight } from 'lucide-react'
162
+ import { Button, Grid, Inline, NavigationMenu, NavigationMenuFeature, NavigationMenuGroup, NavigationMenuItem, NavigationMenuLink } from '@iamfiro/design'
163
+
164
+ export default function Example() {
165
+ const heroRef = useRef<HTMLDivElement>(null)
166
+
167
+ return (
168
+ <Inline
169
+ ref={heroRef}
170
+ justify="center"
171
+ style={{ width: '100%', padding: 12, borderRadius: 16, background: '#0b1033' }}
172
+ >
173
+ <NavigationMenu tone="inverse" viewportAnchor={heroRef}>
174
+ <NavigationMenuItem value="product" label="제품">
175
+ <Grid columns="1fr 1fr 280px" gap={4}>
176
+ <NavigationMenuGroup title="제품">
177
+ <NavigationMenuLink href="#hire" label="Hire" description="실제 장애 환경에서 보는 채용 실기" />
178
+ <NavigationMenuLink href="#train" label="Train" description="지난 포스트모템이 다음 온콜 훈련으로" />
179
+ </NavigationMenuGroup>
180
+ <NavigationMenuGroup title="활용">
181
+ <NavigationMenuLink href="#cohort" label="기수 채용" description="같은 기수를 같은 주에, 같은 난이도로" />
182
+ <NavigationMenuLink href="#oncall" label="온콜 조직" description="팀의 장애 대응을 정기적으로 훈련" />
183
+ </NavigationMenuGroup>
184
+ <NavigationMenuFeature
185
+ eyebrow="제품 둘러보기"
186
+ title="Hire · Train이 같은 장애 하나로 이어져요"
187
+ tone="inverse"
188
+ background="linear-gradient(180deg, #141a4a 0%, #6b3f78 55%, #f08a4b 100%)"
189
+ action={
190
+ <Button size="sm" iconRight={<ChevronRight />}>
191
+ 흐름 보기
192
+ </Button>
193
+ }
194
+ />
195
+ </Grid>
196
+ </NavigationMenuItem>
197
+ <NavigationMenuItem value="security" label="보안">
198
+ <Grid columns="1fr 1fr" gap={4}>
199
+ <NavigationMenuGroup title="보안">
200
+ <NavigationMenuLink href="#trust" label="보안 센터" description="인증 현황과 감사 보고서" />
201
+ </NavigationMenuGroup>
202
+ <NavigationMenuGroup title="문서">
203
+ <NavigationMenuLink href="#dpa" label="데이터 처리 부속서" description="DPA 서명본 받기" />
204
+ </NavigationMenuGroup>
205
+ </Grid>
206
+ </NavigationMenuItem>
207
+ <NavigationMenuLink href="#pricing" label="요금" />
208
+ </NavigationMenu>
209
+ </Inline>
210
+ )
211
+ }
212
+ ```
213
+
214
+ ### 메뉴 아래 맞춤
215
+
216
+ panelAlign="trigger"면 패널이 내용 폭만큼만 열린 메뉴 이름 아래 가운데에 놓여요. 메뉴마다 폭이 달라도 패널이 크기와 위치를 함께 옮기며 바뀌어요. 링크에 icon(IconTile 등)과 badge를 붙일 수 있어요.
217
+
218
+ ```tsx
219
+ import { BookOpen, Code2, FileText, LifeBuoy, MessagesSquare, Newspaper, PlayCircle, Webhook } from 'lucide-react'
220
+ import { Badge, IconTile, NavigationMenu, NavigationMenuGroup, NavigationMenuItem, NavigationMenuLink } from '@iamfiro/design'
221
+
222
+ export default function Example() {
223
+ return (
224
+ <NavigationMenu panelAlign="trigger">
225
+ <NavigationMenuItem value="docs" label="개발자" width={340}>
226
+ <NavigationMenuLink
227
+ href="#quickstart"
228
+ icon={<IconTile size={32}><PlayCircle /></IconTile>}
229
+ label="빠른 시작"
230
+ description="5분 안에 첫 시나리오 실행하기"
231
+ />
232
+ <NavigationMenuLink
233
+ href="#api"
234
+ icon={<IconTile size={32}><Code2 /></IconTile>}
235
+ label="API 레퍼런스"
236
+ description="REST · GraphQL 엔드포인트 전체"
237
+ />
238
+ <NavigationMenuLink
239
+ href="#webhooks"
240
+ icon={<IconTile size={32}><Webhook /></IconTile>}
241
+ label="웹훅"
242
+ badge={<Badge size="sm" tone="accent">베타</Badge>}
243
+ description="결과가 나오면 바로 받아 보기"
244
+ />
245
+ </NavigationMenuItem>
246
+ <NavigationMenuItem value="resources" label="자료" width={520}>
247
+ <NavigationMenuGroup title="읽을거리">
248
+ <NavigationMenuLink href="#blog" icon={<Newspaper size={18} />} label="블로그" description="장애 대응 팀의 일하는 방식" />
249
+ <NavigationMenuLink href="#guides" icon={<BookOpen size={18} />} label="가이드" description="온콜 훈련을 설계하는 법" />
250
+ <NavigationMenuLink href="#reports" icon={<FileText size={18} />} label="리포트" description="2026 온콜 현황 보고서" />
251
+ </NavigationMenuGroup>
252
+ </NavigationMenuItem>
253
+ <NavigationMenuItem value="support" label="지원" width={280}>
254
+ <NavigationMenuLink href="#help" icon={<LifeBuoy size={18} />} label="도움말 센터" />
255
+ <NavigationMenuLink href="#community" icon={<MessagesSquare size={18} />} label="커뮤니티" />
256
+ </NavigationMenuItem>
257
+ <NavigationMenuLink href="#changelog" label="변경 기록" />
258
+ </NavigationMenu>
259
+ )
260
+ }
261
+ ```
262
+
263
+ ### 클릭으로 열기 · 제어
264
+
265
+ openOn="click"이면 눌러야만 열려요. value · onValueChange로 열린 메뉴를 밖에서 읽고 정해요(null = 닫힘). active 링크는 aria-current="page"로 표시돼요. 링크를 누르면 패널이 닫히고, disabled 메뉴는 ←/→ 이동에서 건너뛰어요.
266
+
267
+ ```tsx
268
+ import { useState } from 'react'
269
+ import { Badge, Inline, Stack, Text, NavigationMenu, NavigationMenuGroup, NavigationMenuItem, NavigationMenuLink } from '@iamfiro/design'
270
+
271
+ const LABELS: Record<string, string> = { product: '제품', company: '회사' }
272
+
273
+ export default function Example() {
274
+ const [open, setOpen] = useState<string | null>(null)
275
+ const [page, setPage] = useState('hire')
276
+
277
+ const go = (id: string) => (e: React.MouseEvent) => {
278
+ e.preventDefault()
279
+ setPage(id)
280
+ }
281
+
282
+ return (
283
+ <Stack gap={4} align="center" style={{ width: '100%' }}>
284
+ <NavigationMenu openOn="click" panelAlign="trigger" value={open} onValueChange={setOpen}>
285
+ <NavigationMenuItem value="product" label="제품" width={300}>
286
+ <NavigationMenuGroup title="제품">
287
+ <NavigationMenuLink href="#hire" label="Hire" active={page === 'hire'} onClick={go('hire')} />
288
+ <NavigationMenuLink href="#train" label="Train" active={page === 'train'} onClick={go('train')} />
289
+ </NavigationMenuGroup>
290
+ </NavigationMenuItem>
291
+ <NavigationMenuItem value="company" label="회사" width={300}>
292
+ <NavigationMenuGroup title="회사">
293
+ <NavigationMenuLink href="#about" label="소개" active={page === 'about'} onClick={go('about')} />
294
+ <NavigationMenuLink href="#careers" label="채용" active={page === 'careers'} onClick={go('careers')} />
295
+ </NavigationMenuGroup>
296
+ </NavigationMenuItem>
297
+ <NavigationMenuItem value="enterprise" label="엔터프라이즈" disabled>
298
+ {null}
299
+ </NavigationMenuItem>
300
+ <NavigationMenuLink href="#pricing" label="요금" active={page === 'pricing'} onClick={go('pricing')} />
301
+ </NavigationMenu>
302
+ <Inline gap={2}>
303
+ <Text variant="body-sm" tone="secondary">
304
+ 열린 메뉴
305
+ </Text>
306
+ <Badge>{open ? LABELS[open] : '없음'}</Badge>
307
+ <Text variant="body-sm" tone="secondary">
308
+ 현재 페이지
309
+ </Text>
310
+ <Badge tone="accent">{page}</Badge>
311
+ </Inline>
312
+ </Stack>
313
+ )
314
+ }
315
+ ```
@@ -0,0 +1,101 @@
1
+ # NoticeStack
2
+
3
+ > 알림 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 닫을 수 있는 공지 카드 묶음(새 기능 소개, 팁, 점검 안내). 평소에는 맨 앞 카드 뒤로 나머지가 살짝 겹쳐 보이고, 마우스를 잠시 올리거나 포커스가 들어오면 펼쳐져요. ×로 닫으면 카드가 등장의 역방향으로 사라지고 다음 카드가 그 자리로 미끄러져 와요. 카드는 Card · CardHeader · CardFooter 조립이에요.
6
+
7
+ ```tsx
8
+ import { NoticeStack, type NoticeItem } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 겹친 카드 묶음(`Toaster`, `NoticeStack`)은 같은 방식: 뒤 카드는 앞 카드 높이로 맞춰 가장자리만 보이고, hover(의도 지연·유예)·focus로 펼치며, 닫힌 카드는 제자리에서 등장 역방향으로 나가고 나머지가 그 자리로 이동.
14
+
15
+ ## API
16
+
17
+ ### NoticeStack
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `items` (필수) | `NoticeItem[]` | | 카드 목록. 첫 번째가 맨 앞. |
22
+ | `onDismiss` | `(id: string) => void` | | 퇴장 애니메이션이 끝난 뒤 불려요. 여기서 items에서 빼 주세요(빼지 않아도 스스로 숨겨요). |
23
+ | `visible` | `number` | `3` | 보이는 카드 수. |
24
+ | `expanded` | `boolean` | | hover · focus와 상관없이 항상 펼침. |
25
+ | `direction` | `'up' \| 'down'` | `'up'` | up: 아래에 붙어 위로 겹치고 펼쳐져요 · down: 반대. |
26
+ | `gap` | `number` | `8` | 펼쳤을 때 카드 사이(px). |
27
+ | `dismissLabel` | `string` | `'닫기'` | × 버튼의 aria-label. |
28
+ | `aria-label` | `string` | `'공지'` | 묶음(section)의 접근성 이름. |
29
+ | `className / style` | `string / CSSProperties` | | 바깥 요소에 전달. |
30
+
31
+ ### NoticeItem
32
+
33
+ | prop | 타입 | 기본값 | 설명 |
34
+ | --- | --- | --- | --- |
35
+ | `id` (필수) | `string` | | 고유 id. |
36
+ | `title` (필수) | `ReactNode` | | 굵은 제목. |
37
+ | `eyebrow` | `ReactNode` | | 제목 위 작은 파란 글자("새 기능"). |
38
+ | `description` | `ReactNode` | | 제목 아래 설명. |
39
+ | `footer` | `ReactNode` | | 아래 줄(예: "전체 변경 기록 ›" 버튼). |
40
+ | `dismissible` | `boolean` | `true` | false면 ×가 없어요. |
41
+
42
+ ## 예제
43
+
44
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
45
+
46
+ ### 사이드바 아래 공지
47
+
48
+ direction='up'(기본): 뒤 카드가 위로 삐져나오고 위로 펼쳐져요. hover는 잠깐(200ms) 머물러야 펼치고, 벗어나도 300ms 유예 뒤 접혀요. 닫은 카드는 onDismiss로 목록에서 빼요. 키보드로 닫으면 포커스가 다음 카드의 ×로 옮겨 가요.
49
+
50
+ ```tsx
51
+ import { useState } from 'react'
52
+ import { ChevronRight, RotateCcw } from 'lucide-react'
53
+ import { Button, Stack, NoticeStack, type NoticeItem } from '@iamfiro/design'
54
+
55
+ const NOTICES: NoticeItem[] = [
56
+ {
57
+ id: 'shared-folders',
58
+ eyebrow: '10월 새 기능',
59
+ title: '공유 폴더',
60
+ description: '폴더째로 팀원과 나눠 쓸 수 있어요.',
61
+ footer: (
62
+ <Button variant="ghost" size="sm" iconRight={<ChevronRight />}>
63
+ 전체 변경 기록
64
+ </Button>
65
+ ),
66
+ },
67
+ { id: 'templates', eyebrow: '새 기능', title: '문서 템플릿', description: '자주 쓰는 형식을 템플릿으로 저장하세요.' },
68
+ { id: 'mobile', eyebrow: '안내', title: '모바일 앱 출시', description: 'iOS · Android에서 바로 받아 보세요.' },
69
+ ]
70
+
71
+ export default function Example() {
72
+ const [items, setItems] = useState(NOTICES)
73
+ return (
74
+ // 사이드바 아래처럼 바닥에 붙여 두면 위로 펼쳐져요.
75
+ <Stack gap={4} justify="end" style={{ width: 260, height: 340 }}>
76
+ <NoticeStack items={items} onDismiss={(id) => setItems((prev) => prev.filter((n) => n.id !== id))} />
77
+ <Button size="sm" variant="ghost" icon={<RotateCcw />} onClick={() => setItems(NOTICES)} disabled={items.length === NOTICES.length}>
78
+ 다시 보기
79
+ </Button>
80
+ </Stack>
81
+ )
82
+ }
83
+ ```
84
+
85
+ ### 항상 펼침 · 아래로
86
+
87
+ expanded면 늘 펼쳐 두고, direction='down'이면 아래로 쌓여요. dismissible: false인 카드는 × 없이 남아요.
88
+
89
+ ```tsx
90
+ import { NoticeStack, type NoticeItem } from '@iamfiro/design'
91
+
92
+ const NOTICES: NoticeItem[] = [
93
+ { id: 'maintenance', eyebrow: '점검 안내', title: '10월 28일 새벽 2시 점검', description: '약 30분 동안 저장이 잠시 멈춰요.', dismissible: false },
94
+ { id: 'export', eyebrow: '새 기능', title: 'PDF로 내보내기', description: '문서를 PDF로 바로 저장할 수 있어요.' },
95
+ { id: 'shortcut', eyebrow: '팁', title: '⌘K로 어디든 이동', description: '검색창에서 페이지 이름을 입력해 보세요.' },
96
+ ]
97
+
98
+ export default function Example() {
99
+ return <NoticeStack items={NOTICES} expanded direction="down" style={{ width: 300 }} />
100
+ }
101
+ ```
@@ -0,0 +1,96 @@
1
+ # NumberStepper
2
+
3
+ > 텍스트 입력 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ − 숫자 + 로 작은 수(작업자 수, 좌석, 재시도 횟수)를 바꿔요. 입력 칸이 아니라 테두리 없이 흰 칩 + 옅은 그림자예요. 짧게 누르면 한 칸(놓는 순간), 길게 누르면 잠깐 뒤부터 연속으로 바뀌고, 숫자는 움직인 방향에서 미끄러져 들어와요. 숫자는 ARIA spinbutton이라 키보드로도 바꿔요.
6
+
7
+ ```tsx
8
+ import { NumberStepper } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 작은 수 조절은 `NumberStepper`: 클릭은 놓을 때 한 칸, 길게 누르면 `TIMING.repeatDelay` 뒤 `repeatInterval`마다 반복.
14
+
15
+ ## API
16
+
17
+ ### NumberStepper
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `value` | `number` | | 현재 값(제어). |
22
+ | `defaultValue` | `number` | | 처음 값(비제어). 없으면 0을 min~max 안으로. |
23
+ | `onValueChange` | `(value: number) => void` | | 값이 바뀔 때(길게 누르는 동안 매번). |
24
+ | `min / max` | `number` | `-Infinity / Infinity` | 범위. 닿으면 그쪽 버튼이 비활성. |
25
+ | `step` | `number` | `1` | 한 번에 바뀌는 양. 소수면 그 자릿수로 반올림. |
26
+ | `largeStep` | `number` | `step × 10` | PageUp / PageDown으로 바뀌는 양. |
27
+ | `size` | `'sm' \| 'md'` | `'md'` | 높이 28 / 34px. |
28
+ | `disabled` | `boolean` | | 비활성. |
29
+ | `formatValue` | `(value: number) => ReactNode` | | 보이는 글자(예: "4명"). 글자면 aria-valuetext에도 쓰여요. |
30
+ | `aria-label / aria-labelledby` | `string` | | spinbutton의 이름. |
31
+ | `decrementLabel / incrementLabel` | `string` | `'줄이기' / '늘리기'` | − / + 버튼의 aria-label. |
32
+ | `name` | `string` | | 폼 제출용 숨은 input 이름. |
33
+ | `id / className / style` | `string / string / CSSProperties` | | id는 spinbutton에, 나머지는 바깥 요소에. |
34
+
35
+ ## 예제
36
+
37
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
38
+
39
+ ### 크기 · 범위 · 비활성
40
+
41
+ md · sm 크기. min / max에 닿으면 그쪽 버튼이 비활성이 돼요. 비활성은 회색 채움.
42
+
43
+ ```tsx
44
+ import { Inline, Stack, Text, NumberStepper } from '@iamfiro/design'
45
+
46
+ export default function Example() {
47
+ return (
48
+ <Stack gap={4} align="start">
49
+ <Inline gap={4}>
50
+ <NumberStepper defaultValue={4} min={1} max={8} aria-label="동시 작업 수" />
51
+ <NumberStepper size="sm" defaultValue={2} min={0} max={10} aria-label="재시도 횟수" />
52
+ <NumberStepper defaultValue={3} disabled aria-label="좌석 수" />
53
+ </Inline>
54
+ <Text variant="caption" tone="tertiary">
55
+ 버튼을 길게 누르면 잠깐 뒤 연속으로 바뀌어요. 숫자에 포커스하고 ↑ ↓ · PageUp/PageDown · Home/End로도 바꿔요.
56
+ </Text>
57
+ </Stack>
58
+ )
59
+ }
60
+ ```
61
+
62
+ ### 카드 머리글에서 제어
63
+
64
+ 작업 대기열 카드의 일시정지 버튼 옆에 둔 예. value / onValueChange로 제어하고 formatValue로 "4명"처럼 보여요.
65
+
66
+ ```tsx
67
+ import { useState } from 'react'
68
+ import { Pause, Play } from 'lucide-react'
69
+ import { Button, Inline, Text, Card, CardBody, CardHeader, NumberStepper } from '@iamfiro/design'
70
+
71
+ export default function Example() {
72
+ const [workers, setWorkers] = useState(4)
73
+ const [paused, setPaused] = useState(false)
74
+ return (
75
+ <Card padding="sm" style={{ width: 420 }}>
76
+ <CardHeader
77
+ title="작업 대기열"
78
+ description={paused ? '일시정지됨' : `작업자 ${workers}명이 처리 중이에요`}
79
+ actions={
80
+ <Inline gap={2} wrap={false}>
81
+ <Button size="sm" icon={paused ? <Play /> : <Pause />} onClick={() => setPaused((v) => !v)}>
82
+ {paused ? '다시 시작' : '일시정지'}
83
+ </Button>
84
+ <NumberStepper size="sm" value={workers} onValueChange={setWorkers} min={1} max={12} aria-label="작업자 수" formatValue={(v) => `${v}명`} />
85
+ </Inline>
86
+ }
87
+ />
88
+ <CardBody>
89
+ <Text as="p" variant="caption" tone="tertiary">
90
+ value / onValueChange로 제어해요. formatValue는 화면 글자와 aria-valuetext에 함께 쓰여요.
91
+ </Text>
92
+ </CardBody>
93
+ </Card>
94
+ )
95
+ }
96
+ ```