@hjmds/design-contracts 1.12.1 → 1.13.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 (288) hide show
  1. package/dist/avatar-fallback.d.ts +11 -0
  2. package/dist/avatar-fallback.d.ts.map +1 -1
  3. package/dist/avatar-fallback.js +21 -0
  4. package/dist/avatar-fallback.js.map +1 -1
  5. package/dist/base-recipes.d.ts +17 -0
  6. package/dist/base-recipes.d.ts.map +1 -1
  7. package/dist/base-recipes.js +17 -0
  8. package/dist/base-recipes.js.map +1 -1
  9. package/dist/catalog.d.ts +25 -0
  10. package/dist/catalog.d.ts.map +1 -1
  11. package/dist/command-palette.d.ts +14 -9
  12. package/dist/command-palette.d.ts.map +1 -1
  13. package/dist/command-palette.js +8 -9
  14. package/dist/command-palette.js.map +1 -1
  15. package/dist/component-recipes.d.ts +17 -0
  16. package/dist/component-recipes.d.ts.map +1 -1
  17. package/dist/component-recipes.js +5 -0
  18. package/dist/component-recipes.js.map +1 -1
  19. package/dist/provider-button.d.ts.map +1 -1
  20. package/dist/provider-button.js +3 -0
  21. package/dist/provider-button.js.map +1 -1
  22. package/dist/reactions.d.ts +10 -0
  23. package/dist/reactions.d.ts.map +1 -1
  24. package/dist/reactions.js +7 -0
  25. package/dist/reactions.js.map +1 -1
  26. package/dist/screen-patterns.d.ts +147 -0
  27. package/dist/screen-patterns.d.ts.map +1 -0
  28. package/dist/screen-patterns.js +149 -0
  29. package/dist/screen-patterns.js.map +1 -0
  30. package/dist/slider.d.ts +8 -0
  31. package/dist/slider.d.ts.map +1 -1
  32. package/dist/slider.js +6 -1
  33. package/dist/slider.js.map +1 -1
  34. package/dist/upload-item.d.ts +5 -0
  35. package/dist/upload-item.d.ts.map +1 -1
  36. package/dist/upload-item.js +5 -0
  37. package/dist/upload-item.js.map +1 -1
  38. package/dist/version.d.ts +1 -1
  39. package/dist/version.js +1 -1
  40. package/dist/version.js.map +1 -1
  41. package/docs/action-session.md +3 -3
  42. package/docs/agreement.md +5 -0
  43. package/docs/avatar-fallback.md +7 -0
  44. package/docs/bottom-navigation.md +6 -0
  45. package/docs/brand-boundary.md +1 -1
  46. package/docs/button-label.md +6 -0
  47. package/docs/clipboard.md +3 -0
  48. package/docs/command-palette.md +45 -2
  49. package/docs/consumer-policy.md +5 -1
  50. package/docs/data-table.md +6 -4
  51. package/docs/dialog.md +8 -2
  52. package/docs/form.md +51 -0
  53. package/docs/generated/component-maturity.md +1 -1
  54. package/docs/generated/renderer-evidence.json +3 -3
  55. package/docs/generated/renderer-evidence.md +1 -1
  56. package/docs/generated/showcase-manifest.json +1 -1
  57. package/docs/link.md +8 -0
  58. package/docs/migration-native-legacy-removal.md +45 -1
  59. package/docs/optional-adapters.md +1 -1
  60. package/docs/password-field.md +5 -0
  61. package/docs/product-composition-adoption.md +40 -0
  62. package/docs/progress.md +19 -1
  63. package/docs/provider-button.md +13 -0
  64. package/docs/result.md +3 -0
  65. package/docs/screen-chrome.md +10 -0
  66. package/docs/screen-patterns.md +376 -0
  67. package/docs/sheet.md +12 -0
  68. package/docs/splitter.md +8 -2
  69. package/docs/theming.md +36 -29
  70. package/docs/toggle-group.md +7 -0
  71. package/docs/tour.md +7 -1
  72. package/docs/tree.md +5 -2
  73. package/docs/upload-item.md +7 -0
  74. package/docs/usage/README.md +236 -0
  75. package/docs/usage/STANDARD.md +108 -0
  76. package/docs/usage/components/accordion.md +107 -0
  77. package/docs/usage/components/activity-heatmap.md +104 -0
  78. package/docs/usage/components/affix.md +86 -0
  79. package/docs/usage/components/agreement.md +129 -0
  80. package/docs/usage/components/alert-dialog.md +130 -0
  81. package/docs/usage/components/anchor.md +96 -0
  82. package/docs/usage/components/aspect-ratio.md +89 -0
  83. package/docs/usage/components/asset.md +126 -0
  84. package/docs/usage/components/auth-provider-button.md +116 -0
  85. package/docs/usage/components/auth-screen-layout.md +129 -0
  86. package/docs/usage/components/avatar.md +114 -0
  87. package/docs/usage/components/badge.md +84 -0
  88. package/docs/usage/components/bottom-cta.md +125 -0
  89. package/docs/usage/components/bottom-info.md +99 -0
  90. package/docs/usage/components/bottom-navigation.md +136 -0
  91. package/docs/usage/components/breadcrumb.md +81 -0
  92. package/docs/usage/components/button.md +118 -0
  93. package/docs/usage/components/calendar.md +122 -0
  94. package/docs/usage/components/card.md +110 -0
  95. package/docs/usage/components/carousel.md +113 -0
  96. package/docs/usage/components/celebration.md +96 -0
  97. package/docs/usage/components/chat-message.md +122 -0
  98. package/docs/usage/components/chat-screen.md +112 -0
  99. package/docs/usage/components/checkbox-group.md +104 -0
  100. package/docs/usage/components/checkbox.md +103 -0
  101. package/docs/usage/components/chip.md +104 -0
  102. package/docs/usage/components/code-block.md +111 -0
  103. package/docs/usage/components/collapsible.md +112 -0
  104. package/docs/usage/components/color-picker.md +86 -0
  105. package/docs/usage/components/combobox.md +137 -0
  106. package/docs/usage/components/command-palette.md +125 -0
  107. package/docs/usage/components/comment-thread-screen.md +125 -0
  108. package/docs/usage/components/container.md +98 -0
  109. package/docs/usage/components/content-transition.md +101 -0
  110. package/docs/usage/components/context-menu.md +136 -0
  111. package/docs/usage/components/counter-badge.md +107 -0
  112. package/docs/usage/components/data-table.md +122 -0
  113. package/docs/usage/components/date-picker.md +142 -0
  114. package/docs/usage/components/date-range-picker.md +111 -0
  115. package/docs/usage/components/description-list.md +103 -0
  116. package/docs/usage/components/design-system-provider.md +124 -0
  117. package/docs/usage/components/dialog.md +176 -0
  118. package/docs/usage/components/divider.md +89 -0
  119. package/docs/usage/components/editor-screen.md +126 -0
  120. package/docs/usage/components/effect-surface.md +120 -0
  121. package/docs/usage/components/empty-state.md +114 -0
  122. package/docs/usage/components/field.md +129 -0
  123. package/docs/usage/components/file-picker.md +114 -0
  124. package/docs/usage/components/floating-action-button.md +138 -0
  125. package/docs/usage/components/form.md +162 -0
  126. package/docs/usage/components/grid.md +99 -0
  127. package/docs/usage/components/heading.md +87 -0
  128. package/docs/usage/components/icon-button.md +126 -0
  129. package/docs/usage/components/icon.md +105 -0
  130. package/docs/usage/components/image.md +122 -0
  131. package/docs/usage/components/keyboard-avoiding.md +93 -0
  132. package/docs/usage/components/keyboard-dock.md +110 -0
  133. package/docs/usage/components/keyboard-form-scroll-view.md +95 -0
  134. package/docs/usage/components/keyboard-motion-provider.md +86 -0
  135. package/docs/usage/components/layout.md +117 -0
  136. package/docs/usage/components/link.md +121 -0
  137. package/docs/usage/components/list-detail-screen.md +103 -0
  138. package/docs/usage/components/list-row.md +124 -0
  139. package/docs/usage/components/list.md +119 -0
  140. package/docs/usage/components/load-more.md +115 -0
  141. package/docs/usage/components/masonry.md +109 -0
  142. package/docs/usage/components/media-selection-screen.md +119 -0
  143. package/docs/usage/components/mentions.md +119 -0
  144. package/docs/usage/components/menu.md +129 -0
  145. package/docs/usage/components/menubar.md +93 -0
  146. package/docs/usage/components/message-composer.md +124 -0
  147. package/docs/usage/components/moderation-screen.md +113 -0
  148. package/docs/usage/components/notice.md +106 -0
  149. package/docs/usage/components/notification-inbox-screen.md +97 -0
  150. package/docs/usage/components/notification-item.md +98 -0
  151. package/docs/usage/components/number-field.md +131 -0
  152. package/docs/usage/components/onboarding-screen.md +106 -0
  153. package/docs/usage/components/otp-field.md +101 -0
  154. package/docs/usage/components/pagination.md +82 -0
  155. package/docs/usage/components/password-field.md +137 -0
  156. package/docs/usage/components/permission-screen.md +107 -0
  157. package/docs/usage/components/photo-source-sheet.md +119 -0
  158. package/docs/usage/components/popover.md +108 -0
  159. package/docs/usage/components/profile-screen.md +89 -0
  160. package/docs/usage/components/progress.md +122 -0
  161. package/docs/usage/components/qr-code.md +122 -0
  162. package/docs/usage/components/radio-group.md +124 -0
  163. package/docs/usage/components/radio.md +104 -0
  164. package/docs/usage/components/result.md +116 -0
  165. package/docs/usage/components/saved-items-screen.md +126 -0
  166. package/docs/usage/components/screen-layout.md +119 -0
  167. package/docs/usage/components/search-field.md +120 -0
  168. package/docs/usage/components/search-screen.md +215 -0
  169. package/docs/usage/components/section.md +111 -0
  170. package/docs/usage/components/segmented-control.md +138 -0
  171. package/docs/usage/components/select.md +142 -0
  172. package/docs/usage/components/settings-screen.md +126 -0
  173. package/docs/usage/components/shared-transition-element.md +111 -0
  174. package/docs/usage/components/shared-transition-screen.md +86 -0
  175. package/docs/usage/components/sheet.md +151 -0
  176. package/docs/usage/components/side-panel.md +104 -0
  177. package/docs/usage/components/sidebar.md +107 -0
  178. package/docs/usage/components/skeleton.md +105 -0
  179. package/docs/usage/components/skip-nav.md +76 -0
  180. package/docs/usage/components/slider.md +121 -0
  181. package/docs/usage/components/sortable-collection.md +127 -0
  182. package/docs/usage/components/spinner.md +86 -0
  183. package/docs/usage/components/splitter.md +103 -0
  184. package/docs/usage/components/stack.md +93 -0
  185. package/docs/usage/components/statistic.md +123 -0
  186. package/docs/usage/components/steps.md +110 -0
  187. package/docs/usage/components/surface.md +91 -0
  188. package/docs/usage/components/swipe-actions.md +124 -0
  189. package/docs/usage/components/switch.md +120 -0
  190. package/docs/usage/components/tabs.md +134 -0
  191. package/docs/usage/components/tag.md +84 -0
  192. package/docs/usage/components/tags-input.md +111 -0
  193. package/docs/usage/components/text-area.md +112 -0
  194. package/docs/usage/components/text-format.md +75 -0
  195. package/docs/usage/components/text-transition.md +104 -0
  196. package/docs/usage/components/text.md +101 -0
  197. package/docs/usage/components/thinking-orb.md +105 -0
  198. package/docs/usage/components/timeline.md +105 -0
  199. package/docs/usage/components/toast.md +145 -0
  200. package/docs/usage/components/toggle-group.md +95 -0
  201. package/docs/usage/components/tooltip.md +103 -0
  202. package/docs/usage/components/top-bar.md +124 -0
  203. package/docs/usage/components/top.md +89 -0
  204. package/docs/usage/components/tour.md +118 -0
  205. package/docs/usage/components/transfer-list.md +115 -0
  206. package/docs/usage/components/tree.md +91 -0
  207. package/docs/usage/components/upload-item.md +99 -0
  208. package/docs/usage/components/virtual-list.md +105 -0
  209. package/docs/usage/components/visually-hidden.md +72 -0
  210. package/docs/usage/components/watermark.md +78 -0
  211. package/docs/usage/compositions/action-recovery-optimistic.md +180 -0
  212. package/docs/usage/compositions/action-recovery-save.md +235 -0
  213. package/docs/usage/compositions/action-recovery-undo.md +193 -0
  214. package/docs/usage/compositions/common-message.md +132 -0
  215. package/docs/usage/compositions/common-notification.md +101 -0
  216. package/docs/usage/compositions/compound-controls.md +186 -0
  217. package/docs/usage/compositions/data-layouts.md +157 -0
  218. package/docs/usage/compositions/disclosure.md +144 -0
  219. package/docs/usage/compositions/environment-matrix.md +139 -0
  220. package/docs/usage/compositions/expo-interactions.md +149 -0
  221. package/docs/usage/compositions/family-drawer.md +201 -0
  222. package/docs/usage/compositions/floating-action-button.md +197 -0
  223. package/docs/usage/compositions/input-sheet.md +148 -0
  224. package/docs/usage/compositions/interaction-adapters.md +190 -0
  225. package/docs/usage/compositions/interaction-flow-apply.md +205 -0
  226. package/docs/usage/compositions/interaction-flow-draft.md +188 -0
  227. package/docs/usage/compositions/interaction-flow-search.md +171 -0
  228. package/docs/usage/compositions/native-renderers.md +106 -0
  229. package/docs/usage/compositions/navigation-bar-collection.md +164 -0
  230. package/docs/usage/compositions/optional-adapters.md +169 -0
  231. package/docs/usage/compositions/optional-motion.md +109 -0
  232. package/docs/usage/compositions/photo-source.md +104 -0
  233. package/docs/usage/compositions/purpose-input-comment.md +110 -0
  234. package/docs/usage/compositions/purpose-input-message.md +119 -0
  235. package/docs/usage/compositions/reference-first.md +96 -0
  236. package/docs/usage/compositions/reference-review.md +107 -0
  237. package/docs/usage/compositions/reference-settings.md +107 -0
  238. package/docs/usage/compositions/selection-scope.md +174 -0
  239. package/docs/usage/compositions/stea-event-ticket.md +166 -0
  240. package/docs/usage/compositions/stea-flip-card.md +162 -0
  241. package/docs/usage/compositions/stea-order-progress.md +184 -0
  242. package/docs/usage/compositions/stea-otp-verify.md +215 -0
  243. package/docs/usage/compositions/stea-pixel-empty.md +140 -0
  244. package/docs/usage/compositions/stea-schedule-card.md +169 -0
  245. package/docs/usage/compositions/stea-stat-summary.md +154 -0
  246. package/docs/usage/compositions/time-selection.md +174 -0
  247. package/docs/usage/compositions/toast-layout.md +128 -0
  248. package/docs/usage/compositions/visual-foundations.md +185 -0
  249. package/docs/usage/compositions/web-additions.md +146 -0
  250. package/docs/usage/compositions/web-navigation.md +143 -0
  251. package/docs/usage/screens/common-chat.md +127 -0
  252. package/docs/usage/screens/common-comments.md +108 -0
  253. package/docs/usage/screens/common-inbox.md +110 -0
  254. package/docs/usage/screens/common-login.md +98 -0
  255. package/docs/usage/screens/common-profile.md +221 -0
  256. package/docs/usage/screens/common-saved.md +127 -0
  257. package/docs/usage/screens/common-search.md +274 -0
  258. package/docs/usage/screens/common-settings.md +126 -0
  259. package/docs/usage/screens/common-shell.md +108 -0
  260. package/docs/usage/screens/dashboard.md +245 -0
  261. package/docs/usage/screens/discovery-gallery.md +306 -0
  262. package/docs/usage/screens/flow-collection.md +96 -0
  263. package/docs/usage/screens/flow-editor.md +120 -0
  264. package/docs/usage/screens/flow-media.md +111 -0
  265. package/docs/usage/screens/flow-moderation.md +120 -0
  266. package/docs/usage/screens/flow-onboarding.md +193 -0
  267. package/docs/usage/screens/flow-permission.md +103 -0
  268. package/docs/usage/screens/landing.md +347 -0
  269. package/docs/usage/screens/mockup-studio.md +190 -0
  270. package/docs/usage/screens/notification-settings.md +206 -0
  271. package/docs/usage/screens/reference-comparison.md +159 -0
  272. package/docs/usage/templates/component.md +61 -0
  273. package/docs/usage/templates/composition.md +47 -0
  274. package/docs/usage/templates/screen.md +56 -0
  275. package/docs/usage/templates/token.md +32 -0
  276. package/docs/usage/tokens/color.md +142 -0
  277. package/docs/usage/tokens/elevation-opacity.md +86 -0
  278. package/docs/usage/tokens/layers.md +98 -0
  279. package/docs/usage/tokens/layout.md +114 -0
  280. package/docs/usage/tokens/motion.md +88 -0
  281. package/docs/usage/tokens/radius.md +53 -0
  282. package/docs/usage/tokens/size.md +74 -0
  283. package/docs/usage/tokens/spacing.md +73 -0
  284. package/docs/usage/tokens/stroke.md +50 -0
  285. package/docs/usage/tokens/theme-studio.md +70 -0
  286. package/docs/usage/tokens/typography-studio.md +70 -0
  287. package/docs/usage/tokens/typography.md +89 -0
  288. package/package.json +7 -1
@@ -0,0 +1,131 @@
1
+ # NumberField
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [NumberField](../../number-field.md), [DurationField](../../compound-controls.md#durationfield), `src/number-field.ts`(`numberFieldRecipe`)
9
+ - 스토리북: `배포/컴포넌트/입력/숫자 입력`, `배포/컴포넌트/입력/소요 시간 입력`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 범위가 정해진 **정확한 수 하나**를 입력받을 때 쓴다. 예약 인원, 수량, 글자 수 제한처럼
14
+ “이 숫자 그대로”가 중요한 입력이다. 직접 타이핑과 한 단계씩 증감하는 버튼을 함께 제공한다.
15
+ 경과 시간(정수 초)은 NumberField 세 개를 합성한 `DurationField`를 쓴다.
16
+
17
+ ## 쓰지 않을 때
18
+
19
+ | 상황 | 대신 쓸 것 |
20
+ | --- | --- |
21
+ | 대략적인 값을 끌어서 고름 | [Slider](slider.md) |
22
+ | 전화번호·우편번호·카드번호처럼 숫자로 된 문자열 | [Field](field.md)의 TextField |
23
+ | 인증번호 | [OtpField](otp-field.md) |
24
+ | 시각(몇 시 몇 분)·날짜 | [DatePicker](date-picker.md) |
25
+ | 수치를 보여 주기만 함 | [Statistic](statistic.md) |
26
+
27
+ ## 공개 이름과 import
28
+
29
+ | 이름 | 역할 | Web | Native |
30
+ | --- | --- | --- | --- |
31
+ | `NumberField` | 기본 | `@hjmds/react`, `/forms`, `/number-field` | `@hjmds/react-native`, `/inputs`, `/number-field` |
32
+ | `DurationField` | 시·분·초 세 칸 합성(optional-extension, 추가 peer 없음) | `/duration-field` | `/duration-field` |
33
+
34
+ ## 최소 사용 예
35
+
36
+ ```tsx
37
+ // Web
38
+ import { NumberField } from "@hjmds/react/number-field";
39
+
40
+ <NumberField
41
+ label={t("booking.guests")}
42
+ min={1}
43
+ max={10}
44
+ value={guests}
45
+ onValueChange={setGuests}
46
+ decrementLabel={t("booking.guests.decrease")}
47
+ incrementLabel={t("booking.guests.increase")}
48
+ />
49
+ ```
50
+
51
+ ```tsx
52
+ // Native
53
+ import { NumberField } from "@hjmds/react-native/number-field";
54
+
55
+ <NumberField
56
+ label={t("booking.guests")}
57
+ min={1}
58
+ max={10}
59
+ value={guests}
60
+ onValueChange={setGuests}
61
+ decrementLabel={t("booking.guests.decrease")}
62
+ incrementLabel={t("booking.guests.increase")}
63
+ />
64
+ ```
65
+
66
+ ```tsx
67
+ // Web — DurationField(Native도 같은 props, import는 `@hjmds/react-native/duration-field`). value는 정수 초
68
+ import { DurationField } from "@hjmds/react/duration-field";
69
+
70
+ const increaseKey = { hours: "timer.increase.hours", minutes: "timer.increase.minutes", seconds: "timer.increase.seconds" } as const;
71
+ const decreaseKey = { hours: "timer.decrease.hours", minutes: "timer.decrease.minutes", seconds: "timer.decrease.seconds" } as const;
72
+
73
+ <DurationField
74
+ value={seconds}
75
+ max={3 * 3600}
76
+ onValueChange={setSeconds}
77
+ labels={{
78
+ label: t("timer.duration"), hours: t("unit.hours"), minutes: t("unit.minutes"), seconds: t("unit.seconds"),
79
+ increment: (unit) => t(increaseKey[unit]), decrement: (unit) => t(decreaseKey[unit]),
80
+ }}
81
+ />
82
+ ```
83
+
84
+ ## 축과 기본값
85
+
86
+ | prop | 값 | 기본값 | 설명 |
87
+ | --- | --- | --- | --- |
88
+ | `value` · `defaultValue` | `number \| null` | `null` | `null`은 아직 입력하지 않은 상태 |
89
+ | `onValueChange` | `(value: number \| null) => void` | — | blur 확정·증감 때 호출. 지운 채 확정하면 `null` |
90
+ | `min` · `max` | 숫자 | 필수 | |
91
+ | `step` | 양수 | 1 | |
92
+ | `size` | `medium` · `large` | `medium` | |
93
+ | `decrementLabel` · `incrementLabel` | 문자열 | 필수 | 증감 버튼의 접근성 이름 |
94
+ | `getValueText` | `(value: number) => string` | — | 보조기기용 값 문구(단위·통화 등). 편집 문자열은 바꾸지 않는다 |
95
+ | `inputMode` | `decimal` · `numeric` · `text` | 자동 | 생략하면 `min < 0`이면 `text`, 정수 step이면 `numeric`, 아니면 `decimal` |
96
+ | `layoutStyle` | 배치 전용 style 객체 | — | 필드 전체 배치. Web `style`은 안쪽 input에 붙는다 |
97
+ | `value`(DurationField) | 정수 초 | 필수 | 제어형만 |
98
+ | `onValueChange`(DurationField) | `(seconds: number) => void` | 필수 | 단위 하나를 바꿔도 합친 초로 넘긴다(범위로 clamp) |
99
+ | `labels`(DurationField) | `{ label, hours, minutes, seconds, increment: (unit) => string, decrement: (unit) => string }` | 필수 | `unit`은 `"hours" \| "minutes" \| "seconds"`. 키는 위 예처럼 상수 표로 고른다 |
100
+ | `min`(DurationField) | 정수 초 | 0 | |
101
+ | `max`(DurationField) | 정수 초 | 필수 | 1시간 미만이면 시 칸이 비활성 |
102
+
103
+ 타이핑은 blur에서 clamp·step snap으로 확정되고, 증감 버튼과 ↑/↓는 즉시 확정된다.
104
+
105
+ ## 배치
106
+
107
+ | 항목 | 값 | 근거 |
108
+ | --- | --- | --- |
109
+ | 크기 | 높이 `medium` 44(`fieldFrameContract.minHeight`) · `large` 52(`control.buttonHeight.large`). 증감 버튼 각 44×44(`control.minTouchTarget`) | `numberFieldRecipe.sizes`, `.hjm-number-field__stepper` |
110
+ | 간격 | 라벨·입력·설명·오류 사이 `spacing.xs` 8. 값 좌우 `medium` 16(`spacing.md`) · `large` 20(`spacing.lg`). 버튼과 값은 1px 구분선. DurationField 칸 사이 `spacing.md` 16, 라벨과 칸 사이 `spacing.sm` 12 | `formSupportContract.gap`, `.hjm-number-field__input`, `duration-field.tsx` |
111
+ | 순서·정렬 | `[−] [ 값 ] [+]`: 감소가 시작 쪽, 증가가 끝 쪽(RTL 반전). 값은 가운데 정렬·고정폭 숫자. DurationField는 시 → 분 → 초 | `.hjm-number-field__*`, `duration-field.tsx` |
112
+ | 고정·스크롤 | 폼 안에서 다른 Field와 같은 열. 폭은 부모를 따르고, 짧은 값이면 `layoutStyle`/`className`으로 폭을 줄여 라벨 아래에 둔다 | — |
113
+ | 좁은 폭·큰 글자 | DurationField는 줄바꿈된다(Web 칸 최소 `10ch`, Native 칸 기준 폭 `spacing.xxxl` × 3 × 글자 배율) | `duration-field.tsx`(Web·Native) |
114
+
115
+ ## 꼭 지킬 것
116
+
117
+ - `decrementLabel`·`incrementLabel`은 필수이며 i18n 키로 넣는다. 화면에 보이지 않아도 접근성 이름이다.
118
+ - 단위·통화·소수 자릿수 표시는 만들지 않는다. 보조기기용 문구가 필요하면 `getValueText`로 제품이 포맷한다.
119
+ - 제어형과 비제어형을 렌더 사이에 바꾸지 않는다(`value`를 넣었다 뺐다 하면 던진다).
120
+ - `min ≥ max`, `step ≤ 0`, 범위 밖 `value`는 던진다. DurationField도 범위 밖·정수 아닌 초를 던진다.
121
+ - 색·높이를 덮지 않는다. 배치는 `layoutStyle`로 한다. Native `inputStyle`·`containerStyle`은 deprecated(개발 모드 경고, 다음 major 제거) — 높이·글꼴은 `size`, 배치는 `layoutStyle`.
122
+
123
+ ## 플랫폼 차이
124
+
125
+ | 항목 | Web | Native |
126
+ | --- | --- | --- |
127
+ | 역할 | `spinbutton`, `type="text"` | 입력 + 증감 button, accessibility action |
128
+ | label·description·error 타입 | `ReactNode` | `string` |
129
+ | 추가 이름 | 없음 | `accessibilityLabel`, `accessibilityHint` |
130
+ | 클래스·스타일 | `className`, `inputClassName`, `layoutStyle`(필드 전체). `style`은 안쪽 input | `layoutStyle`(필드 전체). `inputStyle`·`containerStyle`은 deprecated |
131
+ | DurationField 배치 | `className`, `layoutStyle`(fieldset) | `layoutStyle`(미게시(1.12.1 이후), 1.12.1은 감싸는 View) |
@@ -0,0 +1,106 @@
1
+ # OnboardingScreen
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.12.1 이후)
7
+ - 검토일: 2026-10-06
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
+ - 스토리북: `배포/화면/소개/온보딩`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 첫 실행 소개·초기 설정처럼 **몇 단계를 차례로 넘기는 화면**에 쓴다. 현재 단계의 제목·설명·본문,
14
+ 진행 문구(“2/4”), 다음·이전·건너뛰기·완료 행동을 [ScreenLayout](screen-layout.md) 위에 조합한다.
15
+ 단계 범위가 틀리면 렌더 중 던진다.
16
+
17
+ ## 쓰지 않을 때
18
+
19
+ | 상황 | 대신 쓸 것 |
20
+ | --- | --- |
21
+ | 단계 표시만 필요(본문은 자유 배치) | [Steps](steps.md) |
22
+ | 화면 위에 겹치는 기능 안내 | [Tour](tour.md) |
23
+ | 권한 하나를 요청하는 단계 | [PermissionScreen](permission-screen.md) |
24
+ | 좌우로 넘기는 이미지 소개 | [Carousel](carousel.md) |
25
+
26
+ ## 공개 이름과 import
27
+
28
+ | 이름 | 역할 | Web | Native |
29
+ | --- | --- | --- | --- |
30
+ | `OnboardingScreen` | supplemental, 루트 barrel에 없음 | `/screen-flows` | `/screen-flows` |
31
+
32
+ `@hjmds/react/screen-flows`, `@hjmds/react-native/screen-flows`로만 import한다. 추가 optional peer는 없다.
33
+
34
+ ## 최소 사용 예
35
+
36
+ ```tsx
37
+ // Web
38
+ import { OnboardingScreen } from "@hjmds/react/screen-flows";
39
+
40
+ <OnboardingScreen
41
+ steps={[
42
+ { id: "welcome", title: t("onboarding.welcome.title"), description: t("onboarding.welcome.body"), content: welcomeArt },
43
+ { id: "goal", title: t("onboarding.goal.title"), description: t("onboarding.goal.body"), content: goalPicker },
44
+ ]}
45
+ index={index}
46
+ onIndexChange={setIndex}
47
+ nextLabel={t("common.next")}
48
+ backLabel={t("common.back")}
49
+ complete={{ label: t("onboarding.start"), onAction: finish, pending: saving }}
50
+ skip={{ label: t("common.skip"), onAction: finish }}
51
+ progressLabel={(current, total) => t("onboarding.progress", { current, total })}
52
+ />
53
+ ```
54
+
55
+ ```tsx
56
+ // Native — props는 Web과 같다
57
+ import { OnboardingScreen } from "@hjmds/react-native/screen-flows";
58
+
59
+ <OnboardingScreen
60
+ steps={[
61
+ { id: "welcome", title: t("onboarding.welcome.title"), description: t("onboarding.welcome.body"), content: welcomeArt },
62
+ { id: "goal", title: t("onboarding.goal.title"), description: t("onboarding.goal.body"), content: goalPicker },
63
+ ]}
64
+ index={index}
65
+ onIndexChange={setIndex}
66
+ nextLabel={t("common.next")}
67
+ backLabel={t("common.back")}
68
+ complete={{ label: t("onboarding.start"), onAction: finish, pending: saving }}
69
+ skip={{ label: t("common.skip"), onAction: finish }}
70
+ progressLabel={(current, total) => t("onboarding.progress", { current, total })}
71
+ />
72
+ ```
73
+
74
+ ### 제품이 공급할 것
75
+
76
+ | prop | 내용 |
77
+ | --- | --- |
78
+ | `steps` | `{ id, title, description, content }[]`. 1개 이상. 제목·설명은 지역화 문자열, `content`는 단계 본문 |
79
+ | `index`, `onIndexChange` | 현재 단계(0부터, 제어형). 범위를 벗어나면 던진다 |
80
+ | `nextLabel`, `backLabel` | 다음·이전 문구. 첫 단계에는 이전이 없다 |
81
+ | `complete` | 마지막 단계의 주 행동 `{ label, onAction, disabled?, pending? }` |
82
+ | `skip` | 선택. 상단 actions 자리에 보조 버튼으로 놓인다 |
83
+ | `progressLabel(current, total)` | 1부터 센 현재 단계와 전체 수로 진행 문구를 만든다 |
84
+
85
+ ## 배치
86
+
87
+ | 항목 | 값 | 근거 |
88
+ | --- | --- | --- |
89
+ | 크기 | 한 단계의 ScreenLayout 폭(최대 720); 다음·완료·이전은 `Button` 기본 크기 | `OnboardingScreen` |
90
+ | 간격 | 화면 padding `spacing.md` 16(진행 문구 notice는 좌우만); footer 다음/완료–이전 `spacing.sm` 12; 단계 본문 안 간격은 `content`(제품) 소유 | Web·Native `OnboardingScreen` `Stack gap="sm"` |
91
+ | 순서·정렬 | 헤더(단계 제목·설명 → 건너뛰기 ghost) → 진행 문구(caption) → 단계 `content` → footer(다음 또는 완료 primary → 이전 ghost, 첫 단계는 이전 없음) | 렌더 순서 |
92
+ | 고정·스크롤 | 헤더·진행 문구·footer 고정, 단계 본문 화면 스크롤 | `ScreenLayout` |
93
+ | 좁은 폭·큰 글자 | 제목 열 최소 폭 120 × 글자 배율, 모자라면 건너뛰기가 다음 줄로 내려간다; footer 버튼은 세로로 쌓인다 | `screenPatternRecipe.headerMinWidth` |
94
+
95
+ ## 꼭 지킬 것
96
+
97
+ - 단계에서 고른 값의 저장, 완료 여부 저장, 다시 보여 주지 않기는 제품 소유다. `complete.onAction`에서 처리한다.
98
+ - ScreenLayout의 `header`·`state`·`contentInset` 같은 화면 props는 받지 않는다. 화면 틀을 바꿔야 하면 ScreenLayout으로 직접 조합한다.
99
+ 배치 prop은 `layoutStyle` 하나이며 Web·Native 모두 화면 루트(ScreenLayout)에 넘긴다.
100
+ - 문구·일러스트·브랜드 이미지는 제품 소유다. `content`에 제품 자산을 넣는다.
101
+
102
+ ## 플랫폼 차이
103
+
104
+ | 항목 | Web | Native |
105
+ | --- | --- | --- |
106
+ | 배치 prop | `layoutStyle`(ScreenLayout 루트, 미게시(1.12.1 이후)) | `layoutStyle`(ScreenLayout 루트, 미게시(1.12.1 이후)) |
@@ -0,0 +1,101 @@
1
+ # OtpField
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [OtpField](../../otp-field.md), `src/otp-field.ts`(`otpFieldRecipe`)
9
+ - 스토리북: `배포/컴포넌트/입력/인증번호 입력`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 문자·메일로 받은 **숫자 인증번호**를 칸 모양으로 입력받을 때 쓴다. 화면에는 칸이 여러 개 보이지만
14
+ 실제 입력은 하나라서 붙여넣기·지우기·SMS 자동 채움이 플랫폼 기본 동작으로 처리된다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 비밀번호·PIN처럼 가려야 하는 값 | [PasswordField](password-field.md) |
21
+ | 영문이 섞인 코드(쿠폰·초대 코드) | [Field](field.md)의 TextField (`align="center"`) |
22
+ | 범위가 있는 수량 | [NumberField](number-field.md) |
23
+
24
+ ## 공개 이름과 import
25
+
26
+ | 이름 | 역할 | Web | Native |
27
+ | --- | --- | --- | --- |
28
+ | `OtpField` | 기본 | `@hjmds/react`, `/forms`, `/otp-field` | `@hjmds/react-native`, `/inputs`, `/otp-field` |
29
+
30
+ ## 최소 사용 예
31
+
32
+ ```tsx
33
+ // Web
34
+ import { OtpField } from "@hjmds/react/otp-field";
35
+
36
+ <OtpField
37
+ label={t("verify.code")}
38
+ length={6}
39
+ value={code}
40
+ onValueChange={setCode}
41
+ onComplete={submitCode}
42
+ busy={verifying}
43
+ error={codeError ? t("verify.code.invalid") : undefined}
44
+ />
45
+ ```
46
+
47
+ ```tsx
48
+ // Native
49
+ import { OtpField } from "@hjmds/react-native/otp-field";
50
+
51
+ <OtpField
52
+ label={t("verify.code")}
53
+ length={6}
54
+ value={code}
55
+ onValueChange={setCode}
56
+ onComplete={submitCode}
57
+ busy={verifying}
58
+ {...(codeError ? { error: t("verify.code.invalid") } : {})}
59
+ />
60
+ ```
61
+
62
+ ## 축과 기본값
63
+
64
+ | prop | 값 | 기본값 | 설명 |
65
+ | --- | --- | --- | --- |
66
+ | `length` | 2 이상의 정수 | 필수 | 값은 숫자만 남기고 `length`로 자른다(붙여넣은 하이픈·공백 허용) |
67
+ | `value` · `defaultValue` | 숫자 문자열 | 비제어 `""` | 숫자가 아니거나 `length`보다 긴 제어값은 던진다 |
68
+ | `onValueChange` | `(value: string) => void` | — | 정리된 숫자 문자열 |
69
+ | `onComplete` | `(value: string) => void` | — | 값이 `length`에 **도달하는 순간** 한 번 호출된다. 처음부터 꽉 찬 값으로 마운트하면 호출되지 않는다 |
70
+ | `size` | `medium` · `large` | `medium` | |
71
+ | `presentation` | `boxes` · `underline` | `boxes` | |
72
+ | `busy` | `boolean` | `false` | 서버 확인 중. 입력은 포커스를 유지한 채 읽기 전용이 된다 |
73
+ | `description` · `error` | Web `ReactNode` · Native `string` | — | Native는 `undefined`를 받지 않아 조건부 spread로 넘긴다(위 예) |
74
+ | `layoutStyle` | 배치 전용 style 객체 | — | 필드 전체(라벨·칸) 배치. Native는 1.13부터 숨은 TextInput이 아니라 바깥 frame에 붙는다. Web `style`은 안쪽 input에 붙는다 |
75
+
76
+ 자동 채움은 고정이다: Web `autoComplete="one-time-code"`·`inputMode="numeric"`, Native `textContentType="oneTimeCode"`·`keyboardType="number-pad"`.
77
+
78
+ ## 배치
79
+
80
+ | 항목 | 값 | 근거 |
81
+ | --- | --- | --- |
82
+ | 크기 | 칸 `medium` 44(`control.minTouchTarget`) · `large` 52(`control.buttonHeight.large`). 전체 폭 최대 칸 × `length` + 간격 × (`length` − 1)(6자리 `medium` 304), 부모가 좁으면 칸이 줄어든다 | `otpFieldRecipe.sizes`, `.hjm-otp-field__control`, Native `maxWidth` |
83
+ | 간격 | 칸 사이 `medium` 8(`spacing.xs`) · `large` 12(`spacing.sm`). 라벨·칸·설명·오류 사이 `spacing.xs` 8 | `otpFieldRecipe`, `formSupportContract.gap` |
84
+ | 순서·정렬 | 안내 문구 아래, 다시 보내기·확인 행동 위. 칸 묶음은 시작 쪽, 가운데 정렬은 부모가 한다. RTL에서도 숫자는 왼쪽→오른쪽(`direction: ltr`) | `.hjm-otp-field__control`, Native `direction: "ltr"` |
85
+ | 고정·스크롤 | 본문 흐름 안. `onComplete`로 자동 확인하면 확인 버튼을 생략할 수 있다. 확인 버튼을 두면 단독 화면 폼은 [BottomCTA](bottom-cta.md), Card 안 구성은 [인증번호 확인과 다시 입력](../compositions/stea-otp-verify.md)처럼 필드 아래 본문에 둔다 | — |
86
+ | 좁은 폭·큰 글자 | Native 칸 높이 = max(칸 크기, 줄 높이 × 글자 배율 + 위아래 `spacing.xs`) | `react-native/src/inputs.tsx`(`OtpField`) |
87
+
88
+ ## 꼭 지킬 것
89
+
90
+ - 칸마다 별도 input을 만들거나 OtpField를 칸별로 쪼개 쓰지 않는다. 접근성 이름·값이 하나여야 한다([계약](../../otp-field.md)).
91
+ - `onComplete`에서 확인 요청을 보내고 `busy`로 잠근다. 실패하면 `error`에 지역화 문구를 넣고 값 초기화 여부는 제품이 정한다.
92
+ - 숫자가 아닌 `value`, `length`보다 긴 `value`를 제어값으로 넣으면 던진다.
93
+ - 배치는 `layoutStyle`(Web은 `className`도)로만 한다. 칸 색·테두리는 recipe 소유다.
94
+
95
+ ## 플랫폼 차이
96
+
97
+ | 항목 | Web | Native |
98
+ | --- | --- | --- |
99
+ | 이름 | `label` | `label` 또는 `accessibilityLabel`만 |
100
+ | 칸 스타일 통로 | 없음 | `slotStyle`, `slotTextStyle`은 deprecated(개발 모드 경고, 다음 major 제거). 외형은 `size`·`presentation` |
101
+ | busy 표현 | read-only + `aria-busy` | `editable={false}` + `accessibilityState.busy` |
@@ -0,0 +1,82 @@
1
+ # Pagination
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Pagination](../../pagination.md), `src/pagination.ts`(`paginationRecipe`)
9
+ - 스토리북: `배포/컴포넌트/탐색/페이지 이동`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 총 개수(또는 총 페이지 수)가 정해진 결과 집합에서 사용자가 **임의의 페이지로 바로 이동**해야 할 때
14
+ Web에서 쓴다. 검색 결과, 관리자 테이블, 기록 목록이 여기에 속한다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 총량을 모르거나 계속 이어지는 피드 | [LoadMore](load-more.md) |
21
+ | Native 긴 목록 | [LoadMore](load-more.md), [VirtualList](virtual-list.md) (Pagination은 Web 전용) |
22
+ | 단계형 흐름의 이전/다음 | [Steps](steps.md) |
23
+ | 이미지·카드 넘기기 | [Carousel](carousel.md) |
24
+
25
+ 한 목록에는 Pagination과 LoadMore 중 하나의 탐색 모델만 쓴다.
26
+
27
+ ## 공개 이름과 import
28
+
29
+ | 이름 | 역할 | Web | Native |
30
+ | --- | --- | --- | --- |
31
+ | `Pagination` | 기본 | `@hjmds/react`, `/navigation`, `/pagination` | — |
32
+
33
+ ## 최소 사용 예
34
+
35
+ ```tsx
36
+ // Web
37
+ import { Pagination } from "@hjmds/react/pagination";
38
+
39
+ <Pagination
40
+ label={t("records.pagination")}
41
+ descriptor={{ currentPage: page, totalCount: total, pageSize: 20 }}
42
+ labels={{ previous: t("pagination.previous"), next: t("pagination.next") }}
43
+ composeAccessibleName={({ page, totalPages, current }) =>
44
+ t(current ? "pagination.current" : "pagination.goTo", { page, totalPages })}
45
+ onPageChange={(next) => setPage(next)}
46
+ />
47
+ ```
48
+
49
+ Native 예는 없다(renderer 없음).
50
+
51
+ ## 축과 기본값
52
+
53
+ 타입은 `@hjmds/design-contracts/components/pagination`에서 가져온다.
54
+
55
+ | prop | 값 | 기본값 | 설명 |
56
+ | --- | --- | --- | --- |
57
+ | `label` | 문자열 | 필수 | `<nav>` 이름. 비면 던진다 |
58
+ | `descriptor` | `{ currentPage, totalCount, pageSize, siblingCount?, boundaryCount? }` 또는 `{ currentPage, totalPages, siblingCount?, boundaryCount? }` | 필수 | 정확히 하나의 형태. `currentPage`는 1부터 시작하는 정수 |
59
+ | `siblingCount` · `boundaryCount`(descriptor 안) | 정수 | 1 | 생략된 구간은 말줄임으로 표시한다 |
60
+ | `labels` | `{ previous: string; next: string }` | 필수 | 이전·다음 버튼 문구 |
61
+ | `composeAccessibleName` | `(info: { page: number; totalPages: number; current: boolean }) => string` | 필수 | 페이지 버튼마다 부른다. 어순은 제품 i18n이 정한다 |
62
+ | `onPageChange` | `(page: number, reason: "previous" \| "next" \| "page") => void` | 필수 | 제품이 `currentPage`를 바꾼다 |
63
+ | `className` · `layoutStyle` | 문자열 · 배치 전용 style 객체 | — | 루트 배치만 |
64
+
65
+ ## 배치
66
+
67
+ | 항목 | 값 | 근거 |
68
+ | --- | --- | --- |
69
+ | 크기 | 각 칸 최소 44×44(`control.minTouchTarget`), radius `radius.md` 12 | `paginationRecipe.item`, `.hjm-pagination__item` |
70
+ | 간격 | 칸 안쪽 `spacing.xs` 8, 칸 사이 `spacing.xxs` 4 | `paginationRecipe.gap`, `.hjm-pagination__list` |
71
+ | 순서·정렬 | `[‹ 이전] [1] … [4] [5] [6] … [20] [다음 ›]`. 이전·다음은 아이콘 버튼(문구는 접근성 이름), RTL에서 화살표 반전. 정렬은 놓는 레이아웃이 정한다(관리자 테이블은 끝, 검색 결과는 가운데가 흔하다) | `src/pagination.tsx`, `.hjm-pagination__previous`·`__next` |
72
+ | 고정·스크롤 | 목록·테이블 **바로 아래** 한 번. 페이지를 바꾸면 목록 시작으로 스크롤·포커스를 옮기는 것은 제품이 한다 | — |
73
+ | 좁은 폭·큰 글자 | 폭이 모자라면 줄바꿈된다(`flex-wrap`). 좁은 폭에서는 `siblingCount`·`boundaryCount`를 0~1로 줄여 한 줄을 유지한다 | `.hjm-pagination__list` |
74
+
75
+ ## 꼭 지킬 것
76
+
77
+ - `label`(nav 이름)은 비어 있으면 던진다. `labels`와 `composeAccessibleName`이 만드는 페이지 이름까지 모두 제품 i18n에서 만든다.
78
+ 어순·조사는 제품이 조립하고, 현재 페이지 여부·총 페이지 계산은 HJM이 넘겨 준다.
79
+ - `currentPage`는 제어값이다. `onPageChange`에서 제품 상태를 바꾸고 데이터 요청·URL 동기화는 제품이 한다.
80
+ - 범위 밖 `currentPage`(0 이하, 총 페이지 초과)는 던진다. 결과 개수가 줄면 페이지를 먼저 보정한다.
81
+ - 페이지 크기 변경·페이지 번호 직접 입력은 없다. 필요하면 별도 컴포넌트로 옆에 합성한다.
82
+ - `className`·`layoutStyle`은 배치에만 쓴다.
@@ -0,0 +1,137 @@
1
+ # PasswordField
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [PasswordField](../../password-field.md), `src/password-field.ts`(`passwordFieldRecipe`)
9
+ - 스토리북: `배포/컴포넌트/입력/비밀번호 입력`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 비밀번호를 입력받고, 필요할 때만 값을 눈으로 확인하게 할 때 쓴다. 로그인, 가입,
14
+ 비밀번호 변경, 이메일 계정 연결 화면의 비밀번호 칸이 여기에 속한다.
15
+
16
+ 소셜 전용 제품의 이메일·비밀번호 칸은 스토어 심사자 폼뿐이다(루트 `docs/LOGIN_SCREEN_STANDARD.md` LS-07). 서버 env와
17
+ `review=1`이 모두 맞을 때만 [AuthScreenLayout](auth-screen-layout.md) `main` 카드 안, 개발 버튼 다음에 제목
18
+ (`Text variant="label"`)과 한 줄 설명을 붙여 그린다. 제출 버튼은 제공자 버튼보다 낮은 `tone="secondary"`, `size="medium"`이다.
19
+
20
+ ## 쓰지 않을 때
21
+
22
+ | 상황 | 대신 쓸 것 |
23
+ | --- | --- |
24
+ | 문자로 받은 숫자 인증번호 | [OtpField](otp-field.md) |
25
+ | 가릴 필요가 없는 일반 텍스트 | [Field](field.md)의 TextField |
26
+ | 소셜 로그인 | [AuthProviderButton](auth-provider-button.md) |
27
+
28
+ ## 공개 이름과 import
29
+
30
+ | 이름 | 역할 | Web | Native |
31
+ | --- | --- | --- | --- |
32
+ | `PasswordField` | 기본 | `@hjmds/react`, `/forms`, `/password-field` | `@hjmds/react-native`, `/inputs`, `/password-field` |
33
+
34
+ ## 최소 사용 예
35
+
36
+ ```tsx
37
+ // Web
38
+ import { PasswordField } from "@hjmds/react/password-field";
39
+
40
+ <PasswordField
41
+ label={t("auth.password")}
42
+ autofillHint="current"
43
+ revealLabel={t("auth.password.show")}
44
+ concealLabel={t("auth.password.hide")}
45
+ value={password}
46
+ onValueChange={setPassword}
47
+ error={passwordError}
48
+ />
49
+ ```
50
+
51
+ ```tsx
52
+ // Native — 로그인 폼: return 키로 아이디 → 비밀번호 → 제출(LS-10)
53
+ import { useRef } from "react";
54
+ import type { TextInput } from "react-native";
55
+ import { TextField } from "@hjmds/react-native/inputs";
56
+ import { PasswordField } from "@hjmds/react-native/password-field";
57
+
58
+ const passwordRef = useRef<TextInput>(null);
59
+
60
+ <>
61
+ <TextField
62
+ label={t("auth.email")}
63
+ keyboardType="email-address"
64
+ autoCapitalize="none"
65
+ textContentType="username"
66
+ returnKeyType="next"
67
+ onSubmitEditing={() => passwordRef.current?.focus()}
68
+ value={email}
69
+ onValueChange={setEmail}
70
+ />
71
+ <PasswordField
72
+ ref={passwordRef}
73
+ label={t("auth.password")}
74
+ autofillHint="current"
75
+ revealLabel={t("auth.password.show")}
76
+ concealLabel={t("auth.password.hide")}
77
+ returnKeyType="go"
78
+ onSubmitEditing={() => { if (canSubmit) void submit(); }}
79
+ value={password}
80
+ onValueChange={setPassword}
81
+ {...(passwordError ? { error: passwordError } : {})}
82
+ />
83
+ </>
84
+ ```
85
+
86
+ ## 축과 기본값
87
+
88
+ | prop | 값 | 기본값 | 설명 |
89
+ | --- | --- | --- | --- |
90
+ | `value` · `defaultValue` | 문자열 | 비제어 `""` | |
91
+ | `onValueChange` | `(value: string) => void` | — | Web은 DOM `onChange`도 함께 받는다 |
92
+ | `autofillHint` | `current`(로그인) · `new`(가입·변경) | 필수 | Web은 `current-password`/`new-password`, Native는 iOS `textContentType` `password`/`newPassword`로 번역된다 |
93
+ | `revealLabel` · `concealLabel` | 문자열 | 필수 | 토글의 접근성 이름. 각각 "누르면 보임"·"누르면 숨김" 행동 문구 |
94
+ | `revealed` · `defaultRevealed` | `boolean` | `false` | 가림 상태(제어·비제어). 값과 독립된 축이다 |
95
+ | `onRevealedChange` | `(revealed: boolean) => void` | — | 토글을 누를 때 |
96
+ | `size` | `medium` · `large` | `medium` | |
97
+ | `renderToggleIcon` | `(props: { name: "visibility" \| "visibilityOff"; color; size: number; revealed: boolean; disabled: boolean }) => ReactNode` | HJM 기본 아이콘 | 토글 아이콘을 바꿀 때 쓴다. `color`는 Web `"currentColor"`, Native 테마 색 문자열 |
98
+ | `description` · `error` | Web `ReactNode` · Native `string` | — | 규칙 안내·검증 실패 문구 |
99
+ | `layoutStyle` | 배치 전용 style 객체 | — | 필드 전체 배치. Web `style`은 안쪽 input에 붙는다 |
100
+ | `ref` | Web `HTMLInputElement` · Native `TextInput` | — | return 키로 다음 칸 focus를 옮길 때 쓴다 |
101
+
102
+ ## 배치
103
+
104
+ | 항목 | 값 | 근거 |
105
+ | --- | --- | --- |
106
+ | 크기 | 높이 `medium` 44(`fieldFrameContract.minHeight`) · `large` 52(`control.buttonHeight.large`). 토글 44×44 원형(`control.minTouchTarget`) | `passwordFieldRecipe.sizes`, `.hjm-password-field__toggle` |
107
+ | 간격 | 라벨·입력·설명·오류 사이 `spacing.xs` 8. 입력 좌우 `medium` 16(`spacing.md`) · `large` 20(`spacing.lg`) | `formSupportContract.gap`, `passwordFieldRecipe.sizes` |
108
+ | 순서·정렬 | 아이디(이메일) Field 바로 아래. 가입·변경은 새 비밀번호 → 확인. 토글은 입력 칸 **끝**(RTL에서는 시작) 안쪽 | `.hjm-password-field__toggle` |
109
+ | 고정·스크롤 | 폼 흐름 안. 제출은 이 칸 아래, 같은 폼의 행동이다(Web [Form](form.md) `actions`, Native는 Form 내장 버튼 또는 `Stack` + `Button`). 로그인·심사자 폼은 AuthScreenLayout `main` 카드 안에 두고 하단에 고정하지 않는다. 그 안에서는 바깥 `ScrollView`·`Container`·`KeyboardAvoidingView`로 다시 감싸지 않는다(키보드 inset·드래그 내리기·탭 유지는 레이아웃이 가진다, LS-10) | LS-07, LS-10, `authScreenRecipe` |
110
+ | 좁은 폭·큰 글자 | 폭은 같은 폼의 다른 Field와 맞춘다 | — |
111
+
112
+ ## 꼭 지킬 것
113
+
114
+ - `revealLabel`(가려져 있을 때 = 누르면 보임)과 `concealLabel`(보일 때 = 누르면 숨김)은 **행동** 문구로 지역화한다. “숨겨짐” 같은 상태 문구를 넣지 않는다.
115
+ - `autofillHint`는 화면 목적에 맞게 제품이 정한다. 로그인 화면에 `new`를 주면 OS·브라우저 자동 채움이 깨진다.
116
+ - `type`·`autoComplete`(Web), `secureTextEntry`·`textContentType`(Native)은 Props에서 제외돼 있다. 직접 넘기지 않는다.
117
+ - 비밀번호 규칙 문구는 `description`, 검증 실패는 `error`에 지역화해 넣는다. 규칙 자체는 제품 소유다.
118
+ - 배치는 `layoutStyle`(Web은 `className`/`fieldClassName`도)로만 한다.
119
+ - 토글에 눌림·선택 상태를 덧붙이지 않는다. 이름이 이미 다음 행동("비밀번호 보이기/숨기기")을 말하므로 상태를 더하면
120
+ "비밀번호 숨기기, 선택됨"처럼 두 답이 읽힌다(계약 [PasswordField](../../password-field.md) 2026-10-06 정정).
121
+ - Native 로그인 폼은 return 키를 잇는다(LS-10). 아이디 칸은 `returnKeyType="next"` + `onSubmitEditing`에서 PasswordField
122
+ `ref.focus()`, PasswordField는 `returnKeyType="go"` + `onSubmitEditing`에서 제출하되 버튼의 disabled 조건을 그대로 따른다.
123
+ Native `Form`은 밖에서 제출을 부를 수 없으므로 return 제출이 필요한 폼은 Form 대신 `Stack` + `Button`(`loading`)으로 짠다.
124
+
125
+ ## 플랫폼 차이
126
+
127
+ | 항목 | Web | Native |
128
+ | --- | --- | --- |
129
+ | 이름 | `label` | `label` 또는 `accessibilityLabel`만 |
130
+ | 토글 | 다음 행동을 이름으로 가진 `<button>`(상태 속성 `aria-pressed` 없음), 토글 후 선택 영역 복원 | `button` + 다음 행동 `accessibilityLabel`, `accessibilityState`는 `disabled`만(`selected` 없음) |
131
+ | 이벤트 | `onValueChange`와 DOM `onChange` 둘 다 | `onValueChange` |
132
+ | ref·나머지 props | `HTMLInputElement`, `<input>` HTML 속성(`type`·`autoComplete` 제외) | `TextInput`, RN `TextInput` props(`returnKeyType`·`onSubmitEditing` 등, `secureTextEntry`·`textContentType`·`style` 제외) |
133
+
134
+ ## 함정
135
+
136
+ - Native `error`·`description`은 `string`이라 `exactOptionalPropertyTypes`에서 `undefined`를 받지 않는다(Web은 `ReactNode`라 통과).
137
+ 값이 없을 수 있으면 위 예처럼 조건부 spread로 넘긴다.
@@ -0,0 +1,107 @@
1
+ # PermissionScreen
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 미게시(1.12.1 이후)
7
+ - 검토일: 2026-10-06
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
+ - 스토리북: `배포/화면/소개/권한 안내`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 카메라·위치·알림 같은 권한이 **왜 필요한지 설명하고 다음 행동을 고르게 하는** 화면에 쓴다.
14
+ 제품이 넘긴 권한 상태에 따라 하단 주 행동 하나를 고른다.
15
+
16
+ | `status` | 주 행동 |
17
+ | --- | --- |
18
+ | `prompt` | `request` |
19
+ | `denied` | `settings` |
20
+ | `granted` | `continueAction` |
21
+ | `unavailable` | 없음(`skip`만 표시 가능) |
22
+
23
+ PermissionScreen은 **OS 권한을 요청하지도, 조회하지도 않는다.** 소스(`screen-flows.tsx`, 두 renderer)는
24
+ `react`·`react-native`의 `View` 외에 권한·설정 API를 import하지 않고, 버튼은 넘겨받은 `onAction`만 호출한다.
25
+ 실제 권한 요청, 설정 앱 열기, 앱 복귀 후 권한 재조회와 `status` 갱신은 모두 제품이 한다.
26
+
27
+ ## 쓰지 않을 때
28
+
29
+ | 상황 | 대신 쓸 것 |
30
+ | --- | --- |
31
+ | 화면 일부에 “권한이 꺼져 있음” 안내 | [Notice](notice.md) |
32
+ | 접근 제한으로 본문을 대체 | [ScreenLayout](screen-layout.md)의 `state={{ kind: "restricted", ... }}` |
33
+ | 사진 앨범·촬영 선택 | [PhotoSourceSheet](photo-source-sheet.md) |
34
+ | 여러 단계 첫 실행 소개 | [OnboardingScreen](onboarding-screen.md) |
35
+
36
+ ## 공개 이름과 import
37
+
38
+ | 이름 | 역할 | Web | Native |
39
+ | --- | --- | --- | --- |
40
+ | `PermissionScreen` | supplemental, 루트 barrel에 없음 | `/screen-flows` | `/screen-flows` |
41
+
42
+ `@hjmds/react/screen-flows`, `@hjmds/react-native/screen-flows`로만 import한다. 추가 optional peer는 없다.
43
+
44
+ ## 최소 사용 예
45
+
46
+ ```tsx
47
+ // Web — 브라우저는 설정 화면을 열 수 없으므로 settings는 브라우저 권한 안내로 연결한다
48
+ import { Text } from "@hjmds/react/layout";
49
+ import { PermissionScreen } from "@hjmds/react/screen-flows";
50
+
51
+ <PermissionScreen
52
+ title={t("permission.camera.title")}
53
+ status={cameraStatus /* 제품이 navigator.permissions·getUserMedia 결과로 매핑 */}
54
+ illustration={cameraArt}
55
+ explanation={<Text>{t("permission.camera.why")}</Text>}
56
+ request={{ label: t("permission.allow"), onAction: requestCamera, pending: requesting }}
57
+ settings={{ label: t("permission.browserHelp"), onAction: openBrowserPermissionHelp }}
58
+ continueAction={{ label: t("common.continue"), onAction: goNext }}
59
+ skip={{ label: t("common.later"), onAction: goNext }}
60
+ />
61
+ ```
62
+
63
+ ```tsx
64
+ // Native
65
+ import { Text } from "@hjmds/react-native/primitives";
66
+ import { PermissionScreen } from "@hjmds/react-native/screen-flows";
67
+ import { Linking } from "react-native";
68
+
69
+ <PermissionScreen
70
+ title={t("permission.camera.title")}
71
+ status={cameraStatus /* 제품이 OS에서 조회한 값 */}
72
+ illustration={cameraArt}
73
+ explanation={<Text>{t("permission.camera.why")}</Text>}
74
+ request={{ label: t("permission.allow"), onAction: requestCamera, pending: requesting }}
75
+ settings={{ label: t("permission.openSettings"), onAction: () => Linking.openSettings() }}
76
+ continueAction={{ label: t("common.continue"), onAction: goNext }}
77
+ skip={{ label: t("common.later"), onAction: goNext }}
78
+ />
79
+ ```
80
+
81
+ ### 제품이 공급할 것
82
+
83
+ | prop | 내용 |
84
+ | --- | --- |
85
+ | `status` | `prompt` · `denied` · `granted` · `unavailable`. OS 조회 결과를 제품이 매핑한다 |
86
+ | `explanation` | 권한이 필요한 이유(필수, 지역화) |
87
+ | `illustration` | 선택. 제품 일러스트 |
88
+ | `request`, `settings`, `continueAction` | 세 행동 모두 필수 `{ label, onAction, disabled?, pending? }`. 상태에 맞는 하나만 보인다 |
89
+ | `skip` | 선택 보조 행동 |
90
+ | ScreenLayout props | `title`, `description`, `header`, `leading`, `notice`, `state` 등(`footer` 제외) |
91
+
92
+ ## 배치
93
+
94
+ | 항목 | 값 | 근거 |
95
+ | --- | --- | --- |
96
+ | 크기 | ScreenLayout 폭(최대 720); 그림 크기는 `illustration`(제품) 소유; 행동은 `Button` 기본 크기 | `PermissionScreen` |
97
+ | 간격 | 화면 padding `spacing.md` 16; 본문 그림–설명 `spacing.xl` 24(가운데 정렬); footer 주 행동–나중에 `spacing.sm` 12 | Web·Native `PermissionScreen` `Stack gap="xl" align="center"`·`gap="sm"` |
98
+ | 순서·정렬 | 헤더(제목) → 본문(그림 → 설명, 가운데) → footer(상태별 주 행동 primary → 나중에 ghost) | 렌더 순서, `resolvePermissionAction` |
99
+ | 고정·스크롤 | 헤더·footer 고정, 본문 화면 스크롤; OS 권한 창은 제품이 `request.onAction`에서 호출 | `ScreenLayout` |
100
+ | 좁은 폭·큰 글자 | 설명은 줄바꿈되고 본문이 스크롤된다; footer 버튼은 세로로 쌓인다; `unavailable`이면 주 행동 없이 `skip`만 남는다 | `PermissionScreen` |
101
+
102
+ ## 꼭 지킬 것
103
+
104
+ - 설정 앱에서 돌아온 뒤 권한을 다시 조회해 `status`를 갱신한다. HJM은 앱 복귀를 감지하지 않는다.
105
+ - 렌더링만으로 권한 요청을 띄우지 않는다. 요청은 사용자가 `request` 버튼을 눌렀을 때만 제품이 실행한다.
106
+ - 알 수 없는 `status` 값은 던진다.
107
+ - 권한 설명 문구·스토어 심사용 사용 목적 문자열은 제품 소유다.