@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
  - 근거: recipe `fieldRecipe`(`src/base-recipes.ts`), GestureSheetInput: [Optional presentation adapters](../../optional-adapters.md)
9
9
  - 스토리북: `배포/컴포넌트/입력/입력 필드`
10
10
 
@@ -84,6 +84,8 @@ import { Field } from "@hjmds/react-native/forms";
84
84
 
85
85
  ## 배치
86
86
 
87
+ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력 host에 연결한다. 기본 UI stack은 OS 서체를 유지하고, 제품이 지정한 첫 named font의 등록·글리프 확인은 제품이 맡는다.
88
+
87
89
  | 항목 | 값 | 근거 |
88
90
  | --- | --- | --- |
89
91
  | 크기 | 컨트롤 최소 높이 44(`control.minTouchTarget`), 안쪽 여백 좌우 `spacing.md`(16)·위아래 `spacing.sm`(12), 테두리 1. 여러 줄 입력은 최소 80(`fieldRecipe.multilineMinHeight`)이고 `minVisibleLines`를 줘도 이 하한 아래로 내려가지 않는다(한 줄 시작은 MessageComposer 내부만). 폭은 부모를 채운다 | `base-recipes.ts` `fieldRecipe`, `styles.css` `.hjm-field__control` |
@@ -127,3 +129,26 @@ TextField와 같은 테두리·글꼴·placeholder 색을 입힌다. 라벨·도
127
129
  - Native `TextField`·`Field`의 `error`·`description`은 `string`이라 `exactOptionalPropertyTypes`에서 `undefined`를 받지
128
130
  않는다(TS2375). 위 예처럼 조건부 spread로 넘긴다. Web은 `ReactNode`라 `undefined`를 그대로 넘겨도 된다.
129
131
  - Web `Field`는 `controlId`가 필수이고, Native `Field`에는 `controlId`가 없다. 공용 코드에서 같은 props 객체를 넘기지 않는다.
132
+
133
+ ### 날짜 조각 직접 입력 (실험·미게시)
134
+
135
+ 알고 있는 날짜를 년·월·일로 직접 편집하려면 `DateEntry`를 사용한다.
136
+ Web `@hjmds/react/date-entry`, Native `@hjmds/react-native/date-entry`의 Field 확장이다.
137
+ 원문 초안·입력 순서·오류 대상을 공유하며 입력은 기존 TextField로 렌더링한다.
138
+ [날짜 직접 입력 지침](../compositions/date-entry.md)에 배치·props·날짜 파싱 소유권이 있다.
139
+ 달력에서 날짜를 고르는 경우에는 기존 DatePicker를 쓴다.
140
+
141
+
142
+ ### 그룹 오류를 한 번 표시할 때
143
+
144
+ Web TextField의 `aria-invalid={true}`는 외부 오류 ID를 `aria-describedby`로 연결하는 경우에도
145
+ 오류 테두리를 표시한다. Native TextField/TextArea의 `invalid`는 인라인 error 문구 없이 오류
146
+ 테두리를 표시하는 미게시 옵션이다. 같은 오류를 `accessibilityHint`로 연결하고 그룹 안내를
147
+ 별도로 보여 준다. error가 있으면 해당 문구가 hint보다 우선하고, hint가 없으면 description을 쓴다.
148
+ 2026-10-07 날짜 직접 입력의 큰 글자 오류가 세 번 반복된 관찰에 따라 이 경로를 추가했다.
149
+
150
+ ### 관련 입력 그룹 (실험·미게시)
151
+
152
+ 여러 입력이 한 질문에 답하면 `FieldGroup`을 사용한다. Form의 제출 경계와 구분하며
153
+ `@hjmds/react/field-group`, `@hjmds/react-native/field-group`에서 제공한다.
154
+ [관련 입력 묶음 지침](../compositions/field-group.md)의 renderField 연결·오류·잠금 규칙을 따른다.
@@ -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
  - 근거: [Form](../../form.md), recipe `formRecipe`(`src/form.ts`)
9
9
  - 스토리북: `배포/컴포넌트/입력/입력 양식`
10
10
 
@@ -14,6 +14,11 @@
14
14
  **제출 세션**(중복 제출 차단, 진행 중 잠금, 폼 단위 오류 표시)만 소유한다. 값·검증·dirty 판단은
15
15
  제품(React Hook Form 등)이 소유한다.
16
16
 
17
+ Form은 주소·연락처처럼 관련 입력을 이름 붙여 묶는 일반 fieldset API가 아니다.
18
+ Web 내부 fieldset은 제출 중 잠금을 위한 것이며 그룹 legend를 제공하지 않는다.
19
+ 그룹 제목을 만들려고 Form을 중첩하지 않는다. 일반 관련 입력은 [FieldGroup](../compositions/field-group.md) 실험에서 제공한다. 2026-10-07 GOV.UK 주소 그룹과 대조해
20
+ 제출 경계와 입력 그룹을 구분했다. 선택 묶음은 CheckboxGroup/RadioGroup, 날짜 부분 입력은 DateEntry를 사용한다.
21
+
17
22
  ## 쓰지 않을 때
18
23
 
19
24
  | 상황 | 대신 쓸 것 |
@@ -22,14 +27,19 @@
22
27
  | 한 번 확인하고 닫히는 위험 행동 | [AlertDialog](alert-dialog.md) |
23
28
  | 필드 하나의 라벨·도움말·오류 프레임 | [Field](field.md) |
24
29
  | 약관 동의 묶음 | [Agreement](agreement.md) |
25
- | 하단 고정 제출 버튼이 필요한 긴 화면 | Web: Form 안의 필드 + [BottomCTA](bottom-cta.md)(`actions`는 비운다). Native: Form은 내장 제출 버튼을 항상 그리므로 Form 없이 필드 + BottomCTA로 제품이 제출을 소유한다 |
26
- | Native에서 return 키로 제출해야 하는 폼(로그인·심사자 폼) | Form 없이 `TextField`(ref) + [Button](button.md)(`loading`). Native Form은 밖에서 제출을 부를 수 없다 |
30
+
31
+ 하단 고정 행동·Native return 키 제출도 기존 Form을 사용한다. Native는 `actions={null}`과
32
+ `ref.current?.submit()`으로 같은 제출 경로를 호출하고 [배치](#배치)의 loading 연결을 따른다.
33
+ 2026-10-07 참고 폼 조사에서 초기 선택 표가 이후 배치 안내와 모순된 것을 확인해 오래된
34
+ "내장 버튼 숨김·외부 제출 불가" 안내를 제거했다. Native 1.14.0 게시 타입의 FormHandle/actions/ref를
35
+ 직접 대조했으며 새 폼 엔진을 제품에 복제할 이유가 없다. FormHandle·actions는 1.14.0부터다.
27
36
 
28
37
  ## 공개 이름과 import
29
38
 
30
39
  | 이름 | 역할 | Web | Native |
31
40
  | --- | --- | --- | --- |
32
41
  | `Form` | 기본 | `@hjmds/react`, `/forms` | `@hjmds/react-native`, `/forms` |
42
+ | `FormHandle` | 같은 Native 제출을 밖에서 호출하는 ref 타입(1.14.0부터) | — | `/forms` |
33
43
  | `createFormSubmitSession` | 보조 — 제출 결과를 값으로 받거나 언마운트 때 정산해야 할 때 쓰는 세션 | `@hjmds/design-contracts/components/form` | 같음 |
34
44
  | `resolveFirstInvalidFieldFocusTarget` | 보조 — 필드 순서와 무효 id로 첫 오류 필드를 고른다 | `@hjmds/design-contracts/components/form` | 같음 |
35
45
 
@@ -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
  - 근거: [Heading](../../heading.md), recipe `headingRecipe`(`src/heading.ts`)
9
9
  - 스토리북: `배포/컴포넌트/글자와 아이콘/제목`
10
10
 
@@ -53,6 +53,12 @@ import { Heading } from "@hjmds/react-native/heading";
53
53
  | `layoutStyle` | 배치 key(margin·폭·정렬 등) | — | 바깥 배치만. 크기·줄 높이·굵기·색은 받지 않는다 |
54
54
 
55
55
  - 범위 밖 값은 `TypeError`로 거부된다.
56
+ - 위 크기는 프로필 없는 기본값이다. [디자인 프로필](../../design-profile.md)의
57
+ `tokens.heading.level1`~`level5`로 다섯 시각 단계를 지정하면 양 renderer가 따르며,
58
+ `semanticLevel`은 그대로다. 크기를 맞추려고 문서 단계를 바꾸지 않는다.
59
+ - `defineHjmDesignProfile`에서 `tokens.typography.heading/titleLarge/title`을 바꾸면 기존
60
+ level3/4/5 연결을 유지한다. 같은 단계에 `tokens.heading`도 지정하면 명시한 heading
61
+ 값이 우선한다. 큰 제목 level1/2는 본문 크기에서 자동 추정하지 않는다.
56
62
 
57
63
  ## 배치
58
64
 
@@ -0,0 +1,82 @@
1
+ # ImageComparison
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.14.0
7
+ - 검토일: 2026-10-07
8
+ - 근거: [계약](../../reference-controls.md)
9
+ - 스토리북: `배포/컴포넌트/데이터 표시/이미지 전후 비교`
10
+
11
+ 승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
12
+
13
+ ## 언제 쓰나
14
+
15
+ 같은 좌표와 비율의 두 이미지를 겹쳐 변화량을 비교할 때 쓴다.
16
+
17
+ ## 쓰지 않을 때
18
+
19
+ | 상황 | 대신 쓸 것 |
20
+ | --- | --- |
21
+ | 서로 다른 장면의 목록 | [Image](image.md)와 [Grid](grid.md) |
22
+
23
+ ## 공개 이름과 import
24
+
25
+ | 이름 | 역할 | Web | Native |
26
+ | --- | --- | --- | --- |
27
+ | `ImageComparison` | 독립 supplemental | `@hjmds/react/image-comparison` | `@hjmds/react-native/image-comparison` |
28
+
29
+ ## 최소 사용 예
30
+
31
+ ```tsx
32
+ // Web
33
+ import { ImageComparison } from "@hjmds/react/image-comparison";
34
+ <ImageComparison label={label} before={before} after={after} value={percentage} onValueChange={setPercentage} getValueText={formatPercentage} />
35
+ ```
36
+
37
+ ```tsx
38
+ // Native
39
+ import { ImageComparison } from "@hjmds/react-native/image-comparison";
40
+ <ImageComparison label={label} before={before} after={after} value={percentage} onValueChange={setPercentage} getValueText={formatPercentage} decrementLabel={decrementLabel} incrementLabel={incrementLabel} />
41
+ ```
42
+
43
+ Native는 import를 `@hjmds/react-native/image-comparison`로 바꾼다.
44
+
45
+ ## 축과 기본값
46
+
47
+ | prop | 값 | 기본값 | 설명 |
48
+ | --- | --- | --- | --- |
49
+ | `value` | number | 필수 | value는 0~100. before/after는 src·width·height·label을 가지며 두 비율이 같아야 한다. |
50
+ | Web `layoutStyle` | 배치 전용 style | 없음 | 바깥 틀의 폭·margin·flex 배치 |
51
+ | `disabled` | boolean | false | 변경을 막고 현재 값을 유지 |
52
+
53
+ ## 배치
54
+
55
+ | 항목 | 값 | 근거 |
56
+ | --- | --- | --- |
57
+ | 크기 | 바깥 폭을 채우고 원본 aspect ratio로 높이를 정한다. 두 이미지는 같은 전체 크기이며 before만 잘린다. | renderer `image-comparison.tsx` |
58
+ | 간격 | 위 라벨 사이 space.sm=12px. 실제 조작 Slider는 이미지 바로 아래에 둔다. | foundations spacing |
59
+ | 순서·정렬 | 전후 라벨→이미지→비율 조절 | renderer 순서 |
60
+ | 고정·스크롤 | 바깥 화면이 스크롤을 소유한다 | 별도 스크롤 없음 |
61
+ | 좁은 폭·큰 글자 | 폭을 강제 고정하지 않고 본문/행의 줄바꿈을 허용한다 | 위 배치 |
62
+
63
+ ## 꼭 지킬 것
64
+
65
+ - 이미지 경계는 별도 조작 엔진이 아니다. before 비율은 물리적 왼쪽 기준이며 RTL에서도 이미지 의미를 뒤집지 않는다. Native에는 decrementLabel/incrementLabel도 제품 언어로 전달한다.
66
+ - 브랜드는 HJM provider의 semantic palette로 연결한다. 점수 집계·이미지 변환·저장은 제품 소유다.
67
+
68
+ ## 플랫폼 차이
69
+
70
+ | 항목 | Web | Native |
71
+ | --- | --- | --- |
72
+ | 조작 | Web은 Slider의 range/키보드, Native는 Slider의 adjustable 및 증감 버튼을 그대로 사용한다. | 같은 의미를 플랫폼 host로 번역 |
73
+
74
+ ### 이미지 host와 fixture
75
+
76
+ 2026-10-07 iOS Expo Go에서 SVG data URI가 오류 fallback을 표시했지만 슬라이더는 정상
77
+ 동작했다. 예제는 `scripts/generate-comparison-fixtures.py`로 같은 좌표의 PNG를 생성해
78
+ 양쪽 기본 이미지 host에서 표시한다. 값 변경·AX 이미지 이름만으로 이미지 로딩 성공을
79
+ 판정하지 않는다. 제품은 지원되는 실제 자산 형식과 로딩/실패 화면을 확인한다.
80
+
81
+ RTL에서도 보정 전 라벨은 왼쪽, 보정 후는 오른쪽이다. 슬라이더와 문구 쓰기 방향은
82
+ 제품 언어를 따르며 이미지 경계의 물리적 의미와 구분한다.
@@ -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
  - 근거: [Image](../../image.md), [GridReveal](../../grid-reveal.md), [ImageViewer](../../optional-adapters.md#behavior-boundaries), recipe `imageRecipe`(`src/image.ts`)
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/이미지` · `배포/컴포넌트/시각 효과/격자 등장 효과`
10
10
 
@@ -82,12 +82,17 @@ import { ImageViewer } from "@hjmds/react-native/image-viewer";
82
82
  | ImageViewer `items` | `id`·`uri`·`label` | — | 라벨 6종, `safeAreaInsets`가 필수 |
83
83
  | ImageViewer `initialIndex` | 0 이상 정수 | `0` | — |
84
84
  | ImageViewer `onClose` · `onIndexChange` | `() => void` · `(index: number) => void` | `onClose` 필수 | — |
85
- | ImageViewer `safeAreaInsets` | `{ top: number; bottom: number }` | 필수 | — |
85
+ | ImageViewer `safeAreaInsets` | `{ top: number; bottom: number; left?: number; right?: number }` | 필수, 좌우 0 | 버튼·caption·상태 안내를 물리적 좌우 안전 영역 안에 배치. 사진은 전체 갤러리 폭 사용 |
86
+ | ImageViewer `supportedOrientations` | RN Modal의 orientation 배열 | RN 기본값 | 제품 manifest·기기 회전 잠금 범위 안에서 허용. Modal은 fullScreen |
87
+ | ImageViewer `renderImage` | `(props: ImageViewerImageRenderProps) => ReactNode` | RN Image | Native 전용, 미게시. `item`, 측정된 `width`·`height`, `onReady`·`onError`를 전달. 제품 이미지 host의 캐시·표시 이벤트를 연결 |
88
+ | ImageViewer `onImageStatusChange` | `({ item, status }) => void` | 없음 | `loading`·`ready`·`error`. 마운트된 각 이미지 기준이며 비선택 페이지도 포함할 수 있음 |
86
89
 
87
90
  - 실패하면 중립 배경 위에 오류 기호를 그리고, 정보 이미지의 이름은 그대로 유지한다. `fallback`은 시각만 바꾼다.
88
91
 
89
92
  ## 배치
90
93
 
94
+ Native 이미지 프레임은 `imageRecipe.radius` 역할을 Provider의 `tokens.radius`에서 읽는다. intrinsic 치수·대체 상태·접근성 구분은 유지한다.
95
+
91
96
  | 항목 | 값 | 근거 |
92
97
  | --- | --- | --- |
93
98
  | 크기 | 자리는 `width`·`height` 비율로 미리 잡는다(Web `aspect-ratio`, Native `aspectRatio`). 로드 전후로 높이가 바뀌지 않는다. 모서리는 `radius.md` 12로 잘린다(`imageRecipe.radius`) | `design-contracts/src/image.ts`(`imageRecipe`), `design-contracts/src/foundations.ts`(`radius`), `react/src/supplemental-display.tsx`(Image) |
@@ -105,6 +110,82 @@ import { ImageViewer } from "@hjmds/react-native/image-viewer";
105
110
  - ImageViewer는 불러오는 중(`loadingLabel`)과 실패(`errorLabel`)를 모두 알린다. 실패는 Android assertive live region, iOS는 `announceForAccessibility`다(미게시(1.12.1 이후). 1.12.1은 실패를 알리지 않았다).
106
111
  - 원격 이미지 권한·URL 수명·캐시는 제품 소유다. HJM은 메타데이터를 가져오지 않는다.
107
112
 
113
+ ### Native ImageViewer의 제품 이미지 호스트
114
+
115
+ Utilverse의 사진 결과 확인은 Expo `onDisplay`에 의존한다(ADR-0020). RN Image `onLoad`를
116
+ 그대로 표시 완료로 간주하지 않도록 기존 optional ImageViewer에 host 슬롯을 추가했다.
117
+ HJM에 Expo 의존성을 넣거나 별도 갤러리를 복제하지 않는다. 아래는 Expo를 이미 쓰는 제품의 연결 예다.
118
+
119
+ ```tsx
120
+ import { Image as ExpoImage } from "expo-image";
121
+ import { ImageViewer } from "@hjmds/react-native/image-viewer";
122
+
123
+ <ImageViewer {...viewerProps}
124
+ renderImage={({ item, width, height, onReady, onError }) => (
125
+ <ExpoImage source={{ uri: item.uri }} cachePolicy="none" contentFit="contain"
126
+ accessibilityLabel={item.label} style={{ width, height }}
127
+ onDisplay={onReady} onError={onError} />
128
+ )}
129
+ onImageStatusChange={({ item, status }) => recordImageStatus(item.id, status)}
130
+ />
131
+ ```
132
+
133
+ `viewerProps`는 위의 open/items/라벨/inset/닫기 props다. host는 이미지의 접근성 이름과
134
+ 크기를 연결하고 상태 문구·재시도 버튼을 다시 만들지 않는다. 재시도는 host를 새로 마운트한다.
135
+ 이전 시도의 이벤트와 닫힌 세션의 이벤트는 무시하며 오류는 재시도 전까지 유지한다.
136
+ `ready`는 연결한 host 이벤트의 의미일 뿐이다. 기본 경로는 계속 RN onLoad이므로 실제 표시나
137
+ 사용자의 검토 완료를 뜻하지 않는다. 상태 통지는 렌더링된 페이지마다 발생하므로 현재 선택·
138
+ 열림·결과 URI·보기 모드·사용자 확인 조건은 제품이 결합해야 한다. 닫을 때 별도의 상태 이벤트를
139
+ 보내지 않는다. 제품은 닫기/교체에서 검토를 무효화한다. host 변경만으로 세션이 새로 열리지 않는다.
140
+ 회전을 허용하는 제품은 `supportedOrientations={["portrait", "landscape"]}`와 갱신되는
141
+ safeAreaInsets 네 방향을 제공한다. 앱 manifest가 portrait 고정이면 이 prop만으로 회전이 보장되지 않는다.
142
+ 화면 크기가 바뀌면 버튼을 제외한 남은 갤러리 영역을 다시 측정해 host에 전달한다.
143
+ 아래 inspection 확장은 미게시이며, Expo 표시 확인·결과 승인 회귀와 Utilverse 채택은 별도 검증한다.
144
+
145
+ ### 결과 검사 크기 계산
146
+
147
+ `@hjmds/design-contracts/components/image`의 `resolveImageInspectionGeometry`는 원본과
148
+ 실측 viewport 크기, `fit | double | pixels`를 받아 scale·width·height·panBounds를 반환한다.
149
+ fit은 전체가 들어가는 크기, double은 fit의 2배, pixels는 원본 수치와 같은 layout 단위다.
150
+ 기기의 물리 pixel 배율을 추정하지 않는다. 0 크기는 측정 전 상태이므로 호출을 미룬다.
151
+
152
+ ```ts
153
+ import { resolveImageInspectionGeometry } from "@hjmds/design-contracts/components/image";
154
+
155
+ const geometry = resolveImageInspectionGeometry(
156
+ { width: 600, height: 600 }, { width: 402, height: 454 }, "double",
157
+ ); // width/height 804, panBounds x=201 y=175 (중앙에서 양방향)
158
+ ```
159
+
160
+ ### Native 결과 검사 보기 (미게시)
161
+
162
+ `ImageViewer`의 선택적 `inspection`은 위 계산을 사용한다. 모든 `items`에 실제 출력의
163
+ 양수 `width`·`height`를 제공한다. 일반 Gallery의 pinch/paging과 별개로 고정 배율의 결과를
164
+ 검사하는 용도다. `inspection`을 생략하면 기존 Gallery 동작을 유지한다.
165
+
166
+ ```tsx
167
+ <ImageViewer {...viewerProps}
168
+ items={[{ id: "result", uri: outputUri, label: resultLabel, width: outputWidth, height: outputHeight }]}
169
+ inspection={{
170
+ mode, onModeChange: next => { invalidateReview(); setMode(next); },
171
+ labels: { mode: "결과 보기", fit: "맞춤", double: "2배", pixels: "출력 크기",
172
+ left: "왼쪽", right: "오른쪽", up: "위", down: "아래", center: "중앙" },
173
+ getPositionText: ({ x, y }) => `위치 ${Math.round(x)}, ${Math.round(y)}`,
174
+ }} />
175
+ ```
176
+
177
+ 문구는 예시이며 제품 i18n에서 공급한다. 닫기 아래 SegmentedControl, 남은 이미지 viewport,
178
+ 방향/중앙 버튼과 위치 안내, 이미지 설명 순으로 배치한다. 큰 글자에서는 선택·방향 버튼이
179
+ 줄바꿈되며 그 아래 실제 남은 viewport로 배율을 다시 계산한다. `renderImage`의 width/height는
180
+ 이 모드에서 **이미지의 표시 크기**이며 viewport보다 클 수 있다.
181
+
182
+ - fit은 전체 맞춤, double은 fit의 2배, pixels는 출력 수치와 같은 layout 단위다. 물리 기기 pixel의 1:1 decode를 보장하지 않는다.
183
+ - 드래그 또는 방향 버튼으로 양축 끝까지 이동한다. 버튼은 viewport의 80%씩 이동해 20% 문맥을 유지하며 물리 방향은 RTL에서도 뒤집지 않는다. 넘침이 없는 축은 비활성이다.
184
+ - 확대/축소 pinch·double tap과 swipe 페이지 전환은 사용하지 않는다. 여러 이미지는 이전/다음 버튼으로 전환하고 한 장이면 두 버튼을 숨긴다.
185
+ - 모드·viewport·이미지·재시도가 바뀌면 중앙에서 새 host를 열고 loading부터 시작한다. 폐기된 host/제스처 콜백은 새 상태를 바꾸거나 위치를 알리지 않는다.
186
+ - 위치는 이미지 중앙에서 본 viewport의 x/y 오프셋과 maxX/maxY 한계다. `getPositionText`는 비어 있지 않은 지역화 문자열을 반환한다. 화면에서는 한 줄로 제한해 위치 문구 변화가 viewport를 바꾸지 않게 하고 접근성 이름은 전체를 제공한다.
187
+ - 위치 알림은 Android live region, iOS 완료된 이동의 announce API를 사용한다. 실제 스크린리더 검증은 남아 있다. 제품의 표시 완료/수동 확인/내보내기 승인 계약은 기존 host 절대로 유지한다.
188
+
108
189
  ## 플랫폼 차이
109
190
 
110
191
  | 항목 | Web | Native |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [Native platform](../../native-platform.md#키보드-회피), 판정 `resolveKeyboardInset`(`src/native-platform.ts`)
9
9
  - 스토리북: 없음
10
10
 
@@ -14,6 +14,11 @@
14
14
  키보드가 뜨면 측정한 높이만큼 아래 여백을 늘리고, 내려가면 safe area 여백만 남긴다.
15
15
  `react-native-keyboard-controller`를 설치하지 않은 앱의 기본 선택이다(Native 전용, 별도 보조 기능).
16
16
 
17
+ 이 컴포넌트는 keyboard event의 높이를 사용하며 wrapper의 window 위치와 실제 겹침은
18
+ 측정하지 않는다. 2026-10-07 Utilverse의 측정 기반 host 대조에서 이 차이를 확인했다.
19
+ 이미 adjustResize나 제품 host가 겹침을 처리하면 중첩하지 말고, safe area·하단 독·회전에서
20
+ 같은 여백이 두 번 적용되지 않는지 확인한 뒤 교체한다.
21
+
17
22
  ## 쓰지 않을 때
18
23
 
19
24
  | 상황 | 대신 쓸 것 |
@@ -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
  - 근거: [Link](../../link.md), recipe `linkRecipe`(`src/component-recipes.ts`), 목적지 검증 `src/link.ts`
9
9
  - 스토리북: `배포/컴포넌트/동작/링크`
10
10
 
@@ -95,6 +95,8 @@ const renderGlyph = createLucideGlyph({ chevronEnd: ChevronRight }); // 제품
95
95
 
96
96
  ## 꼭 지킬 것
97
97
 
98
+ - Web 파일 다운로드는 실제 `href`와 anchor `download` 속성을 사용한다. 파일 설명·형식·크기는 제품이 정확한 현지화 문구로 제공한다. Native Link는 탐색 계약이므로 파일 저장·공유 작업의 완료나 실패를 자동 처리하지 않는다. 2026-10-07 Nucleus/TFWM 파일 사례 대조에서 탐색·다운로드·업로드를 같은 상태로 분류하지 않기 위해 이 경계를 명시했다.
99
+
98
100
  - 라벨은 i18n 키로 넣는다. 접근성 이름을 따로 줄 때도 보이는 문구를 포함한다.
99
101
  - navigation을 `onClick`/`onPress` callback으로 대신하지 않는다. Web은 실제 `href`를, Native는 `destination`을 둔다.
100
102
  - 앞뒤 아이콘은 장식이다. 이름은 링크 문구가 소유한다.
@@ -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
  - 근거: [ListRow](../../list-row.md), recipe `listRowRecipe`(`src/component-recipes.ts`)
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/목록 행`
10
10
 
@@ -77,6 +77,8 @@ import { Avatar, ListRow } from "@hjmds/react-native/data-display";
77
77
 
78
78
  ## 배치
79
79
 
80
+ Native 원형 leading의 `full`은 Provider token에서 읽지만 고정 원형 역할을 유지한다. square leading은 모서리를 부여하지 않는다.
81
+
80
82
  | 항목 | 값 | 근거 |
81
83
  | --- | --- | --- |
82
84
  | 크기 | 폭은 부모를 가득 채운다(Web `inline-size: 100%`). leading 프레임은 40×40(`leadingSize`), `circle`이면 `radius.full`. trailing 아이콘은 `glyph.sm` 20. 최소 높이는 아래 density 표 | `design-contracts/src/component-recipes.ts`(`listRowRecipe`), `design-contracts/src/foundations.ts`(`layout.rowHeight`) |
@@ -102,6 +104,8 @@ import { Avatar, ListRow } from "@hjmds/react-native/data-display";
102
104
 
103
105
  ## 꼭 지킬 것
104
106
 
107
+ - Web `href`는 탐색 링크이며 `download` prop은 없다. 다운로드 속성이 필요한 파일은 [Link](link.md)를 사용한다. 큰 미리보기와 다운로드·메뉴 등 독립 행동은 Card로 구성하며 클릭 가능한 행 안에 링크를 중첩하지 않는다. 2026-10-07 파일 사례 대조에서 파일 행의 외형만으로 다운로드 지원을 추론한 대응표를 바로잡았다.
108
+
105
109
  - 제목·설명은 i18n 키로 넣는다. leading의 사진·아이콘은 장식으로 두고 의미는 제목이 말한다.
106
110
  - 행 안에 다른 버튼을 넣지 않는다. Native는 별도 target을 `trailingAction`에 둔다(행 명령 옆에 따로 그린다).
107
111
  - 배치는 `layoutStyle`로 한다. 높이·여백·배경을 덮지 않는다. 밀도는 `density`로 바꾼다.
@@ -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
  - 근거: recipe `listRecipe`(`src/component-recipes.ts`), TaskList 계약: [Task list](../../task-list.md)
9
9
  - 스토리북: `배포/컴포넌트/데이터 표시/목록` · `배포/컴포넌트/입력/할 일 목록`
10
10
 
@@ -77,6 +77,7 @@ import { Text } from "@hjmds/react-native/primitives";
77
77
  | TaskList `items` | `readonly TaskItem[]` — `{ id, label, completed, description?, disabled? }` | — | `id`·`label`이 비거나 `id`가 중복되거나 `completed`가 boolean이 아니면 `TypeError` |
78
78
  | TaskList `onCompletedChange` | `(id: string, completed: boolean) => void` | — | 필수. 체크 하나마다 한 번. 목록 순서·저장은 제품이 정한다 |
79
79
  | TaskList `disabled` | `true` · `false` | `false` | 전체 체크박스를 끈다 |
80
+ | TaskList `renderItemAction` | `({ item, disabled }) => ReactNode` | — | 체크 아래 별도 행동. disabled는 전체/항목 잠금의 합이며 제품 버튼에 전달한다. 간격 spacing.sm 12, 체크 안에 중첩하지 않음 |
80
81
  | TaskList `emptyContent` | ReactNode | — | `items`가 비면 `List` 안에 그린다 |
81
82
  | TaskList `renderCollection` | `(context: { items, renderItem: (item: TaskItem) => ReactNode }) => ReactNode` | — | SortableCollection을 합성할 수 있다. 이때 목록 루트는 제품이 그린다 |
82
83
  | TaskList `layoutStyle`(Web) | 배치 key만 | — | List 루트 배치. `renderCollection`을 쓰면 무시되고 제품 루트를 배치한다. Native TaskList는 `layoutStyle`이 없다 |
@@ -85,6 +86,8 @@ import { Text } from "@hjmds/react-native/primitives";
85
86
 
86
87
  ## 배치
87
88
 
89
+ Native `grouped` 프레임은 Provider의 `tokens.radius.lg`를 읽는다. plain 목록과 separator 의미는 변하지 않는다.
90
+
88
91
  | 항목 | 값 | 근거 |
89
92
  | --- | --- | --- |
90
93
  | 크기 | 화면 본문 폭을 채우는 세로 묶음이다. `grouped`는 배경 `--hjm-color-bg`와 모서리 `radius.lg` 16으로 한 덩어리가 된다 | `design-contracts/src/foundations.ts`(`radius`), `react/src/styles.css`(`.hjm-list`) |
@@ -117,3 +120,7 @@ import { Text } from "@hjmds/react-native/primitives";
117
120
  | 구조 | `role="list"` + 자식마다 `role="listitem"` | `accessibilityRole="list"` |
118
121
  | 구분선 | CSS(`data-separator`) | 행 사이 1px `View` |
119
122
  | 배치 | `layoutStyle`(+ `className`) | `layoutStyle`(`style`은 deprecated) |
123
+
124
+ 2026-10-07 Utilverse 항목 삭제 채택을 위해 `renderItemAction`을 추가했다. 체크와 삭제의 초점·누름을 분리하고 큰 글자 라벨 폭을 보존하도록 행동을 다음 줄에 둔다. `renderCollection`의 `renderItem`에도 포함된다. 삭제 저장·실패·되돌리기는 제품이 처리한다.
125
+
126
+ 항목 삭제 뒤에는 제품이 다음 항목의 독립 행동(없으면 이전 항목, 목록이 비면 후속 행동)으로 초점을 복구한다. Web Showcase는 ref와 커밋 후 focus 예시를 제공한다. Native 접근성 초점 검증은 아직 남아 있다.
@@ -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
  - 근거: [LoadMore](../../load-more.md), `src/component-recipes.ts`(`loadMoreRecipe`), `src/load-more.ts`(상태·controller)
9
9
  - 스토리북: `배포/컴포넌트/탐색/더 보기`
10
10
 
@@ -76,6 +76,8 @@ const loadMore = useRef<LoadMoreHandle>(null);
76
76
 
77
77
  ## 배치
78
78
 
79
+ Native trigger 모서리는 Provider token에서 `loadMoreRecipe.trigger.radius`를 읽는다. requestKey·중복 요청 방지와 요청 상태는 그대로다.
80
+
79
81
  | 항목 | 값 | 근거 |
80
82
  | --- | --- | --- |
81
83
  | 크기 | 더 보기·다시 시도 버튼은 최소 높이 `control.minTouchTarget` 44, 좌우 안쪽 `spacing.md` 16, radius `radius.md` 12의 텍스트 버튼. 끝 문구는 `caption` | `loadMoreRecipe.trigger`, `.hjm-load-more__trigger` |
@@ -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
  - 근거: [Mentions](../../mentions.md), 트리거 판정 `src/mentions.ts`
9
9
  - 스토리북: `배포/컴포넌트/입력/사용자 언급`
10
10
 
@@ -74,6 +74,8 @@ import { Mentions } from "@hjmds/react-native/mentions";
74
74
 
75
75
  ## 배치
76
76
 
77
+ Native 후보 목록의 모서리는 `comboboxRecipe.popover.radius` 역할을 Provider의 `tokens.radius`에서 해석한다. 프로필 교체가 입력·caret·후보 선택을 초기화하지 않는다.
78
+
77
79
  | 항목 | 값 | 근거 |
78
80
  | --- | --- | --- |
79
81
  | 크기 | 후보 한 줄 최소 높이 44(`control.minTouchTarget`). Web 목록 최대 높이 `14rem`, 입력 폭에 맞춤 | `.hjm-mentions__option`·`__list`, Native `minHeight: 44` |
@@ -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
  - 근거: [Dropdown 판정](../../dropdown.md), [Popover 경계](../../popover.md), `src/component-recipes.ts`(`menuRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/탐색/메뉴`
10
10
 
@@ -84,6 +84,8 @@ import { Menu } from "@hjmds/react-native/navigation";
84
84
 
85
85
  ## 배치
86
86
 
87
+ Native popover와 항목 모서리는 Provider token에서 recipe 역할을 해석한다. 선택·열림·dismiss와 action 순서는 프로필 교체로 초기화하지 않는다.
88
+
87
89
  | 항목 | 값 | 근거 |
88
90
  | --- | --- | --- |
89
91
  | 크기 | 항목 높이 `comfortable` 56(`layout.rowHeight.singleLine`) · `compact` 44(`control.minTouchTarget`). Web 표면 폭 `13.75rem`~`min(24rem, 90vw)`, 높이 최대 `100dvh − 2 × spacing.md`. Native 표면 폭 100%·최대 520, 높이 최대 75% | `menuRecipe.density`, `.hjm-menu__content`, `react-native/src/navigation.tsx` |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-07
8
8
  - 근거: [Menubar](../../menubar.md), `src/menubar.ts`(`menubarRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/탐색/메뉴 막대`
10
10
 
@@ -91,3 +91,9 @@ Native 예는 없다(renderer 없음).
91
91
  - 항목 `textValue`는 계약 타입상 필수다. 지역화한 문구를 그대로 넣는다.
92
92
  - 메뉴 구성과 단축키 문구는 제품 소유다. 단축키 자체의 키 바인딩은 Menubar가 등록하지 않으므로 제품이 따로 연결한다.
93
93
  - `className`·`layoutStyle`은 배치에만 쓴다. 색·높이를 덮지 않는다.
94
+
95
+ 2026-10-07 Web 전체 회귀 중 키보드로 옮긴 항목이 첫 항목으로 돌아가는 문제를 재현했다.
96
+ 미게시(1.14.0 이후) 수정은 패널의 늦은 mouseenter가 키보드 선택을 덮지 않고 실제 마우스 이동에
97
+ 맞춰 활성 항목을 바꾼다. 메뉴 막대 라벨의 hover 전환·클릭·비활성 건너뛰기는 유지한다.
98
+ 고정 지연이나 키보드 검사 재시도로 가리는 대신 입력 의도를 구분했다.
99
+ [검증 기록](../../../../../docs/qa/2026-10-07-command-records.md).
@@ -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-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
9
9
  - 스토리북: `배포/구성/입력과 작성/메시지 작성`, `배포/구성/입력과 작성/댓글 작성`, `배포/화면/소통/채팅`
10
10
 
@@ -74,7 +74,11 @@ import { Image } from "react-native";
74
74
  | prop | 값 | 기본값 | 설명 |
75
75
  | --- | --- | --- | --- |
76
76
  | `value` | `string` | 필수 | 완전 제어형. HJM은 초안을 지우지 않는다 |
77
- | `label` | `string` | 필수 | placeholder 겸 접근성 이름 |
77
+ | `label` | `string` | 필수 | 접근성 이름; placeholder 생략 시 같은 문구 |
78
+ | `placeholder` | `string` | `label` | 짧은 시각 안내와 전체 접근성 이름을 분리 |
79
+ | `description` · `error` · `invalid` | `string` · `string` · `boolean` | 없음 | TextArea의 안내·오류 연결. Web describedby/invalid, Native hint/오류 표면 |
80
+ | `onBlur` | `() => void` | 없음 | host의 입력 중 상태 해제 |
81
+ | `submitMode` | `"newline"` · `"send"` | `"newline"` | Web Enter 전송은 Shift/IME/229 보호. Native send는 submit, newline은 기존 줄바꿈 |
78
82
  | `sendLabel` | `string` | 필수 | 전송 버튼 문구·접근성 이름 |
79
83
  | `onValueChange` | `(value: string) => void` | 필수 | 입력 변경 |
80
84
  | `onSend` | `(value: string) => void` | 필수 | 현재 문자열만 넘긴다 |
@@ -89,7 +93,7 @@ import { Image } from "react-native";
89
93
  | `sendIcon` | `ReactNode` | 없음 | 주면 아이콘 모드: 빈 입력에서는 입력창 안에 `attachmentAction`, 내용이 있거나 `pending`이면 전송 아이콘. 없으면 입력창 옆 텍스트 `Button` |
90
94
  | `sendPresentation` | `"inline"` · `"circle"` | `"inline"` | `circle`은 primary 원형 `IconButton size="small"`(댓글·DM 레퍼런스). `sendIcon`이 있을 때만 의미가 있다 |
91
95
  | `attachmentAction` | `{ label, icon, onPress(), disabled? }` | 없음 | 첨부 버튼(ghost `IconButton`) |
92
- | `attachments` | `readonly { id, removeLabel, preview }[]` | `[]` | 첨부 미리보기. 있으면 `onRemoveAttachment` 필수 |
96
+ | `attachments` | `readonly { id, removeLabel, preview, disabled? }[]` | `[]` | 첨부 미리보기. 있으면 `onRemoveAttachment` 필수 |
93
97
  | `onRemoveAttachment` | `(id: string) => void` | 없음 | 첨부 제거 |
94
98
  | Web `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 작성창 루트 배치(margin·width·flex 등). 미게시(1.12.1 이후) |
95
99
 
@@ -107,11 +111,13 @@ import { Image } from "react-native";
107
111
 
108
112
  ## 꼭 지킬 것
109
113
 
110
- - 문구(`label`은 placeholder 겸 접근성 이름, `sendLabel`, `removeLabel`, `cancelLabel`)는 모두 i18n 키로 넣는다.
114
+ - 문구(`label`은 접근성 이름; placeholder 생략 시 같은 문구, `sendLabel`, `removeLabel`, `cancelLabel`)는 모두 i18n 키로 넣는다.
111
115
  - `onSend(value)`는 현재 문자열만 넘긴다. 첨부 목록은 제품 상태에서 읽고, **서버 성공 뒤에만** 제품이 글·첨부·답장 대상을 지운다.
112
116
  - 첨부 `id`는 비어 있지 않고 유일해야 하며 `removeLabel`이 필요하다. 첨부가 있으면 `onRemoveAttachment`가 필수다. 어기면 던진다.
113
117
  - 사진 권한·선택기·업로드·개수 제한은 제품 소유다. 출처 선택 UI는 [PhotoSourceSheet](photo-source-sheet.md)를 쓴다.
114
- - Enter는 줄바꿈이다(IME 조합 보호). Enter 전송을 덧붙이지 않는다.
118
+ - Enter는 기본 줄바꿈이다. 데스크톱 채팅만 `submitMode="send"`로 선택할 수 있고 Shift+Enter·IME 조합·Safari keyCode 229는 전송하지 않는다. Native는 별도로 선택하지 않으면 줄바꿈을 유지한다.
119
+ - 2026-10-07 번뚝 채택에서 기존 blur/IME/검증 연결이 누락되어 이 공개 축을 추가했다. 제품이 내부 TextArea를 따로 조립하는 대안 대신 기존 MessageComposer 엔진을 확장한다.
120
+ - 로컬 준비 사진은 `attachments`로 표시하며 UploadItem의 서버 업로드 완료로 가장하지 않는다. 준비 중 해당 항목의 `disabled`와 attachmentAction.disabled를 연결해 편집 가능한 본문과 사진 제거를 분리한다. 서버 성공 전 초안을 지우지 않는다.
115
121
  - Web 배치는 `layoutStyle`로 한다. Native는 스타일 통로가 없어 감싸는 레이아웃에서 배치한다.
116
122
 
117
123
  ## 플랫폼 차이
@@ -122,3 +128,8 @@ import { Image } from "react-native";
122
128
  | 첨부 목록 | CSS 클래스 `hjm-message-composer__attachments` | 가로 ScrollView |
123
129
  | 이벤트 이름 | `attachmentAction.onPress`(내부에서 onClick으로 연결) | `onPress` |
124
130
  | 배치 prop | `layoutStyle`(루트) | 없음 |
131
+
132
+
133
+ ### 고정 아이콘과 큰 글자
134
+
135
+ 2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
@@ -73,6 +73,15 @@ import { Notice } from "@hjmds/react-native/feedback";
73
73
  | `announcement`(Native) | `none` · `polite` · `assertive` | `none` | 새로 나타나는 상태만 발표한다(아래 플랫폼 차이) |
74
74
  | `layoutStyle` | 배치 전용 style 객체 | — | 바깥 여백·폭만 |
75
75
 
76
+ ### 디자인 프로필 상속
77
+
78
+ 2026-10-07 테마 소비 경로 점검에서 고정 foundation/recipe 값이 남은 곳을 보완했다.
79
+ 모서리의 recipe 역할은 유지하고 값은 가장 가까운 Provider의 `designProfile.tokens.radius`를
80
+ 읽는다. Dialog/AlertDialog/Sheet/일반 Toast의 그림자는 `tokens.shadow.floating`을 읽으며
81
+ 프로필 없는 소비자의 기본값은 유지한다. 상태·초안·선택·Modal teardown은 이 축의 소유가 아니다.
82
+ 플랫폼 근사와 미검증 범위는 [프로필 계약](../../design-profile.md#오버레이선택-입력의-프로필-연결-보완)을 따른다.
83
+
84
+
76
85
  ## 배치
77
86
 
78
87
  | 항목 | 값 | 근거 |
@@ -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
  - 근거: [NumberField](../../number-field.md), [DurationField](../../compound-controls.md#durationfield), `src/number-field.ts`(`numberFieldRecipe`)
9
9
  - 스토리북: `배포/컴포넌트/입력/숫자 입력`, `배포/컴포넌트/입력/소요 시간 입력`
10
10
 
@@ -104,6 +104,8 @@ const decreaseKey = { hours: "timer.decrease.hours", minutes: "timer.decrease.mi
104
104
 
105
105
  ## 배치
106
106
 
107
+ Native는 제품 프로필의 `tokens.fontFamily.ui`를 실제 텍스트/입력 host에 연결한다. 기본 UI stack은 OS 서체를 유지하고, 제품이 지정한 첫 named font의 등록·글리프 확인은 제품이 맡는다.
108
+
107
109
  | 항목 | 값 | 근거 |
108
110
  | --- | --- | --- |
109
111
  | 크기 | 높이 `medium` 44(`fieldFrameContract.minHeight`) · `large` 52(`control.buttonHeight.large`). 증감 버튼 각 44×44(`control.minTouchTarget`) | `numberFieldRecipe.sizes`, `.hjm-number-field__stepper` |