@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,391 @@
1
+ # Dialog
2
+
3
+ > 메뉴 & 오버레이 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 페이지 위에 뜨는 모달. 열려 있는 동안 포커스는 안에 갇히고 뒤 페이지는 스크롤되지 않아요. Esc나 바깥 클릭으로 닫히고, 닫히면 포커스가 열었던 버튼으로 돌아가요. 부드럽게 커지며 열리고 반대로 줄어들며 닫히며, 휴대폰에서는 아래에서 올라오는 바텀시트가 돼요. 되돌릴 수 없는 작업의 확인은 AlertDialog를 쓰세요.
6
+
7
+ ```tsx
8
+ import { Dialog } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## 디자인 규칙
12
+
13
+ - 모달(`Dialog`): 포커스 가두기(`useFocusTrap`), 배경 스크롤 잠금(`useScrollLock`), Esc·바깥 클릭으로 닫기, 닫으면 트리거로 포커스 복귀. 저장 중엔 `dismissible={false}`. 휴대폰에선 바텀시트.
14
+
15
+ ## API
16
+
17
+ ### Dialog
18
+
19
+ | prop | 타입 | 기본값 | 설명 |
20
+ | --- | --- | --- | --- |
21
+ | `title` (필수) | `ReactNode` | | 제목. 대화상자의 접근성 이름(aria-labelledby)이 돼요. |
22
+ | `description` | `ReactNode` | | 제목 아래 설명(aria-describedby). |
23
+ | `children` | `ReactNode` | | 본문. 길면 이 부분만 스크롤돼요. |
24
+ | `footer` | `ReactNode` | | 아래 버튼 줄(오른쪽 정렬). 동작을 이름으로 쓰세요 — "저장", "보내기". |
25
+ | `trigger` | `ReactElement` | | 누르면 여는 버튼. aria-haspopup · aria-expanded가 붙고, 원래 onClick도 실행돼요. preventDefault하면 열리지 않아요. |
26
+ | `open / onOpenChange` | `boolean / (open) => void` | | 열림 상태를 밖에서 제어할 때. |
27
+ | `defaultOpen` | `boolean` | `false` | 처음 열림 상태(비제어). |
28
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 패널 폭 400 / 520 / 720px. 휴대폰에선 바텀시트. |
29
+ | `dismissible` | `boolean` | `true` | Esc·바깥 클릭·×로 닫기. 저장 중엔 false로. |
30
+ | `showClose` | `boolean` | `true` | 오른쪽 위 × 버튼. dismissible이 false면 숨겨져요. |
31
+ | `initialFocus` | `RefObject<HTMLElement>` | | 열릴 때 포커스할 요소. 생략하면 첫 번째 포커스 가능한 요소(× 버튼)예요. |
32
+ | `bare` | `boolean` | `false` | children을 제목줄·본문·버튼 줄 없이 패널에 바로 넣어요. 여백·×도 없고 footer는 무시돼요. 패널은 모서리를 자르는 세로 flex라 자식이 min-height: 0으로 스크롤을 맡을 수 있어요. title은 여전히 필수이고 화면에서만 숨겨져요. |
33
+ | `hideTitle` | `boolean` | `false` | 기본 레이아웃은 그대로 두고 제목·설명만 화면에서 숨겨요(스크린 리더는 읽어요). |
34
+ | `placement` | `'center' \| 'top'` | `'center'` | center는 가운데(휴대폰에선 바텀시트). top은 위에서 12vh 아래에 고정돼 내용 높이가 바뀌어도 움직이지 않고, 휴대폰에서도 좌우 12px 여백의 떠 있는 패널로 남아요. |
35
+ | `className` | `string` | | 패널에 붙는 클래스. |
36
+
37
+ ## 예제
38
+
39
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
40
+
41
+ ### 기본
42
+
43
+ trigger에 버튼을 넘기면 누를 때 열려요(버튼의 onClick도 그대로 실행돼요). 오른쪽 위 ×, Esc, 바깥 클릭으로 닫혀요.
44
+
45
+ ```tsx
46
+ import { Keyboard } from 'lucide-react'
47
+ import { Button, Inline, Kbd, Stack, Text, Dialog } from '@iamfiro/design'
48
+
49
+ const SHORTCUTS = [
50
+ { label: '빠른 검색', keys: ['mod', 'K'] },
51
+ { label: '새 문서 만들기', keys: ['mod', 'N'] },
52
+ { label: '복제', keys: ['mod', 'D'] },
53
+ { label: '실행 취소', keys: ['mod', 'Z'] },
54
+ ]
55
+
56
+ export default function Example() {
57
+ return (
58
+ <Dialog
59
+ trigger={<Button icon={<Keyboard />}>단축키 보기</Button>}
60
+ title="키보드 단축키"
61
+ description="자주 쓰는 동작을 키보드로 바로 실행할 수 있어요."
62
+ >
63
+ <Stack gap={3}>
64
+ {SHORTCUTS.map((s) => (
65
+ <Inline key={s.label} justify="between" wrap={false}>
66
+ <Text variant="body-md">{s.label}</Text>
67
+ <Kbd keys={s.keys} />
68
+ </Inline>
69
+ ))}
70
+ </Stack>
71
+ </Dialog>
72
+ )
73
+ }
74
+ ```
75
+
76
+ ### 폼
77
+
78
+ 저장하는 동안 dismissible을 끄면 Esc·바깥 클릭·×가 막혀 작업이 중간에 끊기지 않아요. initialFocus로 첫 입력칸에 바로 포커스를 줘요. 모달 안의 Select 팝오버는 모달보다 위에 뜨고, Select의 Esc는 팝오버만 닫아요.
79
+
80
+ ```tsx
81
+ import { useRef, useState } from 'react'
82
+ import { UserPlus } from 'lucide-react'
83
+ import { Button, Field, Input, Stack, Dialog, Select, toast, type ListOption } from '@iamfiro/design'
84
+
85
+ const ROLES: ListOption[] = [
86
+ { value: 'viewer', label: '보기 전용', description: '문서를 읽고 댓글만 달 수 있어요' },
87
+ { value: 'editor', label: '편집자', description: '문서를 만들고 고칠 수 있어요' },
88
+ { value: 'admin', label: '관리자', description: '멤버와 결제까지 관리해요' },
89
+ ]
90
+
91
+ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
92
+
93
+ export default function Example() {
94
+ const [open, setOpen] = useState(false)
95
+ const [saving, setSaving] = useState(false)
96
+ const [email, setEmail] = useState('')
97
+ const [role, setRole] = useState<string | null>('editor')
98
+ const emailRef = useRef<HTMLInputElement>(null)
99
+
100
+ const openDialog = () => {
101
+ setEmail('')
102
+ setRole('editor')
103
+ setOpen(true)
104
+ }
105
+
106
+ const save = async () => {
107
+ setSaving(true)
108
+ await wait(1200)
109
+ setSaving(false)
110
+ setOpen(false)
111
+ toast.success('초대를 보냈어요', { description: `${email}에게 메일이 도착할 거예요.` })
112
+ }
113
+
114
+ return (
115
+ <>
116
+ <Button variant="primary" icon={<UserPlus />} onClick={openDialog}>
117
+ 멤버 초대
118
+ </Button>
119
+ <Dialog
120
+ open={open}
121
+ onOpenChange={setOpen}
122
+ title="멤버 초대"
123
+ description="초대받은 사람은 메일의 링크로 워크스페이스에 들어와요."
124
+ initialFocus={emailRef}
125
+ dismissible={!saving}
126
+ footer={
127
+ <>
128
+ <Button disabled={saving} onClick={() => setOpen(false)}>
129
+ 취소
130
+ </Button>
131
+ <Button variant="primary" loading={saving} disabled={!email.includes('@')} onClick={save}>
132
+ 저장
133
+ </Button>
134
+ </>
135
+ }
136
+ >
137
+ <Stack gap={5}>
138
+ <Field label="이메일" htmlFor="invite-email">
139
+ <Input
140
+ ref={emailRef}
141
+ id="invite-email"
142
+ type="email"
143
+ placeholder="name@company.com"
144
+ value={email}
145
+ onChange={(e) => setEmail(e.target.value)}
146
+ disabled={saving}
147
+ />
148
+ </Field>
149
+ <Field label="권한" htmlFor="invite-role">
150
+ <Select id="invite-role" options={ROLES} value={role} onValueChange={setRole} disabled={saving} />
151
+ </Field>
152
+ </Stack>
153
+ </Dialog>
154
+ </>
155
+ )
156
+ }
157
+ ```
158
+
159
+ ### 제어
160
+
161
+ open과 onOpenChange로 바깥 상태에서 열고 닫아요. trigger 없이 어디서든 열 수 있고, Esc·바깥 클릭으로 닫힐 때도 onOpenChange(false)가 불려요.
162
+
163
+ ```tsx
164
+ import { useState } from 'react'
165
+ import { Sparkles } from 'lucide-react'
166
+ import { Button, Stack, Text, Dialog, toast } from '@iamfiro/design'
167
+
168
+ export default function Example() {
169
+ const [open, setOpen] = useState(false)
170
+
171
+ const update = () => {
172
+ setOpen(false)
173
+ toast.success('최신 버전으로 업데이트했어요')
174
+ }
175
+
176
+ return (
177
+ <Stack gap={3} align="start">
178
+ <Button icon={<Sparkles />} onClick={() => setOpen(true)}>
179
+ 새 버전 안내 열기
180
+ </Button>
181
+ <Text variant="body-sm" tone="tertiary">
182
+ 지금 상태: {open ? '열림' : '닫힘'}
183
+ </Text>
184
+ <Dialog
185
+ open={open}
186
+ onOpenChange={setOpen}
187
+ title="새 버전이 준비됐어요"
188
+ description="2.4 버전에서는 댓글 알림과 검색 속도가 좋아졌어요. 지금 업데이트하면 열린 문서는 그대로 유지돼요."
189
+ footer={
190
+ <>
191
+ <Button onClick={() => setOpen(false)}>나중에</Button>
192
+ <Button variant="primary" onClick={update}>
193
+ 지금 업데이트
194
+ </Button>
195
+ </>
196
+ }
197
+ />
198
+ </Stack>
199
+ )
200
+ }
201
+ ```
202
+
203
+ ### 크기
204
+
205
+ sm · md · lg = 400 · 520 · 720px. 휴대폰에서는 모두 바텀시트로 바뀌어요.
206
+
207
+ ```tsx
208
+ import { Button, Inline, Text, Dialog } from '@iamfiro/design'
209
+
210
+ const SIZES = [
211
+ { size: 'sm', label: '작게 (400px)', text: '짧은 안내나 한두 개의 입력에 맞아요.' },
212
+ { size: 'md', label: '보통 (520px)', text: '대부분의 폼과 설정에 쓰는 기본 크기예요.' },
213
+ { size: 'lg', label: '크게 (720px)', text: '표, 미리보기, 두 단 레이아웃처럼 내용이 넓을 때 써요.' },
214
+ ] as const
215
+
216
+ export default function Example() {
217
+ return (
218
+ <Inline gap={2}>
219
+ {SIZES.map((s) => (
220
+ <Dialog key={s.size} size={s.size} trigger={<Button>{s.label}</Button>} title={s.label}>
221
+ <Text variant="body-md" tone="secondary">
222
+ {s.text} 휴대폰에서는 크기와 관계없이 화면 아래에서 올라오는 시트로 바뀌어요.
223
+ </Text>
224
+ </Dialog>
225
+ ))}
226
+ </Inline>
227
+ )
228
+ }
229
+ ```
230
+
231
+ ### 긴 내용
232
+
233
+ 내용이 화면보다 길면 본문만 스크롤되고 제목과 버튼 줄은 제자리에 남아요. 바깥에서 시작해 바깥에서 끝난 클릭만 닫기로 봐서, 글자를 드래그하다 밖에서 놓아도 닫히지 않아요.
234
+
235
+ ```tsx
236
+ import { useState } from 'react'
237
+ import { FileText } from 'lucide-react'
238
+ import { Button, Prose, Dialog, toast } from '@iamfiro/design'
239
+
240
+ const SECTIONS = [
241
+ { title: '제1조 목적', body: '이 약관은 회사가 제공하는 Lumen 서비스의 이용 조건과 절차, 회사와 이용자의 권리·의무 및 책임 사항을 정하는 것을 목적으로 해요.' },
242
+ { title: '제2조 정의', body: '"이용자"란 이 약관에 따라 서비스를 이용하는 회원을 말해요. "워크스페이스"란 이용자가 문서와 멤버를 관리하는 공간을 말해요.' },
243
+ { title: '제3조 약관의 효력과 변경', body: '회사는 관련 법령을 어기지 않는 범위에서 약관을 바꿀 수 있어요. 바뀐 약관은 시행일 7일 전부터 서비스 안에 공지해요.' },
244
+ { title: '제4조 계정 관리', body: '이용자는 자신의 계정과 비밀번호를 안전하게 관리해야 해요. 계정이 도용된 것을 알게 되면 바로 회사에 알려 주세요.' },
245
+ { title: '제5조 서비스 제공', body: '회사는 연중무휴, 하루 24시간 서비스를 제공하는 것을 원칙으로 해요. 다만 정기 점검이나 장애 대응을 위해 잠시 멈출 수 있어요.' },
246
+ { title: '제6조 유료 플랜', body: '유료 플랜은 매월 같은 날짜에 자동으로 결제돼요. 해지하면 남은 기간이 끝날 때까지 계속 이용할 수 있어요.' },
247
+ { title: '제7조 이용자의 콘텐츠', body: '이용자가 올린 문서와 파일의 권리는 이용자에게 있어요. 회사는 서비스를 제공하는 데 필요한 범위에서만 콘텐츠를 처리해요.' },
248
+ { title: '제8조 이용 제한', body: '다른 이용자의 서비스 이용을 방해하거나 법령을 어기는 경우, 회사는 사전 안내 후 이용을 제한할 수 있어요.' },
249
+ { title: '제9조 책임의 한계', body: '천재지변이나 이용자의 귀책 사유로 생긴 손해에 대해서는 회사가 책임지지 않아요.' },
250
+ ]
251
+
252
+ export default function Example() {
253
+ const [open, setOpen] = useState(false)
254
+
255
+ const agree = () => {
256
+ setOpen(false)
257
+ toast.success('약관에 동의했어요')
258
+ }
259
+
260
+ return (
261
+ <>
262
+ <Button icon={<FileText />} onClick={() => setOpen(true)}>
263
+ 이용약관 보기
264
+ </Button>
265
+ <Dialog
266
+ open={open}
267
+ onOpenChange={setOpen}
268
+ title="서비스 이용약관"
269
+ description="2026년 11월 1일부터 적용돼요."
270
+ footer={
271
+ <>
272
+ <Button onClick={() => setOpen(false)}>닫기</Button>
273
+ <Button variant="primary" onClick={agree}>
274
+ 동의하고 계속
275
+ </Button>
276
+ </>
277
+ }
278
+ >
279
+ <Prose size="sm">
280
+ {SECTIONS.map((s) => (
281
+ <section key={s.title}>
282
+ <h3>{s.title}</h3>
283
+ <p>{s.body}</p>
284
+ </section>
285
+ ))}
286
+ </Prose>
287
+ </Dialog>
288
+ </>
289
+ )
290
+ }
291
+ ```
292
+
293
+ ### 빈 패널 · 위쪽 배치
294
+
295
+ bare면 제목줄·여백·× 없이 children이 패널에 바로 들어가요(⌘K 명령 팔레트처럼). 제목은 화면에서만 숨겨져 대화상자 이름으로 계속 읽혀요. placement="top"은 화면 위에서 12vh 아래에 고정돼 결과가 걸러지며 높이가 바뀌어도 검색칸이 움직이지 않고, 휴대폰에서도 키보드를 피해 위에 떠 있어요. initialFocus로 검색칸에 바로 포커스가 가요.
296
+
297
+ ```tsx
298
+ import { useRef, useState, type KeyboardEvent } from 'react'
299
+ import { FilePlus, FileText, Search, Settings, UserPlus } from 'lucide-react'
300
+ import { Box, Button, Kbd, SearchInput, Dialog, EmptyState, List, ListItem, toast } from '@iamfiro/design'
301
+
302
+ const COMMANDS = [
303
+ { id: 'new', label: '새 문서 만들기', icon: <FilePlus />, keys: ['mod', 'N'] },
304
+ { id: 'invite', label: '멤버 초대', icon: <UserPlus /> },
305
+ { id: 'roadmap', label: '2분기 로드맵', description: '김민지 · 10월 25일 수정', icon: <FileText /> },
306
+ { id: 'notes', label: '주간 회의록', description: '이도현 · 2분 전 수정', icon: <FileText /> },
307
+ { id: 'settings', label: '설정 열기', icon: <Settings />, keys: ['mod', ','] },
308
+ ]
309
+
310
+ export default function Example() {
311
+ const [open, setOpen] = useState(false)
312
+ const [query, setQuery] = useState('')
313
+ const [active, setActive] = useState(0)
314
+ const inputRef = useRef<HTMLInputElement>(null)
315
+
316
+ const results = COMMANDS.filter((c) => c.label.includes(query.trim()))
317
+
318
+ const openPalette = () => {
319
+ setQuery('')
320
+ setActive(0)
321
+ setOpen(true)
322
+ }
323
+
324
+ const run = (label: string) => {
325
+ setOpen(false)
326
+ toast.success(`'${label}' 실행`)
327
+ }
328
+
329
+ const onKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
330
+ if (e.nativeEvent.isComposing || !results.length) return
331
+ if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
332
+ e.preventDefault()
333
+ const step = e.key === 'ArrowDown' ? 1 : -1
334
+ setActive((i) => (i + step + results.length) % results.length)
335
+ } else if (e.key === 'Enter') {
336
+ e.preventDefault()
337
+ run(results[Math.min(active, results.length - 1)].label)
338
+ }
339
+ }
340
+
341
+ return (
342
+ <>
343
+ <Button icon={<Search />} onClick={openPalette}>
344
+ 명령 검색
345
+ </Button>
346
+ <Dialog
347
+ bare
348
+ placement="top"
349
+ size="lg"
350
+ open={open}
351
+ onOpenChange={setOpen}
352
+ title="명령 검색"
353
+ description="명령이나 문서 이름을 입력하고 화살표 키로 고른 뒤 Enter로 실행해요."
354
+ initialFocus={inputRef}
355
+ >
356
+ <Box padding={3}>
357
+ <SearchInput
358
+ ref={inputRef}
359
+ placeholder="명령이나 문서 검색"
360
+ value={query}
361
+ onChange={(e) => {
362
+ setQuery(e.target.value)
363
+ setActive(0)
364
+ }}
365
+ onKeyDown={onKeyDown}
366
+ />
367
+ </Box>
368
+ <Box paddingX={2} paddingY={2}>
369
+ {results.length ? (
370
+ <List aria-label="검색 결과">
371
+ {results.map((c, i) => (
372
+ <ListItem
373
+ key={c.id}
374
+ leading={c.icon}
375
+ title={c.label}
376
+ description={c.description}
377
+ trailing={c.keys && <Kbd keys={c.keys} size="sm" />}
378
+ selected={i === active}
379
+ onClick={() => run(c.label)}
380
+ />
381
+ ))}
382
+ </List>
383
+ ) : (
384
+ <EmptyState size="sm" icon={<Search />} title="결과가 없어요" description={`'${query}'와 맞는 명령이나 문서가 없어요.`} />
385
+ )}
386
+ </Box>
387
+ </Dialog>
388
+ </>
389
+ )
390
+ }
391
+ ```