@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,392 @@
1
+ # List
2
+
3
+ > 컨테이너 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 사람 · 파일 · 설정처럼 같은 모양의 행을 나열해요. 행 사이는 선 대신 여백으로 구분하고, 앞(leading) · 제목 · 설명 · 메타 · 뒤(trailing) 자리를 채워 조합해요. href나 onClick을 주면 행 전체가 눌리고, trailing 안의 컨트롤은 행과 따로 동작해요.
6
+
7
+ ```tsx
8
+ import { List, ListItem } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 순위 목록(많이 본 페이지 · 유입 경로 · 국가)은 `BarList`: 이름 뒤로 옅은 막대(`tone` 또는 `color` 16% 섞기), 값은 오른쪽 tabular. 긴 목록은 `limit` + "더 보기"(0fr ↔ 1fr), 순위가 바뀌면 행이 새 자리로 미끄러진다. 행 진행률 하나면 `ListItem progress`.
14
+ - 사람·파일·설정 같은 행은 `List` + `ListItem`. 행 사이는 선 대신 간격, 누를 수 있는 행은 hover 채움.
15
+ - 파운데이션보다 **이미 있는 조합 컴포넌트를 먼저** 쓴다: 카드 제목줄은 `CardHeader`(오른쪽은 `actions`), 행 목록은 `List` + `ListItem`(`leading` · `trailing`). Stack/Inline으로 같은 것을 다시 짜지 않는다. List를 카드 안에 넣을 땐 `Card padding="sm"`(12px)이면 행 안쪽 여백(12px)과 왼쪽 끝이 맞는다.
16
+ - 행 안의 진행률은 `ListItem progress`(행 뒤로 옅은 채움), 그룹으로 나눈 체크리스트는 `ProgressTracker groups`(Accordion `filled` + StatusIcon).
17
+ - 목록–상세는 `SplitView`(칸마다 스크롤, 경계는 `role="separator"`). 회색 판 위의 선택 행은 `ListItem variant="raised"`.
18
+
19
+ ## API
20
+
21
+ ### List
22
+
23
+ | prop | 타입 | 기본값 | 설명 |
24
+ | --- | --- | --- | --- |
25
+ | `children` (필수) | `ReactNode` | | ListItem들. |
26
+ | `variant` | `'plain' \| 'inset'` | `'plain'` | plain: 페이지 위의 행 · inset: 흰 카드 위에 묶인 행. |
27
+ | `density` | `'compact' \| 'default' \| 'comfortable'` | `'default'` | 행 높이와 여백. |
28
+ | `label` | `ReactNode` | | 행 위의 작은 제목(예: "멤버 6명"). |
29
+ | `aria-label` | `string` | | 목록(ul)의 접근성 이름. label이 없을 때 주세요. |
30
+ | `className / style` | `string / CSSProperties` | | 바깥 요소에 전달. |
31
+
32
+ ### ListItem
33
+
34
+ List.Item으로도 쓸 수 있어요.
35
+
36
+ | prop | 타입 | 기본값 | 설명 |
37
+ | --- | --- | --- | --- |
38
+ | `title` (필수) | `ReactNode` | | 첫 줄. |
39
+ | `description` | `ReactNode` | | 회색 둘째 줄. |
40
+ | `leading` | `ReactNode` | | 왼쪽 아바타 · 아이콘 · 썸네일. |
41
+ | `meta` | `ReactNode` | | 오른쪽 작은 회색 글자(시간, 크기, 개수). |
42
+ | `trailing` | `ReactNode` | | 오른쪽 컨트롤(버튼, 스위치, 배지, 메뉴). 행 클릭과 따로 동작해요. |
43
+ | `href` | `string` | | 행 전체를 링크로. |
44
+ | `onClick` | `() => void` | | 행 전체를 버튼으로(Enter / Space 지원). |
45
+ | `chevron` | `boolean` | | 끝에 › 표시. 다른 화면을 여는 행에. |
46
+ | `selected` | `boolean` | | 선택 상태. 링크면 aria-current="page", 버튼이면 aria-pressed. |
47
+ | `disabled` | `boolean` | | 비활성. 눌리지 않아요. |
48
+ | `variant` | `'default' \| 'raised'` | `'default'` | raised: 선택된 행이 회색 채움 대신 그림자가 있는 흰 카드로 떠올라요. 옅은 회색 판(SplitView 목록) 위의 목록에. |
49
+ | `progress` | `number` | | 0–1. 행 내용 뒤로 옅은 채움이 왼쪽부터 자라요(행 안의 작업 진행률). 바뀌면 너비가 부드럽게 움직여요. 채움은 장식이라 진행률은 글자로도 보여 주세요. |
50
+ | `progressTone` | `'accent' \| 'success' \| 'warning' \| 'danger' \| 'neutral'` | `'accent'` | progress 채움 색. 일시정지는 neutral, 완료는 success처럼. |
51
+ | `className` | `string` | | 행(li)에 전달. |
52
+
53
+ ## 예제
54
+
55
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
56
+
57
+ ### 멤버
58
+
59
+ leading에 Avatar, trailing에 Badge, meta에 최근 활동 시간.
60
+
61
+ ```tsx
62
+ import { Avatar, Badge, type Tone, List, ListItem } from '@iamfiro/design'
63
+
64
+ const MEMBERS: Array<{ name: string; email: string; role: string; tone: Tone; seen: string; online?: boolean }> = [
65
+ { name: '김민지', email: 'minji@lumen.app', role: '관리자', tone: 'accent', seen: '접속 중', online: true },
66
+ { name: '이도현', email: 'dohyun@lumen.app', role: '편집자', tone: 'neutral', seen: '2분 전', online: true },
67
+ { name: '박서연', email: 'seoyeon@lumen.app', role: '초대됨', tone: 'info', seen: '10월 24일' },
68
+ { name: '최준호', email: 'junho@lumen.app', role: '뷰어', tone: 'neutral', seen: '3일 전' },
69
+ ]
70
+
71
+ export default function Example() {
72
+ return (
73
+ <List label={`멤버 ${MEMBERS.length}명`} style={{ width: 460 }}>
74
+ {MEMBERS.map((m) => (
75
+ <ListItem
76
+ key={m.email}
77
+ leading={<Avatar name={m.name} size={36} online={m.online} />}
78
+ title={m.name}
79
+ description={m.email}
80
+ meta={m.seen}
81
+ trailing={<Badge tone={m.tone}>{m.role}</Badge>}
82
+ />
83
+ ))}
84
+ </List>
85
+ )
86
+ }
87
+ ```
88
+
89
+ ### 파일
90
+
91
+ leading에 아이콘, meta에 크기 · 수정 시간, trailing에 DropdownMenu. 메뉴를 눌러도 행은 눌리지 않아요.
92
+
93
+ ```tsx
94
+ import { useState } from 'react'
95
+ import { Download, FileSpreadsheet, FileText, Image, MoreHorizontal, Pencil, Trash2 } from 'lucide-react'
96
+ import { Button, DropdownMenu, DropdownMenuItem, DropdownMenuSeparator, List, ListItem } from '@iamfiro/design'
97
+
98
+ const FILES = [
99
+ { id: 'f1', icon: <FileText />, name: '3분기 사업 계획서.pdf', size: '2.4MB', time: '2분 전' },
100
+ { id: 'f2', icon: <FileSpreadsheet />, name: '10월 매출 정리.xlsx', size: '860KB', time: '1시간 전' },
101
+ { id: 'f3', icon: <Image />, name: '랜딩 페이지 시안.png', size: '5.1MB', time: '10월 23일' },
102
+ ]
103
+
104
+ export default function Example() {
105
+ const [opened, setOpened] = useState<string | null>(null)
106
+ return (
107
+ <List aria-label="최근 파일" style={{ width: 480 }}>
108
+ {FILES.map((f) => (
109
+ <ListItem
110
+ key={f.id}
111
+ leading={f.icon}
112
+ title={f.name}
113
+ description={opened === f.id ? '방금 열어 봤어요' : undefined}
114
+ meta={`${f.size} · ${f.time}`}
115
+ onClick={() => setOpened(f.id)}
116
+ selected={opened === f.id}
117
+ trailing={
118
+ <DropdownMenu align="end" aria-label={`${f.name} 메뉴`} trigger={<Button iconOnly variant="ghost" size="sm" icon={<MoreHorizontal />} aria-label="더보기" />}>
119
+ <DropdownMenuItem icon={<Download />}>다운로드</DropdownMenuItem>
120
+ <DropdownMenuItem icon={<Pencil />}>이름 바꾸기</DropdownMenuItem>
121
+ <DropdownMenuSeparator />
122
+ <DropdownMenuItem icon={<Trash2 />} danger>
123
+ 삭제
124
+ </DropdownMenuItem>
125
+ </DropdownMenu>
126
+ }
127
+ />
128
+ ))}
129
+ </List>
130
+ )
131
+ }
132
+ ```
133
+
134
+ ### 설정
135
+
136
+ trailing에 Switch를 둔 행과, chevron으로 다음 화면을 여는 행. 실제 앱에선 href로 링크를, 여기선 onClick으로 버튼을 만들었어요. Enter / Space로도 눌러요.
137
+
138
+ ```tsx
139
+ import { useState } from 'react'
140
+ import { Bell, Globe, KeyRound, Moon, Smartphone } from 'lucide-react'
141
+ import { Stack, Switch, List, ListItem } from '@iamfiro/design'
142
+
143
+ export default function Example() {
144
+ const [push, setPush] = useState(true)
145
+ const [dark, setDark] = useState(false)
146
+ const [opened, setOpened] = useState<string | null>(null)
147
+
148
+ return (
149
+ <Stack gap={6} style={{ width: 440 }}>
150
+ <List variant="inset" label="일반">
151
+ <ListItem
152
+ leading={<Bell />}
153
+ title="푸시 알림"
154
+ description="멘션과 댓글을 바로 알려 드려요."
155
+ trailing={<Switch checked={push} onChange={(e) => setPush(e.target.checked)} aria-label="푸시 알림" />}
156
+ />
157
+ <ListItem
158
+ leading={<Moon />}
159
+ title="다크 모드"
160
+ trailing={<Switch checked={dark} onChange={(e) => setDark(e.target.checked)} aria-label="다크 모드" />}
161
+ />
162
+ <ListItem leading={<Globe />} title="언어" meta="한국어" onClick={() => setOpened('language')} selected={opened === 'language'} chevron />
163
+ </List>
164
+ <List variant="inset" label="보안">
165
+ <ListItem
166
+ leading={<KeyRound />}
167
+ title="비밀번호 변경"
168
+ description={opened === 'password' ? '변경 화면을 열었어요.' : '마지막 변경 3개월 전'}
169
+ onClick={() => setOpened('password')}
170
+ chevron
171
+ />
172
+ <ListItem
173
+ leading={<Smartphone />}
174
+ title="로그인된 기기"
175
+ meta="3대"
176
+ onClick={() => setOpened('devices')}
177
+ selected={opened === 'devices'}
178
+ chevron
179
+ />
180
+ <ListItem leading={<KeyRound />} title="보안 키" description="조직 관리자만 설정할 수 있어요." disabled chevron />
181
+ </List>
182
+ </Stack>
183
+ )
184
+ }
185
+ ```
186
+
187
+ ### 카드로 묶은 목록
188
+
189
+ Card padding="sm" 안에 List를 넣으면 왼쪽 끝이 맞아요. leading에 IconTile(아이콘 · 글자 타일), trailing에 Kbd 단축키, 제목 뒤엔 회색 도메인을 붙였어요. label이 그룹 제목이 돼요.
190
+
191
+ ```tsx
192
+ import { Bookmark, FileSearch, History, Languages } from 'lucide-react'
193
+ import { IconTile, Kbd, Stack, Text, Card, List, ListItem } from '@iamfiro/design'
194
+
195
+ const TOOLS = [
196
+ { id: 'bookmark', label: '북마크', icon: <Bookmark />, keys: ['mod', 'shift', 'B'] },
197
+ { id: 'history', label: '방문 기록', icon: <History />, keys: ['mod', 'Y'] },
198
+ { id: 'find', label: '페이지에서 찾기', icon: <FileSearch />, keys: ['mod', 'F'] },
199
+ { id: 'translate', label: '번역', icon: <Languages /> },
200
+ ]
201
+
202
+ const SUGGESTED = [
203
+ { id: 'wiki', title: '팀 위키', domain: 'wiki.lumen.app' },
204
+ { id: 'design', title: '디자인 가이드', domain: 'design.lumen.app' },
205
+ { id: 'news', title: '사내 소식', domain: 'news.lumen.app' },
206
+ ]
207
+
208
+ export default function Example() {
209
+ return (
210
+ <Stack gap={3} style={{ width: 300 }}>
211
+ {/* Card padding="sm"(12px)이면 행 안쪽 여백(12px)과 왼쪽 끝이 맞아요. */}
212
+ <Card padding="sm">
213
+ <List label="도구" density="compact">
214
+ {TOOLS.map((t) => (
215
+ <ListItem
216
+ key={t.id}
217
+ leading={<IconTile>{t.icon}</IconTile>}
218
+ title={t.label}
219
+ onClick={() => {}}
220
+ trailing={t.keys && <Kbd keys={t.keys} size="sm" />}
221
+ />
222
+ ))}
223
+ </List>
224
+ </Card>
225
+ <Card padding="sm">
226
+ <List label="추천" density="compact">
227
+ {SUGGESTED.map((s) => (
228
+ <ListItem
229
+ key={s.id}
230
+ leading={<IconTile>{s.title.slice(0, 1)}</IconTile>}
231
+ title={
232
+ <>
233
+ {s.title}{' '}
234
+ <Text as="span" variant="body-sm" tone="tertiary">
235
+ {s.domain}
236
+ </Text>
237
+ </>
238
+ }
239
+ href="#"
240
+ />
241
+ ))}
242
+ </List>
243
+ </Card>
244
+ </Stack>
245
+ )
246
+ }
247
+ ```
248
+
249
+ ### 행 안의 진행률 (progress)
250
+
251
+ progress(0–1)를 주면 행 뒤로 옅은 채움이 자라요. 작업 대기열처럼 행마다 진행 상황이 다를 때. 일시정지하면 회색(neutral), 끝나면 초록(success)으로 바꿨어요.
252
+
253
+ ```tsx
254
+ import { useEffect, useState } from 'react'
255
+ import { FileSpreadsheet, FileText, Image, Pause, Play } from 'lucide-react'
256
+ import { Button, IconTile, Text, Card, CardHeader, List, ListItem } from '@iamfiro/design'
257
+
258
+ const JOBS = [
259
+ { id: 'j1', icon: <FileSpreadsheet />, title: '10월 정산 내보내기', worker: '김민지', speed: 0.07 },
260
+ { id: 'j2', icon: <Image />, title: '상품 사진 1,240장 압축', worker: '이도현', speed: 0.04 },
261
+ { id: 'j3', icon: <FileText />, title: '계약서 PDF 변환', worker: '김민지', speed: 0.11 },
262
+ ]
263
+
264
+ export default function Example() {
265
+ const [paused, setPaused] = useState(false)
266
+ const [progress, setProgress] = useState<Record<string, number>>({ j1: 0.35, j2: 0.6, j3: 0.12 })
267
+
268
+ // 데모용 진행: 1초마다 조금씩 채워지고, 다 차면 처음부터 다시.
269
+ useEffect(() => {
270
+ if (paused) return
271
+ const id = window.setInterval(() => {
272
+ setProgress((prev) => Object.fromEntries(JOBS.map((j) => [j.id, prev[j.id] >= 1 ? 0 : Math.min(1, prev[j.id] + j.speed)])))
273
+ }, 1000)
274
+ return () => window.clearInterval(id)
275
+ }, [paused])
276
+
277
+ return (
278
+ <Card padding="sm" style={{ width: 440 }}>
279
+ <CardHeader
280
+ title="작업 대기열"
281
+ actions={
282
+ <Button variant="secondary" size="sm" icon={paused ? <Play /> : <Pause />} onClick={() => setPaused((v) => !v)}>
283
+ {paused ? '다시 시작' : '일시정지'}
284
+ </Button>
285
+ }
286
+ />
287
+ <List density="compact" aria-label="실행 중인 작업">
288
+ {JOBS.map((j) => {
289
+ const p = progress[j.id]
290
+ return (
291
+ <ListItem
292
+ key={j.id}
293
+ progress={p}
294
+ progressTone={p >= 1 ? 'success' : paused ? 'neutral' : 'accent'}
295
+ leading={<IconTile size={28}>{j.icon}</IconTile>}
296
+ title={j.title}
297
+ description={j.worker}
298
+ meta={
299
+ <Text as="span" variant="caption" tone="tertiary" numeric>
300
+ {p >= 1 ? '완료' : paused ? '일시정지' : `${Math.round(p * 100)}%`}
301
+ </Text>
302
+ }
303
+ />
304
+ )
305
+ })}
306
+ </List>
307
+ </Card>
308
+ )
309
+ }
310
+ ```
311
+
312
+ ### 떠오르는 선택 (variant="raised")
313
+
314
+ 회색 판 위의 목록에서 선택한 행이 흰 카드로 떠올라요. hover는 반투명이라 어떤 회색 위에서도 보여요. SplitView의 목록 쪽에 써요.
315
+
316
+ ```tsx
317
+ import { useState } from 'react'
318
+ import { FileSignature, Receipt, UserPlus } from 'lucide-react'
319
+ import { Box, IconTile, List, ListItem } from '@iamfiro/design'
320
+
321
+ const ITEMS = [
322
+ { id: 'a1', icon: <Receipt />, title: '10월 법인 카드 정산', meta: '경비', time: '방금' },
323
+ { id: 'a2', icon: <UserPlus />, title: '이도현 님 계정 권한 요청', meta: '권한', time: '14분' },
324
+ { id: 'a3', icon: <FileSignature />, title: '협력사 계약서 검토', meta: '계약', time: '1시간' },
325
+ ]
326
+
327
+ export default function Example() {
328
+ const [selected, setSelected] = useState('a1')
329
+ return (
330
+ <Box background="subtle" radius="lg" padding={2} style={{ width: 360 }}>
331
+ <List aria-label="승인 대기">
332
+ {ITEMS.map((it) => (
333
+ <ListItem
334
+ key={it.id}
335
+ variant="raised"
336
+ selected={selected === it.id}
337
+ onClick={() => setSelected(it.id)}
338
+ leading={<IconTile tone="surface">{it.icon}</IconTile>}
339
+ title={it.title}
340
+ description={it.meta}
341
+ meta={it.time}
342
+ />
343
+ ))}
344
+ </List>
345
+ </Box>
346
+ )
347
+ }
348
+ ```
349
+
350
+ ### 표면과 밀도
351
+
352
+ plain은 페이지 위에 바로, inset은 흰 카드 위에 묶어요. density로 행 높이를 compact / default / comfortable 중에 골라요.
353
+
354
+ ```tsx
355
+ import { useState } from 'react'
356
+ import { Avatar, SegmentedControl, Stack, Grid, List, ListItem, type ListProps } from '@iamfiro/design'
357
+
358
+ type Density = NonNullable<ListProps['density']>
359
+
360
+ const PEOPLE = [
361
+ { name: '김민지', role: '프로덕트 디자이너' },
362
+ { name: '이도현', role: '프론트엔드 개발자' },
363
+ { name: '박서연', role: '리서처' },
364
+ ]
365
+
366
+ export default function Example() {
367
+ const [density, setDensity] = useState<Density>('default')
368
+ return (
369
+ <Stack gap={5} align="start">
370
+ <SegmentedControl<Density>
371
+ ariaLabel="밀도"
372
+ value={density}
373
+ onChange={setDensity}
374
+ options={[
375
+ { value: 'compact', label: 'compact' },
376
+ { value: 'default', label: 'default' },
377
+ { value: 'comfortable', label: 'comfortable' },
378
+ ]}
379
+ />
380
+ <Grid columns={2} gap={6} style={{ width: 640 }}>
381
+ {(['plain', 'inset'] as const).map((variant) => (
382
+ <List key={variant} variant={variant} density={density} label={variant}>
383
+ {PEOPLE.map((p) => (
384
+ <ListItem key={p.name} leading={<Avatar name={p.name} />} title={p.name} description={p.role} />
385
+ ))}
386
+ </List>
387
+ ))}
388
+ </Grid>
389
+ </Stack>
390
+ )
391
+ }
392
+ ```
@@ -0,0 +1,142 @@
1
+ # Marker
2
+
3
+ > 대화 & 활동 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 대화 · 활동 기록 사이에 끼는 이벤트 한 줄. 누가 말한 것이 아니라 일어난 일(멤버 초대, PR 병합, 메시지 고정)을 작은 아이콘 + 회색 글로 보여 줘요. 테두리나 배경 없이 글자 톤만으로 메시지와 구분되고, 참조(#482)는 Link variant="inherit"로 밑줄만 그어요. CommentThread 안에 넣으면 메시지 사이 간격이 자동으로 맞춰져요.
6
+
7
+ ```tsx
8
+ import { Marker } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 대화 사이의 이벤트 줄(초대 · 병합 · 고정)은 `Marker`: 16px 아이콘 + 회색 한 줄, 배경 · 테두리 없음. 아이콘은 기본 회색, 결과에 상태가 담긴 이벤트만 `tone`(병합 = success). 스레드 안에선 `gutter={avatarSize}`로 아바타 열에 맞춘다. 글 속 참조(#482)는 `Link variant="inherit"`(주변 색 + 밑줄).
14
+
15
+ ## API
16
+
17
+ ### Marker
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `children` (필수) | `ReactNode` | | 일어난 일. 회색 body-md 한 줄(길면 줄바꿈). |
22
+ | `icon` | `ReactNode` | | 16px 아이콘(UserPlus, GitMerge, Pin…). |
23
+ | `tone` | `'neutral' \| 'accent' \| 'success' \| 'warning' \| 'danger' \| 'info'` | `'neutral'` | 아이콘 색. 글자는 항상 회색이에요. |
24
+ | `date` | `Date \| string \| number` | | 일어난 시각. 글 끝에 "2분 전"처럼, 올리면 전체 날짜. |
25
+ | `gutter` | `number` | | 아이콘 열 폭(px). Comment의 avatarSize를 주면 아바타 · 본문 선에 맞춰져요. |
26
+ | `appear` | `boolean` | | 처음 나타날 때 아래에서 올라오며 등장(실시간으로 들어온 이벤트). |
27
+ | `className / style` | `string / CSSProperties` | | 바깥 요소에 전달. |
28
+
29
+ ## 예제
30
+
31
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
32
+
33
+ ### 기본
34
+
35
+ 아이콘은 기본 회색(neutral). 병합처럼 결과에 상태가 담긴 이벤트만 tone으로 색을 줘요.
36
+
37
+ ```tsx
38
+ import { GitMerge, Pin, UserPlus } from 'lucide-react'
39
+ import { Link, Stack, Marker } from '@iamfiro/design'
40
+
41
+ export default function Example() {
42
+ return (
43
+ <Stack gap={3}>
44
+ <Marker icon={<UserPlus />}>김민지 님이 이도현, 박솔 님을 초대했어요</Marker>
45
+ <Marker icon={<GitMerge />} tone="success">
46
+ 박솔 님이 <Link variant="inherit" href="#">#482</Link>를 main에 병합했어요
47
+ </Marker>
48
+ <Marker icon={<Pin />}>박솔 님이 메시지를 고정했어요</Marker>
49
+ </Stack>
50
+ )
51
+ }
52
+ ```
53
+
54
+ ### 아이콘 색 · 시간
55
+
56
+ Badge와 같은 tone. date를 주면 글 끝에 RelativeTime이 붙고, 올리면 전체 날짜가 나와요. 긴 글은 줄이 바뀌어도 아이콘이 첫 줄 옆에 있어요.
57
+
58
+ ```tsx
59
+ import { CircleAlert, GitPullRequestClosed, Rocket, Sparkles, TriangleAlert, UserMinus } from 'lucide-react'
60
+ import { Link, Stack, Marker } from '@iamfiro/design'
61
+
62
+ const minutesAgo = (m: number) => new Date(Date.now() - m * 60_000)
63
+
64
+ export default function Example() {
65
+ return (
66
+ <Stack gap={3} style={{ maxWidth: 420 }}>
67
+ <Marker icon={<UserMinus />} date={minutesAgo(42)}>
68
+ 이도현 님이 채널을 나갔어요
69
+ </Marker>
70
+ <Marker icon={<Sparkles />} tone="accent" date={minutesAgo(18)}>
71
+ AI가 대화를 요약했어요
72
+ </Marker>
73
+ <Marker icon={<Rocket />} tone="info" date={minutesAgo(9)}>
74
+ <Link variant="inherit" href="#">v2.14.0</Link>이 스테이징에 배포됐어요
75
+ </Marker>
76
+ <Marker icon={<TriangleAlert />} tone="warning" date={minutesAgo(5)}>
77
+ 빌드 시간이 평소보다 오래 걸렸어요
78
+ </Marker>
79
+ <Marker icon={<GitPullRequestClosed />} tone="danger" date={minutesAgo(2)}>
80
+ 김민지 님이 <Link variant="inherit" href="#">#479</Link>를 병합하지 않고 닫았어요
81
+ </Marker>
82
+ <Marker icon={<CircleAlert />} date={minutesAgo(1)}>
83
+ 이 채널의 메시지는 90일이 지나면 자동으로 삭제돼요. 보관이 필요한 내용은 고정하거나 문서로 옮겨 주세요
84
+ </Marker>
85
+ </Stack>
86
+ )
87
+ }
88
+ ```
89
+
90
+ ### 대화 안에서
91
+
92
+ CommentThread에 Comment와 섞어 넣었어요. gutter에 Comment의 avatarSize(28)를 주면 아이콘이 아바타 열 가운데, 글은 메시지 본문 선에 맞춰져요. 버튼으로 이벤트를 추가하면 appear로 아래에서 올라와요.
93
+
94
+ ```tsx
95
+ import { useState } from 'react'
96
+ import { GitMerge, Pin, UserPlus } from 'lucide-react'
97
+ import { Button, Link, Card, CardBody, CardHeader, Comment, CommentThread, Marker } from '@iamfiro/design'
98
+
99
+ const minutesAgo = (m: number) => new Date(Date.now() - m * 60_000)
100
+ const INVITED = minutesAgo(30)
101
+ const ASKED = minutesAgo(24)
102
+ const MERGED = minutesAgo(12)
103
+ const ANSWERED = minutesAgo(10)
104
+
105
+ export default function Example() {
106
+ const [pinned, setPinned] = useState<Date | null>(null)
107
+
108
+ return (
109
+ <Card padding="sm" style={{ width: 400 }}>
110
+ <CardHeader
111
+ title="# 디자인-시스템"
112
+ actions={
113
+ <Button size="sm" variant="ghost" icon={<Pin />} disabled={pinned != null} onClick={() => setPinned(new Date())}>
114
+ 메시지 고정
115
+ </Button>
116
+ }
117
+ />
118
+ <CardBody>
119
+ <CommentThread aria-label="채널 메시지">
120
+ <Marker icon={<UserPlus />} gutter={28} date={INVITED}>
121
+ 김민지 님이 이도현, 박솔 님을 초대했어요
122
+ </Marker>
123
+ <Comment author="이도현" date={ASKED}>
124
+ 버튼 토큰 정리한 PR 올렸어요. 리뷰 부탁드려요.
125
+ </Comment>
126
+ <Marker icon={<GitMerge />} tone="success" gutter={28} date={MERGED}>
127
+ 박솔 님이 <Link variant="inherit" href="#">#482</Link>를 main에 병합했어요
128
+ </Marker>
129
+ <Comment author="박솔" date={ANSWERED}>
130
+ 확인하고 병합했어요. 다음 배포에 들어가요.
131
+ </Comment>
132
+ {pinned && (
133
+ <Marker icon={<Pin />} gutter={28} date={pinned} appear>
134
+ 박솔 님이 메시지를 고정했어요
135
+ </Marker>
136
+ )}
137
+ </CommentThread>
138
+ </CardBody>
139
+ </Card>
140
+ )
141
+ }
142
+ ```
@@ -0,0 +1,127 @@
1
+ # MaskedText
2
+
3
+ > 텍스트 & 코드 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 계좌번호 · IBAN · 키처럼 민감한 값을 양 끝만 보이고 가운데는 점 묶음으로 가려요("DE89 •••• 3000"). 숫자는 tabular(또는 고정폭)라 보기로 바꿔도 폭이 덜 흔들려요. 화면 읽기 프로그램은 점 대신 "가려진 값, 앞 DE89, 끝 3000"을 읽어요. 눈 버튼(툴팁 포함)으로 보기 · 가리기, CopyButton으로 전체 값 복사를 붙일 수 있어요.
6
+
7
+ ```tsx
8
+ import { MaskedText, maskValue } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 민감한 값은 `MaskedText`(양 끝만, 점 한 묶음이라 길이를 드러내지 않음, 화면 읽기는 "가려진 값"), 글 속 강조 + 메모 말풍선은 `Highlight note`, 첨부 파일 링크는 `FileChip`(확장자는 줄이지 않음).
14
+
15
+ ## API
16
+
17
+ ### MaskedText
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `value` (필수) | `string` | | 가릴 전체 값. |
22
+ | `label` | `string` | | 무슨 값인지("IBAN"). 화면 읽기 프로그램이 가려진 상태와 함께 읽어요. |
23
+ | `visibleStart` | `number` | `4` | 앞에 보일 글자 수. |
24
+ | `visibleEnd` | `number` | `4` | 끝에 보일 글자 수. |
25
+ | `mask` | `string` | `'•'` | 가리는 문자. |
26
+ | `groupSize` | `number` | `4` | 한 묶음의 점 개수. 묶음 사이는 공백. |
27
+ | `maskLength` | `number` | `groupSize` | 그릴 점 개수. 기본은 한 묶음이라 실제 길이를 숨겨요. |
28
+ | `revealable` | `boolean` | | 보기 · 가리기 눈 버튼(툴팁, aria-pressed). |
29
+ | `revealed / onRevealedChange` | `boolean / (revealed) => void` | | 보기 상태를 밖에서 제어할 때. |
30
+ | `defaultRevealed` | `boolean` | `false` | 처음 보기 상태(비제어). |
31
+ | `copyable` | `boolean` | | 뒤에 CopyButton. 언제나 전체 값을 복사해요. |
32
+ | `copyValue` | `string` | | 복사할 값(기본 value). 공백 없는 형태 등. |
33
+ | `mono` | `boolean` | | 고정폭 글꼴(--font-mono). 기본은 주변 글꼴의 tabular 숫자. |
34
+
35
+ ### maskValue(value, options)
36
+
37
+ 같은 규칙으로 가린 문자열을 돌려줘요. maskParts는 { start, hidden, end }로 나눠 줘요.
38
+
39
+ | prop | 타입 | 기본값 | 설명 |
40
+ | --- | --- | --- | --- |
41
+ | `value` (필수) | `string` | | 가릴 값. |
42
+ | `options` | `MaskOptions` | | visibleStart · visibleEnd · mask · groupSize · maskLength. |
43
+
44
+ ## 예제
45
+
46
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
47
+
48
+ ### 기본
49
+
50
+ 기본은 앞 4 · 끝 4글자, 가운데 점 4개 한 묶음. 점 개수가 실제 길이를 드러내지 않아요. 값의 공백은 무시해요.
51
+
52
+ ```tsx
53
+ import { MaskedText, Stack, Text } from '@iamfiro/design'
54
+
55
+ export default function Example() {
56
+ return (
57
+ <Stack gap={3}>
58
+ <Text variant="body-md">
59
+ IBAN <MaskedText label="IBAN" value="DE89 3704 0044 0532 0130 00" />
60
+ </Text>
61
+ <Text variant="body-md">
62
+ 카드 <MaskedText label="카드 번호" value="5412 7512 3412 3456" visibleStart={0} />
63
+ </Text>
64
+ <Text variant="body-md" tone="secondary">
65
+ 짧은 값은 모두 가려요: <MaskedText label="PIN" value="4821" />
66
+ </Text>
67
+ </Stack>
68
+ )
69
+ }
70
+ ```
71
+
72
+ ### 보기 · 복사
73
+
74
+ revealable은 눈 버튼(누르면 아이콘이 바뀌고 값이 아래에서 떠올라요), copyable은 CopyButton. 복사는 가려진 상태에서도 전체 값을 복사해요.
75
+
76
+ ```tsx
77
+ import { useState } from 'react'
78
+ import { MaskedText, Stack, Text, DescriptionList } from '@iamfiro/design'
79
+
80
+ export default function Example() {
81
+ const [revealed, setRevealed] = useState(false)
82
+ return (
83
+ <Stack gap={4}>
84
+ <DescriptionList
85
+ labelStyle="muted"
86
+ size="sm"
87
+ columns={1}
88
+ aria-label="입금 계좌"
89
+ items={[
90
+ {
91
+ label: '입금 계좌 · 한빛은행',
92
+ value: <MaskedText label="입금 계좌" value="110-482-301193" copyValue="110482301193" visibleStart={3} revealable copyable />,
93
+ },
94
+ {
95
+ label: '배포 키',
96
+ value: <MaskedText label="배포 키" value="dk_prod_8f2a91c4e7b05d36" visibleStart={8} mono revealable revealed={revealed} onRevealedChange={setRevealed} />,
97
+ },
98
+ ]}
99
+ />
100
+ <Text variant="caption" tone="tertiary">
101
+ 배포 키: {revealed ? '보이는 중' : '가려짐'}
102
+ </Text>
103
+ </Stack>
104
+ )
105
+ }
106
+ ```
107
+
108
+ ### 자릿수 · 문자 · 고정폭
109
+
110
+ visibleStart · visibleEnd · mask · groupSize · maskLength로 모양을, mono로 고정폭 글꼴을 골라요. 같은 규칙의 문자열이 필요하면 maskValue().
111
+
112
+ ```tsx
113
+ import { MaskedText, Stack, Text, maskValue } from '@iamfiro/design'
114
+
115
+ export default function Example() {
116
+ return (
117
+ <Stack gap={3}>
118
+ <MaskedText label="계좌" value="3333-01-5528419" visibleStart={0} visibleEnd={4} maskLength={8} />
119
+ <MaskedText label="주문 번호" value="ORD-20261025-7731" visibleStart={3} visibleEnd={2} mask="*" groupSize={3} maskLength={6} />
120
+ <MaskedText label="접속 키" value="dk_live_51Hc2k9Qe8Zx" visibleStart={7} mono />
121
+ <Text variant="caption" tone="tertiary">
122
+ maskValue: {maskValue('kim.minji@example.com', { visibleStart: 3, visibleEnd: 12 })}
123
+ </Text>
124
+ </Stack>
125
+ )
126
+ }
127
+ ```