@hjmds/design-contracts 1.18.0 → 1.19.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 (244) hide show
  1. package/dist/action-session.d.ts +16 -0
  2. package/dist/action-session.d.ts.map +1 -1
  3. package/dist/action-session.js +18 -0
  4. package/dist/action-session.js.map +1 -1
  5. package/dist/behaviors.d.ts +7 -6
  6. package/dist/behaviors.d.ts.map +1 -1
  7. package/dist/behaviors.js +4 -3
  8. package/dist/behaviors.js.map +1 -1
  9. package/dist/bottom-navigation.d.ts +4 -0
  10. package/dist/bottom-navigation.d.ts.map +1 -1
  11. package/dist/bottom-navigation.js +11 -0
  12. package/dist/bottom-navigation.js.map +1 -1
  13. package/dist/carousel.d.ts +3 -0
  14. package/dist/carousel.d.ts.map +1 -1
  15. package/dist/carousel.js.map +1 -1
  16. package/dist/catalog.d.ts +42 -0
  17. package/dist/catalog.d.ts.map +1 -1
  18. package/dist/component-recipes.d.ts +42 -0
  19. package/dist/component-recipes.d.ts.map +1 -1
  20. package/dist/component-recipes.js +14 -1
  21. package/dist/component-recipes.js.map +1 -1
  22. package/dist/field-group.d.ts +14 -0
  23. package/dist/field-group.d.ts.map +1 -1
  24. package/dist/field-group.js +9 -0
  25. package/dist/field-group.js.map +1 -1
  26. package/dist/form.d.ts.map +1 -1
  27. package/dist/form.js +10 -3
  28. package/dist/form.js.map +1 -1
  29. package/dist/screen-patterns.d.ts +4 -0
  30. package/dist/screen-patterns.d.ts.map +1 -1
  31. package/dist/screen-patterns.js +12 -3
  32. package/dist/screen-patterns.js.map +1 -1
  33. package/dist/toast.d.ts +10 -1
  34. package/dist/toast.d.ts.map +1 -1
  35. package/dist/toast.js +43 -2
  36. package/dist/toast.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/dist/version.js.map +1 -1
  40. package/docs/action-session.md +2 -0
  41. package/docs/asset.md +7 -0
  42. package/docs/bottom-navigation.md +11 -0
  43. package/docs/brand-boundary.md +3 -3
  44. package/docs/collection-rail.md +4 -0
  45. package/docs/compound-controls.md +8 -3
  46. package/docs/consumer-policy.md +39 -120
  47. package/docs/generated/component-maturity.md +1 -1
  48. package/docs/generated/renderer-evidence.json +3 -3
  49. package/docs/generated/renderer-evidence.md +1 -1
  50. package/docs/generated/showcase-manifest.json +1 -1
  51. package/docs/gravity-letters.md +2 -0
  52. package/docs/grid-reveal.md +2 -0
  53. package/docs/layout.md +2 -2
  54. package/docs/load-more.md +5 -0
  55. package/docs/migration-native-legacy-removal.md +30 -17
  56. package/docs/optional-adapters.md +4 -0
  57. package/docs/screen-patterns.md +23 -1
  58. package/docs/toast.md +41 -2
  59. package/docs/usage/README.md +248 -245
  60. package/docs/usage/SELECTION.md +43 -0
  61. package/docs/usage/STANDARD.md +22 -10
  62. package/docs/usage/components/accordion.md +5 -3
  63. package/docs/usage/components/agreement.md +6 -9
  64. package/docs/usage/components/alert-dialog.md +12 -6
  65. package/docs/usage/components/asset.md +10 -4
  66. package/docs/usage/components/auth-provider-button.md +2 -2
  67. package/docs/usage/components/avatar.md +8 -6
  68. package/docs/usage/components/badge.md +4 -4
  69. package/docs/usage/components/bottom-cta.md +4 -4
  70. package/docs/usage/components/bottom-info.md +2 -2
  71. package/docs/usage/components/bottom-navigation.md +16 -5
  72. package/docs/usage/components/button.md +2 -2
  73. package/docs/usage/components/card.md +1 -1
  74. package/docs/usage/components/carousel.md +12 -4
  75. package/docs/usage/components/chat-message.md +6 -2
  76. package/docs/usage/components/chat-screen.md +5 -5
  77. package/docs/usage/components/checkbox-group.md +1 -1
  78. package/docs/usage/components/checkbox.md +2 -2
  79. package/docs/usage/components/chip.md +2 -2
  80. package/docs/usage/components/code-block.md +3 -4
  81. package/docs/usage/components/collapsible.md +6 -4
  82. package/docs/usage/components/combobox.md +2 -2
  83. package/docs/usage/components/command-palette.md +1 -1
  84. package/docs/usage/components/comment-thread-screen.md +7 -4
  85. package/docs/usage/components/container.md +1 -1
  86. package/docs/usage/components/content-transition.md +2 -2
  87. package/docs/usage/components/context-menu.md +2 -2
  88. package/docs/usage/components/counter-badge.md +3 -3
  89. package/docs/usage/components/data-table.md +1 -1
  90. package/docs/usage/components/date-picker.md +4 -4
  91. package/docs/usage/components/description-list.md +3 -3
  92. package/docs/usage/components/design-system-provider.md +2 -2
  93. package/docs/usage/components/dialog.md +14 -3
  94. package/docs/usage/components/divider.md +2 -2
  95. package/docs/usage/components/editor-screen.md +5 -3
  96. package/docs/usage/components/effect-surface.md +1 -1
  97. package/docs/usage/components/empty-state.md +6 -6
  98. package/docs/usage/components/field.md +5 -3
  99. package/docs/usage/components/file-picker.md +6 -3
  100. package/docs/usage/components/form.md +1 -1
  101. package/docs/usage/components/heading.md +3 -3
  102. package/docs/usage/components/icon-button.md +3 -3
  103. package/docs/usage/components/icon.md +1 -1
  104. package/docs/usage/components/image.md +5 -4
  105. package/docs/usage/components/keyboard-dock.md +1 -1
  106. package/docs/usage/components/layout.md +1 -1
  107. package/docs/usage/components/link.md +2 -2
  108. package/docs/usage/components/list-detail-screen.md +6 -6
  109. package/docs/usage/components/list-row.md +13 -4
  110. package/docs/usage/components/list.md +5 -4
  111. package/docs/usage/components/masonry.md +2 -1
  112. package/docs/usage/components/media-selection-screen.md +11 -5
  113. package/docs/usage/components/mentions.md +2 -2
  114. package/docs/usage/components/menu.md +3 -2
  115. package/docs/usage/components/menubar.md +2 -2
  116. package/docs/usage/components/message-composer.md +3 -3
  117. package/docs/usage/components/moderation-screen.md +3 -2
  118. package/docs/usage/components/notice.md +2 -2
  119. package/docs/usage/components/notification-inbox-screen.md +1 -1
  120. package/docs/usage/components/notification-item.md +2 -2
  121. package/docs/usage/components/number-field.md +3 -3
  122. package/docs/usage/components/otp-field.md +1 -1
  123. package/docs/usage/components/overview-screen.md +1 -1
  124. package/docs/usage/components/permission-screen.md +1 -1
  125. package/docs/usage/components/photo-source-sheet.md +1 -1
  126. package/docs/usage/components/popover.md +2 -2
  127. package/docs/usage/components/profile-screen.md +1 -1
  128. package/docs/usage/components/progress.md +2 -2
  129. package/docs/usage/components/qr-code.md +2 -2
  130. package/docs/usage/components/radio-group.md +1 -2
  131. package/docs/usage/components/radio.md +1 -2
  132. package/docs/usage/components/result.md +1 -1
  133. package/docs/usage/components/saved-items-screen.md +4 -2
  134. package/docs/usage/components/screen-layout.md +4 -3
  135. package/docs/usage/components/search-field.md +1 -1
  136. package/docs/usage/components/search-screen.md +4 -4
  137. package/docs/usage/components/section.md +3 -3
  138. package/docs/usage/components/segmented-control.md +4 -4
  139. package/docs/usage/components/select.md +2 -3
  140. package/docs/usage/components/settings-screen.md +11 -49
  141. package/docs/usage/components/sheet.md +10 -5
  142. package/docs/usage/components/sidebar.md +1 -1
  143. package/docs/usage/components/skeleton.md +1 -1
  144. package/docs/usage/components/skip-nav.md +1 -1
  145. package/docs/usage/components/slider.md +2 -2
  146. package/docs/usage/components/spinner.md +2 -2
  147. package/docs/usage/components/splitter.md +1 -1
  148. package/docs/usage/components/statistic.md +5 -4
  149. package/docs/usage/components/steps.md +2 -2
  150. package/docs/usage/components/surface.md +2 -3
  151. package/docs/usage/components/switch.md +1 -1
  152. package/docs/usage/components/tabs.md +4 -4
  153. package/docs/usage/components/tag.md +2 -2
  154. package/docs/usage/components/tags-input.md +4 -4
  155. package/docs/usage/components/text-area.md +2 -3
  156. package/docs/usage/components/text.md +85 -4
  157. package/docs/usage/components/timeline.md +2 -2
  158. package/docs/usage/components/toast.md +17 -7
  159. package/docs/usage/components/toggle-group.md +3 -3
  160. package/docs/usage/components/tooltip.md +1 -1
  161. package/docs/usage/components/top-bar.md +5 -4
  162. package/docs/usage/components/top.md +3 -3
  163. package/docs/usage/components/transfer-list.md +5 -4
  164. package/docs/usage/components/upload-item.md +3 -3
  165. package/docs/usage/components/virtual-list.md +1 -0
  166. package/docs/usage/compositions/action-feedback.md +42 -6
  167. package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
  168. package/docs/usage/compositions/action-recovery-save.md +19 -23
  169. package/docs/usage/compositions/action-recovery-undo.md +6 -1
  170. package/docs/usage/compositions/activity-status.md +11 -5
  171. package/docs/usage/compositions/collection-detail.md +2 -0
  172. package/docs/usage/compositions/command-records.md +15 -5
  173. package/docs/usage/compositions/common-message.md +4 -2
  174. package/docs/usage/compositions/common-notification.md +5 -3
  175. package/docs/usage/compositions/compound-controls.md +13 -5
  176. package/docs/usage/compositions/content-transition-comparison.md +2 -2
  177. package/docs/usage/compositions/date-entry.md +10 -5
  178. package/docs/usage/compositions/date-time-selection.md +3 -3
  179. package/docs/usage/compositions/design-profile-comparison.md +7 -5
  180. package/docs/usage/compositions/disclosure.md +1 -1
  181. package/docs/usage/compositions/document-resource.md +10 -3
  182. package/docs/usage/compositions/expo-interactions.md +3 -2
  183. package/docs/usage/compositions/family-drawer.md +9 -4
  184. package/docs/usage/compositions/field-group.md +7 -1
  185. package/docs/usage/compositions/floating-action-button.md +18 -18
  186. package/docs/usage/compositions/illustrated-outcome.md +3 -2
  187. package/docs/usage/compositions/input-sheet.md +6 -2
  188. package/docs/usage/compositions/interaction-adapters.md +1 -1
  189. package/docs/usage/compositions/interaction-flow-apply.md +6 -3
  190. package/docs/usage/compositions/interaction-flow-draft.md +5 -2
  191. package/docs/usage/compositions/interaction-flow-search.md +5 -2
  192. package/docs/usage/compositions/live-list.md +7 -5
  193. package/docs/usage/compositions/navigation-bar-collection.md +8 -7
  194. package/docs/usage/compositions/optional-adapters.md +1 -1
  195. package/docs/usage/compositions/origin-dialog.md +5 -5
  196. package/docs/usage/compositions/paper-surface.md +1 -1
  197. package/docs/usage/compositions/photo-source.md +1 -1
  198. package/docs/usage/compositions/purpose-input-comment.md +5 -3
  199. package/docs/usage/compositions/purpose-input-message.md +5 -3
  200. package/docs/usage/compositions/reference-first.md +63 -42
  201. package/docs/usage/compositions/reference-review.md +60 -50
  202. package/docs/usage/compositions/reference-settings.md +60 -50
  203. package/docs/usage/compositions/selection-scope.md +48 -35
  204. package/docs/usage/compositions/stea-order-progress.md +1 -0
  205. package/docs/usage/compositions/stea-otp-verify.md +5 -3
  206. package/docs/usage/compositions/stea-pixel-empty.md +3 -3
  207. package/docs/usage/compositions/time-selection.md +6 -5
  208. package/docs/usage/compositions/upload-recovery.md +10 -1
  209. package/docs/usage/compositions/video-dialog.md +10 -3
  210. package/docs/usage/compositions/web-additions.md +5 -5
  211. package/docs/usage/compositions/web-navigation.md +1 -1
  212. package/docs/usage/screens/common-chat.md +21 -8
  213. package/docs/usage/screens/common-comments.md +13 -1
  214. package/docs/usage/screens/common-inbox.md +19 -6
  215. package/docs/usage/screens/common-login.md +19 -7
  216. package/docs/usage/screens/common-profile.md +22 -9
  217. package/docs/usage/screens/common-saved.md +16 -2
  218. package/docs/usage/screens/common-search.md +24 -4
  219. package/docs/usage/screens/common-settings.md +22 -50
  220. package/docs/usage/screens/common-shell.md +14 -2
  221. package/docs/usage/screens/dashboard.md +52 -109
  222. package/docs/usage/screens/discovery-gallery.md +57 -148
  223. package/docs/usage/screens/flow-collection.md +38 -9
  224. package/docs/usage/screens/flow-editor.md +25 -7
  225. package/docs/usage/screens/flow-media.md +23 -7
  226. package/docs/usage/screens/flow-moderation.md +15 -2
  227. package/docs/usage/screens/flow-onboarding.md +3 -3
  228. package/docs/usage/screens/flow-permission.md +17 -3
  229. package/docs/usage/screens/landing.md +37 -20
  230. package/docs/usage/screens/mockup-studio.md +22 -7
  231. package/docs/usage/screens/notification-settings.md +18 -4
  232. package/docs/usage/screens/product-bento.md +18 -9
  233. package/docs/usage/screens/reference-comparison.md +29 -10
  234. package/docs/usage/tokens/color.md +6 -3
  235. package/docs/usage/tokens/elevation-opacity.md +4 -2
  236. package/docs/usage/tokens/font-roles.md +2 -2
  237. package/docs/usage/tokens/layers.md +3 -3
  238. package/docs/usage/tokens/motion.md +2 -2
  239. package/docs/usage/tokens/radius.md +4 -2
  240. package/docs/usage/tokens/spacing.md +3 -3
  241. package/docs/usage/tokens/theme-studio.md +2 -2
  242. package/docs/usage/tokens/typography-studio.md +14 -8
  243. package/docs/usage/tokens/typography.md +15 -5
  244. package/package.json +1 -1
@@ -34,8 +34,7 @@
34
34
 
35
35
  공개 guide가 HJM profile context를 서버 module에서 평가해 실패한 사례(2026-10-07)를 보완했다.
36
36
  현재 main의 Web `/layout`은 client entry이므로 서버가 작성한 JSX children과 직렬화 가능한 props를
37
- Surface로 넘길 수 있다. 서버 page 전체를 client로 바꿀 필요는 없다. 이 보완은1.15.0 이후 미게시이므로
38
- 설치1.15.0에서는 제품의 작은 `"use client"` 재수출 경계를 유지한다. callback/ref 등 client 실행이
37
+ Surface로 넘길 수 있다. 서버 page 전체를 client로 바꿀 필요는 없다. 이 client entry는 1.16.0 변경이다. callback/ref 등 client 실행이
39
38
  필요한 props는 소비 Client Component에서 만든다. 루트 barrel/다른 entry를 서버에 직접 import해도
40
39
  된다는 보장은 아니다. [재현·검증](../../../../../docs/qa/2026-10-07-rsc-profile-boundary.md)을 따른다.
41
40
 
@@ -71,7 +70,7 @@ import { Surface } from "@hjmds/react-native/primitives";
71
70
  | `as`(Web) | `div` · `section` · `article` | `div` | — |
72
71
  | `layoutStyle` | `HjmCompositionStyleProp` | — | Surface 자신의 바깥 여백·폭·flex·`alignSelf`. Web·Native 모두 |
73
72
 
74
- ### 프로필 표면 질감(미게시)
73
+ ### 프로필 표면 질감(1.15.0 추가)
75
74
 
76
75
  선택한 `designProfile.material.surface`를 자동으로 읽는다. glass는 지원하는 Web에서 실제 배경 흐림,
77
76
  clay는 안쪽 그림자를 사용한다. Native는 루트 Provider의 선택형 `surfaceEffects` host와 inset capability를
@@ -69,7 +69,7 @@ import { Switch } from "@hjmds/react-native/inputs";
69
69
  | `description` | 문구 | — | 이름과 따로 연결한다(Web `aria-describedby`, Native `accessibilityHint` 기본값) |
70
70
  | `disabled` | `boolean` | `false` | — |
71
71
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트(행) 배치. Web·Native 모두 |
72
- | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `size`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
72
+ | `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `size`. 현재 공개 props에서 제거되었다 |
73
73
 
74
74
  큰 글자(공통 large-text 기준)에서 `row`는 설명 아래로 track을 내린다.
75
75
 
@@ -77,12 +77,12 @@ import { Tabs } from "@hjmds/react-native/navigation";
77
77
  | `overflow` | `scroll` · `clip` | `scroll` | — |
78
78
  | `orientation` | `horizontal` · `vertical` | `horizontal` | — |
79
79
  | `loop` | `boolean` | `true` | — |
80
- | `appearance` | `standard` · `slide` · `gooey` | 미지정 시 프로필의 selectionMotion; 없으면 `standard` | 미게시(1.14.0 이후): `slide`는 2점 표시선 이동, `gooey`는 6점 표시선 늘어남. 명시 값 우선, 세로는 standard 유지 |
80
+ | `appearance` | `standard` · `slide` · `gooey` | 미지정 시 프로필의 selectionMotion; 없으면 `standard` | 1.15.0 변경: `slide`는 2점 표시선 이동, `gooey`는 6점 표시선 늘어남. 명시 값 우선, 세로는 standard 유지 |
81
81
  | `renderPanels` | `boolean` | `true` | `false`면 패널을 그리지 않는다. 패널을 라우터·스크롤 상태와 함께 따로 둘 때 `TabPanel`에 `tabsId`(Tabs의 `id`와 같게)·`activeValue`·`value`를 넘긴다 |
82
82
  | `children`(Native) | `(selectedValue: Value) => ReactNode` | — | 항목 `panel` 대신 선택 값으로 패널을 그린다 |
83
83
  | `TabPanel` | `{ tabsId, activeValue, children, mode?: "keyed", value, mountPolicy? }` 또는 `{ mode: "dynamic" }`, Native는 `label` 필수 | — | — |
84
84
  | `layoutStyle` | `HjmCompositionStyleProp` | — | Tabs·TabPanel 루트 배치. Web·Native 모두 |
85
- | Native `style`·`tabListStyle`, TabPanel `style` | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `size`·`layout`·`overflow`·`appearance`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
85
+ | Native `style`·`tabListStyle`, TabPanel `style` | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `size`·`layout`·`overflow`·`appearance`. 현재 공개 props에서 제거되었다 |
86
86
 
87
87
  ## 배치
88
88
 
@@ -112,7 +112,7 @@ import { Tabs } from "@hjmds/react-native/navigation";
112
112
  Web controlled `value`가 비활성·없는 항목이면 `RangeError`다.
113
113
  - 아이콘은 `renderLeading`으로 넣고 받은 `color`·`size`를 쓴다. 색을 직접 정하지 않는다.
114
114
  - 탭을 바꿔도 화면 이동(뒤로 가기 대상)이 아니다. 주소에 남겨야 하면 제품이 controlled 값으로 동기화한다.
115
- - 배치는 `layoutStyle`로 한다. Web 루트 `div` 속성(`className`·`style`)과 Native의 deprecated `style`·`tabListStyle`로
115
+ - 배치는 `layoutStyle`로 한다. Web 루트 `div` 속성(`className`·`style`)과 Native에서 제거된 `style`·`tabListStyle`로
116
116
  색·높이·인디케이터를 덮지 않는다.
117
117
 
118
118
  ## 플랫폼 차이
@@ -131,7 +131,7 @@ import { Tabs } from "@hjmds/react-native/navigation";
131
131
 
132
132
  - 2026-10-07 [Animated Tabs 대조](../../../../../docs/plans/aceternity-interaction-adoption-2026-10-07.md)에서
133
133
  선택 표시의 이동과 겹친 패널의 이동을 구분했다. 현재 `appearance="gooey"`는 표시선만 늘어나며,
134
- 패널을 겹쳐 복제하거나 순서를 바꾸지 않는다. 미게시(1.14.0 이후) 후속 구현에서
134
+ 패널을 겹쳐 복제하거나 순서를 바꾸지 않는다. 1.15.0 변경 후속 구현에서
135
135
  `appearance`를 생략하면 `designProfile.interactions.selectionMotion="slide"`가 일반 이동선으로
136
136
  연결된다. 명시 `standard`는 이동을 끄고 명시 `gooey`만 늘어남을 고른다. `slide`와 `gooey`를
137
137
  같은 의미로 간주하지 않는다. 전환 표현이 필요하면 [ContentTransition](content-transition.md)의
@@ -53,7 +53,7 @@ import { Tag } from "@hjmds/react-native/data-display";
53
53
  | `tone` | `neutral` · `info` · `success` · `attention` · `brand` | `neutral` | `warning`·`danger`는 없다(이유는 계약 문서) |
54
54
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 바깥 여백·`alignSelf` 같은 배치. Web·Native 모두 |
55
55
  | `accessibilityLabel`(Native) | `string` | 라벨 | — |
56
- | `style`·`labelStyle`(Native) | `StyleProp` | — | deprecated — `layoutStyle` 또는 `tone`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
56
+ | `style`·`labelStyle`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `tone`. 현재 공개 props에서 제거되었다 |
57
57
 
58
58
  모양은 radius `sm` 사각형, 최소 높이 20(큰 글자에서 늘어남), caption 크기 semibold 글자다.
59
59
 
@@ -81,6 +81,6 @@ Native의 모서리는 Provider의 `tokens.radius.sm`이다. metadata 의미와
81
81
  | 항목 | Web | Native |
82
82
  | --- | --- | --- |
83
83
  | 접근 이름 | 보이는 글자 | 기본은 라벨, `accessibilityLabel`로 바꿀 수 있음 |
84
- | 추가 style prop | `className`·`style`(HTML 속성) | `style`·`labelStyle`(deprecated) |
84
+ | 추가 style prop | `className`·`style`(HTML 속성) | `style`·`labelStyle`(제거된 속성) |
85
85
  | ref | `forwardRef`(`span`) | 없음 |
86
86
  | import 경로 | `/display` | `/data-display` |
@@ -80,7 +80,7 @@ import { TagsInput } from "@hjmds/react-native/tags-input";
80
80
  | `suggestions` · `suggestionsLabel` | 제품이 거른 후보(`id`·`label`·`value?`·`disabled?`)와 목록 이름 | — | 거르려면 `onDraftChange`로 입력 중 문자를 받는다 |
81
81
  | `placeholder`·`description`·`disabled` | `string`·`string`·`boolean` | — | `disabled`는 라벨과 입력 틀을 `fieldRecipe.disabledOpacity`(0.6)로 흐리고 `description`은 그대로 둔다([Field](field.md)) |
82
82
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 필드 전체 배치. Web·Native 모두 |
83
- | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
83
+ | `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
84
84
 
85
85
  ## 배치
86
86
 
@@ -91,7 +91,7 @@ Native 입력 프레임의 `md` 모서리는 Provider의 `tokens.radius.md`를
91
91
 
92
92
  | 항목 | 값 | 근거 |
93
93
  | --- | --- | --- |
94
- | 크기 | 입력 틀 최소 높이 44(필드 틀과 같음), 모서리 `radius.md` 12. 입력 칸 최소 Web `8ch`, Native 80. 태그 칩 높이 28, 삭제 버튼 터치 영역 44(`control.minTouchTarget`). 후보 행 최소 높이 44(두 플랫폼, Native는 미게시(1.12.1 이후). 1.12.1 Native는 28) | `tagsInputRecipe`, `.hjm-tags-input__frame`·`__remove::after`·`__suggestion` |
94
+ | 크기 | 입력 틀 최소 높이 44(필드 틀과 같음), 모서리 `radius.md` 12. 입력 칸 최소 Web `8ch`, Native 80. 태그 칩 높이 28, 삭제 버튼 터치 영역 44(`control.minTouchTarget`). 후보 행 최소 높이 44(두 플랫폼, Native는 1.13.0 변경. 1.12.1 Native는 28) | `tagsInputRecipe`, `.hjm-tags-input__frame`·`__remove::after`·`__suggestion` |
95
95
  | 간격 | 라벨과 틀 사이 Web `spacing.xxs` 4, Native `spacing.xs` 8. 틀 안 태그·입력 칸 사이 `spacing.xs` 8. 이웃 필드와의 간격은 폼이 정한다 | `.hjm-tags-input`, `react-native/src/tags-input.tsx` |
96
96
  | 순서·정렬 | 라벨 → 입력 틀 → 후보 목록 → 설명. 폼 안에서 다른 [Field](field.md)와 같은 세로 줄에 두고 폭을 꽉 채운다 | `react/src/tags-input.tsx`, `react-native/src/tags-input.tsx` |
97
97
  | 고정·스크롤 | 후보 목록은 틀 바로 아래 문서 흐름에 펼쳐진다(떠 있는 층이 아니다). Web은 최대 높이 12rem에서 스크롤 | `.hjm-tags-input__suggestions` |
@@ -103,7 +103,7 @@ Native 입력 프레임의 `md` 모서리는 Provider의 `tokens.radius.md`를
103
103
  - 거절 사유 문장("이미 있어요")은 제품이 `reason`으로 만든다. HJM은 판정만 한다.
104
104
  - 한국어처럼 공백이 값의 일부인 도메인에서는 `Space`를 확정 키로 켜지 않는다.
105
105
  - 후보 필터링·정렬은 제품이 한다. HJM은 받은 후보를 보여 주고 고른 값을 확정한다.
106
- - 배치는 `layoutStyle`로 한다(Web `className`도 받는다). Native `style`은 deprecated다.
106
+ - 배치는 `layoutStyle`로 한다(Web `className`도 받는다). Native `style`은 제거된 속성이다.
107
107
 
108
108
  ## 플랫폼 차이
109
109
 
@@ -113,7 +113,7 @@ Native 입력 프레임의 `md` 모서리는 Provider의 `tokens.radius.md`를
113
113
  | 빈 입력 Backspace | 첫 번째는 마지막 태그 선택, 두 번째에 삭제 | 해당 키 계약 없음, 태그 옆 삭제 버튼 |
114
114
  | 후보 이동 | ArrowUp/Down(끝에서 순환) | 후보를 버튼으로 누름 |
115
115
  | ref | `forwardRef`(`input`) | 없음 |
116
- | 스타일 prop | `className`, `layoutStyle` | `layoutStyle`(`style`은 deprecated) |
116
+ | 스타일 prop | `className`, `layoutStyle` | `layoutStyle`(`style`은 제거된 속성) |
117
117
 
118
118
 
119
119
  ### 고정 아이콘과 큰 글자
@@ -69,7 +69,7 @@ import { TextArea } from "@hjmds/react-native/inputs";
69
69
  | `onChange`(Web) | `(event: ChangeEvent<HTMLTextAreaElement>) => void` | — | DOM 이벤트. 값은 `event.currentTarget.value` |
70
70
  | `onValueChange`(Native) | `(value: string) => void` | — | 다음 문자열 |
71
71
  | `minVisibleLines` · `maxVisibleLines` | 줄 수 | 최소 높이 80, 상한 없음 | 높이는 줄 수로만 정한다. 최소 높이는 `max(80, 줄 수 × typography.body 줄 높이 20 + spacing.sm 12 × 2)`라 `minVisibleLines`가 1·2여도 80(`fieldRecipe.multilineMinHeight`) 아래로 내려가지 않고 3부터 커진다(3줄 84). Web·Native 같다 |
72
- | `leadingAction` | `ReactNode` | — | 틀 안 글자 앞 행동 버튼. 자라는 글자의 세로 가운데에 맞추고 흐린 affix 색을 쓰지 않는다. 미게시(1.12.1 이후) |
72
+ | `leadingAction` | `ReactNode` | — | 틀 안 글자 앞 행동 버튼. 자라는 글자의 세로 가운데에 맞추고 흐린 affix 색을 쓰지 않는다. 1.13.0 변경 |
73
73
  | `trailing` | `ReactNode` | — | 틀 안 글자 뒤 슬롯 |
74
74
  | `description` · `error` · `required` · `disabled` | Web `ReactNode`, Native `string` · `boolean` | — | Field와 같은 틀로 그려지고 보조기기 설명에 연결된다 |
75
75
  | `busy`(Native) | `boolean` | `false` | — |
@@ -110,8 +110,7 @@ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력
110
110
 
111
111
  ## 함정
112
112
 
113
- - 현재 Web `TextField`의 `onValueChange` 주석은 "Web TextArea와 같은 콜백"이라고 적지만 Web `TextAreaProps`에는
114
- `onValueChange`가 없다(`react/src/forms.tsx`). 공유 폼 코드는 Web TextArea만 `onChange`로 분기한다.
113
+ - Web `TextAreaProps`에는 `onValueChange`가 없다. 공유 폼 코드는 Web TextArea의 `onChange`에서 `event.currentTarget.value`를 읽어 같은 값 변경 함수로 연결한다.
115
114
 
116
115
 
117
116
  2026-10-09 번뚝 시뮬레이터 확인: Native bounded 입력은 같은 너비의 비노출 텍스트로 줄 높이를 측정한다. iOS TextInput의 contentSize가 현재 viewport 높이로 제한되는 경우를 피하고, 마지막 줄바꿈도 크기에 포함한다. 화면에서 수동 픽셀 높이를 덧씌우지 않는다. 최대 표시 줄에 도달하기 전에는 커지고 이후 내부 스크롤을 사용한다.
@@ -17,6 +17,7 @@
17
17
 
18
18
  | 상황 | 대신 쓸 것 |
19
19
  | --- | --- |
20
+ | 짧은 상태·결과 문구를 변경 알림으로 전달 | [StatusText](#statustext) |
20
21
  | 문서 제목 단계(`h1`~)가 필요하거나 24px보다 큰 제목 | [Heading](heading.md) |
21
22
  | 화면 첫 자리의 제목 블록(보조 문장·행동 포함) | [Top](top.md) |
22
23
  | 본문 묶음의 헤더 행 | [Section](section.md) |
@@ -33,6 +34,7 @@ Text와 Heading의 경계: Text의 `variant="heading"`(24px)은 **모양만**
33
34
  | 이름 | 역할 | Web | Native |
34
35
  | --- | --- | --- | --- |
35
36
  | `Text` | 기본 | `@hjmds/react`, `/layout` | `@hjmds/react-native`, `/primitives` |
37
+ | `StatusText` | 짧은 상태·결과 알림 companion | `@hjmds/react`, `/status-text` | `@hjmds/react-native`, `/status-text` |
36
38
  | `GravityLetters` | 확장(홍보용 글자 낙하 장식, optional-extension) | `/gravity-letters` | `/gravity-letters` |
37
39
 
38
40
  `GravityLetters`는 granular subpath로만 import 된다. 추가 peer 의존성은 없다(Web은 WAAPI, Native는 RN `Animated`).
@@ -58,8 +60,8 @@ import { Text } from "@hjmds/react-native/primitives";
58
60
  | prop | 값 | 기본값 | 설명 |
59
61
  | --- | --- | --- | --- |
60
62
  | `variant` | `caption`(11) · `label`(12) · `body`(14) · `bodyLarge`(16) · `title`(18) · `titleLarge`(20) · `heading`(24) | `body` | 화면·섹션 제목은 모양이 아니라 [Heading](heading.md)으로 쓴다 |
61
- | `fontRole` | `ui` · `display` · `reading` · `code` | 제목 variant는 display, 본문은 reading, caption/label은 ui | 명시값이 기본을 덮는다. [역할 지침](../tokens/font-roles.md), 1.15.0 이후 미게시 |
62
- | `emphasis` | `regular` · `medium`(semibold) · `strong`(bold) | `regular` | — |
63
+ | `fontRole` | `ui` · `display` · `reading` · `code` | 제목 variant는 display, 본문은 reading, caption/label은 ui | 명시값이 기본을 덮는다. [역할 지침](../tokens/font-roles.md), 1.16.0 변경 |
64
+ | `emphasis` | `regular` · `medium`(semibold) · `strong`(bold) | 프로필이 있으면 variant 굵기, 없으면 `regular` | 명시값이 프로필과 variant보다 우선 |
63
65
  | `tone` | `primary` · `body` · `muted` · `subtle` · `weak` · `brand` · `danger` · `inverse` | `primary` | — |
64
66
  | `as` | `span` · `p` · `div` · `strong` · `small` | `span` | Web만. 문단은 `as="p"` |
65
67
  | `align` | `TextStyle["textAlign"]` | 쓰기 방향의 시작 쪽 | Native만 |
@@ -100,5 +102,84 @@ Native는 Text의 역할을 해당 stack에, 입력/조작 host는 제품 프로
100
102
 
101
103
  ## 함정
102
104
 
103
- - `emphasis`가 `variant`의 굵기를 덮는다. `variant="title"`도 기본 `emphasis="regular"`면 보통 굵기로
104
- 그려진다. 굵은 제목이 필요하면 `emphasis="strong"`을 명시한다(두 renderer 동일).
105
+ - `emphasis`를 명시하면 `variant`의 굵기를 덮는다. `designProfile`이 있으면 생략한 `emphasis`는 해당 프로필의 `tokens.typography[variant].fontWeight`를 따른다. 프로필이 없으면 기존 기본값 `regular`를 유지하므로 `variant="title"`도 보통 굵기로 그려진다. 그 경우 굵은 제목은 `emphasis="strong"`으로 지정한다(두 renderer 동일).
106
+
107
+
108
+ ## StatusText
109
+
110
+ Text와 같은 표현을 유지하면서 상태 변경 알림을 연결하는 공개 companion이다.
111
+ 기존 `본문 글자` Story의 `상태 알림`에서 지속 안내와 저장 결과를 확인한다.
112
+
113
+ ### StatusText 선택 기준
114
+
115
+ 저장 결과, 선택 개수, 요청 진행처럼 **짧은 문자열 하나**를 화면 안에 유지하며 변경을 알릴 때 쓴다.
116
+ Text의 companion으로 같은 `variant`·`tone`·`emphasis`·`layoutStyle`을 받으며 글꼴·색·간격을 새로 만들지 않는다.
117
+ 문구와 성공·실패 판정은 제품이 소유한다.
118
+
119
+ | 상황 | 선택 |
120
+ | --- | --- |
121
+ | 읽기만 하는 본문·계속 표시되는 설명 | Text |
122
+ | 짧은 상태·결과 문자열의 변경 알림 | StatusText |
123
+ | 제목·설명·아이콘·재시도 행동을 묶은 안내 | Notice |
124
+ | 화면 위에 일시적으로 나타나며 닫히는 알림 | Toast |
125
+
126
+ ### 가져오기
127
+
128
+ ```tsx
129
+ // Web
130
+ import { StatusText } from "@hjmds/react/status-text";
131
+ // Native: import { StatusText } from "@hjmds/react-native/status-text";
132
+ // 두 renderer의 root에서도 내보낸다.
133
+ ```
134
+
135
+ ### StatusText가 놓이는 자리
136
+
137
+ ```text
138
+ 관련 입력 또는 행동
139
+ ↕ 부모 Stack의 gap
140
+ StatusText: 결과 문자열
141
+ ```
142
+
143
+ 자체 여백·판·최소 높이는 없다. 관련 행동 가까이에 두고 부모 Stack이 간격을 소유한다.
144
+ 화면 전체를 대체하는 로딩·실패·빈 상태는 해당 영역의 ScreenLayout/StateView/Notice로 구성한다.
145
+
146
+ ### 공개 props
147
+
148
+ | prop | 기본값 | 의미 |
149
+ | --- | --- | --- |
150
+ | `children: string` | 필수 | 제품의 지역화된 상태 문구. 빈 문자열도 허용 |
151
+ | `announcement` | `polite` | `none`·`polite`·`assertive`. 시각적 tone과 알림 우선순위는 독립 |
152
+ | `announceOnMount` | `false` | Native iOS 옵션. 조건부로 새로 나타난 행동 결과를 처음에도 알림 |
153
+ | 나머지 Text props·ref | Text와 같음 | Web role/live/atomic, Native live region은 StatusText가 소유 |
154
+
155
+ ### 지속 안내와 새 결과
156
+
157
+ ```tsx
158
+ // Native
159
+ import { StatusText } from "@hjmds/react-native/status-text";
160
+
161
+ // 계속 유지되는 노드: 최초 안내는 iOS에서 읽지 않고 이후 문자열 변경만 알린다.
162
+ <StatusText>{message}</StatusText>
163
+
164
+ // Native: 행동 뒤 조건부로 새로 마운트되는 결과는 opt-in한다.
165
+ {saved ? <StatusText announceOnMount>{t("save.complete")}</StatusText> : null}
166
+
167
+ <StatusText announcement="assertive" tone="danger">{errorMessage}</StatusText>
168
+ ```
169
+
170
+ Web은 `polite`를 `role="status"`, `assertive`를 `role="alert"`로 연결하고 `aria-live`와
171
+ `aria-atomic`을 설정한다. `none`은 `aria-live="off"`이고 상태 role을 붙이지 않는다.
172
+ 같은 live node의 텍스트를 갱신하는 것이 기본이다. Web에는 `announceOnMount` prop이 없으며,
173
+ 새로 마운트된 live region의 읽힘은 브라우저·보조기술에 따라 달라진다.
174
+
175
+ Native Android는 `accessibilityLiveRegion`만 사용한다. iOS는 앱이 active이고 비어 있지 않은 문구가
176
+ 바뀔 때 `announceForAccessibilityWithOptions`를 호출한다. `polite`는 queue, `assertive`는 즉시 우선순위다.
177
+ `none`, 같은 문구 재렌더, 빈 문구, 배경 변경은 발표하지 않는다. foreground 복귀나 announcement 모드 변경만으로
178
+ 과거 문구를 재생하지 않는다. 빈 문구로 지웠다가 같은 결과를 다시 표시하면 새 변경이다.
179
+ `announceOnMount`는 마운트 시점에 결정하며 나중에 true로 바꿔 초기 안내를 재생하지 않는다.
180
+
181
+ ### 경계
182
+
183
+ 동일 결과를 매 렌더 다시 알리기 위해 key를 바꾸지 않는다. Toast·Notice에서 이미 알리는 결과를 중복 알리지 않는다.
184
+ Showcase의 `PatternStatus`는 이 공개 컴포넌트를 가리키는 내부 별칭일 뿐이며 제품에서 Showcase 파일을 import하지 않는다.
185
+ DOM 속성·Native host 호출 검사는 실제 VoiceOver·TalkBack의 낭독 확인과 구분한다.
@@ -75,7 +75,7 @@ import { Timeline } from "@hjmds/react-native/data-display";
75
75
  | `items[].tone` | `neutral` · `info` · `success` · `attention` | `neutral` | `warning`·`danger`는 없다 |
76
76
  | `composeAccessibleName`(필수) | `(info: { position: number, total: number, label: string }) => string` | — | position은 1부터 |
77
77
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
78
- | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
78
+ | `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
79
79
  | 나머지 | Web `<ol>` 속성, Native `ViewProps`(`style` 제외) | — | — |
80
80
 
81
81
  ## 배치
@@ -94,7 +94,7 @@ import { Timeline } from "@hjmds/react-native/data-display";
94
94
  - 빈 배열, 중복 `id`, 앞뒤 공백이 있는 `id`, 빈 문자열 필드, 목록 밖 tone은 실행 중 오류를 던진다.
95
95
  - 접근 이름의 어순은 언어마다 다르므로 `composeAccessibleName`은 i18n 키로 만든다.
96
96
  - 제품 고유 색(구단 색 등)은 adapter에서 네 tone 중 하나로 먼저 매핑한다. dot 색을 직접 칠하지 않는다.
97
- - 배치는 `layoutStyle`로 한다. Web `className`/`style`과 Native의 deprecated `style`로 dot·connector·글자 색을 덮지 않는다.
97
+ - 배치는 `layoutStyle`로 한다. Web `className`/`style`과 Native에서 제거된 `style`로 dot·connector·글자 색을 덮지 않는다.
98
98
 
99
99
  ## 플랫폼 차이
100
100
 
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-10
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [Toast](../../toast.md), [Liquid Toast](../../../../react-native/docs/liquid-toast.md), `src/component-recipes.ts`(`toastRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드 토스트`, `실험/구성/피드백과 복구/백그라운드 작업 상태`
10
10
 
@@ -79,22 +79,23 @@ function useProfileSavedToast() {
79
79
 
80
80
  | prop | 값 | 기본값 | 설명 |
81
81
  | --- | --- | --- | --- |
82
- | descriptor | `id`·`description`·`closeLabel` 필수, `title`·`tone`·`priority`·`durationMs`·`action`·`announcement`·`presentation`·`media`·`progress` 선택 | — | 문구는 모두 `string`(ReactNode 아님). `media`만 예외 |
82
+ | descriptor | `id`·`description`·`closeLabel` 필수, `title`·`tone`·`priority`·`durationMs`·`action`·`press`·`announcement`·`presentation`·`media`·`progress` 선택 | — | 문구는 모두 `string`(ReactNode 아님). `media`만 예외 |
83
83
  | `media` | `unknown`(Web·Native에서 `ReactNode`를 넘긴다) | — | 미게시(1.17.2 이후). 장식 시각물(마스코트·아바타). tone 배지 자리를 44(`toastRecipe.media.size`) 원으로 대신하고 보조기술에서 숨긴다 |
84
84
  | `progress` | `{ steps: readonly string[]; current: number }` | — | 미게시(1.17.2 이후). 단계 2~5개, `current`는 0부터. 설명 아래 점+이름 줄로 그리고, 기본 낭독 끝에 현재 단계 이름을 붙인다. 범위 밖이면 throw |
85
85
  | `action` | `{ label, accessibilityLabel?, onAction: () => void, dismissOnAction? }` | — | 누르면 `onAction`, 기본으로 알림이 닫힌다 |
86
+ | `press` | `{ accessibilityLabel: string; onPress: () => void }` | 없음 | 미게시. 카드 본문·미디어·여백을 누르면 콜백 후 `press` 사유로 닫힌다. 실행·닫기 버튼과 스와이프는 열기 동작을 실행하지 않는다. Web은 Tab/Enter/Space, Native는 독립 접근성 버튼 |
86
87
  | `tone` | `neutral` · `info` · `success` · `warning` · `danger` | `neutral` | — |
87
88
  | `priority` | `normal` · `high` | — | 낭독 순서, 색과 무관 |
88
- | `durationMs` | ms · `null` | 3000 | 더 짧게 줘도 3000으로 올라간다. `null`은 수동으로 닫을 때까지 유지, `action`이 있으면 기본 유지 |
89
+ | `durationMs` | ms · `null` | 3000 | 더 짧게 줘도 3000으로 올라간다. `null`은 수동으로 닫을 때까지 유지, `action` 또는 `press`가 있으면 기본 유지 |
89
90
  | `placement` | `bottom` · `top` · `top-start` · `top-end` · `bottom-start` · `bottom-end` | `bottom` | — |
90
91
  | 큐 | `maxVisible` · `maxQueued` · `duplicatePolicy` · `timerUpdatePolicy` · `overflowPolicy` | 1 · 20 · `update` · `preserve` · `discard-oldest` | — |
91
92
  | `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" }` 중 하나 |
92
93
  | `dismiss` | Web `(id, reason) => boolean`, Native `(id, reason?) => boolean` | — | 닫았으면 `true`. Web은 `close(id)`도 있다. Native는 `pause`·`resume(id, reason?)`도 있다 |
93
- | 닫힘 사유 `ToastDismissReason` | `timeout` · `action` · `close-action` · `escape` · `swipe` · `programmatic` · `queue-overflow` · `interrupted` | — | Web `Toast` `onDismissRequest`·Native `onDismiss`가 받는다 |
94
+ | 닫힘 사유 `ToastDismissReason` | `timeout` · `action` · `press` · `close-action` · `escape` · `swipe` · `programmatic` · `queue-overflow` · `interrupted` | — | Web `Toast` `onDismissRequest`·Native `onDismiss`가 받는다 |
94
95
  | `toasts`/`defaultToasts` + `onToastsChange`(Native `ToastRegion`) | `readonly ToastDescriptor[]`, `(toasts) => void` | — | 큐를 제품 상태로 제어할 때 |
95
96
  | `safeAreaInsets`(Native) | `{ top?, bottom?, left?, right?, start?, end? }` | `{}` | — |
96
97
  | `layoutStyle`(Native `Toast`·`ToastRegion`) | `HjmCompositionStyleProp` | — | Web Toast·ToastProvider에는 없다(떠 있는 층이라 배치 대상이 아님) |
97
- | `style`·`toastStyle`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `placement`·`safeAreaInsets`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
98
+ | `style`·`toastStyle`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `placement`·`safeAreaInsets`. 현재 공개 props에서 제거되었다 |
98
99
 
99
100
  ### 디자인 프로필 상속
100
101
 
@@ -177,14 +178,14 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.xl`
177
178
  - 리퀴드는 `placement="top"`과 `maxVisible={1}`일 때만 동작하고, 아니면 `ToastRegion`이 `TypeError`를 던진다.
178
179
  어댑터는 render 밖에서 만들거나 memo한다.
179
180
  - ToastRegion 배치는 `layoutStyle={{ flex: 1 }}`로 쓴다. Native 예제도 이 경로를 사용한다.
180
- - Web `Toast` 단독 렌더는 나머지 HTML 속성(id·data-*·이벤트)을 루트에 전달한다(미게시(1.12.1 이후). 1.12.1은 `className`만 전달). `role`·`aria-labelledby`·`aria-describedby`·`data-tone`·`data-state`는 Toast가 정하므로 덮이지 않는다. 배치는 Provider를 쓴다.
181
+ - Web `Toast` 단독 렌더는 나머지 HTML 속성(id·data-*·이벤트)을 루트에 전달한다(1.13.0 변경. 1.12.1은 `className`만 전달). `role`·`aria-labelledby`·`aria-describedby`·`data-tone`·`data-state`는 Toast가 정하므로 덮이지 않는다. 배치는 Provider를 쓴다.
181
182
 
182
183
 
183
184
  ### 고정 아이콘과 큰 글자
184
185
 
185
186
  2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
186
187
 
187
- ### 프로필 행간 소비 — 미게시
188
+ ### 프로필 행간 소비 — 1.16.0 변경
188
189
 
189
190
  2026-10-07 소비 감사에서 Web 제목/설명의 1.35/1.45 고정 행간이 `typography.body.lineHeight`를
190
191
  우회하는 것을 확인했다. 두 slot은 기존 body 역할이므로 선택된 프로필의 `--hjm-type-body-line-height`를
@@ -192,3 +193,12 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.xl`
192
193
  일괄 기본 행간 변경은 기존 알림 레이아웃까지 바꾸므로 채택하지 않았다. Native 본문 행간은 이미 body
193
194
  토큰을 소비하며, 이번 Native 변경은 action 라벨의 ui 서체 역할만 명시한다. 알림 문구·큐·시간·닫기·
194
195
  행동·초점 계약은 바꾸지 않는다.
196
+
197
+ ### 카드에서 관련 화면 열기
198
+
199
+ 댓글·답장·작업 완료 알림에서 `press: { accessibilityLabel: t("reply.open"), onPress: openReply }`를
200
+ 같은 descriptor에 넣는다. `action`은 카드와 별개의 실행 버튼이 필요할 때만 함께 쓴다.
201
+ 본문·이미지·카드 여백 전체가 열기 영역이며 실행·닫기 버튼은 각각 자기 동작만 수행한다.
202
+ Web은 전체 영역의 투명한 button과 두 제어 버튼을 형제로, Native는 뒤쪽 Pressable과 위쪽 제어 버튼을
203
+ 독립적으로 배치한다. label은 동작 이름, 설명은 기존 알림 문구이며 기본 카드 배치와 크기는 바뀌지 않는다.
204
+ 라우터는 HJM이 가져오지 않고 제품 콜백이 실행한다. 화면 이동 뒤 provider가 없어져도 중복 실행하지 않는다.
@@ -64,7 +64,7 @@ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
64
64
  | `size` | `small` · `medium` | `medium` | medium은 최소 높이 44 |
65
65
  | `items[].disabled` | `boolean` | `false` | `disabled` 항목은 켜지지 않고 눌림 집합에도 들어가지 않는다. 사라진 id의 눌림 상태는 렌더 때 버려진다(`reconcileToggleGroupSelection`) |
66
66
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 묶음 컨테이너 배치. Web·Native 모두 |
67
- | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `size`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
67
+ | `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `size`. 현재 공개 props에서 제거되었다 |
68
68
 
69
69
  ## 배치
70
70
 
@@ -83,7 +83,7 @@ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
83
83
  - 라벨은 i18n 키로 넣고 짧게 둔다. 단일 선택 모드는 없다. 하나만 고르게 하려고 변환하지 않는다.
84
84
  - 눌림 표시 색은 recipe(`idle`/`pressed`)가 소유한다. 상태는 Web `aria-pressed`, Native `selected`로
85
85
  알리므로 색만으로 상태를 바꿔 그리지 않는다.
86
- - 배치는 `layoutStyle`(묶음 컨테이너)로 한다. Web `className`·Native의 deprecated `style`로 항목 모양을 덮지 않는다.
86
+ - 배치는 `layoutStyle`(묶음 컨테이너)로 한다. Web `className`·Native에서 제거된 `style`로 항목 모양을 덮지 않는다.
87
87
 
88
88
  ## 플랫폼 차이
89
89
 
@@ -91,7 +91,7 @@ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
91
91
  | --- | --- | --- |
92
92
  | 컨테이너 | `role="group"` + `aria-label` | `View` + `accessibilityLabel`(role 없음) |
93
93
  | 배치 | CSS(`hjm-toggle-group`) | 가로 `flexWrap: "wrap"` |
94
- | 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은 deprecated) |
94
+ | 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은 제거된 속성) |
95
95
  | 키보드 | 항목마다 tab stop(roving 없음) | 해당 없음 |
96
96
 
97
97
  ### 제품 테마의 모서리
@@ -72,7 +72,7 @@ Native: 없음. 계약상 Native는 `unsupported`이고 hover UI를 흉내 내
72
72
 
73
73
  | 항목 | 값 | 근거 |
74
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` |
75
+ | 크기 | 말풍선 안쪽 여백 사방 `spacing.xs` 8(`tooltipRecipe.surface.padding`; 2026-10-06까지 Web은 좌우 12였다, 1.13.0 변경), 모서리 `radius.sm` 8, 최대 폭 280(`tooltipRecipe.content.maxWidth`, 화면 폭 − 24를 넘지 않음). trigger는 그 자체로 최소 터치 영역 44를 지켜야 한다(Tooltip은 영역을 넓히지 않는다). 보통 [IconButton](icon-button.md) | `tooltipRecipe.surface`, `.hjm-tooltip__content` |
76
76
  | 간격 | trigger와 4(`positioning.sideOffset` = `spacing.xxs`), 뷰포트 가장자리와 최소 12(`positioning.collisionPadding` = `spacing.sm`) | `tooltipRecipe.positioning`, `react/src/overlays.tsx`(`Tooltip`) |
77
77
  | 순서·정렬 | trigger 바로 옆에 붙는다. 한 줄에 Tooltip 달린 아이콘 버튼이 여럿이면 하나만 보이고, 300ms 안에 옆으로 옮기면 바로 열린다 | `react/src/overlays.tsx`(`Tooltip`) |
78
78
  | 고정·스크롤 | 떠 있는 층(`position: fixed`)이라 문서 흐름에 자리를 만들지 않고 trigger 크기도 바꾸지 않는다. 공간이 모자라면 반대쪽으로 뒤집힌다 | `.hjm-tooltip__content`, `react/src/portal.tsx` |
@@ -75,10 +75,11 @@ import { TopBar, TopBarAction } from "@hjmds/react-native/top-bar";
75
75
  | `titleLeading` | `ReactNode` | — | 제목 앞 작은 시각 요소 |
76
76
  | `leading` / `actions`(별칭 `trailing`) | `ReactNode` | — | 둘 중 한 이름만 쓴다 |
77
77
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
78
- | Native `style`·`leadingStyle`·`titleStyle`·`trailingStyle` | `StyleProp` | — | deprecated — `layoutStyle` 또는 `centered`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
78
+ | Native `style`·`leadingStyle`·`titleStyle`·`trailingStyle` | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `centered`. 현재 공개 props에서 제거되었다 |
79
79
  | `TopBarAction` `intent` | `button` · `link` | `button` | `button`은 `onPress: (event) => void`, `link`는 `destination` + `onNavigate?: (destination: LinkDestination) => void \| Promise<void>` 또는 `renderLink` |
80
80
  | `TopBarAction` `labelVisibility` | `visible` · `accessibility-only` | `visible` | — |
81
- | `TopBarAction` `layoutStyle` | `HjmCompositionStyleProp` | — | `style`·`labelStyle`은 deprecated |
81
+ | `TopBarAction` `layoutStyle` | `HjmCompositionStyleProp` | — | `style`·`labelStyle`은 제거된 속성 |
82
+ | Native `TopBarAction` `renderAction`·`renderLink` | 렌더 콜백 | — | 전달받은 control props의 `style`·접근성·동작은 정식 host 입력이다. 이를 host에 그대로 연결하며 제거된 소비자 override prop과 구분한다 |
82
83
  | `NavigationBar` | `label`·`brand`·`children` 필수, `actions` 선택 | — | Web만 `layoutStyle`도 받는다 |
83
84
 
84
85
  ## 배치
@@ -108,7 +109,7 @@ NavigationBar의 Native 프레임 모서리는 Provider의 `tokens.radius.xl`을
108
109
  - `title` 없이 `titleLeading`·`onTitleClick`/`onTitlePress`·`titleAccessibilityLabel`을 주면 `TypeError`다.
109
110
  - 이동은 슬롯 안의 Link(Native는 `intent="link"`)로, 제목 클릭은 실제 버튼으로 표현한다.
110
111
  - 화면 이름·행동 라벨은 i18n 키로, 아이콘은 제품 소유다. 높이·간격·제목 색은 recipe가 정한다.
111
- 배치는 `layoutStyle`로만 하고 Native의 deprecated 슬롯 스타일로 덮지 않는다.
112
+ 배치는 `layoutStyle`로만 하고 Native에서 제거된 슬롯 스타일로 덮지 않는다.
112
113
  - 큰 글자에서 Native는 슬롯 구조를 다시 만들어 슬롯 안 로컬 상태·포커스가 초기화될 수 있다.
113
114
  유지할 상태는 TopBar 밖에 둔다.
114
115
  - `NavigationBar`는 `label`(빈 문자열이면 throw)·`brand`·`children` 필수, `actions` 선택이다.
@@ -120,7 +121,7 @@ NavigationBar의 Native 프레임 모서리는 Provider의 `tokens.radius.xl`을
120
121
  | --- | --- | --- |
121
122
  | 루트 | `div`(Dialog 안에서도 landmark를 추가하지 않음), ref·HTML 속성 전달 | `accessibilityRole="toolbar"` `View`, ref 없음 |
122
123
  | 제목 접근성 | `h1`~`h6` | `accessibilityRole="header"`, `titleAccessibilityHint`(`onTitlePress` 필수) |
123
- | 슬롯 스타일 | `className`·`style`, 배치는 `layoutStyle` | 배치는 `layoutStyle`(`style`·`leadingStyle`·`titleStyle`·`trailingStyle`은 deprecated) |
124
+ | 슬롯 스타일 | `className`·`style`, 배치는 `layoutStyle` | 배치는 `layoutStyle`(`style`·`leadingStyle`·`titleStyle`·`trailingStyle`은 제거된 속성) |
124
125
  | 큰 글자 | CSS가 제목을 다음 행으로 | 글자 배율이 `largeTextThreshold` 이상이면 leading+제목 행 아래에 행동 행 |
125
126
  | 행동 컴포넌트 | 일반 `IconButton`·`Link` | `TopBarAction`(아이콘 + 마이크로 라벨, 44pt) |
126
127
  | `NavigationBar` 배경 | 지원 시 blur + 92% 불투명 | 불투명 surface(blur 없음) |
@@ -60,7 +60,7 @@ import { Top } from "@hjmds/react-native/top";
60
60
  | `descriptor.headingLevel` | `1` · `2` · `3` | `1` | 한 라우트가 여러 화면을 담을 때만 낮춘다(Web만 반영) |
61
61
  | `trailing` | `ReactNode`(요소 하나) | — | 제목 줄을 같이 쓰는 보조 행동. 공간이 모자라거나 큰 글자면 아래로 내려간다 |
62
62
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
63
- | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
63
+ | `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
64
64
 
65
65
  ## 배치
66
66
 
@@ -78,7 +78,7 @@ import { Top } from "@hjmds/react-native/top";
78
78
  - `eyebrow`에는 제목을 한정하는 짧은 말(카테고리·단계)만 넣고 제목에 없는 새 정보를 넣지 않는다.
79
79
  - 주 행동을 `trailing`에 두지 않는다. 화면의 주 행동은 [BottomCTA](bottom-cta.md)나 본문 [Button](button.md)이다.
80
80
  - 상태가 없다. 제목을 바꾸려면 다른 문자열을 넘긴다.
81
- - 배치는 `layoutStyle`로 한다(Web `className`도 받는다). Native `style`은 deprecated다. 글자 크기·색은 recipe가 정하므로 덮지 않는다.
81
+ - 배치는 `layoutStyle`로 한다(Web `className`도 받는다). Native `style`은 제거된 속성이다. 글자 크기·색은 recipe가 정하므로 덮지 않는다.
82
82
 
83
83
  ## 플랫폼 차이
84
84
 
@@ -92,4 +92,4 @@ import { Top } from "@hjmds/react-native/top";
92
92
 
93
93
  2026-10-07 소비 감사에서 두 renderer의 제목 크기·행간·굵기가 foundation으로 확정된 recipe를 직접 읽어 프로필을 우회했다. `large`는 가까운 프로필의 `heading.level2`, `medium`은 `typography.titleLarge`를 읽고 프로필이 없으면 기존 recipe를 유지한다. 시각 역할은 `headingLevel`과 독립이며 display 서체·제목 의미·보조 행동·초점은 바꾸지 않는다.
94
94
 
95
- 프로필 연결은 1.15.0 이후 미게시 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
95
+ 프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
@@ -73,7 +73,7 @@ import { TransferList } from "@hjmds/react-native/transfer-list";
73
73
  | `items` | `{ id, label, textValue, description?, disabled? }[]` | — | `disabled` 항목은 선택도 이동도 되지 않는다 |
74
74
  | 패널별 체크 선택 | 내부 상태 | — | 이동 전 임시 상태라 prop으로 받지 않는다. 이동 버튼은 해당 패널에 선택이 있을 때만 활성화된다 |
75
75
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 최상위 컨테이너 배치. Web·Native 모두 |
76
- | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
76
+ | `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
77
77
 
78
78
  ## 배치
79
79
 
@@ -101,7 +101,8 @@ Web ≥ 480 Web < 480 · Native
101
101
 
102
102
  - `labels`의 여섯 문구는 모두 제품 i18n으로 넣는다. HJM은 문장을 만들지 않는다.
103
103
  - 이동 결과 낭독은 제품 몫이다. `onMove(movedIds, direction)`로 "2명 이동" 같은 문장을 만들어 알린다.
104
- - 배치는 `layoutStyle`(최상위 컨테이너)로 한다. Web `className`·Native의 deprecated `style`로 패널·행 모양을 덮지 않는다.
104
+ - 빈 패널의 안내는 해당 목록 내부에서 중앙 정렬한다. Native에서 패널에 남는 높이가 있으면 안내가 그 영역의 세로 중앙을 채운다.
105
+ - 배치는 `layoutStyle`(최상위 컨테이너)로 한다. Web `className`·Native에서 제거된 `style`로 패널·행 모양을 덮지 않는다.
105
106
 
106
107
  ## 플랫폼 차이
107
108
 
@@ -112,10 +113,10 @@ Web ≥ 480 Web < 480 · Native
112
113
  | 키보드 | 화살표·Home/End 이동, Space 선택, Enter로 그 행만 이동 | 없음 |
113
114
  | 이동 후 포커스 | 빈 자리로 올라온 행, 비면 빈 상태 문구 | 옮기지 않음 |
114
115
  | 패널 비었을 때 전체 선택 | 활성 | `disabled` |
115
- | 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은 deprecated) |
116
+ | 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은 제거된 속성) |
116
117
 
117
118
  ### 프로필 모서리·제목 소비
118
119
 
119
120
  2026-10-07 소비 감사에서 Native 패널의 모서리 12가 프로필을 우회했다. Web 패널의 기존 `radius.md`와 같은 역할을 Native도 가까운 프로필에서 읽으며, 프로필이 없으면 기존 12를 유지한다. 선택 집합·이동·비활성 항목·초점 계약은 바꾸지 않는다.
120
121
 
121
- 프로필 연결은 1.15.0 이후 미게시 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
122
+ 프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
@@ -70,7 +70,7 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
70
70
  | `onCancel` / `onRetry` | `(id: string) => void` | — | descriptor `id`를 받는다. `uploading`·`error` 상태에서는 각각 필수 |
71
71
  | `leading` | `ReactNode` | — | 아이콘·썸네일(장식) |
72
72
  | `layoutStyle` | `HjmCompositionStyleProp` | — | 행 바깥 배치. Web·Native 모두 |
73
- | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
73
+ | `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
74
74
 
75
75
  ## 배치
76
76
 
@@ -96,7 +96,7 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
96
96
 
97
97
  | 항목 | Web | Native |
98
98
  | --- | --- | --- |
99
- | 루트 | `role="group"`, `aria-label`=파일명, `HTMLAttributes`·`ref`·`layoutStyle` 전달 | 일반 `View`, `layoutStyle`(`style`은 deprecated) |
99
+ | 루트 | `role="group"`, `aria-label`=파일명, `HTMLAttributes`·`ref`·`layoutStyle` 전달 | 일반 `View`, `layoutStyle`(`style`은 제거된 속성) |
100
100
  | 상태 낭독 | 상태 문장 live region | 파일 정보 묶음이 한 요소(`busy` state, value=상태 문장), 액션은 별도 버튼 |
101
101
  | `leading` | `aria-hidden` | 접근성 트리에서 숨김 |
102
102
 
@@ -104,4 +104,4 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
104
104
 
105
105
  2026-10-07 소비 감사에서 Native 행의 모서리 12가 프로필을 우회했다. Web의 기존 `radius.md`와 같은 역할을 Native도 가까운 프로필에서 읽으며, 프로필이 없으면 기존 12를 유지한다. 업로드 상태·취소/재시도·진행 알림은 바꾸지 않는다.
106
106
 
107
- 프로필 연결은 1.15.0 이후 미게시 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
107
+ 프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
@@ -93,6 +93,7 @@ import { VirtualList } from "@hjmds/react-native/virtual-list";
93
93
  - `rowHeight`는 현재 글자 배율에서 행 내용이 들어가는 값으로 제품이 정한다. 행 내용이 넘쳐도 잘리거나 겹칠 뿐
94
94
  늘어나지 않는다. 높이를 확정할 수 없으면 List를 쓴다.
95
95
  - 행 내용·데이터 가져오기는 제품 소유, 창 계산·키보드 탐색은 HJM 소유다.
96
+ - `empty` 안내는 `height`로 정한 목록 내부 영역의 가로·세로 중앙에 놓인다. Web에서 포인터나 프로그램으로 행에 초점을 옮긴 뒤에도 방향키는 그 행부터 이어진다.
96
97
 
97
98
  ## 플랫폼 차이
98
99
 
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.14.0
7
- - 검토일: 2026-10-07
7
+ - 검토일: 2026-10-13
8
8
  - 근거: 공개 API를 사용하는 `showcase/*/reference-adoption-previews.tsx`
9
9
  - 스토리북: `배포/구성/피드백과 복구/버튼 완료 피드백`
10
10
 
@@ -12,7 +12,7 @@
12
12
 
13
13
  ## 언제 쓰나
14
14
 
15
- 입력을 유지하며 저장의 진행·성공·재시도 가능 실패를 보여 줄 때 쓴다. 별도 신규 wrapper API가 아니라 아래 공개 컴포넌트의 조합 규격이다.
15
+ 입력을 유지하며 저장의 진행·성공·재시도 가능 실패를 보여 줄 때 쓴다. 화면 wrapper 없이 아래 공개 컴포넌트와 `resolveSaveFeedback`을 조합한다.
16
16
 
17
17
  ## 구성 요소
18
18
 
@@ -50,16 +50,50 @@
50
50
  | 진행 중 | 실제 작업 상태에 맞는 loading 또는 열린 도구 | 중복 요청 차단, 입력 유지 |
51
51
  | 실패 | 문구와 가능한 재시도 | 원래 입력/파일 유지, 결과 안내 |
52
52
 
53
+ ## 현재 초안과 저장 응답 연결
54
+
55
+ `@hjmds/design-contracts/action-session`의 `resolveSaveFeedback`은 Web/Native 공통 순수 함수다.
56
+ 알림 설정과 첫 기록 화면에서 반복하던 저장 응답·현재 초안 분기를 공유한다.
57
+ `phase`는 기존 action session 또는 제품 mutation의 상태, `dirty`는 **현재 초안과 마지막 확정 값**의 비교,
58
+ `valid`는 제품 검증 결과, `hasSaved`는 확정 값 존재 여부다. 낙관적 값과 비교해 저장 완료로 판단하지 않는다.
59
+ 선택적 `disabled`는 권한 등 제품의 실행 잠금이며 기본값은 false다.
60
+
61
+ | 결과 | 의미 |
62
+ | --- | --- |
63
+ | `pending` | 요청 진행 중. 입력을 유지하고 제출은 막음 |
64
+ | `saved` | 성공한 확정 값과 현재 초안이 같음 |
65
+ | `saved-with-changes` | 이전 제출은 성공했지만 현재 초안은 아직 미저장 |
66
+ | `error` | 요청 실패 후 변경이 남음. 현재 초안을 다시 저장할 수 있음 |
67
+ | `unsaved` | 아직 저장하지 않은 변경이 있음 |
68
+ | `unchanged` | 새 변경이 없음. 저장값으로 되돌린 뒤에는 이전 오류를 다시 표시하지 않음 |
69
+
70
+ `feedback`과 별도로 `currentSaved`는 현재 초안이 확정 값과 같은지 나타낸다.
71
+ `canSubmit`은 변경됨·유효함·진행 중 아님·잠금 아님을 모두 만족할 때만 true다.
72
+ 이 함수는 요청 잠금이나 응답 무효화를 구현하지 않는다. 기존 `session.run`이 중복 실행을 막고
73
+ `session.reset`이 늦은 응답을 무효화한다. 버튼의 `disabled`만으로 요청 경합을 제어하지 않는다.
74
+ 제품은 비교·문구·영구 저장을 소유하고, 실패 후 현재 초안을 저장할 때 새 제출값을 캡처해 `run`한다.
75
+ `retry`는 기존 실패 요청의 값 재시도이므로 현재 초안 저장과 구분한다.
76
+
77
+ ```ts
78
+ import { resolveSaveFeedback } from "@hjmds/design-contracts/action-session";
79
+ const feedback = resolveSaveFeedback({
80
+ phase: state.status,
81
+ dirty: !sameDraft(draft, confirmedValue),
82
+ valid: isValid(draft),
83
+ hasSaved,
84
+ });
85
+ ```
86
+
53
87
  ## 코드 골격
54
88
 
55
89
  ```tsx
56
90
  // Web
57
- <Button loading={busy} onClick={save}>{busy ? pendingLabel : saveLabel}</Button>
91
+ <Button loading={feedback.pending} disabled={!feedback.canSubmit} onClick={save}>{feedback.pending ? pendingLabel : saveLabel}</Button>
58
92
  ```
59
93
 
60
94
  ```tsx
61
95
  // Native
62
- <Button loading={busy} onPress={save}>{busy ? pendingLabel : saveLabel}</Button>
96
+ <Button loading={feedback.pending} disabled={!feedback.canSubmit} onPress={save}>{feedback.pending ? pendingLabel : saveLabel}</Button>
63
97
  ```
64
98
 
65
99
  Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native` entry를 쓴다.
@@ -67,11 +101,13 @@ Button의 실행 콜백은 Web `onClick`, Native `onPress`로 연결한다. 위
67
101
 
68
102
  예제 응답은 useDemoAction의 인위 지연이며 실제 네트워크·영구 저장이 아니다.
69
103
  완료 문구는 응답 확정 뒤 표시한다. 저장 중 입력 편집이 가능하므로, 저장 결과는 현재
70
- 초안으로 바꿔 쓰지 말고 응답 값으로 표시한다. 실패 체험 설정은 요청 중 바꾸지 않는다.
104
+ 초안으로 바꿔 쓰지 말고 응답 값으로 표시한다. 현재 초안과 저장한 제목이 다르면 완료 체크를 제거하고
105
+ “현재 제목은 아직 저장하지 않았어요”와 마지막 저장 제목을 따로 보여 준다. 2026-10-13 실제 예제에서
106
+ 진행 중 편집한 제목에도 이전 요청의 완료 체크가 남는 경로를 확인해 구분했다. 실패 체험 설정은 요청 중 바꾸지 않는다.
71
107
 
72
108
  ## 플랫폼 차이
73
109
 
74
110
  | 항목 | Web | Native |
75
111
  | --- | --- | --- |
76
112
  | 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
77
- | 큰 글자·좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |
113
+ | 좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |