@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,471 @@
1
+ # Drawer · Sheet
2
+
3
+ > 메뉴 & 오버레이 · `@iamfiro/design` · 직접 만들지 말고 이 컴포넌트를 쓴다.
4
+
5
+ 화면 가장자리에서 미끄러져 들어오는 패널. 상세 보기·필터·설정처럼 지금 화면을 떠나지 않고 조금 긴 내용을 다룰 때 써요. Dialog와 같은 장치(포커스 가두기, 배경 스크롤 잠금, Esc·바깥 클릭 닫기, 닫으면 트리거로 포커스 복귀)를 쓰고, 나갈 때는 들어온 방향으로 다시 미끄러져 나가요. 아래에서 올라오는 Sheet는 손잡이·제목줄을 끌어 닫거나 높이를 바꿀 수 있어요.
6
+
7
+ ```tsx
8
+ import { Drawer, Sheet } from '@iamfiro/design'
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### Drawer
14
+
15
+ | prop | 타입 | 기본값 | 설명 |
16
+ | --- | --- | --- | --- |
17
+ | `title` (필수) | `ReactNode` | | 제목. 패널의 접근성 이름(aria-labelledby)이 돼요. |
18
+ | `description` | `ReactNode` | | 제목 아래 설명(aria-describedby). |
19
+ | `children` | `ReactNode` | | 본문. 길면 이 부분만 스크롤돼요. |
20
+ | `footer` | `ReactNode` | | 아래에 고정되는 버튼 줄(오른쪽 정렬). |
21
+ | `trigger` | `ReactElement` | | 누르면 여는 버튼. aria-haspopup · aria-expanded가 붙고 원래 onClick도 실행돼요. |
22
+ | `open / onOpenChange` | `boolean / (open) => void` | | 열림 상태를 밖에서 제어할 때. |
23
+ | `defaultOpen` | `boolean` | `false` | 처음 열림 상태(비제어). |
24
+ | `side` | `'right' \| 'left' \| 'top' \| 'bottom'` | `'right'` | 들어오는 가장자리. |
25
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'full'` | `'md'` | 좌우: 폭 360 / 440 / 640px / 전체(휴대폰에선 화면 폭 - 40px까지). 위아래: 최대 높이 50 / 75 / 92% / 전체. |
26
+ | `dismissible` | `boolean` | `true` | Esc · 바깥 클릭 · × · 끌어 닫기. 저장 중엔 false로. |
27
+ | `showClose` | `boolean` | `true` | 오른쪽 위 ×. dismissible이 false면 숨겨져요. |
28
+ | `initialFocus` | `RefObject<HTMLElement>` | | 열릴 때 포커스할 요소. 생략하면 첫 번째 포커스 가능한 요소. |
29
+ | `hideTitle` | `boolean` | `false` | 제목·설명을 화면에서만 숨겨요(스크린 리더는 읽어요). |
30
+ | `modal` | `boolean` | `true` | false면 배경 어둡게·스크롤 잠금·포커스 가두기 없이 뒤 페이지를 그대로 쓸 수 있어요. 바깥 클릭으로는 닫히지 않고 Esc·×로 닫혀요. |
31
+ | `inset` | `boolean` | `false` | 가장자리에서 8px 띄우고 모든 모서리를 둥글게. |
32
+ | `handle` | `boolean` | `side === 'bottom'` | 끌기 손잡이(위·아래 서랍만). 아래 서랍은 제목줄도 함께 끌려요. |
33
+ | `snapPoints` | `number[]` | | 멈춤 지점 — 전체 높이에 대한 비율(오름차순, 예: [0.5, 1]). 놓으면 가장 가까운 지점에, 튕기면 그 방향으로 넘어가요. |
34
+ | `snap / onSnapChange` | `number / (snap) => void` | | 현재 멈춤 지점(제어). |
35
+ | `defaultSnap` | `number` | `snapPoints의 가장 큰 값` | 열릴 때마다 처음 멈춤 지점(비제어). |
36
+ | `className` | `string` | | 패널에 붙는 클래스. |
37
+
38
+ ### Sheet
39
+
40
+ Drawer side='bottom'과 같아요(손잡이 기본 켜짐). side를 뺀 모든 prop을 받아요.
41
+
42
+ | prop | 타입 | 기본값 | 설명 |
43
+ | --- | --- | --- | --- |
44
+ | `…DrawerProps` | `Omit<DrawerProps, 'side'>` | | Drawer와 같아요. |
45
+
46
+ ## 예제
47
+
48
+ 예제를 감싼 `style={{ ... }}` div는 미리보기 배치용이다. 실제 화면 배치는 `Stack` · `Inline` · `Grid`로 한다.
49
+
50
+ ### 기본
51
+
52
+ 오른쪽에서 열리는 상세 패널. 제목·설명은 위에, 버튼 줄은 아래에 고정되고 본문만 스크롤돼요.
53
+
54
+ ```tsx
55
+ import { Receipt } from 'lucide-react'
56
+ import { Avatar, Badge, Button, Inline, Stack, Text, DescriptionList, Drawer, List, ListItem } from '@iamfiro/design'
57
+
58
+ const ITEMS = [
59
+ { id: 'i1', name: '무선 키보드 K3', option: '화이트 · 1개', price: '129,000원' },
60
+ { id: 'i2', name: '손목 받침대', option: '그레이 · 2개', price: '76,000원' },
61
+ ]
62
+
63
+ export default function Example() {
64
+ return (
65
+ <Drawer
66
+ trigger={<Button icon={<Receipt />}>주문 상세 보기</Button>}
67
+ title="주문 #20481"
68
+ description="10월 25일 오후 2:14에 결제됐어요."
69
+ footer={
70
+ <>
71
+ <Button>영수증 받기</Button>
72
+ <Button variant="primary">배송 조회</Button>
73
+ </>
74
+ }
75
+ >
76
+ <Stack gap={5}>
77
+ <Inline gap={2}>
78
+ <Badge tone="info" dot>
79
+ 배송 중
80
+ </Badge>
81
+ <Badge>무료 배송</Badge>
82
+ </Inline>
83
+ <DescriptionList
84
+ size="sm"
85
+ columns={1}
86
+ layout="inline"
87
+ aria-label="주문 정보"
88
+ items={[
89
+ {
90
+ label: '주문자',
91
+ value: (
92
+ <Inline gap={2} wrap={false}>
93
+ <Avatar name="김민지" size={20} />
94
+ 김민지
95
+ </Inline>
96
+ ),
97
+ },
98
+ { label: '받는 곳', value: '서울시 성동구 왕십리로 83' },
99
+ { label: '결제 수단', value: '카드 · 일시불' },
100
+ { label: '합계', value: '205,000원' },
101
+ ]}
102
+ />
103
+ <Stack gap={2}>
104
+ <Text variant="label-md">주문 상품</Text>
105
+ <List aria-label="주문 상품">
106
+ {ITEMS.map((it) => (
107
+ <ListItem
108
+ key={it.id}
109
+ title={it.name}
110
+ description={it.option}
111
+ trailing={
112
+ <Text variant="label-md" numeric>
113
+ {it.price}
114
+ </Text>
115
+ }
116
+ />
117
+ ))}
118
+ </List>
119
+ </Stack>
120
+ </Stack>
121
+ </Drawer>
122
+ )
123
+ }
124
+ ```
125
+
126
+ ### 방향
127
+
128
+ side = 'left' · 'right' · 'top' · 'bottom'. 닫힐 때는 들어온 방향으로 다시 나가요.
129
+
130
+ ```tsx
131
+ import type { ReactNode } from 'react'
132
+ import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp } from 'lucide-react'
133
+ import { Button, Inline, Text, Drawer, type DrawerSide } from '@iamfiro/design'
134
+
135
+ const SIDES: Array<{ side: DrawerSide; label: string; icon: ReactNode }> = [
136
+ { side: 'left', label: '왼쪽', icon: <ArrowRight /> },
137
+ { side: 'right', label: '오른쪽', icon: <ArrowLeft /> },
138
+ { side: 'top', label: '위', icon: <ArrowDown /> },
139
+ { side: 'bottom', label: '아래', icon: <ArrowUp /> },
140
+ ]
141
+
142
+ export default function Example() {
143
+ return (
144
+ <Inline gap={2}>
145
+ {SIDES.map(({ side, label, icon }) => (
146
+ <Drawer
147
+ key={side}
148
+ side={side}
149
+ size="sm"
150
+ trigger={<Button icon={icon}>{label}</Button>}
151
+ title={`${label}에서 열리는 서랍`}
152
+ description="들어온 방향으로 다시 미끄러져 나가요."
153
+ >
154
+ <Text variant="body-md" tone="secondary">
155
+ Esc, 바깥 클릭, 오른쪽 위 ×로 닫을 수 있어요. 닫히면 포커스가 열었던 버튼으로 돌아가요.
156
+ </Text>
157
+ </Drawer>
158
+ ))}
159
+ </Inline>
160
+ )
161
+ }
162
+ ```
163
+
164
+ ### 폼 · 저장 중 잠금
165
+
166
+ 저장하는 동안 dismissible을 끄면 Esc·바깥 클릭·×·끌어 닫기가 모두 막혀요. initialFocus로 첫 입력칸에 바로 포커스가 가요.
167
+
168
+ ```tsx
169
+ import { useRef, useState } from 'react'
170
+ import { SlidersHorizontal } from 'lucide-react'
171
+ import { Button, Checkbox, Field, Input, Stack, Switch, Text, Drawer, toast } from '@iamfiro/design'
172
+
173
+ const STATUSES = ['진행 중', '검토 대기', '완료', '보류']
174
+
175
+ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
176
+
177
+ export default function Example() {
178
+ const [open, setOpen] = useState(false)
179
+ const [saving, setSaving] = useState(false)
180
+ const [keyword, setKeyword] = useState('')
181
+ const [statuses, setStatuses] = useState<string[]>(['진행 중', '검토 대기'])
182
+ const [mine, setMine] = useState(true)
183
+ const keywordRef = useRef<HTMLInputElement>(null)
184
+
185
+ const toggle = (s: string) => setStatuses((prev) => (prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]))
186
+
187
+ const apply = async () => {
188
+ setSaving(true)
189
+ await wait(1200)
190
+ setSaving(false)
191
+ setOpen(false)
192
+ toast.success('필터를 저장했어요', { description: `상태 ${statuses.length}개${mine ? ' · 내 작업만' : ''}` })
193
+ }
194
+
195
+ return (
196
+ <Drawer
197
+ open={open}
198
+ onOpenChange={setOpen}
199
+ trigger={<Button icon={<SlidersHorizontal />}>필터</Button>}
200
+ title="작업 필터"
201
+ description="저장한 필터는 다음에 열 때도 그대로 적용돼요."
202
+ initialFocus={keywordRef}
203
+ dismissible={!saving}
204
+ footer={
205
+ <>
206
+ <Button disabled={saving} onClick={() => setOpen(false)}>
207
+ 취소
208
+ </Button>
209
+ <Button variant="primary" loading={saving} onClick={apply}>
210
+ 필터 저장
211
+ </Button>
212
+ </>
213
+ }
214
+ >
215
+ <Stack gap={6}>
216
+ <Field label="검색어" htmlFor="filter-keyword">
217
+ <Input
218
+ ref={keywordRef}
219
+ id="filter-keyword"
220
+ placeholder="제목이나 담당자"
221
+ value={keyword}
222
+ onChange={(e) => setKeyword(e.target.value)}
223
+ disabled={saving}
224
+ />
225
+ </Field>
226
+ <Stack gap={3}>
227
+ <Text variant="label-md">상태</Text>
228
+ {STATUSES.map((s) => (
229
+ <Checkbox key={s} label={s} checked={statuses.includes(s)} onChange={() => toggle(s)} disabled={saving} />
230
+ ))}
231
+ </Stack>
232
+ <Switch label="내가 맡은 작업만" checked={mine} onChange={(e) => setMine(e.target.checked)} disabled={saving} />
233
+ </Stack>
234
+ </Drawer>
235
+ )
236
+ }
237
+ ```
238
+
239
+ ### 바텀시트
240
+
241
+ Sheet는 손잡이가 달린 아래쪽 서랍이에요. 손잡이나 제목줄을 아래로 끌어 높이의 35%를 넘기거나 빠르게 튕기면 닫히고, 아니면 제자리로 돌아와요. 위로 끌면 고무줄처럼 버티고, 끄는 도중 Esc를 누르면 원래 자리로 취소돼요.
242
+
243
+ ```tsx
244
+ import { useState } from 'react'
245
+ import { Copy, Link2, Mail, MessageCircle, Share2 } from 'lucide-react'
246
+ import { Button, IconTile, List, ListItem, Sheet, toast } from '@iamfiro/design'
247
+
248
+ const TARGETS = [
249
+ { id: 'link', label: '링크 복사', description: '보기 권한이 있는 사람만 열 수 있어요', icon: <Link2 /> },
250
+ { id: 'mail', label: '메일로 보내기', description: '받는 사람에게 초대 메일을 보내요', icon: <Mail /> },
251
+ { id: 'chat', label: '팀 채팅에 공유', description: '#디자인-시스템 채널', icon: <MessageCircle /> },
252
+ { id: 'copy', label: '사본 만들기', description: '내 문서함에 복사본을 만들어요', icon: <Copy /> },
253
+ ]
254
+
255
+ export default function Example() {
256
+ const [open, setOpen] = useState(false)
257
+ return (
258
+ <Sheet
259
+ open={open}
260
+ onOpenChange={setOpen}
261
+ size="sm"
262
+ trigger={<Button icon={<Share2 />}>공유</Button>}
263
+ title="10월 디자인 리뷰"
264
+ description="손잡이나 제목줄을 아래로 끌어 닫을 수 있어요."
265
+ >
266
+ <List aria-label="공유 방법">
267
+ {TARGETS.map((t) => (
268
+ <ListItem
269
+ key={t.id}
270
+ leading={<IconTile>{t.icon}</IconTile>}
271
+ title={t.label}
272
+ description={t.description}
273
+ onClick={() => {
274
+ setOpen(false)
275
+ toast.success(`${t.label}을(를) 마쳤어요`)
276
+ }}
277
+ />
278
+ ))}
279
+ </List>
280
+ </Sheet>
281
+ )
282
+ }
283
+ ```
284
+
285
+ ### 멈춤 지점
286
+
287
+ snapPoints(전체 높이에 대한 비율)를 주면 놓을 때 가장 가까운 지점에 멈춰요. 빠르게 튕기면 그 방향으로 넘어가요. 손잡이는 버튼이 되어 누르거나 Enter로 지점을 차례로 옮겨요.
288
+
289
+ ```tsx
290
+ import { useState } from 'react'
291
+ import { MapPin } from 'lucide-react'
292
+ import { Badge, Button, IconTile, Text, List, ListItem, Sheet } from '@iamfiro/design'
293
+
294
+ const PLACES = [
295
+ { id: 'p1', name: '성수 카페 온더', distance: '120m', open: true },
296
+ { id: 'p2', name: '서울숲 북카페', distance: '340m', open: true },
297
+ { id: 'p3', name: '뚝섬 브루어리', distance: '510m', open: false },
298
+ { id: 'p4', name: '왕십리 베이커리', distance: '780m', open: true },
299
+ { id: 'p5', name: '응봉 라운지', distance: '1.1km', open: true },
300
+ { id: 'p6', name: '옥수 티하우스', distance: '1.4km', open: false },
301
+ { id: 'p7', name: '금호 로스터스', distance: '1.6km', open: true },
302
+ { id: 'p8', name: '행당 브런치', distance: '1.9km', open: true },
303
+ ]
304
+
305
+ export default function Example() {
306
+ const [snap, setSnap] = useState(0.45)
307
+ return (
308
+ <Sheet
309
+ trigger={<Button icon={<MapPin />}>근처 장소</Button>}
310
+ title="근처 장소 8곳"
311
+ description={snap < 1 ? '위로 끌거나 손잡이를 누르면 펼쳐져요.' : '아래로 끌면 반만 보이고, 더 끌면 닫혀요.'}
312
+ size="lg"
313
+ snapPoints={[0.45, 1]}
314
+ snap={snap}
315
+ onSnapChange={setSnap}
316
+ onOpenChange={(open) => open && setSnap(0.45)}
317
+ >
318
+ <List aria-label="근처 장소">
319
+ {PLACES.map((p) => (
320
+ <ListItem
321
+ key={p.id}
322
+ leading={<IconTile>{p.name.slice(0, 1)}</IconTile>}
323
+ title={p.name}
324
+ meta={
325
+ <Text variant="caption" tone="secondary" numeric>
326
+ {p.distance}
327
+ </Text>
328
+ }
329
+ trailing={
330
+ p.open ? (
331
+ <Badge tone="success" dot size="sm">
332
+ 영업 중
333
+ </Badge>
334
+ ) : (
335
+ <Badge size="sm">준비 중</Badge>
336
+ )
337
+ }
338
+ />
339
+ ))}
340
+ </List>
341
+ </Sheet>
342
+ )
343
+ }
344
+ ```
345
+
346
+ ### 비모달
347
+
348
+ modal={false}면 배경이 어두워지지 않고 뒤 페이지를 그대로 쓸 수 있어요(검사 패널). 열릴 때 포커스가 들어오고, Esc로 닫혀요.
349
+
350
+ ```tsx
351
+ import { useState } from 'react'
352
+ import { PanelRightOpen } from 'lucide-react'
353
+ import { Avatar, Button, Inline, Stack, Text, DescriptionList, Drawer, List, ListItem } from '@iamfiro/design'
354
+
355
+ const PEOPLE = [
356
+ { id: 'u1', name: '김민지', role: '디자인 리드', team: '디자인 시스템', joined: '2021년 3월' },
357
+ { id: 'u2', name: '이도현', role: '프론트엔드 개발', team: '플랫폼', joined: '2022년 8월' },
358
+ { id: 'u3', name: '박서준', role: '프로덕트 매니저', team: '성장', joined: '2023년 1월' },
359
+ ]
360
+
361
+ export default function Example() {
362
+ const [open, setOpen] = useState(false)
363
+ // Keep the last person while the drawer slides out.
364
+ const [selected, setSelected] = useState(PEOPLE[0].id)
365
+ const person = PEOPLE.find((p) => p.id === selected) ?? PEOPLE[0]
366
+
367
+ return (
368
+ <Stack gap={3} style={{ width: '100%', maxWidth: 420 }}>
369
+ <Text variant="body-sm" tone="secondary">
370
+ 서랍이 열린 채로 다른 사람을 눌러 보세요. 뒤 페이지가 계속 동작해요.
371
+ </Text>
372
+ <List aria-label="멤버">
373
+ {PEOPLE.map((p) => (
374
+ <ListItem
375
+ key={p.id}
376
+ leading={<Avatar name={p.name} size={32} tooltip={false} />}
377
+ title={p.name}
378
+ description={p.role}
379
+ selected={open && p.id === selected}
380
+ onClick={() => {
381
+ setSelected(p.id)
382
+ setOpen(true)
383
+ }}
384
+ trailing={<PanelRightOpen size={16} aria-hidden />}
385
+ />
386
+ ))}
387
+ </List>
388
+ <Drawer
389
+ open={open}
390
+ onOpenChange={setOpen}
391
+ modal={false}
392
+ inset
393
+ size="sm"
394
+ title={person.name}
395
+ description={person.role}
396
+ footer={
397
+ <Button variant="primary" onClick={() => setOpen(false)}>
398
+ 완료
399
+ </Button>
400
+ }
401
+ >
402
+ <Stack gap={5}>
403
+ <Inline gap={3} wrap={false}>
404
+ <Avatar name={person.name} size={48} online tooltip={false} />
405
+ <Text variant="body-sm" tone="secondary">
406
+ {person.team} 팀에서 {person.joined}부터 함께하고 있어요.
407
+ </Text>
408
+ </Inline>
409
+ <DescriptionList
410
+ size="sm"
411
+ columns={1}
412
+ layout="inline"
413
+ aria-label="멤버 정보"
414
+ items={[
415
+ { label: '팀', value: person.team },
416
+ { label: '역할', value: person.role },
417
+ { label: '입사', value: person.joined },
418
+ ]}
419
+ />
420
+ </Stack>
421
+ </Drawer>
422
+ </Stack>
423
+ )
424
+ }
425
+ ```
426
+
427
+ ### 크기 · 띄우기
428
+
429
+ 좌우 서랍은 폭 360 · 440 · 640px · 전체, 위아래는 최대 높이 50 · 75 · 92% · 전체. inset은 가장자리에서 8px 떠 있어요.
430
+
431
+ ```tsx
432
+ import { Button, Inline, Text, Drawer, type DrawerSize } from '@iamfiro/design'
433
+
434
+ const SIZES: Array<{ size: DrawerSize; label: string }> = [
435
+ { size: 'sm', label: 'sm · 360px' },
436
+ { size: 'md', label: 'md · 440px' },
437
+ { size: 'lg', label: 'lg · 640px' },
438
+ { size: 'full', label: 'full · 전체 화면' },
439
+ ]
440
+
441
+ export default function Example() {
442
+ return (
443
+ <Inline gap={2}>
444
+ {SIZES.map(({ size, label }) => (
445
+ <Drawer
446
+ key={size}
447
+ size={size}
448
+ trigger={<Button>{label}</Button>}
449
+ title={`크기 ${size}`}
450
+ description="휴대폰에서는 화면 폭에서 40px을 뺀 만큼까지만 넓어져요."
451
+ >
452
+ <Text variant="body-md" tone="secondary">
453
+ 좌우 서랍은 폭이, 위아래 서랍은 최대 높이가 바뀌어요. 내용이 길면 본문만 스크롤돼요.
454
+ </Text>
455
+ </Drawer>
456
+ ))}
457
+ <Drawer
458
+ size="sm"
459
+ inset
460
+ trigger={<Button variant="ghost">띄우기(inset)</Button>}
461
+ title="띄운 서랍"
462
+ description="가장자리에서 8px 떨어져 모든 모서리가 둥글어요."
463
+ >
464
+ <Text variant="body-md" tone="secondary">
465
+ 화면 가장자리에 붙지 않고 떠 있는 패널이 어울리는 화면에서 써요.
466
+ </Text>
467
+ </Drawer>
468
+ </Inline>
469
+ )
470
+ }
471
+ ```