@hjmds/design-contracts 1.13.1 → 1.15.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 (200) hide show
  1. package/dist/agreement.d.ts +7 -1
  2. package/dist/agreement.d.ts.map +1 -1
  3. package/dist/agreement.js +14 -3
  4. package/dist/agreement.js.map +1 -1
  5. package/dist/behaviors.d.ts +1 -1
  6. package/dist/catalog.d.ts +4 -0
  7. package/dist/catalog.d.ts.map +1 -1
  8. package/dist/content-transition.d.ts +17 -0
  9. package/dist/content-transition.d.ts.map +1 -1
  10. package/dist/content-transition.js +21 -0
  11. package/dist/content-transition.js.map +1 -1
  12. package/dist/date-entry.d.ts +71 -0
  13. package/dist/date-entry.d.ts.map +1 -0
  14. package/dist/date-entry.js +78 -0
  15. package/dist/date-entry.js.map +1 -0
  16. package/dist/design-profile-layout.d.ts +11 -0
  17. package/dist/design-profile-layout.d.ts.map +1 -0
  18. package/dist/design-profile-layout.js +18 -0
  19. package/dist/design-profile-layout.js.map +1 -0
  20. package/dist/design-profile.d.ts +89 -0
  21. package/dist/design-profile.d.ts.map +1 -0
  22. package/dist/design-profile.js +262 -0
  23. package/dist/design-profile.js.map +1 -0
  24. package/dist/design-system-provider.d.ts +6 -0
  25. package/dist/design-system-provider.d.ts.map +1 -1
  26. package/dist/design-system-provider.js +4 -2
  27. package/dist/design-system-provider.js.map +1 -1
  28. package/dist/document-resource.d.ts +104 -0
  29. package/dist/document-resource.d.ts.map +1 -0
  30. package/dist/document-resource.js +77 -0
  31. package/dist/document-resource.js.map +1 -0
  32. package/dist/effect-surface.d.ts +6 -1
  33. package/dist/effect-surface.d.ts.map +1 -1
  34. package/dist/effect-surface.js +5 -2
  35. package/dist/effect-surface.js.map +1 -1
  36. package/dist/field-group.d.ts +47 -0
  37. package/dist/field-group.d.ts.map +1 -0
  38. package/dist/field-group.js +92 -0
  39. package/dist/field-group.js.map +1 -0
  40. package/dist/gooey-navigation.d.ts +19 -1
  41. package/dist/gooey-navigation.d.ts.map +1 -1
  42. package/dist/gooey-navigation.js +37 -2
  43. package/dist/gooey-navigation.js.map +1 -1
  44. package/dist/image.d.ts +17 -0
  45. package/dist/image.d.ts.map +1 -1
  46. package/dist/image.js +19 -0
  47. package/dist/image.js.map +1 -1
  48. package/dist/internal/effect-noise.d.ts +2 -0
  49. package/dist/internal/effect-noise.d.ts.map +1 -0
  50. package/dist/internal/effect-noise.js +4 -0
  51. package/dist/internal/effect-noise.js.map +1 -0
  52. package/dist/palette-contrast.d.ts +6 -0
  53. package/dist/palette-contrast.d.ts.map +1 -1
  54. package/dist/palette-contrast.js +17 -0
  55. package/dist/palette-contrast.js.map +1 -1
  56. package/dist/progressive-blur.d.ts +32 -0
  57. package/dist/progressive-blur.d.ts.map +1 -0
  58. package/dist/progressive-blur.js +28 -0
  59. package/dist/progressive-blur.js.map +1 -0
  60. package/dist/reference-controls.d.ts +32 -0
  61. package/dist/reference-controls.d.ts.map +1 -0
  62. package/dist/reference-controls.js +28 -0
  63. package/dist/reference-controls.js.map +1 -0
  64. package/dist/screen-patterns.d.ts +16 -1
  65. package/dist/screen-patterns.d.ts.map +1 -1
  66. package/dist/screen-patterns.js +4 -0
  67. package/dist/screen-patterns.js.map +1 -1
  68. package/dist/scroll-progress.d.ts +7 -1
  69. package/dist/scroll-progress.d.ts.map +1 -1
  70. package/dist/scroll-progress.js +21 -2
  71. package/dist/scroll-progress.js.map +1 -1
  72. package/dist/text-annotation.d.ts +43 -0
  73. package/dist/text-annotation.d.ts.map +1 -0
  74. package/dist/text-annotation.js +137 -0
  75. package/dist/text-annotation.js.map +1 -0
  76. package/dist/toast-liquid.d.ts +3 -1
  77. package/dist/toast-liquid.d.ts.map +1 -1
  78. package/dist/toast-liquid.js +6 -2
  79. package/dist/toast-liquid.js.map +1 -1
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/dist/version.js.map +1 -1
  83. package/docs/agreement.md +14 -0
  84. package/docs/asset.md +7 -0
  85. package/docs/brand-boundary.md +14 -5
  86. package/docs/code-block.md +12 -1
  87. package/docs/collapsible.md +6 -0
  88. package/docs/design-profile.md +263 -0
  89. package/docs/design-system-provider.md +7 -0
  90. package/docs/dialog.md +20 -1
  91. package/docs/effect-surface.md +19 -3
  92. package/docs/generated/component-maturity.md +1 -1
  93. package/docs/generated/renderer-evidence.json +3 -3
  94. package/docs/generated/renderer-evidence.md +1 -1
  95. package/docs/generated/showcase-manifest.json +1 -1
  96. package/docs/gooey-navigation.md +37 -6
  97. package/docs/heading.md +15 -0
  98. package/docs/image.md +17 -0
  99. package/docs/optional-adapters.md +25 -0
  100. package/docs/popover.md +15 -0
  101. package/docs/rating.md +6 -1
  102. package/docs/reference-controls.md +40 -0
  103. package/docs/task-list.md +15 -1
  104. package/docs/text-annotation.md +85 -0
  105. package/docs/theming.md +18 -5
  106. package/docs/usage/README.md +23 -1
  107. package/docs/usage/components/activity-heatmap.md +3 -1
  108. package/docs/usage/components/agreement.md +15 -3
  109. package/docs/usage/components/alert-dialog.md +16 -1
  110. package/docs/usage/components/asset.md +9 -2
  111. package/docs/usage/components/avatar.md +33 -1
  112. package/docs/usage/components/badge.md +3 -1
  113. package/docs/usage/components/bottom-cta.md +3 -1
  114. package/docs/usage/components/bottom-navigation.md +5 -1
  115. package/docs/usage/components/button.md +7 -1
  116. package/docs/usage/components/calendar.md +3 -1
  117. package/docs/usage/components/card.md +26 -3
  118. package/docs/usage/components/carousel.md +10 -1
  119. package/docs/usage/components/chat-message.md +11 -1
  120. package/docs/usage/components/chip.md +5 -0
  121. package/docs/usage/components/code-block.md +14 -2
  122. package/docs/usage/components/collapsible.md +10 -2
  123. package/docs/usage/components/combobox.md +12 -1
  124. package/docs/usage/components/command-palette.md +11 -6
  125. package/docs/usage/components/content-transition.md +29 -5
  126. package/docs/usage/components/context-menu.md +7 -1
  127. package/docs/usage/components/date-picker.md +3 -1
  128. package/docs/usage/components/design-system-provider.md +7 -5
  129. package/docs/usage/components/dialog.md +70 -1
  130. package/docs/usage/components/effect-surface.md +4 -2
  131. package/docs/usage/components/empty-state.md +10 -2
  132. package/docs/usage/components/field.md +26 -1
  133. package/docs/usage/components/form.md +13 -3
  134. package/docs/usage/components/heading.md +7 -1
  135. package/docs/usage/components/image-comparison.md +82 -0
  136. package/docs/usage/components/image.md +83 -2
  137. package/docs/usage/components/keyboard-avoiding.md +6 -1
  138. package/docs/usage/components/link.md +3 -1
  139. package/docs/usage/components/list-row.md +5 -1
  140. package/docs/usage/components/list.md +8 -1
  141. package/docs/usage/components/load-more.md +3 -1
  142. package/docs/usage/components/mentions.md +3 -1
  143. package/docs/usage/components/menu.md +3 -1
  144. package/docs/usage/components/menubar.md +7 -1
  145. package/docs/usage/components/message-composer.md +16 -5
  146. package/docs/usage/components/notice.md +9 -0
  147. package/docs/usage/components/number-field.md +3 -1
  148. package/docs/usage/components/onboarding-screen.md +16 -9
  149. package/docs/usage/components/overview-screen.md +73 -0
  150. package/docs/usage/components/password-field.md +5 -1
  151. package/docs/usage/components/popover.md +14 -1
  152. package/docs/usage/components/progress.md +28 -0
  153. package/docs/usage/components/progressive-blur.md +113 -0
  154. package/docs/usage/components/rating.md +74 -0
  155. package/docs/usage/components/saved-items-screen.md +3 -1
  156. package/docs/usage/components/screen-layout.md +9 -1
  157. package/docs/usage/components/search-field.md +13 -1
  158. package/docs/usage/components/search-screen.md +5 -0
  159. package/docs/usage/components/segmented-control.md +17 -0
  160. package/docs/usage/components/select.md +9 -0
  161. package/docs/usage/components/sheet.md +14 -1
  162. package/docs/usage/components/skeleton.md +9 -0
  163. package/docs/usage/components/slider.md +3 -1
  164. package/docs/usage/components/statistic.md +14 -1
  165. package/docs/usage/components/surface.md +8 -1
  166. package/docs/usage/components/tabs.md +11 -2
  167. package/docs/usage/components/tag.md +3 -1
  168. package/docs/usage/components/tags-input.md +11 -1
  169. package/docs/usage/components/text-area.md +3 -1
  170. package/docs/usage/components/text-transition.md +8 -2
  171. package/docs/usage/components/text.md +3 -1
  172. package/docs/usage/components/toast.md +23 -1
  173. package/docs/usage/components/top-bar.md +3 -1
  174. package/docs/usage/components/upload-item.md +3 -1
  175. package/docs/usage/compositions/action-feedback.md +77 -0
  176. package/docs/usage/compositions/adaptive-content.md +81 -0
  177. package/docs/usage/compositions/command-records.md +106 -0
  178. package/docs/usage/compositions/content-transition-comparison.md +108 -0
  179. package/docs/usage/compositions/context-toolbar.md +85 -0
  180. package/docs/usage/compositions/date-entry.md +108 -0
  181. package/docs/usage/compositions/date-time-selection.md +110 -0
  182. package/docs/usage/compositions/design-profile-comparison.md +114 -0
  183. package/docs/usage/compositions/document-resource.md +124 -0
  184. package/docs/usage/compositions/field-group.md +104 -0
  185. package/docs/usage/compositions/illustrated-outcome.md +91 -0
  186. package/docs/usage/compositions/live-list.md +104 -0
  187. package/docs/usage/compositions/optional-adapters.md +1 -1
  188. package/docs/usage/compositions/origin-dialog.md +108 -0
  189. package/docs/usage/compositions/selection-motion.md +73 -0
  190. package/docs/usage/compositions/texture-comparison.md +86 -0
  191. package/docs/usage/compositions/upload-recovery.md +80 -0
  192. package/docs/usage/compositions/video-dialog.md +100 -0
  193. package/docs/usage/screens/flow-onboarding.md +5 -3
  194. package/docs/usage/screens/product-bento.md +117 -0
  195. package/docs/usage/tokens/color.md +9 -2
  196. package/docs/usage/tokens/elevation-opacity.md +8 -1
  197. package/docs/usage/tokens/motion.md +10 -1
  198. package/docs/usage/tokens/radius.md +8 -1
  199. package/docs/usage/tokens/typography.md +15 -1
  200. package/package.json +49 -1
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: `src/base-recipes.ts`(`surfaceRecipe`·`surfaceDefaults`·`surfaceGeometry`)
9
9
  - 스토리북: `배포/컴포넌트/레이아웃/배경 영역`
10
10
 
@@ -62,6 +62,13 @@ import { Surface } from "@hjmds/react-native/primitives";
62
62
  | `as`(Web) | `div` · `section` · `article` | `div` | — |
63
63
  | `layoutStyle` | `HjmCompositionStyleProp` | — | Surface 자신의 바깥 여백·폭·flex·`alignSelf`. Web·Native 모두 |
64
64
 
65
+ ### 프로필 표면 질감(미게시)
66
+
67
+ 선택한 `designProfile.material.surface`를 자동으로 읽는다. glass는 지원하는 Web에서 실제 배경 흐림,
68
+ clay는 안쪽 그림자를 사용한다. Native는 루트 Provider의 선택형 `surfaceEffects` host와 inset capability를
69
+ 한 번 등록한다. 사용할 수 없거나 투명도 줄이기 설정이면 불투명 표면을 유지한다. 효과와 입력은 서로 다른
70
+ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지한다. [범위·등록·대비 계약](../../design-profile.md#surfacecard의-유리클레이-질감)을 따른다.
71
+
65
72
  ## 배치
66
73
 
67
74
  | 항목 | 값 | 근거 |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [Gooey navigation](../../gooey-navigation.md), `src/component-recipes.ts`(`tabsRecipe`), `src/behaviors.ts`(`tabsBehaviorDefaults`)
9
9
  - 스토리북: `배포/컴포넌트/탐색/탭`, `배포/컴포넌트/탐색/선택 표시가 이어지는 탭`
10
10
 
@@ -77,7 +77,7 @@ import { Tabs } from "@hjmds/react-native/navigation";
77
77
  | `overflow` | `scroll` · `clip` | `scroll` | — |
78
78
  | `orientation` | `horizontal` · `vertical` | `horizontal` | — |
79
79
  | `loop` | `boolean` | `true` | — |
80
- | `appearance` | `standard` · `gooey` | `standard` | `gooey`는 가로일 때만 선택 표시가 늘어나며 이동, 세로는 standard 유지 |
80
+ | `appearance` | `standard` · `slide` · `gooey` | 미지정 시 프로필의 selectionMotion; 없으면 `standard` | 미게시(1.14.0 이후): `slide`는 2점 표시선 이동, `gooey`는 6점 표시선 늘어남. 명시 값 우선, 세로는 standard 유지 |
81
81
  | `renderPanels` | `boolean` | `true` | `false`면 패널을 그리지 않는다. 패널을 라우터·스크롤 상태와 함께 따로 둘 때 `TabPanel`에 `tabsId`(Tabs의 `id`와 같게)·`activeValue`·`value`를 넘긴다 |
82
82
  | `children`(Native) | `(selectedValue: Value) => ReactNode` | — | 항목 `panel` 대신 선택 값으로 패널을 그린다 |
83
83
  | `TabPanel` | `{ tabsId, activeValue, children, mode?: "keyed", value, mountPolicy? }` 또는 `{ mode: "dynamic" }`, Native는 `label` 필수 | — | — |
@@ -129,6 +129,15 @@ import { Tabs } from "@hjmds/react-native/navigation";
129
129
 
130
130
  ## 함정
131
131
 
132
+ - 2026-10-07 [Animated Tabs 대조](../../../../../docs/plans/aceternity-interaction-adoption-2026-10-07.md)에서
133
+ 선택 표시의 이동과 겹친 패널의 이동을 구분했다. 현재 `appearance="gooey"`는 표시선만 늘어나며,
134
+ 패널을 겹쳐 복제하거나 순서를 바꾸지 않는다. 미게시(1.14.0 이후) 후속 구현에서
135
+ `appearance`를 생략하면 `designProfile.interactions.selectionMotion="slide"`가 일반 이동선으로
136
+ 연결된다. 명시 `standard`는 이동을 끄고 명시 `gooey`만 늘어남을 고른다. `slide`와 `gooey`를
137
+ 같은 의미로 간주하지 않는다. 전환 표현이 필요하면 [ContentTransition](content-transition.md)의
138
+ 단일 콘텐츠 전환과 Tabs의 `mountPolicy`·`panelMode`를 함께 검토한다. 프로필이 탭·패널 수명을
139
+ 자동 결정하거나 active 패널의 unmount 후 로컬 초안을 보존한다고 안내하지 않는다.
140
+
132
141
  - Native `Tabs`는 예전 `options` prop을 받으면 `TypeError`("options was removed")를 던진다. `items`로 옮긴다.
133
142
  - 외부 `TabPanel`을 쓸 때 Tabs에 `id`를 주지 않으면 Web은 생성 id를 써서 `tabsId`를 맞출 수 없다.
134
143
  `id`를 명시하고 같은 값을 `TabPanel tabsId`에 넘긴다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [Tag contract](../../tag.md), `src/tag.ts`(`tagRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/태그`
10
10
 
@@ -59,6 +59,8 @@ import { Tag } from "@hjmds/react-native/data-display";
59
59
 
60
60
  ## 배치
61
61
 
62
+ Native의 모서리는 Provider의 `tokens.radius.sm`이다. metadata 의미와 tone은 변하지 않는다.
63
+
62
64
  | 항목 | 값 | 근거 |
63
65
  | --- | --- | --- |
64
66
  | 크기 | 최소 높이 20, 테두리 1, radius `sm` 8. 누르는 대상이 아니므로 44 터치 영역을 두지 않는다 | `tagRecipe.size`, `styles.css` `.hjm-tag`, `react-native/src/data-display.tsx` |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [TagsInput](../../tags-input.md), `src/tags-input.ts`(`tagsInputRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/입력/태그 입력`
10
10
 
@@ -84,6 +84,11 @@ import { TagsInput } from "@hjmds/react-native/tags-input";
84
84
 
85
85
  ## 배치
86
86
 
87
+ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력 host에 연결한다. 기본 UI stack은 OS 서체를 유지하고, 제품이 지정한 첫 named font의 등록·글리프 확인은 제품이 맡는다.
88
+ Native 입력칸은 `tokens.typography.body`의 글자·줄 높이도 읽는다. Provider가 textScale을 제어하면 한 번만 확대하고 시스템의 추가 확대를 끈다. 태그/편집 초안은 테마 전환 때 유지한다.
89
+
90
+ Native 입력 프레임의 `md` 모서리는 Provider의 `tokens.radius.md`를 읽는다. pill 태그와 고정 삭제 glyph는 그대로이며 프로필 교체는 입력 중인 초안·태그를 유지한다.
91
+
87
92
  | 항목 | 값 | 근거 |
88
93
  | --- | --- | --- |
89
94
  | 크기 | 입력 틀 최소 높이 44(필드 틀과 같음), 모서리 `radius.md` 12. 입력 칸 최소 Web `8ch`, Native 80. 태그 칩 높이 28, 삭제 버튼 터치 영역 44(`control.minTouchTarget`). 후보 행 최소 높이 44(두 플랫폼, Native는 미게시(1.12.1 이후). 1.12.1 Native는 28) | `tagsInputRecipe`, `.hjm-tags-input__frame`·`__remove::after`·`__suggestion` |
@@ -109,3 +114,8 @@ import { TagsInput } from "@hjmds/react-native/tags-input";
109
114
  | 후보 이동 | ArrowUp/Down(끝에서 순환) | 후보를 버튼으로 누름 |
110
115
  | ref | `forwardRef`(`input`) | 없음 |
111
116
  | 스타일 prop | `className`, `layoutStyle` | `layoutStyle`(`style`은 deprecated) |
117
+
118
+
119
+ ### 고정 아이콘과 큰 글자
120
+
121
+ 2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: `src/base-recipes.ts`(`fieldRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/입력/여러 줄 입력`
10
10
 
@@ -79,6 +79,8 @@ import { TextArea } from "@hjmds/react-native/inputs";
79
79
 
80
80
  ## 배치
81
81
 
82
+ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력 host에 연결한다. 기본 UI stack은 OS 서체를 유지하고, 제품이 지정한 첫 named font의 등록·글리프 확인은 제품이 맡는다.
83
+
82
84
  | 항목 | 값 | 근거 |
83
85
  | --- | --- | --- |
84
86
  | 크기 | 최소 높이 80(`fieldRecipe.multilineMinHeight`)에서 시작해 입력에 따라 자란다. 좌우 안쪽 여백 `spacing.md` 16, 모서리 기본 `radius.md` 12 | `fieldRecipe`, `.hjm-field__control--multiline` |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: `src/content-transition.ts`(`resolveContentTransition`). 독립 계약 문서 없음(supplemental)
9
9
  - 스토리북: `배포/컴포넌트/시각 효과/내용 전환`, `배포/구성/직접 조작과 모션/끌기·밀기·화면 전환`
10
10
 
@@ -70,7 +70,7 @@ const uploadStatusKey = {
70
70
  | prop | 값 | 기본값 | 설명 |
71
71
  | --- | --- | --- | --- |
72
72
  | `text`(필수) | 문자열 | — | 바뀔 때마다 전환한다. 같은 문자열이면 아무것도 하지 않는다 |
73
- | `preset` | `fade` · `rise`(아래 12에서) · `slide`(가로 16, RTL이면 반대) · `scale`(0.96에서) | `fade` | — |
73
+ | `preset` | `fade` · `rise`(아래 12에서) · `slide`(가로 16, RTL이면 반대) · `scale`(0.96에서) | provider `designProfile.interactions.contentTransition`, 프로필 없으면 `fade` | 명시한 prop이 프로필보다 우선 |
74
74
  | `motion` | `system` · `none` | `system` | `system`이어도 provider의 reduced motion이면 즉시 바뀐다 |
75
75
  | `layoutStyle`(Web) | `HjmCompositionStyleProp` | — | 바깥 `<div>` 배치. Native에는 없다 |
76
76
  | `ContentTransition` `stateKey`·`children` | `string`·`ReactNode` | — (필수) | `stateKey`가 바뀔 때 하위 트리를 전환한다 |
@@ -93,6 +93,12 @@ const uploadStatusKey = {
93
93
  - 문자열은 i18n 키로 만든 최종 문구를 넘긴다. 글자 수 변화를 노린 연출용으로 쓰지 않는다.
94
94
  - 글자 모양 prop은 없다. 글자 모양은 감싸는 쪽이 정한다(아래 플랫폼 차이). Web은 `layoutStyle`로 배치만 하고, Native는 배치 prop이 없어 감싸는 쪽이 배치한다.
95
95
  - Web에서 이 subpath를 쓰려면 앱에 `framer-motion`을 설치한다. 없으면 import 시점에 실패한다.
96
+ - 테마별 전환을 따르려면 `preset`을 생략한다. `motion="none"`과 환경의 모션 감소는 프로필보다 우선한다.
97
+ - [Motion Text Loop 실제 검토](../../../../../docs/qa/2026-10-07-motion-reference-page-review.md)에서
98
+ 모션 감소 중에도 순환·공간 이동, exit/enter 두 문구가 동시에 읽히는 경로를 확인했다.
99
+ 현재 TextTransition은 단일 값의 전환만 맡으며 timer/배열/자동 순환 API를 제공하지 않는다.
100
+ 소개용 문구를 순환하는 구성에는 정지·읽기 시간·정적 대체·locale별 줄바꿈의 별도 계약이 필요하다.
101
+ 실제 처리 상태는 제품 상태에서 받은 `text`로 표시하고 타이머로 성공/진행을 만들지 않는다.
96
102
 
97
103
  ## 플랫폼 차이
98
104
 
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: `src/component-recipes.ts`(`textRecipe`), [Gravity Letters](../../gravity-letters.md)
9
9
  - 스토리북: `배포/컴포넌트/글자와 아이콘/본문 글자`, `배포/컴포넌트/시각 효과/중력 글자`
10
10
 
@@ -68,6 +68,8 @@ import { Text } from "@hjmds/react-native/primitives";
68
68
 
69
69
  ## 배치
70
70
 
71
+ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력 host에 연결한다. 기본 UI stack은 OS 서체를 유지하고, 제품이 지정한 첫 named font의 등록·글리프 확인은 제품이 맡는다.
72
+
71
73
  | 항목 | 값 | 근거 |
72
74
  | --- | --- | --- |
73
75
  | 크기 | 폭은 부모를 따른다. 긴 읽기 글은 부모 폭을 `layout.readingMaxWidth` 720 이하로 둔다 | `foundations.ts`(`layout`) |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [Toast](../../toast.md), [Liquid Toast](../../../../react-native/docs/liquid-toast.md), `src/component-recipes.ts`(`toastRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드 토스트`
10
10
 
@@ -93,6 +93,23 @@ function useProfileSavedToast() {
93
93
  | `layoutStyle`(Native `Toast`·`ToastRegion`) | `HjmCompositionStyleProp` | — | Web Toast·ToastProvider에는 없다(떠 있는 층이라 배치 대상이 아님) |
94
94
  | `style`·`toastStyle`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `placement`·`safeAreaInsets`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
95
95
 
96
+ ### 디자인 프로필 상속
97
+
98
+ 2026-10-07 테마 소비 경로 점검에서 고정 foundation/recipe 값이 남은 곳을 보완했다.
99
+ 모서리의 recipe 역할은 유지하고 값은 가장 가까운 Provider의 `designProfile.tokens.radius`를
100
+ 읽는다. Dialog/AlertDialog/Sheet/일반 Toast의 그림자는 `tokens.shadow.floating`을 읽으며
101
+ 프로필 없는 소비자의 기본값은 유지한다. 상태·초안·선택·Modal teardown은 이 축의 소유가 아니다.
102
+ 플랫폼 근사와 미검증 범위는 [프로필 계약](../../design-profile.md#오버레이선택-입력의-프로필-연결-보완)을 따른다.
103
+
104
+ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`와 `shadow.raised`를
105
+ 읽는다. 일반 Toast의 `floating` 대신 `raised`를 유지하는 이유는 리퀴드 카드의 얕은 깊이를
106
+ 정한 기존 검토다. RN 내용 clip과 Skia 카드 모서리를 함께 바꾸고 그림자의 위·아래·좌·우
107
+ 여유를 확보한다. 프로필이 없으면 기존 모서리 12와 foundation `raised`를 유지한다.
108
+ 원형 시작점·큐·행동은 유지하며 Web은 일반 Toast fallback이다. 기존
109
+ `실험/구성/비교와 검증/테마 조합`의 Native ‘물방울 알림의 테마 비교’에서 같은 알림을
110
+ 유지한 채 다음 테마로 바꿀 수 있다. 기기 시각·음성·제스처 검증은 별도로 남는다.
111
+
112
+
96
113
  ## 배치
97
114
 
98
115
  | 항목 | 값 | 근거 |
@@ -143,3 +160,8 @@ function useProfileSavedToast() {
143
160
  어댑터는 render 밖에서 만들거나 memo한다.
144
161
  - ToastRegion 배치는 `layoutStyle={{ flex: 1 }}`로 쓴다. Native 예제도 이 경로를 사용한다.
145
162
  - Web `Toast` 단독 렌더는 나머지 HTML 속성(id·data-*·이벤트)을 루트에 전달한다(미게시(1.12.1 이후). 1.12.1은 `className`만 전달). `role`·`aria-labelledby`·`aria-describedby`·`data-tone`·`data-state`는 Toast가 정하므로 덮이지 않는다. 배치는 Provider를 쓴다.
163
+
164
+
165
+ ### 고정 아이콘과 큰 글자
166
+
167
+ 2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [화면 제목과 마지막 행동](../../screen-chrome.md), [NavigationBar](../../navigation-bar.md), `src/component-recipes.ts`(`topBarRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/탐색/상단 탐색 막대`, `배포/컴포넌트/탐색/검색·메뉴가 있는 상단 바`, `배포/컴포넌트/탐색/내비게이션 바`
10
10
 
@@ -83,6 +83,8 @@ import { TopBar, TopBarAction } from "@hjmds/react-native/top-bar";
83
83
 
84
84
  ## 배치
85
85
 
86
+ NavigationBar의 Native 프레임 모서리는 Provider의 `tokens.radius.xl`을 읽는다. 기존 불투명 semantic 표면과 목적지·행동 슬롯을 유지한다.
87
+
86
88
  | 항목 | 값 | 근거 |
87
89
  | --- | --- | --- |
88
90
  | 크기 | 최소 높이 `control.buttonHeight.large` 52 + 위쪽 안전 영역. Web은 `max(env(safe-area-inset-top), safeAreaTop)`을 위 여백으로, Native는 `safeAreaTop`을 `paddingTop`과 `minHeight`(52 + `safeAreaTop`)에 더한다. 좌우 열 최소 폭 `control.minTouchTarget` 44, Native `TopBarAction` 최소 44×44 | `topBarRecipe.minHeight`·`sideMinWidth`·`action`, `.hjm-top-bar` |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [UploadItem](../../upload-item.md), `src/upload-item.ts`(`uploadItemRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/업로드 항목`
10
10
 
@@ -84,6 +84,8 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
84
84
 
85
85
  ## 꼭 지킬 것
86
86
 
87
+ - 이미 게시된 문서를 열거나 내려받는 목록에 `success` 상태를 전용하지 않는다. 이 API의 행동은 업로드 취소·재시도이며 다운로드 행동이 아니다. Web 다운로드는 [Link](link.md), 미리보기와 여러 행동은 [Card](card.md)를 대조한다. 2026-10-07 파일 사례 조사에서 다운로드와 업로드의 초기 대응표가 같은 계약으로 오인될 수 있어 구분했다.
88
+
87
89
  - `progress`에 100을 곱해 넘기지 않는다. renderer가 내부 Progress에 `value={progress * 100}`으로 바꾼다.
88
90
  - `uploading` 상태에서 `onCancel`이, `error` 상태에서 `onRetry`가 없으면 렌더 중 `TypeError`가 난다.
89
91
  - 바이트 포맷(`sizeLabel`)·상태 문구·`message`·업로드 요청·재시도 로직은 제품 소유다. 행 모양·상태 색·액션 결정은 HJM 소유다.
@@ -0,0 +1,77 @@
1
+ # 버튼 완료 피드백
2
+
3
+ - 단계: 구성
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.14.0
7
+ - 검토일: 2026-10-07
8
+ - 근거: 공개 API를 사용하는 `showcase/*/reference-adoption-previews.tsx`
9
+ - 스토리북: `배포/구성/피드백과 복구/버튼 완료 피드백`
10
+
11
+ 승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
12
+
13
+ ## 언제 쓰나
14
+
15
+ 입력을 유지하며 저장의 진행·성공·재시도 가능 실패를 보여 줄 때 쓴다. 별도 신규 wrapper API가 아니라 아래 공개 컴포넌트의 조합 규격이다.
16
+
17
+ ## 구성 요소
18
+
19
+ | 컴포넌트 | 역할 | 지침 |
20
+ | --- | --- | --- |
21
+ | Button · TextField · ContentTransition · action-session | 입력·표현·행동의 역할 분리 | [입력/동작](../components/button.md), [상태/전환](../components/content-transition.md) |
22
+
23
+ ## 배치
24
+
25
+ ```text
26
+ [제목]
27
+ [기록 제목 입력]
28
+ [저장 / 다시 저장 버튼]
29
+ [진행·실패·성공 결과]
30
+ [실패 체험 스위치 + 서버 요청 없음 안내]
31
+ ```
32
+
33
+ | 영역 | 컴포넌트 | 위치 | 크기·간격 |
34
+ | --- | --- | --- | --- |
35
+ | 바깥 틀 | Stack | 화면의 본문 흐름 | gap md=16px, 전체 폭 |
36
+ | 내용 | 해당 공개 API | 입력과 행동 사이 | 자체 recipe 크기, 줄바꿈 허용 |
37
+ | 행동 | Button/기존 컨트롤 | 관련 항목 바로 뒤 | inline일 때 wrap, md=16px |
38
+
39
+ ## 흐름과 상태
40
+
41
+ 1. 입력→요청→실제 결과에 따라 성공/실패. 다시 저장은 클릭한 시점의 현재 초안을 새 요청으로 보낸다.
42
+ 2026-10-07 실패 후 제목 수정·재저장 확인에서 기존 문서의 “같은 입력 재시도”와 구현이
43
+ 다른 것을 확인했다. 편집 가능한 초안에 맞춰 현재 값 저장을 유지한다. 실패한 요청의
44
+ 원래 값 재시도가 필요하면 별도 행동에서 action-session.retry를 연결하고 대상 값을 알린다.
45
+ 2. 서버 응답·파일 권한·문구·브랜드는 제품이 전달한다. Showcase의 예제 응답과 고정 데이터를 가져오지 않는다.
46
+
47
+ | 상태 | 모습 | 포커스·알림 |
48
+ | --- | --- | --- |
49
+ | 기본 | 입력과 사용 가능한 행동 | 조작 이름은 제품 언어 |
50
+ | 진행 중 | 실제 작업 상태에 맞는 loading 또는 열린 도구 | 중복 요청 차단, 입력 유지 |
51
+ | 실패 | 문구와 가능한 재시도 | 원래 입력/파일 유지, 결과 안내 |
52
+
53
+ ## 코드 골격
54
+
55
+ ```tsx
56
+ // Web
57
+ <Button loading={busy} onClick={save}>{busy ? pendingLabel : saveLabel}</Button>
58
+ ```
59
+
60
+ ```tsx
61
+ // Native
62
+ <Button loading={busy} onPress={save}>{busy ? pendingLabel : saveLabel}</Button>
63
+ ```
64
+
65
+ Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native` entry를 쓴다.
66
+ Button의 실행 콜백은 Web `onClick`, Native `onPress`로 연결한다. 위 골격의 도메인 함수·변수는 제품이 제공한다.
67
+
68
+ 예제 응답은 useDemoAction의 인위 지연이며 실제 네트워크·영구 저장이 아니다.
69
+ 완료 문구는 응답 확정 뒤 표시한다. 저장 중 입력 편집이 가능하므로, 저장 결과는 현재
70
+ 초안으로 바꿔 쓰지 말고 응답 값으로 표시한다. 실패 체험 설정은 요청 중 바꾸지 않는다.
71
+
72
+ ## 플랫폼 차이
73
+
74
+ | 항목 | Web | Native |
75
+ | --- | --- | --- |
76
+ | 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
77
+ | 큰 글자·좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |
@@ -0,0 +1,81 @@
1
+ # 높이가 이어지는 패널
2
+
3
+ - 단계: 구성
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.14.0
7
+ - 검토일: 2026-10-07
8
+ - 근거: 공개 API를 사용하는 `showcase/*/reference-adoption-previews.tsx`
9
+ - 스토리북: `배포/구성/직접 조작과 모션/높이가 이어지는 패널`
10
+
11
+ 승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
12
+
13
+ ## 언제 쓰나
14
+
15
+ 패널의 길이가 달라질 때 아래 행동이 새 높이로 이동해야 하는 작은 내용 영역에 쓴다. 별도 신규 wrapper API가 아니라 아래 공개 컴포넌트의 조합 규격이다.
16
+
17
+ ## 구성 요소
18
+
19
+ | 컴포넌트 | 역할 | 지침 |
20
+ | --- | --- | --- |
21
+ | SegmentedControl · ContentTransition · Surface · TextField | 입력·표현·행동의 역할 분리 | [입력/동작](../components/segmented-control.md), [상태/전환](../components/content-transition.md) |
22
+
23
+ ## 배치
24
+
25
+ ```text
26
+ [요약 / 상세 단일 선택]
27
+ [높이가 바뀌는 콘텐츠 패널]
28
+ [전환 바깥의 메모 입력: 유지]
29
+ ```
30
+
31
+ | 영역 | 컴포넌트 | 위치 | 크기·간격 |
32
+ | --- | --- | --- | --- |
33
+ | 바깥 틀 | Stack | 화면의 본문 흐름 | gap md=16px, 전체 폭 |
34
+ | 내용 | 해당 공개 API | 입력과 행동 사이 | 자체 recipe 크기, 줄바꿈 허용 |
35
+ | 행동 | Button/기존 컨트롤 | 관련 항목 바로 뒤 | inline일 때 wrap, md=16px |
36
+
37
+ ## 흐름과 상태
38
+
39
+ 1. 선택→내용 교체→입력은 바깥에서 유지
40
+ 2. 서버 응답·파일 권한·문구·브랜드는 제품이 전달한다. Showcase의 예제 응답과 고정 데이터를 가져오지 않는다.
41
+
42
+ | 상태 | 모습 | 포커스·알림 |
43
+ | --- | --- | --- |
44
+ | 기본 | 입력과 사용 가능한 행동 | 조작 이름은 제품 언어 |
45
+ | 진행 중 | 실제 작업 상태에 맞는 loading 또는 열린 도구 | 중복 요청 차단, 입력 유지 |
46
+ | 실패 | 문구와 가능한 재시도 | 원래 입력/파일 유지, 결과 안내 |
47
+
48
+ ## 코드 골격
49
+
50
+ ```tsx
51
+ // Web
52
+ <ContentTransition stateKey={panel} animateHeight><Panel /></ContentTransition>
53
+ ```
54
+
55
+ ```tsx
56
+ // Native
57
+ <ContentTransition stateKey={panel} animateHeight><Panel /></ContentTransition>
58
+ ```
59
+
60
+ Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native` entry를 쓴다.
61
+ 선택은 SegmentedControl의 onValueChange로 연결한다. 패널 바깥 입력은 unmount하지 않는다.
62
+ 2026-10-07 Web 390px·큰 글자에서 상세→요약 방향키 연속 전환 뒤 단일 내용과 초안 보존을
63
+ 확인했다. 이 확인은 프레임 속도나 Native 성능 동등성의 근거가 아니다.
64
+
65
+ ## 플랫폼 차이
66
+
67
+ | 항목 | Web | Native |
68
+ | --- | --- | --- |
69
+ | 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
70
+ | 큰 글자·좁은 폭 | 줄바꿈·단일 내용 | 동일 순서와 스크롤 host 필요 |
71
+
72
+
73
+ ### Native 입력과 화면 끝
74
+
75
+ 2026-10-07 큰 글자에서 상세 패널이 늘어나면 키보드 아래로 메모가 밀려났고,
76
+ 스크롤 없는 Showcase canvas에서는 다시 접근할 수 없었다. Native 예제는
77
+ ScreenLayout의 본문 스크롤과 바깥 KeyboardAvoiding으로 사용 가능한 높이를 확보한다.
78
+ Storybook의 기존 gutter 때문에 contentInset=none을 쓰고, 마지막 입력의 테두리가
79
+ 스크롤 경계에 걸리지 않도록 본문 끝에 spacing.md(16) 여백을 둔다.
80
+ 본문 조합 자체에 두 번째 scroll view를 만들지 말고 제품의 기존 화면 host를 재사용한다.
81
+ KeyboardAvoiding은 아래 여백을 제공하며 포커스된 필드를 자동 탐색하는 API는 아니다.
@@ -0,0 +1,106 @@
1
+ # 명령 기록 표시
2
+
3
+ - 단계: 구성
4
+ - 상태: 실험
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.14.0 이후)
7
+ - 검토일: 2026-10-07
8
+ - 근거: [후보 조사](../../../../../docs/qa/2026-10-07-reference-parallel-b.md), 양 Showcase `command-records-preview.tsx`
9
+ - 스토리북: `실험/구성/정보 표시/명령 기록 표시`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 명령 원문과 출력 기록을 선택·읽기·복사할 때 쓴다. [Magic Terminal](https://magicui.design/docs/components/terminal)의
14
+ 명령/출력 구분과 [Aceternity 코드 블록](https://ui.aceternity.com/components/code-block)의 탭/복사 표현을
15
+ 기존 API에 합성했다. 터미널 에뮬레이터·명령 실행기·구문 분석기·서버 스트리밍 엔진이 아니다.
16
+ 자동 타이핑은 정확한 전체 원문과 복사 계약을 대신하지 않는다. 해당 기능은 별도 lifecycle/읽기 검토 대상이다.
17
+
18
+ ## 구성 요소
19
+
20
+ | 컴포넌트 | 역할 | 지침 |
21
+ | --- | --- | --- |
22
+ | Provider | 10개 테마 표현 상속 | [프로필](../../design-profile.md) |
23
+ | Container·Section·Stack | 제목·세로 흐름·폭 | [컨테이너](../components/container.md) · [구역](../components/section.md) · [스택](../components/stack.md) |
24
+ | SegmentedControl | 줄바꿈/가로 스크롤 중 하나 선택 | [선택 입력](../components/segmented-control.md) |
25
+ | Tabs·TabPanel | 명령/출력 두 패널 중 하나 | [탭](../components/tabs.md) |
26
+ | CodeBlock | 선택 가능한 LTR 원문 | [코드 블록](../components/code-block.md) |
27
+ | ClipboardButton | Web 원문 복사와 성공/오류 callback | [코드 블록의 복사 슬롯](../components/code-block.md#최소-사용-예) |
28
+ | Button·Notice·Text | 갱신·진행·실패·원문 선택 안내 | [버튼](../components/button.md) · [알림](../components/notice.md) |
29
+ | Collapsible | 결정적인 Storybook 응답 도구 | [접기](../components/collapsible.md) |
30
+
31
+ ## 배치
32
+
33
+ ```text
34
+ Section 제목/설명 → 현재 테마/다음 테마
35
+ 긴 줄 표시: 줄바꿈 / 가로 스크롤
36
+ 명령 / 출력 탭 → 언어·복사 보조 행동 → 선택 가능한 원문
37
+ 복사 실패 안내 / 닫기
38
+ 갱신 진행 또는 원문을 유지한 실패 알림
39
+ 표시 새로고침(주 행동) → 예제 응답 안내
40
+ 검증 도구: 미리보기 응답 받기 / 다음 갱신 실패
41
+ ```
42
+
43
+ | 영역 | 컴포넌트 | 위치 | 크기·간격 |
44
+ | --- | --- | --- | --- |
45
+ | 바깥 틀 | Container·Section·Stack | 세로 문서 흐름 | Container `gutter="compact"` 16, Stack `spacing.md` 16. Native ScrollView 위아래 `spacing.lg` 20 |
46
+ | 보기 선택 | SegmentedControl | 탭 목록 위 | `presentation="pills"`, 큰 글자에서 줄바꿈 |
47
+ | 패널 | TabPanel·CodeBlock | 탭 목록 아래 | dynamic 단일 패널. CodeBlock의 `radius.lg`·`spacing.md`·프로필 code font, 원문 LTR |
48
+ | 복사 | ClipboardButton | Web CodeBlock 머리 줄 끝 | `tone="secondary" size="small"`. Native는 원문 시스템 선택; 제품 host action이 있으면 같은 슬롯 |
49
+ | 갱신·복구 | Button·Notice | 원문 아래 | 기본 Button 높이 `control.buttonHeight.medium` 44; 진행 중 잠금, 이전 원문 유지 |
50
+
51
+ ## 흐름과 상태
52
+
53
+ 1. 명령 또는 출력을 고른다. manual Tabs의 화살표는 포커스만 움직이고 Enter/Space로 선택한다.
54
+ 2. 긴 줄 표시를 바꾼다. 줄바꿈은 원문을 바꾸지 않고 가로 스크롤은 코드 영역에 한정한다.
55
+ 3. 테마를 바꿔도 현재 패널·보기·원문·갱신 상태를 유지한다. CodeBlock이나 복사 행동을 key로 다시 만들지 않는다.
56
+ 4. Web 복사는 현재 원문 전체를 전달하며 권한 거부를 Notice로 알린다. Native는 시스템 선택 또는 제품이 공급한 복사 host를 쓴다.
57
+ 5. 표시 새로고침 동안 이전 원문을 읽을 수 있다. fixture 응답 실패도 원문을 유지하며 재시도할 수 있다.
58
+
59
+ | 상태 | 모습 | 포커스·알림 |
60
+ | --- | --- | --- |
61
+ | 기본 | 정확한 전체 원문, 두 패널·두 보기 방식 | 탭 manual focus, Web pre focus/Native selectable Text |
62
+ | 진행 중 | 갱신 버튼 loading, 기존 원문·선택 유지 | 응답 도구만 갱신 상태 확정, Web status/Native PatternStatus |
63
+ | 실패 | danger Notice·같은 갱신 재시도, 이전 원문 유지 | Web alert/Native Notice, Web 응답 뒤 갱신 버튼으로 복귀 |
64
+ | 복사 실패 | danger Notice·직접 원문 선택 안내 | 닫기는 안내만 제거하며 성공 아님. Web 닫기 후 복사 버튼으로 복귀 |
65
+ | 복사 성공 | Web ClipboardButton copied | OS 요청 뒤 live status, 진행 중 포커스 유지 |
66
+
67
+ ## 코드 골격
68
+
69
+ ```tsx
70
+ // Web: 원문·출력·갱신 상태는 제품 데이터이며 실행하지 않는다.
71
+ import { Tabs, TabPanel } from "@hjmds/react/navigation";
72
+ import { CodeBlock } from "@hjmds/react/code-block";
73
+ import { ClipboardButton } from "@hjmds/react/clipboard";
74
+ <Tabs id={id} label={tabsLabel} items={items} value={selected} onValueChange={setSelected}
75
+ renderPanels={false} panelMode="dynamic" activationMode="manual" />
76
+ <TabPanel tabsId={id} activeValue={selected} mode="dynamic">
77
+ <CodeBlock code={code} label={sourceLabel} wrap={wrap} copyAction={
78
+ <ClipboardButton value={code} labels={copyLabels} tone="secondary" size="small"
79
+ onCopy={onCopy} onCopyError={onCopyError} />
80
+ } />
81
+ </TabPanel>
82
+ ```
83
+
84
+ ```tsx
85
+ // Native: 선택 가능한 원문은 기존 CodeBlock, OS 복사는 제품 host 경계다.
86
+ import { CodeBlock } from "@hjmds/react-native/code-block";
87
+ <CodeBlock code={code} label={sourceLabel} wrap={wrap} copyAction={productCopyAction} />
88
+ ```
89
+
90
+ ## 플랫폼 차이
91
+
92
+ | 항목 | Web | Native |
93
+ | --- | --- | --- |
94
+ | 복사 | ClipboardButton·OS Promise·live status | 원문 길게 누르기와 시스템 선택. ClipboardButton API 없음 |
95
+ | 원문 | focus 가능한 pre, 긴 줄 자체 scroll | selectable Text, 긴 줄 horizontal ScrollView |
96
+ | 갱신 응답 뒤 포커스 | fixture는 주 갱신 행동으로 복귀 | 기기/AT 검증 별도 |
97
+ | 진행 알림 | Text role=status | Showcase PatternStatus iOS announce/Android live region. 제품은 같은 정책 연결 |
98
+
99
+ ## 함정
100
+
101
+ Showcase fixture를 제품에서 import하지 않는다. 제품은 i18n 문구·원문·허용 데이터·조회/스트리밍
102
+ 정책을 공급한다. 출력 문자열은 실제 CI·배포 성공 증거가 아니다. 비밀값은 제품이 표시 전에 제거한다.
103
+ token.text는 공백까지 원문과 같아야 하며 HTML로 삽입하지 않는다.
104
+ 미게시 Web ClipboardButton 후속 수정은 중복 OS 쓰기를 막고, 원문 변경/언마운트 뒤 이전 결과 callback과
105
+ 성공 표시를 무시한다. 취소할 수 없는 OS 쓰기가 끝날 때까지 loading 잠금을 유지해 다음 쓰기와 순서가
106
+ 뒤집히지 않게 한다. Native 복사 실패 스토리는 제품 host 오류의 표시 fixture이며 실제 OS 거부 재현이 아니다.
@@ -0,0 +1,108 @@
1
+ # 내용 전환 비교
2
+
3
+ - 단계: 구성
4
+ - 상태: 실험
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.14.0 이후)
7
+ - 검토일: 2026-10-07
8
+ - 근거: [Motion 검토](../../../../../docs/qa/2026-10-07-motion-reference-page-review.md), 양 Showcase `content-transition-comparison-preview.tsx`
9
+ - 스토리북: `실험/구성/비교와 검증/내용 전환 비교`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 동일 내용의 전환 표현을 테마와 비교하거나 단계별 입력·완료·복구를 검토할 때 쓴다.
14
+ Motion Primitives Transition Panel의 두 제공 예제를 실제 검토한 후보 단위 등록이다.
15
+ 11개 사이트 전체 조사 완료나 모든 환경 검증을 뜻하지 않는다. 제품은 Showcase를 import하지 않고
16
+ 아래 공개 API에 자기 문구·데이터·완료 callback을 공급한다.
17
+
18
+ ## 구성 요소
19
+
20
+ | 컴포넌트 | 역할 | 지침 |
21
+ | --- | --- | --- |
22
+ | Heading | 비교 구역의 의미 있는 제목 | [제목](../components/heading.md) |
23
+ | Provider | 10개 테마의 내용 전환 기본값 상속 | [프로필 계약](../../design-profile.md) |
24
+ | Tabs·TabPanel | 수동 활성화와 선택 패널의 의미 | [탭](../components/tabs.md) |
25
+ | ContentTransition | 현재 본문 하나의 등장과 높이 전환 | [내용 전환](../components/content-transition.md) |
26
+ | OnboardingScreen | 이전·다음·마지막 완료와 진행 표시 | [안내 화면](../components/onboarding-screen.md) |
27
+ | TextField | 제품 상태에 보관한 같은 초안 | [텍스트 입력](../components/field.md) |
28
+ | SegmentedControl | 한 번에 하나의 전환 표현 선택 | [선택 입력](../components/segmented-control.md) |
29
+ | Button·Notice | 표현 선택·실패 예약·복구 결과 | [버튼](../components/button.md) · [알림](../components/notice.md) |
30
+
31
+ ## 배치
32
+
33
+ ```text
34
+ 설명 → 현재 테마/다음 테마 → 전환 표현(줄바꿈)
35
+ 탭 비교: 탭 목록 → 현재 패널 → 제목/내용/같은 초안
36
+ 단계 비교: 안내 제목/설명/진행 → 본문 전환/초안 → 고정 이전·다음·완료
37
+ 마지막 단계: 실패 재현 예약 → 실패 알림 → 같은 완료로 재시도
38
+ 완료: 확인 알림 → 초안 → 다시 비교
39
+ ```
40
+
41
+ | 영역 | 컴포넌트 | 위치 | 크기·간격 |
42
+ | --- | --- | --- | --- |
43
+ | 바깥 틀 | Stack | 세로 | `spacing.lg` 20 |
44
+ | 표현 선택 | SegmentedControl | 안내 아래 | 공개 `presentation="pills"`, 큰 글자·좁은 화면 줄바꿈 |
45
+ | 탭 본문 | TabPanel·ContentTransition·Stack | 탭 목록 아래 | `spacing.md` 16, 높이는 현재 내용 측정 |
46
+ | 단계 틀 | OnboardingScreen | 표현 선택 아래 | Web preview 70dvh/minHeight 360, Native preview 720. 이는 fixture viewport이며 제품은 실제 가용 높이 사용 |
47
+ | 주 행동 | OnboardingScreen footer | 본문 scroll 바깥 | [기존 안내 배치](../components/onboarding-screen.md#배치) |
48
+
49
+ ## 흐름과 상태
50
+
51
+ 1. 초안을 입력하고 탭을 바꾼다. Tabs는 수동 활성화여서 Web 화살표는 포커스만 이동하고 Enter/Space로 선택한다.
52
+ 2. 테마를 순회하거나 나타남·떠오름·옆으로·확대·즉시를 선택한다. `preset` 생략은 테마 기본값, 명시 값은 우선한다.
53
+ 3. 단계별 완료 스토리에서 다음·이전으로 이동한다. 화면 전체를 key로 다시 마운트하지 않고 본문만 전환한다.
54
+ 4. 마지막 단계에서 완료 실패 재현을 예약하고 기록 확인을 누른다. 초안을 유지한 실패 알림 뒤 같은 완료를 다시 누른다.
55
+ 5. 확인 결과와 초안을 보고 다시 비교한다. 완료는 로컬 fixture이며 서버 저장·라우팅 완료로 안내하지 않는다.
56
+
57
+ | 상태 | 모습 | 포커스·알림 |
58
+ | --- | --- | --- |
59
+ | 기본 | 선택한 내용 하나·같은 초안 | 탭/본문 이름 연결, 이전 내용을 복제하지 않음 |
60
+ | 진행 중 | 현재 내용 등장/높이 전환 | 화면 제목·탭 목록·footer는 전환 바깥. 모션 감소·즉시는 즉시 반영 |
61
+ | 실패 | 마지막 단계의 danger Notice | 초안 보존, Web alert·Native assertive; 완료 버튼으로 재시도. Web 성공 뒤 다시 비교에 포커스 |
62
+ | 성공 | success Notice·확인한 초안 | 다시 비교 가능; 서버 성공을 의미하지 않음 |
63
+
64
+ ## 코드 골격
65
+
66
+ ```tsx
67
+ // Web: 선택 상태와 초안은 전환 바깥의 제품 상태다.
68
+ import { Tabs, TabPanel } from "@hjmds/react/navigation";
69
+ import { ContentTransition } from "@hjmds/react/content-transition";
70
+ <Tabs id={id} label={label} items={items} value={value} onValueChange={setValue}
71
+ activationMode="manual" panelMode="dynamic" renderPanels={false} />
72
+ <TabPanel tabsId={id} activeValue={value} mode="dynamic">
73
+ <ContentTransition stateKey={value} animateHeight>{body}</ContentTransition>
74
+ </TabPanel>
75
+ ```
76
+
77
+ ```tsx
78
+ // Native: label로 현재 패널 이름도 공급한다. 완료/오류는 제품이 소유한다.
79
+ import { TabPanel } from "@hjmds/react-native/navigation";
80
+ import { ContentTransition } from "@hjmds/react-native/content-transition";
81
+ import { OnboardingScreen } from "@hjmds/react-native/screen-flows";
82
+ <TabPanel tabsId={id} activeValue={value} label={currentLabel} mode="dynamic">
83
+ <ContentTransition stateKey={value} animateHeight>{body}</ContentTransition>
84
+ </TabPanel>
85
+ <OnboardingScreen steps={steps} index={index} onIndexChange={setIndex}
86
+ nextLabel={nextLabel} backLabel={backLabel} complete={complete}
87
+ progressLabel={progressLabel} />
88
+ ```
89
+
90
+ ## 플랫폼 차이
91
+
92
+ | 항목 | Web | Native |
93
+ | --- | --- | --- |
94
+ | 표현 엔진 | optional framer-motion·높이 측정 | core Animated·높이는 opt-in JS driver |
95
+ | 전환 본문 | 키가 바뀌면 내부 subtree remount | 현재 children 하나 유지. 실기기 포커스/AT 확인 별도 |
96
+ | 안내 배치 | 제목·설명·진행은 body scroll 위 | 안내도 body 안 scroll, footer 고정 |
97
+ | 포커스 | 탭 동작 유지. 본문 내부 행동이 본문을 교체하면 공개 focusTarget 검토 | focusTarget 없음; 같은 동작이라고 추정하지 않음 |
98
+
99
+ ## 함정
100
+
101
+ 동적 패널의 입력은 controlled 제품 상태로 보존한다. DOM identity가 필요한 입력은 전환 바깥에
102
+ 두거나 keyed/visited Tabs를 쓴다. 원본의 exit 복제·이름 없는 탭 버튼·마지막 Close의 빈 callback은
103
+ 복사하지 않는다. 무거운 내용과 실제 키보드·스크린리더·Native 기기의 높이 전환 검증은 별도다.
104
+
105
+ 2026-10-07 로컬 토큰 감사에서 Web preview 최소 높이360이 raw-length로 검출됐다.
106
+ 이는 짧은 viewport에서도 body scroll과 footer를 함께 검증하려는 fixture 한계값이며
107
+ 제품의 spacing/width 토큰을 높이로 전용하지 않는다. token-boundary-exceptions.json에
108
+ 해당 selector·minHeight·360만 한정해 등록했다. 제품 host는 실제 가용 높이를 공급한다.