@hjmds/design-contracts 1.12.1 → 1.13.1

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 (288) hide show
  1. package/dist/avatar-fallback.d.ts +11 -0
  2. package/dist/avatar-fallback.d.ts.map +1 -1
  3. package/dist/avatar-fallback.js +21 -0
  4. package/dist/avatar-fallback.js.map +1 -1
  5. package/dist/base-recipes.d.ts +17 -0
  6. package/dist/base-recipes.d.ts.map +1 -1
  7. package/dist/base-recipes.js +17 -0
  8. package/dist/base-recipes.js.map +1 -1
  9. package/dist/catalog.d.ts +26 -0
  10. package/dist/catalog.d.ts.map +1 -1
  11. package/dist/command-palette.d.ts +14 -9
  12. package/dist/command-palette.d.ts.map +1 -1
  13. package/dist/command-palette.js +8 -9
  14. package/dist/command-palette.js.map +1 -1
  15. package/dist/component-recipes.d.ts +31 -0
  16. package/dist/component-recipes.d.ts.map +1 -1
  17. package/dist/component-recipes.js +20 -0
  18. package/dist/component-recipes.js.map +1 -1
  19. package/dist/provider-button.d.ts.map +1 -1
  20. package/dist/provider-button.js +3 -0
  21. package/dist/provider-button.js.map +1 -1
  22. package/dist/reactions.d.ts +10 -0
  23. package/dist/reactions.d.ts.map +1 -1
  24. package/dist/reactions.js +7 -0
  25. package/dist/reactions.js.map +1 -1
  26. package/dist/screen-patterns.d.ts +147 -0
  27. package/dist/screen-patterns.d.ts.map +1 -0
  28. package/dist/screen-patterns.js +149 -0
  29. package/dist/screen-patterns.js.map +1 -0
  30. package/dist/slider.d.ts +8 -0
  31. package/dist/slider.d.ts.map +1 -1
  32. package/dist/slider.js +6 -1
  33. package/dist/slider.js.map +1 -1
  34. package/dist/upload-item.d.ts +5 -0
  35. package/dist/upload-item.d.ts.map +1 -1
  36. package/dist/upload-item.js +5 -0
  37. package/dist/upload-item.js.map +1 -1
  38. package/dist/version.d.ts +1 -1
  39. package/dist/version.js +1 -1
  40. package/dist/version.js.map +1 -1
  41. package/docs/action-session.md +3 -3
  42. package/docs/agreement.md +5 -0
  43. package/docs/avatar-fallback.md +7 -0
  44. package/docs/bottom-navigation.md +6 -0
  45. package/docs/brand-boundary.md +1 -1
  46. package/docs/button-label.md +6 -0
  47. package/docs/clipboard.md +3 -0
  48. package/docs/command-palette.md +45 -2
  49. package/docs/consumer-policy.md +5 -1
  50. package/docs/data-table.md +6 -4
  51. package/docs/dialog.md +8 -2
  52. package/docs/form.md +51 -0
  53. package/docs/generated/component-maturity.md +1 -1
  54. package/docs/generated/renderer-evidence.json +3 -3
  55. package/docs/generated/renderer-evidence.md +1 -1
  56. package/docs/generated/showcase-manifest.json +1 -1
  57. package/docs/link.md +8 -0
  58. package/docs/migration-native-legacy-removal.md +45 -1
  59. package/docs/optional-adapters.md +1 -1
  60. package/docs/password-field.md +5 -0
  61. package/docs/product-composition-adoption.md +40 -0
  62. package/docs/progress.md +19 -1
  63. package/docs/provider-button.md +13 -0
  64. package/docs/result.md +3 -0
  65. package/docs/screen-chrome.md +10 -0
  66. package/docs/screen-patterns.md +378 -0
  67. package/docs/sheet.md +21 -0
  68. package/docs/splitter.md +8 -2
  69. package/docs/theming.md +36 -29
  70. package/docs/toggle-group.md +13 -0
  71. package/docs/tour.md +7 -1
  72. package/docs/tree.md +5 -2
  73. package/docs/upload-item.md +7 -0
  74. package/docs/usage/README.md +236 -0
  75. package/docs/usage/STANDARD.md +108 -0
  76. package/docs/usage/components/accordion.md +107 -0
  77. package/docs/usage/components/activity-heatmap.md +104 -0
  78. package/docs/usage/components/affix.md +86 -0
  79. package/docs/usage/components/agreement.md +129 -0
  80. package/docs/usage/components/alert-dialog.md +130 -0
  81. package/docs/usage/components/anchor.md +96 -0
  82. package/docs/usage/components/aspect-ratio.md +89 -0
  83. package/docs/usage/components/asset.md +126 -0
  84. package/docs/usage/components/auth-provider-button.md +116 -0
  85. package/docs/usage/components/auth-screen-layout.md +129 -0
  86. package/docs/usage/components/avatar.md +114 -0
  87. package/docs/usage/components/badge.md +84 -0
  88. package/docs/usage/components/bottom-cta.md +125 -0
  89. package/docs/usage/components/bottom-info.md +99 -0
  90. package/docs/usage/components/bottom-navigation.md +136 -0
  91. package/docs/usage/components/breadcrumb.md +81 -0
  92. package/docs/usage/components/button.md +118 -0
  93. package/docs/usage/components/calendar.md +122 -0
  94. package/docs/usage/components/card.md +110 -0
  95. package/docs/usage/components/carousel.md +113 -0
  96. package/docs/usage/components/celebration.md +96 -0
  97. package/docs/usage/components/chat-message.md +122 -0
  98. package/docs/usage/components/chat-screen.md +112 -0
  99. package/docs/usage/components/checkbox-group.md +104 -0
  100. package/docs/usage/components/checkbox.md +103 -0
  101. package/docs/usage/components/chip.md +107 -0
  102. package/docs/usage/components/code-block.md +111 -0
  103. package/docs/usage/components/collapsible.md +112 -0
  104. package/docs/usage/components/color-picker.md +86 -0
  105. package/docs/usage/components/combobox.md +137 -0
  106. package/docs/usage/components/command-palette.md +125 -0
  107. package/docs/usage/components/comment-thread-screen.md +125 -0
  108. package/docs/usage/components/container.md +98 -0
  109. package/docs/usage/components/content-transition.md +101 -0
  110. package/docs/usage/components/context-menu.md +136 -0
  111. package/docs/usage/components/counter-badge.md +107 -0
  112. package/docs/usage/components/data-table.md +122 -0
  113. package/docs/usage/components/date-picker.md +142 -0
  114. package/docs/usage/components/date-range-picker.md +111 -0
  115. package/docs/usage/components/description-list.md +103 -0
  116. package/docs/usage/components/design-system-provider.md +124 -0
  117. package/docs/usage/components/dialog.md +176 -0
  118. package/docs/usage/components/divider.md +89 -0
  119. package/docs/usage/components/editor-screen.md +126 -0
  120. package/docs/usage/components/effect-surface.md +120 -0
  121. package/docs/usage/components/empty-state.md +114 -0
  122. package/docs/usage/components/field.md +129 -0
  123. package/docs/usage/components/file-picker.md +114 -0
  124. package/docs/usage/components/floating-action-button.md +138 -0
  125. package/docs/usage/components/form.md +162 -0
  126. package/docs/usage/components/grid.md +99 -0
  127. package/docs/usage/components/heading.md +87 -0
  128. package/docs/usage/components/icon-button.md +126 -0
  129. package/docs/usage/components/icon.md +105 -0
  130. package/docs/usage/components/image.md +122 -0
  131. package/docs/usage/components/keyboard-avoiding.md +93 -0
  132. package/docs/usage/components/keyboard-dock.md +110 -0
  133. package/docs/usage/components/keyboard-form-scroll-view.md +95 -0
  134. package/docs/usage/components/keyboard-motion-provider.md +86 -0
  135. package/docs/usage/components/layout.md +117 -0
  136. package/docs/usage/components/link.md +121 -0
  137. package/docs/usage/components/list-detail-screen.md +103 -0
  138. package/docs/usage/components/list-row.md +124 -0
  139. package/docs/usage/components/list.md +119 -0
  140. package/docs/usage/components/load-more.md +115 -0
  141. package/docs/usage/components/masonry.md +109 -0
  142. package/docs/usage/components/media-selection-screen.md +119 -0
  143. package/docs/usage/components/mentions.md +119 -0
  144. package/docs/usage/components/menu.md +129 -0
  145. package/docs/usage/components/menubar.md +93 -0
  146. package/docs/usage/components/message-composer.md +124 -0
  147. package/docs/usage/components/moderation-screen.md +113 -0
  148. package/docs/usage/components/notice.md +106 -0
  149. package/docs/usage/components/notification-inbox-screen.md +97 -0
  150. package/docs/usage/components/notification-item.md +98 -0
  151. package/docs/usage/components/number-field.md +131 -0
  152. package/docs/usage/components/onboarding-screen.md +106 -0
  153. package/docs/usage/components/otp-field.md +101 -0
  154. package/docs/usage/components/pagination.md +82 -0
  155. package/docs/usage/components/password-field.md +137 -0
  156. package/docs/usage/components/permission-screen.md +107 -0
  157. package/docs/usage/components/photo-source-sheet.md +119 -0
  158. package/docs/usage/components/popover.md +108 -0
  159. package/docs/usage/components/profile-screen.md +89 -0
  160. package/docs/usage/components/progress.md +122 -0
  161. package/docs/usage/components/qr-code.md +122 -0
  162. package/docs/usage/components/radio-group.md +124 -0
  163. package/docs/usage/components/radio.md +104 -0
  164. package/docs/usage/components/result.md +116 -0
  165. package/docs/usage/components/saved-items-screen.md +126 -0
  166. package/docs/usage/components/screen-layout.md +119 -0
  167. package/docs/usage/components/search-field.md +120 -0
  168. package/docs/usage/components/search-screen.md +221 -0
  169. package/docs/usage/components/section.md +111 -0
  170. package/docs/usage/components/segmented-control.md +146 -0
  171. package/docs/usage/components/select.md +142 -0
  172. package/docs/usage/components/settings-screen.md +126 -0
  173. package/docs/usage/components/shared-transition-element.md +111 -0
  174. package/docs/usage/components/shared-transition-screen.md +86 -0
  175. package/docs/usage/components/sheet.md +157 -0
  176. package/docs/usage/components/side-panel.md +104 -0
  177. package/docs/usage/components/sidebar.md +107 -0
  178. package/docs/usage/components/skeleton.md +105 -0
  179. package/docs/usage/components/skip-nav.md +76 -0
  180. package/docs/usage/components/slider.md +121 -0
  181. package/docs/usage/components/sortable-collection.md +127 -0
  182. package/docs/usage/components/spinner.md +86 -0
  183. package/docs/usage/components/splitter.md +103 -0
  184. package/docs/usage/components/stack.md +93 -0
  185. package/docs/usage/components/statistic.md +123 -0
  186. package/docs/usage/components/steps.md +110 -0
  187. package/docs/usage/components/surface.md +91 -0
  188. package/docs/usage/components/swipe-actions.md +124 -0
  189. package/docs/usage/components/switch.md +120 -0
  190. package/docs/usage/components/tabs.md +134 -0
  191. package/docs/usage/components/tag.md +84 -0
  192. package/docs/usage/components/tags-input.md +111 -0
  193. package/docs/usage/components/text-area.md +112 -0
  194. package/docs/usage/components/text-format.md +75 -0
  195. package/docs/usage/components/text-transition.md +104 -0
  196. package/docs/usage/components/text.md +101 -0
  197. package/docs/usage/components/thinking-orb.md +105 -0
  198. package/docs/usage/components/timeline.md +105 -0
  199. package/docs/usage/components/toast.md +145 -0
  200. package/docs/usage/components/toggle-group.md +95 -0
  201. package/docs/usage/components/tooltip.md +103 -0
  202. package/docs/usage/components/top-bar.md +124 -0
  203. package/docs/usage/components/top.md +89 -0
  204. package/docs/usage/components/tour.md +118 -0
  205. package/docs/usage/components/transfer-list.md +115 -0
  206. package/docs/usage/components/tree.md +91 -0
  207. package/docs/usage/components/upload-item.md +99 -0
  208. package/docs/usage/components/virtual-list.md +105 -0
  209. package/docs/usage/components/visually-hidden.md +72 -0
  210. package/docs/usage/components/watermark.md +78 -0
  211. package/docs/usage/compositions/action-recovery-optimistic.md +180 -0
  212. package/docs/usage/compositions/action-recovery-save.md +235 -0
  213. package/docs/usage/compositions/action-recovery-undo.md +193 -0
  214. package/docs/usage/compositions/common-message.md +132 -0
  215. package/docs/usage/compositions/common-notification.md +101 -0
  216. package/docs/usage/compositions/compound-controls.md +186 -0
  217. package/docs/usage/compositions/data-layouts.md +157 -0
  218. package/docs/usage/compositions/disclosure.md +144 -0
  219. package/docs/usage/compositions/environment-matrix.md +139 -0
  220. package/docs/usage/compositions/expo-interactions.md +149 -0
  221. package/docs/usage/compositions/family-drawer.md +201 -0
  222. package/docs/usage/compositions/floating-action-button.md +197 -0
  223. package/docs/usage/compositions/input-sheet.md +148 -0
  224. package/docs/usage/compositions/interaction-adapters.md +190 -0
  225. package/docs/usage/compositions/interaction-flow-apply.md +205 -0
  226. package/docs/usage/compositions/interaction-flow-draft.md +188 -0
  227. package/docs/usage/compositions/interaction-flow-search.md +171 -0
  228. package/docs/usage/compositions/native-renderers.md +106 -0
  229. package/docs/usage/compositions/navigation-bar-collection.md +164 -0
  230. package/docs/usage/compositions/optional-adapters.md +169 -0
  231. package/docs/usage/compositions/optional-motion.md +109 -0
  232. package/docs/usage/compositions/photo-source.md +104 -0
  233. package/docs/usage/compositions/purpose-input-comment.md +110 -0
  234. package/docs/usage/compositions/purpose-input-message.md +119 -0
  235. package/docs/usage/compositions/reference-first.md +96 -0
  236. package/docs/usage/compositions/reference-review.md +107 -0
  237. package/docs/usage/compositions/reference-settings.md +107 -0
  238. package/docs/usage/compositions/selection-scope.md +174 -0
  239. package/docs/usage/compositions/stea-event-ticket.md +166 -0
  240. package/docs/usage/compositions/stea-flip-card.md +162 -0
  241. package/docs/usage/compositions/stea-order-progress.md +184 -0
  242. package/docs/usage/compositions/stea-otp-verify.md +215 -0
  243. package/docs/usage/compositions/stea-pixel-empty.md +140 -0
  244. package/docs/usage/compositions/stea-schedule-card.md +169 -0
  245. package/docs/usage/compositions/stea-stat-summary.md +154 -0
  246. package/docs/usage/compositions/time-selection.md +174 -0
  247. package/docs/usage/compositions/toast-layout.md +128 -0
  248. package/docs/usage/compositions/visual-foundations.md +185 -0
  249. package/docs/usage/compositions/web-additions.md +146 -0
  250. package/docs/usage/compositions/web-navigation.md +143 -0
  251. package/docs/usage/screens/common-chat.md +127 -0
  252. package/docs/usage/screens/common-comments.md +108 -0
  253. package/docs/usage/screens/common-inbox.md +110 -0
  254. package/docs/usage/screens/common-login.md +98 -0
  255. package/docs/usage/screens/common-profile.md +221 -0
  256. package/docs/usage/screens/common-saved.md +127 -0
  257. package/docs/usage/screens/common-search.md +279 -0
  258. package/docs/usage/screens/common-settings.md +126 -0
  259. package/docs/usage/screens/common-shell.md +108 -0
  260. package/docs/usage/screens/dashboard.md +245 -0
  261. package/docs/usage/screens/discovery-gallery.md +306 -0
  262. package/docs/usage/screens/flow-collection.md +96 -0
  263. package/docs/usage/screens/flow-editor.md +120 -0
  264. package/docs/usage/screens/flow-media.md +111 -0
  265. package/docs/usage/screens/flow-moderation.md +120 -0
  266. package/docs/usage/screens/flow-onboarding.md +193 -0
  267. package/docs/usage/screens/flow-permission.md +103 -0
  268. package/docs/usage/screens/landing.md +347 -0
  269. package/docs/usage/screens/mockup-studio.md +190 -0
  270. package/docs/usage/screens/notification-settings.md +206 -0
  271. package/docs/usage/screens/reference-comparison.md +159 -0
  272. package/docs/usage/templates/component.md +61 -0
  273. package/docs/usage/templates/composition.md +47 -0
  274. package/docs/usage/templates/screen.md +56 -0
  275. package/docs/usage/templates/token.md +32 -0
  276. package/docs/usage/tokens/color.md +142 -0
  277. package/docs/usage/tokens/elevation-opacity.md +86 -0
  278. package/docs/usage/tokens/layers.md +98 -0
  279. package/docs/usage/tokens/layout.md +114 -0
  280. package/docs/usage/tokens/motion.md +88 -0
  281. package/docs/usage/tokens/radius.md +53 -0
  282. package/docs/usage/tokens/size.md +74 -0
  283. package/docs/usage/tokens/spacing.md +73 -0
  284. package/docs/usage/tokens/stroke.md +50 -0
  285. package/docs/usage/tokens/theme-studio.md +70 -0
  286. package/docs/usage/tokens/typography-studio.md +70 -0
  287. package/docs/usage/tokens/typography.md +89 -0
  288. package/package.json +7 -1
@@ -0,0 +1,145 @@
1
+ # Toast
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Toast](../../toast.md), [Liquid Toast](../../../../react-native/docs/liquid-toast.md), `src/component-recipes.ts`(`toastRecipe`)
9
+ - 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드 토스트`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 방금 한 행동의 결과처럼 **무시해도 안전한 짧은 알림**에 쓴다. 저장 완료, 복사 완료, 백그라운드 작업 완료
14
+ 알림이 여기에 속한다. 화면 흐름 밖에 떠서 3초 뒤 스스로 닫히고, 큐에 쌓인 다음 알림이 이어서 뜬다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 화면 안에 계속 남아야 하는 안내·경고(점검 예정, 권한 없음, 폼 오류 요약) | [Notice](notice.md) |
21
+ | 사용자가 응답해야 진행되는 확인·삭제 확인·필수 선택 | [AlertDialog](alert-dialog.md) |
22
+ | 작업 결과를 화면 전체로 보여 줌 | [Result](result.md) |
23
+
24
+ 선택 기준: 사용자가 놓쳐도 되고 자리를 차지하면 안 되면 Toast, 그 화면을 보는 동안 계속 보여야 하거나
25
+ 읽지 않으면 다음 행동을 잘못할 수 있으면 Notice다. Toast에는 그 알림에서만 할 수 있는 행동을 두지 않는다.
26
+
27
+ ## 공개 이름과 import
28
+
29
+ | 이름 | 역할 | Web | Native |
30
+ | --- | --- | --- | --- |
31
+ | `Toast` | 기본(알림 한 장, controlled) | `@hjmds/react`, `/toast` | `@hjmds/react-native`, `/feedback` |
32
+ | `ToastProvider`·`useToast` | 동반(Web 큐·viewport) | `@hjmds/react`, `/toast` | — |
33
+ | `ToastRegion`·`useToastRegion` | 동반(Native 큐·영역) | — | `@hjmds/react-native`, `/feedback` |
34
+ | `createLiquidToastPresentation` | 확장(선택형 리퀴드 표현) | — | `/toast-liquid`만(root 재노출 없음) |
35
+
36
+ ## 최소 사용 예
37
+
38
+ ```tsx
39
+ // Web: 앱 루트에 한 번
40
+ import { ToastProvider, useToast } from "@hjmds/react/toast";
41
+
42
+ <ToastProvider label={t("common.notifications")}>{children}</ToastProvider>;
43
+
44
+ // 화면 컴포넌트 안에서 알림을 낸다
45
+ function useProfileSavedToast() {
46
+ const toast = useToast();
47
+ return () =>
48
+ toast.publish({
49
+ id: "profile-save",
50
+ description: t("profile.saved"),
51
+ tone: "success",
52
+ closeLabel: t("common.closeNotification"),
53
+ });
54
+ }
55
+ ```
56
+
57
+ ```tsx
58
+ // Native: 앱 루트에 한 번
59
+ import { ToastRegion, useToastRegion } from "@hjmds/react-native/feedback";
60
+
61
+ <ToastRegion safeAreaInsets={insets} accessibilityLabel={t("common.notifications")}>
62
+ {children}
63
+ </ToastRegion>;
64
+
65
+ function useProfileSavedToast() {
66
+ const toast = useToastRegion();
67
+ return () =>
68
+ toast.publish({
69
+ id: "profile-save",
70
+ description: t("profile.saved"),
71
+ tone: "success",
72
+ closeLabel: t("common.closeNotification"),
73
+ });
74
+ }
75
+ ```
76
+
77
+ ## 축과 기본값
78
+
79
+ | prop | 값 | 기본값 | 설명 |
80
+ | --- | --- | --- | --- |
81
+ | descriptor | `id`·`description`·`closeLabel` 필수, `title`·`tone`·`priority`·`durationMs`·`action`·`announcement`·`presentation` 선택 | — | 모두 `string`(ReactNode 아님) |
82
+ | `action` | `{ label, accessibilityLabel?, onAction: () => void, dismissOnAction? }` | — | 누르면 `onAction`, 기본으로 알림이 닫힌다 |
83
+ | `tone` | `neutral` · `info` · `success` · `warning` · `danger` | `neutral` | — |
84
+ | `priority` | `normal` · `high` | — | 낭독 순서, 색과 무관 |
85
+ | `durationMs` | ms · `null` | 3000 | 더 짧게 줘도 3000으로 올라간다. `null`은 수동으로 닫을 때까지 유지, `action`이 있으면 기본 유지 |
86
+ | `placement` | `bottom` · `top` · `top-start` · `top-end` · `bottom-start` · `bottom-end` | `bottom` | — |
87
+ | 큐 | `maxVisible` · `maxQueued` · `duplicatePolicy` · `timerUpdatePolicy` · `overflowPolicy` | 1 · 20 · `update` · `preserve` · `discard-oldest` | — |
88
+ | `publish` | Web `(descriptor, options?) => ToastPublishResult`, Native `(descriptor) => ToastPublishResult` | — | 결과는 `{ outcome: "added" \| "updated", id, position: "visible" \| "queued" }`, `{ outcome: "ignored", id, reason: "duplicate" \| "closing" }`, `{ outcome: "discarded", id, reason: "queue-overflow" }` 중 하나 |
89
+ | `dismiss` | Web `(id, reason) => boolean`, Native `(id, reason?) => boolean` | — | 닫았으면 `true`. Web은 `close(id)`도 있다. Native는 `pause`·`resume(id, reason?)`도 있다 |
90
+ | 닫힘 사유 `ToastDismissReason` | `timeout` · `action` · `close-action` · `escape` · `swipe` · `programmatic` · `queue-overflow` · `interrupted` | — | Web `Toast` `onDismissRequest`·Native `onDismiss`가 받는다 |
91
+ | `toasts`/`defaultToasts` + `onToastsChange`(Native `ToastRegion`) | `readonly ToastDescriptor[]`, `(toasts) => void` | — | 큐를 제품 상태로 제어할 때 |
92
+ | `safeAreaInsets`(Native) | `{ top?, bottom?, left?, right?, start?, end? }` | `{}` | — |
93
+ | `layoutStyle`(Native `Toast`·`ToastRegion`) | `HjmCompositionStyleProp` | — | Web Toast·ToastProvider에는 없다(떠 있는 층이라 배치 대상이 아님) |
94
+ | `style`·`toastStyle`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `placement`·`safeAreaInsets`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
95
+
96
+ ## 배치
97
+
98
+ | 항목 | 값 | 근거 |
99
+ | --- | --- | --- |
100
+ | 크기 | 카드 최대 폭 420(Web `min(420px, 100vw − 2×spacing.md)`, Native 화면 폭 − 안전 영역 − 2×16에서 420으로 자름). 최소 높이 56(`layout.rowHeight.singleLine`). 열은 [아이콘 배지 32][문구][닫기 44]. `action`은 높이 44(`control.minTouchTarget`), 좌우 여백 `spacing.md` 16의 pill | `toastRecipe.viewport`·`surface`·`icon`·`action`, `.hjm-toast`, `react-native/src/feedback.tsx` |
101
+ | 간격 | 화면 가장자리에서 `spacing.md` 16 + 안전 영역(`safeAreaMode: "additive"`). 여러 장 사이 `spacing.sm` 12. 카드 안 열 간격 `spacing.sm` 12 | `toastRecipe.viewport`, `.hjm-toast-viewport` |
102
+ | 순서·정렬 | 기본은 아래 가운데(`placement="bottom"`). 위쪽 배치는 위에서 아래로, 아래쪽 배치는 가장자리부터 위로 쌓인다. `action`은 둘째 줄 끝 | `toastRecipe.placements`, `.hjm-toast__action` |
103
+ | 고정·스크롤 | 화면 흐름 밖에 떠 있는 층이라 본문 레이아웃에 자리를 만들지 않는다. 하단 고정 바(BottomNavigation·BottomCTA)가 있으면 Web `bottomOffset`(px 또는 CSS 길이), Native `keyboardOffset`으로 그 위에 띄운다. Native 아래쪽 배치는 `avoidKeyboard`(기본 true)로 키보드 위에 선다 | `react/src/toast.tsx`, `react-native/src/feedback.tsx`(`ToastRegion`) |
104
+ | 좁은 폭·큰 글자 | 폭이 줄고 문구가 줄바꿈되어 카드가 세로로 자란다. 잘라 내지 않는다 | `.hjm-toast__content`(`overflow-wrap: anywhere`) |
105
+
106
+ ```text
107
+ 아래 배치(기본) 하단 바가 있을 때
108
+ ┌──────────────────────────────┐ ┌──────────────────────────────┐
109
+ │ 본문(스크롤) │ │ 본문(스크롤) │
110
+ │ │ │ ┌──────────────────────────┐ │
111
+ │ ┌──────────────────────────┐ │ │ │(●) 저장했어요 [×] │ │
112
+ │ │(●) 저장했어요 [×] │ │ │ │ [되돌리기] │ │ ← action(선택)
113
+ │ └──────────────────────────┘ │ │ └──────────────────────────┘ │
114
+ │ ↑ 16 + 안전 영역 │ │ ↑ bottomOffset/keyboardOffset
115
+ └──────────────────────────────┘ │ [홈] [검색] [내 정보] │ ← 고정 바
116
+ 좌우 16, 최대 폭 420, 가운데 └──────────────────────────────┘
117
+ ```
118
+
119
+ ## 꼭 지킬 것
120
+
121
+ - Provider/Region은 앱에 하나만 두고 화면마다 새로 만들지 않는다. 진행 상태를 바꿀 때는 같은 `id`로 다시 `publish`한다.
122
+ - 문구는 모두 i18n 키로 만든 일반 문자열이다(ReactNode 아님). 아이콘만 있는 닫기 버튼 때문에 `closeLabel`이 필수다.
123
+ - 색·모양은 tone이 정한다. 카드에 브랜드 색을 덮지 않는다. 문구·id·action은 제품 소유, 카드 모양·큐·타이머는 HJM 소유다.
124
+ - Native는 `safeAreaInsets`를 제품이 넘긴다(기본 `{}`). 하단 고정 바가 있으면 Web `bottomOffset`, Native `keyboardOffset`으로 띄운다.
125
+
126
+ ## 플랫폼 차이
127
+
128
+ | 항목 | Web | Native |
129
+ | --- | --- | --- |
130
+ | 영역 이름 | `label` 필수(빈 문자열이면 throw) | `accessibilityLabel` 선택 |
131
+ | 키보드 | `hotkey`+`hotkeyHelp`(둘 다 주거나 둘 다 생략), Escape로 닫기 | `avoidKeyboard`(기본 true) |
132
+ | `Toast` 단독 닫힘 콜백 | `onDismissRequest` 필수 | `onDismiss` 선택 |
133
+ | tone 아이콘 교체 | 없음 | `renderToneIcon` |
134
+ | 모달 위 가림 | 없음 | `occluded` |
135
+ | `presentation: "liquid"` | 무시하고 기본 카드 | `presentationAdapter`가 있을 때만 리퀴드 |
136
+
137
+ ## 함정
138
+
139
+ - `/toast-liquid`는 앱에 없을 수 있는 optional native peer `@shopify/react-native-skia`·`react-native-reanimated`·
140
+ `react-native-worklets`를 import한다(peerDependenciesMeta에서 모두 optional). 2026-10에 이런 subpath가
141
+ tsc·테스트는 통과했는데 기기 Metro에서 크래시가 났다. 세 peer가 개발 클라이언트 바이너리에 들어 있을 때만 import한다.
142
+ - 리퀴드는 `placement="top"`과 `maxVisible={1}`일 때만 동작하고, 아니면 `ToastRegion`이 `TypeError`를 던진다.
143
+ 어댑터는 render 밖에서 만들거나 memo한다.
144
+ - ToastRegion 배치는 `layoutStyle={{ flex: 1 }}`로 쓴다. Native 예제도 이 경로를 사용한다.
145
+ - Web `Toast` 단독 렌더는 나머지 HTML 속성(id·data-*·이벤트)을 루트에 전달한다(미게시(1.12.1 이후). 1.12.1은 `className`만 전달). `role`·`aria-labelledby`·`aria-describedby`·`data-tone`·`data-state`는 Toast가 정하므로 덮이지 않는다. 배치는 Provider를 쓴다.
@@ -0,0 +1,95 @@
1
+ # ToggleGroup
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [ToggleGroup](../../toggle-group.md), `src/toggle-group.ts`(`toggleGroupRecipe`)
9
+ - 스토리북: `배포/컴포넌트/입력/토글 그룹`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 여러 개를 동시에 켜고 끄는 짧은 버튼 묶음에 쓴다. 굵게·기울임·밑줄 같은 서식 토글,
14
+ 함께 거는 필터 몇 개가 여기에 속한다. 아무것도 켜지지 않은 상태도 유효한 값이다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 여러 개 중 정확히 하나를 고름(어떤 화면을 볼지) | [SegmentedControl](segmented-control.md) |
21
+ | 목록 안에서 항목마다 줄·설명이 있는 다중 선택 | [CheckboxGroup](checkbox-group.md) |
22
+ | 단독 켜고 끄기 설정 | [Switch](switch.md), 토글 버튼 하나는 [Button](button.md) `selected` |
23
+ | 많은 필터를 칩으로 나열 | [Chip](chip.md) |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `ToggleGroup` | 기본 | `@hjmds/react`, `/toggle-group` | `@hjmds/react-native`, `/toggle-group` |
30
+
31
+ descriptor 타입과 helper는 `@hjmds/design-contracts/components/toggle-group`에 있다.
32
+
33
+ ## 최소 사용 예
34
+
35
+ ```tsx
36
+ // Web
37
+ import { ToggleGroup } from "@hjmds/react/toggle-group";
38
+
39
+ const descriptor = {
40
+ accessibilityLabel: t("editor.format.group"),
41
+ items: [
42
+ { id: "bold", label: t("editor.format.bold") },
43
+ { id: "italic", label: t("editor.format.italic") },
44
+ ],
45
+ } as const;
46
+
47
+ <ToggleGroup descriptor={descriptor} pressedIds={formats} onPressedIdsChange={setFormats} />
48
+ ```
49
+
50
+ ```tsx
51
+ // Native
52
+ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
53
+
54
+ <ToggleGroup descriptor={descriptor} pressedIds={formats} onPressedIdsChange={setFormats} size="small" />
55
+ ```
56
+
57
+ ## 축과 기본값
58
+
59
+ | prop | 값 | 기본값 | 설명 |
60
+ | --- | --- | --- | --- |
61
+ | `pressedIds` / `defaultPressedIds` | `ReadonlySet<Id>` | 빈 집합 | 제어 또는 비제어 |
62
+ | `onPressedIdsChange` | `(ids: ReadonlySet<Id>) => void` | — | 다음 눌림 집합 전체를 받는다 |
63
+ | `descriptor` | `{ accessibilityLabel: string, items: readonly { id, label, disabled? }[] }` | — (필수) | — |
64
+ | `size` | `small` · `medium` | `medium` | medium은 최소 높이 44 |
65
+ | `items[].disabled` | `boolean` | `false` | `disabled` 항목은 켜지지 않고 눌림 집합에도 들어가지 않는다. 사라진 id의 눌림 상태는 렌더 때 버려진다(`reconcileToggleGroupSelection`) |
66
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | 묶음 컨테이너 배치. Web·Native 모두 |
67
+ | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `size`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
68
+
69
+ ## 배치
70
+
71
+ | 항목 | 값 | 근거 |
72
+ | --- | --- | --- |
73
+ | 크기 | 항목 높이는 두 size 모두 최소 44(`control.minTouchTarget`). 좌우 여백 `small` `spacing.sm` 12, `medium` `spacing.md` 16, 모서리 `radius.md` 12 | `toggleGroupRecipe.sizes`·`radius` |
74
+ | 간격 | 항목 사이 `spacing.xxs` 4 | `toggleGroupRecipe.gap` |
75
+ | 순서·정렬 | `items` 배열 순서 그대로, 시작 쪽(왼쪽)에 붙는다. 편집 도구 줄·필터 줄처럼 적용될 내용 바로 위에 가로로 놓는다 | `.hjm-toggle-group`, `react-native/src/toggle-group.tsx` |
76
+ | 고정·스크롤 | 가로 스크롤로 바꾸지 않는다 | — |
77
+ | 좁은 폭·큰 글자 | 폭이 모자라면 다음 줄로 넘어간다(Web·Native 모두 wrap) | `.hjm-toggle-group`(`flex-wrap`), `react-native/src/toggle-group.tsx` |
78
+
79
+ ## 꼭 지킬 것
80
+
81
+ - `descriptor.accessibilityLabel`은 필수다. 묶음 이름이며 개별 버튼 라벨로 대신할 수 없다.
82
+ 비거나 `items`가 비었거나 id가 겹치면 렌더 중 예외가 난다.
83
+ - 라벨은 i18n 키로 넣고 짧게 둔다. 단일 선택 모드는 없다. 하나만 고르게 하려고 변환하지 않는다.
84
+ - 눌림 표시 색은 recipe(`idle`/`pressed`)가 소유한다. 상태는 Web `aria-pressed`, Native `selected`로
85
+ 알리므로 색만으로 상태를 바꿔 그리지 않는다.
86
+ - 배치는 `layoutStyle`(묶음 컨테이너)로 한다. Web `className`·Native의 deprecated `style`로 항목 모양을 덮지 않는다.
87
+
88
+ ## 플랫폼 차이
89
+
90
+ | 항목 | Web | Native |
91
+ | --- | --- | --- |
92
+ | 컨테이너 | `role="group"` + `aria-label` | `View` + `accessibilityLabel`(role 없음) |
93
+ | 배치 | CSS(`hjm-toggle-group`) | 가로 `flexWrap: "wrap"` |
94
+ | 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은 deprecated) |
95
+ | 키보드 | 항목마다 tab stop(roving 없음) | 해당 없음 |
@@ -0,0 +1,103 @@
1
+ # Tooltip
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Tooltip](../../tooltip.md), [Popover의 Tooltip·Menu·Popover 판정](../../popover.md), `src/component-recipes.ts`(`tooltipRecipe`)
9
+ - 스토리북: `배포/컴포넌트/오버레이/툴팁`
10
+
11
+ ## 언제 쓰나
12
+
13
+ Web에서 이미 이름과 focus를 가진 컨트롤(대개 [IconButton](icon-button.md))에 **짧은 보충 설명 한 문장**을
14
+ 붙일 때 쓴다. 마우스를 올리면 500ms 뒤, 키보드 focus면 바로 열리고 Escape로 닫힌다.
15
+ 브라우저 `title` 속성 툴팁 대신 쓴다.
16
+
17
+ ## 쓰지 않을 때
18
+
19
+ | 상황 | 대신 쓸 것 |
20
+ | --- | --- |
21
+ | 버튼·링크·입력처럼 사용자가 눌러야 하는 내용이 들어감 | [Popover](popover.md) |
22
+ | 행동 항목 목록 | [Menu](menu.md), [ContextMenu](context-menu.md) |
23
+ | 오류·필수 정보처럼 놓치면 안 되는 내용 | [Field](field.md)의 설명·오류, [Notice](notice.md) |
24
+ | 처음 쓰는 사용자에게 순서대로 기능 소개 | [Tour](tour.md) |
25
+ | 컨트롤의 접근성 이름 자체 | 컨트롤의 `label`(Tooltip은 이름을 대신하지 않는다) |
26
+
27
+ Tooltip과 Popover의 구분: 안에 focus가 들어가야 하면(눌러야 할 것이 있으면) Popover다. Tooltip은 plain
28
+ string만 받고 focus를 받지 않으며, hover·focus가 풀리면 닫힌다. Popover는 click으로 열고 안의 컨트롤로
29
+ focus가 이동하며 `title`·`closeLabel`이 필수다.
30
+
31
+ ## 공개 이름과 import
32
+
33
+ | 이름 | 역할 | Web | Native |
34
+ | --- | --- | --- | --- |
35
+ | `Tooltip` | 기본 | `@hjmds/react`, `/overlays` | — |
36
+
37
+ ## 최소 사용 예
38
+
39
+ ```tsx
40
+ // Web
41
+ import { IconButton } from "@hjmds/react/actions";
42
+ import { Tooltip } from "@hjmds/react/overlays";
43
+
44
+ <Tooltip
45
+ content={t("notifications.tooltip")}
46
+ trigger={
47
+ <IconButton label={t("notifications.open")} onClick={openInbox}>
48
+ <BellIcon />
49
+ </IconButton>
50
+ }
51
+ />
52
+ ```
53
+
54
+ Native: 없음. 계약상 Native는 `unsupported`이고 hover UI를 흉내 내지 않는다.
55
+
56
+ ## 축과 기본값
57
+
58
+ | prop | 값 | 기본값 | 설명 |
59
+ | --- | --- | --- | --- |
60
+ | `content` | 현지화된 비어 있지 않은 문자열 | — | ReactNode·링크·버튼은 받지 않는다 |
61
+ | `placement` | `top` · `bottom` · `start` · `end` | `top` | 공간이 모자라면 renderer가 반대쪽으로 뒤집는다 |
62
+ | `align` | `center` · `start` · `end` | `center` | — |
63
+ | `pointerOpenDelayMs` | ms | 500 | 최근 Tooltip이 닫힌 뒤 300ms 안의 이웃은 바로 열린다 |
64
+ | `focusOpenDelayMs` | ms | 0 | — |
65
+ | `open` / `defaultOpen` | `boolean` | `defaultOpen` `false` | controlled(`open`+`onOpenChange` 필수)·uncontrolled 중 하나 |
66
+ | `onOpenChange` | `(open: boolean, detail: { reason }) => void` | — | `reason`은 `pointer` · `focus` · `pointer-leave` · `blur` · `escape` · `trigger-activation` · `another-tooltip` |
67
+ | `trigger` | `ReactElement`(focus 가능한 단일 요소) | — (필수) | — |
68
+ | `portalContainer` | `HTMLElement` | `document.body` | 말풍선을 붙일 곳 |
69
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | trigger를 감싼 바깥 `span`의 배치(margin·`alignSelf` 등). 말풍선 위치에는 영향이 없다 |
70
+
71
+ ## 배치
72
+
73
+ | 항목 | 값 | 근거 |
74
+ | --- | --- | --- |
75
+ | 크기 | 말풍선 안쪽 여백 사방 `spacing.xs` 8(`tooltipRecipe.surface.padding`; 2026-10-06까지 Web은 좌우 12였다, 1.12.1 이후 미게시), 모서리 `radius.sm` 8, 최대 폭 280(`tooltipRecipe.content.maxWidth`, 화면 폭 − 24를 넘지 않음). trigger는 그 자체로 최소 터치 영역 44를 지켜야 한다(Tooltip은 영역을 넓히지 않는다). 보통 [IconButton](icon-button.md) | `tooltipRecipe.surface`, `.hjm-tooltip__content` |
76
+ | 간격 | trigger와 4(`positioning.sideOffset` = `spacing.xxs`), 뷰포트 가장자리와 최소 12(`positioning.collisionPadding` = `spacing.sm`) | `tooltipRecipe.positioning`, `react/src/overlays.tsx`(`Tooltip`) |
77
+ | 순서·정렬 | trigger 바로 옆에 붙는다. 한 줄에 Tooltip 달린 아이콘 버튼이 여럿이면 하나만 보이고, 300ms 안에 옆으로 옮기면 바로 열린다 | `react/src/overlays.tsx`(`Tooltip`) |
78
+ | 고정·스크롤 | 떠 있는 층(`position: fixed`)이라 문서 흐름에 자리를 만들지 않고 trigger 크기도 바꾸지 않는다. 공간이 모자라면 반대쪽으로 뒤집힌다 | `.hjm-tooltip__content`, `react/src/portal.tsx` |
79
+ | 좁은 폭·큰 글자 | 넘치면 줄바꿈하고 자르지 않는다(`overflow-wrap: anywhere`) | `.hjm-tooltip__content` |
80
+
81
+ ```text
82
+ placement="top"(기본), align="center"
83
+ ┌──────────────────┐
84
+ │ 알림 보기 │ ← 말풍선(최대 280)
85
+ └────────┬─────────┘
86
+ │ 4
87
+ [ 🔔 ] ← trigger(IconButton, 44)
88
+ 뷰포트 가장자리와 12 미만이면 아래로 뒤집힌다.
89
+ ```
90
+
91
+ ## 꼭 지킬 것
92
+
93
+ - `trigger`는 focus 가능한 단일 interactive 요소다. Tooltip은 role·tabIndex·접근성 이름을 만들지 않으므로
94
+ 클릭되는 `span` 같은 것을 trigger로 쓰지 않는다.
95
+ - Tooltip 없이도 컨트롤의 이름과 결과를 이해할 수 있어야 한다. 핵심 정보를 Tooltip에만 두지 않는다.
96
+ - trigger 자리의 배치는 `layoutStyle`로 한다. 말풍선 위치는 `placement`·`align`으로만 정하고 `className`으로 말풍선 모양을 덮지 않는다.
97
+ - `HjmProvider` 안에서는 한 번에 하나만 보인다. Provider 밖이면 이 조정이 없다.
98
+
99
+ ## 함정
100
+
101
+ - 터치에는 hover가 없어서, 터치 탭은 trigger의 원래 click을 실행하면서 Tooltip도 연다(두 번째 탭이나 바깥
102
+ 누름으로 닫힘). 탭이 곧 행동이므로 모바일 Web에서 Tooltip을 미리 읽고 누르는 흐름을 기대하지 않는다.
103
+ - 접근성 이름 구실을 하는 속성(`iframe title` 등)은 Tooltip으로 바꾸지 않는다.
@@ -0,0 +1,124 @@
1
+ # TopBar
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [화면 제목과 마지막 행동](../../screen-chrome.md), [NavigationBar](../../navigation-bar.md), `src/component-recipes.ts`(`topBarRecipe`)
9
+ - 스토리북: `배포/컴포넌트/탐색/상단 탐색 막대`, `배포/컴포넌트/탐색/검색·메뉴가 있는 상단 바`, `배포/컴포넌트/탐색/내비게이션 바`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 화면 맨 위에 붙는 **크롬**에 쓴다. 뒤로가기·닫기(`leading`), 짧은 화면 이름(`title`), 소수의 행동
14
+ (`actions`/`trailing`)을 담는다. 스크롤과 무관한 자리이며 safe area 위쪽 여백을 처리한다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 본문 첫 블록의 제목·설명(스크롤과 함께 움직임) | [Top](top.md) |
21
+ | 최상위 route 전환 탭 | [BottomNavigation](bottom-navigation.md) |
22
+ | 웹 사이드 탐색 | [Sidebar](sidebar.md) |
23
+ | 화면 하단 주 행동 | [BottomCTA](bottom-cta.md) |
24
+ | 행동이 많아 줄에 다 안 들어감 | 하나만 남기고 [Menu](menu.md)로 묶는다 |
25
+
26
+ Top과 TopBar의 구분: TopBar는 고정 크롬(이동·화면 이름·액션), [Top](top.md)은 그 아래 본문의 첫 제목이다.
27
+ 브랜드·탐색 항목·검색/계정이 여러 개 들어가는 사이트 헤더는 TopBar가 아니라 `NavigationBar`다.
28
+
29
+ ## 공개 이름과 import
30
+
31
+ | 이름 | 역할 | Web | Native |
32
+ | --- | --- | --- | --- |
33
+ | `TopBar` | 기본 | `@hjmds/react`, `/top-bar` | `@hjmds/react-native`, `/navigation`, `/top-bar` |
34
+ | `TopBarAction` | 동반(아이콘 위 짧은 라벨 행동) | — | `@hjmds/react-native`, `/navigation`, `/top-bar` |
35
+ | `NavigationBar` | 확장(사이트 탐색 다중 슬롯 조합) | `/navigation-bar` | `/navigation-bar` |
36
+
37
+ ## 최소 사용 예
38
+
39
+ ```tsx
40
+ // Web
41
+ import { IconButton } from "@hjmds/react/actions";
42
+ import { TopBar } from "@hjmds/react/top-bar";
43
+
44
+ <TopBar
45
+ title={t("settings.title")}
46
+ leading={<IconButton label={t("common.back")} onClick={goBack}><BackIcon /></IconButton>}
47
+ />
48
+ ```
49
+
50
+ ```tsx
51
+ // Native
52
+ import { TopBar, TopBarAction } from "@hjmds/react-native/top-bar";
53
+
54
+ <TopBar
55
+ title={t("settings.title")}
56
+ safeAreaTop={insets.top}
57
+ leading={
58
+ <TopBarAction label={t("common.back")} labelVisibility="accessibility-only" onPress={goBack}>
59
+ <BackIcon />
60
+ </TopBarAction>
61
+ }
62
+ actions={<TopBarAction label={t("common.save")} onPress={save}><SaveIcon /></TopBarAction>}
63
+ />
64
+ ```
65
+
66
+ ## 축과 기본값
67
+
68
+ | prop | 값 | 기본값 | 설명 |
69
+ | --- | --- | --- | --- |
70
+ | `centered` | `boolean` | `true` | 남은 제목 영역 안에서의 정렬이며 좌우 열을 같은 폭으로 맞추지 않는다 |
71
+ | `safeAreaTop` | 0 이상 유한수 | 0 | Web은 `env(safe-area-inset-top)`과 큰 값을 쓴다 |
72
+ | `headingLevel` | 1~6 | 1 | Web만. 페이지 구조에 맞춘다 |
73
+ | `onTitleClick`(Web) | `(event: MouseEvent<HTMLButtonElement>) => void` | — | 제목을 버튼으로 만든다 |
74
+ | `onTitlePress`(Native) | `(event: GestureResponderEvent) => void` | — | 제목을 버튼으로 만든다 |
75
+ | `titleLeading` | `ReactNode` | — | 제목 앞 작은 시각 요소 |
76
+ | `leading` / `actions`(별칭 `trailing`) | `ReactNode` | — | 둘 중 한 이름만 쓴다 |
77
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
78
+ | Native `style`·`leadingStyle`·`titleStyle`·`trailingStyle` | `StyleProp` | — | deprecated — `layoutStyle` 또는 `centered`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
79
+ | `TopBarAction` `intent` | `button` · `link` | `button` | `button`은 `onPress: (event) => void`, `link`는 `destination` + `onNavigate?: (destination: LinkDestination) => void \| Promise<void>` 또는 `renderLink` |
80
+ | `TopBarAction` `labelVisibility` | `visible` · `accessibility-only` | `visible` | — |
81
+ | `TopBarAction` `layoutStyle` | `HjmCompositionStyleProp` | — | `style`·`labelStyle`은 deprecated |
82
+ | `NavigationBar` | `label`·`brand`·`children` 필수, `actions` 선택 | — | Web만 `layoutStyle`도 받는다 |
83
+
84
+ ## 배치
85
+
86
+ | 항목 | 값 | 근거 |
87
+ | --- | --- | --- |
88
+ | 크기 | 최소 높이 `control.buttonHeight.large` 52 + 위쪽 안전 영역. Web은 `max(env(safe-area-inset-top), safeAreaTop)`을 위 여백으로, Native는 `safeAreaTop`을 `paddingTop`과 `minHeight`(52 + `safeAreaTop`)에 더한다. 좌우 열 최소 폭 `control.minTouchTarget` 44, Native `TopBarAction` 최소 44×44 | `topBarRecipe.minHeight`·`sideMinWidth`·`action`, `.hjm-top-bar` |
89
+ | 간격 | 좌우 여백 `spacing.md` 16, 슬롯 사이·슬롯 안 행동 사이 `spacing.xs` 8. `TopBarAction` 아이콘–라벨 `spacing.xxs` 4, 좌우 여백 4. 배경 `canvas` | `topBarRecipe.paddingHorizontal`·`gap`·`action` |
90
+ | 순서·정렬 | 세 열: `leading`(뒤로·닫기) · 제목 · `actions`(끝 정렬). `centered`(기본)는 남은 제목 영역 안에서만 가운데 정렬. 행동은 1~2개만 끝 열에, 넘치면 하나만 남기고 [Menu](menu.md)로 묶는다. 주 행동(저장·완료)은 끝 열의 마지막 | `.hjm-top-bar` grid, Native `TopBar` |
91
+ | 고정·스크롤 | 화면 맨 위, 스크롤 영역 **밖**에 둔다. TopBar 자체는 `position: sticky`/고정을 걸지 않으므로 화면 골격이 그 아래에 스크롤 영역을 둬야 고정된다. 바로 아래 본문 첫 블록은 [Top](top.md) | `.hjm-top-bar`(position 없음) |
92
+ | 좁은 폭·큰 글자 | 글자 배율 ≥ `largeTextThreshold` 1.6: Web은 첫 행 leading·행동, 둘째 행 제목(시작 정렬, 위아래 `spacing.xs` 8). Native는 첫 행 leading+제목, 둘째 행 행동(끝 정렬, 줄바꿈 허용) | `.hjm-top-bar[data-large-text]`, `react-native/src/navigation.tsx` `TopBar` |
93
+
94
+ ```text
95
+ 기본 큰 글자 Web 큰 글자 Native
96
+ ┌──────────── safe area ───────────┐ ┌──────────────────────┐ ┌──────────────────────┐
97
+ │ [<] 화면 이름 [⋯][✓]│ │ [<] [⋯][✓] │ │ [<] 화면 이름 │
98
+ └──────────────────────────────────┘ │ 화면 이름 │ │ [⋯][✓] │
99
+ leading 44+ title(1fr) actions 44+ └──────────────────────┘ └──────────────────────┘
100
+ ───────────── 아래부터 스크롤 영역 ─────────────
101
+ ```
102
+
103
+ ## 꼭 지킬 것
104
+
105
+ - `actions`와 `trailing`은 같은 슬롯의 두 이름이다. 둘을 함께 주면 `TypeError`다.
106
+ - `title` 없이 `titleLeading`·`onTitleClick`/`onTitlePress`·`titleAccessibilityLabel`을 주면 `TypeError`다.
107
+ - 이동은 슬롯 안의 Link(Native는 `intent="link"`)로, 제목 클릭은 실제 버튼으로 표현한다.
108
+ - 화면 이름·행동 라벨은 i18n 키로, 아이콘은 제품 소유다. 높이·간격·제목 색은 recipe가 정한다.
109
+ 배치는 `layoutStyle`로만 하고 Native의 deprecated 슬롯 스타일로 덮지 않는다.
110
+ - 큰 글자에서 Native는 슬롯 구조를 다시 만들어 슬롯 안 로컬 상태·포커스가 초기화될 수 있다.
111
+ 유지할 상태는 TopBar 밖에 둔다.
112
+ - `NavigationBar`는 `label`(빈 문자열이면 throw)·`brand`·`children` 필수, `actions` 선택이다.
113
+ 메뉴·검색 동작은 그 안의 [Menu](menu.md)·[SearchField](search-field.md)가 소유한다.
114
+
115
+ ## 플랫폼 차이
116
+
117
+ | 항목 | Web | Native |
118
+ | --- | --- | --- |
119
+ | 루트 | `div`(Dialog 안에서도 landmark를 추가하지 않음), ref·HTML 속성 전달 | `accessibilityRole="toolbar"` `View`, ref 없음 |
120
+ | 제목 접근성 | `h1`~`h6` | `accessibilityRole="header"`, `titleAccessibilityHint`(`onTitlePress` 필수) |
121
+ | 슬롯 스타일 | `className`·`style`, 배치는 `layoutStyle` | 배치는 `layoutStyle`(`style`·`leadingStyle`·`titleStyle`·`trailingStyle`은 deprecated) |
122
+ | 큰 글자 | CSS가 제목을 다음 행으로 | 글자 배율이 `largeTextThreshold` 이상이면 leading+제목 행 아래에 행동 행 |
123
+ | 행동 컴포넌트 | 일반 `IconButton`·`Link` | `TopBarAction`(아이콘 + 마이크로 라벨, 44pt) |
124
+ | `NavigationBar` 배경 | 지원 시 blur + 92% 불투명 | 불투명 surface(blur 없음) |
@@ -0,0 +1,89 @@
1
+ # Top
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Top](../../top.md), `src/top.ts`(`topRecipe`)
9
+ - 스토리북: `배포/컴포넌트/레이아웃/화면 제목과 설명`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 화면 **본문의 첫 블록**에 쓴다. 사용자가 "이 화면이 무엇을 묻는지" 읽는 제목과 보조 문장이며, 스크롤과
14
+ 함께 올라간다. 화면당 하나다. 시트·모달 안 화면의 첫 제목에는 `size: "medium"`을 쓴다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 화면 위에 고정된 뒤로가기·화면 이름·행동 | [TopBar](top-bar.md) |
21
+ | 본문 중간의 묶음 제목 | [Section](section.md) |
22
+ | 제목 글자만 필요 | [Heading](heading.md) |
23
+ | 사이트 전체 탐색 바 | [TopBar](top-bar.md)의 `NavigationBar` |
24
+
25
+ Top과 TopBar의 구분: TopBar는 화면에 붙어 있는 크롬(safe area, 뒤로가기, 액션)이고 Top은 그 아래에서 스크롤되는
26
+ 본문 제목이다. 한 화면에 둘을 같이 써도 된다. 이때 TopBar `title`을 생략하거나 Top과 다른 짧은 이름으로 둔다.
27
+
28
+ ## 공개 이름과 import
29
+
30
+ | 이름 | 역할 | Web | Native |
31
+ | --- | --- | --- | --- |
32
+ | `Top` | 기본 | `@hjmds/react`, `/top` | `@hjmds/react-native`, `/top` |
33
+
34
+ ## 최소 사용 예
35
+
36
+ ```tsx
37
+ // Web
38
+ import { Link } from "@hjmds/react/actions";
39
+ import { Top } from "@hjmds/react/top";
40
+
41
+ <Top
42
+ descriptor={{ title: t("signup.title"), description: t("signup.description") }}
43
+ trailing={<Link href="/help">{t("common.help")}</Link>}
44
+ />
45
+ ```
46
+
47
+ ```tsx
48
+ // Native
49
+ import { Top } from "@hjmds/react-native/top";
50
+
51
+ <Top descriptor={{ eyebrow: t("signup.step", { n: 1 }), title: t("signup.title") }} />
52
+ ```
53
+
54
+ ## 축과 기본값
55
+
56
+ | prop | 값 | 기본값 | 설명 |
57
+ | --- | --- | --- | --- |
58
+ | `descriptor` | `{ title: string, eyebrow?: string, description?: string, size?, headingLevel? }` | — | 값을 주면 공백만 있는 문자열은 `TypeError` |
59
+ | `descriptor.size` | `large` · `medium` | `large` | `large`는 화면 첫 제목, `medium`은 시트·모달 안. 세 번째 크기는 없다 |
60
+ | `descriptor.headingLevel` | `1` · `2` · `3` | `1` | 한 라우트가 여러 화면을 담을 때만 낮춘다(Web만 반영) |
61
+ | `trailing` | `ReactNode`(요소 하나) | — | 제목 줄을 같이 쓰는 보조 행동. 공간이 모자라거나 큰 글자면 아래로 내려간다 |
62
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
63
+ | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
64
+
65
+ ## 배치
66
+
67
+ | 항목 | 값 | 근거 |
68
+ | --- | --- | --- |
69
+ | 크기 | 좌우 여백 없음. 위·아래 여백 `large` `spacing.xl` 24 / `spacing.md` 16, `medium` `spacing.md` 16 / `spacing.sm` 12 | `topRecipe.sizes`, `.hjm-top` |
70
+ | 간격 | Web은 eyebrow 아래 `spacing.xxs` 4, description 위 `spacing.xs` 8. Native는 세 줄 사이 `spacing.xs` 8. 제목–`trailing` `spacing.xs` 8. 아래 본문 첫 블록과는 아래 여백으로 떨어진다 | `topRecipe.eyebrow`·`description`·`gap`, `.hjm-top__row` |
71
+ | 순서·정렬 | eyebrow → 제목 줄 → description. 제목 줄은 제목(남은 폭) + `trailing`(끝 정렬) | `.hjm-top__row`, `react-native/src/top.tsx` |
72
+ | 고정·스크롤 | 스크롤 영역의 맨 처음, [TopBar](top-bar.md) 바로 아래. 화면 페이지 여백(`layout.pagePadding`, 보통 `regular` 20) 안에 넣는다 | `foundations.ts` `layout.pagePadding` |
73
+ | 좁은 폭·큰 글자 | Web은 폭이 모자라면 줄바꿈으로, Native는 글자 배율 1.6 이상이면 `trailing`을 제목 아래로 내린다 | `.hjm-top__row`(flex-wrap), `react-native/src/top.tsx` |
74
+
75
+ ## 꼭 지킬 것
76
+
77
+ - 문구는 모두 i18n 키로 만든 문자열이다. `description`은 잘리지 않고 줄바꿈되므로 말줄임을 덧대지 않는다.
78
+ - `eyebrow`에는 제목을 한정하는 짧은 말(카테고리·단계)만 넣고 제목에 없는 새 정보를 넣지 않는다.
79
+ - 주 행동을 `trailing`에 두지 않는다. 화면의 주 행동은 [BottomCTA](bottom-cta.md)나 본문 [Button](button.md)이다.
80
+ - 상태가 없다. 제목을 바꾸려면 다른 문자열을 넘긴다.
81
+ - 배치는 `layoutStyle`로 한다(Web `className`도 받는다). Native `style`은 deprecated다. 글자 크기·색은 recipe가 정하므로 덮지 않는다.
82
+
83
+ ## 플랫폼 차이
84
+
85
+ | 항목 | Web | Native |
86
+ | --- | --- | --- |
87
+ | 제목 요소 | 실제 `h1`~`h3`(`headingLevel`) | `accessibilityRole="header"`(단계 없음, `headingLevel` 미사용) |
88
+ | `trailing` 내림 기준 | CSS 줄바꿈 | 글자 배율 1.6 이상이면 세로로 쌓음 |
89
+ | ref | `forwardRef`(`header` 요소) | 없음 |