@hjmds/design-contracts 1.18.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/dist/action-session.d.ts +16 -0
  2. package/dist/action-session.d.ts.map +1 -1
  3. package/dist/action-session.js +18 -0
  4. package/dist/action-session.js.map +1 -1
  5. package/dist/behaviors.d.ts +7 -6
  6. package/dist/behaviors.d.ts.map +1 -1
  7. package/dist/behaviors.js +4 -3
  8. package/dist/behaviors.js.map +1 -1
  9. package/dist/bottom-navigation.d.ts +4 -0
  10. package/dist/bottom-navigation.d.ts.map +1 -1
  11. package/dist/bottom-navigation.js +11 -0
  12. package/dist/bottom-navigation.js.map +1 -1
  13. package/dist/carousel.d.ts +3 -0
  14. package/dist/carousel.d.ts.map +1 -1
  15. package/dist/carousel.js.map +1 -1
  16. package/dist/catalog.d.ts +42 -0
  17. package/dist/catalog.d.ts.map +1 -1
  18. package/dist/component-recipes.d.ts +42 -0
  19. package/dist/component-recipes.d.ts.map +1 -1
  20. package/dist/component-recipes.js +14 -1
  21. package/dist/component-recipes.js.map +1 -1
  22. package/dist/field-group.d.ts +14 -0
  23. package/dist/field-group.d.ts.map +1 -1
  24. package/dist/field-group.js +9 -0
  25. package/dist/field-group.js.map +1 -1
  26. package/dist/form.d.ts.map +1 -1
  27. package/dist/form.js +10 -3
  28. package/dist/form.js.map +1 -1
  29. package/dist/screen-patterns.d.ts +4 -0
  30. package/dist/screen-patterns.d.ts.map +1 -1
  31. package/dist/screen-patterns.js +12 -3
  32. package/dist/screen-patterns.js.map +1 -1
  33. package/dist/toast.d.ts +10 -1
  34. package/dist/toast.d.ts.map +1 -1
  35. package/dist/toast.js +43 -2
  36. package/dist/toast.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/dist/version.js.map +1 -1
  40. package/docs/action-session.md +2 -0
  41. package/docs/asset.md +7 -0
  42. package/docs/bottom-navigation.md +11 -0
  43. package/docs/brand-boundary.md +3 -3
  44. package/docs/collection-rail.md +4 -0
  45. package/docs/compound-controls.md +8 -3
  46. package/docs/consumer-policy.md +39 -120
  47. package/docs/generated/component-maturity.md +1 -1
  48. package/docs/generated/renderer-evidence.json +3 -3
  49. package/docs/generated/renderer-evidence.md +1 -1
  50. package/docs/generated/showcase-manifest.json +1 -1
  51. package/docs/gravity-letters.md +2 -0
  52. package/docs/grid-reveal.md +2 -0
  53. package/docs/layout.md +2 -2
  54. package/docs/load-more.md +5 -0
  55. package/docs/migration-native-legacy-removal.md +30 -17
  56. package/docs/optional-adapters.md +4 -0
  57. package/docs/screen-patterns.md +23 -1
  58. package/docs/toast.md +41 -2
  59. package/docs/usage/README.md +248 -245
  60. package/docs/usage/SELECTION.md +43 -0
  61. package/docs/usage/STANDARD.md +22 -10
  62. package/docs/usage/components/accordion.md +5 -3
  63. package/docs/usage/components/agreement.md +6 -9
  64. package/docs/usage/components/alert-dialog.md +12 -6
  65. package/docs/usage/components/asset.md +10 -4
  66. package/docs/usage/components/auth-provider-button.md +2 -2
  67. package/docs/usage/components/avatar.md +8 -6
  68. package/docs/usage/components/badge.md +4 -4
  69. package/docs/usage/components/bottom-cta.md +4 -4
  70. package/docs/usage/components/bottom-info.md +2 -2
  71. package/docs/usage/components/bottom-navigation.md +16 -5
  72. package/docs/usage/components/button.md +2 -2
  73. package/docs/usage/components/card.md +1 -1
  74. package/docs/usage/components/carousel.md +12 -4
  75. package/docs/usage/components/chat-message.md +6 -2
  76. package/docs/usage/components/chat-screen.md +5 -5
  77. package/docs/usage/components/checkbox-group.md +1 -1
  78. package/docs/usage/components/checkbox.md +2 -2
  79. package/docs/usage/components/chip.md +2 -2
  80. package/docs/usage/components/code-block.md +3 -4
  81. package/docs/usage/components/collapsible.md +6 -4
  82. package/docs/usage/components/combobox.md +2 -2
  83. package/docs/usage/components/command-palette.md +1 -1
  84. package/docs/usage/components/comment-thread-screen.md +7 -4
  85. package/docs/usage/components/container.md +1 -1
  86. package/docs/usage/components/content-transition.md +2 -2
  87. package/docs/usage/components/context-menu.md +2 -2
  88. package/docs/usage/components/counter-badge.md +3 -3
  89. package/docs/usage/components/data-table.md +1 -1
  90. package/docs/usage/components/date-picker.md +4 -4
  91. package/docs/usage/components/description-list.md +3 -3
  92. package/docs/usage/components/design-system-provider.md +2 -2
  93. package/docs/usage/components/dialog.md +14 -3
  94. package/docs/usage/components/divider.md +2 -2
  95. package/docs/usage/components/editor-screen.md +5 -3
  96. package/docs/usage/components/effect-surface.md +1 -1
  97. package/docs/usage/components/empty-state.md +6 -6
  98. package/docs/usage/components/field.md +5 -3
  99. package/docs/usage/components/file-picker.md +6 -3
  100. package/docs/usage/components/form.md +1 -1
  101. package/docs/usage/components/heading.md +3 -3
  102. package/docs/usage/components/icon-button.md +3 -3
  103. package/docs/usage/components/icon.md +1 -1
  104. package/docs/usage/components/image.md +5 -4
  105. package/docs/usage/components/keyboard-dock.md +1 -1
  106. package/docs/usage/components/layout.md +1 -1
  107. package/docs/usage/components/link.md +2 -2
  108. package/docs/usage/components/list-detail-screen.md +6 -6
  109. package/docs/usage/components/list-row.md +13 -4
  110. package/docs/usage/components/list.md +5 -4
  111. package/docs/usage/components/masonry.md +2 -1
  112. package/docs/usage/components/media-selection-screen.md +11 -5
  113. package/docs/usage/components/mentions.md +2 -2
  114. package/docs/usage/components/menu.md +3 -2
  115. package/docs/usage/components/menubar.md +2 -2
  116. package/docs/usage/components/message-composer.md +3 -3
  117. package/docs/usage/components/moderation-screen.md +3 -2
  118. package/docs/usage/components/notice.md +2 -2
  119. package/docs/usage/components/notification-inbox-screen.md +1 -1
  120. package/docs/usage/components/notification-item.md +2 -2
  121. package/docs/usage/components/number-field.md +3 -3
  122. package/docs/usage/components/otp-field.md +1 -1
  123. package/docs/usage/components/overview-screen.md +1 -1
  124. package/docs/usage/components/permission-screen.md +1 -1
  125. package/docs/usage/components/photo-source-sheet.md +1 -1
  126. package/docs/usage/components/popover.md +2 -2
  127. package/docs/usage/components/profile-screen.md +1 -1
  128. package/docs/usage/components/progress.md +2 -2
  129. package/docs/usage/components/qr-code.md +2 -2
  130. package/docs/usage/components/radio-group.md +1 -2
  131. package/docs/usage/components/radio.md +1 -2
  132. package/docs/usage/components/result.md +1 -1
  133. package/docs/usage/components/saved-items-screen.md +4 -2
  134. package/docs/usage/components/screen-layout.md +4 -3
  135. package/docs/usage/components/search-field.md +1 -1
  136. package/docs/usage/components/search-screen.md +4 -4
  137. package/docs/usage/components/section.md +3 -3
  138. package/docs/usage/components/segmented-control.md +4 -4
  139. package/docs/usage/components/select.md +2 -3
  140. package/docs/usage/components/settings-screen.md +11 -49
  141. package/docs/usage/components/sheet.md +10 -5
  142. package/docs/usage/components/sidebar.md +1 -1
  143. package/docs/usage/components/skeleton.md +1 -1
  144. package/docs/usage/components/skip-nav.md +1 -1
  145. package/docs/usage/components/slider.md +2 -2
  146. package/docs/usage/components/spinner.md +2 -2
  147. package/docs/usage/components/splitter.md +1 -1
  148. package/docs/usage/components/statistic.md +5 -4
  149. package/docs/usage/components/steps.md +2 -2
  150. package/docs/usage/components/surface.md +2 -3
  151. package/docs/usage/components/switch.md +1 -1
  152. package/docs/usage/components/tabs.md +4 -4
  153. package/docs/usage/components/tag.md +2 -2
  154. package/docs/usage/components/tags-input.md +4 -4
  155. package/docs/usage/components/text-area.md +2 -3
  156. package/docs/usage/components/text.md +85 -4
  157. package/docs/usage/components/timeline.md +2 -2
  158. package/docs/usage/components/toast.md +17 -7
  159. package/docs/usage/components/toggle-group.md +3 -3
  160. package/docs/usage/components/tooltip.md +1 -1
  161. package/docs/usage/components/top-bar.md +5 -4
  162. package/docs/usage/components/top.md +3 -3
  163. package/docs/usage/components/transfer-list.md +5 -4
  164. package/docs/usage/components/upload-item.md +3 -3
  165. package/docs/usage/components/virtual-list.md +1 -0
  166. package/docs/usage/compositions/action-feedback.md +42 -6
  167. package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
  168. package/docs/usage/compositions/action-recovery-save.md +19 -23
  169. package/docs/usage/compositions/action-recovery-undo.md +6 -1
  170. package/docs/usage/compositions/activity-status.md +11 -5
  171. package/docs/usage/compositions/collection-detail.md +2 -0
  172. package/docs/usage/compositions/command-records.md +15 -5
  173. package/docs/usage/compositions/common-message.md +4 -2
  174. package/docs/usage/compositions/common-notification.md +5 -3
  175. package/docs/usage/compositions/compound-controls.md +13 -5
  176. package/docs/usage/compositions/content-transition-comparison.md +2 -2
  177. package/docs/usage/compositions/date-entry.md +10 -5
  178. package/docs/usage/compositions/date-time-selection.md +3 -3
  179. package/docs/usage/compositions/design-profile-comparison.md +7 -5
  180. package/docs/usage/compositions/disclosure.md +1 -1
  181. package/docs/usage/compositions/document-resource.md +10 -3
  182. package/docs/usage/compositions/expo-interactions.md +3 -2
  183. package/docs/usage/compositions/family-drawer.md +9 -4
  184. package/docs/usage/compositions/field-group.md +7 -1
  185. package/docs/usage/compositions/floating-action-button.md +18 -18
  186. package/docs/usage/compositions/illustrated-outcome.md +3 -2
  187. package/docs/usage/compositions/input-sheet.md +6 -2
  188. package/docs/usage/compositions/interaction-adapters.md +1 -1
  189. package/docs/usage/compositions/interaction-flow-apply.md +6 -3
  190. package/docs/usage/compositions/interaction-flow-draft.md +5 -2
  191. package/docs/usage/compositions/interaction-flow-search.md +5 -2
  192. package/docs/usage/compositions/live-list.md +7 -5
  193. package/docs/usage/compositions/navigation-bar-collection.md +8 -7
  194. package/docs/usage/compositions/optional-adapters.md +1 -1
  195. package/docs/usage/compositions/origin-dialog.md +5 -5
  196. package/docs/usage/compositions/paper-surface.md +1 -1
  197. package/docs/usage/compositions/photo-source.md +1 -1
  198. package/docs/usage/compositions/purpose-input-comment.md +5 -3
  199. package/docs/usage/compositions/purpose-input-message.md +5 -3
  200. package/docs/usage/compositions/reference-first.md +63 -42
  201. package/docs/usage/compositions/reference-review.md +60 -50
  202. package/docs/usage/compositions/reference-settings.md +60 -50
  203. package/docs/usage/compositions/selection-scope.md +48 -35
  204. package/docs/usage/compositions/stea-order-progress.md +1 -0
  205. package/docs/usage/compositions/stea-otp-verify.md +5 -3
  206. package/docs/usage/compositions/stea-pixel-empty.md +3 -3
  207. package/docs/usage/compositions/time-selection.md +6 -5
  208. package/docs/usage/compositions/upload-recovery.md +10 -1
  209. package/docs/usage/compositions/video-dialog.md +10 -3
  210. package/docs/usage/compositions/web-additions.md +5 -5
  211. package/docs/usage/compositions/web-navigation.md +1 -1
  212. package/docs/usage/screens/common-chat.md +21 -8
  213. package/docs/usage/screens/common-comments.md +13 -1
  214. package/docs/usage/screens/common-inbox.md +19 -6
  215. package/docs/usage/screens/common-login.md +19 -7
  216. package/docs/usage/screens/common-profile.md +22 -9
  217. package/docs/usage/screens/common-saved.md +16 -2
  218. package/docs/usage/screens/common-search.md +24 -4
  219. package/docs/usage/screens/common-settings.md +22 -50
  220. package/docs/usage/screens/common-shell.md +14 -2
  221. package/docs/usage/screens/dashboard.md +52 -109
  222. package/docs/usage/screens/discovery-gallery.md +57 -148
  223. package/docs/usage/screens/flow-collection.md +38 -9
  224. package/docs/usage/screens/flow-editor.md +25 -7
  225. package/docs/usage/screens/flow-media.md +23 -7
  226. package/docs/usage/screens/flow-moderation.md +15 -2
  227. package/docs/usage/screens/flow-onboarding.md +3 -3
  228. package/docs/usage/screens/flow-permission.md +17 -3
  229. package/docs/usage/screens/landing.md +37 -20
  230. package/docs/usage/screens/mockup-studio.md +22 -7
  231. package/docs/usage/screens/notification-settings.md +18 -4
  232. package/docs/usage/screens/product-bento.md +18 -9
  233. package/docs/usage/screens/reference-comparison.md +29 -10
  234. package/docs/usage/tokens/color.md +6 -3
  235. package/docs/usage/tokens/elevation-opacity.md +4 -2
  236. package/docs/usage/tokens/font-roles.md +2 -2
  237. package/docs/usage/tokens/layers.md +3 -3
  238. package/docs/usage/tokens/motion.md +2 -2
  239. package/docs/usage/tokens/radius.md +4 -2
  240. package/docs/usage/tokens/spacing.md +3 -3
  241. package/docs/usage/tokens/theme-studio.md +2 -2
  242. package/docs/usage/tokens/typography-studio.md +14 -8
  243. package/docs/usage/tokens/typography.md +15 -5
  244. package/package.json +1 -1
@@ -0,0 +1,43 @@
1
+ # 화면과 구성 선택
2
+
3
+ 검토일: 2026-10-13
4
+
5
+ 2026-10-13 설계 점검에서 화면 이름만으로 공통 화면을 강제하면 저장·스크롤·권한 흐름이
6
+ 다른 제품까지 같은 틀에 맞출 수 있음을 확인했다. 화면 → 구성 → 컴포넌트 → 토큰 순으로
7
+ 찾되, 아래 행동과 소유권이 맞는지 먼저 판단한다.
8
+
9
+ ## 선택 기준
10
+
11
+ | 선택 | 조건 | 구현 경로 |
12
+ | --- | --- | --- |
13
+ | 그대로 사용 | 영역 순서, 저장 시점, 상태 교체, 스크롤 소유가 제품과 맞는다 | 공통 화면에 데이터·문구·콜백을 연결 |
14
+ | 공개 슬롯으로 확장 | 공통 행동은 같고 헤더·본문 콘텐츠·보조 행동만 다르다 | header·notice·footer 등 해당 API의 공개 슬롯 사용 |
15
+ | 제품 전용 구성 | 저장·탐색·권한 흐름 또는 가상화·스크롤 구조가 공개 API로 표현되지 않는다 | 기존 HJM 구성·컴포넌트로 제품에서 조합하고 차이를 기존 코드/작업 기록에 설명 |
16
+
17
+ 공개 슬롯으로 해결할 수 있는 틀·상태 엔진은 복제하지 않는다. 제품 전용 구성도 semantic token,
18
+ 접근성·입력·실패 복구 계약을 유지한다. 내부 selector나 제거된 style prop은 확장 경로가 아니다.
19
+ 여러 제품이 공유하는 공백이면 공통 API의 의미 있는 축으로 검토한다. 별도 승인·ADR은 자동 요구하지 않는다.
20
+
21
+ ## 선택 전에 확인할 것
22
+
23
+ - **저장:** 즉시 저장인지 일괄 저장인지, pending의 소유자와 실패 시 마지막 확인값·초안이 어떻게 남는지.
24
+ - **스크롤:** host와 본문 중 누가 스크롤하는지, 목록 가상화·페이지 추가·위치 복구가 필요한지.
25
+ - **상태:** 최초 조회의 본문 교체와 저장/재조회 오류의 비차단 notice가 구분되는지.
26
+ - **권한:** 접근 제한에서 작성·전송을 숨길지, 읽기와 쓰기 권한이 다른지.
27
+ - **플랫폼:** Web 포커스와 Native 키보드·safe area를 누가 처리하는지. 같은 inset·스크롤을 중복 적용하지 않는다.
28
+
29
+ SettingsScreen의 행별 즉시 저장은 행 콜백에 제품 mutation을 연결한다. 일괄 저장도 공개 footer와
30
+ notice로 표현되면 같은 API를 사용한다. CommentThreadScreen의 데이터 구조나 스크롤이 대규모
31
+ 가상화에 맞지 않으면 이름만 보고 강제하지 않고 ScreenLayout과 적합한 목록을 조합한다.
32
+
33
+ ## 상태와 버전 읽기
34
+
35
+ | 정보 | 원본 | 해석 |
36
+ | --- | --- | --- |
37
+ | 제공 기준 | 컴포넌트 지침의 적용 버전과 설치 패키지 exports/타입 | 확인한 기본 API의 게시 버전이며 최초 출시 버전이나 후속 prop 전체의 제공 보증은 아님 |
38
+ | 플랫폼 지원·API 안정성 | 설치 버전 catalog의 surfaceStatus와 선택형 API 계약 | canonical과 supplemental·optional API를 구분. catalog 밖 API를 자동 stable로 간주하지 않음 |
39
+ | Storybook 분류 | 스토리 제목의 배포/실험 | 예제 분류. npm 게시·소비 앱 반영을 의미하지 않음 |
40
+ | 검증 | 해당 SHA·플랫폼·환경의 실행 결과 | 타입·브라우저·host mock·기기 결과의 범위를 구분 |
41
+
42
+ 구성·화면은 여러 API를 사용하므로 별도 라이브러리 버전을 복제하지 않고 각 API 지침을 참조한다.
43
+ 설치 버전보다 새 문서의 prop은 설치된 타입과 export에서 다시 확인한다.
@@ -1,11 +1,11 @@
1
1
  # 사용 지침 규격
2
2
 
3
- 검토일: 2026-10-06 · 강제: `pnpm usage:check`(`scripts/check-usage-docs.mjs`)
3
+ 검토일: 2026-10-13 · 선택적 형식 감사: `pnpm usage:check`(`scripts/check-usage-docs.mjs`)
4
4
 
5
5
  2026-10-06 사용자 요청: 지침은 단계마다 같은 규격으로 나와야 한다. 그래야 에이전트가 어느 지침을 열어도
6
6
  같은 자리에서 같은 정보(위치·크기·간격·순서·상태)를 찾고, 빠진 정보를 검사기가 잡는다. 자유 형식이던
7
- 첫 작성본은 같은 정보가 절마다 다른 이름·표 모양으로 흩어져 있었다. 이 규격을 바꾸면 검사기·템플릿·
8
- 전체 지침을 같은 변경에서 바꾼다.
7
+ 첫 작성본은 같은 정보가 절마다 다른 이름·표 모양으로 흩어져 있었다. 형식은 탐색과 자동 색인 생성을 위한 작성 관례다. 제품 계약과 예제의 정확성은 유지하되,
8
+ 절 순서·표 모양을 개발·npm 게시의 차단 조건으로 삼지 않는다. 규격 변경은 영향받는 생성기와 지침만 함께 갱신한다.
9
9
 
10
10
  ## 1. 공통
11
11
 
@@ -15,9 +15,9 @@
15
15
 
16
16
  ```markdown
17
17
  - 단계: 컴포넌트 # 토큰 | 컴포넌트 | 구성 | 화면
18
- - 상태: 배포 # 담당 스토리에 배포가 하나라도 있으면 배포, 모두 실험이면 실험(검사기가 대조)
18
+ - 상태: 배포 # Storybook 분류만 의미. API 안정성·npm 게시 상태가 아님
19
19
  - 지원: Web · Native # Web · Native | Web | Native
20
- - 적용: 1.12.1 # 확인한 게시 버전 x.y.z, 아직 게시 안 된 API는 `미게시(1.12.1 이후)`
20
+ - 적용: 1.17.1 # 확인한 기본 API의 게시 버전. 최초 출시 버전이 아님; 조합 예제는 `구성 API 지침 참조`
21
21
  - 검토일: 2026-10-06
22
22
  - 근거: `../../button-label.md`(링크), `src/base-recipes.ts` # 계약 문서·소스
23
23
  - 스토리북: `배포/컴포넌트/동작/버튼` # 담당 제목 전부, 백틱. 구성·화면·토큰은 검사기가 이 줄로 담당을 판정
@@ -36,17 +36,20 @@
36
36
  - `- 스토리북: 없음`은 어느 단계에도 그 API를 보여 주는 스토리가 없는 보조 API에만 쓴다.
37
37
  - 색인의 `분류` 열은 `스토리북:` 줄의 담당 제목 셋째 마디로 만든다(다른 단계 예제만 참조하는 컴포넌트는 `<단계>/<분류>`).
38
38
  머리말에 분류 키를 따로 두지 않는다. 2026-10-06 분류 어휘를 고정하면서 색인을 메뉴 순서로 묶었고, 키를 두면 제목과 두 벌이 된다.
39
- - `##` 절은 아래 단계 표의 이름·순서만 쓴다. 필수(●)는 반드시, 선택(○)은 해당될 때만 그 자리에 둔다.
39
+ - 색인과 필수 정보의 기준 절은 아래 이름·순서를 쓴다. 필요한 추가 설명 절은 허용한다. 필수(●)와 선택(○)은 정보의 유무를 구분한다.
40
40
  절 안에서는 `###`를 자유롭게 쓴다.
41
41
  - 표는 단계 표에 적힌 **열 이름 그대로** 쓴다. 해당 없는 칸은 `—`.
42
42
  - 숫자는 토큰 이름과 값을 함께 쓴다(`spacing.md` 16). 소스에서 확인한 것만 쓴다.
43
43
  - **규칙과 관찰을 구분한다.** 스토리·소스가 다른 지침이나 토큰 규칙과 어긋나면(예: 제목을 `Text`로 그림, Native
44
44
  ScrollView에 여백을 직접 줌) 지침 본문은 규칙대로 쓰고 어긋남은 `## 함정`에 "현재 스토리는 …"으로 적는다.
45
45
  2026-10-06 블라인드 검증에서 화면 지침이 스토리 관찰값을 규칙처럼 옮겨 컴포넌트·토큰 지침과 충돌했다.
46
- - **코드 예는 타입 검사로 확인한 것만 쓴다**(`strict` + `exactOptionalPropertyTypes`, 설치 버전의 타입). 같은 검증에서
47
- `grid.md`의 `gap="md"`와 Web `onChange` 예가 실제 타입과 달라 그대로 쓰면 컴파일이 깨졌다.
46
+ - 코드 예는 `strict` + `exactOptionalPropertyTypes`로 실제 공개 타입을 확인한다. `usage:check`는 문서 형식 검사이며 타입 검사 결과가 아니다.
47
+ - 같은 예제를 문서와 Storybook에 복사하지 않는다. 실행되는 TSX 원본을 참조할 수 있으며 플랫폼마다
48
+ `<!-- usage-example: Web 상대/경로.tsx -->` 또는 `Native` 표시와 읽을 수 있는 링크를 함께 둔다.
49
+ 검사기는 참조 파일의 존재만 확인한다. 변경한 원본은 해당 Showcase 타입 검사 또는 파일 지정 타입 검사로 확인한다.
50
+ 2026-10-13 설정 화면의 중복 코드 골격을 실제 Web/Native preview 참조로 교체했다. 제품은 예제를 읽고 공개 API를 사용하며 showcase를 import하지 않는다.
48
51
  - 배치도는 ```text 블록 ASCII. 위→아래, 주 행동·보조 행동·고정 영역·스크롤 영역·안전 영역을 표시한다.
49
- - 코드 예는 ```tsx 블록, 첫 줄 주석 `// Web` 또는 `// Native`. `지원`에 적은 플랫폼마다 예를 하나 이상 둔다. 문구는 `t("...")`.
52
+ - 인라인 코드 예는 ```tsx 블록과 첫 줄 `// Web` 또는 `// Native`를 쓴다. 실행 원본 참조도 코드 예로 인정한다. 제품 문구는 i18n으로 공급한다.
50
53
  - 규격 절 이름을 `###`로 내려 쓰지 않는다(표·행 검사를 피하게 된다). 다른 내용은 해당 규격 절 안의 `###`로 둔다.
51
54
  - 링크: 같은 단계 `name.md`, 다른 단계 `../<폴더>/name.md`, 계약 문서 `../../<doc>.md`.
52
55
  - 문체: 한국어 평서체(-다).
@@ -103,6 +106,15 @@
103
106
  | `## 플랫폼 차이` | ○ | `\| 항목 \| Web \| Native \|` |
104
107
  | `## 함정` | ○ | 목록 |
105
108
 
106
- ## 3. 템플릿
109
+ ## 3. 원본과 확인 범위
110
+
111
+ 선택은 [화면과 구성 선택](SELECTION.md)을 따른다. canonical API 안정성은 생성된 manifest에서 색인으로
112
+ 읽고 별도 숫자나 stable 목록을 복제하지 않는다. 구성·화면의 `적용: 구성 API 지침 참조`는
113
+ 본문의 API 링크를 따라 확인한다. `미게시(x.y.z 이후)`는 실제 미게시 추가분에만 쓴다.
114
+
115
+ `node scripts/check-usage-docs.mjs --files <지침 경로> ...`는 지정한 지침만 감사한다.
116
+ `--write`를 함께 주면 색인을 갱신한다. 전체 지침 감사는 명시적으로 선택할 때만 실행한다.
117
+
118
+ ## 4. 템플릿
107
119
 
108
120
  [`templates/`](templates/)의 네 파일을 복사해 시작한다. 템플릿은 검사 대상이 아니다.
@@ -72,7 +72,7 @@ import { Text } from "@hjmds/react-native/primitives";
72
72
  | Native `label` | `string` | 필수 | 그룹 접근성 이름 |
73
73
  | Native `renderIndicator` | `(props: { value, expanded, disabled, color, size }) => ReactNode` | `+`/`−` | 펼침 표시만 교체한다 |
74
74
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용 |
75
- | Native `style`·`itemStyle`·`triggerStyle`·`titleStyle`·`indicatorStyle`·`panelStyle` | — | — | deprecated — `layoutStyle` 또는 `density`/`renderIndicator`(개발 모드 1회 경고, 다음 major 제거) |
75
+ | Native `style`·`itemStyle`·`triggerStyle`·`titleStyle`·`indicatorStyle`·`panelStyle` | — | — | 제거된 속성 — `layoutStyle` 또는 `density`/`renderIndicator` |
76
76
 
77
77
  ## 배치
78
78
 
@@ -86,12 +86,14 @@ import { Text } from "@hjmds/react-native/primitives";
86
86
 
87
87
  ## 꼭 지킬 것
88
88
 
89
- - 항목은 하나 이상, 식별자(Web `id`, Native `value`)는 고유해야 한다. 빈 목록·중복 id·없는 값·
89
+ - 항목은 하나 이상, 식별자(Web `id`, Native `value`)는 고유해야 한다. 빈 목록·중복 id·최초 기본값 또는 제어 값에 없는 식별자·
90
90
  단일 모드에서 둘 이상 펼침은 렌더 중 `TypeError`/`RangeError`로 실패한다.
91
+ - 비제어 상태에서 펼쳐진 항목을 삭제하면 그 식별자를 펼침 값에서 제거하고 변경 콜백을 한 번 알린다. 재삽입해도 저절로 다시 열리지 않는다. 삭제로 오류 화면이 되거나 이전 선택이 되살아나는 문제를 막기 위한 규칙이다.
92
+ - 펼쳐진 항목의 `disabled` 전환은 추가 토글만 막고 읽던 본문은 유지한다. 웹에서 삭제된 항목 또는 새로 비활성화된 트리거에 포커스가 있었다면 다음 활성 트리거로 옮긴다(없으면 그룹). 사용자가 이미 외부로 옮긴 포커스는 유지한다.
91
93
  - 제목·본문은 i18n 키로 넣는다. Native는 그룹 `label`이 필수이며 접근성 이름이 된다.
92
94
  - 펼침 표시(`+`/`−`)와 trigger·panel 접근성 관계는 HJM이 소유한다. 직접 버튼과 패널을 다시 조립하지 않는다.
93
95
  - 배치는 `layoutStyle`로만 한다. Native의 `style`·`itemStyle`·`triggerStyle`·`titleStyle`·`indicatorStyle`·`panelStyle`은
94
- deprecated다. 새 코드에 쓰지 않는다([소비 정책 §3](../../consumer-policy.md), [Native 이관](../../migration-native-legacy-removal.md)).
96
+ 제거된 속성이다. 새 코드에 쓰지 않는다([소비 정책 §3](../../consumer-policy.md), [Native 이관](../../migration-native-legacy-removal.md)).
95
97
 
96
98
  ## 플랫폼 차이
97
99
 
@@ -74,11 +74,11 @@ import { Agreement } from "@hjmds/react-native/agreement";
74
74
  | 전체 동의 | tri-state(파생) | — | 저장되는 값이 아니라 개별 항목에서 파생한다. 비활성 항목은 분모에서 빠진다 |
75
75
  | `checkedIds` · `defaultCheckedIds` | `ReadonlySet<Id>` | 비제어 빈 Set | 제어·비제어. 목록에서 빠진 항목의 체크는 자동으로 버려진다(`reconcileAgreementSelection`) |
76
76
  | `onCheckedIdsChange` | `(ids: ReadonlySet<Id>) => void` | — | 체크 목록이 바뀔 때 |
77
- | `onStateChange` | `(state: AgreementState) => void`, `state` = `{ all: boolean \| "mixed", satisfied: boolean, missingRequiredIds: readonly Id[] }` | — | 마운트 때 초기 상태로 한 번, 그 뒤 체크를 바꿀 때마다 호출된다(마운트 호출은 미게시(1.12.1 이후)) |
77
+ | `onStateChange` | `(state: AgreementState) => void`, `state` = `{ all: boolean \| "mixed", satisfied: boolean, missingRequiredIds: readonly Id[] }` | — | 마운트와 실제 체크/필수 항목 판정 변경 때 호출된다. 제어 요청 거부는 가상의 동의 완료를 알리지 않는다 |
78
78
  | `onDetail` | `(id: Id) => void` | — | 전문 보기. Web은 `detail.href`가 없을 때만 호출 |
79
79
  | `requiredLabel` · `optionalLabel` | `string` | 필수 | 행 끝 "(필수)"·"(선택)" 문구 |
80
80
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용 |
81
- | Native `style` | — | — | deprecated — `layoutStyle` 또는 recipe(개발 모드 1회 경고, 다음 major 제거) |
81
+ | Native `style` | — | — | 제거된 속성 — `layoutStyle` 또는 recipe |
82
82
 
83
83
  ## 배치
84
84
 
@@ -110,27 +110,24 @@ Native의 전체 동의 프레임 `md`와 선택 표시 `sm`은 Provider의 `tok
110
110
  ## 꼭 지킬 것
111
111
 
112
112
  - 필수 동의 판정은 `resolveAgreementState`의 `satisfied`를 사용하고 제출 가능 여부는 제품의 입력 검증·진행 중 상태와 함께 결정한다. 묶음 잠금은 동의 사실을 바꾸지 않으므로 `satisfied`가 true인 채 잠길 수 있다. 남은 필수 항목 안내는 `missingRequiredIds`로 제품이 문장을 만든다.
113
- - `onStateChange`는 마운트와 사용자 토글 때 알린다. 제품이 약관 버전이나 제어 `checkedIds`를 직접 바꾸면 같은 렌더에서 `resolveAgreementState(descriptor, checkedIds)`로 판정한다. 문서 버전 변경 시 이전 동의를 초기화하는 책임은 제품에 있다.
113
+ - `onStateChange`는 마운트와 실제 동의/필수 항목 판정 변경 때 알린다. 외부 `checkedIds`·필수 항목 변경도 반영하며, 부모가 제어 요청을 거부하면 미수락 상태를 완료로 알리지 않는다. 같은 렌더에서 판정이 필요하면 `resolveAgreementState(descriptor, checkedIds)`를 사용한다. 문서 버전 변경 시 이전 동의를 초기화하는 책임은 제품에 있다.
114
114
  - `accessibilityLabel`·`allLabel`·항목 `label`·`requiredLabel`·`optionalLabel`은 모두 제품의 i18n 문구다.
115
115
  빈 문자열, 빈 목록, 중복 id, 필수이면서 `disabled`인 항목은 throw다.
116
116
  - 약관 문구·링크 주소·법적 유효성·동의 기록 저장은 제품 소유다. HJM은 판정과 배치만 가진다.
117
117
  - 전문 보기 컨트롤은 체크박스와 별개이며 눌러도 체크되지 않는다. 같은 행에 다른 누름 영역을 덧붙이지 않는다.
118
- - 색·간격은 recipe가 정한다. 배치는 `layoutStyle`로만 한다. Native `style`은 deprecated다([소비 정책 §3](../../consumer-policy.md)).
118
+ - 색·간격은 recipe가 정한다. 배치는 `layoutStyle`로만 한다. Native `style`은 제거된 속성이다([소비 정책 §3](../../consumer-policy.md)).
119
119
 
120
120
  ## 플랫폼 차이
121
121
 
122
122
  | 항목 | Web | Native |
123
123
  | --- | --- | --- |
124
124
  | 전문 열기 | `detail.href`가 있으면 `<a>`, 없으면 버튼이 `onDetail(id)` 호출 | `href`를 쓰지 않고 항상 `onDetail(id)` |
125
- | 배치 prop | `layoutStyle`(+`className`) | `layoutStyle`(`style`은 deprecated) |
125
+ | 배치 prop | `layoutStyle`(+`className`) | `layoutStyle`(`style`은 제거된 속성) |
126
126
  | `ref` | root `div`로 전달 | 없음 |
127
127
 
128
128
  ## 함정
129
129
 
130
- - 1.12.1까지 `onStateChange`는 사용자가 체크를 바꿀 때만 호출돼, `defaultCheckedIds`로 필수 항목을 미리 채운 경우 첫
131
- `satisfied`를 받지 못했다. 미게시(1.12.1 이후) 버전은 마운트 때 초기 상태를 한 번 알린다. 1.12.1에서는 제출 버튼의 첫 상태를
132
- `resolveAgreementState(descriptor, checkedIds)`(`@hjmds/design-contracts/components/agreement`)로 직접 계산한다.
133
- 호출 횟수를 세는 코드는 마운트 1회가 늘어난다.
130
+ - `onStateChange`는 마운트 때 초기 상태와 이후 실제 체크/필수 항목 판정 변경을 알린다. 제출 버튼의 활성 상태는 이 결과의 `satisfied`에 연결한다. 사용자 조작 횟수로 해석하지 않는다.
134
131
 
135
132
  2026-10-07 Utilverse 가입은 요청 중 필수 Checkbox 둘을 잠그는데, 개별 필수 item에 disabled를
136
133
  주면 Agreement 검증이 거절했다. 묶음 `descriptor.disabled`를 추가해 필수 항목을 분모에서
@@ -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
  - 근거: `AlertDialogRequest`·`createAlertDialogSession`(`src/alert-dialog.ts`), recipe `alertDialogRecipe`, Popover와의 경계 [ConfirmPopover 결정](../../confirm-popover.md), Native 긴 문구 처리 [Dialog](../../dialog.md)
9
9
  - 스토리북: `배포/컴포넌트/오버레이/확인 대화상자`
10
10
 
@@ -39,6 +39,7 @@ import { AlertDialog } from "@hjmds/react/overlays";
39
39
  <AlertDialog
40
40
  open={open}
41
41
  onOpenChange={(next) => setOpen(next)}
42
+ returnFocusRef={openButtonRef}
42
43
  request={{
43
44
  mode: "confirm",
44
45
  tone: "danger",
@@ -52,6 +53,10 @@ import { AlertDialog } from "@hjmds/react/overlays";
52
53
  />
53
54
  ```
54
55
 
56
+ Web의 외부 열기 버튼과 `returnFocusRef`에 같은 `useRef<HTMLButtonElement>(null)`을 연결한다.
57
+ macOS WebKit에서는 버튼 클릭만으로 호출 위치에 초점이 남지 않을 수 있다. 삭제 성공으로 버튼도
58
+ 제거될 때는 목록 제목 등 작업을 이어갈 유효한 위치를 복귀 대상으로 정한다([Dialog 초점 복귀](dialog.md#최소-사용-예)).
59
+
55
60
  ```tsx
56
61
  // Native
57
62
  import { AlertDialog } from "@hjmds/react-native/overlays";
@@ -60,6 +65,7 @@ import { AlertDialog } from "@hjmds/react-native/overlays";
60
65
  open={open}
61
66
  onOpenChange={(next) => setOpen(next)}
62
67
  onResult={(result) => { if (result.outcome === "confirmed") router.back(); }}
68
+ size="small"
63
69
  request={request}
64
70
  />
65
71
  ```
@@ -79,9 +85,9 @@ import { AlertDialog } from "@hjmds/react-native/overlays";
79
85
  | 닫기 | — | — | 바깥 누름으로 닫히지 않는다. Escape(Web)·뒤로(Native)는 취소로 끝난다. 처리 중(`busy`)에는 닫을 수 없다 |
80
86
  | `request.onConfirm` 실패 | — | — | 열린 채로 `resolveErrorMessage` 또는 `fallbackErrorMessage`를 보여 준다 |
81
87
  | Web `trigger` | `ReactNode` | — | 비제어면 필수 |
82
- | Web `size` | `small` · `medium` · `large` | `medium`(Dialog recipe) | — |
88
+ | `size` | `small` · `medium` · `large` | Web `medium` · Native `small` | 각 플랫폼 recipe의 최대 폭·안쪽 여백을 선택한다 |
83
89
  | Web `modalPriority` | `number` | `0` | 높은 우선순위 모달이 뒤에 열린 낮은 모달 위에서 동작한다 |
84
- | Native `contentStyle` | 배치 key(margin·width·flex·`alignSelf`)만 | — | 색·radius·padding 등 시각 key는 deprecated(개발 모드 1회 경고, 다음 major에서 배치 key로 좁힘) |
90
+ | Native `contentStyle` | 배치 key(margin·width·flex·`alignSelf`)만 | — | `HjmCompositionStyleProp` 배치 전용 타입. 색·radius·padding은 받지 않으며 크기는 `size`로 지정한다 |
85
91
 
86
92
  ### 디자인 프로필 상속
87
93
 
@@ -96,8 +102,8 @@ import { AlertDialog } from "@hjmds/react-native/overlays";
96
102
 
97
103
  | 항목 | 값 | 근거 |
98
104
  | --- | --- | --- |
99
- | 크기 | Web `size` `medium` `min(36rem, 100%)` · `small` 28rem · `large` 48rem, 최대 높이 `100dvh − 32`. Native는 recipe 기본 `small`로 최대 폭 320, 폭 100%. 아이콘 원 44(`control.minTouchTarget`). Native 행동 버튼 최소 폭 96 | `.hjm-alert-dialog`, `alertDialogRecipe`·`dialogRecipe.sizes`, `react-native/src/overlays.tsx` |
100
- | 간격 | 화면 가장자리와 최소 `spacing.md` 16. 안쪽 여백 `spacing.lg` 20(Web·Native small). 요소 사이 Web `spacing.sm` 12 · Native `dialogRecipe.content.gap` `spacing.md` 16. 행동 사이 `spacing.sm` 12, 세로로 쌓이면 `spacing.xs` 8. Native 버튼 좌우 `spacing.md` 16 | `.hjm-overlay`, `.hjm-alert-dialog__actions`, `alertDialogRecipe.actions` |
105
+ | 크기 | Web `size` `medium` `min(36rem, 100%)` · `small` 28rem · `large` 48rem, 최대 높이 `100dvh − 32`. Native는 폭 100%, 최대 폭은 `small` 320 · `medium` 420 · `large` 640이며 기본은 `small`이다. 아이콘 원 44(`control.minTouchTarget`). Native 행동 버튼 최소 폭 96 | `.hjm-alert-dialog`, `alertDialogRecipe`·`dialogRecipe.sizes`, `react-native/src/overlays.tsx` |
106
+ | 간격 | 화면 가장자리와 최소 `spacing.md` 16. 안쪽 여백은 Native `small` `spacing.lg` 20, `medium`·`large` `spacing.xl` 24이다. 요소 사이 Web `spacing.sm` 12 · Native `dialogRecipe.content.gap` `spacing.md` 16. 행동 사이 `spacing.sm` 12, 세로로 쌓이면 `spacing.xs` 8. Native 버튼 좌우 `spacing.md` 16 | `.hjm-overlay`, `.hjm-alert-dialog__actions`, `alertDialogRecipe.actions` |
101
107
  | 순서·정렬 | 화면 가운데. 위→아래 [아이콘] → 제목 → 설명 → 오류 문구 → 행동. 행동은 끝 정렬 한 줄 [취소][확인]. 버튼 수는 `mode`가 정한다(alert 1 · confirm 2) | `.hjm-overlay`, `alertDialogRecipe.slots` |
102
108
  | 고정·스크롤 | 배경막(`backdrop.modal`)이 화면 전체를 덮고 하단 고정 바보다 위에 쌓인다(Web z-index `layer.modal` 900). 내용이 길면 대화상자 안에서 스크롤한다 | `.hjm-overlay`, `.hjm-alert-dialog` |
103
109
  | 좁은 폭·큰 글자 | 폭 < 600(`breakpoint.medium`)이면 행동을 세로로 쌓고 **확인이 위**에 온다(Web DOM은 [취소][확인] 유지). Native는 글자 배율 1.6 이상(`largeTextThreshold`)에서도 쌓는다 | `alertDialogRecipe.actions.stackBelow`·`stackedOrder`, `react-native/src/overlays.tsx` |
@@ -129,7 +135,7 @@ import { AlertDialog } from "@hjmds/react-native/overlays";
129
135
  | 여는 요소 | `trigger`(선택, 비제어면 필수) | 없음. `open`/`defaultOpen`으로만 연다 |
130
136
  | 결과 콜백 | 없음(`onOpenChange` 사유 `confirm`/`cancel-action`/`escape`) | `onResult({ outcome })` |
131
137
  | 아이콘 | `icon` | 없음 |
132
- | 크기·우선순위·portal | `size`, `modalPriority`, `portalContainer` | 없음(RN `Modal` props 일부를 그대로 받는다) |
138
+ | 크기·우선순위·portal | `size`, `modalPriority`, `portalContainer` | `size`와 RN `Modal` props 일부. `modalPriority`·`portalContainer`는 없다 |
133
139
  | 초점 복귀 | `returnFocusRef`(없으면 trigger) | `returnFocusRef` |
134
140
  | 배치 prop | 없음(`layoutStyle` 제외 대상) | `contentStyle` 배치 key만 |
135
141
 
@@ -9,7 +9,7 @@
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/이미지·영상 표시` · `배포/컴포넌트/데이터 표시/음성 메모`
10
10
 
11
11
  개발 중인 프로필 지원에서 `rounded`는 가장 가까운 프로필의 `tokens.radius.md`를 따른다.
12
- 프로필이 없으면 기존 12px이고 명시한 square/circle은 그대로다. 이 보강은 아직 미게시이며
12
+ 프로필이 없으면 기존 12px이고 명시한 square/circle은 그대로다. 이 보강은 1.15.0 변경이며
13
13
  1.12.1의 기존 Asset 제공 여부와 구분한다([근거](../../../../../docs/qa/2026-10-07-3dicons-page-review.md)).
14
14
 
15
15
  ## 언제 쓰나
@@ -89,7 +89,7 @@ import { VoiceNote } from "@hjmds/react-native/voice-note";
89
89
  | `children` | `ReactNode` 또는 `(state: { animate: boolean }) => ReactNode` | 필수 | `animate`는 `lottie`·`video`이고 reduced motion이 아닐 때만 `true`다 |
90
90
  | `accessory` | `ReactNode` | — | 액자 바깥 모서리에 붙는 작은 표식(재생 아이콘, 상태 점) |
91
91
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | Asset·AssetGroup 바깥 배치 전용 |
92
- | Native `style`(Asset·AssetGroup) | — | — | deprecated — `layoutStyle` 또는 descriptor `size`/`shape`(개발 모드 1회 경고, 다음 major 제거) |
92
+ | Native `style`(Asset·AssetGroup) | — | — | 제거된 속성 — `layoutStyle` 또는 descriptor `size`/`shape` |
93
93
  | `AssetGroup` `label` · `size` | `string` · `AssetSize` | `label` 필수 · `size` `medium` | 겹침은 크기의 30%로 Avatar와 같다 |
94
94
  | `VoiceNote` `descriptor` | `{ title, state: "paused" \| "playing" \| "loading" \| "error", duration: number \| null, position: number, disabled? }` | 필수 | `duration: null`(메타데이터 미확인)과 `0`이면 재생·탐색이 막힌다 |
95
95
  | `VoiceNote` `labels` | `{ play, pause, seek, loading, error, retry, backward, forward }`(모두 `string`) | 필수 | i18n 문구 |
@@ -101,7 +101,7 @@ import { VoiceNote } from "@hjmds/react-native/voice-note";
101
101
 
102
102
  | 항목 | 값 | 근거 |
103
103
  | --- | --- | --- |
104
- | 크기 | 정사각 액자. 한 변 `small` 32 · `medium` 48 · `large` 72 · `xlarge` 120. 모서리 `square` 0 · `rounded` `radius.md`(무프로필 12, 미게시 보강에서는 프로필 값) · `circle` foundation `radius.full`. 터치 대상이 아니므로 누를 수 있게 하려면 감싸는 버튼·행이 최소 44를 확보한다 | `assetRecipe.sizes`·`shapes`, `.hjm-asset__frame` |
104
+ | 크기 | 정사각 액자. 한 변 `small` 32 · `medium` 48 · `large` 72 · `xlarge` 120. 모서리 `square` 0 · `rounded` `radius.md`(무프로필 12, 프로필이 있으면 프로필 값) · `circle` foundation `radius.full`. 터치 대상이 아니므로 누를 수 있게 하려면 감싸는 버튼·행이 최소 44를 확보한다 | `assetRecipe.sizes`·`shapes`, `.hjm-asset__frame` |
105
105
  | 간격 | `accessory`는 액자 끝·아래 모서리 바깥으로 `spacing.xxs` 4 띄워 붙으므로 옆 요소와 `spacing.xs` 8 이상 띄운다. `AssetGroup` 겹침은 크기의 30%(48이면 −14) | `assetRecipe.accessory`·`overlapRatio`, `react/src/asset.tsx` |
106
106
  | 순서·정렬 | 늘어나지 않는 인라인 요소(Web `inline-flex`, `flex: 0 0 auto`). 행 안에서는 시작 쪽에 둔다. 한 줄에 종류가 다른 그림을 섞을 때 모두 같은 `size`·`shape`를 준다 | `.hjm-asset`, `assetBehavior.scenarios` |
107
107
  | 고정·스크롤 | 고정 영역이 없다 | — |
@@ -112,6 +112,12 @@ import { VoiceNote } from "@hjmds/react-native/voice-note";
112
112
  - 뜻이 있는 그림은 `accessibilityLabel`을, 장식은 `decorative: true`만 준다. 이름 없는 비장식과
113
113
  둘 다 준 경우 모두 렌더 중 `TypeError`가 난다.
114
114
  - reduced motion에서는 숨기지 말고 `animate`로 재생을 멈춘다. 판단을 제품에서 다시 만들지 않는다.
115
+ - `animate`는 허용 값이다. 슬롯의 플레이어가 값 변경에 맞춰 멈추거나 정지 원화로 전환하도록
116
+ 연결한다. autoplay 초기값만 넘기면 실행 중 설정 변경을 반영하지 못할 수 있다.
117
+ - 1회 완료 연출은 플레이어의 반복 옵션을 명시한다(Lottie React Native는 설치 버전 API를
118
+ 확인해 `loop={false}`). 확인된 결과 ID로 재생 여부를 관리해 재진입만으로 다시 축하하지 않는다.
119
+ 재생 완료 콜백으로 작업 성공을 확정하거나 다음 버튼을 늦게 열지 않는다. 자산 실패에도
120
+ 상태·다음 행동을 남긴다([2026-10-12 근거와 선택 기준](../../../../../docs/INTERACTION_QUALITY.md#캐릭터매체-모션의-채택-기준)).
115
121
  - Lottie·비디오·오디오 엔진, 그림 자산은 제품 소유다. HJM은 액자·크기·겹침·표식 위치만 소유한다.
116
122
  - 테마별 그림은 제품의 자산 목록에서 선택해 슬롯에 넣는다. Asset이 URL을 만들거나 재질·각도
117
123
  변형을 자동 생성하지 않는다. 자산마다 지원하는 조합과 실패 대체를 확인하며, 3D 제공자의
@@ -122,7 +128,7 @@ import { VoiceNote } from "@hjmds/react-native/voice-note";
122
128
 
123
129
  | 항목 | Web | Native |
124
130
  | --- | --- | --- |
125
- | 배치 prop | `layoutStyle`(+`className`) | `layoutStyle`(`style`은 deprecated). `VoiceNote`는 Native에 `layoutStyle` 없음 |
131
+ | 배치 prop | `layoutStyle`(+`className`) | `layoutStyle`(`style`은 제거된 속성). `VoiceNote`는 Native에 `layoutStyle` 없음 |
126
132
  | 매체 크기 | CSS가 자식을 액자 안으로 줄인다(`max-*: 100%`) | 가운데 정렬만 한다. 자식 크기를 직접 준다 |
127
133
  | `AssetGroup` 겹침 | CSS로 `.hjm-asset` 형제에 적용 | `children`이 배열일 때만 각 자식을 감싸 적용 |
128
134
  | `VoiceNote` 앞뒤 이동 문구 | `labels.backward/forward`를 받지만 쓰지 않는다 | Slider 접근성 동작 문구로 쓴다 |
@@ -62,7 +62,7 @@ import { AuthProviderButton } from "@hjmds/react-native/provider-button";
62
62
  | `logo` | `ReactNode` | 필수 | 제품이 공급하는 제공자 로고 |
63
63
  | Web `onClick` · Native `onPress` | `(event: MouseEvent<HTMLButtonElement>) => void` · `() => void` | Native 필수 | 로그인 시작 |
64
64
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용 |
65
- | Native `style` | — | — | deprecated — `layoutStyle`(외형은 `authProviderButtonRecipe` 소유, 개발 모드 1회 경고, 다음 major 제거) |
65
+ | Native `style` | — | — | 제거된 속성 — `layoutStyle`(외형은 `authProviderButtonRecipe` 소유) |
66
66
  | — | — | HJM 소유 | 크기: 높이 `buttonRecipe` medium(Native는 최소 터치 타깃과 큰 쪽), radius `md`, 로고 상자 20, 테두리 1(테두리를 규정한 변형만), 포커스 링은 fill 바깥(offset 2) |
67
67
  | Web `type` | HTML button type | `"button"` | — |
68
68
 
@@ -105,7 +105,7 @@ main 카드(mainCard)
105
105
  | --- | --- | --- |
106
106
  | 이벤트 | `onClick` 등 `<button>` 속성(선택) | `onPress`(필수) |
107
107
  | 비활성 | `descriptor.disabled`(`disabled` 속성은 받지 않음) | `descriptor.disabled` |
108
- | 배치 입력 | `layoutStyle`(+`className`, `style` 없음) | `layoutStyle`(`style`은 deprecated) |
108
+ | 배치 입력 | `layoutStyle`(+`className`, `style` 없음) | `layoutStyle`(`style`은 제거된 속성) |
109
109
  | 접근성 이름 | `aria-label={label}` | `accessibilityLabel={label}` |
110
110
  | `testID`·ref | ref 전달 | 둘 다 없음 |
111
111
 
@@ -76,9 +76,9 @@ import { Avatar } from "@hjmds/react-native/data-display";
76
76
  | Web `alt` | `string` | `name` | `""`이면 보조기기에서 숨긴다 |
77
77
  | Web `imageProps` | `img` 속성(`alt`·`src` 제외) | — | `onError`는 `(event: SyntheticEvent<HTMLImageElement>) => void`이며 대체 표시로 바꾼 뒤 불린다 |
78
78
  | Native `accessibilityLabel` · `decorative` | `{ accessibilityLabel: string }` 또는 `{ decorative: true }` | — | 둘 중 하나가 타입으로 강제된다 |
79
- | Native `renderImage` | `(context: AvatarImageRenderProps) => ReactNode` | 기본 Native Image | 제품의 이미지 캐시·표시 호스트를 연결한다(미게시, 1.13.1 이후). `source`, `size`, HJM `fallback`, 세대가 보호된 `onError` 제공 |
79
+ | Native `renderImage` | `(context: AvatarImageRenderProps) => ReactNode` | 기본 Native Image | 제품의 이미지 캐시·표시 호스트를 연결한다(1.14.0 추가). `source`, `size`, HJM `fallback`, 세대가 보호된 `onError` 제공 |
80
80
  | Native `initials` | `string` | 이름에서 계산 | 앞뒤 공백을 지우고 최대 3자, 대문자 |
81
- | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`imageStyle`은 deprecated — layoutStyle 또는 `size`/`renderFallback` |
81
+ | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`imageStyle`은 제거된 속성 — layoutStyle 또는 `size`/`renderFallback` |
82
82
  | `AvatarGroup`(Web) `label` · `size` · `overflow` | `string` · Avatar 크기 · `ReactNode` | `label` 필수 · `size` `medium` | 빈 `label`은 `TypeError`. `overflow`는 제품이 만든 "+3" 같은 문구이며 `aria-hidden`이다(남은 인원은 `label`에 담는다). 겹침은 크기의 30%다 |
83
83
 
84
84
  ## 배치
@@ -96,13 +96,13 @@ import { Avatar } from "@hjmds/react-native/data-display";
96
96
  - `name`은 빈 문자열이면 `TypeError`다. 접근성 이름은 Avatar가 소유하고, 대체 그림은 장식으로만 그린다(버튼 등 상호작용 금지).
97
97
  - Blobatar `seed`에는 공개 제품 식별자를 쓴다. 이름·이메일·자격증명은 seed로 쓰지 않는다.
98
98
  - 사진·표시 이름·남은 인원 문구는 제품 소유다. `overflow`는 i18n 키로 만든다. `overflow`는 보조기기에서 숨겨지므로 전체 인원은 `label` 문구에 넣는다.
99
- - 배치는 `layoutStyle`로만 한다. Native `style`·`imageStyle`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
99
+ - 배치는 `layoutStyle`로만 한다. Native `style`·`imageStyle`은 제거된 속성이다.
100
100
 
101
101
  ## 플랫폼 차이
102
102
 
103
103
  | 항목 | Web | Native |
104
104
  | --- | --- | --- |
105
- | 사진 | `src`(문자열), `imageProps`로 `img` 속성·`onError` 전달 | `source`(`ImageSourcePropType`). `imageStyle`은 deprecated — layoutStyle 또는 `size` |
105
+ | 사진 | `src`(문자열), `imageProps`로 `img` 속성·`onError` 전달 | `source`(`ImageSourcePropType`). `imageStyle`은 제거된 속성 — layoutStyle 또는 `size` |
106
106
  | 접근성 이름 | `alt`(기본 `name`), `alt=""`이면 숨김 | `accessibilityLabel` 필수, 또는 `decorative` |
107
107
  | 대체 표시 | `fallback` 노드 또는 이니셜 | `initials`(최대 3자) 또는 이니셜 |
108
108
  | 크기·모양 축 | 4단 이름 · `circle`/`rounded` | 숫자 · 원만 |
@@ -116,7 +116,7 @@ Stack처럼 내용 폭을 수용하는 영역에 둔다. leading의 overflow/크
116
116
 
117
117
  - `src`/`source`에 `undefined`를 직접 넘기면 `exactOptionalPropertyTypes`에서 타입 오류다. 사진이 없으면 prop을 빼거나 spread로 조건부로 넣는다.
118
118
  - Web `AvatarGroup`은 이제 `style`을 버리지 않고 `layoutStyle`과 합친다. 겹침 변수(`--hjm-avatar-overlap`)는 마지막에 덮이므로 `style`로 겹침을 바꿀 수 없다.
119
- - 이니셜은 두 플랫폼 모두 `resolveAvatarInitials`(`@hjmds/design-contracts/avatar-fallback`)로 첫 단어와 마지막 단어의 첫 글자(code point)다. 1.12.1까지 Web은 앞 두 단어를 써서 "Kim Min Jun"이 Web "KM", Native "KJ"였다(미게시 변경). 1.12.1에서 일치가 필요하면 Native `initials`·Web `fallback`으로 같은 값을 준다.
119
+ - 이니셜은 두 플랫폼 모두 `resolveAvatarInitials`(`@hjmds/design-contracts/avatar-fallback`)로 첫 단어와 마지막 단어의 첫 글자(code point)다. "Kim Min Jun"은 두 플랫폼 모두 "KJ"다. 제품이 다른 이니셜 규칙을 쓰면 Native `initials`·Web `fallback`으로 같은 값을 준다.
120
120
 
121
121
  ### Native 제품 이미지 호스트
122
122
 
@@ -143,4 +143,6 @@ import { Avatar } from "@hjmds/react-native/data-display";
143
143
  실패 시 전달된 `onError`를 호출해야 공통 대체 표시로 바뀐다. 이전 source의 callback은
144
144
  A→B→A로 되돌아와도 무시한다. source를 같은 값으로 재생성하는 것은 재시도가 아니다.
145
145
  슬롯의 자식은 장식 전용이며 공통 wrapper가 접근성 트리에서 숨긴다. 버튼·링크를 넣지 않는다.
146
- 기본 Native Image 경로는 그대로다. Web은 기존 img/imageProps 경로를 사용한다.
146
+ 기본 Native Image 경로는 그대로다. Web은 img/imageProps 경로를 사용하며 src가 바뀌면 요청 img 노드를 교체해 이전 DOM 오류가 새 사진의 대체 표시를 켜지 않도록 한다.
147
+
148
+ Web animated Blobatar는 IntersectionObserver를 사용할 수 없으면 같은 seed·표정의 정적 그림을 유지한다. 장식의 관찰 기능 미지원 때문에 Avatar 본문을 실패시키지 않는다.
@@ -53,7 +53,7 @@ import { Badge } from "@hjmds/react-native/data-display";
53
53
  | `leading` | `ReactNode` | — | 라벨 앞 아이콘 자리이며 보조기기에서 숨겨진다 |
54
54
  | Web `children` · Native `label` | `ReactNode` · `string \| number`(필수) | — | 표시 라벨 |
55
55
  | Native `accessibilityLabel` | `string` | `String(label)` | 숫자 라벨에 단위를 붙여 읽힐 때 쓴다 |
56
- | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`labelStyle`은 deprecated — layoutStyle 또는 tone/토큰 |
56
+ | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`labelStyle`은 제거된 속성 — layoutStyle 또는 tone/토큰 |
57
57
 
58
58
  콜백이 없다. 누를 수 없는 표시다.
59
59
 
@@ -73,7 +73,7 @@ Native 모서리는 Provider token에서 recipe 역할을 읽는다. Badge의 `f
73
73
 
74
74
  - 라벨은 i18n 키로 넣고 짧게 둔다. 상태 뜻을 색에만 싣지 않는다(글자가 뜻을 말해야 한다).
75
75
  - 색은 `tone`으로만 고른다. 브랜드 색을 `style`·`className`·`labelStyle`로 덮지 않는다.
76
- - 배치는 Web·Native 모두 `layoutStyle`로만 한다. Native `style`·`labelStyle`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
76
+ - 배치는 Web·Native 모두 `layoutStyle`로만 한다. Native `style`·`labelStyle`은 제거된 속성이다.
77
77
 
78
78
  ## 플랫폼 차이
79
79
 
@@ -81,6 +81,6 @@ Native 모서리는 Provider token에서 recipe 역할을 읽는다. Badge의 `f
81
81
  | --- | --- | --- |
82
82
  | 라벨 | `children` | `label`(문자열·숫자, 필수) |
83
83
  | 접근성 이름 | 라벨 텍스트 | `accessibilityLabel`(기본 `String(label)`) |
84
- | 배치 | `layoutStyle` | `layoutStyle`(`style`은 deprecated) |
85
- | 라벨 스타일 훅 | 없음 | `labelStyle` deprecated — tone/토큰 |
84
+ | 배치 | `layoutStyle` | `layoutStyle`(`style`은 제거된 속성) |
85
+ | 라벨 스타일 훅 | 없음 | `labelStyle` 제거된 속성 — tone/토큰 |
86
86
  | 기본 폭 | inline | `alignSelf: "flex-start"` |
@@ -68,7 +68,7 @@ const insets = useSafeAreaInsets();
68
68
  | `accessibilityLabel` | `string` | — | 바 전체(Web `role="group"`, Native `toolbar`)의 이름 |
69
69
  | `safeAreaBottom` | 0 이상의 유한수 | `0` | 음수·무한대는 `RangeError` |
70
70
  | Web `position` | `flow` · `sticky` | `flow` | fixed는 제공하지 않는다 |
71
- | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`은 deprecated — layoutStyle 또는 tone/토큰 |
71
+ | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`은 제거된 속성 — layoutStyle 또는 tone/토큰 |
72
72
  | 큰 글자 | — | — | 두 행동을 세로로 쌓고 **주 행동을 위**에 둔다(`column-reverse`, Native `textScale >= 1.6`, Web `isLargeTextScale`) |
73
73
 
74
74
  ## 배치
@@ -101,7 +101,7 @@ const insets = useSafeAreaInsets();
101
101
  - 폼 제출을 BottomCTA로 옮기면 [Form](form.md)의 제출 버튼과 중복하지 않는다. Web은 Form `actions`에 submit Button을 넣지 않고,
102
102
  Native Form은 내장 제출 버튼(`submitLabel` 필수)을 항상 그리므로 Form 대신 필드 + contracts `createFormSubmitSession`
103
103
  (`@hjmds/design-contracts/components/form`)으로 제출 세션을 잡고 그 상태를 `primaryAction.loading`에 연결한다.
104
- - 배치는 `layoutStyle`로만 한다. Native `style`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
104
+ - 배치는 `layoutStyle`로만 한다. Native `style`은 제거된 속성이다.
105
105
  - 하단 inset을 넘긴다. Native는 자동으로 읽지 않으므로 `safeAreaBottom`이 없으면 홈 인디케이터에 붙는다.
106
106
  - 키보드가 열리는 폼 화면(Native)은 `KeyboardAvoiding`(`/keyboard`) 또는 `KeyboardDock`(`/keyboard-controller`,
107
107
  optional peer `react-native-keyboard-controller` 설치 필요)로 감싼다.
@@ -116,7 +116,7 @@ const insets = useSafeAreaInsets();
116
116
  | `loadingLabel` 타입 | `string`(접근성 이름) | `ReactNode` |
117
117
  | `accessibilityHint` | 없음 | 있음 |
118
118
  | root 역할 | `role="group"` | `accessibilityRole="toolbar"` |
119
- | 그 밖 | HTML 속성·`className`·`style` 전달 | `testID`(`style`은 deprecated) |
119
+ | 그 밖 | HTML 속성·`className`·`style` 전달 | `testID`(`style`은 제거된 속성) |
120
120
  | 빈 `label` | `TypeError` | 검사 없음(Button이 빈 이름으로 그려진다) |
121
121
 
122
122
  ## 함정
@@ -124,4 +124,4 @@ const insets = useSafeAreaInsets();
124
124
  - Web `BottomCTA`의 `style`은 recipe CSS 변수 뒤에 펼쳐지므로 `--hjm-bottom-cta-*` 변수를 덮을 수 있다. 외형은 recipe 소유이므로 `style`로 변수를 바꾸지 않는다.
125
125
  - Native는 하단 inset을 스스로 읽지 않는다. `safeAreaBottom`을 빠뜨려도 오류가 없고 홈 인디케이터에 붙어 보인다.
126
126
 
127
- 미게시(1.14.0 이후): 프로필의 `shadow.floating` 색·강도·반경을 상속하되, 위 콘텐츠와 겹치는 footer 역할이라 offsetY는 `-abs(offsetY)`로 위쪽에 표시한다. Native의 0-opacity 프로필은 Android elevation도 0이다. 프로필 없는 Web은 기존 무그림자, Native는 기존 위쪽 recipe 그림자를 유지한다. safe area·큰 글자·로딩/실패와 제품 행동 소유권은 바뀌지 않는다.
127
+ 1.15.0 변경: 프로필의 `shadow.floating` 색·강도·반경을 상속하되, 위 콘텐츠와 겹치는 footer 역할이라 offsetY는 `-abs(offsetY)`로 위쪽에 표시한다. Native의 0-opacity 프로필은 Android elevation도 0이다. 프로필 없는 Web은 기존 무그림자, Native는 기존 위쪽 recipe 그림자를 유지한다. safe area·큰 글자·로딩/실패와 제품 행동 소유권은 바뀌지 않는다.
@@ -54,7 +54,7 @@ import { BottomInfo } from "@hjmds/react-native/bottom-info";
54
54
  | `items` | 비지 않은 문자열 배열 | 필수 | 빈 배열·빈 문자열은 거부한다. 2개 이상이면 목록 표식이 붙는다(`listMarkerFrom: 2`) |
55
55
  | `tone` | `muted` · `emphasis` | `muted` | 법적 고지처럼 놓치면 안 되는 문장만 `emphasis`. danger·success tone은 없다 |
56
56
  | `renderItem` | `(item: string, index: number) => ReactNode` | — | 한 줄을 rich copy로 바꾼다(문장 안 약관 링크 등). `null`·`undefined`를 돌려주면 원래 문장을 그린다 |
57
- | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`은 deprecated — layoutStyle 또는 tone/토큰 |
57
+ | `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`은 제거된 속성 — layoutStyle 또는 tone/토큰 |
58
58
 
59
59
  상태가 없는 표시 컴포넌트다. 이벤트 콜백이 없다.
60
60
 
@@ -90,7 +90,7 @@ import { BottomInfo } from "@hjmds/react-native/bottom-info";
90
90
  | 항목 | Web | Native |
91
91
  | --- | --- | --- |
92
92
  | root | `<aside>`, 여러 줄이면 `<ul>/<li>` | `View` + 줄마다 `·` 표식 텍스트 |
93
- | 전달 가능 속성 | HTML 속성·`className`·ref·`layoutStyle` | `layoutStyle`(`style`은 deprecated) |
93
+ | 전달 가능 속성 | HTML 속성·`className`·ref·`layoutStyle` | `layoutStyle`(`style`은 제거된 속성) |
94
94
  | `renderItem` 결과 위치 | `<li>`/`<p>` 안 | `Text` 안. 인라인 텍스트(문자열·`Text`·인라인 링크)만 돌려준다 |
95
95
 
96
96
  ## 함정
@@ -84,7 +84,7 @@ import { BottomNavigation } from "@hjmds/react-native/navigation";
84
84
  | Native `renderBadge` | `(props: { item; badge: { visibleLabel: string; hiddenFromAccessibility: true }; count: number; max?: number; selected: boolean }) => ReactNode` | 기본 배지 | 결과 subtree는 보조기기에서 숨겨진다 |
85
85
  | Native `getItemTestID` | `(item) => string \| undefined` | — | — |
86
86
  | Native `safeAreaBottom` | 0 이상 숫자 | 0 | recipe 최소 하단 padding에 더한다 |
87
- | `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` |
88
88
 
89
89
  ## 배치
90
90
 
@@ -119,7 +119,7 @@ floating·capsule: 바깥 좌우 16 · 위 8 띄운 둥근 표면, 최대 폭 38
119
119
  - icon은 `name`만 넘긴다. 크기·색·굵기는 `renderIcon`이 받은 값을 그대로 적용한다(덮어쓰기 금지).
120
120
  - label과 badge `accessibilityLabel`은 i18n 키로 만든다. icon 이름·아이콘 세트는 제품 소유다.
121
121
  - 생성 버튼을 항목으로 넣지 않는다. `primaryAction`으로 둔다.
122
- - 배치는 `layoutStyle`로만 한다. Native의 `style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 deprecated(개발 모드 1회 경고, 다음 major 제거)이며 외형은 `configuration`이 소유한다.
122
+ - 배치는 `layoutStyle`로만 한다. Native의 `style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 제거된 속성이며 외형은 `configuration`이 소유한다.
123
123
 
124
124
  ## 플랫폼 차이
125
125
 
@@ -131,13 +131,24 @@ floating·capsule: 바깥 좌우 16 · 위 8 띄운 둥근 표면, 최대 폭 38
131
131
  | 하단 inset | `env(safe-area-inset-bottom)` 자동 | `safeAreaBottom`(recipe 최소 padding에 더함) |
132
132
  | 키보드 감지 | `visualViewport` 높이 | `Keyboard` 이벤트 |
133
133
  | `renderIcon`의 `color` | `"currentColor"` | 해석된 색 문자열 |
134
- | 그 밖 | `className`·`style`·HTML 속성·`layoutStyle` | `renderBadge`, `getItemTestID`, `layoutStyle`(`style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 deprecated) |
134
+ | 그 밖 | `className`·`style`·HTML 속성·`layoutStyle` | `renderBadge`, `getItemTestID`, `layoutStyle`(`style`·`surfaceStyle`·`listStyle`·`primaryActionStyle`은 제거된 속성) |
135
135
 
136
136
  ## 함정
137
137
 
138
138
  - Web root가 `position: fixed`라 본문 마지막 내용이 막대 아래로 들어간다. 제품 레이아웃이 하단 여백을 둔다.
139
139
  - `renderLink` 없이 쓰면 일반 `<a>`로 그려 SPA 전환이 일어나지 않는다.
140
140
  - Web `onActivate`는 링크 기본 이동을 막지 않는다. `renderLink`의 라우터 Link가 이동하고 `onActivate`는 계측·맨 위로 스크롤 같은 부수 동작에만 쓴다. 수정키·가운데 클릭에는 불리지 않는다.
141
- - 현재 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 축으로 정한다.
142
142
 
143
- 미게시(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
 
@@ -110,7 +110,7 @@ Button으로 [Dialog](dialog.md)를 열고, 위치 연결이 필요하면 Dialog
110
110
  같은 흐름 안에서 내용을 늘릴 때는 [Collapsible](collapsible.md)를 고른다. 이미지·제목이 각각 이동하는
111
111
  shared-element 효과와 일반 상세 Dialog는 다르며, 전자를 제공한다고 안내하지 않는다.
112
112
 
113
- ### 프로필 표면 질감(미게시)
113
+ ### 프로필 표면 질감(1.15.0 추가)
114
114
 
115
115
  선택한 `designProfile.material.surface`를 자동으로 읽는다. glass는 지원하는 Web에서 실제 배경 흐림,
116
116
  clay는 안쪽 그림자를 사용한다. Native는 루트 Provider의 선택형 `surfaceEffects` host와 inset capability를
@@ -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 게시·소비 앱 정식 채택은 별도다.