@hjmds/design-contracts 1.18.0 → 1.20.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 (251) hide show
  1. package/dist/action-session.d.ts +16 -0
  2. package/dist/action-session.d.ts.map +1 -1
  3. package/dist/action-session.js +18 -0
  4. package/dist/action-session.js.map +1 -1
  5. package/dist/behaviors.d.ts +8 -6
  6. package/dist/behaviors.d.ts.map +1 -1
  7. package/dist/behaviors.js +4 -3
  8. package/dist/behaviors.js.map +1 -1
  9. package/dist/bottom-navigation.d.ts +14 -0
  10. package/dist/bottom-navigation.d.ts.map +1 -1
  11. package/dist/bottom-navigation.js +21 -0
  12. package/dist/bottom-navigation.js.map +1 -1
  13. package/dist/carousel.d.ts +3 -0
  14. package/dist/carousel.d.ts.map +1 -1
  15. package/dist/carousel.js.map +1 -1
  16. package/dist/catalog.d.ts +61 -0
  17. package/dist/catalog.d.ts.map +1 -1
  18. package/dist/component-recipes.d.ts +67 -0
  19. package/dist/component-recipes.d.ts.map +1 -1
  20. package/dist/component-recipes.js +27 -1
  21. package/dist/component-recipes.js.map +1 -1
  22. package/dist/field-group.d.ts +14 -0
  23. package/dist/field-group.d.ts.map +1 -1
  24. package/dist/field-group.js +9 -0
  25. package/dist/field-group.js.map +1 -1
  26. package/dist/form.d.ts.map +1 -1
  27. package/dist/form.js +10 -3
  28. package/dist/form.js.map +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/screen-patterns.d.ts +4 -0
  33. package/dist/screen-patterns.d.ts.map +1 -1
  34. package/dist/screen-patterns.js +12 -3
  35. package/dist/screen-patterns.js.map +1 -1
  36. package/dist/semantic-colors.d.ts +9 -0
  37. package/dist/semantic-colors.d.ts.map +1 -1
  38. package/dist/semantic-colors.js +5 -0
  39. package/dist/semantic-colors.js.map +1 -1
  40. package/dist/toast.d.ts +10 -1
  41. package/dist/toast.d.ts.map +1 -1
  42. package/dist/toast.js +43 -2
  43. package/dist/toast.js.map +1 -1
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist/version.js.map +1 -1
  47. package/docs/action-session.md +2 -0
  48. package/docs/asset.md +7 -0
  49. package/docs/bottom-navigation.md +22 -0
  50. package/docs/brand-boundary.md +3 -3
  51. package/docs/collection-rail.md +4 -0
  52. package/docs/compound-controls.md +8 -3
  53. package/docs/consumer-policy.md +39 -120
  54. package/docs/generated/component-maturity.md +1 -1
  55. package/docs/generated/renderer-evidence.json +3 -3
  56. package/docs/generated/renderer-evidence.md +1 -1
  57. package/docs/generated/showcase-manifest.json +1 -1
  58. package/docs/gravity-letters.md +2 -0
  59. package/docs/grid-reveal.md +2 -0
  60. package/docs/layout.md +2 -2
  61. package/docs/load-more.md +5 -0
  62. package/docs/migration-native-legacy-removal.md +30 -17
  63. package/docs/optional-adapters.md +4 -0
  64. package/docs/screen-patterns.md +23 -1
  65. package/docs/toast.md +41 -2
  66. package/docs/usage/README.md +248 -245
  67. package/docs/usage/SELECTION.md +43 -0
  68. package/docs/usage/STANDARD.md +22 -10
  69. package/docs/usage/components/accordion.md +5 -3
  70. package/docs/usage/components/agreement.md +6 -9
  71. package/docs/usage/components/alert-dialog.md +12 -6
  72. package/docs/usage/components/asset.md +10 -4
  73. package/docs/usage/components/auth-provider-button.md +2 -2
  74. package/docs/usage/components/avatar.md +8 -6
  75. package/docs/usage/components/badge.md +4 -4
  76. package/docs/usage/components/bottom-cta.md +4 -4
  77. package/docs/usage/components/bottom-info.md +2 -2
  78. package/docs/usage/components/bottom-navigation.md +30 -6
  79. package/docs/usage/components/button.md +2 -2
  80. package/docs/usage/components/card.md +1 -1
  81. package/docs/usage/components/carousel.md +12 -4
  82. package/docs/usage/components/chat-message.md +6 -2
  83. package/docs/usage/components/chat-screen.md +5 -5
  84. package/docs/usage/components/checkbox-group.md +1 -1
  85. package/docs/usage/components/checkbox.md +2 -2
  86. package/docs/usage/components/chip.md +2 -2
  87. package/docs/usage/components/code-block.md +3 -4
  88. package/docs/usage/components/collapsible.md +6 -4
  89. package/docs/usage/components/combobox.md +2 -2
  90. package/docs/usage/components/command-palette.md +1 -1
  91. package/docs/usage/components/comment-thread-screen.md +7 -4
  92. package/docs/usage/components/container.md +1 -1
  93. package/docs/usage/components/content-transition.md +2 -2
  94. package/docs/usage/components/context-menu.md +2 -2
  95. package/docs/usage/components/counter-badge.md +3 -3
  96. package/docs/usage/components/data-table.md +1 -1
  97. package/docs/usage/components/date-picker.md +4 -4
  98. package/docs/usage/components/description-list.md +3 -3
  99. package/docs/usage/components/design-system-provider.md +2 -2
  100. package/docs/usage/components/dialog.md +14 -3
  101. package/docs/usage/components/divider.md +2 -2
  102. package/docs/usage/components/editor-screen.md +5 -3
  103. package/docs/usage/components/effect-surface.md +1 -1
  104. package/docs/usage/components/empty-state.md +6 -6
  105. package/docs/usage/components/field.md +5 -3
  106. package/docs/usage/components/file-picker.md +6 -3
  107. package/docs/usage/components/form.md +1 -1
  108. package/docs/usage/components/heading.md +3 -3
  109. package/docs/usage/components/icon-button.md +3 -3
  110. package/docs/usage/components/icon.md +1 -1
  111. package/docs/usage/components/image.md +5 -4
  112. package/docs/usage/components/keyboard-dock.md +1 -1
  113. package/docs/usage/components/layout.md +1 -1
  114. package/docs/usage/components/link.md +2 -2
  115. package/docs/usage/components/list-detail-screen.md +6 -6
  116. package/docs/usage/components/list-row.md +13 -4
  117. package/docs/usage/components/list.md +5 -4
  118. package/docs/usage/components/masonry.md +2 -1
  119. package/docs/usage/components/media-selection-screen.md +11 -5
  120. package/docs/usage/components/mentions.md +2 -2
  121. package/docs/usage/components/menu.md +3 -2
  122. package/docs/usage/components/menubar.md +2 -2
  123. package/docs/usage/components/message-composer.md +3 -3
  124. package/docs/usage/components/moderation-screen.md +3 -2
  125. package/docs/usage/components/notice.md +2 -2
  126. package/docs/usage/components/notification-inbox-screen.md +1 -1
  127. package/docs/usage/components/notification-item.md +2 -2
  128. package/docs/usage/components/number-field.md +3 -3
  129. package/docs/usage/components/otp-field.md +1 -1
  130. package/docs/usage/components/overview-screen.md +1 -1
  131. package/docs/usage/components/permission-screen.md +1 -1
  132. package/docs/usage/components/photo-source-sheet.md +1 -1
  133. package/docs/usage/components/popover.md +2 -2
  134. package/docs/usage/components/profile-screen.md +1 -1
  135. package/docs/usage/components/progress.md +2 -2
  136. package/docs/usage/components/qr-code.md +2 -2
  137. package/docs/usage/components/radio-group.md +1 -2
  138. package/docs/usage/components/radio.md +1 -2
  139. package/docs/usage/components/result.md +1 -1
  140. package/docs/usage/components/saved-items-screen.md +4 -2
  141. package/docs/usage/components/screen-layout.md +4 -3
  142. package/docs/usage/components/search-field.md +1 -1
  143. package/docs/usage/components/search-screen.md +4 -4
  144. package/docs/usage/components/section.md +3 -3
  145. package/docs/usage/components/segmented-control.md +4 -4
  146. package/docs/usage/components/select.md +2 -3
  147. package/docs/usage/components/settings-screen.md +11 -49
  148. package/docs/usage/components/sheet.md +10 -5
  149. package/docs/usage/components/sidebar.md +1 -1
  150. package/docs/usage/components/skeleton.md +1 -1
  151. package/docs/usage/components/skip-nav.md +1 -1
  152. package/docs/usage/components/slider.md +2 -2
  153. package/docs/usage/components/spinner.md +2 -2
  154. package/docs/usage/components/splitter.md +1 -1
  155. package/docs/usage/components/statistic.md +5 -4
  156. package/docs/usage/components/steps.md +2 -2
  157. package/docs/usage/components/surface.md +2 -3
  158. package/docs/usage/components/switch.md +1 -1
  159. package/docs/usage/components/tabs.md +4 -4
  160. package/docs/usage/components/tag.md +2 -2
  161. package/docs/usage/components/tags-input.md +4 -4
  162. package/docs/usage/components/text-area.md +2 -3
  163. package/docs/usage/components/text.md +85 -4
  164. package/docs/usage/components/timeline.md +2 -2
  165. package/docs/usage/components/toast.md +17 -7
  166. package/docs/usage/components/toggle-group.md +3 -3
  167. package/docs/usage/components/tooltip.md +1 -1
  168. package/docs/usage/components/top-bar.md +5 -4
  169. package/docs/usage/components/top.md +3 -3
  170. package/docs/usage/components/transfer-list.md +5 -4
  171. package/docs/usage/components/upload-item.md +3 -3
  172. package/docs/usage/components/virtual-list.md +1 -0
  173. package/docs/usage/compositions/action-feedback.md +42 -6
  174. package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
  175. package/docs/usage/compositions/action-recovery-save.md +19 -23
  176. package/docs/usage/compositions/action-recovery-undo.md +6 -1
  177. package/docs/usage/compositions/activity-status.md +11 -5
  178. package/docs/usage/compositions/collection-detail.md +2 -0
  179. package/docs/usage/compositions/command-records.md +15 -5
  180. package/docs/usage/compositions/common-message.md +4 -2
  181. package/docs/usage/compositions/common-notification.md +5 -3
  182. package/docs/usage/compositions/compound-controls.md +13 -5
  183. package/docs/usage/compositions/content-transition-comparison.md +2 -2
  184. package/docs/usage/compositions/date-entry.md +10 -5
  185. package/docs/usage/compositions/date-time-selection.md +3 -3
  186. package/docs/usage/compositions/design-profile-comparison.md +7 -5
  187. package/docs/usage/compositions/disclosure.md +1 -1
  188. package/docs/usage/compositions/document-resource.md +10 -3
  189. package/docs/usage/compositions/expo-interactions.md +3 -2
  190. package/docs/usage/compositions/family-drawer.md +9 -4
  191. package/docs/usage/compositions/field-group.md +7 -1
  192. package/docs/usage/compositions/floating-action-button.md +18 -18
  193. package/docs/usage/compositions/illustrated-outcome.md +3 -2
  194. package/docs/usage/compositions/input-sheet.md +6 -2
  195. package/docs/usage/compositions/interaction-adapters.md +1 -1
  196. package/docs/usage/compositions/interaction-flow-apply.md +6 -3
  197. package/docs/usage/compositions/interaction-flow-draft.md +5 -2
  198. package/docs/usage/compositions/interaction-flow-search.md +5 -2
  199. package/docs/usage/compositions/live-list.md +7 -5
  200. package/docs/usage/compositions/navigation-bar-collection.md +8 -7
  201. package/docs/usage/compositions/optional-adapters.md +1 -1
  202. package/docs/usage/compositions/origin-dialog.md +5 -5
  203. package/docs/usage/compositions/paper-surface.md +1 -1
  204. package/docs/usage/compositions/photo-source.md +1 -1
  205. package/docs/usage/compositions/purpose-input-comment.md +5 -3
  206. package/docs/usage/compositions/purpose-input-message.md +5 -3
  207. package/docs/usage/compositions/reference-first.md +63 -42
  208. package/docs/usage/compositions/reference-review.md +60 -50
  209. package/docs/usage/compositions/reference-settings.md +60 -50
  210. package/docs/usage/compositions/selection-scope.md +48 -35
  211. package/docs/usage/compositions/stea-order-progress.md +1 -0
  212. package/docs/usage/compositions/stea-otp-verify.md +5 -3
  213. package/docs/usage/compositions/stea-pixel-empty.md +3 -3
  214. package/docs/usage/compositions/time-selection.md +6 -5
  215. package/docs/usage/compositions/upload-recovery.md +10 -1
  216. package/docs/usage/compositions/video-dialog.md +10 -3
  217. package/docs/usage/compositions/web-additions.md +5 -5
  218. package/docs/usage/compositions/web-navigation.md +1 -1
  219. package/docs/usage/screens/common-chat.md +21 -8
  220. package/docs/usage/screens/common-comments.md +13 -1
  221. package/docs/usage/screens/common-inbox.md +19 -6
  222. package/docs/usage/screens/common-login.md +19 -7
  223. package/docs/usage/screens/common-profile.md +22 -9
  224. package/docs/usage/screens/common-saved.md +16 -2
  225. package/docs/usage/screens/common-search.md +24 -4
  226. package/docs/usage/screens/common-settings.md +22 -50
  227. package/docs/usage/screens/common-shell.md +14 -2
  228. package/docs/usage/screens/dashboard.md +52 -109
  229. package/docs/usage/screens/discovery-gallery.md +57 -148
  230. package/docs/usage/screens/flow-collection.md +38 -9
  231. package/docs/usage/screens/flow-editor.md +25 -7
  232. package/docs/usage/screens/flow-media.md +23 -7
  233. package/docs/usage/screens/flow-moderation.md +15 -2
  234. package/docs/usage/screens/flow-onboarding.md +3 -3
  235. package/docs/usage/screens/flow-permission.md +17 -3
  236. package/docs/usage/screens/landing.md +37 -20
  237. package/docs/usage/screens/mockup-studio.md +22 -7
  238. package/docs/usage/screens/notification-settings.md +18 -4
  239. package/docs/usage/screens/product-bento.md +18 -9
  240. package/docs/usage/screens/reference-comparison.md +29 -10
  241. package/docs/usage/tokens/color.md +6 -3
  242. package/docs/usage/tokens/elevation-opacity.md +4 -2
  243. package/docs/usage/tokens/font-roles.md +2 -2
  244. package/docs/usage/tokens/layers.md +3 -3
  245. package/docs/usage/tokens/motion.md +2 -2
  246. package/docs/usage/tokens/radius.md +4 -2
  247. package/docs/usage/tokens/spacing.md +3 -3
  248. package/docs/usage/tokens/theme-studio.md +2 -2
  249. package/docs/usage/tokens/typography-studio.md +14 -8
  250. package/docs/usage/tokens/typography.md +15 -5
  251. package/package.json +1 -1
@@ -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
  - 스토리북: `배포/구성/정보 표시/대화 메시지`, `배포/화면/소통/채팅`
@@ -84,7 +84,7 @@ import { Text } from "@hjmds/react-native/primitives";
84
84
  | `interactiveContent` | `boolean` | `false` | 사진 앨범·링크처럼 자식이 버튼을 가지면 `true`. 안쪽 버튼·링크의 누름과 Web Enter/Space는 그 컨트롤이 받는다 |
85
85
  | `actions` | `ReactNode` | 없음 | 시각 옆 슬롯. 반응 집계 배지·재시도는 제품이 넣는다 |
86
86
  | `children` | `ReactNode` | 필수 | 메시지 본문. Native는 말풍선이 이 텍스트로 읽힌다 |
87
- | Web `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 메시지 행(`article`) 배치. `transform`은 스와이프 오프셋이 소유한다. 미게시(1.12.1 이후) |
87
+ | Web `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 메시지 행(`article`) 배치. `transform`은 스와이프 오프셋이 소유한다. 1.13.0 변경 |
88
88
 
89
89
  ## 배치
90
90
 
@@ -134,3 +134,7 @@ import { Text } from "@hjmds/react-native/primitives";
134
134
  2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
135
135
 
136
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
 
@@ -70,7 +70,7 @@ import { ContentTransition } from "@hjmds/react-native/content-transition";
70
70
  | `travelDirection` | `forward` · `backward` | `forward` | `slide`의 등장 방향. 이전 페이지는 `backward`, 다음 페이지는 `forward`; 언어 방향과 함께 공통 resolver가 계산한다. |
71
71
  | `motion` | `system` · `none` | `system` | `system`은 reduced motion을 따르고 `none`은 항상 즉시 교체 |
72
72
  | `stateKey` | `string` | — (필수) | 바뀔 때만 새 내용이 나타난다 |
73
- | `enterOnMount` | boolean | false | 미게시: 새 데이터 항목이 처음 추가될 때도 등장 전환. 이미 표시한 항목은 stable key를 유지한다 |
73
+ | `enterOnMount` | boolean | false | 1.14.0 추가: 새 데이터 항목이 처음 추가될 때도 등장 전환. 이미 표시한 항목은 stable key를 유지한다 |
74
74
  | `animateHeight` | boolean | false | 내용의 측정 높이가 바뀔 때 주변 틀 높이를 전환한다. 모션 감소에서는 즉시 반영 |
75
75
  | Web `focusTarget` | `RefObject<HTMLElement \| null>` | — | 바뀌기 전 포커스가 안에 있었으면 전환 뒤 이 요소로 옮긴다 |
76
76
  | Web `layoutStyle` | 배치 전용 style | — | 바깥 고정 wrapper에 붙는다(키가 바뀌는 안쪽 패널이 아님) |
@@ -103,7 +103,7 @@ import { ContentTransition } from "@hjmds/react-native/content-transition";
103
103
  | 앱이 백그라운드로 감 | 해당 없음 | 진행 중 전환을 멈추고 바로 표시 |
104
104
  | 배치 prop | `layoutStyle`(바깥 wrapper) | 없음 |
105
105
 
106
- ### 측정 기반 overlay 전환 준비 (미게시)
106
+ ### 측정 기반 overlay 전환 준비 (1.14.0 추가)
107
107
 
108
108
  `@hjmds/design-contracts/content-transition`의 `resolveOriginTransition(origin, destination, reducedMotion)`은
109
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에서 다시 확인한다.
@@ -91,7 +91,7 @@ import { DatePicker } from "@hjmds/react-native/date-picker";
91
91
  | `renderCellContent` | `(cell: ResolvedCalendarDateCell) => ReactNode` | — | 날짜 아래 보조 표시 |
92
92
  | Web `onNavigateBeyondGrid` | `(detail: { date, intent, overflow: "before" \| "after" }, focusDate: (date: string) => void) => void` | — | 키보드가 격자 밖으로 나갈 때 |
93
93
  | Native `safeAreaInsets` | Sheet `safeAreaInsets` | Provider 값 | — |
94
- | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`은 deprecated |
94
+ | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`은 제거된 속성 |
95
95
 
96
96
  ## 배치
97
97
 
@@ -99,7 +99,7 @@ Native trigger 모서리는 `datePickerRecipe.frame.radius` 역할을 Provider
99
99
 
100
100
  | 항목 | 값 | 근거 |
101
101
  | --- | --- | --- |
102
- | 크기 | 트리거 높이: `medium` 44 · `large` 52(`datePickerRecipe.sizes`, 두 플랫폼), 좌우 여백 16 · 20. 2026-10-06까지 렌더 값이 Web 44·56, Native 48·56이었다(1.12.1 이후 미게시). 지우기 버튼 44×44. Web 팝오버 폭 `min(22.5rem, 100vw − 2rem)`(최대 360). 날짜 셀 44, 7열 | `datePickerRecipe.sizes`, `packages/react/src/styles.css` `.hjm-date-picker*`, `packages/react-native/src/date-picker.tsx`, `src/calendar.ts` |
102
+ | 크기 | 트리거 높이: `medium` 44 · `large` 52(`datePickerRecipe.sizes`, 두 플랫폼), 좌우 여백 16 · 20. 2026-10-06까지 렌더 값이 Web 44·56, Native 48·56이었다(1.13.0 변경). 지우기 버튼 44×44. Web 팝오버 폭 `min(22.5rem, 100vw − 2rem)`(최대 360). 날짜 셀 44, 7열 | `datePickerRecipe.sizes`, `packages/react/src/styles.css` `.hjm-date-picker*`, `packages/react-native/src/date-picker.tsx`, `src/calendar.ts` |
103
103
  | 간격 | 라벨·트리거·설명·오류 사이 Web `spacing.xs` 8, Native 6(렌더러 고정값). 트리거 가로 여백 `medium` `spacing.md` 16, `large` `spacing.lg` 20. 팝오버는 트리거 아래 `spacing.xs` 8, 안쪽 여백 `spacing.sm` 12 | 같은 파일 |
104
104
  | 순서·정렬 | 폼 안에서 다른 필드와 같은 폭으로 세로로 쌓는다. 위에서 라벨 → 트리거 → 설명 → 오류. 지우기 버튼(값이 있을 때)은 Web은 트리거 안 끝에 겹치고(트리거가 끝 여백 3rem을 비움), Native는 트리거 바깥 끝에 최소 터치 영역으로 붙는다. Web 팝오버는 시작 쪽 정렬 | `packages/react/src/styles.css`, `packages/react-native/src/date-picker.tsx` |
105
105
  | 고정·스크롤 | Web 팝오버는 필드에 붙어 층 800에 뜬다. Native 달력은 화면 아래에서 올라오는 [Sheet](sheet.md)이며 하단 안전 영역은 Provider `safeAreaInsets`를 쓴다 | 같은 파일 |
@@ -125,7 +125,7 @@ Web Native
125
125
  - 모든 문구(라벨·placeholder·`clearLabel`·`closeLabel`·월 이동 라벨·셀 이름)는 i18n 키로 만든다.
126
126
  셀 이름(`composeAccessibleName`)의 어순·문법은 제품이 소유한다.
127
127
  - 달을 넘기면 제품이 새 달의 `cells`와 `monthLabel`을 다시 만들어 넘긴다. 달을 바꿔도 선택값은 바뀌지 않는다.
128
- - 배치는 `layoutStyle`로 한다(Web·Native). 트리거 색·높이를 덮지 않는다. Native `style`은 deprecated — `layoutStyle` 또는 `size`로 옮긴다.
128
+ - 배치는 `layoutStyle`로 한다(Web·Native). 트리거 색·높이를 덮지 않는다. Native `style`은 제거된 속성 — `layoutStyle` 또는 `size`로 옮긴다.
129
129
 
130
130
  ## 플랫폼 차이
131
131
 
@@ -135,7 +135,7 @@ Web Native
135
135
  | `description`·`error` | `ReactNode` | `string` |
136
136
  | 격자 밖 이동 알림 `onNavigateBeyondGrid` | 있음 | 없음 |
137
137
  | 하단 안전 영역 `safeAreaInsets` | 없음 | 있음(기본은 Provider 값) |
138
- | 배치 | `layoutStyle`(그 밖에 `className`) | `layoutStyle`(`style`은 deprecated) |
138
+ | 배치 | `layoutStyle`(그 밖에 `className`) | `layoutStyle`(`style`은 제거된 속성) |
139
139
 
140
140
  ## 함정
141
141
 
@@ -66,7 +66,7 @@ import { DescriptionList } from "@hjmds/react-native/data-display";
66
66
  | 항목 | `{ id, label: string, value: string }` | — (필수) | 셋 다 빈 문자열이면 안 된다. 항목이 없거나 `id`가 겹치면 예외가 난다 |
67
67
  | Native `label` | `string` | — (필수) | 목록 전체의 접근성 이름 |
68
68
  | Native `availableWidth` | 양수 | 측정값 | 주면 측정보다 우선 |
69
- | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`·`itemStyle`은 deprecated |
69
+ | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`·`itemStyle`은 제거된 속성 |
70
70
 
71
71
  콜백 prop은 없다.
72
72
 
@@ -86,7 +86,7 @@ import { DescriptionList } from "@hjmds/react-native/data-display";
86
86
  - 큰 글자 대응으로 화면에서 `fontScale`을 보고 `columns={1}`을 넘기지 않는다. 접힘은 resolver가 한다
87
87
  ([계약](../../description-list.md#큰-글자에서의-전환--resolver가-소유)).
88
88
  - 라벨은 i18n 키로 넣는다. Native는 목록 전체의 접근성 이름 `label`이 필수다.
89
- - 배치는 `layoutStyle`로만 한다(Web·Native). 색·글자 스타일을 덮지 않는다. Native `style`·`itemStyle`은 deprecated — 배치는 `layoutStyle`, 열 배치는 `descriptor.columns`로 옮긴다.
89
+ - 배치는 `layoutStyle`로만 한다(Web·Native). 색·글자 스타일을 덮지 않는다. Native `style`·`itemStyle`은 제거된 속성 — 배치는 `layoutStyle`, 열 배치는 `descriptor.columns`로 옮긴다.
90
90
 
91
91
  ## 플랫폼 차이
92
92
 
@@ -95,7 +95,7 @@ import { DescriptionList } from "@hjmds/react-native/data-display";
95
95
  | 데이터 전달 | `items`, `columns` 평면 prop | `descriptor={{ items, columns }}` |
96
96
  | 목록 이름 | 없음(`dl`/`dt`/`dd` 의미 구조) | `label` 필수, `accessibilityRole="list"` |
97
97
  | 폭 측정 | 요소 폭 측정 | `availableWidth`가 우선, 없으면 `onLayout` 측정, 첫 프레임은 창 폭 |
98
- | 배치 prop | `layoutStyle`(`style`과 합친다) | `layoutStyle`(`style`·`itemStyle`은 deprecated) |
98
+ | 배치 prop | `layoutStyle`(`style`과 합친다) | `layoutStyle`(`style`·`itemStyle`은 제거된 속성) |
99
99
 
100
100
  ## 함정
101
101
 
@@ -86,9 +86,9 @@ expect(checkBrandPaletteContrast(PRODUCT_BRAND_PALETTE)).toEqual({ light: [], da
86
86
  | `reducedMotion` | `true` · `false` | `false` | — |
87
87
  | `minimumVisualTarget` | `true` · `false` | `false` | — |
88
88
  | `brandPalette` | `{ light?, dark? }` | — | 각각 `ThemeColors` 17개 key 중 필요한 것만 넘긴다(부분 병합). 상태 강조색은 덮을 수 없다. 중첩 Provider는 가장 가까운 상위의 값을 물려받는다 |
89
- | `designProfile` | `HjmDesignProfile` | 가장 가까운 상위 프로필 또는 없음 | 기본 프로필은1.15.0 게시. `hjmDesignPresets` 또는 `defineHjmDesignProfile` 결과만 넣는다. 후속 글자 역할·질감/구성 개선은 아직 미게시다. [프로필 계약](../../design-profile.md)의 토큰·질감·전환·구성·화면 기본값을 상속한다 |
89
+ | `designProfile` | `HjmDesignProfile` | 가장 가까운 상위 프로필 또는 없음 | 기본 프로필·표면 질감은 1.15.0 추가. `hjmDesignPresets` 또는 `defineHjmDesignProfile` 결과만 넣는다. 글자 역할·관련 구성 개선은 1.16.0 변경. [프로필 계약](../../design-profile.md)의 토큰·질감·전환·구성·화면 기본값을 상속한다 |
90
90
  | `value` | `DesignSystemProviderValue`(`resolveDesignSystemProviderValue` 결과) | — | 테스트·스토리·임베딩용. 환경 prop·`brandPalette`·`designProfile`과 함께 쓸 수 없고(타입이 막는다), 주면 OS theme·모션 관찰과 상위 `brandPalette` 상속이 멈춘다 |
91
- | `surfaceEffects`(Native) | `{ renderBackdrop?, insetShadows? }` | 상위 host 또는 없음 | 미게시 실험. 실제 blur host·inset capability를 한 번 등록하며 `{}`로 하위 영역을 해제. [Surface 질감 계약](../../design-profile.md#surfacecard의-유리클레이-질감) |
91
+ | `surfaceEffects`(Native) | `{ renderBackdrop?, insetShadows? }` | 상위 host 또는 없음 | 1.15.0 실험 API. 실제 blur host·inset capability를 한 번 등록하며 `{}`로 하위 영역을 해제. [Surface 질감 계약](../../design-profile.md#surfacecard의-유리클레이-질감) |
92
92
  | `safeAreaInsets`(Native) | `{ top?, right?, bottom?, left? }`(pt) | `{}` | 보통 `useSafeAreaInsets()` 결과. 중첩 Provider는 가장 가까운 상위 값을 물려받는다 |
93
93
 
94
94
  - 제품 상태를 바꾸는 이벤트 prop은 없다. Native의 선택형 `surfaceEffects.renderBackdrop`은 장식 host만 공급한다. 해석된 값은 Web `useHjmTheme()`, Native `useHjmNativeTheme()`로 읽는다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-07
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [Dialog](../../dialog.md), [명령형 오버레이](../../overlay-stack.md), recipe `dialogRecipe`(`src/component-recipes.ts`)
9
9
  - 스토리북: `배포/컴포넌트/오버레이/대화상자`
10
10
 
@@ -49,6 +49,7 @@ import { Dialog } from "@hjmds/react/overlays";
49
49
  <Dialog
50
50
  open={open}
51
51
  onOpenChange={(next) => setOpen(next)}
52
+ returnFocusRef={openButtonRef}
52
53
  title={t("album.rename.title")}
53
54
  description={t("album.rename.description")}
54
55
  closeLabel={t("common.close")}
@@ -64,6 +65,11 @@ import { Dialog } from "@hjmds/react/overlays";
64
65
  </Dialog>
65
66
  ```
66
67
 
68
+ Web에서 바깥 버튼으로 `open`을 제어할 때는 그 버튼의 ref를 `returnFocusRef`로 연결한다.
69
+ 위 `openButtonRef`는 `useRef<HTMLButtonElement>(null)`로 만들고 여는 버튼의 `ref`에도 준다.
70
+ macOS WebKit은 버튼 클릭이 초점을 옮기지 않을 수 있어, 열기 직전 `activeElement`만으로는
71
+ 닫힌 뒤 호출 위치를 복구할 수 없다(2026-10-13 브라우저 대조). `trigger` 슬롯을 사용하면 이 연결은 내부에서 처리한다.
72
+
67
73
  ```tsx
68
74
  // Native
69
75
  import { Dialog } from "@hjmds/react-native/overlays";
@@ -89,6 +95,11 @@ Native `save`는 저장 Promise를 반환한다. Dialog가 완료를 기다리
89
95
  이벤트 밖에서 시작한 요청은 외부 `busy`로 연결한다. 이전 즉시 닫힘 대응 예제가 현재 Promise 계약과 충돌해
90
96
  2026-10-06 독립 재구현 검증에서 교정했다.
91
97
 
98
+ Native Dialog는 화면 하단에 붙은 키보드가 나타나면 그 위의 사용 가능한 영역에 가운데 정렬한다.
99
+ 입력 본문은 내부에서 스크롤하고 제목·닫기·저장 행동은 그 밖에 유지한다. 별도 KeyboardAvoidingView를
100
+ 중첩하지 않는다. iOS 입력 예제에서 저장 버튼이 키보드에 가려진 재현을 바탕으로 Sheet와 같은 좌표 계산을
101
+ 공유한다(2026-10-13). Android가 이미 줄인 모달 높이는 다시 빼지 않으며 떠 있는 키보드는 이 여백 계산에서 제외한다.
102
+
92
103
  ```tsx
93
104
  // Web 명령형: 앱 루트에 <OverlayStackProvider>를 두고
94
105
  import { useDialog } from "@hjmds/react/overlay-stack";
@@ -111,8 +122,8 @@ await handle.closed; // portal 제거·초점 복구 뒤. 다음 오버레이는
111
122
  | `onOpenChange` | `(open: boolean, detail: { reason }) => void` | — | Web `reason`: `"trigger"`·`"close-action"`·`"escape"`·`"outside"`. Native: `"close-action"`·`"back"`·`"outside"`. `busy`이거나 `dismissible={false}`면 닫기 요청을 보내지 않는다 |
112
123
  | `onDismissComplete`(Web) | `(detail: { reason: "close-action" \| "escape" \| "outside" \| "programmatic" }) => void` | — | 한 번 열릴 때마다 한 번, portal 제거·초점 복구 뒤. 다음 오버레이는 여기서 연다 |
113
124
  | `trigger`(Web) | `ReactElement`(버튼 요소) | — | 누르면 `reason: "trigger"`로 열린다 |
114
- | `primaryAction`·`secondaryAction`(Native) | `{ label, onPress: () => void \| Promise<void>, tone?, disabled?, accessibilityHint? }` | — | 동기 함수는 같은 누름에서 닫힌다. Promise를 반환하면 기다리는 동안 누른 버튼이 loading이 되고 행동·닫기·back·바깥 닫기가 모두 막히며, 성공하면 `close-action`으로 닫힌다. 던지거나 거절되면 열린 채 행동이 다시 켜지고 `onActionError`를 호출한다. 외부 `busy`도 반복 행동·닫기를 막는다. 미게시(1.12.1 이후) — 1.12.1은 Promise를 무시하고 바로 닫았다 |
115
- | `onActionError`(Native) | `(error: unknown) => void` | — | action 실패를 본문 오류·재시도로 연결한다. 오류를 처리하는 동안 초안을 보존한다. 없으면 개발 빌드에서 실패마다 `console.error`를 남긴다(중복 제거 없음). 미게시(1.12.1 이후) |
125
+ | `primaryAction`·`secondaryAction`(Native) | `{ label, onPress: () => void \| Promise<void>, tone?, disabled?, accessibilityHint? }` | — | 동기 함수는 같은 누름에서 닫힌다. Promise를 반환하면 기다리는 동안 누른 버튼이 loading이 되고 행동·닫기·back·바깥 닫기가 모두 막히며, 성공하면 `close-action`으로 닫힌다. 던지거나 거절되면 열린 채 행동이 다시 켜지고 `onActionError`를 호출한다. 외부 `busy`도 반복 행동·닫기를 막는다. 1.13.0 변경 — 1.12.1은 Promise를 무시하고 바로 닫았다 |
126
+ | `onActionError`(Native) | `(error: unknown) => void` | — | action 실패를 본문 오류·재시도로 연결한다. 오류를 처리하는 동안 초안을 보존한다. 없으면 개발 빌드에서 실패마다 `console.error`를 남긴다(중복 제거 없음). 1.13.0 변경 |
116
127
  | `children`(Native) | `ReactNode` | — | 고정된 제목 줄 아래 스크롤 본문(`ScrollView`) 안에 들어간다. `FlatList`·`SectionList`를 넣지 않는다 |
117
128
  | `contentStyle`(Native) | 배치 key만(`HjmCompositionStyleProp`) | — | 대화상자 상자의 배치. 높이는 `size`로 |
118
129
  | `useDialog()`(Web) | `(request) => { closed: Promise<void>, close(): void }` | — | request는 `open`·`defaultOpen`·`onOpenChange`·`trigger`를 뺀 DialogProps. `OverlayStackProvider` 안에서만 |
@@ -54,7 +54,7 @@ import { Divider } from "@hjmds/react-native/data-display";
54
54
  | `orientation` | `horizontal` · `vertical` | `horizontal` | 가로선·세로선 |
55
55
  | `inset` | Web `none` · `start` · `both` / Native 숫자(pt, 토큰 값으로 넘긴다) | Web `none`(0) / Native 0 | Web `start`·`both`는 `spacing.md`. Native는 가로선이면 좌우, 세로선이면 위아래 여백이 된다 |
56
56
  | Web `decorative` | `boolean` | `false` | `true`면 `role="presentation"`·`aria-hidden` |
57
- | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`은 deprecated |
57
+ | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`은 제거된 속성 |
58
58
 
59
59
  콜백 prop은 없다.
60
60
 
@@ -71,7 +71,7 @@ import { Divider } from "@hjmds/react-native/data-display";
71
71
  ## 꼭 지킬 것
72
72
 
73
73
  - 색·두께를 `style`/`className`으로 바꾸지 않는다. 구분선 색은 제품 테마의 `border` key로 바뀐다.
74
- 배치는 `layoutStyle`로 한다. Native `style`은 deprecated — `layoutStyle` 또는 `orientation`·`inset`으로 옮긴다.
74
+ 배치는 `layoutStyle`로 한다. Native `style`은 제거된 속성 — `layoutStyle` 또는 `orientation`·`inset`으로 옮긴다.
75
75
  - Web에서 의미가 없는 장식선은 `decorative`로 표시한다(`role="presentation"`, `aria-hidden`).
76
76
  기본은 `role="separator"`다.
77
77
 
@@ -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
 
@@ -92,7 +92,7 @@ import { TextField } from "@hjmds/react-native/inputs";
92
92
  | prop | 값 | 기본값 | 설명 |
93
93
  | --- | --- | --- | --- |
94
94
  | `dirty` | `boolean` | 필수 | `true`에서 닫기를 누르면 `discard` 확인창을 띄우고, 확인하면 `cancel.onAction`을 부른다. `false`면 바로 부른다 |
95
- | `submit` | `ScreenFlowAction`(`{ label, onAction(), disabled?, pending? }`) | 필수 | `pending`이면 loading·비활성. 그동안 닫기 버튼도 비활성 |
95
+ | `submit` | `ScreenFlowAction`(`{ label, onAction(), disabled?, pending? }`) | 필수 | `pending`이면 loading·비활성. 그동안 닫기 버튼도 비활성. 본문 `state`가 `ready`가 아니면 저장 비활성 |
96
96
  | `cancel` | `ScreenFlowAction` | 필수 | `leading` 자리의 ghost 닫기 버튼 |
97
97
  | `discard` | AlertDialog confirm 요청에서 `onConfirm`을 뺀 것 + `fallbackErrorMessage: string` | 필수 | `onConfirm`은 EditorScreen이 채운다 |
98
98
  | `submitPlacement` | `"footer"` · `"header"` | `"footer"` | `header`면 저장이 상단 `actions` 자리로 가고 footer에는 `draftStatus`만 남는다 |
@@ -115,6 +115,8 @@ import { TextField } from "@hjmds/react-native/inputs";
115
115
  - 모든 문구(`label`, `discard`의 제목·설명·버튼)는 i18n 키로 넣는다. 컴포넌트는 기본 문구를 갖지 않는다.
116
116
  - `discard`는 `fallbackErrorMessage`까지 필수 타입이다. `onConfirm`은 EditorScreen이 채우므로 넘기지 않는다.
117
117
  - 확인 후 초안을 되돌리는 일은 `cancel.onAction` 안에서 제품이 한다.
118
+ - 저장 시작 또는 `cancel.disabled`/`cancel.pending` 전환은 이미 열린 초안 버리기 확인도 종료한다. 잠금이 풀렸다고 확인창이 다시 열리지는 않는다. 2026-10-13 이전 확인창으로 저장 중 이탈 잠금을 우회할 수 있던 경로를 수정했다.
119
+ - 최초 조회·오류·권한 제한 등 본문 대체 상태에서는 저장을 잠근다. 새 문서의 빈 초안은 `state="empty"`가 아니라 `ready`와 빈 입력으로 표현하고 제품 validation은 `submit.disabled`에 연결한다.
118
120
  - 라우터 뒤로가기·Android back·브라우저 이탈은 이 컴포넌트가 막지 않는다. 제품이 `dirty`로 guard를 건다.
119
121
  - 저장 성공 판정·오류 표시는 제품 상태(`notice`, Toast 등)로 한다.
120
122
 
@@ -62,7 +62,7 @@ import { EffectSurface } from "@hjmds/react-native/effect-surface";
62
62
 
63
63
  | prop | 값 | 기본값 | 설명 |
64
64
  | --- | --- | --- | --- |
65
- | `descriptor.layers` | `mesh` · `glow` · `grain` · `noise` · `ruled` | `["mesh"]` | 서로 다른 1~4개. ruled는 1.15.0 이후 미게시 실험 |
65
+ | `descriptor.layers` | `mesh` · `glow` · `grain` · `noise` · `ruled` | `["mesh"]` | 서로 다른 1~4개. ruled는 1.16.0 추가 실험 |
66
66
  | `descriptor.ruledSpacing` | 유한한 8~128 host units | `24` | ruled의 1unit 선 간격. 텍스트 baseline과 독립적이며 모션 밖 정적 레이어 |
67
67
  | `descriptor.intensity` | 0~1 | `0.22` | — |
68
68
  | `descriptor.period` | 2~120초 | `12` | — |