@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,162 @@
1
+ # OTP Input
2
+
3
+ > 텍스트 입력 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 인증번호처럼 정해진 자리수의 코드를 입력하는 칸. 실제 입력창 하나 위에 칸을 그리는 구조라 타이핑, 붙여넣기, SMS 자동완성, IME가 모두 자연스럽게 동작해요. 확인 중에는 회색으로 빛이 지나가고, 틀리면 빨갛게 흔들리고, 맞으면 초록색이 돼요.
6
+
7
+ ```tsx
8
+ import { OtpInput } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### OtpInput
14
+
15
+ ref는 안쪽 <input>을 가리켜요. 초기화 후 ref.current.focus()로 다시 포커스할 수 있어요.
16
+
17
+ | prop | 타입 | 기본값 | 설명 |
18
+ | --- | --- | --- | --- |
19
+ | `length` | `number` | `6` | 자리수. |
20
+ | `groups` | `number[]` | | 칸 묶음. 예: [3, 3]. 합이 length와 다르면 무시돼요. |
21
+ | `value` | `string` | | 입력값(제어). |
22
+ | `defaultValue` | `string` | `''` | 처음 값(비제어). |
23
+ | `onValueChange` | `(value: string) => void` | | 값이 바뀔 때. 걸러진 값(숫자만, 대문자)이 와요. |
24
+ | `onComplete` | `(value: string) => void` | | 모든 칸이 채워지는 순간 한 번. 여기서 확인을 시작하세요. |
25
+ | `pattern` | `'numeric' \| 'alphanumeric'` | `'numeric'` | 받을 글자 종류. |
26
+ | `status` | `'loading' \| 'error' \| 'success'` | | loading: 회색 + shimmer · error: 빨강 + 흔들림 · success: 초록. loading과 success는 입력을 잠가요. |
27
+ | `mask` | `boolean` | | 글자 대신 점 표시. |
28
+ | `size` | `'md' \| 'lg'` | `'md'` | 칸 크기. |
29
+ | `disabled` | `boolean` | | 비활성. |
30
+ | `autoFocus` | `boolean` | | 처음에 바로 포커스. |
31
+ | `name` | `string` | | 폼 제출용 이름. |
32
+ | `id` | `string` | | 안쪽 input의 id. Field htmlFor와 연결돼요. |
33
+ | `aria-label` | `string` | `'인증번호'` | 스크린 리더 이름. 뒤에 'n자리'가 붙어요. |
34
+
35
+ ## 예제
36
+
37
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
38
+
39
+ ### 인증 흐름
40
+
41
+ onComplete에서 확인을 시작하고 status를 'loading' → 'success' / 'error'로 바꿔요. 확인 중과 완료 후에는 입력이 잠겨요. 틀렸을 땐 지우고 다시 입력하면 돼요. 이 예제의 정답은 123456.
42
+
43
+ ```tsx
44
+ import { useRef, useState } from 'react'
45
+ import { RotateCcw } from 'lucide-react'
46
+ import { Button, Text, OtpInput, type OtpInputProps } from '@iamfiro/design'
47
+
48
+ // 서버 확인을 흉내 내요. 이 예제의 정답은 123456이에요.
49
+ const checkCode = (code: string) => new Promise<boolean>((resolve) => setTimeout(() => resolve(code === '123456'), 1200))
50
+
51
+ const MESSAGES = {
52
+ loading: '확인하는 중이에요…',
53
+ success: '인증이 완료됐어요.',
54
+ error: '인증번호가 달라요. 다시 입력해 주세요.',
55
+ }
56
+
57
+ export default function Example() {
58
+ const inputRef = useRef<HTMLInputElement>(null)
59
+ const [code, setCode] = useState('')
60
+ const [status, setStatus] = useState<OtpInputProps['status']>()
61
+
62
+ const verify = async (value: string) => {
63
+ setStatus('loading')
64
+ const ok = await checkCode(value)
65
+ setStatus(ok ? 'success' : 'error')
66
+ }
67
+
68
+ const reset = () => {
69
+ setCode('')
70
+ setStatus(undefined)
71
+ inputRef.current?.focus()
72
+ }
73
+
74
+ return (
75
+ <div style={{ display: 'grid', gap: 12, justifyItems: 'center' }}>
76
+ <OtpInput
77
+ ref={inputRef}
78
+ groups={[3, 3]}
79
+ value={code}
80
+ onValueChange={(value) => {
81
+ setCode(value)
82
+ setStatus(undefined)
83
+ }}
84
+ onComplete={verify}
85
+ status={status}
86
+ aria-label="문자로 받은 인증번호"
87
+ />
88
+ <Text variant="body-sm" tone={status === 'success' ? 'success' : status === 'error' ? 'danger' : 'tertiary'}>
89
+ {status ? MESSAGES[status] : '문자로 받은 6자리 번호를 입력해 주세요.'}
90
+ </Text>
91
+ {(status === 'success' || status === 'error') && (
92
+ <Button variant="ghost" size="sm" icon={<RotateCcw />} onClick={reset}>
93
+ 처음부터 다시
94
+ </Button>
95
+ )}
96
+ </div>
97
+ )
98
+ }
99
+ ```
100
+
101
+ ### 그룹 · 붙여넣기
102
+
103
+ groups로 칸 사이에 구분선을 넣어요(합이 length와 같아야 해요). 붙여넣은 "482-913"의 하이픈·공백은 자동으로 빠져요.
104
+
105
+ ```tsx
106
+ import { CopyButton, Text, OtpInput } from '@iamfiro/design'
107
+
108
+ export default function Example() {
109
+ return (
110
+ <div style={{ display: 'grid', gap: 16, justifyItems: 'center' }}>
111
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
112
+ <Text variant="body-sm" tone="secondary">
113
+ 인증번호는{' '}
114
+ <Text as="strong" variant="label-md" numeric>
115
+ 482-913
116
+ </Text>
117
+ 이에요.
118
+ </Text>
119
+ <CopyButton value="482-913" label="복사" />
120
+ </div>
121
+ <OtpInput groups={[3, 3]} aria-label="인증번호" />
122
+ </div>
123
+ )
124
+ }
125
+ ```
126
+
127
+ ### 가리기
128
+
129
+ mask는 글자 대신 점을 보여줘요. 비밀번호·PIN에 쓰세요. size는 md / lg.
130
+
131
+ ```tsx
132
+ import { Field, OtpInput } from '@iamfiro/design'
133
+
134
+ export default function Example() {
135
+ return (
136
+ <Field label="결제 비밀번호" hint="숫자 4자리" htmlFor="payment-pin">
137
+ <OtpInput id="payment-pin" length={4} mask size="lg" aria-label="결제 비밀번호" />
138
+ </Field>
139
+ )
140
+ }
141
+ ```
142
+
143
+ ### 영문 + 숫자
144
+
145
+ pattern='alphanumeric'은 영문과 숫자를 받고 대문자로 바꿔요. 기본 'numeric'은 숫자 외 입력을 무시해요.
146
+
147
+ ```tsx
148
+ import { Field, OtpInput } from '@iamfiro/design'
149
+
150
+ export default function Example() {
151
+ return (
152
+ <div style={{ display: 'grid', gap: 20 }}>
153
+ <Field label="백업 코드" hint="영문과 숫자 8자리. 소문자로 입력해도 대문자로 바뀌어요." htmlFor="backup-code">
154
+ <OtpInput id="backup-code" length={8} groups={[4, 4]} pattern="alphanumeric" aria-label="백업 코드" />
155
+ </Field>
156
+ <Field label="비활성" htmlFor="backup-code-off">
157
+ <OtpInput id="backup-code-off" length={8} groups={[4, 4]} pattern="alphanumeric" defaultValue="K7PQ2M9X" disabled aria-label="백업 코드" />
158
+ </Field>
159
+ </div>
160
+ )
161
+ }
162
+ ```
@@ -0,0 +1,189 @@
1
+ # Pagination
2
+
3
+ > 내비게이션 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 긴 목록을 페이지로 나눠 이동해요. 현재 페이지는 채워진 칩이고, 페이지를 바꾸면 칩이 새 번호로 미끄러져요. 양 끝(boundaryCount)과 현재 페이지 양옆(siblingCount)만 보이고 나머지는 "…"로 접혀요. 칸 수가 일정해서 넘겨도 버튼이 들썩이지 않아요. 페이지 수 대신 전체 개수 + 페이지 크기를 주면 "1–20 / 248개" 범위 글과 "페이지당 N개" Select를 함께 보여 줘요.
6
+
7
+ ```tsx
8
+ import { Pagination, paginationRange } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### Pagination
14
+
15
+ <nav aria-label>로 감싸고, 현재 페이지에는 aria-current="page", 각 번호 버튼에는 "N페이지" 이름이 붙어요.
16
+
17
+ | prop | 타입 | 기본값 | 설명 |
18
+ | --- | --- | --- | --- |
19
+ | `page` | `number` | | 현재 페이지(1부터, 제어). |
20
+ | `defaultPage` | `number` | `1` | 처음 페이지(비제어). |
21
+ | `onPageChange` | `(page: number) => void` | | 페이지가 바뀔 때. |
22
+ | `totalPages` | `number` | | 전체 페이지 수. 없으면 totalItems ÷ pageSize로 계산해요. |
23
+ | `totalItems` | `number` | | 전체 항목 수. 주면 범위 글("1–20 / 248개")이 보여요. |
24
+ | `pageSize / defaultPageSize` | `number` | `20` | 페이지당 항목 수(제어 / 비제어). |
25
+ | `onPageSizeChange` | `(pageSize: number) => void` | | 페이지 크기가 바뀔 때. |
26
+ | `pageSizeOptions` | `number[]` | | 주면 "페이지당 N개" Select가 보여요. |
27
+ | `siblingCount` | `number` | `1` | 현재 페이지 양옆에 보일 페이지 수. |
28
+ | `boundaryCount` | `number` | `1` | 양 끝에 항상 보일 페이지 수. |
29
+ | `variant` | `'default' \| 'simple'` | `'default'` | default: 번호 버튼 · simple: "3 / 12" 글자만. |
30
+ | `tone` | `'neutral' \| 'primary'` | `'neutral'` | 현재 페이지 칩 색. neutral 회색 · primary 거의 검정. |
31
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 버튼 높이 28 · 34 · 40. |
32
+ | `showFirstLast` | `boolean` | | 첫 · 마지막 페이지 버튼. |
33
+ | `showRange` | `boolean` | `totalItems가 있으면 true` | 범위 글 표시. |
34
+ | `formatRange` | `({ start, end, total }) => ReactNode` | | 범위 글을 직접 만들 때. |
35
+ | `showJump` | `boolean` | | 번호를 입력해 이동하는 작은 칸. |
36
+ | `disabled` | `boolean` | | 모든 버튼 비활성(불러오는 중). |
37
+ | `aria-label` | `string` | `'페이지'` | nav 랜드마크 이름. 한 화면에 여럿이면 구분되게. |
38
+
39
+ ### paginationRange
40
+
41
+ 번호를 직접 그릴 때 쓰는 계산 함수. 결과는 (number | "start-ellipsis" | "end-ellipsis")[].
42
+
43
+ | prop | 타입 | 기본값 | 설명 |
44
+ | --- | --- | --- | --- |
45
+ | `page` (필수) | `number` | | 현재 페이지. |
46
+ | `count` (필수) | `number` | | 전체 페이지 수. |
47
+ | `siblingCount` | `number` | `1` | 현재 페이지 양옆 개수. |
48
+ | `boundaryCount` | `number` | `1` | 양 끝 개수. |
49
+
50
+ ## 예제
51
+
52
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
53
+
54
+ ### 기본
55
+
56
+ 비제어(defaultPage). 첫 · 마지막 페이지에서는 화살표가 비활성돼요. 숫자는 tabular라 자릿수가 바뀌어도 폭이 흔들리지 않아요.
57
+
58
+ ```tsx
59
+ import { Pagination } from '@iamfiro/design'
60
+
61
+ export default function Example() {
62
+ return <Pagination totalPages={20} defaultPage={5} />
63
+ }
64
+ ```
65
+
66
+ ### 표 아래 · 개수와 페이지 크기
67
+
68
+ totalItems + pageSize로 페이지 수를 계산하고, 왼쪽에 범위 글("1–10 / 248개")과 페이지당 개수 Select가 붙어요. 페이지 크기를 바꾸면 보고 있던 첫 항목이 들어 있는 페이지로 옮겨 가요.
69
+
70
+ ```tsx
71
+ import { useState } from 'react'
72
+ import { Pagination, Table, Stack } from '@iamfiro/design'
73
+
74
+ const NAMES = ['김민지', '이도현', '박서연', '최준호', '정하은', '강지우', '윤서준', '임수아', '한예린', '오태윤']
75
+ const TEAMS = ['디자인', '개발', '마케팅', '운영']
76
+ const TOTAL = 248
77
+
78
+ const members = Array.from({ length: TOTAL }, (_, i) => ({
79
+ id: i + 1,
80
+ name: NAMES[i % NAMES.length],
81
+ team: TEAMS[(i * 7) % TEAMS.length],
82
+ joined: `${(i % 12) + 1}월 ${(i % 28) + 1}일`,
83
+ }))
84
+
85
+ export default function Example() {
86
+ const [page, setPage] = useState(1)
87
+ const [pageSize, setPageSize] = useState(10)
88
+ const rows = members.slice((page - 1) * pageSize, page * pageSize)
89
+
90
+ return (
91
+ <Stack gap={4}>
92
+ <Table
93
+ density="compact"
94
+ rowKey={(r) => r.id}
95
+ columns={[
96
+ { key: 'id', header: '번호', numeric: true, width: 72 },
97
+ { key: 'name', header: '이름' },
98
+ { key: 'team', header: '팀' },
99
+ { key: 'joined', header: '가입일', align: 'end' },
100
+ ]}
101
+ rows={rows}
102
+ />
103
+ <Pagination
104
+ page={page}
105
+ onPageChange={setPage}
106
+ totalItems={TOTAL}
107
+ pageSize={pageSize}
108
+ onPageSizeChange={setPageSize}
109
+ pageSizeOptions={[10, 20, 50]}
110
+ size="sm"
111
+ aria-label="멤버 목록 페이지"
112
+ />
113
+ </Stack>
114
+ )
115
+ }
116
+ ```
117
+
118
+ ### simple · 처음/마지막 · 범위
119
+
120
+ variant='simple'은 번호 대신 "3 / 12"만 보여 줘요. showFirstLast는 « » 버튼, tone='primary'는 진한 칩이에요.
121
+
122
+ ```tsx
123
+ import { Pagination, Stack, Text } from '@iamfiro/design'
124
+
125
+ export default function Example() {
126
+ return (
127
+ <Stack gap={6}>
128
+ <Stack gap={2}>
129
+ <Text variant="label-sm" tone="secondary">
130
+ simple — 좁은 곳 · 모바일
131
+ </Text>
132
+ <Pagination variant="simple" totalPages={12} defaultPage={3} aria-label="사진 페이지" />
133
+ </Stack>
134
+ <Stack gap={2}>
135
+ <Text variant="label-sm" tone="secondary">
136
+ 처음 · 마지막 버튼 + primary 칩
137
+ </Text>
138
+ <Pagination totalPages={48} defaultPage={24} showFirstLast tone="primary" aria-label="검색 결과 페이지" />
139
+ </Stack>
140
+ <Stack gap={2}>
141
+ <Text variant="label-sm" tone="secondary">
142
+ siblingCount 2 · boundaryCount 2
143
+ </Text>
144
+ <Pagination totalPages={80} defaultPage={40} siblingCount={2} boundaryCount={2} aria-label="게시글 페이지" />
145
+ </Stack>
146
+ </Stack>
147
+ )
148
+ }
149
+ ```
150
+
151
+ ### 페이지로 이동
152
+
153
+ showJump: 페이지가 많을 때 번호를 입력해 바로 이동해요. Enter = 이동, Esc나 포커스를 떠나면 비워요. 한글 조합 중 Enter는 무시해요.
154
+
155
+ ```tsx
156
+ import { useState } from 'react'
157
+ import { Pagination, Stack, Text } from '@iamfiro/design'
158
+
159
+ export default function Example() {
160
+ const [page, setPage] = useState(37)
161
+ return (
162
+ <Stack gap={3}>
163
+ <Pagination page={page} onPageChange={setPage} totalPages={120} showFirstLast showJump aria-label="주문 내역 페이지" />
164
+ <Text variant="body-sm" tone="secondary" numeric>
165
+ 지금 {page}페이지를 보고 있어요. 번호를 입력하고 Enter를 누르면 이동하고, 범위를 넘으면 끝 페이지로 가요.
166
+ </Text>
167
+ </Stack>
168
+ )
169
+ }
170
+ ```
171
+
172
+ ### 크기와 비활성
173
+
174
+ size sm / md / lg(버튼 높이 28 · 34 · 40). disabled면 모든 버튼이 잠겨요.
175
+
176
+ ```tsx
177
+ import { Pagination, Stack } from '@iamfiro/design'
178
+
179
+ export default function Example() {
180
+ return (
181
+ <Stack gap={4}>
182
+ <Pagination size="sm" totalPages={10} defaultPage={2} aria-label="작은 페이지 이동" />
183
+ <Pagination size="md" totalPages={10} defaultPage={2} aria-label="기본 페이지 이동" />
184
+ <Pagination size="lg" totalPages={10} defaultPage={2} aria-label="큰 페이지 이동" />
185
+ <Pagination totalPages={10} defaultPage={2} disabled aria-label="불러오는 중인 페이지" />
186
+ </Stack>
187
+ )
188
+ }
189
+ ```
@@ -0,0 +1,131 @@
1
+ # PhoneInput
2
+
3
+ > 텍스트 입력 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 국가를 고르고 전화번호를 입력하는 칸. 왼쪽 국기 + 국가 번호를 누르면 국가 목록이 칸 전체 폭으로 열리고, 번호 칸엔 숫자만 들어가며 그 나라 방식대로 하이픈 · 괄호가 붙어요. 붙여넣은 "+82 10-…"는 국가까지 맞춰 줘요.
6
+
7
+ ```tsx
8
+ import { PhoneInput } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 숫자만 받는 칸(금액 · 전화번호 · 카드 번호)은 `useDigitInput`을 Input에 펼친다: 글자는 아예 안 들어가고, 쉼표 · 하이픈은 `format`으로 붙이고, 커서는 같은 숫자 뒤에 머물고, 구분자 위 Backspace는 앞 숫자를 지운다. `type="number"`는 쓰지 않는다. 전화번호는 `PhoneInput`(칸 안 `Select bare` 국가 선택 + 국기 `Flag`, 목록은 `anchorRef`로 칸 전체 폭). 국기는 이모지 대신 `Flag`(Windows에서 이모지 국기는 글자로 나온다).
14
+
15
+ ## API
16
+
17
+ ### PhoneInput
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `value / defaultValue` | `string` | | 국내 번호 숫자만("01012345678"). |
22
+ | `onValueChange` | `(digits, { country, e164 }) => void` | | 번호가 바뀔 때. e164는 "+821012345678"처럼 국제 번호(맨 앞 0 제외). |
23
+ | `country / defaultCountry` | `FlagCode` | `'KR'` | 고른 국가. |
24
+ | `onCountryChange` | `(code: FlagCode) => void` | | 국가가 바뀔 때. |
25
+ | `countries` | `PhoneCountry[]` | `PHONE_COUNTRIES` | 보여 줄 국가와 순서. 각 국가는 code · name · dial · pattern("###-####-####") · example · trunk. |
26
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 높이 36 / 44 / 52px. |
27
+ | `invalid` | `boolean` | | 오류 표시. |
28
+ | `disabled` | `boolean` | | 국가 선택과 번호 칸 모두 비활성. |
29
+ | `placeholder` | `string` | | 생략하면 그 나라의 예시 번호. |
30
+ | `name` | `string` | | 폼으로 보낼 때 이 이름으로 국제 번호를 보내요. |
31
+
32
+ ### useDigitInput
33
+
34
+ 숫자만 받는 입력 훅(@/ds/hooks). PhoneInput과 금액 칸이 같이 써요. 결과를 Input에 펼쳐 넣어요.
35
+
36
+ | prop | 타입 | 기본값 | 설명 |
37
+ | --- | --- | --- | --- |
38
+ | `digits` (필수) | `string` | | 숫자만 담긴 값. |
39
+ | `onDigitsChange` (필수) | `(digits: string) => void` | | 숫자가 바뀔 때. |
40
+ | `format` | `(digits: string) => string` | | 보여 줄 모양(쉼표, 하이픈). 커서는 같은 숫자 뒤에 머물러요. |
41
+ | `maxDigits` | `number` | | 최대 자릿수. |
42
+
43
+ ## 예제
44
+
45
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
46
+
47
+ ### 기본
48
+
49
+ 숫자만 들어가요. 글자를 쳐도 무시되고, 하이픈 위에서 Backspace를 누르면 앞 숫자가 지워져요. 국가를 바꾸면 자릿수와 모양이 따라 바뀌어요.
50
+
51
+ ```tsx
52
+ import { Field, PhoneInput } from '@iamfiro/design'
53
+
54
+ export default function Example() {
55
+ return (
56
+ <div style={{ width: 320 }}>
57
+ <Field label="휴대폰 번호" htmlFor="phone" hint="인증번호를 문자로 보내 드려요.">
58
+ <PhoneInput id="phone" />
59
+ </Field>
60
+ </div>
61
+ )
62
+ }
63
+ ```
64
+
65
+ ### 제어 · 국제 번호
66
+
67
+ value / country로 제어하고 onValueChange의 e164로 저장할 국제 번호(+821012345678)를 받아요. "+1 201 555 0123"을 붙여넣어 보세요.
68
+
69
+ ```tsx
70
+ import { useState } from 'react'
71
+ import { Field, Stack, DescriptionList, PhoneInput, type PhoneInputProps } from '@iamfiro/design'
72
+
73
+ export default function Example() {
74
+ const [country, setCountry] = useState<PhoneInputProps['country']>('KR')
75
+ const [digits, setDigits] = useState('01012345678')
76
+ const [e164, setE164] = useState('+821012345678')
77
+
78
+ return (
79
+ <Stack gap={4} style={{ width: 320 }}>
80
+ <Field label="연락처" htmlFor="contact">
81
+ <PhoneInput
82
+ id="contact"
83
+ country={country}
84
+ onCountryChange={setCountry}
85
+ value={digits}
86
+ onValueChange={(next, { e164 }) => {
87
+ setDigits(next)
88
+ setE164(e164)
89
+ }}
90
+ />
91
+ </Field>
92
+ <DescriptionList
93
+ variant="soft"
94
+ layout="inline"
95
+ items={[
96
+ { label: '국가', value: country },
97
+ { label: '입력한 숫자', value: digits || '—' },
98
+ { label: '국제 번호', value: e164 || '—' },
99
+ ]}
100
+ />
101
+ </Stack>
102
+ )
103
+ }
104
+ ```
105
+
106
+ ### 크기와 상태
107
+
108
+ Input과 같은 size · invalid · disabled.
109
+
110
+ ```tsx
111
+ import { Field, PhoneInput } from '@iamfiro/design'
112
+
113
+ export default function Example() {
114
+ return (
115
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 280px)', gap: 16, alignItems: 'end' }}>
116
+ <Field label="작게 (sm)" htmlFor="phone-sm">
117
+ <PhoneInput id="phone-sm" size="sm" defaultCountry="US" />
118
+ </Field>
119
+ <Field label="크게 (lg)" htmlFor="phone-lg">
120
+ <PhoneInput id="phone-lg" size="lg" defaultCountry="JP" />
121
+ </Field>
122
+ <Field label="오류" htmlFor="phone-invalid" hint="번호를 끝까지 입력해 주세요." invalid>
123
+ <PhoneInput id="phone-invalid" defaultValue="0101234" invalid />
124
+ </Field>
125
+ <Field label="비활성" htmlFor="phone-disabled">
126
+ <PhoneInput id="phone-disabled" defaultValue="01098765432" disabled />
127
+ </Field>
128
+ </div>
129
+ )
130
+ }
131
+ ```