@hjmds/design-contracts 1.17.2 → 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 (274) 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/activity-chip.d.ts +127 -0
  6. package/dist/activity-chip.d.ts.map +1 -0
  7. package/dist/activity-chip.js +65 -0
  8. package/dist/activity-chip.js.map +1 -0
  9. package/dist/base-recipes.d.ts +1 -1
  10. package/dist/base-recipes.d.ts.map +1 -1
  11. package/dist/base-recipes.js +2 -1
  12. package/dist/base-recipes.js.map +1 -1
  13. package/dist/behaviors.d.ts +9 -6
  14. package/dist/behaviors.d.ts.map +1 -1
  15. package/dist/behaviors.js +5 -3
  16. package/dist/behaviors.js.map +1 -1
  17. package/dist/bottom-navigation.d.ts +16 -0
  18. package/dist/bottom-navigation.d.ts.map +1 -1
  19. package/dist/bottom-navigation.js +18 -0
  20. package/dist/bottom-navigation.js.map +1 -1
  21. package/dist/card.d.ts +4 -0
  22. package/dist/card.d.ts.map +1 -1
  23. package/dist/card.js +4 -1
  24. package/dist/card.js.map +1 -1
  25. package/dist/carousel.d.ts +3 -0
  26. package/dist/carousel.d.ts.map +1 -1
  27. package/dist/carousel.js.map +1 -1
  28. package/dist/catalog.d.ts +80 -9
  29. package/dist/catalog.d.ts.map +1 -1
  30. package/dist/component-recipes.d.ts +74 -3
  31. package/dist/component-recipes.d.ts.map +1 -1
  32. package/dist/component-recipes.js +38 -6
  33. package/dist/component-recipes.js.map +1 -1
  34. package/dist/content-transition.d.ts +1 -1
  35. package/dist/content-transition.d.ts.map +1 -1
  36. package/dist/content-transition.js +2 -2
  37. package/dist/content-transition.js.map +1 -1
  38. package/dist/field-group.d.ts +14 -0
  39. package/dist/field-group.d.ts.map +1 -1
  40. package/dist/field-group.js +9 -0
  41. package/dist/field-group.js.map +1 -1
  42. package/dist/form.d.ts.map +1 -1
  43. package/dist/form.js +10 -3
  44. package/dist/form.js.map +1 -1
  45. package/dist/icon-button-recipe.d.ts +1 -5
  46. package/dist/icon-button-recipe.d.ts.map +1 -1
  47. package/dist/icon-button-recipe.js +2 -1
  48. package/dist/icon-button-recipe.js.map +1 -1
  49. package/dist/index.d.ts +3 -2
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +2 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/screen-patterns.d.ts +10 -0
  54. package/dist/screen-patterns.d.ts.map +1 -1
  55. package/dist/screen-patterns.js +17 -3
  56. package/dist/screen-patterns.js.map +1 -1
  57. package/dist/toast-liquid.d.ts +1 -1
  58. package/dist/toast-liquid.d.ts.map +1 -1
  59. package/dist/toast-liquid.js +3 -2
  60. package/dist/toast-liquid.js.map +1 -1
  61. package/dist/toast.d.ts +29 -2
  62. package/dist/toast.d.ts.map +1 -1
  63. package/dist/toast.js +59 -3
  64. package/dist/toast.js.map +1 -1
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/dist/version.js.map +1 -1
  68. package/docs/action-session.md +2 -0
  69. package/docs/activity-chip.md +34 -0
  70. package/docs/asset.md +7 -0
  71. package/docs/bottom-navigation.md +16 -1
  72. package/docs/brand-boundary.md +3 -3
  73. package/docs/collection-rail.md +4 -0
  74. package/docs/compound-controls.md +8 -3
  75. package/docs/consumer-policy.md +39 -120
  76. package/docs/generated/component-maturity.md +1 -1
  77. package/docs/generated/renderer-evidence.json +3 -3
  78. package/docs/generated/renderer-evidence.md +1 -1
  79. package/docs/generated/showcase-manifest.json +1 -1
  80. package/docs/gravity-letters.md +2 -0
  81. package/docs/grid-reveal.md +2 -0
  82. package/docs/layout.md +2 -2
  83. package/docs/load-more.md +5 -0
  84. package/docs/migration-native-legacy-removal.md +30 -17
  85. package/docs/optional-adapters.md +4 -0
  86. package/docs/screen-patterns.md +23 -1
  87. package/docs/toast.md +64 -2
  88. package/docs/usage/README.md +248 -243
  89. package/docs/usage/SELECTION.md +43 -0
  90. package/docs/usage/STANDARD.md +22 -10
  91. package/docs/usage/components/accordion.md +5 -3
  92. package/docs/usage/components/activity-chip.md +109 -0
  93. package/docs/usage/components/agreement.md +6 -9
  94. package/docs/usage/components/alert-dialog.md +12 -6
  95. package/docs/usage/components/asset.md +10 -4
  96. package/docs/usage/components/auth-provider-button.md +2 -2
  97. package/docs/usage/components/avatar.md +8 -6
  98. package/docs/usage/components/badge.md +4 -4
  99. package/docs/usage/components/bottom-cta.md +4 -4
  100. package/docs/usage/components/bottom-info.md +2 -2
  101. package/docs/usage/components/bottom-navigation.md +18 -6
  102. package/docs/usage/components/button.md +6 -3
  103. package/docs/usage/components/card.md +28 -7
  104. package/docs/usage/components/carousel.md +12 -4
  105. package/docs/usage/components/chat-message.md +10 -2
  106. package/docs/usage/components/chat-screen.md +5 -5
  107. package/docs/usage/components/checkbox-group.md +1 -1
  108. package/docs/usage/components/checkbox.md +2 -2
  109. package/docs/usage/components/chip.md +2 -2
  110. package/docs/usage/components/code-block.md +3 -4
  111. package/docs/usage/components/collapsible.md +6 -4
  112. package/docs/usage/components/combobox.md +2 -2
  113. package/docs/usage/components/command-palette.md +1 -1
  114. package/docs/usage/components/comment-thread-screen.md +7 -4
  115. package/docs/usage/components/container.md +1 -1
  116. package/docs/usage/components/content-transition.md +3 -2
  117. package/docs/usage/components/context-menu.md +2 -2
  118. package/docs/usage/components/counter-badge.md +3 -3
  119. package/docs/usage/components/data-table.md +1 -1
  120. package/docs/usage/components/date-picker.md +4 -4
  121. package/docs/usage/components/description-list.md +3 -3
  122. package/docs/usage/components/design-system-provider.md +2 -2
  123. package/docs/usage/components/dialog.md +14 -3
  124. package/docs/usage/components/divider.md +2 -2
  125. package/docs/usage/components/editor-screen.md +5 -3
  126. package/docs/usage/components/effect-surface.md +1 -1
  127. package/docs/usage/components/empty-state.md +6 -6
  128. package/docs/usage/components/field.md +5 -3
  129. package/docs/usage/components/file-picker.md +6 -3
  130. package/docs/usage/components/form.md +1 -1
  131. package/docs/usage/components/heading.md +3 -3
  132. package/docs/usage/components/icon-button.md +6 -3
  133. package/docs/usage/components/icon.md +1 -1
  134. package/docs/usage/components/image.md +5 -4
  135. package/docs/usage/components/keyboard-dock.md +1 -1
  136. package/docs/usage/components/layout.md +1 -1
  137. package/docs/usage/components/link.md +2 -2
  138. package/docs/usage/components/list-detail-screen.md +6 -6
  139. package/docs/usage/components/list-row.md +13 -4
  140. package/docs/usage/components/list.md +5 -4
  141. package/docs/usage/components/masonry.md +2 -1
  142. package/docs/usage/components/media-selection-screen.md +11 -5
  143. package/docs/usage/components/mentions.md +2 -2
  144. package/docs/usage/components/menu.md +3 -2
  145. package/docs/usage/components/menubar.md +2 -2
  146. package/docs/usage/components/message-composer.md +3 -3
  147. package/docs/usage/components/moderation-screen.md +3 -2
  148. package/docs/usage/components/notice.md +2 -2
  149. package/docs/usage/components/notification-inbox-screen.md +1 -1
  150. package/docs/usage/components/notification-item.md +2 -2
  151. package/docs/usage/components/number-field.md +3 -3
  152. package/docs/usage/components/otp-field.md +1 -1
  153. package/docs/usage/components/overview-screen.md +1 -1
  154. package/docs/usage/components/permission-screen.md +1 -1
  155. package/docs/usage/components/photo-source-sheet.md +1 -1
  156. package/docs/usage/components/popover.md +2 -2
  157. package/docs/usage/components/profile-screen.md +1 -1
  158. package/docs/usage/components/progress.md +2 -2
  159. package/docs/usage/components/qr-code.md +2 -2
  160. package/docs/usage/components/radio-group.md +1 -2
  161. package/docs/usage/components/radio.md +1 -2
  162. package/docs/usage/components/result.md +1 -1
  163. package/docs/usage/components/saved-items-screen.md +4 -2
  164. package/docs/usage/components/screen-layout.md +4 -3
  165. package/docs/usage/components/search-field.md +1 -1
  166. package/docs/usage/components/search-screen.md +4 -4
  167. package/docs/usage/components/section.md +3 -3
  168. package/docs/usage/components/segmented-control.md +4 -4
  169. package/docs/usage/components/select.md +2 -3
  170. package/docs/usage/components/settings-screen.md +18 -56
  171. package/docs/usage/components/sheet.md +10 -5
  172. package/docs/usage/components/sidebar.md +1 -1
  173. package/docs/usage/components/skeleton.md +1 -1
  174. package/docs/usage/components/skip-nav.md +1 -1
  175. package/docs/usage/components/slider.md +2 -2
  176. package/docs/usage/components/spinner.md +2 -2
  177. package/docs/usage/components/splitter.md +1 -1
  178. package/docs/usage/components/statistic.md +5 -4
  179. package/docs/usage/components/steps.md +2 -2
  180. package/docs/usage/components/surface.md +2 -3
  181. package/docs/usage/components/switch.md +1 -1
  182. package/docs/usage/components/tabs.md +4 -4
  183. package/docs/usage/components/tag.md +2 -2
  184. package/docs/usage/components/tags-input.md +4 -4
  185. package/docs/usage/components/text-area.md +5 -3
  186. package/docs/usage/components/text.md +85 -4
  187. package/docs/usage/components/timeline.md +2 -2
  188. package/docs/usage/components/toast.md +41 -13
  189. package/docs/usage/components/toggle-group.md +3 -3
  190. package/docs/usage/components/tooltip.md +1 -1
  191. package/docs/usage/components/top-bar.md +5 -4
  192. package/docs/usage/components/top.md +3 -3
  193. package/docs/usage/components/transfer-list.md +5 -4
  194. package/docs/usage/components/upload-item.md +3 -3
  195. package/docs/usage/components/virtual-list.md +1 -0
  196. package/docs/usage/compositions/action-feedback.md +42 -6
  197. package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
  198. package/docs/usage/compositions/action-recovery-save.md +19 -23
  199. package/docs/usage/compositions/action-recovery-undo.md +6 -1
  200. package/docs/usage/compositions/activity-status.md +168 -0
  201. package/docs/usage/compositions/collection-detail.md +2 -0
  202. package/docs/usage/compositions/command-records.md +15 -5
  203. package/docs/usage/compositions/common-message.md +4 -2
  204. package/docs/usage/compositions/common-notification.md +5 -3
  205. package/docs/usage/compositions/compound-controls.md +13 -5
  206. package/docs/usage/compositions/content-transition-comparison.md +2 -2
  207. package/docs/usage/compositions/date-entry.md +10 -5
  208. package/docs/usage/compositions/date-time-selection.md +3 -3
  209. package/docs/usage/compositions/design-profile-comparison.md +7 -5
  210. package/docs/usage/compositions/disclosure.md +1 -1
  211. package/docs/usage/compositions/document-resource.md +10 -3
  212. package/docs/usage/compositions/expo-interactions.md +3 -2
  213. package/docs/usage/compositions/family-drawer.md +9 -4
  214. package/docs/usage/compositions/field-group.md +7 -1
  215. package/docs/usage/compositions/floating-action-button.md +18 -18
  216. package/docs/usage/compositions/illustrated-outcome.md +3 -2
  217. package/docs/usage/compositions/input-sheet.md +6 -2
  218. package/docs/usage/compositions/interaction-adapters.md +1 -1
  219. package/docs/usage/compositions/interaction-flow-apply.md +6 -3
  220. package/docs/usage/compositions/interaction-flow-draft.md +5 -2
  221. package/docs/usage/compositions/interaction-flow-search.md +5 -2
  222. package/docs/usage/compositions/live-list.md +7 -5
  223. package/docs/usage/compositions/navigation-bar-collection.md +8 -7
  224. package/docs/usage/compositions/optional-adapters.md +1 -1
  225. package/docs/usage/compositions/origin-dialog.md +5 -5
  226. package/docs/usage/compositions/paper-surface.md +1 -1
  227. package/docs/usage/compositions/photo-source.md +1 -1
  228. package/docs/usage/compositions/purpose-input-comment.md +5 -3
  229. package/docs/usage/compositions/purpose-input-message.md +5 -3
  230. package/docs/usage/compositions/reference-first.md +63 -42
  231. package/docs/usage/compositions/reference-review.md +60 -50
  232. package/docs/usage/compositions/reference-settings.md +60 -50
  233. package/docs/usage/compositions/selection-scope.md +48 -35
  234. package/docs/usage/compositions/stea-order-progress.md +1 -0
  235. package/docs/usage/compositions/stea-otp-verify.md +5 -3
  236. package/docs/usage/compositions/stea-pixel-empty.md +3 -3
  237. package/docs/usage/compositions/time-selection.md +6 -5
  238. package/docs/usage/compositions/upload-recovery.md +10 -1
  239. package/docs/usage/compositions/video-dialog.md +10 -3
  240. package/docs/usage/compositions/web-additions.md +5 -5
  241. package/docs/usage/compositions/web-navigation.md +1 -1
  242. package/docs/usage/screens/common-chat.md +21 -8
  243. package/docs/usage/screens/common-comments.md +13 -1
  244. package/docs/usage/screens/common-inbox.md +19 -6
  245. package/docs/usage/screens/common-login.md +19 -7
  246. package/docs/usage/screens/common-profile.md +22 -9
  247. package/docs/usage/screens/common-saved.md +16 -2
  248. package/docs/usage/screens/common-search.md +24 -4
  249. package/docs/usage/screens/common-settings.md +22 -50
  250. package/docs/usage/screens/common-shell.md +14 -2
  251. package/docs/usage/screens/dashboard.md +52 -109
  252. package/docs/usage/screens/discovery-gallery.md +57 -148
  253. package/docs/usage/screens/flow-collection.md +38 -9
  254. package/docs/usage/screens/flow-editor.md +25 -7
  255. package/docs/usage/screens/flow-media.md +23 -7
  256. package/docs/usage/screens/flow-moderation.md +15 -2
  257. package/docs/usage/screens/flow-onboarding.md +3 -3
  258. package/docs/usage/screens/flow-permission.md +17 -3
  259. package/docs/usage/screens/landing.md +37 -20
  260. package/docs/usage/screens/mockup-studio.md +22 -7
  261. package/docs/usage/screens/notification-settings.md +18 -4
  262. package/docs/usage/screens/product-bento.md +18 -9
  263. package/docs/usage/screens/reference-comparison.md +29 -10
  264. package/docs/usage/tokens/color.md +6 -3
  265. package/docs/usage/tokens/elevation-opacity.md +4 -2
  266. package/docs/usage/tokens/font-roles.md +2 -2
  267. package/docs/usage/tokens/layers.md +3 -3
  268. package/docs/usage/tokens/motion.md +2 -2
  269. package/docs/usage/tokens/radius.md +4 -2
  270. package/docs/usage/tokens/spacing.md +3 -3
  271. package/docs/usage/tokens/theme-studio.md +2 -2
  272. package/docs/usage/tokens/typography-studio.md +14 -8
  273. package/docs/usage/tokens/typography.md +15 -5
  274. package/package.json +7 -1
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-08
7
+ - 검토일: 2026-10-10
8
8
  - 근거: [BottomNavigation](../../bottom-navigation.md), recipe `bottomNavigationRecipe`
9
9
  - 스토리북: `배포/컴포넌트/탐색/하단 탐색`
10
10
 
@@ -73,6 +73,7 @@ import { BottomNavigation } from "@hjmds/react-native/navigation";
73
73
  | `configuration.density` | `regular` · `compact` | `regular` | — |
74
74
  | `configuration.direction` | `ltr` · `rtl` | Provider 환경(없으면 `ltr`) | — |
75
75
  | `configuration.keyboardBehavior` | `hide` · `remain` | `hide` | 기본은 소프트 키보드가 열리면 막대를 숨긴다 |
76
+ | `configuration.labels` | `visible` · `hidden` | `visible` | `hidden`은 아이콘만 남기고 목적지 이름을 화면에서 뺀다. 이름은 항목의 접근성 이름으로 그대로 읽힌다. 아이콘을 칸 가운데에 둔다. 선택 표시는 아이콘 굵기·확대(와 제품 선택 표시)만 남으므로 의도한 아이콘 전용 막대에만 쓴다 |
76
77
  | `primaryAction` | `ReactNode`(Button/IconButton) | — | `configuration` 밖의 별도 prop. 생성 같은 비목적지 행동이며 `center-gap` 또는 `capsule`과 짝짓는다 |
77
78
  | 항목 `badge` | `{ count, max?, accessibilityLabel }` | `max` 99 | 숫자만. 점 배지는 없다. `count`가 0이면 그리지 않는다 |
78
79
  | `onActivate` | `(activation: { key: Key; reason: "navigate" \| "reselect" }) => void` | Web 선택 · Native 필수 | 이동 의도만 알린다. 선택은 바꾸지 않는다. `reselect`는 이미 선택된 항목을 다시 누른 경우 |
@@ -83,7 +84,7 @@ import { BottomNavigation } from "@hjmds/react-native/navigation";
83
84
  | Native `renderBadge` | `(props: { item; badge: { visibleLabel: string; hiddenFromAccessibility: true }; count: number; max?: number; selected: boolean }) => ReactNode` | 기본 배지 | 결과 subtree는 보조기기에서 숨겨진다 |
84
85
  | Native `getItemTestID` | `(item) => string \| undefined` | — | — |
85
86
  | Native `safeAreaBottom` | 0 이상 숫자 | 0 | recipe 최소 하단 padding에 더한다 |
86
- | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 deprecated — layoutStyle 또는 `configuration` |
87
+ | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 제거된 속성 — layoutStyle 또는 `configuration` |
87
88
 
88
89
  ## 배치
89
90
 
@@ -118,7 +119,7 @@ floating·capsule: 바깥 좌우 16 · 위 8 띄운 둥근 표면, 최대 폭 38
118
119
  - icon은 `name`만 넘긴다. 크기·색·굵기는 `renderIcon`이 받은 값을 그대로 적용한다(덮어쓰기 금지).
119
120
  - label과 badge `accessibilityLabel`은 i18n 키로 만든다. icon 이름·아이콘 세트는 제품 소유다.
120
121
  - 생성 버튼을 항목으로 넣지 않는다. `primaryAction`으로 둔다.
121
- - 배치는 `layoutStyle`로만 한다. Native의 `style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 deprecated(개발 모드 1회 경고, 다음 major 제거)이며 외형은 `configuration`이 소유한다.
122
+ - 배치는 `layoutStyle`로만 한다. Native의 `style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 제거된 속성이며 외형은 `configuration`이 소유한다.
122
123
 
123
124
  ## 플랫폼 차이
124
125
 
@@ -130,13 +131,24 @@ floating·capsule: 바깥 좌우 16 · 위 8 띄운 둥근 표면, 최대 폭 38
130
131
  | 하단 inset | `env(safe-area-inset-bottom)` 자동 | `safeAreaBottom`(recipe 최소 padding에 더함) |
131
132
  | 키보드 감지 | `visualViewport` 높이 | `Keyboard` 이벤트 |
132
133
  | `renderIcon`의 `color` | `"currentColor"` | 해석된 색 문자열 |
133
- | 그 밖 | `className`·`style`·HTML 속성·`layoutStyle` | `renderBadge`, `getItemTestID`, `layoutStyle`(`style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 deprecated) |
134
+ | 그 밖 | `className`·`style`·HTML 속성·`layoutStyle` | `renderBadge`, `getItemTestID`, `layoutStyle`(`style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 제거된 속성) |
134
135
 
135
136
  ## 함정
136
137
 
137
138
  - Web root가 `position: fixed`라 본문 마지막 내용이 막대 아래로 들어간다. 제품 레이아웃이 하단 여백을 둔다.
138
139
  - `renderLink` 없이 쓰면 일반 `<a>`로 그려 SPA 전환이 일어나지 않는다.
139
140
  - Web `onActivate`는 링크 기본 이동을 막지 않는다. `renderLink`의 라우터 Link가 이동하고 `onActivate`는 계측·맨 위로 스크롤 같은 부수 동작에만 쓴다. 수정키·가운데 클릭에는 불리지 않는다.
140
- - 현재 Native 내비게이션 연구 스토리(`showcase/native/src/reference-navigation-bars.tsx`)는 deprecated `style`(`paddingHorizontal: 0`)·`listStyle`(`borderRadius`)로 recipe 여백·모서리를 덮고, 제목을 `Text variant="heading"`으로 그린다. 규칙은 `configuration`·`layoutStyle`과 [Heading](heading.md)이다(스토리 수정 후보).
141
+ - Native 내비게이션 연구 스토리(`showcase/native/src/reference-navigation-bars.tsx`)는 `configuration`과 [Heading](heading.md)을 사용한다. 과거의 `style`·`listStyle` override는 제거되었으며, 선택 타일과 모서리는 공개 configuration 축으로 정한다.
141
142
 
142
- 미게시(1.14.0 이후): Native의 floating/capsule은 프로필 `shadow.floating`을 읽으며 0-opacity는 Android elevation도 제거한다. bar는 원래 무그림자 계약을 유지한다. Web은 기존 CSS token 경로로 같은 프로필을 읽는다. 선택 route와 이동 intent는 제품이 소유한다.
143
+ 1.15.0 변경: Native의 floating/capsule은 프로필 `shadow.floating`을 읽으며 0-opacity는 Android elevation도 제거한다. bar는 원래 무그림자 계약을 유지한다. Web은 기존 CSS token 경로로 같은 프로필을 읽는다. 선택 route와 이동 intent는 제품이 소유한다.
144
+
145
+
146
+ ### 선택 카드 (2026-10-12)
147
+
148
+ Spint에서 모든 이름·동일 너비를 유지하면서 선택한 목적지를 분명히 구분하기 위해
149
+ `configuration={{ presentation: "floating", distribution: "equal", density: "compact", selection: "tile" }}`를 추가했다.
150
+ 기본 `selection: "emphasis"`는 기존 외형을 유지한다. `tile`은 floating·equal·visible labels에만 허용된다.
151
+ Web·Native 모두 막대에는 surfaceAccent, 선택한 항목에는 primary/onPrimary를 사용한다.
152
+ 연한 선택 카드가 도크와 구분되지 않는 사용자 피드백에 따라 선택한 카드 전체를 브랜드 색으로 채운다.
153
+ 안쪽 여백과 카드 사이 간격은 spacing.xxs, 카드 모서리는 radius.lg다.
154
+ 라우팅·누름 방지·키보드·접근성·배지는 기존 BottomNavigation 계약을 그대로 따른다.
@@ -71,9 +71,9 @@ import { Button } from "@hjmds/react-native/actions";
71
71
  | Native `fullWidth` | `boolean` | `false` | 부모 폭을 채운다 |
72
72
  | Native `loadingLabel` · `renderLoadingIndicator` | `ReactNode` · `(props: { color: string; size: "small" }) => ReactNode` | — | 로딩 중 읽기 문구·스피너 교체 |
73
73
  | Native `growWithContent` | `boolean` | `false` | 이미지 등 사용자 콘텐츠가 recipe 높이를 넘어 늘어나게 한다. 문자열·숫자 라벨은 이 옵션 없이도 줄 수에 맞춰 늘어난다 |
74
- | `ClipboardButton` | `value: string`, `labels: { idle; copied }`, `onCopy?: (value: string) => void`, `onCopyError?: (error: unknown) => void`, `feedbackDuration?`(ms, 2000) | — | Button props를 물려받는다(`tone` 기본 `secondary`, 미게시(1.12.1 이후). 1.12.1은 `primary`). 쓰는 법은 [CodeBlock](code-block.md) |
74
+ | `ClipboardButton` | `value: string`, `labels: { idle; copied }`, `onCopy?: (value: string) => void`, `onCopyError?: (error: unknown) => void`, `feedbackDuration?`(ms, 2000) | — | Button props를 물려받는다(`tone` 기본 `secondary`, 1.13.0 변경). 쓰는 법은 [CodeBlock](code-block.md) |
75
75
  | `InlineConfirm` | `label`·`prompt`·`confirmLabel`·`cancelLabel`·`pendingLabel`·`successLabel`·`errorLabel`(모두 `string`), `onConfirm: () => void \| Promise<void>` | — | danger 버튼 → 같은 자리 확인 묶음. Promise가 거부되면 `errorLabel`을 alert로 보인다. Web만 `layoutStyle` |
76
- | `ReactionPicker` | `label: string`, `options: readonly { id; emoji; label; count?; disabled? }[]`, `value: string \| null`, `onValueChange: (value: string \| null) => void`, `layout?: "wrap" \| "strip"`, `more?: { label; options }` | `layout` `wrap` | 제어 전용. 같은 반응을 다시 누르면 `null`. 이모지는 `strip`(대화 반응 줄)에서만 `typography.title` 18/26으로 커지고 `wrap`은 버튼 글자 크기 그대로다. `more`를 주면 끝에 `+` 토글이 생겨 카탈로그를 펼치고, 카탈로그에만 있는 이모지를 고르면 접히면서 Web 키보드 포커스가 `+` 버튼으로 간다. Web만 `layoutStyle`(루트 기본 `minWidth`보다 우선). `layout`·`more`·`layoutStyle`은 미게시(1.12.1 이후) |
76
+ | `ReactionPicker` | `label: string`, `options: readonly { id; emoji; label; count?; disabled? }[]`, `value: string \| null`, `onValueChange: (value: string \| null) => void`, `layout?: "wrap" \| "strip"`, `more?: { label; options }` | `layout` `wrap` | 제어 전용. 같은 반응을 다시 누르면 `null`. 이모지는 `strip`(대화 반응 줄)에서만 `typography.title` 18/26으로 커지고 `wrap`은 버튼 글자 크기 그대로다. `more`를 주면 끝에 `+` 토글이 생겨 카탈로그를 펼치고, 카탈로그에만 있는 이모지를 고르면 접히면서 Web 키보드 포커스가 `+` 버튼으로 간다. Web만 `layoutStyle`(루트 기본 `minWidth`보다 우선). `layout`·`more`·`layoutStyle`은 1.13.0 변경 |
77
77
 
78
78
  ## 배치
79
79
 
@@ -106,7 +106,7 @@ import { Button } from "@hjmds/react-native/actions";
106
106
  Native에서 `style`·`labelStyle`을 넘기면 실행 중 `TypeError`가 난다.
107
107
  - 색은 tone과 제품 테마 토큰으로 바꾼다. 버튼마다 브랜드 색을 하드코딩하지 않는다.
108
108
  - 진행 중 상태는 `loading`으로 표시하고 같은 자리에 별도 Spinner를 겹치지 않는다. children을 빈 문자열이나 다른 길이의 진행 문구로 바꾸지 않는다. 렌더러가 문구·아이콘을 숨긴 자리에 중앙 스피너만 표시하고 기존 크기·접근성 이름을 유지한다. 2026-10-06 독립 검증에서 이 사용자 요구가 사용 지침에는 빠져 있음을 확인해 명시했다.
109
- - "한 화면 `primary` 하나"의 예외는 다음과 같다. `selected`를 준 버튼은 tone과 관계없이 `buttonRecipe.states.selected`(배경 `bg`, 글자·테두리 `contentBrand`)로 칠해져 primary 채움이 아니므로 세지 않고(Web `.hjm-button[data-selected="true"]`, Native `internal/recipe-button.tsx`, 검색·작품 탐색 스토리의 필터 줄), Sheet·Dialog 안의 행동은 그 표면 안에서 primary 하나를 센다([Sheet](sheet.md) `footer`, 작품 탐색·랜딩 스토리의 시트).
109
+ - "한 화면 `primary` 하나"의 예외는 다음과 같다. `selected`를 준 버튼은 tone과 관계없이 `buttonRecipe.states.selected`(배경 `bg`, 글자 `contentBrand`, 선택 테두리 없음)로 칠해져 primary 채움이 아니므로 세지 않고(Web `.hjm-button[data-selected="true"]`, Native `internal/recipe-button.tsx`, 검색·작품 탐색 스토리의 필터 줄), Sheet·Dialog 안의 행동은 그 표면 안에서 primary 하나를 센다([Sheet](sheet.md) `footer`, 작품 탐색·랜딩 스토리의 시트).
110
110
 
111
111
  - 긴 소개 화면에서 위·아래 CTA가 **동일한 행동**으로 이어지고 서로 다른 스크롤 구간에 있으면 둘 다 primary를 허용한다. 큰 글자에서 첫 CTA가 멀어지는 문제를 보완하기 위한 예외이며, 서로 다른 가입·구매 행동을 동시에 강조하는 근거로 쓰지 않는다.
112
112
 
@@ -122,3 +122,6 @@ import { Button } from "@hjmds/react-native/actions";
122
122
 
123
123
 
124
124
  - 2026-10-06 독립 지침 재구현에서 footer 순서를 모든 본문 행동에 강제하는 것으로 읽혔다. 시간 선택처럼 주 행동 다음에 초기화가 오는 구성과 AlertDialog의 좁은 폭 순서는 각각 명시된 구성 계약을 따른다.
125
+
126
+ 2026-10-10 사용자 요청으로 선택 시 추가되는 테두리를 제거했다. `selected` 접근성 상태와
127
+ contentBrand 글자·아이콘 색은 유지하며 키보드 focus outline은 선택과 별도로 유지한다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-07
7
+ - 검토일: 2026-10-10
8
8
  - 근거: recipe `cardRecipe`(`src/card.ts`), 바탕 `surfaceRecipe`(`src/base-recipes.ts`). 별도 계약 문서는 없다
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/카드`
10
10
 
@@ -78,9 +78,31 @@ import { Button } from "@hjmds/react-native/actions";
78
78
  | `selected` | `boolean` | `false` | `true`면 tone이 `accent`로 바뀐다(`cardRecipe.selectedTone`) |
79
79
  | `headingLevel`(Web만) | `2` · `3` · `4` | `3` | 제목이 `h3`로 렌더되므로 문서 위계에 맞춰 고른다 |
80
80
  | `title` · `description` · `leading` · `media` · `actions` · `children` | `ReactNode` | — | 슬롯. 순서는 HJM이 고정한다 |
81
+ | `onActivate` | `() => void` | — | 카드 전체가 하나의 행동일 때 사용(미게시). Web `aria-label`, Native `accessibilityLabel` 필수 |
82
+ | `disabled` | `boolean` | `false` | 전체 카드 행동 차단·비활성 피드백(미게시) |
81
83
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. 두 플랫폼 모두 root Surface에 적용된다 |
82
84
 
83
- Card 자체에는 콜백이 없다. 누름 행동은 `actions`의 Button(Web `onClick: (event: MouseEvent<HTMLButtonElement>) => void`, Native `onPress`)이 갖는다.
85
+ ### 카드 전체 누름(미게시)
86
+
87
+ 2026-10-10 번뚝의 세 열 시작점 선택에서 별도 선택 버튼이 좁은 카드의 절반을 차지했다.
88
+ `onActivate`는 기존 Card의 구조를 유지하면서 전체 표면을 하나의 행동으로 만든다.
89
+ 제품이 Card 바깥에 Pressable을 다시 조립하는 대신 HJM이 버튼 역할, 선택·비활성 상태,
90
+ 44pt 최소 대상, 눌림·초점 피드백을 소유한다. 같은 카드를 다시 눌러도 콜백을 호출하므로
91
+ 제품이 수정된 초안 교체 확인을 처리할 수 있다. RadioGroup은 단일 옵션 값 변경용이며,
92
+ 이처럼 같은 항목을 다시 실행하는 카드 동작을 대신하지 않는다.
93
+
94
+ ```tsx
95
+ <Card aria-label={starter.title} onActivate={() => choose(starter)}
96
+ selected={selectedId === starter.id} disabled={pending} bordered={selectedId === starter.id} padding="sm">
97
+ <span>{starter.emoji} {starter.title}</span>
98
+ </Card>
99
+ // Native는 aria-label 대신 accessibilityLabel을 전달한다.
100
+ ```
101
+
102
+ `onActivate`와 `actions`를 함께 쓰지 않는다. children·media·leading에도 입력/링크/버튼을
103
+ 넣지 않는다. 내부 행동이 여러 개인 카드에는 기존 `actions` 슬롯을 사용한다.
104
+ 기존 passive Card는 콜백 없이 동일하게 유지된다. 이 API는 로컬 source/candidate 단계이며
105
+ npm 게시 및 소비 제품의 게시 버전 채택 완료를 뜻하지 않는다.
84
106
 
85
107
  2026-10-07 [확장 카드·그리드 대조](../../../../../docs/plans/aceternity-interaction-adoption-2026-10-07.md)에서
86
108
  카드와 상세 표시를 별도 새 카드 엔진으로 복제할 필요가 없는 경우를 확인했다. 상세는 `actions`의
@@ -88,7 +110,7 @@ Button으로 [Dialog](dialog.md)를 열고, 위치 연결이 필요하면 Dialog
88
110
  같은 흐름 안에서 내용을 늘릴 때는 [Collapsible](collapsible.md)를 고른다. 이미지·제목이 각각 이동하는
89
111
  shared-element 효과와 일반 상세 Dialog는 다르며, 전자를 제공한다고 안내하지 않는다.
90
112
 
91
- ### 프로필 표면 질감(미게시)
113
+ ### 프로필 표면 질감(1.15.0 추가)
92
114
 
93
115
  선택한 `designProfile.material.surface`를 자동으로 읽는다. glass는 지원하는 Web에서 실제 배경 흐림,
94
116
  clay는 안쪽 그림자를 사용한다. Native는 루트 Provider의 선택형 `surfaceEffects` host와 inset capability를
@@ -112,13 +134,13 @@ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지
112
134
  - 색·radius·padding은 `tone`·`radius`·`padding` 축으로만 바꾼다. 카드마다 브랜드 색을 칠하지 않는다.
113
135
  - 배치는 두 플랫폼 모두 `layoutStyle`로 한다. Native `CardProps`에는 `style`이 없다. Web의 `className`·`style`로 recipe 값을 덮지 않는다.
114
136
  카드 목록 사이 간격은 바깥 [Stack](stack.md)·[Grid](grid.md)이 정한다.
115
- - Card 자체는 누름 행동이 없다(Native는 `onPress`가 없다). 카드 안 행동은 `actions`의 Button으로 둔다.
137
+ - 카드 전체 행동은 `onActivate`만 사용한다. 카드 안 행동이 있으면 전체 행동을 빼고 `actions`의 Button으로 둔다.
116
138
 
117
139
  ## 플랫폼 차이
118
140
 
119
141
  | 항목 | Web | Native |
120
142
  | --- | --- | --- |
121
- | root | `<article>` | `View`(Surface) |
143
+ | root | 기본 `<article>`, 전체 행동은 `<button type="button">` | 기본 `View`(Surface), 전체 행동은 `Pressable` |
122
144
  | 제목 | `h{headingLevel}` | `Text` + `accessibilityRole="header"`, 수준 지정 없음 |
123
145
  | 배치 | `layoutStyle` | `layoutStyle` |
124
146
  | 내용 clip | tone의 `clipsContent`(`raised`는 그림자 때문에 clip 안 함) | 내부 `View`가 항상 clip, 그림자는 바깥에 남는다. frame과 clip이 같은 Provider radius token을 사용 |
@@ -129,5 +151,4 @@ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지
129
151
  등록되면 그 테마의 `tokens.radius.lg`를 쓴다. 2026-10-07 카드 갤러리 비교에서 Native의 바깥
130
152
  Surface만 테마를 따르고 내부 media clip은 foundation 값을 쓰는 누락을 발견해 같은 token으로 연결했다.
131
153
 
132
- - `selected`는 tone만 바꾼다. Web은 `data-state="selected"` 속성뿐이고 Native는 접근성 state를
133
- 알리지 않는다. 선택 가능한 카드 목록이라면 선택 상태를 문구나 다른 컨트롤로도 전달한다.
154
+ - passive Card의 `selected`는 tone 표시다. 전체 행동 Card는 Web `aria-pressed`, Native `accessibilityState.selected`로도 선택을 알린다.
@@ -13,11 +13,14 @@
13
13
  한 번에 카드 하나만 보이고 사용자가 순서대로 넘겨 보는 유한한 묶음에 쓴다. 오늘 경기 스트립,
14
14
  소개 카드 몇 장이 여기에 속한다. 끝에서 처음으로 돌아가지 않고, 자동 재생은 `autoplay`를 줄 때만 켜진다.
15
15
 
16
+ 비제어 Carousel에서 현재 항목이 삭제되면 삭제 전 위치의 항목을 선택하고, 그 위치가 없어졌으면 새 마지막 항목으로 이동한다. 선택 키가 바뀌면 `onCurrentKeyChange`를 한 번 알리고 번호·선택 표시·탐색 버튼도 함께 갱신한다. 웹에서 삭제된 카드나 점에 포커스가 있었다면 새 선택 점으로 복구한다. 이는 데이터 변경으로 화면이 오류로 닫히지 않도록 한 규칙이다. 최초 `defaultCurrentKey` 오류, 제어형 `currentKey` 오류, 빈 `slides`는 여전히 계약 오류다.
17
+
16
18
  ## 쓰지 않을 때
17
19
 
18
20
  | 상황 | 대신 쓸 것 |
19
21
  | --- | --- |
20
22
  | 여러 항목을 한눈에 비교·훑기 | [List](list.md), [Masonry](masonry.md) |
23
+ | 여러 카드의 독립 입력·행동을 함께 유지하는 유한 가로 탐색 | [CollectionRail](list.md#collectionrail-가로-배치) |
21
24
  | 항목 수가 많거나 끝이 없음 | [VirtualList](virtual-list.md), [LoadMore](load-more.md) |
22
25
  | 같은 자리의 보기 전환 | [Tabs](tabs.md), [SegmentedControl](segmented-control.md) |
23
26
 
@@ -78,7 +81,8 @@ import { Carousel } from "@hjmds/react-native/carousel";
78
81
  | `labels` | `{ previous: string; next: string; pause: string; resume: string; navigation: string }` | 필수 | 다섯 값 모두 비우면 `TypeError` |
79
82
  | `currentKey` + `onCurrentKeyChange` · `defaultCurrentKey` | 슬라이드 `id`, `(key: string) => void` | 첫 슬라이드 | 제어(둘 다 필수)·비제어 중 하나만 타입이 허용한다. 값은 인덱스가 아니라 `id`다 |
80
83
  | `autoplay` | `{ intervalMs: number }`(0보다 큼) | 없음 | 주면 일시정지/재개 버튼이 생긴다. 마지막 슬라이드, reduced motion, 사용자 조작 뒤에는 멈추고 명시적인 재개 전까지 다시 돌지 않는다 |
81
- | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`은 deprecated — layoutStyle 또는 tone/토큰 |
84
+ | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`은 제거된 속성 — `layoutStyle`로 배치하고 내부 표현은 recipe를 따른다 |
85
+ | `CarouselMotion` 탐색 | `navigation?: "buttons" \| "counter"` | `buttons` | `counter`는 이전 아이콘·현재/전체·다음 아이콘을 한 줄에 둔다. 끝에서는 해당 버튼을 비활성화한다. Web/Native 동일, 미게시 |
82
86
  | `CarouselMotion` 선택 | `currentKey: string`, `onCurrentKeyChange(key: string): void` | 필수(항상 제어) | autoplay가 없다. `slides`는 `{ id, label, disabled? }` |
83
87
  | `CarouselMotion` 라벨 | `label`·`previousLabel`·`nextLabel`, 선택 `composeAccessibleName` | — | `composeAccessibleName`이 없으면 슬라이드 `label`을 쓴다 |
84
88
  | `CarouselMotion` 크기(Native) | `width`·`height` 측정값 | 필수 | 양수가 아니면 `TypeError`. Web `CarouselMotion`은 `layoutStyle`을 받고 Native는 받지 않는다 |
@@ -103,20 +107,24 @@ import { Carousel } from "@hjmds/react-native/carousel";
103
107
  [Cedar Filmstrip 대조](../../../../../docs/qa/2026-10-07-reference-parallel-c.md)에서 여러 카드가
104
108
  동시에 보이는 strip은 단일 active panel과 다른 계약임을 확인했다. 현재 Carousel에 임의
105
109
  `basis-1/3`/translate 스타일을 덮어 strip을 제공하지 않는다. 선택 버튼의 초점·현재 위치 의미,
106
- 끝 정렬·폭 변경·부분 노출 항목의 초점/읽기 순서를 함께 갖춘 명시적 구성 확장이 필요한 후보다.
110
+ 끝 정렬·폭 변경·부분 노출 항목의 초점/읽기 순서는 기존 [CollectionRail](list.md#collectionrail-가로-배치)이 담당한다.
107
111
  - 두 renderer는 숨겨진 슬라이드에도 `renderSlide`를 호출한다. 숨김은 네트워크 요청 취소나
108
112
  자식 unmount가 아니다. 권한 확인이 필요한 사진을 현재 페이지만 읽는 제품은 제어된
109
113
  `currentKey`와 비교해 선택되지 않은 콘텐츠를 `null`로 반환한다. 2026-10-07 Utilverse의
110
114
  선택 페이지 단독 조회·로그아웃 후 캐시 제거 계약을 대조하며 확인한 경계다.
111
- - 배치는 `layoutStyle`로만 한다. Native `style`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
115
+ - 배치는 `layoutStyle`로만 한다. Native `style`은 제거된 속성이다.
112
116
 
113
117
  ## 플랫폼 차이
114
118
 
115
119
  | 항목 | Web | Native |
116
120
  | --- | --- | --- |
117
- | 배치 | `layoutStyle`(HTML `className`도 전달) | `layoutStyle`(`style`은 deprecated) |
121
+ | 배치 | `layoutStyle`(HTML `className`도 전달) | `layoutStyle`(`style`은 제거된 속성) |
118
122
  | 위치 표시 | 점 버튼 | 번호 버튼 + 조정 가능(adjustable) 위치 요소 |
119
123
  | 스와이프 | 없음(`CarouselMotion` 필요) | 기본 가로 스와이프 |
120
124
  | 자동 재생 정지 조건 | hover, 포커스, 탭 숨김 | 백그라운드, 스크린 리더 켜짐 |
121
125
  | 키보드 | 컨트롤 영역에서 좌우 화살표(RTL 반전) | 해당 없음 |
122
126
  | `CarouselMotion` 크기 | 컨테이너 폭 | `width`·`height` 필수(측정값, 양수 아니면 던짐) |
127
+
128
+ ## 사진 묶음의 넘김 (2026-10-12)
129
+
130
+ 번뚝 사진 말풍선의 한 장 교체 방식이 손가락을 따라 움직이지 않았고, 자체 컨트롤이 공통 캐러셀과 분리돼 있었다. 기존 `CarouselMotion`을 사용하고 `navigation="counter"`로 사진 번호와 두 이동 아이콘을 한 줄로 표시한다. 기본 `buttons` 표현은 유지한다. Native 크기는 실제 말풍선 폭과 이미지 슬롯 높이를 전달한다. 추가 optional peer는 위 설치 목록과 동일하며, 새로운 native 모듈을 도입하지 않는다. 제품은 활성 슬라이드만 조회하고 이미지 디코딩이 끝날 때까지 같은 슬롯 중앙의 Spinner를 제공한다. `counter`는 로컬 소스 변경이며 npm 게시·소비 앱 정식 채택은 별도다.
@@ -3,7 +3,7 @@
3
3
  - 단계: 컴포넌트
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
6
+ - 적용: 1.17.1
7
7
  - 검토일: 2026-10-07
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
9
  - 스토리북: `배포/구성/정보 표시/대화 메시지`, `배포/화면/소통/채팅`
@@ -73,6 +73,8 @@ import { Text } from "@hjmds/react-native/primitives";
73
73
  | `direction` | `"incoming"` · `"outgoing"` | 필수 | 수신은 시작 쪽, 발신은 끝 쪽에 붙는다 |
74
74
  | `author` | `string` | 필수 | 빈 문자열이면 작성자 줄을 그리지 않는다(연속 메시지 그룹) |
75
75
  | `timestamp` | `string` | 필수 | 제품이 포맷한 시각. 빈 문자열이고 `deliveryLabel`·`actions`도 없으면 메타 줄을 그리지 않는다 |
76
+ | `timestampPresentation` | `"always"` · `"swipe"` | `"always"` | `swipe`는 수신은 오른쪽·발신은 왼쪽으로 끄는 동안 반대편 여백에 시각을 보여 주고 놓으면 숨긴다. 이 모드에서는 같은 제스처로 답장을 실행하지 않는다. 시각은 보조기술에서도 읽을 수 있다. |
77
+ | `bubbleTail` | `boolean` | `false` | 말풍선 본체와 이어진 꼬리를 수신 시작 쪽·발신 끝 쪽에 표시한다. 같은 표면·테두리를 쓰며 행 양쪽 `spacing.xs` 8 여백으로 잘림을 막는다. |
76
78
  | `deliveryLabel` | `string` | 없음 | 시각 뒤에 ` · `로 붙는 전송 상태 |
77
79
  | `avatar` | `ReactNode` | 없음 | 버블 옆 아바타 |
78
80
  | `reply` | `ReactNode` | 없음 | 인용 노드. `replyLink`가 없으면 버블 안 인용으로 그린다 |
@@ -82,7 +84,7 @@ import { Text } from "@hjmds/react-native/primitives";
82
84
  | `interactiveContent` | `boolean` | `false` | 사진 앨범·링크처럼 자식이 버튼을 가지면 `true`. 안쪽 버튼·링크의 누름과 Web Enter/Space는 그 컨트롤이 받는다 |
83
85
  | `actions` | `ReactNode` | 없음 | 시각 옆 슬롯. 반응 집계 배지·재시도는 제품이 넣는다 |
84
86
  | `children` | `ReactNode` | 필수 | 메시지 본문. Native는 말풍선이 이 텍스트로 읽힌다 |
85
- | Web `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 메시지 행(`article`) 배치. `transform`은 스와이프 오프셋이 소유한다. 미게시(1.12.1 이후) |
87
+ | Web `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 메시지 행(`article`) 배치. `transform`은 스와이프 오프셋이 소유한다. 1.13.0 변경 |
86
88
 
87
89
  ## 배치
88
90
 
@@ -130,3 +132,9 @@ import { Text } from "@hjmds/react-native/primitives";
130
132
  ### 고정 아이콘과 큰 글자
131
133
 
132
134
  2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
135
+
136
+ 2026-10-09 번뚝 DM 사용자 요청으로 시각 공개 동작을 공용 ChatMessage에 추가했다. 앱별 PanResponder/PointerEvent 복제를 피하고 세로 스크롤·내부 컨트롤 우선권을 유지한다.
137
+
138
+ ## 대화 스와이프 복귀 (2026-10-12)
139
+
140
+ 번뚝의 좌우 넘김 개선 요청으로 Native의 이동값은 Animated.Value에 전달하고 놓으면 공통 motion.normal(200ms)로 복귀한다. 매 이동마다 메시지 트리를 다시 렌더링하지 않는다. Web도 드래그 중에는 손가락을 따라가고 놓을 때만 200ms 복귀한다. 동작 줄이기는 즉시 복귀한다. 사진 캐러셀처럼 가로 제스처가 있는 메시지는 제품에서 timestampPresentation="always"를 선택해 사진 넘김과 시간 표시가 경쟁하지 않게 한다.
@@ -3,8 +3,8 @@
3
3
  - 단계: 컴포넌트
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
7
- - 검토일: 2026-10-06
6
+ - 적용: 1.17.1
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
9
  - 스토리북: `배포/화면/소통/채팅`
10
10
 
@@ -69,7 +69,7 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
69
69
  | `title`(필수) | 지역화한 대화 제목 | `header`를 주면 Web은 `aria-label`로만 쓴다 |
70
70
  | `header` / `leading` / `actions` / `description` | 기존 navigation 헤더, 또는 뒤로 가기·도구 슬롯 | `header`를 주면 기본 헤더를 그리지 않는다 |
71
71
  | `children` | 타임라인(가상화 목록 + `ChatMessage`) | 자동 스크롤·새 메시지 배지·이전 메시지 위치는 제품 |
72
- | `composer`(필수) | `MessageComposer` 등 작성창 | `state.kind === "ready"`일 때만 보인다 |
72
+ | `composer`(필수) | `MessageComposer` 등 작성창 | `ready`와 `empty`에서 보인다(빈 대화의 첫 메시지 입력) |
73
73
  | `state` / `stateAction` | `loading`·`empty`·`error`·`restricted` + 지역화 `title` | ready 외에는 본문을 교체한다 |
74
74
  | `notice` | 새로고침 실패 같은 비차단 안내 | 초안 유지가 필요하면 `state` 대신 이것 |
75
75
 
@@ -77,10 +77,10 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
77
77
 
78
78
  | prop | 값 | 기본값 | 설명 |
79
79
  | --- | --- | --- | --- |
80
- | `composer` | `ReactNode` | 필수 | footer에 고정되는 작성창. `state.kind === "ready"`일 때만 보인다 |
80
+ | `composer` | `ReactNode` | 필수 | footer에 고정되는 작성창. `ready`와 `empty`에서 보인다(빈 대화의 첫 메시지 입력) |
81
81
  | `scroll` | `"content"` · `"screen"` | `"content"` | 가상화 목록이 스크롤을 소유하므로 작은 예제 외에는 바꾸지 않는다 |
82
82
  | `contentInset` | `"default"` · `"none"` | `"default"` | 이미 gutter를 주는 route 안에서는 `"none"`으로 이중 여백을 막는다 |
83
- | `state` | `ScreenContentState`(`{ kind: "ready" }` 또는 `{ kind, title, description? }`) | `{ kind: "ready" }` | ready 외에는 본문을 교체하고 composer를 숨긴다 |
83
+ | `state` | `ScreenContentState`(`{ kind: "ready" }` 또는 `{ kind, title, description? }`) | `{ kind: "ready" }` | ready 외에는 본문을 교체한다. composer는 loading·error·restricted에서 숨긴다 |
84
84
  | `as` (Web) | `"main"` · `"section"` | `"main"` | 제품 shell이 이미 `main`이면 `"section"` |
85
85
  | 나머지 | `ScreenLayout`과 같음(`footer` 제외) | — | [ScreenLayout](screen-layout.md) |
86
86
 
@@ -89,7 +89,7 @@ import { CheckboxGroup } from "@hjmds/react-native/inputs";
89
89
  - 항목 id는 비지 않고 중복되지 않아야 한다. 제어 `value`에 목록에 없는 id가 있으면 `RangeError`다.
90
90
  항목이 바뀌면 제어하는 쪽이 `value`를 먼저 정리한다.
91
91
  - 문구는 모두 i18n 키로 넣는다. 각 항목의 아이콘은 `renderLeading(item, appearance)`로 그린다.
92
- - 배치는 `layoutStyle`로 한다. Native의 `style`과 행 슬롯 스타일(`controlStyle`·`labelStyle` 등)은 deprecated —
92
+ - 배치는 `layoutStyle`로 한다. Native의 `style`과 행 슬롯 스타일(`controlStyle`·`labelStyle` 등)은 제거된 속성 —
93
93
  `layoutStyle` 또는 `presentation`·`size`·`renderIndicator`를 쓴다([이관 문서](../../migration-native-legacy-removal.md)).
94
94
 
95
95
  ## 플랫폼 차이
@@ -83,8 +83,8 @@ import { Checkbox } from "@hjmds/react-native/inputs";
83
83
  - `label`(필수)과 `description`은 i18n 키로 넣는다. Native는 둘 다 `string`만 받는다.
84
84
  - 선택 아이콘을 바꾸려면 `renderLeading`(Native는 `renderIndicator`도)을 쓴다. 색·테두리를 직접 칠하지 않는다.
85
85
  - 배치는 `layoutStyle`로 한다. Native의 `style`·`controlStyle`·`indicatorStyle`·`leadingStyle`·`contentStyle`·
86
- `labelStyle`·`descriptionStyle`은 deprecated — `layoutStyle` 또는 `presentation`·`size`·`renderIndicator`를 쓴다
87
- (개발 모드 1회 경고, 다음 major 제거. [이관 문서](../../migration-native-legacy-removal.md)).
86
+ `labelStyle`·`descriptionStyle`은 제거된 속성 — `layoutStyle` 또는 `presentation`·`size`·`renderIndicator`를 쓴다
87
+ ([이관 문서](../../migration-native-legacy-removal.md)).
88
88
 
89
89
  ## 플랫폼 차이
90
90
 
@@ -84,7 +84,7 @@ import { Chip } from "@hjmds/react-native/inputs";
84
84
 
85
85
  - `label`은 i18n 키로 넣는다. Native는 `string`만 받고, 다른 읽기 이름이 필요하면 `accessibilityLabel`을 준다.
86
86
  - `single` 줄은 제품이 하나만 `selected`가 되도록 관리한다. Chip이 형제를 해제하지 않는다.
87
- - 색·테두리를 덮지 않는다. 배치는 두 renderer 모두 `layoutStyle`로 한다. Native `labelStyle`은 deprecated —
87
+ - 색·테두리를 덮지 않는다. 배치는 두 renderer 모두 `layoutStyle`로 한다. Native `labelStyle`은 제거된 속성 —
88
88
  `size`·`selected`로 글자 모양을 정한다([이관 문서](../../migration-native-legacy-removal.md)).
89
89
 
90
90
  ## 플랫폼 차이
@@ -97,7 +97,7 @@ import { Chip } from "@hjmds/react-native/inputs";
97
97
  | `label` 타입 | `ReactNode` | `string` |
98
98
  | 배치 | `layoutStyle` | `layoutStyle`, 슬롯별 `leadingStyle`·`indicatorStyle`·`trailingStyle`(배치 key만) |
99
99
  | 선택 표시 위치 | 표시 → leading → 라벨 | leading → 표시 → 라벨 |
100
- | 높이 | `min-block-size` | `minHeight`(미게시(1.13.1 이후). 1.13.0 이하는 고정 `height`) |
100
+ | 높이 | `min-block-size` | `minHeight`(1.13.1 변경. 1.13.0 이하는 고정 `height`) |
101
101
 
102
102
  ## 함정
103
103
 
@@ -74,7 +74,7 @@ import { CodeBlock } from "@hjmds/react-native/code-block";
74
74
  | `ClipboardButton` `value` · `labels` | `string` · `{ idle: ReactNode; copied: ReactNode }` | 필수 | 복사할 원문과 두 상태 문구 |
75
75
  | `ClipboardButton` `onCopy` · `onCopyError` | `(value: string) => void` · `(error: unknown) => void` | — | 실패(권한 거부 등)는 `onCopyError`로만 알 수 있다 |
76
76
  | `ClipboardButton` `feedbackDuration` | ms | 2000 | "복사했어요" 상태 유지 시간 |
77
- | `ClipboardButton` `tone` · `size` | [Button](button.md)과 같다 | `secondary` · `medium`(1.12.1은 `primary`) | 코드 블록 안에서는 `small`로 낮춘다 |
77
+ | `ClipboardButton` `tone` · `size` | [Button](button.md)과 같다 | `secondary` · `medium` | 코드 블록 안에서는 `small`로 낮춘다 |
78
78
 
79
79
  ## 배치
80
80
 
@@ -96,8 +96,7 @@ Native 코드 제목은 `tokens.fontFamily.ui`, 선택 가능한 원문과 그 t
96
96
  - 토큰은 제품이 고른 하이라이터로 만든다(제품 소유). 토큰 색은 HJM semantic 색이 정한다.
97
97
  - `label`은 i18n 키로 넣는다. Native 접근성 이름에는 `label`과 원문이 함께 들어간다.
98
98
  - 복사 버튼과 실패 응답은 제품이 `copyAction`으로 공급한다. `onCopyError`에서 "직접 선택해 복사" 같은 안내를 보인다.
99
- - 복사 버튼은 화면의 주 행동이 아니다. 기본 tone은 `secondary`다(미게시(1.12.1 이후). 1.12.1은 Button 기본 `primary`를
100
- 물려받으므로 `tone="secondary"`를 명시한다). 코드 블록 안에서는 `size="small"`을 준다([Button](button.md)의 한 화면 primary 하나 규칙).
99
+ - 복사 버튼은 화면의 주 행동이 아니다. 기본 tone은 `secondary`다(1.13.0 변경). 코드 블록 안에서는 `size="small"`을 준다([Button](button.md)의 한 화면 primary 하나 규칙).
101
100
  - 배치는 Web `layoutStyle`로 한다. Native CodeBlock은 배치 prop이 없으므로 감싸는 레이아웃(`Stack` 등)이 배치한다.
102
101
 
103
102
  ## 플랫폼 차이
@@ -115,7 +114,7 @@ Native 코드 제목은 `tokens.fontFamily.ui`, 선택 가능한 원문과 그 t
115
114
  사용자는 실패를 알 수 없다.
116
115
 
117
116
  2026-10-07 명령 기록 후보를 구현하며 지연 OS 응답을 검증한 결과, 이전 원문의 복사 성공/오류가
118
- 새 원문과 언마운트 뒤에도 callback을 호출하고 중복 쓰기를 허용했다. 미게시(1.14.0 이후) 후속
117
+ 새 원문과 언마운트 뒤에도 callback을 호출하고 중복 쓰기를 허용했다. 1.15.0 변경 후속
119
118
  수정은 OS 쓰기 동안 Button loading/aria-busy/aria-disabled로 잠그고 키보드 포커스를 유지한다.
120
119
  원문이 바뀌어도 진행 중 OS 쓰기는 취소할 수 없으므로 완료까지 새 쓰기를 막는다. 이전 결과의
121
120
  성공 표시·callback은 무시하고 완료 뒤 현재 원문을 다시 복사할 수 있다. value가 바뀔 때마다
@@ -85,7 +85,7 @@ FolderPreview의 Native preview `md`와 앞면 `lg`는 Provider radius를 읽는
85
85
 
86
86
  | 항목 | 값 | 근거 |
87
87
  | --- | --- | --- |
88
- | 크기 | 트리거는 폭을 꽉 채운다. 트리거 최소 높이 44(`control.minTouchTarget`, 두 플랫폼. Native는 미게시(1.12.1 이후)), Web 위아래 `spacing.xs` 8. `FolderPreview` 표지는 최대 260×160 고정 그림 | `.hjm-collapsible__trigger`, `react-native/src/collapsible.tsx`, `react/src/folder-preview.tsx` |
88
+ | 크기 | 트리거는 폭을 꽉 채운다. 트리거 최소 높이 44(`control.minTouchTarget`, 두 플랫폼. Native는 1.13.0 변경), Web 위아래 `spacing.xs` 8. `FolderPreview` 표지는 최대 260×160 고정 그림 | `.hjm-collapsible__trigger`, `react-native/src/collapsible.tsx`, `react/src/folder-preview.tsx` |
89
89
  | 간격 | 트리거↔내용, 트리거 안 문구↔표시 `spacing.xs` 8(Web 트리거 안은 `spacing.sm` 12) | `collapsibleRecipe.gap`, `.hjm-collapsible__trigger` |
90
90
  | 순서·정렬 | 위→아래 [트리거: 문구 시작 쪽 · ▸/▾ 끝 쪽] → [내용(열렸을 때만)]. `FolderPreview`는 트리거 안에 [표지 그림(가운데)] → [label]이 쌓인다 | `react/src/collapsible.tsx`, `react/src/folder-preview.tsx` |
91
91
  | 고정·스크롤 | 고정 영역이 없다. 닫히면 내용이 트리에서 빠져 아래 내용이 올라온다 | `react-native/src/collapsible.tsx` 주석 |
@@ -94,16 +94,16 @@ FolderPreview의 Native preview `md`와 앞면 `lg`는 Provider radius를 읽는
94
94
  ## 꼭 지킬 것
95
95
 
96
96
  - `trigger`는 무엇이 열리는지 알 수 있는 i18n 문구로 넣는다. trigger 안에 다른 버튼·링크를 넣지 않는다(trigger 자체가 버튼이다).
97
- - 닫힌 내용은 unmount된다. 닫혀도 유지해야 할 입력 상태는 바깥에서 들고 있는다.
97
+ - 기본 `keepMounted=false`에서는 닫힌 내용이 unmount된다. 다시 열 때 로컬 입력을 유지하려면 `keepMounted`를 쓰거나 입력 상태를 바깥에서 관리한다. 숨겨진 내용의 접근성·초점 차단은 HJM이 유지한다.
98
98
  - `FolderPreview`의 `previews`는 장식이다(접근성·터치에서 숨김). 항목 이름과 행동은 펼친 `children`에 다시 둔다.
99
99
  미리보기 이미지·라벨 문구는 제품 소유다.
100
- - 배치는 `layoutStyle`로 한다. Native `style`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
100
+ - 배치는 `layoutStyle`로 한다. Native `style`은 제거된 속성이다.
101
101
 
102
102
  ## 플랫폼 차이
103
103
 
104
104
  | 항목 | Web | Native |
105
105
  | --- | --- | --- |
106
- | 배치 | `layoutStyle`(`FolderPreview`도) | `layoutStyle`. `style`은 deprecated — `layoutStyle`을 쓴다. `FolderPreview`는 배치 prop이 없다 |
106
+ | 배치 | `layoutStyle`(`FolderPreview`도) | `layoutStyle`. `style`은 제거된 속성 — `layoutStyle`을 쓴다. `FolderPreview`는 배치 prop이 없다 |
107
107
  | 열린 영역 관계 | `aria-controls` + `role="region"` | 중첩 구조, `accessibilityState.expanded` |
108
108
  | 문자열 trigger | 그대로 버튼 내용 | HJM `Text`로 감싼다 |
109
109
  | `FolderPreview` 모션 | CSS transform, reduced motion이면 없음 | `ContentTransition`(`scale`) |
@@ -118,3 +118,5 @@ FolderPreview의 Native preview `md`와 앞면 `lg`는 Provider radius를 읽는
118
118
  2026-10-07 테마 전환 시 입력이 사라지는 문제를 피하려고 `presentation="inline"`과 `keepMounted`를 추가했다.
119
119
  inline은 트리거를 숨기고 항상 내용을 펼친다. 기본 disclosure는 기존 open/defaultOpen 규칙을 유지한다.
120
120
  keepMounted가 있어도 닫힌 내용은 포커스·스크린리더·터치 대상에서 제외한다. 단순 read-only 내용은 기존 기본값을 쓴다.
121
+
122
+ Web에서 외부 상태 변경으로 닫힐 때 내부에 있던 초점은 trigger로 돌아간다. 동시에 disabled면 disclosure host로 복구하고, 이미 외부로 이동한 초점은 유지한다. 열린 도구를 trigger로 직접 닫으면 해당 버튼에 초점을 유지하므로 Enter로 다시 열 수 있다.
@@ -97,7 +97,7 @@ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력
97
97
  | 항목 | 값 | 근거 |
98
98
  | --- | --- | --- |
99
99
  | 크기 | 입력 높이 `medium` 44(`fieldFrameContract`) · `large` 52(`control.buttonHeight.large`), 폭은 폼 열을 채운다. Web 목록 최대 높이 22.5rem(360, recipe `popover.maxHeight`), 선택지 최소 높이 `compact` 44 · `comfortable` 56(3.5rem). Native 시트 최대 높이 화면의 75% | `selectRecipe.sizes`·`popover`, `.hjm-combobox__listbox`·`__option`, `react-native/src/forms.tsx` |
100
- | 간격 | 라벨·설명·오류는 [Field](field.md) 규칙(`formSupportContract.gap` `spacing.xs` 8). Web 목록 안쪽 `spacing.xs` 8(`comboboxRecipe.popover.padding` = Select와 같은 표면. 2026-10-06까지 4, 1.12.1 이후 미게시), 선택지 안쪽 위아래 `spacing.xs` 8 · 좌우 `spacing.sm` 12. Native 시트 안쪽 `spacing.md` 16, 아래는 `spacing.md` + 하단 안전 영역, 요소 사이 `spacing.sm` 12 | `.hjm-combobox__option`, `react-native/src/forms.tsx` |
100
+ | 간격 | 라벨·설명·오류는 [Field](field.md) 규칙(`formSupportContract.gap` `spacing.xs` 8). Web 목록 안쪽 `spacing.xs` 8(`comboboxRecipe.popover.padding` = Select와 같은 표면. 2026-10-06까지 4, 1.13.0 변경), 선택지 안쪽 위아래 `spacing.xs` 8 · 좌우 `spacing.sm` 12. Native 시트 안쪽 `spacing.md` 16, 아래는 `spacing.md` + 하단 안전 영역, 요소 사이 `spacing.sm` 12 | `.hjm-combobox__option`, `react-native/src/forms.tsx` |
101
101
  | 순서·정렬 | 폼 안에서 다른 입력과 같은 열에 둔다. Web은 입력 바로 아래(공간이 없으면 위, `data-placement`)에 목록이 붙는다. Native는 화면 아래에서 시트가 올라오고 [제목·닫기] → [상태 문구 또는 선택지 목록] 순서다 | `react/src/advanced-forms.tsx`(`AnchoredPortal`), `CollectionSheetHeader` |
102
102
  | 고정·스크롤 | 목록은 그 안에서 스크롤한다. Web 목록은 `position: fixed`(z-index `layer.dropdown` 400)로 떠서 화면 스크롤과 무관하다. Native 시트는 배경막(`backdrop.modal`)과 함께 모달로 뜬다 | `.hjm-combobox__listbox`, `react-native/src/forms.tsx` |
103
103
  | 좁은 폭·큰 글자 | 선택지 문구는 줄바꿈되고(`overflow-wrap: anywhere`) 높이가 늘어난다. 좁은 폭에서도 Web 목록은 입력에 붙는다 | `.hjm-combobox__option` |
@@ -125,7 +125,7 @@ Web Native
125
125
  - 입력을 고치면 확정 값이 지워진다(Web은 `""`, Native는 결과 sheet에서 다시 골라야 확정). 확정 값을 서버로
126
126
  보낼 때는 입력어가 아니라 `value`/`selectedKey`를 쓴다.
127
127
  - 배치는 `layoutStyle`(필드 바깥)로 한다. Web `style`·`className`은 input에, `fieldClassName`은 필드 바깥에 붙는다.
128
- Native `style`은 deprecated — `layoutStyle` 또는 `density`를 쓴다.
128
+ Native `style`은 제거된 속성 — `layoutStyle` 또는 `density`를 쓴다.
129
129
 
130
130
  ## 플랫폼 차이
131
131
 
@@ -125,6 +125,6 @@ Native: 없음.
125
125
  `closeLabel`이 없다. 새 코드는 로컬 필터링 기본값과 `emptyMessage`·`closeLabel`을 쓴다.
126
126
 
127
127
  2026-10-07 동일한 메뉴 입력 패턴을 검사해 늦은 mouseenter가 키보드로 선택한 명령을 되돌리는
128
- 문제를 재현했다. 미게시(1.14.0 이후) 수정은 실제 마우스 이동으로만 활성 행을 바꾸므로 팝업
128
+ 문제를 재현했다. 1.15.0 변경 수정은 실제 마우스 이동으로만 활성 행을 바꾸므로 팝업
129
129
  배치의 경계 이벤트는 키보드 선택을 덮지 않는다. 검색 초기화·비활성/오래된 결과 잠금·클릭 실행은
130
130
  유지한다. [검증 기록](../../../../../docs/qa/2026-10-07-command-records.md).
@@ -3,7 +3,7 @@
3
3
  - 단계: 컴포넌트
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
6
+ - 적용: 1.17.1
7
7
  - 검토일: 2026-10-07
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
9
  - 스토리북: `배포/화면/소통/댓글`
@@ -126,14 +126,14 @@ threadHeader도 댓글과 함께 숨겨지므로 항상 보여야 하는 안내
126
126
  | --- | --- | --- |
127
127
  | 배치 | `layoutStyle`, `className` | `layoutStyle` |
128
128
  | 새로고침·키보드 스크롤 | 없음 | `scrollProps`(`refreshControl`, `keyboardDismissMode` 등), `scrollRef` |
129
- | 답글 펼침 상태 | 펼침 버튼이 `aria-expanded`를 노출한다(미게시(1.12.1 이후)) | 펼침 버튼이 `accessibilityState.expanded`를 노출한다(미게시(1.12.1 이후)) |
129
+ | 답글 펼침 상태 | 펼침 버튼이 `aria-expanded`를 노출한다(1.13.0 변경) | 펼침 버튼이 `accessibilityState.expanded`를 노출한다(1.13.0 변경) |
130
130
 
131
131
  ## 함정
132
132
 
133
133
  - 펼침 상태는 두 플랫폼 모두 버튼의 expanded 상태로 전한다(2026-10-06 Web `aria-expanded` 추가). 그래도 `repliesLabel(count, expanded)`가
134
134
  버튼 이름이므로 문구에서도 펼침 여부가 드러나게 만든다.
135
135
 
136
- - 작성창 노출 조건이 ChatScreen과 다르다. ChatScreen은 `ready`에서만, CommentThreadScreen은 `ready`·`empty`에서 보인다.
136
+ - ChatScreen과 CommentThreadScreen 모두 `ready`·`empty`에서 작성창을 보인다.
137
137
  `loading`·`error`·`restricted`에서는 둘 다 숨는다.
138
138
 
139
139
  2026-10-07 사용자 요청으로 댓글의 추가 행동을 본문 아래에서 하트 바로 옆으로 옮겼다.
@@ -141,4 +141,7 @@ threadHeader도 댓글과 함께 숨겨지므로 항상 보여야 하는 안내
141
141
  두 버튼은 같은 가로 행이며 더보기 glyph는 세로 점3개(`MoreVertical`)를 제품이 Menu
142
142
  trigger로 공급한다. `actions`는 자유 노드 슬롯을 유지하고 메뉴 권한/항목/전송은 제품 소유다.
143
143
  순서는 좋아요 → 더보기이며 RTL에서는 논리 방향을 따른다. `likeAction=null`도 그대로 지원한다.
144
- 이 배치 수정은 1.14.0 이후 미게시 변경이며 npm/제품 배포와 별개다.
144
+ 이 배치 수정은 1.16.1 변경이다. 실제 설치 버전과 제품 화면 검증은 별도로 확인한다.
145
+
146
+ 2026-10-12 Spint의 작성자 없는 개발 댓글에서 빈 머리글이 본문을 아래로 밀었다.
147
+ `author`와 `timeLabel`이 모두 비면 머리글 행을 생략해 avatar와 본문 시작 높이를 맞춘다. Web·Native 동일하다.
@@ -84,7 +84,7 @@ Native는 `ScrollView`가 세로 스크롤을, 그 안의 Container가 좌우
84
84
  - 최대 폭과 여백은 `size`·`gutter`로만 고른다. 임의 px `maxWidth`나 좌우 padding을 제품마다
85
85
  새로 정하지 않는다(배제 이유는 [계약](../../container.md#배제한-축)).
86
86
  - 바깥 배치(margin·flex)는 `layoutStyle`로 한다. `style`로 `maxWidth`·`padding`을 덮으면
87
- 계약 폭이 사라진다(두 renderer 모두 `style`이 recipe 값 뒤에 합쳐진다). Native `style`은 deprecated —
87
+ 계약 폭이 사라진다(Web은 `style`을 받지만 시각 override에 쓰지 않는다). Native `style`은 제거되었으므로
88
88
  `layoutStyle` 또는 `size`·`gutter`를 쓴다.
89
89
  - Container는 배경·테두리·스크롤을 갖지 않는다. 필요하면 바깥 컴포넌트가 맡는다.
90
90
 
@@ -67,9 +67,10 @@ import { ContentTransition } from "@hjmds/react-native/content-transition";
67
67
  | prop | 값 | 기본값 | 설명 |
68
68
  | --- | --- | --- | --- |
69
69
  | `preset` | `fade` · `rise` · `slide` · `scale` | provider `designProfile.interactions.contentTransition`, 프로필 없으면 `fade` | 명시한 prop이 프로필보다 우선. `rise`는 아래 12에서, `slide`는 가로 16(RTL이면 반대), `scale`은 0.96에서 시작 |
70
+ | `travelDirection` | `forward` · `backward` | `forward` | `slide`의 등장 방향. 이전 페이지는 `backward`, 다음 페이지는 `forward`; 언어 방향과 함께 공통 resolver가 계산한다. |
70
71
  | `motion` | `system` · `none` | `system` | `system`은 reduced motion을 따르고 `none`은 항상 즉시 교체 |
71
72
  | `stateKey` | `string` | — (필수) | 바뀔 때만 새 내용이 나타난다 |
72
- | `enterOnMount` | boolean | false | 미게시: 새 데이터 항목이 처음 추가될 때도 등장 전환. 이미 표시한 항목은 stable key를 유지한다 |
73
+ | `enterOnMount` | boolean | false | 1.14.0 추가: 새 데이터 항목이 처음 추가될 때도 등장 전환. 이미 표시한 항목은 stable key를 유지한다 |
73
74
  | `animateHeight` | boolean | false | 내용의 측정 높이가 바뀔 때 주변 틀 높이를 전환한다. 모션 감소에서는 즉시 반영 |
74
75
  | Web `focusTarget` | `RefObject<HTMLElement \| null>` | — | 바뀌기 전 포커스가 안에 있었으면 전환 뒤 이 요소로 옮긴다 |
75
76
  | Web `layoutStyle` | 배치 전용 style | — | 바깥 고정 wrapper에 붙는다(키가 바뀌는 안쪽 패널이 아님) |
@@ -102,7 +103,7 @@ import { ContentTransition } from "@hjmds/react-native/content-transition";
102
103
  | 앱이 백그라운드로 감 | 해당 없음 | 진행 중 전환을 멈추고 바로 표시 |
103
104
  | 배치 prop | `layoutStyle`(바깥 wrapper) | 없음 |
104
105
 
105
- ### 측정 기반 overlay 전환 준비 (미게시)
106
+ ### 측정 기반 overlay 전환 준비 (1.14.0 추가)
106
107
 
107
108
  `@hjmds/design-contracts/content-transition`의 `resolveOriginTransition(origin, destination, reducedMotion)`은
108
109
  같은 물리 viewport 좌표계의 `TransitionRect { x, y, width, height }` 두 개를 받는다.
@@ -86,7 +86,7 @@ import { Pressable } from "react-native";
86
86
  | 항목 | 값 | 근거 |
87
87
  | --- | --- | --- |
88
88
  | 크기 | 메뉴(Web): 최대 폭 `min(24rem, 90vw)`, 최대 높이 뷰포트 높이 − 2×`spacing.md`(16). 항목 최소 높이 `control.minTouchTarget` 44. 감싼 영역 래퍼(`.hjm-context-menu-host`)는 `min-inline-size: 0`만 가지며 크기·여백을 더하지 않는다 | `packages/react/src/styles.css` `.hjm-context-menu*` |
89
- | 간격 | 메뉴 안쪽 여백 `spacing.xs` 8(`menuRecipe.surface.padding`, Menu와 같다. 2026-10-06까지 4, 1.12.1 이후 미게시), 항목 여백 `spacing.xs` 8 · `spacing.sm` 12, 라벨–단축키 간격 `spacing.sm` 12 | `packages/react/src/styles.css` `.hjm-context-menu__item` |
89
+ | 간격 | 메뉴 안쪽 여백 `spacing.xs` 8(`menuRecipe.surface.padding`, Menu와 같다. 2026-10-06까지 4, 1.13.0 변경), 항목 여백 `spacing.xs` 8 · `spacing.sm` 12, 라벨–단축키 간격 `spacing.sm` 12 | `packages/react/src/styles.css` `.hjm-context-menu__item` |
90
90
  | 순서·정렬 | 항목은 `items` 배열 순서대로 위에서 아래로 그린다. 단축키는 끝에 붙고 줄바꿈하지 않는다. 우클릭·길게 누르기는 누른 좌표, Shift+F10·메뉴 키는 영역의 시작 쪽 아래 모서리에 메뉴를 연다 | `packages/react/src/context-menu.tsx`, `src/context-menu.ts` |
91
91
  | 고정·스크롤 | `position: fixed`로 뜨고 뷰포트 밖으로 넘치면 안쪽으로 밀어 넣는다. 최대 높이를 넘으면 메뉴 안에서 스크롤한다 | `packages/react/src/context-menu.tsx` |
92
92
  | 좁은 폭·큰 글자 | 좁은 폭에서는 최대 폭이 `90vw`로 줄고 항목 라벨은 줄바꿈한다. Native `NativeContextMenu`의 위치·크기·미리보기는 OS가 정한다 | `packages/react/src/styles.css` |
@@ -136,7 +136,7 @@ import { Pressable } from "react-native";
136
136
  | 성숙도 | stable | 실험적 어댑터(기기 증거 전까지 canonical unsupported) |
137
137
 
138
138
  2026-10-07 Web 키보드 회귀에서 팝업이 정지한 커서 아래에 배치된 뒤 mouseenter가 활성 항목을
139
- 첫 항목으로 되돌리는 문제를 재현했다. 미게시(1.14.0 이후) 수정은 실제 마우스 이동으로 항목을
139
+ 첫 항목으로 되돌리는 문제를 재현했다. 1.15.0 변경 수정은 실제 마우스 이동으로 항목을
140
140
  바꾸며, 늦은 경계 이벤트만으로 키보드 선택을 덮지 않는다. 클릭·키보드 실행·포커스 복귀와
141
141
  Native OS 어댑터는 기존 계약을 유지한다. 고정 대기 시간을 추가하지 않고 입력 의도를 구분했다.
142
142
  [검증 기록](../../../../../docs/qa/2026-10-07-command-records.md).
@@ -62,7 +62,7 @@ import { CounterBadge } from "@hjmds/react-native/data-display";
62
62
  | `dot` | `true` · `false` | `false` | Web만. 숫자 대신 8px 점을 그린다 |
63
63
  | `count` | `number` | — (필수) | 0 이하면 아무것도 그리지 않는다 |
64
64
  | `accessibilityLabel` | `string` | — | 없으면 보조기술에서 숨는다. 빈 문자열은 `TypeError` |
65
- | `layoutStyle` | 배치 전용 style | — | 위치·여백만. Native `style`은 deprecated |
65
+ | `layoutStyle` | 배치 전용 style | — | 위치·여백만. Native `style`은 제거된 속성 |
66
66
 
67
67
  콜백 prop은 없다.
68
68
 
@@ -91,14 +91,14 @@ inline (행 끝) floating (아이콘 모서리)
91
91
  - 개수 표기·`+` 처리는 컴포넌트에 맡기고 `"99+"` 같은 문자열을 앱에서 만들지 않는다. 배지가 아닌
92
92
  자리에서 같은 표기가 필요하면 `formatCounterBadgeCount`(`@hjmds/design-contracts`, `/recipes`)를 쓴다.
93
93
  - 색은 `tone`과 제품 테마 토큰으로 바꾼다. 배지 색을 직접 칠하지 않는다.
94
- - 배치는 `layoutStyle`로 한다(Web·Native). Native `style`은 deprecated — 배치는 `layoutStyle`, 외형은 `tone`·`size`·`variant`로 옮긴다(개발 모드 1회 경고, 다음 major 제거).
94
+ - 배치는 `layoutStyle`로 한다(Web·Native). Native `style`은 제거된 속성 — 배치는 `layoutStyle`, 외형은 `tone`·`size`·`variant`로 옮긴다.
95
95
 
96
96
  ## 플랫폼 차이
97
97
 
98
98
  | 항목 | Web | Native |
99
99
  | --- | --- | --- |
100
100
  | `dot` | 있음 | 없음 |
101
- | 배치 | `layoutStyle`(그 밖에 `span` 속성 전달, `style`과 합친다) | `layoutStyle`(`style`은 deprecated) |
101
+ | 배치 | `layoutStyle`(그 밖에 `span` 속성 전달, `style`과 합친다) | `layoutStyle`(`style`은 제거된 속성) |
102
102
  | ref | `HTMLSpanElement` | 없음 |
103
103
 
104
104
  ## 함정
@@ -119,4 +119,4 @@ Native: 없음. Native renderer가 없다.
119
119
  - `asyncState`의 `message`는 표 위에 `status`/`alert`로 나오지만 표는 그대로 그려진다. 빈 상태 화면이
120
120
  따로 필요하면 [EmptyState](empty-state.md)로 표를 대신 그린다.
121
121
  - `Table`의 `onSortChange`는 `null`을 받지 못해 항상 two-state로 돈다. `emptyState`는 필수 prop이다.
122
- - 미게시(1.12.1 이후) 변경: 1.12.1까지 Web `regular` 셀은 사방 `spacing.sm` 12였다. 열 폭을 그 값으로 맞춘 화면은 가로 16에서 다시 확인한다.
122
+ - 1.13.0 변경: 1.12.1까지 Web `regular` 셀은 사방 `spacing.sm` 12였다. 열 폭을 그 값으로 맞춘 화면은 가로 16에서 다시 확인한다.