@hjmds/design-contracts 1.12.1 → 1.13.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 (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 +25 -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 +17 -0
  16. package/dist/component-recipes.d.ts.map +1 -1
  17. package/dist/component-recipes.js +5 -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 +376 -0
  67. package/docs/sheet.md +12 -0
  68. package/docs/splitter.md +8 -2
  69. package/docs/theming.md +36 -29
  70. package/docs/toggle-group.md +7 -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 +104 -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 +215 -0
  169. package/docs/usage/components/section.md +111 -0
  170. package/docs/usage/components/segmented-control.md +138 -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 +151 -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 +274 -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,93 @@
1
+ # Menubar
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Menubar](../../menubar.md), `src/menubar.ts`(`menubarRecipe`)
9
+ - 스토리북: `배포/컴포넌트/탐색/메뉴 막대`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 데스크톱 Web 앱 상단에 항상 같은 자리에 있는 가로 메뉴 막대(파일·편집·보기)에 쓴다.
14
+ 막대 전체가 tab stop 하나이고, 한 번에 메뉴 하나만 열리며, 열린 상태에서 ←/→로 옆 메뉴로 넘어간다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 버튼 하나에 붙는 action 목록 | [Menu](menu.md) |
21
+ | 포인터 위치에서 여는 메뉴 | [ContextMenu](context-menu.md) |
22
+ | 화면(페이지)을 바꾸는 상단 탐색 | [Tabs](tabs.md), [TopBar](top-bar.md) |
23
+ | 모바일·Native 앱 | 없음. 앱 바는 OS 소유이고 Native renderer가 없다 |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `Menubar` | 기본 | `@hjmds/react`, `/menubar` | — |
30
+
31
+ ## 최소 사용 예
32
+
33
+ ```tsx
34
+ // Web
35
+ import { Menubar } from "@hjmds/react/menubar";
36
+
37
+ <Menubar
38
+ descriptor={{
39
+ accessibilityLabel: t("editor.menubar"),
40
+ menus: [
41
+ { id: "file", label: t("menu.file"), items: [
42
+ { id: "save", label: t("menu.save"), textValue: t("menu.save"), shortcut: "⌘S" },
43
+ ] },
44
+ { id: "edit", label: t("menu.edit"), items: [
45
+ { id: "undo", label: t("menu.undo"), textValue: t("menu.undo") },
46
+ ] },
47
+ ],
48
+ }}
49
+ onAction={(itemId, menuId) => run(menuId, itemId)}
50
+ />
51
+ ```
52
+
53
+ Native 예는 없다(renderer 없음).
54
+
55
+ ## 축과 기본값
56
+
57
+ | prop | 값 | 기본값 | 설명 |
58
+ | --- | --- | --- | --- |
59
+ | `descriptor` | `{ accessibilityLabel: string; menus: readonly { id, label: string, items, disabled? }[] }` | 필수 | 타입 `MenubarDescriptor`(`@hjmds/design-contracts/components/menubar`) |
60
+ | 항목 | `{ id, label: string, textValue: string, description?, shortcut?, tone?, disabled? }`(`MenuItemDescriptor`) | — | `textValue` 필수. `shortcut`은 표시 문구일 뿐 키를 등록하지 않는다 |
61
+ | `onAction` | `(id: Key, menuId: MenuKey) => void` | 필수 | 항목 id가 먼저, 메뉴 id가 두 번째 |
62
+ | `openMenuId` · `onOpenMenuIdChange` | `MenuKey \| null` · `(id: MenuKey \| null) => void` | — | 제어형 열린 메뉴 |
63
+ | `defaultOpenMenuId` | `MenuKey \| null` | `null`(닫힘) | 비제어형 열린 메뉴 |
64
+ | 메뉴 `disabled` | `boolean` | `false` | 키보드 이동에서 건너뛴다 |
65
+ | `className` · `layoutStyle` | 문자열 · 배치 전용 style 객체 | — | 막대 루트 배치. 색·높이 변수는 덮이지 않는다 |
66
+
67
+ ## 배치
68
+
69
+ | 항목 | 값 | 근거 |
70
+ | --- | --- | --- |
71
+ | 크기 | 막대 높이 최소 44(`control.minTouchTarget`). 라벨 높이 44 이상, radius `radius.sm` 8. 패널 폭 `13.75rem`~`min(24rem, 90vw)`. 항목 높이 44 이상 | `menubarRecipe`, `.hjm-menubar*` |
72
+ | 간격 | 막대 좌우 `spacing.xs` 8, 라벨 사이 `spacing.xxs` 4, 라벨 좌우 `spacing.sm` 12. 패널 안쪽 4, radius `radius.md` 12. 항목 위아래 8 · 좌우 12, 단축키와 12 | `menubarRecipe`, `.hjm-menubar__panel`·`__item` |
73
+ | 순서·정렬 | 메뉴는 데스크톱 관례(파일 → 편집 → 보기 → … → 도움말). 패널은 해당 라벨 바로 아래 시작 쪽에 붙는다. 단축키 문구는 항목 끝(`space-between`) | `.hjm-menubar__panel` |
74
+ | 고정·스크롤 | 화면 맨 위(앱 제목·TopBar 아래나 같은 줄 시작 쪽). sticky 여부는 놓는 레이아웃이 정한다. 패널은 Menu와 같은 portal·fixed 배치(`layer.dropdown` 400)로 공간이 부족하면 뒤집고 화면 안으로 민다. 모달 안에서는 소유 모달 + 1이다 | `useAnchoredPopup`, `.hjm-menubar__panel` |
75
+ | 좁은 폭·큰 글자 | 폭이 모자라면 라벨이 다음 줄로 감긴다(`flex-wrap`). 모바일 폭은 대상이 아니다 | `.hjm-menubar` |
76
+
77
+ ```text
78
+ ┌ 화면 (Web, 데스크톱) ─────────────────────────────────────┐
79
+ │ [파일] [편집] [보기] ← 막대 ≥44 │
80
+ │ └┬───────────────────┐ │
81
+ │ │ 저장 ⌘S │ ← 패널: 라벨 바로 아래, 시작 정렬 │
82
+ │ │ 다른 이름… ⇧⌘S │ 13.75rem ~ 24rem │
83
+ │ └───────────────────┘ │
84
+ │ 본문(스크롤) │
85
+ └───────────────────────────────────────────────────────────┘
86
+ ```
87
+
88
+ ## 꼭 지킬 것
89
+
90
+ - `descriptor.accessibilityLabel`, 각 메뉴 `id`·`label`은 비어 있으면 안 되고, 메뉴마다 항목이 하나 이상 있어야 한다. 어기면 던진다.
91
+ - 항목 `textValue`는 계약 타입상 필수다. 지역화한 문구를 그대로 넣는다.
92
+ - 메뉴 구성과 단축키 문구는 제품 소유다. 단축키 자체의 키 바인딩은 Menubar가 등록하지 않으므로 제품이 따로 연결한다.
93
+ - `className`·`layoutStyle`은 배치에만 쓴다. 색·높이를 덮지 않는다.
@@ -0,0 +1,124 @@
1
+ # MessageComposer
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.12.1 이후)
7
+ - 검토일: 2026-10-06
8
+ - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
+ - 스토리북: `배포/구성/입력과 작성/메시지 작성`, `배포/구성/입력과 작성/댓글 작성`, `배포/화면/소통/채팅`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 채팅·DM·댓글 입력창에 쓴다. 1~5줄로 자라는 TextArea와 전송 버튼, 선택적으로 사진 첨부 미리보기,
14
+ 답장 대상 표시, 첨부 버튼을 하나로 조합한다. 값은 완전 제어형이고 HJM은 초안을 지우지 않는다.
15
+
16
+ 용도별 조합은 [댓글 작성](../compositions/purpose-input-comment.md)과 [메시지 작성](../compositions/purpose-input-message.md)을 먼저 선택한다. 입력·전송 엔진은 같고 답글·첨부·문구만 목적에 맞게 연결한다.
17
+
18
+ ## 쓰지 않을 때
19
+
20
+ | 상황 | 대신 쓸 것 |
21
+ | --- | --- |
22
+ | 일반 폼의 여러 줄 입력 | [TextArea](text-area.md) |
23
+ | @멘션 자동완성 입력 | [Mentions](mentions.md) |
24
+ | 채팅 화면 전체 틀(타임라인 + 작성창 자리) | [ChatScreen](chat-screen.md)의 `composer` 슬롯에 이 컴포넌트를 넣는다 |
25
+ | 댓글 화면 전체 | [CommentThreadScreen](comment-thread-screen.md)의 `composer` 슬롯 |
26
+ | 키보드 위에 붙이기 | [KeyboardDock](keyboard-dock.md) 등 keyboard 계열과 합성 |
27
+
28
+ ## 공개 이름과 import
29
+
30
+ | 이름 | 역할 | Web | Native |
31
+ | --- | --- | --- | --- |
32
+ | `MessageComposer` | supplemental, 루트 barrel에 없음 | `/screens` | `/screens` |
33
+
34
+ `@hjmds/react/screens`, `@hjmds/react-native/screens`로만 import한다. 추가 optional peer는 없다.
35
+
36
+ ## 최소 사용 예
37
+
38
+ ```tsx
39
+ // Web
40
+ import { MessageComposer } from "@hjmds/react/screens";
41
+
42
+ <MessageComposer
43
+ label={t("chat.input")}
44
+ sendLabel={t("chat.send")}
45
+ value={draft}
46
+ onValueChange={setDraft}
47
+ pending={sending}
48
+ onSend={async (text) => { const ok = await send(text, photos); if (ok) { setDraft(""); setPhotos([]); } }}
49
+ />
50
+ ```
51
+
52
+ ```tsx
53
+ // Native
54
+ import { MessageComposer } from "@hjmds/react-native/screens";
55
+ import { Image } from "react-native";
56
+
57
+ <MessageComposer
58
+ label={t("chat.input")}
59
+ sendLabel={t("chat.send")}
60
+ value={draft}
61
+ onValueChange={setDraft}
62
+ pending={sending}
63
+ sendIcon={<ArrowUpIcon />}
64
+ sendPresentation="circle"
65
+ attachmentAction={{ label: t("chat.attachPhoto"), icon: <PhotoIcon />, onPress: openPhotoSource }}
66
+ attachments={photos.map((p) => ({ id: p.id, preview: <Image source={{ uri: p.uri }} />, removeLabel: t("chat.removePhoto") }))}
67
+ onRemoveAttachment={removePhoto}
68
+ onSend={sendDraft}
69
+ />
70
+ ```
71
+
72
+ ## 축과 기본값
73
+
74
+ | prop | 값 | 기본값 | 설명 |
75
+ | --- | --- | --- | --- |
76
+ | `value` | `string` | 필수 | 완전 제어형. HJM은 초안을 지우지 않는다 |
77
+ | `label` | `string` | 필수 | placeholder 겸 접근성 이름 |
78
+ | `sendLabel` | `string` | 필수 | 전송 버튼 문구·접근성 이름 |
79
+ | `onValueChange` | `(value: string) => void` | 필수 | 입력 변경 |
80
+ | `onSend` | `(value: string) => void` | 필수 | 현재 문자열만 넘긴다 |
81
+ | `disabled` · `pending` | `boolean` | `false` | 둘 중 하나면 입력·첨부·삭제·답장 취소가 모두 잠긴다. `pending`은 전송 버튼 loading |
82
+ | `sendDisabled` | `boolean` | `false` | 전송만 막고 편집은 열어 둔다 |
83
+ | `additionalContent` | `boolean` | `false` | 글·첨부 밖에 보낼 내용이 있으면 `true`. 전송 가능 판정에 더한다 |
84
+ | `maxLength` | `number` | 없음 | 입력 최대 글자 수 |
85
+ | `leadingAction` | `ReactNode` | 없음 | 입력 테두리 **안** 글자 앞 시작 쪽 도구 버튼. 자라는 입력의 세로 가운데에 맞춘다(Web `.hjm-field__leading` `align-self: center`, Native `alignSelf: "center"`). 행동이라 흐린 affix 색을 쓰지 않는다 |
86
+ | `inputRef` | Web `Ref<HTMLTextAreaElement>` · Native `Ref<TextInput>` | 없음 | 답글 선택 뒤 포커스 유지 |
87
+ | `context` | `ReactNode` | 없음 | 답장 대상 위 임의 슬롯 |
88
+ | `replyTo` | `{ author, excerpt, cancelLabel, onCancel() }` | 없음 | 입력창 위 답장 대상과 취소 버튼 |
89
+ | `sendIcon` | `ReactNode` | 없음 | 주면 아이콘 모드: 빈 입력에서는 입력창 안에 `attachmentAction`, 내용이 있거나 `pending`이면 전송 아이콘. 없으면 입력창 옆 텍스트 `Button` |
90
+ | `sendPresentation` | `"inline"` · `"circle"` | `"inline"` | `circle`은 primary 원형 `IconButton size="small"`(댓글·DM 레퍼런스). `sendIcon`이 있을 때만 의미가 있다 |
91
+ | `attachmentAction` | `{ label, icon, onPress(), disabled? }` | 없음 | 첨부 버튼(ghost `IconButton`) |
92
+ | `attachments` | `readonly { id, removeLabel, preview }[]` | `[]` | 첨부 미리보기. 있으면 `onRemoveAttachment` 필수 |
93
+ | `onRemoveAttachment` | `(id: string) => void` | 없음 | 첨부 제거 |
94
+ | Web `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 작성창 루트 배치(margin·width·flex 등). 미게시(1.12.1 이후) |
95
+
96
+ 전송 가능 조건: `disabled`·`pending`·`sendDisabled`가 아니고, 글(공백 제외)·첨부·`additionalContent` 중 하나가 있을 때(`canSubmitMessage`).
97
+
98
+ ## 배치
99
+
100
+ | 항목 | 값 | 근거 |
101
+ | --- | --- | --- |
102
+ | 크기 | 입력 `composerMinLines` 1 ~ `composerMaxLines` 5줄(넘으면 입력 안에서 스크롤), `shape="large"`; 첨부 썸네일 `attachmentSize` 80(radius `radius.md`); 제거 버튼은 `IconButton size="small"` 36 + 사방 hit 4(실제 타깃 44), 그 안 보이는 원 `attachmentRemoveSize` 24; circle 전송은 `IconButton size="small"` 36 | `screen-patterns.ts`, `icon-button-recipe.ts` `sizes.small`, Web `styles.css`, Native `MessageComposer` |
103
+ | 간격 | 세로 묶음(context·답장 대상·첨부·입력 줄) 사이와 입력–텍스트 전송 버튼 사이 `screenPatternRecipe.itemGap`(`spacing.sm` 12, 두 플랫폼. 2026-10-06까지 Web은 8, 1.12.1 이후 미게시); 답장 대상 줄·첨부 사이 `spacing.sm` 12; circle 전송은 입력 글자와 `spacing.sm` 12, 오른쪽 끝 8 | `screenPatternRecipe.itemGap`, Web `.hjm-message-composer`·`__row`(`--hjm-message-composer-gap`), Native `Stack gap="sm"`·`itemGap` |
104
+ | 순서·정렬 | `context` → 답장 대상(작성자·발췌 → 취소) → 첨부 썸네일(→ 첨부 버튼) → 입력·전송 한 줄, 전송은 입력 아래쪽(`flex-end`)에 맞춘다. 입력 줄 안은 `leadingAction` → 글자 → (아이콘 모드) 전송/첨부 | 렌더 순서, Web `forms.tsx` `hjm-field__leading` |
105
+ | 고정·스크롤 | 자체 고정 없음: 화면 composer 슬롯(footer)에 넣는다; 첨부는 가로 스크롤; 키보드 adapter는 host 하나만 | ChatScreen·CommentThreadScreen `composer` |
106
+ | 좁은 폭·큰 글자 | 입력이 남은 폭을 채우고(`flex: 1`, 최소 폭 0) 전송 버튼은 줄지 않는다; 큰 글자에서도 5줄 상한 뒤 입력 안 스크롤로 기록을 가리지 않는다 | Web `.hjm-message-composer__row`, Native `layoutStyle={{ flex: 1 }}` |
107
+
108
+ ## 꼭 지킬 것
109
+
110
+ - 문구(`label`은 placeholder 겸 접근성 이름, `sendLabel`, `removeLabel`, `cancelLabel`)는 모두 i18n 키로 넣는다.
111
+ - `onSend(value)`는 현재 문자열만 넘긴다. 첨부 목록은 제품 상태에서 읽고, **서버 성공 뒤에만** 제품이 글·첨부·답장 대상을 지운다.
112
+ - 첨부 `id`는 비어 있지 않고 유일해야 하며 `removeLabel`이 필요하다. 첨부가 있으면 `onRemoveAttachment`가 필수다. 어기면 던진다.
113
+ - 사진 권한·선택기·업로드·개수 제한은 제품 소유다. 출처 선택 UI는 [PhotoSourceSheet](photo-source-sheet.md)를 쓴다.
114
+ - Enter는 줄바꿈이다(IME 조합 보호). Enter 전송을 덧붙이지 않는다.
115
+ - Web 배치는 `layoutStyle`로 한다. Native는 스타일 통로가 없어 감싸는 레이아웃에서 배치한다.
116
+
117
+ ## 플랫폼 차이
118
+
119
+ | 항목 | Web | Native |
120
+ | --- | --- | --- |
121
+ | `inputRef` 타입 | `HTMLTextAreaElement` | `TextInput` |
122
+ | 첨부 목록 | CSS 클래스 `hjm-message-composer__attachments` | 가로 ScrollView |
123
+ | 이벤트 이름 | `attachmentAction.onPress`(내부에서 onClick으로 연결) | `onPress` |
124
+ | 배치 prop | `layoutStyle`(루트) | 없음 |
@@ -0,0 +1,113 @@
1
+ # ModerationScreen
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.12.1 이후)
7
+ - 검토일: 2026-10-06
8
+ - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
+ - 스토리북: `배포/화면/소통/신고와 차단`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 게시물·댓글·사용자 **신고** 화면에 쓴다. 사유 선택, 사유를 고르기 전까지 막힌 신고 버튼,
14
+ 선택적인 차단 버튼과 그 확인 대화상자를 [ScreenLayout](screen-layout.md) 위에 조합한다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 차단·삭제 확인만 필요 | [AlertDialog](alert-dialog.md) |
21
+ | 신고 진입 메뉴(⋯) | [Menu](menu.md) |
22
+ | 자유 입력 위주의 문의 폼 | [Form](form.md), [EditorScreen](editor-screen.md) |
23
+
24
+ ## 공개 이름과 import
25
+
26
+ | 이름 | 역할 | Web | Native |
27
+ | --- | --- | --- | --- |
28
+ | `ModerationScreen` | supplemental, 루트 barrel에 없음 | `/screen-flows` | `/screen-flows` |
29
+
30
+ `@hjmds/react/screen-flows`, `@hjmds/react-native/screen-flows`로만 import한다. 추가 optional peer는 없다.
31
+
32
+ ## 최소 사용 예
33
+
34
+ ```tsx
35
+ // Web
36
+ import { ModerationScreen } from "@hjmds/react/screen-flows";
37
+
38
+ <ModerationScreen
39
+ title={t("report.title")}
40
+ reasonLabel={t("report.reason")}
41
+ reasons={[
42
+ { value: "spam", label: t("report.reason.spam") },
43
+ { value: "abuse", label: t("report.reason.abuse") },
44
+ ]}
45
+ reason={reason}
46
+ onReasonChange={setReason}
47
+ submit={{ label: t("report.submit"), onAction: submitReport, pending: reporting }}
48
+ block={{
49
+ action: { label: t("report.block"), onAction: () => {} },
50
+ confirmation: {
51
+ mode: "confirm", tone: "danger",
52
+ title: t("block.confirm.title"), description: t("block.confirm.body"),
53
+ confirmLabel: t("block.confirm"), cancelLabel: t("common.cancel"),
54
+ onConfirm: blockUser, fallbackErrorMessage: t("block.error"),
55
+ },
56
+ }}
57
+ />
58
+ ```
59
+
60
+ ```tsx
61
+ // Native — props는 Web과 같다
62
+ import { ModerationScreen } from "@hjmds/react-native/screen-flows";
63
+
64
+ <ModerationScreen
65
+ title={t("report.title")}
66
+ reasonLabel={t("report.reason")}
67
+ reasons={[
68
+ { value: "spam", label: t("report.reason.spam") },
69
+ { value: "abuse", label: t("report.reason.abuse") },
70
+ ]}
71
+ reason={reason}
72
+ onReasonChange={setReason}
73
+ submit={{ label: t("report.submit"), onAction: submitReport, pending: reporting }}
74
+ block={{
75
+ action: { label: t("report.block"), onAction: () => {} },
76
+ confirmation: {
77
+ mode: "confirm", tone: "danger",
78
+ title: t("block.confirm.title"), description: t("block.confirm.body"),
79
+ confirmLabel: t("block.confirm"), cancelLabel: t("common.cancel"),
80
+ onConfirm: blockUser, fallbackErrorMessage: t("block.error"),
81
+ },
82
+ }}
83
+ />
84
+ ```
85
+
86
+ ### 제품이 공급할 것
87
+
88
+ | 슬롯·prop | 내용 |
89
+ | --- | --- |
90
+ | `reasons`, `reasonLabel` | 지역화한 사유 목록과 그룹 이름. 기본으로 세로 RadioGroup을 그린다 |
91
+ | `reason`, `onReasonChange` | 선택된 사유(제어형, 없으면 `null`) |
92
+ | `reasonPicker` | 단계형 사유 목록 등 다른 선택 UI. 주면 기본 RadioGroup 대신 그린다 |
93
+ | `children` | 상세 설명 입력 등 추가 내용 |
94
+ | `submit` | 신고 행동 `{ label, onAction, disabled?, pending? }` |
95
+ | `block` | 차단 버튼 행동과 `AlertDialog` 확인 요청(`mode: "confirm"`) |
96
+ | ScreenLayout props | `title`, `description`, `header`, `leading`, `actions`, `notice`, `state`, `stateAction` 등(`footer` 제외) |
97
+
98
+ ## 배치
99
+
100
+ | 항목 | 값 | 근거 |
101
+ | --- | --- | --- |
102
+ | 크기 | ScreenLayout 폭(최대 720); 사유는 세로 `RadioGroup`, 신고·차단은 `Button` 기본 크기 | `ModerationScreen`, `screen-flows.tsx` `Action` |
103
+ | 간격 | 화면 padding `spacing.md` 16; 본문 사유 선택–`children` `spacing.lg` 20; footer 신고–차단 `spacing.sm` 12 | Web·Native `ModerationScreen` `Stack gap="lg"`·`gap="sm"` |
104
+ | 순서·정렬 | 헤더(제목) → 본문(사유 → 추가 설명 `children`) → footer(신고 primary → 차단 ghost) | 렌더 순서 |
105
+ | 고정·스크롤 | 헤더·footer 고정, 본문 화면 스크롤; 차단 확인은 AlertDialog 오버레이 | `ScreenLayout`, `AlertDialog` |
106
+ | 좁은 폭·큰 글자 | 사유 문구는 줄바꿈되고 항목이 세로로 늘어난다; footer 버튼은 세로로 쌓여 좁은 폭에서도 나란히 줄지 않는다 | `RadioGroup orientation="vertical"`, `Stack` |
107
+
108
+ ## 꼭 지킬 것
109
+
110
+ - 신고 버튼은 `reason`이 `reasons` 안의 값이 아니거나 `state`가 `ready`가 아니면 자동으로 막힌다. `reasonPicker`를 쓸 때도 `reasons`에 유효한 값을 넣어야 버튼이 열린다.
111
+ - 차단 버튼의 `block.action.onAction`은 호출되지 않는다. 버튼은 확인 대화상자만 열고, 실제 차단은 `confirmation.onConfirm`에서 한다.
112
+ - 서버 신고·권한·차단 mutation, 완료 후 이동은 제품 소유다. 사유 목록과 정책 문구도 제품이 정한다.
113
+ - 확인 대화상자의 성공 후 닫힘·실패 문구는 [AlertDialog](alert-dialog.md) 계약을 따른다.
@@ -0,0 +1,106 @@
1
+ # Notice
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: `src/component-recipes.ts`(`noticeRecipe`), BottomInfo와의 경계 [BottomInfo](../../bottom-info.md), Toast와의 경계 [Toast](../../toast.md)
9
+ - 스토리북: `배포/컴포넌트/상태와 알림/안내 메시지`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 화면 흐름 안 **제자리에 남아 있는** 상태 알림에 쓴다. 저장 실패, 오프라인, 권한 제한,
14
+ 결제 수단 만료처럼 사용자가 읽고 필요하면 행동(`action`)할 때까지 사라지면 안 되는 내용이 여기에 속한다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 무시해도 안전하고 잠시 뒤 사라지는 짧은 결과 알림(“저장했어요”) | [Toast](toast.md) |
21
+ | 반드시 응답해야 진행되는 확인 | [AlertDialog](alert-dialog.md) |
22
+ | 주 행동 아래 늘 있는 조건 안내(약관 동의 등) | [BottomInfo](bottom-info.md) |
23
+ | 화면 전체가 빈 상태·오류 상태 | [EmptyState](empty-state.md), [Result](result.md) |
24
+ | 입력 하나의 오류 | [Field](field.md)의 `error` |
25
+
26
+ 선택 기준: 사라져도 되는가(Toast) / 남아야 하는가(Notice) / 응답이 필수인가(AlertDialog).
27
+
28
+ ## 공개 이름과 import
29
+
30
+ | 이름 | 역할 | Web | Native |
31
+ | --- | --- | --- | --- |
32
+ | `Notice` | 기본 | `@hjmds/react`, `/feedback` | `@hjmds/react-native`, `/feedback` |
33
+
34
+ ## 최소 사용 예
35
+
36
+ ```tsx
37
+ // Web
38
+ import { Button } from "@hjmds/react/actions";
39
+ import { Notice } from "@hjmds/react/feedback";
40
+
41
+ <Notice
42
+ tone="warning"
43
+ title={t("sync.offline.title")}
44
+ description={t("sync.offline.body")}
45
+ action={<Button tone="secondary" size="small" onClick={retry}>{t("common.retry")}</Button>}
46
+ />
47
+ ```
48
+
49
+ ```tsx
50
+ // Native
51
+ import { Button } from "@hjmds/react-native/actions";
52
+ import { Notice } from "@hjmds/react-native/feedback";
53
+
54
+ <Notice
55
+ tone="warning"
56
+ announcement="polite"
57
+ title={t("sync.offline.title")}
58
+ description={t("sync.offline.body")}
59
+ action={<Button tone="secondary" size="small" onPress={retry}>{t("common.retry")}</Button>}
60
+ />
61
+ ```
62
+
63
+ ## 축과 기본값
64
+
65
+ | prop | 값 | 기본값 | 설명 |
66
+ | --- | --- | --- | --- |
67
+ | `title` | Web `ReactNode` · Native `string` | 필수 | 현지화 |
68
+ | `description` | Web `ReactNode` · Native `string` | — | 현지화 |
69
+ | `tone` | `info` · `success` · `warning` · `attention` · `danger` | `info` | 도메인 상태는 제품 어댑터에서 tone으로 매핑한다 |
70
+ | `action` | 노드 | — | 보조 행동 하나(`Button` `tone="secondary"` `size="small"`) |
71
+ | `icon` | 노드 | — | 장식이다(Web은 `aria-hidden`) |
72
+ | `renderIcon`(Native) | `(props: { tone, color: string, size: number }) => ReactNode` | — | tone 색과 recipe 크기를 받아 그린다 |
73
+ | `announcement`(Native) | `none` · `polite` · `assertive` | `none` | 새로 나타나는 상태만 발표한다(아래 플랫폼 차이) |
74
+ | `layoutStyle` | 배치 전용 style 객체 | — | 바깥 여백·폭만 |
75
+
76
+ ## 배치
77
+
78
+ | 항목 | 값 | 근거 |
79
+ | --- | --- | --- |
80
+ | 크기 | 본문 폭을 꽉 채운다. 아이콘 20(`glyph.sm`), 테두리 1(`stroke.default`), radius `radius.md` 12 | `noticeRecipe` |
81
+ | 간격 | 안쪽 `spacing.md` 16, 아이콘·문구·행동 사이 `spacing.sm` 12, 제목·설명 사이 `spacing.xxs` 4. 이웃 블록과는 `layout.contentGap` 16 | `noticeRecipe`, `.hjm-notice*` |
82
+ | 순서·정렬 | 아이콘 — 제목·설명 — 행동. 행동은 하나, `Button` `size="small"` `tone="secondary"`(또는 `ghost`), `primary` 금지. Web은 행동이 같은 줄 끝, Native는 행동이 문구 줄 **아래** | `.hjm-notice__action`, `react-native/src/feedback.tsx` |
83
+ | 고정·스크롤 | 본문 흐름 안. 먼저 읽어야 하는 경고·입력 오류는 영향을 받는 내용 바로 위(화면 상태는 제목 아래, 폼 검증은 제출 버튼 위). 제출 후 성공·실패 결과는 구성 지침이 지정한 행동 인접 위치에 둔다. 떠 있거나 고정되지 않고 함께 스크롤되며 여러 개 쌓지 않는다 | — |
84
+ | 좁은 폭·큰 글자 | Web은 문구 칸이 60% 아래로 줄면 행동이 다음 줄로 감긴다. 제목·설명은 줄바꿈된다 | `.hjm-notice__content`(`flex: 1 1 60%`) |
85
+
86
+ ## 꼭 지킬 것
87
+
88
+ - 제목·설명·행동 문구는 i18n 키로 넣는다. 아이콘은 제품 소유 자산이고 색은 tone이 정한다.
89
+ - 배치는 `layoutStyle`로만 한다. 색·padding·radius를 `style`/`className`으로 덮지 않는다. Native `style`은 deprecated(개발 모드 경고, 다음 major 제거)다.
90
+ - 같은 화면에 같은 내용의 Notice와 Toast를 동시에 띄우지 않는다.
91
+
92
+ ## 플랫폼 차이
93
+
94
+ | 항목 | Web | Native |
95
+ | --- | --- | --- |
96
+ | 발표 | 항상 live region. `danger`는 `role="alert"`+assertive, 나머지는 `role="status"`+polite | `announcement`: `none`(기본) · `polite` · `assertive`. 지정해야 발표한다 |
97
+ | title/description 타입 | `ReactNode` | `string` |
98
+ | 아이콘 렌더 함수 | 없음 | `renderIcon` |
99
+ | 배치 | `className`, `layoutStyle`(HTML `style`도 받음) | `layoutStyle`(`style`은 deprecated) |
100
+
101
+ ## 함정
102
+
103
+ - Native는 기본이 `announcement="none"`이라, 새로 생긴 오류 Notice를 화면에 넣기만 하면 스크린 리더가 알리지 않는다. 새로 나타나는 상태에는 `polite`/`assertive`를 준다.
104
+ - Web은 반대로 항상 발표되므로, 화면 진입 때부터 늘 있는 안내를 Notice로 두면 매번 읽힌다. 그런 조건 안내는 BottomInfo다.
105
+
106
+ - 2026-10-06 독립 재구현에서 입력 전 경고 위치와 시간 선택의 확정 후 결과 위치가 충돌했다. 시간 선택의 결과는 행동 아래, 입력 시트의 저장 실패는 본문 입력 위로 각 구성에 명시한다. Notice 자체가 결과 위치를 강제하지 않는다.
@@ -0,0 +1,97 @@
1
+ # NotificationInboxScreen
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.12.1 이후)
7
+ - 검토일: 2026-10-06
8
+ - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
+ - 스토리북: `배포/화면/소통/알림함`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 알림함 화면 전체 틀에 쓴다. [ScreenLayout](screen-layout.md) 위에 필터 슬롯을 `notice` 영역에 고정해,
14
+ 로딩·빈 상태·오류로 본문이 바뀌어도 필터는 남게 한다. 본문 행은 [NotificationItem](notification-item.md)을 쓴다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 알림 한 행 | [NotificationItem](notification-item.md) |
21
+ | 상단 바의 알림 진입 아이콘·개수 | `NotificationBell`([IconButton](icon-button.md) 확장, `/notification-bell`) |
22
+ | 필터 없는 일반 목록 화면 | [ScreenLayout](screen-layout.md), [ListDetailScreen](list-detail-screen.md) |
23
+ | 일시적인 결과 알림 | [Toast](toast.md) |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `NotificationInboxScreen` | supplemental, 루트 barrel에 없음 | `/screens` | `/screens` |
30
+
31
+ `@hjmds/react/screens`, `@hjmds/react-native/screens`로만 import한다. 추가 optional peer는 없다.
32
+
33
+ ## 최소 사용 예
34
+
35
+ ```tsx
36
+ // Web
37
+ import { NotificationInboxScreen, NotificationItem } from "@hjmds/react/screens";
38
+
39
+ <NotificationInboxScreen
40
+ title={t("inbox.title")}
41
+ filters={filterControl}
42
+ state={items.length ? { kind: "ready" } : { kind: "empty", title: t("inbox.empty") }}
43
+ >
44
+ {items.map((n) => <NotificationItem key={n.id} {...toItemProps(n)} />)}
45
+ </NotificationInboxScreen>
46
+ ```
47
+
48
+ ```tsx
49
+ // Native
50
+ import { FlatList } from "react-native";
51
+ import { NotificationInboxScreen } from "@hjmds/react-native/screens";
52
+
53
+ <NotificationInboxScreen
54
+ title={t("inbox.title")}
55
+ filters={filterControl}
56
+ scroll="content"
57
+ state={query.isPending ? { kind: "loading", title: t("inbox.loading") } : { kind: "ready" }}
58
+ >
59
+ <FlatList data={items} renderItem={renderNotification} />
60
+ </NotificationInboxScreen>
61
+ ```
62
+
63
+ ### 제품이 공급할 것
64
+
65
+ | 슬롯·prop | 내용 |
66
+ | --- | --- |
67
+ | `title`, `description` | 지역화 제목 |
68
+ | `filters` | 필터 control(SegmentedControl·Chip 등). 상태가 바뀌어도 유지된다 |
69
+ | `notice` | 새로고침 실패 같은 안내. `filters` 위에 함께 놓인다 |
70
+ | `state` | `ready` · `loading` · `empty` · `error` · `restricted`. ready 외에는 지역화 `title` 필수 |
71
+ | `stateAction` | 재시도·로그인 같은 실제 행동 |
72
+ | `children` | 알림 목록(데이터·페이지 합치기·더 보기는 제품) |
73
+ | `header`, `leading`, `actions` | 기존 내비게이션 헤더, 뒤로 가기, “모두 읽음” 같은 행동 |
74
+
75
+ ## 배치
76
+
77
+ | 항목 | 값 | 근거 |
78
+ | --- | --- | --- |
79
+ | 크기 | ScreenLayout 폭(최대 720) 안에서 알림 행이 폭을 채운다 | `ScreenLayout`, `NotificationItem`(ListRow) |
80
+ | 간격 | 화면 padding `spacing.md` 16(notice 영역은 좌우만); `notice`–`filters` `spacing.sm` 12; 행 사이 간격은 목록(제품) 소유 | Web·Native `NotificationInboxScreen` `Stack gap="sm"` |
81
+ | 순서·정렬 | 헤더(`leading` → 제목 → `actions`) → `notice` → `filters` → 알림 목록 | 렌더 순서 |
82
+ | 고정·스크롤 | `notice`·`filters`는 헤더 아래 고정되고 상태 교체 중에도 남는다; 목록은 기본 화면 스크롤(`scroll="screen"`), 가상화 목록이면 `scroll="content"` | `ScreenLayout` `notice`·`scroll` |
83
+ | 좁은 폭·큰 글자 | 제목 열 최소 폭 120 × 글자 배율, 모자라면 `actions`(모두 읽음 등)가 다음 줄로 내려간다; 필터 줄바꿈·가로 스크롤은 `filters` 소유 | `screenPatternRecipe.headerMinWidth` |
84
+
85
+ ## 꼭 지킬 것
86
+
87
+ - 읽음 처리·계정 scope·요청 취소·라우팅·focus 복구는 제품 소유다. 화면 표시만으로 읽음 처리하지 않는다.
88
+ - 새로고침 실패는 `state="error"`가 아니라 `ready` + `notice`로 알린다. 오류로 바꾸면 목록이 내려간다.
89
+ - FlatList 같은 가상화 목록은 `scroll="content"`로 넣는다. 스크롤 컨테이너를 이중으로 만들지 않는다.
90
+ - 이미 `<main>` 안이면 Web은 `as="section"`, 라우트가 여백을 이미 주면 `contentInset="none"`.
91
+
92
+ ## 플랫폼 차이
93
+
94
+ | 항목 | Web | Native |
95
+ | --- | --- | --- |
96
+ | 배치·식별 | `layoutStyle`, `className`, `as` | `layoutStyle`, `testID` |
97
+ | 스크롤 | 없음 | `scrollRef`, `scrollProps`(`refreshControl` 등) |
@@ -0,0 +1,98 @@
1
+ # NotificationItem
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.12.1 이후)
7
+ - 검토일: 2026-10-06
8
+ - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
+ - 스토리북: `배포/구성/정보 표시/알림 항목`, `배포/화면/소통/알림함`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 알림함의 알림 한 행에 쓴다. [ListRow](list-row.md)에 읽음 여부(제목 굵기)와
14
+ 지역화한 “상태 · 시각” 줄을 더한 조합이다. 탭 동작·링크는 ListRow 그대로다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 알림이 아닌 일반 목록 행 | [ListRow](list-row.md) |
21
+ | 알림함 화면 전체 틀 | [NotificationInboxScreen](notification-inbox-screen.md) |
22
+ | 화면 위에 잠시 뜨는 알림 | [Toast](toast.md) |
23
+ | 화면 안에 남는 상태 안내 | [Notice](notice.md) |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `NotificationItem` | supplemental, 루트 barrel에 없음 | `/screens` | `/screens` |
30
+
31
+ `@hjmds/react/screens`, `@hjmds/react-native/screens`로만 import한다. 추가 optional peer는 없다.
32
+
33
+ ## 최소 사용 예
34
+
35
+ ```tsx
36
+ // Web
37
+ import { NotificationItem } from "@hjmds/react/screens";
38
+
39
+ <NotificationItem
40
+ read={n.read}
41
+ statusLabel={n.read ? t("inbox.read") : t("inbox.unread")}
42
+ timestamp={formatRelative(n.createdAt)}
43
+ title={n.title}
44
+ description={n.body}
45
+ leading={senderAvatar}
46
+ href={n.url}
47
+ />
48
+ ```
49
+
50
+ ```tsx
51
+ // Native
52
+ import { NotificationItem } from "@hjmds/react-native/screens";
53
+
54
+ <NotificationItem
55
+ read={n.read}
56
+ statusLabel={n.read ? t("inbox.read") : t("inbox.unread")}
57
+ timestamp={formatRelative(n.createdAt)}
58
+ title={n.title}
59
+ description={n.body}
60
+ onPress={() => openNotification(n)}
61
+ />
62
+ ```
63
+
64
+ ### 제품이 공급할 것
65
+
66
+ | prop | 내용 |
67
+ | --- | --- |
68
+ | `read` | 읽음 여부(필수). 안 읽음이면 제목이 굵어진다 |
69
+ | `statusLabel` | 지역화 읽음 상태 문구(필수). 굵기만으로 상태를 전하지 않도록 글로도 표시한다 |
70
+ | `timestamp` | 제품이 포맷한 상대·절대 시각 문자열(필수) |
71
+ | `title`, `description` | 알림 내용 |
72
+ | 나머지 ListRow props | `leading`·`trailing`·`density`·`disabled` 등, 이동은 `href`/`onClick`(Web), `onPress`(Native) |
73
+
74
+ ## 배치
75
+
76
+ | 항목 | 값 | 근거 |
77
+ | --- | --- | --- |
78
+ | 크기 | ListRow 크기·density와 내용 높이를 따른다; 모서리 radius 0(Web) | Web `.hjm-notification-item`, `ListRow` |
79
+ | 간격 | ListRow 내부 간격; Web은 `description`–상태·시각 줄 `spacing.xxs` 4, Native는 같은 description 문자열 안 줄바꿈; 행을 카드 padding으로 다시 감싸지 않는다 | Web `Stack gap="xxs"`, Native `description` join |
80
+ | 순서·정렬 | leading → 제목(안 읽음은 굵게) → `description` → `statusLabel · timestamp` → trailing | Web·Native `NotificationItem` |
81
+ | 고정·스크롤 | 목록이 스크롤; 행 자체는 고정하지 않는다 | `NotificationInboxScreen` |
82
+ | 좁은 폭·큰 글자 | 제목·설명·상태 줄이 줄바꿈되며 행 높이가 늘어난다; 읽음 여부는 굵기만이 아니라 `statusLabel` 문구로도 전한다 | `NotificationItem` |
83
+
84
+ ## 꼭 지킬 것
85
+
86
+ - 표시·탭·스크롤이 읽음 처리를 하지 않는다. 읽음 mutation은 제품이 `onPress`/`onClick` 또는 화면 정책에서 실행하고 `read`를 갱신한다.
87
+ - `selected`는 받지 않는다(타입에서 제외). 선택 목록이 필요하면 ListRow를 쓴다.
88
+ - 시각 포맷·상대시간은 제품 i18n에서 만든다.
89
+
90
+ ## 플랫폼 차이
91
+
92
+ | 항목 | Web | Native |
93
+ | --- | --- | --- |
94
+ | `title`, `description` 타입 | `ReactNode` | `string` |
95
+ | 상태·시각 줄 | 설명 아래 caption `Text` | `description`과 줄바꿈(`\n`)으로 합친 한 문자열 |
96
+ | 이동 | `href`, `onClick` | `onPress` |
97
+ | 추가 슬롯 | `className`, `layoutStyle` | `titleMetadata`, `trailingAction`, `trailingText`, `layoutStyle` |
98
+ | 제목 굵기 조정 | `Text emphasis` | 렌더러 비공개 입력(`hjmTitleEmphasis`)으로 안 읽음 700(`fontWeight.bold`)·읽음 400(`fontWeight.regular`). deprecated `ListRow.titleStyle`을 쓰지 않으므로 HJM이 `titleStyle` 경고를 내지 않는다. `NotificationItemProps`에는 `titleStyle`이 없다(미게시(1.12.1 이후)) |