@hjmds/design-contracts 1.26.1 → 1.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/dist/agreement.d.ts +1 -0
  2. package/dist/agreement.d.ts.map +1 -1
  3. package/dist/agreement.js +2 -1
  4. package/dist/agreement.js.map +1 -1
  5. package/dist/auth-screen.d.ts +1 -1
  6. package/dist/behaviors.d.ts +1 -1
  7. package/dist/calendar.d.ts +1 -0
  8. package/dist/calendar.d.ts.map +1 -1
  9. package/dist/calendar.js +4 -1
  10. package/dist/calendar.js.map +1 -1
  11. package/dist/catalog.d.ts +78 -30
  12. package/dist/catalog.d.ts.map +1 -1
  13. package/dist/catalog.js +10 -5
  14. package/dist/catalog.js.map +1 -1
  15. package/dist/chart.d.ts +2 -0
  16. package/dist/chart.d.ts.map +1 -1
  17. package/dist/chart.js +1 -1
  18. package/dist/chart.js.map +1 -1
  19. package/dist/component-definitions.d.ts +1 -0
  20. package/dist/component-definitions.d.ts.map +1 -1
  21. package/dist/component-definitions.js +1 -0
  22. package/dist/component-definitions.js.map +1 -1
  23. package/dist/component-recipes.d.ts +53 -6
  24. package/dist/component-recipes.d.ts.map +1 -1
  25. package/dist/component-recipes.js +64 -10
  26. package/dist/component-recipes.js.map +1 -1
  27. package/dist/data-table.d.ts +11 -0
  28. package/dist/data-table.d.ts.map +1 -1
  29. package/dist/data-table.js +5 -0
  30. package/dist/data-table.js.map +1 -1
  31. package/dist/description-list.d.ts +3 -0
  32. package/dist/description-list.d.ts.map +1 -1
  33. package/dist/description-list.js +5 -1
  34. package/dist/description-list.js.map +1 -1
  35. package/dist/design-profile-layout.d.ts +28 -0
  36. package/dist/design-profile-layout.d.ts.map +1 -1
  37. package/dist/design-profile-layout.js +27 -3
  38. package/dist/design-profile-layout.js.map +1 -1
  39. package/dist/design-profile.d.ts.map +1 -1
  40. package/dist/design-profile.js +40 -8
  41. package/dist/design-profile.js.map +1 -1
  42. package/dist/field-group.d.ts +13 -0
  43. package/dist/field-group.d.ts.map +1 -1
  44. package/dist/field-group.js +17 -2
  45. package/dist/field-group.js.map +1 -1
  46. package/dist/foundations.d.ts +24 -11
  47. package/dist/foundations.d.ts.map +1 -1
  48. package/dist/foundations.js +40 -7
  49. package/dist/foundations.js.map +1 -1
  50. package/dist/heading.d.ts +5 -5
  51. package/dist/image-choice.d.ts +23 -0
  52. package/dist/image-choice.d.ts.map +1 -0
  53. package/dist/image-choice.js +26 -0
  54. package/dist/image-choice.js.map +1 -0
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +1 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/masonry.d.ts +8 -0
  60. package/dist/masonry.d.ts.map +1 -1
  61. package/dist/masonry.js +12 -1
  62. package/dist/masonry.js.map +1 -1
  63. package/dist/number-field.d.ts +2 -2
  64. package/dist/number-field.d.ts.map +1 -1
  65. package/dist/number-field.js +4 -2
  66. package/dist/number-field.js.map +1 -1
  67. package/dist/qr-code-recipe.d.ts +1 -0
  68. package/dist/qr-code-recipe.d.ts.map +1 -1
  69. package/dist/qr-code-recipe.js +3 -1
  70. package/dist/qr-code-recipe.js.map +1 -1
  71. package/dist/recipes.d.ts +1 -1
  72. package/dist/recipes.d.ts.map +1 -1
  73. package/dist/recipes.js +1 -1
  74. package/dist/recipes.js.map +1 -1
  75. package/dist/result.d.ts +9 -0
  76. package/dist/result.d.ts.map +1 -1
  77. package/dist/result.js +5 -0
  78. package/dist/result.js.map +1 -1
  79. package/dist/screen-patterns.d.ts +139 -2
  80. package/dist/screen-patterns.d.ts.map +1 -1
  81. package/dist/screen-patterns.js +140 -1
  82. package/dist/screen-patterns.js.map +1 -1
  83. package/dist/sheet.d.ts +15 -1
  84. package/dist/sheet.d.ts.map +1 -1
  85. package/dist/sheet.js +20 -1
  86. package/dist/sheet.js.map +1 -1
  87. package/dist/showcase.d.ts +27 -27
  88. package/dist/showcase.js +27 -27
  89. package/dist/showcase.js.map +1 -1
  90. package/dist/tag.d.ts +10 -2
  91. package/dist/tag.d.ts.map +1 -1
  92. package/dist/tag.js +3 -2
  93. package/dist/tag.js.map +1 -1
  94. package/dist/tags-input.d.ts +6 -0
  95. package/dist/tags-input.d.ts.map +1 -1
  96. package/dist/tags-input.js +3 -1
  97. package/dist/tags-input.js.map +1 -1
  98. package/dist/text-formats.d.ts +2 -2
  99. package/dist/top.d.ts +2 -2
  100. package/dist/transfer-list.d.ts +1 -1
  101. package/dist/transfer-list.d.ts.map +1 -1
  102. package/dist/transfer-list.js +2 -1
  103. package/dist/transfer-list.js.map +1 -1
  104. package/dist/upload-item.d.ts +1 -10
  105. package/dist/upload-item.d.ts.map +1 -1
  106. package/dist/upload-item.js +1 -2
  107. package/dist/upload-item.js.map +1 -1
  108. package/dist/version.d.ts +1 -1
  109. package/dist/version.js +1 -1
  110. package/dist/version.js.map +1 -1
  111. package/docs/activity-chip.md +4 -3
  112. package/docs/catalog-freeze.json +5 -2
  113. package/docs/chart.md +1 -1
  114. package/docs/consumer-policy.md +13 -6
  115. package/docs/description-list.md +4 -0
  116. package/docs/design-profile.md +28 -2
  117. package/docs/font-roles.md +1 -1
  118. package/docs/generated/component-maturity.md +5 -4
  119. package/docs/generated/renderer-evidence.json +92 -6
  120. package/docs/generated/renderer-evidence.md +5 -3
  121. package/docs/generated/showcase-manifest.json +49 -1
  122. package/docs/gravity-letters.md +7 -2
  123. package/docs/masonry.md +4 -2
  124. package/docs/screen-patterns.md +5 -2
  125. package/docs/showcase.md +7 -9
  126. package/docs/stable-promotion.md +13 -1
  127. package/docs/usage/README.md +40 -10
  128. package/docs/usage/SELECTION.md +30 -4
  129. package/docs/usage/STANDARD.md +11 -2
  130. package/docs/usage/components/account-switcher.md +98 -0
  131. package/docs/usage/components/activity-chip.md +4 -4
  132. package/docs/usage/components/badge.md +1 -1
  133. package/docs/usage/components/bezier-editor.md +99 -0
  134. package/docs/usage/components/bottom-cta.md +2 -1
  135. package/docs/usage/components/button.md +26 -12
  136. package/docs/usage/components/carousel.md +4 -4
  137. package/docs/usage/components/chart.md +1 -0
  138. package/docs/usage/components/chat-message.md +18 -12
  139. package/docs/usage/components/chat-screen.md +6 -3
  140. package/docs/usage/components/cohort-retention-table.md +83 -0
  141. package/docs/usage/components/collapsible.md +7 -2
  142. package/docs/usage/components/comment-thread-row.md +102 -0
  143. package/docs/usage/components/comment-thread-screen.md +27 -13
  144. package/docs/usage/components/composition-layout.md +1 -0
  145. package/docs/usage/components/context-menu.md +32 -15
  146. package/docs/usage/components/conversation-row.md +11 -4
  147. package/docs/usage/components/conversion-funnel.md +85 -0
  148. package/docs/usage/components/correlation-matrix.md +80 -0
  149. package/docs/usage/components/data-table.md +4 -0
  150. package/docs/usage/components/description-list.md +2 -1
  151. package/docs/usage/components/editor-screen.md +9 -5
  152. package/docs/usage/components/effect-surface.md +4 -2
  153. package/docs/usage/components/empty-state.md +2 -2
  154. package/docs/usage/components/event-attendance-card.md +104 -0
  155. package/docs/usage/components/expandable-search.md +105 -0
  156. package/docs/usage/components/file-picker.md +5 -0
  157. package/docs/usage/components/filter-recovery.md +97 -0
  158. package/docs/usage/components/follow-profile-card.md +100 -0
  159. package/docs/usage/components/heading.md +6 -2
  160. package/docs/usage/components/histogram-explorer.md +89 -0
  161. package/docs/usage/components/icon-button.md +18 -2
  162. package/docs/usage/components/icon-tile.md +12 -4
  163. package/docs/usage/components/image-choice-group.md +81 -0
  164. package/docs/usage/components/import-field-mapping.md +94 -0
  165. package/docs/usage/components/inline-edit.md +105 -0
  166. package/docs/usage/components/keyboard-avoidance-context.md +60 -0
  167. package/docs/usage/components/keyboard-avoiding.md +5 -1
  168. package/docs/usage/components/list-detail-screen.md +4 -4
  169. package/docs/usage/components/list.md +1 -1
  170. package/docs/usage/components/masonry.md +6 -7
  171. package/docs/usage/components/media-selection-screen.md +7 -5
  172. package/docs/usage/components/media-transport.md +104 -0
  173. package/docs/usage/components/message-composer.md +13 -3
  174. package/docs/usage/components/moderation-screen.md +5 -2
  175. package/docs/usage/components/notification-inbox-screen.md +3 -3
  176. package/docs/usage/components/notification-row.md +64 -8
  177. package/docs/usage/components/onboarding-screen.md +1 -1
  178. package/docs/usage/components/overview-screen.md +9 -3
  179. package/docs/usage/components/pagination.md +18 -3
  180. package/docs/usage/components/password-rules-field.md +90 -0
  181. package/docs/usage/components/permission-screen.md +1 -1
  182. package/docs/usage/components/photo-source-sheet.md +1 -1
  183. package/docs/usage/components/popover.md +6 -3
  184. package/docs/usage/components/pricing-choice.md +85 -0
  185. package/docs/usage/components/profile-summary.md +56 -8
  186. package/docs/usage/components/progress.md +3 -3
  187. package/docs/usage/components/qr-code.md +5 -9
  188. package/docs/usage/components/repeatable-field-group.md +92 -0
  189. package/docs/usage/components/reply-composer.md +83 -0
  190. package/docs/usage/components/reply-message.md +85 -0
  191. package/docs/usage/components/result.md +3 -3
  192. package/docs/usage/components/saved-items-screen.md +7 -5
  193. package/docs/usage/components/screen-layout.md +21 -10
  194. package/docs/usage/components/search-field.md +1 -1
  195. package/docs/usage/components/section.md +2 -2
  196. package/docs/usage/components/selection-action-bar.md +89 -0
  197. package/docs/usage/components/settings-screen.md +13 -11
  198. package/docs/usage/components/sheet.md +19 -7
  199. package/docs/usage/components/shortcut-recorder.md +77 -0
  200. package/docs/usage/components/split-action.md +94 -0
  201. package/docs/usage/components/streaming-answer.md +94 -0
  202. package/docs/usage/components/switch.md +6 -2
  203. package/docs/usage/components/table-column-settings.md +88 -0
  204. package/docs/usage/components/target-summary.md +79 -0
  205. package/docs/usage/components/text.md +11 -5
  206. package/docs/usage/components/toggle-feedback-button.md +94 -0
  207. package/docs/usage/components/top-bar.md +8 -12
  208. package/docs/usage/components/transfer-task.md +87 -0
  209. package/docs/usage/compositions/action-recovery-optimistic.md +18 -14
  210. package/docs/usage/compositions/action-recovery-save.md +18 -14
  211. package/docs/usage/compositions/action-recovery-undo.md +18 -14
  212. package/docs/usage/compositions/activity-status.md +1 -1
  213. package/docs/usage/compositions/adaptive-content.md +2 -1
  214. package/docs/usage/compositions/collection-detail.md +6 -5
  215. package/docs/usage/compositions/collection-editing.md +13 -6
  216. package/docs/usage/compositions/command-records.md +5 -4
  217. package/docs/usage/compositions/communication.md +7 -5
  218. package/docs/usage/compositions/compound-controls.md +7 -3
  219. package/docs/usage/compositions/content-transition-comparison.md +10 -5
  220. package/docs/usage/compositions/data-layouts.md +12 -16
  221. package/docs/usage/compositions/data-workflows.md +7 -4
  222. package/docs/usage/compositions/date-time-selection.md +5 -5
  223. package/docs/usage/compositions/design-profile-comparison.md +22 -6
  224. package/docs/usage/compositions/disclosure.md +14 -9
  225. package/docs/usage/compositions/document-resource.md +1 -1
  226. package/docs/usage/compositions/environment-matrix.md +16 -12
  227. package/docs/usage/compositions/expo-interactions.md +9 -8
  228. package/docs/usage/compositions/family-drawer.md +9 -5
  229. package/docs/usage/compositions/field-group.md +7 -3
  230. package/docs/usage/compositions/interaction-adapters.md +11 -9
  231. package/docs/usage/compositions/interaction-flow-apply.md +17 -15
  232. package/docs/usage/compositions/interaction-flow-search.md +11 -7
  233. package/docs/usage/compositions/live-list.md +12 -8
  234. package/docs/usage/compositions/navigation-bar-collection.md +8 -3
  235. package/docs/usage/compositions/optional-motion.md +2 -2
  236. package/docs/usage/compositions/paper-surface.md +11 -6
  237. package/docs/usage/compositions/purpose-input-comment.md +10 -4
  238. package/docs/usage/compositions/purpose-input-message.md +10 -4
  239. package/docs/usage/compositions/reference-first.md +4 -4
  240. package/docs/usage/compositions/reference-review.md +4 -4
  241. package/docs/usage/compositions/reference-settings.md +4 -4
  242. package/docs/usage/compositions/selection-motion.md +2 -1
  243. package/docs/usage/compositions/social-compositions.md +7 -6
  244. package/docs/usage/compositions/stea-event-ticket.md +2 -4
  245. package/docs/usage/compositions/stea-order-progress.md +19 -21
  246. package/docs/usage/compositions/stea-pixel-empty.md +26 -26
  247. package/docs/usage/compositions/task-controls.md +7 -2
  248. package/docs/usage/compositions/texture-comparison.md +5 -5
  249. package/docs/usage/compositions/toast-layout.md +7 -3
  250. package/docs/usage/compositions/toggle-feedback.md +1 -1
  251. package/docs/usage/compositions/visual-foundations.md +10 -5
  252. package/docs/usage/compositions/web-additions.md +5 -1
  253. package/docs/usage/screens/common-chat.md +5 -3
  254. package/docs/usage/screens/common-comments.md +14 -9
  255. package/docs/usage/screens/common-conversation-list.md +109 -0
  256. package/docs/usage/screens/common-inbox.md +5 -3
  257. package/docs/usage/screens/common-login.md +7 -4
  258. package/docs/usage/screens/common-profile.md +16 -12
  259. package/docs/usage/screens/common-saved.md +12 -8
  260. package/docs/usage/screens/common-search.md +16 -8
  261. package/docs/usage/screens/common-settings.md +12 -11
  262. package/docs/usage/screens/common-shell.md +12 -10
  263. package/docs/usage/screens/dashboard.md +59 -56
  264. package/docs/usage/screens/discovery-gallery.md +18 -9
  265. package/docs/usage/screens/flow-collection.md +10 -6
  266. package/docs/usage/screens/flow-editor.md +9 -6
  267. package/docs/usage/screens/flow-media.md +12 -7
  268. package/docs/usage/screens/flow-moderation.md +9 -3
  269. package/docs/usage/screens/flow-onboarding.md +13 -8
  270. package/docs/usage/screens/flow-permission.md +5 -3
  271. package/docs/usage/screens/landing.md +20 -14
  272. package/docs/usage/screens/mockup-studio.md +100 -88
  273. package/docs/usage/screens/notification-settings.md +9 -7
  274. package/docs/usage/screens/product-bento.md +22 -10
  275. package/docs/usage/screens/reference-comparison.md +2 -2
  276. package/docs/usage/tokens/font-roles.md +18 -2
  277. package/docs/usage/tokens/typography.md +22 -11
  278. package/package.json +7 -1
@@ -23,25 +23,26 @@
23
23
  | HjmProvider/HjmNativeProvider | 앱 소유 profile 한 번 주입 | [테마](../../theming.md) |
24
24
  | OverviewScreen | canvas·카드·도구·footer의 기존 틀 | [개요 화면](../components/overview-screen.md) |
25
25
  | EffectSurface | 화면 내부 canvas의 grain/ruled | [배경 효과](../components/effect-surface.md) |
26
- | SegmentedControl | 상속/명시 표현과 간격 선택 | [단일 선택](../components/segmented-control.md) |
26
+ | SegmentedControl + 보이는 이름 Text(`label`) | 상속/명시 표현과 간격 선택. SegmentedControl `label`은 접근성 이름이라 보이는 이름을 위에 둔다 | [단일 선택](../components/segmented-control.md) |
27
27
  | TextField/Button/Dialog | 초안·확인·상세와 복귀 | [입력](../components/field.md), [Dialog](../components/dialog.md) |
28
28
 
29
29
  ## 배치
30
30
 
31
31
  ```text
32
- [종이 표현: 테마 따르기 / 줄무늬 없는 면 / 줄무늬 추가]
33
- [줄무늬 간격: 24 / 40] [다음 테마]
32
+ 설명 + 표현 한계(caption)
33
+ 종이 표현 [테마 따르기 / 줄무늬 없는 면 / 줄무늬 추가]
34
+ 줄무늬 간격 [좁게 24 / 넓게 40] (줄무늬 추가일 때만 사용 가능, 그 외 흐리게)
35
+ 미리보기 도구: 테마 pills 10개
34
36
  ┌ OverviewScreen: 테마의 기존 화면/구성 기본값 ┐
35
- │ 제목 → 설명 → 산책 메모 │
37
+ │ 제목 → 설명 → 기록 도구(산책 메모 입력) │
36
38
  │ 기록 2개: 제목 → 본문 │
37
39
  │ 확인 결과 → [기록 확인] [자세히 읽기] │
38
40
  └ 정적 canvas: 내용 뒤, 입력/읽기 순서 없음 ──┘
39
- [표현 한계]
40
41
  ```
41
42
 
42
43
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
43
44
  | --- | --- | --- | --- |
44
- | 바깥 틀 | Stack | 화면 앞쪽 비교 도구부터 세로 | gap lg20; Native는 바깥 ScrollView와 기존 테마 비교와 같은 640host-unit 화면 viewport |
45
+ | 바깥 틀 | Stack, 스토리 `Container size="reading"` | 화면 앞쪽 비교 도구부터 세로 | gap lg20, 최대 폭 720; Native는 바깥 ScrollView와 기존 테마 비교와 같은 640host-unit 화면 viewport |
45
46
  | 기록 항목 | OverviewScreen·Stack | items 본문 | gap sm12; rows/grid/cards와 화면은 profile 기본값 |
46
47
  | 줄무늬 | EffectSurface | 화면 canvas 뒤 SVG | 한 선 1host unit, spacing 기본24, 명시 비교40; userSpaceOnUse |
47
48
  | footer | Stack·Button | 기존 OverviewScreen footer | gap sm12, primary 확인 다음 secondary 상세 |
@@ -101,3 +102,7 @@ Native 비교 도구를 높이 없는 Stack에 넣으면 flex 화면이 접혔
101
102
  밀어냈다. 그래서 기존 테마 비교의 640unit viewport를 사용하고 바깥도 스크롤한다. 제품은 이 예제 높이를
102
103
  복사하지 않고 실제 route 영역을 제공한다. iOS 줄무늬는 1unit 폭/백분율 Rect 조합에서 보이지 않아
103
104
  spacing×spacing 타일과 명시적 물리 폭을 사용한다. [수정 전후 기기 결과](../../../../../docs/qa/2026-10-07-native-reference-validation.md)를 참고한다.
105
+
106
+ ## 근거
107
+
108
+ - 2026-10-17 구성 리뷰: 예제 전용 "다음 테마"가 primary라 화면의 primary "기록 확인"과 경쟁했고, 도구 접기 제목과 입력 label이 같은 "산책 메모"였으며, 두 선택에 보이는 이름이 없었다. Web은 그룹 `disabled`를 보이지 않게 그렸다(SegmentedControl이 항목에 전달하도록 수정).
@@ -31,7 +31,7 @@
31
31
 
32
32
  ```text
33
33
  ┌ 바깥 틀: Web 문서 스크롤 · Native ScrollView(위아래 spacing.md 16) ┐
34
- │ ← Container gutter 16(폭 600 미만)/20 → │
34
+ │ ← Container reading, gutter 16(폭 600 미만)/20 → │
35
35
  │ ┌ Surface padding lg ────────────────────────────────┐ │
36
36
  │ │ 댓글 작성 Heading level5 │ │
37
37
  │ │ ┌ MessageComposer ───────────────────────────────┐ │ │
@@ -41,17 +41,19 @@
41
41
  │ │ └────────────────────────────────────────────────┘ │ │
42
42
  │ │ 등록한 댓글 목록(예제) │ │
43
43
  │ └────────────────────────────────────────────────────┘ │
44
- │ [답글 대상 선택] [다음 전송 실패시키기] ← 데모 보조 행동 │
44
+ │ 전송 상태 문구 (status, Surface 밖) │
45
+ │ 미리보기 도구: [답글 대상 선택] [다음 전송 실패시키기] │
45
46
  └──────────────────────────────────────────────────────────────────────┘
46
47
  ```
47
48
 
48
49
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
49
50
  | --- | --- | --- | --- |
50
- | 바깥 틀 | 화면의 composer 슬롯(ChatScreen·CommentThreadScreen `composer`). 단독 예제는 Web 문서 스크롤 > `Container`, Native `ScrollView` > `Container` | 화면 하단(footer). 키보드 adapter는 화면 host 하나만 | 좌우 `Container gutter`: 폭 600 미만 `compact` 16, 이상 `regular` 20. Native 위아래 `spacing.md` 16 |
51
+ | 바깥 틀 | 화면의 composer 슬롯(ChatScreen·CommentThreadScreen `composer`). 단독 예제는 Web 문서 스크롤 > `Container size="reading"`, Native `ScrollView` > `Container size="reading"` | 화면 하단(footer). 키보드 adapter는 화면 host 하나만 | 좌우 `Container gutter`: 폭 600 미만 `compact` 16, 이상 `regular` 20. Native 위아래 `spacing.md` 16 |
51
52
  | 제목(선택) | `Heading level5` | Surface 맨 위 | 아래 `spacing.md` 16 |
52
53
  | composer | `MessageComposer` | 제목 아래 | 입력 1~5줄(`composerMaxLines`), 세로 묶음 `screenPatternRecipe.itemGap` 12(두 플랫폼, 1.12.1 이하 Web은 8)([배치](../components/message-composer.md#배치)) |
53
54
  | 실패 문구 | `context` | composer 맨 위 | composer 세로 간격 |
54
- | 데모 보조 행동 | `Button` secondary·ghost | Surface 아래 가로 줄 | `spacing.sm` 12. 제품에는 넣지 않는다 |
55
+ | 전송 상태 | `Text role="status"`(Native `StatusText`) | Surface 아래. 항상 마운트해 알림 영역을 유지하되 카드 안에 빈 줄을 만들지 않는다 | 바깥 `Stack` 간격 |
56
+ | 데모 행동 | `PreviewTools` 안 `PreviewToolButton`([미리보기 도구](../SELECTION.md)) | 구성 뒤 | 제품에는 넣지 않는다 |
55
57
 
56
58
  ## 흐름과 상태
57
59
 
@@ -116,3 +118,7 @@ import { Text } from "@hjmds/react-native/primitives";
116
118
  - 스토리의 "다음 전송 실패시키기"·350ms 지연·예제 사진은 로컬 시연이다. 업로드·서버 저장·권한은 제품이 연결한다.
117
119
 
118
120
  2026-10-13 예제 검수: Web의 원형 전송 버튼이 잠기거나 없어질 때 초점이 body로 빠져나가던 경로를 `inputRef`로 보완했다. 전송 시작 때만 입력에 초점을 두므로 대기 중 사용자가 다른 곳으로 옮긴 초점을 완료 시 다시 가져오지 않는다. 실패 후 수정한 초안으로 재시도하며, 사진·답장 대상은 성공을 확인한 뒤에만 정리한다.
121
+
122
+ ## 근거
123
+
124
+ - 2026-10-17 구성 리뷰: 답글 대상 선택·대기 예제 종료가 secondary·ghost 제품 버튼처럼 보였고, 항상 마운트된 빈 상태 문구가 Surface 아래에 빈 띠를 만들었으며, 데스크톱에서 카드가 1280 폭 전체로 늘어났다. 그래서 데모 행동을 미리보기 도구로 옮기고 상태 문구를 Surface 밖에 두고 읽기 폭 Container를 적용했다.
@@ -33,7 +33,7 @@
33
33
 
34
34
  ```text
35
35
  ┌ 바깥 틀: Web 문서 스크롤 · Native ScrollView(위아래 spacing.md 16) ┐
36
- │ ← Container gutter 16(폭 600 미만)/20 → │
36
+ │ ← Container reading, gutter 16(폭 600 미만)/20 → │
37
37
  │ ┌ Surface padding lg ────────────────────────────────┐ │
38
38
  │ │ 메시지 작성 Heading level5 │ │
39
39
  │ │ ┌ MessageComposer ───────────────────────────────┐ │ │
@@ -44,17 +44,19 @@
44
44
  │ │ └────────────────────────────────────────────────┘ │ │
45
45
  │ │ 보낸 메시지 목록(예제) │ │
46
46
  │ └────────────────────────────────────────────────────┘ │
47
- │ [답장할 메시지 선택] [다음 전송 실패시키기] ← 데모 보조 행동 │
47
+ │ 전송 상태 문구 (status, Surface 밖) │
48
+ │ 미리보기 도구: [답장할 메시지 선택] [다음 전송 실패시키기] │
48
49
  └──────────────────────────────────────────────────────────────────────┘
49
50
  ```
50
51
 
51
52
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
52
53
  | --- | --- | --- | --- |
53
- | 바깥 틀 | 화면의 composer 슬롯(ChatScreen·CommentThreadScreen `composer`). 단독 예제는 Web 문서 스크롤 > `Container`, Native `ScrollView` > `Container` | 화면 하단(footer). 키보드 adapter는 화면 host 하나만 | 좌우 `Container gutter`: 폭 600 미만 `compact` 16, 이상 `regular` 20. Native 위아래 `spacing.md` 16 |
54
+ | 바깥 틀 | 화면의 composer 슬롯(ChatScreen·CommentThreadScreen `composer`). 단독 예제는 Web 문서 스크롤 > `Container size="reading"`, Native `ScrollView` > `Container size="reading"` | 화면 하단(footer). 키보드 adapter는 화면 host 하나만 | 좌우 `Container gutter`: 폭 600 미만 `compact` 16, 이상 `regular` 20. Native 위아래 `spacing.md` 16 |
54
55
  | 제목(선택) | `Heading level5` | Surface 맨 위 | 아래 `spacing.md` 16 |
55
56
  | composer | `MessageComposer` | 제목 아래 | 입력 1~5줄(`composerMaxLines`), 세로 묶음 `screenPatternRecipe.itemGap` 12(두 플랫폼, 1.12.1 이하 Web은 8)([배치](../components/message-composer.md#배치)) |
56
57
  | 실패 문구 | `context` | composer 맨 위 | composer 세로 간격 |
57
- | 데모 보조 행동 | `Button` secondary·ghost | Surface 아래 가로 줄 | `spacing.sm` 12. 제품에는 넣지 않는다 |
58
+ | 전송 상태 | `Text role="status"`(Native `StatusText`) | Surface 아래. 항상 마운트해 알림 영역을 유지하되 카드 안에 빈 줄을 만들지 않는다 | 바깥 `Stack` 간격 |
59
+ | 데모 행동 | `PreviewTools` 안 `PreviewToolButton`([미리보기 도구](../SELECTION.md)) | 구성 뒤 | 제품에는 넣지 않는다 |
58
60
 
59
61
  ## 흐름과 상태
60
62
 
@@ -121,3 +123,7 @@ import { Text } from "@hjmds/react-native/primitives";
121
123
  - 스토리의 "다음 전송 실패시키기"·350ms 지연·예제 사진은 로컬 시연이다. 업로드·서버 저장·권한은 제품이 연결한다.
122
124
 
123
125
  2026-10-13 예제 검수: Web의 원형 전송 버튼이 잠기거나 없어질 때 초점이 body로 빠져나가던 경로를 `inputRef`로 보완했다. 전송 시작 때만 입력에 초점을 두므로 대기 중 사용자가 다른 곳으로 옮긴 초점을 완료 시 다시 가져오지 않는다. 실패 후 수정한 초안으로 재시도하며, 사진·답장 대상은 성공을 확인한 뒤에만 정리한다.
126
+
127
+ ## 근거
128
+
129
+ - 2026-10-17 구성 리뷰: 답글 대상 선택·대기 예제 종료가 secondary·ghost 제품 버튼처럼 보였고, 항상 마운트된 빈 상태 문구가 Surface 아래에 빈 띠를 만들었으며, 데스크톱에서 카드가 1280 폭 전체로 늘어났다. 그래서 데모 행동을 미리보기 도구로 옮기고 상태 문구를 Surface 밖에 두고 읽기 폭 Container를 적용했다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/시작하기/첫 작업을 만들고 이어하기`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/구성/입력과 작성/첫 작업을 만들고 이어하기`
10
10
 
@@ -53,9 +53,9 @@
53
53
 
54
54
  이 이름의 독립 공개 컴포넌트는 없다. 아래는 공개 `createActionSession`과 입력·Steps·Sheet를 조합하는 예제다. Storybook 예제 파일을 제품에서 import하지 않는다.
55
55
 
56
- - [Web 실행 예제](../../../../../showcase/web/src/patterns/reference-flow-previews.tsx)의 `Editor(kind="first")`
57
- - [Native 실행 예제](../../../../../showcase/native/src/reference-flow-previews.tsx)의 `Editor(kind="first")`
58
- - [공통 검증·초안 비교](../../../../../showcase/shared/reference-flows.ts)의 `draftError`, `sameDraft`는 예제 데이터 규칙이며 제품 규칙으로 교체한다.
56
+ - [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/reference-flow-previews.tsx)의 `Editor(kind="first")`
57
+ - [Native 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/reference-flow-previews.tsx)의 `Editor(kind="first")`
58
+ - [공통 검증·초안 비교](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/shared/reference-flows.ts)의 `draftError`, `sameDraft`는 예제 데이터 규칙이며 제품 규칙으로 교체한다.
59
59
 
60
60
  주제 선택(0) → 입력(1) → 검토(2) → 저장 결과 순서다. 중단은 paused 표시만 바꾸고 draft와 stage를 보존한다. 이어하기는 이를 다시 보여 준다.
61
61
 
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/확인/선택 내용 검토와 수정`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/구성/입력과 작성/선택 내용 검토와 수정`
10
10
 
@@ -53,9 +53,9 @@
53
53
 
54
54
  이 이름의 독립 공개 컴포넌트는 없다. 아래는 공개 `createActionSession`과 입력·Steps·Sheet를 조합하는 예제다. Storybook 예제 파일을 제품에서 import하지 않는다.
55
55
 
56
- - [Web 실행 예제](../../../../../showcase/web/src/patterns/reference-flow-previews.tsx)의 `Editor(kind="review")`
57
- - [Native 실행 예제](../../../../../showcase/native/src/reference-flow-previews.tsx)의 `Editor(kind="review")`
58
- - [공통 검증·초안 비교](../../../../../showcase/shared/reference-flows.ts)의 `draftError`, `sameDraft`는 예제 데이터 규칙이며 제품 규칙으로 교체한다.
56
+ - [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/reference-flow-previews.tsx)의 `Editor(kind="review")`
57
+ - [Native 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/reference-flow-previews.tsx)의 `Editor(kind="review")`
58
+ - [공통 검증·초안 비교](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/shared/reference-flows.ts)의 `draftError`, `sameDraft`는 예제 데이터 규칙이며 제품 규칙으로 교체한다.
59
59
 
60
60
  입력(1) → 검토(2) → 저장 결과 순서다. 검토에서 수정을 누르면 같은 draft로 입력 단계에 돌아간다. 저장은 검토한 draft의 복사본을 캡처한다.
61
61
 
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 구성 API 지침 참조
7
- - 검토일: 2026-10-15
7
+ - 검토일: 2026-10-17
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/설정/변경 저장과 이탈 확인`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/구성/피드백과 복구/변경 저장과 이탈 확인`
10
10
 
@@ -53,9 +53,9 @@
53
53
 
54
54
  이 이름의 독립 공개 컴포넌트는 없다. 아래는 공개 `createActionSession`과 입력·Steps·Sheet를 조합하는 예제다. Storybook 예제 파일을 제품에서 import하지 않는다.
55
55
 
56
- - [Web 실행 예제](../../../../../showcase/web/src/patterns/reference-flow-previews.tsx)의 `Editor(kind="settings")`
57
- - [Native 실행 예제](../../../../../showcase/native/src/reference-flow-previews.tsx)의 `Editor(kind="settings")`
58
- - [공통 검증·초안 비교](../../../../../showcase/shared/reference-flows.ts)의 `draftError`, `sameDraft`는 예제 데이터 규칙이며 제품 규칙으로 교체한다.
56
+ - [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/reference-flow-previews.tsx)의 `Editor(kind="settings")`
57
+ - [Native 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/reference-flow-previews.tsx)의 `Editor(kind="settings")`
58
+ - [공통 검증·초안 비교](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/shared/reference-flows.ts)의 `draftError`, `sameDraft`는 예제 데이터 규칙이며 제품 규칙으로 교체한다.
59
59
 
60
60
  저장값 session.value와 편집 draft를 비교한다. dirty일 때만 저장·되돌리기를 켜고, 이탈은 확인 Sheet를 연다. 계속 수정은 Sheet만 닫고, 버리기는 마지막 저장값으로 draft를 복원한다.
61
61
 
@@ -32,7 +32,8 @@
32
32
 
33
33
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
34
34
  | --- | --- | --- | --- |
35
- | 바깥 틀 | Stack | 화면의 본문 흐름 | gap lg=20px, 전체 폭 |
35
+ | 바깥 틀 | Stack. Web은 제품 화면의 `Container size="reading"` 안 | 화면의 본문 흐름 | gap lg=20px, 폭은 Container를 따른다(Web 최대 `layout.readingMaxWidth` 720) |
36
+ | 제목 | `Heading level="level3"` | 선택 위 | `heading` 글자 크기·semibold |
36
37
  | 선택 | SegmentedControl 2개 | 제목 아래, 연결형 다음 필터형 | 기본 44px 터치 영역, 실제 글자에 따른 높이 |
37
38
  | 결과·메모 | Text · TextField | 선택 컨트롤 아래 | 선택 변경 시 필드 재생성 없음 |
38
39
  | Native 화면 host | ScreenLayout · KeyboardAvoiding | 본문 스크롤과 바깥 키보드 여백 | flex 1, 끝 여백 spacing.md=16 |
@@ -21,7 +21,7 @@ Web은 기존 AnimatedStatistic과 같은 `@number-flow/react` peer가 필요하
21
21
  | 컴포넌트 | 역할 | 지침 |
22
22
  | --- | --- | --- |
23
23
  | FollowProfileCard | Card · Avatar · AnimatedStatistic · Button으로 프로필·팔로워·controlled follow 요청 | [Card](../components/card.md) |
24
- | EventAttendanceCard | Card · Avatar · AnimatedStatistic · Button으로 행사·참석·캘린더 연결 | [Avatar](../components/avatar.md) |
24
+ | EventAttendanceCard | Card · Avatar · AnimatedStatistic · RadioGroup(응답) · Button(캘린더)으로 행사·참석·캘린더 연결 | [Avatar](../components/avatar.md) · [RadioGroup](../components/radio-group.md) |
25
25
 
26
26
  ## 배치
27
27
 
@@ -30,7 +30,7 @@ Web은 기존 AnimatedStatistic과 같은 `@number-flow/react` peer가 필요하
30
30
  [Avatar] 이름 · 소개 제목 · 소개
31
31
  팔로워 수 날짜/시간
32
32
  [팔로우 / 팔로잉] 참석 인원 → 확인된 참가자 Avatar + 이름
33
- 응답 제목 → [참석] [미정] [불참]
33
+ 응답 RadioGroup: (○참석) (○미정) (○불참)
34
34
  [캘린더 연결]
35
35
  오류: 해당 카드 내부 중앙 오류: 해당 카드 내부 중앙
36
36
  ```
@@ -40,8 +40,9 @@ Web은 기존 AnimatedStatistic과 같은 `@number-flow/react` peer가 필요하
40
40
  | 바깥 틀 | Card | 본문·목록·ProfileScreen 안 | 기존 Card recipe |
41
41
  | 프로필 header | Avatar · Card title/description | 상단 leading → 이름·소개 | 기존 Card와 Avatar 기본 크기 |
42
42
  | 본문 | AnimatedStatistic · Button · Text | 수치 → 주요 행동 → 오류 | Stack gap=md (`spacing.md`, 16px) |
43
- | 행사 | Text · AnimatedStatistic · Avatar · Button | 날짜 → 인원 → 참가자 → 응답 → 캘린더 → 오류 | Stack gap=md (`spacing.md`, 16px) |
44
- | 참가자·응답 행 | Stack | inline, 폭이 모자라면 wrap | gap=xs (`spacing.xs`, 8px) |
43
+ | 행사 | Text · AnimatedStatistic · Avatar · RadioGroup · Button | 날짜 → 인원 → 참가자 → 응답 → 캘린더(secondary) → 오류 | Stack gap=md (`spacing.md`, 16px) |
44
+ | 참가자 행 | Stack | inline, 폭이 모자라면 wrap. Avatar와 이름은 세로 가운데 정렬 | gap=xs (`spacing.xs`, 8px) |
45
+ | 응답 | RadioGroup | 가로 plain, 그룹 라벨 = 응답 제목. 아직 응답하지 않았으면 선택 없음 | `selectionGroupRecipe` horizontal plain gap `spacing.sm` 12 |
45
46
  | 오류 | Text | 자신의 안내 영역 가로·세로 중앙 | 임의 상단 margin 없음 |
46
47
 
47
48
  ## 흐름과 상태
@@ -53,8 +54,8 @@ Web은 기존 AnimatedStatistic과 같은 `@number-flow/react` peer가 필요하
53
54
 
54
55
  | 상태 | 모습 | 포커스·알림 |
55
56
  | --- | --- | --- |
56
- | 기본 | 확인된 선택·수치·참가자 | Button의 기존 선택·키보드 계약 |
57
- | 진행 중 | 캡처한 제안 선택·수치, 행동 잠금 | 로딩은 버튼 안, 수치 전환은 AnimatedStatistic |
57
+ | 기본 | 확인된 선택·수치·참가자 | 팔로우는 Button 선택 계약, 응답은 RadioGroup 키보드 계약 |
58
+ | 진행 중 | 캡처한 제안 선택·수치, 행동 잠금 | 팔로우·캘린더 로딩은 버튼 안, 응답은 그룹 비활성 + 설명에 진행 문구, 수치 전환은 AnimatedStatistic |
58
59
  | 실패 | 현재 confirmed state로 복구, 중앙 오류 | Web alert / Native assertive, 같은 행동 재시도 |
59
60
  | 캘린더 완료 | 실제 callback fulfilled 후 완료 label | 같은 mount에서 중복 추가 차단 |
60
61
  | 엔티티 변경 | 새 profileId/eventId의 상태 | 이전 응답은 새 카드에 반영하지 않음 |
@@ -57,7 +57,7 @@
57
57
  | 바깥 틀 | Web: `Container size="reading"`. Native: `ScrollView` → `Container` | 화면 본문, 카드는 스크롤과 함께 움직인다. 안전 영역은 화면 host(SafeArea·navigation header)가 준다. 입력이 없어 키보드 처리는 없다 | Native `ScrollView` 위아래 `spacing.md` 16, 좌우는 `Container` `gutter`(폭 600 미만 `compact` 16, 이상 `regular` 20). 최대 폭 `layout.readingMaxWidth` 720 |
58
58
  | 틀 | `Card` | 바깥 틀 안 | body padding `spacing.md` 16(`cardRecipe`) |
59
59
  | 정보 | `DescriptionList` | Card 머리 아래 | 정보–식별 묶음 `Stack gap="lg"` 20 |
60
- | 식별 묶음 | `QRCode`(+fallback) → 라벨 → 번호 | 정보 아래, 시작 정렬 | 묶음 안 `Stack gap="sm"` 12, QR `size` 기본 192(모듈 수의 정수배로 내림), quiet zone 4모듈. QR–fallback 사이 간격은 컴포넌트가 주지 않는다 |
60
+ | 식별 묶음 | `QRCode`(+fallback) → 라벨 → 번호 | 정보 아래, 시작 정렬 | 묶음 안 `Stack gap="sm"` 12, QR `size` 기본 192(모듈 수의 정수배로 내림), quiet zone 4모듈. QR 아래에 fallback을 세로로 쌓고 가운데 정렬하며 둘 사이 간격 12(`qrCodeRecipe.fallbackGap`)는 컴포넌트가 준다. fallback을 Stack으로 다시 감싸지 않는다 |
61
61
  | 로딩·실패 | `Skeleton`·`Result` | 카드 자리 | Skeleton `block` 기본 높이 `spacing.xxl` 32. Result는 자체 여백(`resultRecipe`) |
62
62
 
63
63
  - QR은 번호 **위**에 두고, 번호는 QR이 읽히지 않을 때 직원이 입력할 수 있도록 항상 같이 보인다.
@@ -99,9 +99,8 @@ import { QRCode } from "@hjmds/react/qr-code";
99
99
  <Stack gap="lg">
100
100
  <DescriptionList aria-label={t("ticket.details")} items={ticketState.ticket.details} />
101
101
  <Stack gap="sm">
102
- {/* QRCode의 svg가 inline이라 fallback을 블록으로 감싸 아래 줄에 둔다. */}
103
102
  <QRCode value={ticketState.ticket.booking} label={t("ticket.qrLabel", { booking: ticketState.ticket.booking })}
104
- fallback={<Stack gap="xs"><Text tone="muted">{t("ticket.qrFallback")}</Text></Stack>} />
103
+ fallback={<Text tone="muted">{t("ticket.qrFallback")}</Text>} />
105
104
  <Text variant="label" tone="muted">{t("ticket.bookingLabel")}</Text>
106
105
  <Text variant="title" emphasis="strong">{ticketState.ticket.booking}</Text>
107
106
  </Stack>
@@ -155,7 +154,6 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
155
154
  | --- | --- | --- |
156
155
  | 바깥 틀 | 문서 스크롤 + `Container` | `ScrollView`(위아래 `spacing.md`) + `Container` |
157
156
  | `DescriptionList` 이름·항목 | `aria-label` + `items` | `label` + `descriptor={{ items }}` |
158
- | fallback 감싸기 | inline svg 옆으로 흐르지 않게 `Stack`으로 감싼다 | `Text` 그대로 |
159
157
  | 번호 선택 | 브라우저 기본 텍스트 선택 | `Text selectable` |
160
158
  | QR 배치 prop | 배치 전용 `layoutStyle`만 | 없음(감싸는 레이아웃에서 정한다) |
161
159
  | QR peer | `qrcode-generator` 2.0.4 | `qrcode-generator` 2.0.4 + `react-native-svg` 15.15.5(dev client 재빌드) |
@@ -22,12 +22,12 @@
22
22
  | --- | --- | --- |
23
23
  | `Card` | 제목("주문 처리 단계")·설명 틀 | [Card](../components/card.md) |
24
24
  | `Steps` | 가로 단계 표시. `currentStepStatus`: `current` · `error` · `complete` | [Steps](../components/steps.md) |
25
- | 진행 문구 `Text` | "서버 확인을 기다리는 중이에요." / "모든 단계가 확정됐어요." | [Text](../components/text.md) |
25
+ | 진행 문구 `StatusText` | "서버 확인을 기다리는 중이에요." / "모든 단계가 확정됐어요." | [Text](../components/text.md#statustext) |
26
26
  | `Notice` `tone="danger"` | 실패 안내(상태는 그대로이니 다시 요청) | [Notice](../components/notice.md) |
27
27
  | `Button` primary | 다음 단계 요청 / 다시 요청. 요청 중 `loading` | [Button](../components/button.md) |
28
28
  | `Button` secondary | 전체 완료 후 "처음부터 다시" | [Button](../components/button.md) |
29
29
  | `Text` label muted + `Timeline` | "처리 기록" 제목과 확정·실패 사건 목록 | [Timeline](../components/timeline.md) |
30
- | `Switch` | 스토리 전용 "다음 요청을 실패로 응답". 제품에 넣지 않는다 | [Switch](../components/switch.md) |
30
+ | 미리보기 도구 | 스토리 전용 "다음 요청 실패시키기" 선택 칩. Card 밖 아래에 두며 제품에 넣지 않는다 | — |
31
31
  | `ScrollView` + `Container`(Native) | 바깥 틀. 세로 스크롤·위아래 여백, 좌우 gutter | [Container](../components/container.md), [화면 여백과 너비](../tokens/layout.md) |
32
32
 
33
33
  ## 배치
@@ -62,7 +62,7 @@ Native 화면(ScrollView, 위아래 spacing.lg 20) > Container gutter 16(폭 < 6
62
62
  | 단계 | `Steps` | Card 머리 아래 맨 위 | 단계 사이 `stepsRecipe.gap` `spacing.xs` 8, 표시 원 `glyph.md` |
63
63
  | 진행 | 진행 문구 | 단계 아래 | `spacing.lg` 20 |
64
64
  | 실패 | `Notice` danger | 진행 문구 아래, 실패 때만 | `spacing.lg` 20 |
65
- | 행동 | `Button` primary(또는 완료 후 secondary) | Notice 아래, 꽉 찬 폭(`Stack` 기본 `align="stretch"`가 채우므로 Web `layoutStyle`·Native `fullWidth`를 따로 주지 않는다) | 높이 44, 행동 묶음 안 `Stack gap="sm"` 12 |
65
+ | 행동 | `Button` primary(또는 완료 후 secondary) | Notice 아래, 꽉 찬 폭(`Stack` 기본 `align="stretch"`가 채우므로 Web `layoutStyle`·Native `fullWidth`를 따로 주지 않는다) | 높이 44, 버튼 하나만 둔다 |
66
66
  | 기록 | label + `Timeline` | 맨 아래, 스크롤 | `spacing.lg` 20, 기록은 아래로 쌓인다 |
67
67
 
68
68
  - 실패 Notice는 버튼 **위**에 둔다. 사용자가 이유를 읽은 다음 "다시 요청"에 닿는다.
@@ -86,7 +86,7 @@ Native 화면(ScrollView, 위아래 spacing.lg 20) > Container gutter 16(폭 < 6
86
86
  | 완료 | 모든 단계 complete, 진행 문구 "모든 단계가 확정됐어요.", secondary 버튼 | 진행 문구가 읽힌다 |
87
87
 
88
88
  - 요청 중 중복 요청은 상태 로직에서 무시한다. 버튼 `loading`만으로는 키보드 반복 입력을 막지 못한다.
89
- - 예제 실패 스위치도 요청 중에는 잠그고 늦은 toggle callback을 reducer에서 무시한다. 응답을 기다리는 동안 스위치가 진행 중 요청의 결과를 뒤집던 실제 예제 검수에 따른 보완이다.
89
+ - 예제 실패 도구도 요청 중에는 잠그고 늦은 toggle callback을 reducer에서 무시한다. 응답을 기다리는 동안 도구가 진행 중 요청의 결과를 뒤집던 실제 예제 검수에 따른 보완이다.
90
90
  - 문구 키는 상태별 상수로 둔다. 상태 이름으로 키를 조립하지 않는다.
91
91
 
92
92
  | 상태 | 진행 문구 키 | Notice 키 | 버튼 키 |
@@ -106,6 +106,7 @@ import { Card, Timeline } from "@hjmds/react/display";
106
106
  import { Notice } from "@hjmds/react/feedback";
107
107
  import { Stack, Text } from "@hjmds/react/layout";
108
108
  import { Steps } from "@hjmds/react/steps";
109
+ import { StatusText } from "@hjmds/react/status-text";
109
110
 
110
111
  // 상태 → 문구 키
111
112
  const noticeKey = { rejected: "order.failed", requestFailed: "order.requestFailed" } as const;
@@ -115,14 +116,12 @@ const progressKey = { requesting: "order.requesting", done: "order.done" } as co
115
116
  <Stack gap="lg">
116
117
  <Steps descriptor={{ steps, currentStepId, currentStepStatus }} statusLabels={statusLabels}
117
118
  composeAccessibleName={({ position, total, label }) => t("order.stepName", { position, total, label })} />
118
- {/* 알림 자리를 위해 status 영역은 늘 마운트한다 */}
119
- <Text role="status">{requesting ? t(progressKey.requesting) : done ? t(progressKey.done) : ""}</Text>
119
+ {/* 알림 자리를 위해 live node는 늘 마운트한다. 비어 있으면 레이아웃에서 빠진다 */}
120
+ <StatusText>{requesting ? t(progressKey.requesting) : done ? t(progressKey.done) : ""}</StatusText>
120
121
  {failure ? <Notice tone="danger" title={t(noticeKey[failure])} /> : null}
121
- <Stack gap="sm">
122
- {done
123
- ? <Button tone="secondary" onClick={restart}>{t("order.restart")}</Button>
124
- : <Button loading={requesting} onClick={request}>{failure ? t("order.retry") : t("order.request")}</Button>}
125
- </Stack>
122
+ {done
123
+ ? <Button tone="secondary" onClick={restart}>{t("order.restart")}</Button>
124
+ : <Button loading={requesting} onClick={request}>{failure ? t("order.retry") : t("order.request")}</Button>}
126
125
  <Text variant="label" tone="muted">{t("order.log")}</Text>
127
126
  <Timeline items={log} composeAccessibleName={({ position, total, label }) => t("order.logName", { position, total, label })} />
128
127
  </Stack>
@@ -139,6 +138,7 @@ import { Card, Timeline } from "@hjmds/react-native/data-display";
139
138
  import { Notice } from "@hjmds/react-native/feedback";
140
139
  import { Container, Stack, Text } from "@hjmds/react-native/primitives";
141
140
  import { Steps } from "@hjmds/react-native/steps";
141
+ import { StatusText } from "@hjmds/react-native/status-text";
142
142
 
143
143
  const noticeKey = { rejected: "order.failed", requestFailed: "order.requestFailed" } as const;
144
144
  const { width } = useWindowDimensions();
@@ -151,13 +151,11 @@ const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
151
151
  <Steps descriptor={{ steps, currentStepId, currentStepStatus }} statusLabels={statusLabels}
152
152
  composeAccessibleName={({ position, total, label }) => t("order.stepName", { position, total, label })} />
153
153
  {/* 빈 Text가 gap 하나만큼 틈을 남기므로 문구가 있을 때만 그린다. */}
154
- {requesting || done ? <Text accessibilityLiveRegion="polite">{done ? t("order.done") : t("order.requesting")}</Text> : null}
154
+ {requesting || done ? <StatusText announceOnMount>{done ? t("order.done") : t("order.requesting")}</StatusText> : null}
155
155
  {failure ? <Notice tone="danger" announcement="assertive" title={t(noticeKey[failure])} /> : null}
156
- <Stack gap="sm">
157
- {done
158
- ? <Button tone="secondary" onPress={restart}>{t("order.restart")}</Button>
159
- : <Button loading={requesting} onPress={request}>{failure ? t("order.retry") : t("order.request")}</Button>}
160
- </Stack>
156
+ {done
157
+ ? <Button tone="secondary" onPress={restart}>{t("order.restart")}</Button>
158
+ : <Button loading={requesting} onPress={request}>{failure ? t("order.retry") : t("order.request")}</Button>}
161
159
  <Text variant="label" tone="muted">{t("order.log")}</Text>
162
160
  <Timeline items={log} composeAccessibleName={({ position, total, label }) => t("order.logName", { position, total, label })} />
163
161
  </Stack>
@@ -166,14 +164,14 @@ const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
166
164
  </ScrollView>;
167
165
  ```
168
166
 
169
- 단계 이름·문구, 실패 스위치, 900ms 지연은 예시다. 단계 정의와 확정·거절 판단은 서버와 제품 소유다.
167
+ 단계 이름·문구, 실패 미리보기 도구, 900ms 지연은 예시다. 단계 정의와 확정·거절 판단은 서버와 제품 소유다.
170
168
  상태 전이는 스토리의 `orderReducer`·`orderStepsDescriptor`를 참고한다.
171
169
 
172
170
  ## 플랫폼 차이
173
171
 
174
172
  | 항목 | Web | Native |
175
173
  | --- | --- | --- |
176
- | 진행 문구 자리 | 빈 `role="status"`를 항상 마운트(알림 자리 유지) | 문구가 있을 때만 마운트(iOS는 live region을 무시하고 빈 Text가 틈을 남김, 2026-10-02 시뮬레이터 확인) |
174
+ | 진행 문구 자리 | `StatusText`를 항상 마운트(알림 자리 유지). 비어 있으면 레이아웃에서 빠져 gap을 만들지 않는다 | 문구가 있을 때만 마운트(iOS는 live region을 무시하고 빈 Text가 틈을 남김, 2026-10-02 시뮬레이터 확인) |
177
175
  | 이벤트 | `onClick` | `onPress` |
178
176
  | 바깥 틀 | HJM ScreenLayout의 본문 | `ScrollView` > `Container` |
179
177
  | 실패 Notice 발표 | `danger`는 늘 `role="alert"` | `announcement="assertive"`를 지정해야 발표(기본 `none`) |
@@ -183,5 +181,5 @@ const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
183
181
  - 실패할 때 커서를 옮기거나 완료 표시를 먼저 그리지 않는다. 서버가 거절한 단계를 사용자가 끝난 것으로 오해한다.
184
182
  - `Timeline` 이름은 단계 이름과 다르게 짓는다("기록 N개 중 M번째"). "5단계 중"으로 읽히면 단계 수와 섞인다.
185
183
  - Steps는 세로 방향·클릭 이동을 지원하지 않는다(`src/steps.ts`). 단계 사이를 눌러 이동하는 UI를 기대하지 않는다.
186
- - 현재 스토리에는 네트워크·서버 오류로 요청 자체가 실패하는 경로가 없다(실패는 스위치로 만든 거절뿐이다).
187
- - Web은 빈 `role="status"` Text도 Stack 자식이라 gap 한 칸(`spacing.lg` 20)이 Steps 아래에 남는다.
184
+ - 현재 스토리에는 네트워크·서버 오류로 요청 자체가 실패하는 경로가 없다(실패는 미리보기 도구로 만든 거절뿐이다).
185
+ - Web에서 빈 `role="status"` Text를 직접 두면 Stack 자식이라 gap 한 칸(`spacing.lg` 20)이 Steps 아래에 남는다. `StatusText`를 쓴다.
@@ -22,8 +22,8 @@
22
22
  | EmptyState | 그림·제목·설명·행동 한 묶음 | [EmptyState](../components/empty-state.md) |
23
23
  | 제품 캐릭터(스토리: 12×12 픽셀 스프라이트) | 장식 그림, 4프레임 220ms 반복 | 제품 소유 |
24
24
  | Button `primary` | 첫 기록 남기기(시작 행동) | [Button](../components/button.md) |
25
- | Button `ghost` | 움직임 멈추기/다시 움직이기 | [Button](../components/button.md) |
26
- | Text `tone="muted"` 상태 문구 | 시작 행동 결과 | [Text](../components/text.md) |
25
+ | Switch | 캐릭터 움직임 켜기·끄기(멈춤 제어) | [Switch](../components/switch.md) |
26
+ | StatusText `tone="muted"` 상태 문구 | 시작 행동 결과 | [Text](../components/text.md#statustext) |
27
27
  | `ScrollView` + `Container`(Native) | 바깥 틀. 세로 스크롤·위아래 여백, 좌우 gutter | [Container](../components/container.md), [화면 여백과 너비](../tokens/layout.md) |
28
28
 
29
29
  ## 배치
@@ -31,7 +31,7 @@
31
31
  ```text
32
32
  Native 화면(ScrollView, 위아래 spacing.lg 20) > Container gutter 16(폭 < 600) · 20(폭 ≥ 600)
33
33
  ┌──────── Stack gap spacing.md 16 ─────────┐
34
- │ ┌──── EmptyState (가운데 정렬) ────────┐ │
34
+ │ ┌──── EmptyState (남은 높이, 가운데) ──┐ │
35
35
  │ │ ┌────────┐ │ │
36
36
  │ │ │ 캐릭터 │ 96×96 │ │
37
37
  │ │ └────────┘ ↕ spacing.xs 8│ │
@@ -39,7 +39,7 @@ Native 화면(ScrollView, 위아래 spacing.lg 20) > Container gutter 16(폭 < 6
39
39
  │ │ 말랑이가 첫 기록을 기다리고 있어요. │ │
40
40
  │ │ [ 첫 기록 남기기 ] primary │ │ ← 주 행동
41
41
  │ └────────────────────────────────────────┘ │
42
- │ [ 움직임 멈추기 ] ghost │ ← 보조(멈춤 제어)
42
+ │ 캐릭터 움직임 (●──) │ ← Switch(멈춤 제어)
43
43
  │ 상태 문구 (muted, live) │
44
44
  └───────────────────────────────────────────┘
45
45
  고정 영역 없음. 안전 영역은 화면 골격이 맡는다
@@ -47,25 +47,25 @@ Native 화면(ScrollView, 위아래 spacing.lg 20) > Container gutter 16(폭 < 6
47
47
 
48
48
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
49
49
  | --- | --- | --- | --- |
50
- | 바깥 틀 | Web: HJM ScreenLayout의 본문. Native: `ScrollView` > `Container` | 이 구성은 바깥 폭·여백을 정하지 않는다(Web 스토리는 Stack만 그린다). Native는 `ScrollView` 안 [Container](../components/container.md)에 둔다. 입력이 없어 키보드 처리는 없다. 안전 영역은 화면 골격(내비게이션 헤더·탭 바)이 맡는다 | Native ScrollView 위아래 `spacing.lg` 20, Container gutter 폭 600 미만 `compact` 16 · 이상 `regular` 20(`layout.pagePadding`) |
51
- | 빈 상태 | EmptyState `density="regular"` | 본문 가운데 | 위아래 Web `spacing.xxl` 32 / Native `spacing.xxxl` 40, 좌우 `spacing.xl` 24, 슬롯 사이 `spacing.xs` 8 |
50
+ | 바깥 틀 | Web: HJM ScreenLayout의 본문. Native: `ScrollView`(`contentContainerStyle` `flexGrow: 1`) > `Container` | 이 구성은 바깥 폭·여백을 정하지 않는다(Web 스토리는 캔버스 높이를 채운 Stack만 그린다). 본문 높이를 받은 Stack 안에서 EmptyState가 남은 높이를 채운다. Native는 `ScrollView` 안 [Container](../components/container.md)에 두고 Container·Stack도 `flexGrow: 1`로 높이를 잇는다. 입력이 없어 키보드 처리는 없다. 안전 영역은 화면 골격(내비게이션 헤더·탭 바)이 맡는다 | Native ScrollView 위아래 `spacing.lg` 20, Container gutter 폭 600 미만 `compact` 16 · 이상 `regular` 20(`layout.pagePadding`) |
51
+ | 빈 상태 | EmptyState `density="regular"` | 본문의 남은 높이를 채우고 가로·세로 가운데 | 위아래 Web `spacing.xxl` 32 / Native `spacing.xxxl` 40, 좌우 `spacing.xl` 24, 슬롯 사이 `spacing.xs` 8 |
52
52
  | 캐릭터 | Web `icon` / Native `illustration` | EmptyState 맨 위 | 96×96(12칸 × `spacing.xs` 8). Native도 그림 노드 자체의 96×96 크기를 사용한다 |
53
53
  | 시작 행동 | Button `primary` | EmptyState `action` | 높이 44, 하나만 |
54
- | 멈춤 제어 | Button `ghost` | EmptyState 아래, 꽉 찬 폭(`Stack` 기본 `align="stretch"`) | 묶음 사이 `spacing.md` 16 |
55
- | 상태 문구 | Text muted | 맨 아래. Web은 빈 `role="status"`를 늘 마운트, Native는 문구가 있을 때만 | `spacing.md` 16 |
54
+ | 멈춤 제어 | Switch "캐릭터 움직임" | EmptyState 바로 아래, 꽉 찬 폭(`Stack` 기본 `align="stretch"`) | 묶음 사이 `spacing.md` 16 |
55
+ | 상태 문구 | StatusText muted | 맨 아래. Web은 늘 마운트하고 비어 있으면 레이아웃에서 빠진다, Native는 문구가 있을 때만 | `spacing.md` 16 |
56
56
 
57
57
  ## 흐름과 상태
58
58
 
59
59
  1. 화면이 열리면 캐릭터가 220ms 간격 4프레임으로 반복한다.
60
60
  2. "첫 기록 남기기"를 누르면 제품의 작성 화면으로 간다(스토리는 상태 문구만 보인다).
61
- 3. "움직임 멈추기"를 누르면 첫 프레임에서 멈추고 라벨이 "다시 움직이기"로 바뀐다.
61
+ 3. "캐릭터 움직임" Switch를 끄면 첫 프레임에서 멈추고, 다시 켜면 움직인다.
62
62
 
63
63
  | 상태 | 모습 | 포커스·알림 |
64
64
  | --- | --- | --- |
65
65
  | 기본 | 캐릭터가 움직인다 | 캐릭터는 숨김, 제목·설명이 의미를 전한다 |
66
66
  | 진행 중 | 시작 행동이 화면 이동이면 없다. 서버 요청(예: 첫 항목 생성)이면 primary `loading` | 포커스는 버튼 유지 |
67
67
  | 실패 | 시작 행동이 서버 요청일 때 실패하면 상태 문구 자리에 실패 문구, primary는 다시 누를 수 있다. 빈 상태 자체를 불러오지 못한 경우는 이 구성이 아니라 오류 화면([Result](../components/result.md) failure)이다 | 상태 문구가 Web `role="status"`, Native live region으로 읽힌다 |
68
- | 멈춤 | 첫 프레임 정지 | 멈춤 버튼 라벨이 바뀐다 |
68
+ | 멈춤 | 첫 프레임 정지 | Switch 상태(켬·끔)가 읽힌다 |
69
69
  | reduced motion | 타이머 없이 첫 프레임 | — |
70
70
  | 탭 숨김(Web)·앱 배경(Native) | 타이머를 걸지 않는다 | — |
71
71
  | 시작 행동 후 | 상태 문구 표시 | Web `role="status"`, Native `accessibilityLiveRegion` |
@@ -77,20 +77,20 @@ Native 화면(ScrollView, 위아래 spacing.lg 20) > Container gutter 16(폭 < 6
77
77
  // Web
78
78
  import { EmptyState } from "@hjmds/react/feedback";
79
79
  import { Button } from "@hjmds/react/actions";
80
- import { Stack, Text } from "@hjmds/react/layout";
80
+ import { Stack } from "@hjmds/react/layout";
81
+ import { Switch } from "@hjmds/react/selection";
82
+ import { StatusText } from "@hjmds/react/status-text";
81
83
 
82
84
  // 상태 → 문구 키
83
85
  const statusKey = { idle: undefined, started: "records.empty.started", failed: "records.empty.startFailed" } as const;
84
86
  const statusText = statusKey[startState];
85
87
 
86
- <Stack gap="md">
88
+ <Stack gap="md" layoutStyle={{ flexGrow: 1 }}>{/* 본문 높이를 가진 flex 부모 안에서 채운다 */}
87
89
  <EmptyState icon={<ProductMascot paused={paused} />} title={t("records.empty.title")}
88
90
  description={t("records.empty.description")}
89
91
  action={<Button loading={starting} onClick={startRecord}>{t("records.empty.start")}</Button>} />
90
- <Button tone="ghost" onClick={() => setPaused((v) => !v)}>
91
- {paused ? t("mascot.resume") : t("mascot.pause")}
92
- </Button>
93
- <Text role="status" tone="muted">{statusText ? t(statusText) : ""}</Text>
92
+ <Switch label={t("mascot.motion")} checked={!paused} onCheckedChange={(on) => setPaused(!on)} />
93
+ <StatusText tone="muted">{statusText ? t(statusText) : ""}</StatusText>
94
94
  </Stack>;
95
95
  ```
96
96
 
@@ -101,23 +101,23 @@ import { spacing } from "@hjmds/design-contracts/foundations";
101
101
  import { resolveWindowClass } from "@hjmds/design-contracts/responsive";
102
102
  import { EmptyState } from "@hjmds/react-native/feedback";
103
103
  import { Button } from "@hjmds/react-native/actions";
104
- import { Container, Stack, Text } from "@hjmds/react-native/primitives";
104
+ import { Switch } from "@hjmds/react-native/inputs";
105
+ import { Container, Stack } from "@hjmds/react-native/primitives";
106
+ import { StatusText } from "@hjmds/react-native/status-text";
105
107
 
106
108
  const statusKey = { idle: undefined, started: "records.empty.started", failed: "records.empty.startFailed" } as const;
107
109
  const statusText = statusKey[startState];
108
110
  const { width } = useWindowDimensions();
109
111
  const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
110
112
 
111
- <ScrollView contentContainerStyle={{ paddingVertical: spacing.lg }}>
112
- <Container gutter={gutter}>
113
- <Stack gap="md">
113
+ <ScrollView contentContainerStyle={{ flexGrow: 1, paddingVertical: spacing.lg }}>
114
+ <Container gutter={gutter} layoutStyle={{ flexGrow: 1 }}>
115
+ <Stack gap="md" layoutStyle={{ flexGrow: 1 }}>
114
116
  <EmptyState illustration={<ProductMascot paused={paused} />}
115
117
  title={t("records.empty.title")} description={t("records.empty.description")}
116
118
  action={<Button loading={starting} onPress={startRecord}>{t("records.empty.start")}</Button>} />
117
- <Button tone="ghost" onPress={() => setPaused((v) => !v)}>
118
- {paused ? t("mascot.resume") : t("mascot.pause")}
119
- </Button>
120
- {statusText ? <Text accessibilityLiveRegion="polite" tone="muted">{t(statusText)}</Text> : null}
119
+ <Switch label={t("mascot.motion")} checked={!paused} onCheckedChange={(on) => setPaused(!on)} />
120
+ {statusText ? <StatusText announceOnMount tone="muted">{t(statusText)}</StatusText> : null}
121
121
  </Stack>
122
122
  </Container>
123
123
  </ScrollView>;
@@ -132,11 +132,11 @@ const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
132
132
  | 그림 슬롯 | `icon` | `illustration`(최소 `glyph.lg` 크기만 보장하고 캐릭터 자체 크기를 존중한다) |
133
133
  | 세로 여백 | `.hjm-empty-state` `spacing.xxl` 32 | recipe `spacing.xxxl` 40 |
134
134
  | 일시정지 감지 | `document.hidden` | `AppState` |
135
- | 상태 문구 자리 | 빈 `role="status"`를 늘 마운트 | 문구가 있을 때만 마운트 |
135
+ | 상태 문구 자리 | `StatusText`를 늘 마운트(비면 레이아웃에서 빠짐) | 문구가 있을 때만 마운트 |
136
136
  | 바깥 틀 | HJM ScreenLayout의 본문 | `ScrollView` > `Container` |
137
137
 
138
138
  ## 함정
139
139
 
140
- - 5초 넘게 반복되는 움직임에는 멈춤 제어를 둔다(WCAG 2.2.2). 스토리의 ghost 버튼이 그 자리다.
140
+ - 5초 넘게 반복되는 움직임에는 멈춤 제어를 둔다(WCAG 2.2.2). 켜고 끄는 제어이므로 라벨이 바뀌는 텍스트 버튼이 아니라 Switch로 둔다([켜고 끄기](../SELECTION.md)).
141
141
  - 캐릭터에 접근성 이름을 붙이지 않는다. EmptyState가 그림을 장식으로 숨긴다.
142
142
  - 현재 스토리의 시작 행동은 상태 문구만 바꾼다. 진행 중·실패 경로는 없다.
@@ -28,7 +28,7 @@
28
28
  ## 배치
29
29
 
30
30
  ```text
31
- 분할 실행: [현재 방식 실행] [방식 메뉴]
31
+ 분할 실행: [현재 방식 실행] [⌄ 방식 메뉴 IconButton]
32
32
  계정 전환: 확인된 현재 계정 → [계정 메뉴]
33
33
  비밀번호: 기존 비밀번호 입력 → 규칙 충족 막대 → 규칙별 상태
34
34
  요금: 주기별 라디오 카드 → 선택한 실제 가격·혜택 → [계속]
@@ -38,7 +38,8 @@
38
38
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
39
39
  | --- | --- | --- | --- |
40
40
  | 바깥 틀 | Stack/Card | 기존 본문·패널 안 | sm 또는 md |
41
- | 분할 행동 | Button/Menu | 같은 inline 행 | xs wrap |
41
+ | 분할 행동 | Button + Menu(IconButton chevronDown 트리거, label=`menuLabel`) | 같은 inline 행, 메뉴는 실행 버튼 끝 | 인라인 간격 `spacing.sm` 12(button.md) |
42
+ | 메뉴 트리거 | 분할 행: IconButton(secondary). 계정 전환: Button(secondary) | 분할 행 끝·계정 이름 아래 | 내용 폭. Web·Native 모두 전체 폭으로 늘이지 않음 |
42
43
  | 비밀번호 | PasswordField/Progress/Text | 위→아래 | sm |
43
44
  | 요금 선택 | RadioGroup | 카드 상단 | card presentation |
44
45
  | 실패 | Text | 구성 내부 중앙 | 임의 상단 여백 없음 |
@@ -93,3 +94,7 @@ import { AccountSwitcher } from "@hjmds/react-native/task-controls";
93
94
  - 옵션 선택으로 결제를 시작하지 않는다. 실제 결제·약관·상품 검증은 onContinue 뒤 제품 흐름 책임이다.
94
95
  - 다른 엔티티/세션으로 전환할 때 host key를 바꾸고 늦은 제품 API 응답도 제품 상태 계층에서 검증한다.
95
96
  - npm 1.21.0 API다. 기기·실제 로그인·결제 검증과 npm 게시를 구분한다.
97
+
98
+ ## 근거
99
+
100
+ - 2026-10-17 구성 리뷰: 분할 실행의 방식 메뉴가 두 번째 텍스트 버튼("실행 방식 선택")으로 보여 별개 행동처럼 읽혔고 인라인 간격이 8이었다. 그래서 메뉴를 chevron IconButton 도구로 바꾸고 간격을 12로 맞췄다.
@@ -24,6 +24,7 @@
24
24
  | --- | --- | --- |
25
25
  | EffectSurface | 질감·테마·장식 수명주기 | [배경 효과](../components/effect-surface.md) |
26
26
  | SegmentedControl | 두 영역의 강도를 함께 선택 | [단일 선택](../components/segmented-control.md) |
27
+ | Heading | 각 비교 영역 제목 | [제목](../components/heading.md) |
27
28
  | TextField·Button·Text | 읽기·입력·행동·결과 확인 | [입력](../components/field.md) |
28
29
 
29
30
  noise는 생성된64×64 PNG alpha mask이며 자체 런타임 픽셀 생성·외부 요청이 없다.
@@ -41,11 +42,10 @@ seed는 고정 타일의 위상만 이동한다. 새 타일 픽셀을 생성하
41
42
 
42
43
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
43
44
  | --- | --- | --- | --- |
44
- | 바깥 틀 | Stack | 화면 본문 세로 | gap lg20 |
45
- | 비교 영역 | EffectSurface | grain 다음 noise | 내부 padding spacing.lg20, Stack gap md16 |
45
+ | 바깥 틀 | Web `Container size="reading"` → Stack, Native KeyboardAvoiding → ScrollView → `Container gutter="compact"` → Stack | 본문 세로 흐름. 화면 머리(ScreenLayout 제목)는 두지 않는다 | gap lg20, Native ScrollView 위아래 spacing.lg20 |
46
+ | 비교 영역 | EffectSurface `padding="lg"` | grain 다음 noise | 내부 여백 20, Stack gap md16, 제목 `Heading level="level5"` |
46
47
  | 질감 | SVG mask | absolute 배경 | viewBox 밖64×64 CSS pixel/Native logical point 타일 반복 |
47
48
  | 본문 | TextField·Button | 장식 앞 일반 레이아웃 | 기존 recipe |
48
- | Native 화면 | ScreenLayout·KeyboardAvoiding | 본문 스크롤과 바깥 키보드 여백 | 긴 내용·큰 글자 접근용 |
49
49
 
50
50
  ## 흐름과 상태
51
51
 
@@ -63,13 +63,13 @@ seed는 고정 타일의 위상만 이동한다. 새 타일 픽셀을 생성하
63
63
  ```tsx
64
64
  // Web
65
65
  import { EffectSurface } from "@hjmds/react/effect-surface";
66
- <EffectSurface descriptor={{ layers: ["noise"], active: false }}><Content /></EffectSurface>
66
+ <EffectSurface descriptor={{ layers: ["noise"], active: false }} padding="lg"><Content /></EffectSurface>
67
67
  ```
68
68
 
69
69
  ```tsx
70
70
  // Native
71
71
  import { EffectSurface } from "@hjmds/react-native/effect-surface";
72
- <EffectSurface descriptor={{ layers: ["noise"], active: false }}><Content /></EffectSurface>
72
+ <EffectSurface descriptor={{ layers: ["noise"], active: false }} padding="lg"><Content /></EffectSurface>
73
73
  ```
74
74
 
75
75
  ## 플랫폼 차이