@hjmds/design-contracts 1.12.0 → 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,86 @@
1
+ # Spinner
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: `src/component-recipes.ts`(`spinnerRecipe`)
9
+ - 스토리북: `배포/컴포넌트/상태와 알림/로딩 표시`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 진행량을 모르고 도착할 내용의 모양도 정해지지 않은 **짧은 대기**를 한 자리에서 알릴 때 쓴다.
14
+ 패널 하나를 다시 불러오는 동안, 작은 영역의 결과를 기다리는 동안이 여기에 속한다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 도착할 목록·카드의 모양을 알고 있음 | [Skeleton](skeleton.md) |
21
+ | 진행량을 알거나 긴 작업(업로드·내보내기) | [Progress](progress.md) |
22
+ | 버튼을 누른 뒤의 처리 중 | [Button](button.md)의 `loading`(같은 자리에 Spinner를 겹치지 않는다) |
23
+ | 목록 끝에서 다음 페이지 | [LoadMore](load-more.md) |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `Spinner` | 기본 | `@hjmds/react`, `/feedback` | `@hjmds/react-native`, `/feedback` |
30
+
31
+ ## 최소 사용 예
32
+
33
+ ```tsx
34
+ // Web
35
+ import { Spinner } from "@hjmds/react/feedback";
36
+
37
+ <Spinner label={t("feed.loading")} />
38
+ ```
39
+
40
+ ```tsx
41
+ // Native
42
+ import { Spinner } from "@hjmds/react-native/feedback";
43
+
44
+ <Spinner label={t("feed.loading")} size="large" />
45
+ ```
46
+
47
+ ## 축과 기본값
48
+
49
+ | prop | 값 | 기본값 | 설명 |
50
+ | --- | --- | --- | --- |
51
+ | `label` | `string` | — (필수) | 화면에는 보이지 않고 보조기기에만 읽힌다 |
52
+ | `size`(Web) | `small` · `medium` · `large` | `medium` | — |
53
+ | `tone`(Web) | `brand` · `neutral` · `inverse` | `brand` | — |
54
+ | `size`(Native) | `small` · `large` | `small` | 플랫폼 `ActivityIndicator`를 테마 `contentBrand` 색으로 그린다. `tone`은 없다 |
55
+ | `layoutStyle` | `HjmCompositionStyleProp`(margin·width·flex 계열·`alignSelf`) | — | 루트 배치. Web·Native 모두 |
56
+ | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle` 또는 `size`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
57
+
58
+ ## 배치
59
+
60
+ | 항목 | 값 | 근거 |
61
+ | --- | --- | --- |
62
+ | 크기 | Web `small` 14 · `medium` 20(기본) · `large` 28(`glyph.xs`·`glyph.sm`·`glyph.lg`, 테두리 `stroke.strong` 2). Native는 플랫폼 `ActivityIndicator`의 `small`(기본)·`large`이고 HJM 숫자를 쓰지 않는다 | `spinnerRecipe`, `styles.css` `.hjm-spinner`·`.hjm-spinner__glyph`, `react-native/src/feedback.tsx` |
63
+ | 간격 | — | — |
64
+ | 순서·정렬 | 기다리는 영역의 가운데에 하나만 둔다. Web·Native 모두 루트가 가운데 정렬(`align-items`·`justify-content: center`)이므로 부모가 영역 크기를 잡아 주면 된다. 화면 전체 대기는 `large`, 카드·행 안의 대기는 `small` | `styles.css` `.hjm-spinner`, `react-native/src/feedback.tsx` |
65
+ | 고정·스크롤 | — | — |
66
+ | 좁은 폭·큰 글자 | — | — |
67
+
68
+ 버튼 안 대기는 Spinner를 겹치지 않고 [Button](button.md)의 `loading`을 쓴다.
69
+
70
+ ## 꼭 지킬 것
71
+
72
+ - `label`은 i18n 키로 넣고 "무엇을" 기다리는지 적는다("불러오는 중"만 반복하지 않는다).
73
+ - 한 영역에 Spinner는 하나다. 목록 행마다 Spinner를 두면 대신 Skeleton을 쓴다.
74
+ - 대기가 끝나면 Spinner를 결과(내용·[EmptyState](empty-state.md)·오류)로 바꾼다.
75
+ - 색은 Web `tone`과 테마로만 바꾼다. 어두운 배경 위에서는 `tone="inverse"`를 쓴다.
76
+ - 배치(바깥 여백·정렬)는 `layoutStyle`로만 한다. Native `style`은 쓰지 않는다.
77
+
78
+ ## 플랫폼 차이
79
+
80
+ | 항목 | Web | Native |
81
+ | --- | --- | --- |
82
+ | 역할 | `role="status"`, `aria-live="polite"` | `accessibilityRole="progressbar"`, `busy: true` |
83
+ | 크기 | 3단계 | `small`·`large` 2단계 |
84
+ | tone | 있음 | 없음(항상 brand 색) |
85
+ | reduced motion | 회전 멈춤(CSS) | 별도 처리 없음(`ActivityIndicator` 그대로) |
86
+ | `style` | HTML 속성(`layoutStyle`과 합쳐지고 `layoutStyle`이 이김) | deprecated |
@@ -0,0 +1,103 @@
1
+ # Splitter
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Splitter](../../splitter.md), `src/splitter.ts`(`splitterRecipe`)
9
+ - 스토리북: `배포/컴포넌트/레이아웃/분할 영역 조절`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 넓은 Web 화면에서 두 영역의 경계를 사용자가 드래그나 키보드로 옮겨 크기를 정할 때 쓴다.
14
+ 파일 트리/편집기 폭, 목록/상세 폭처럼 사용자가 정한 크기가 다시 방문해도 남길 바라는 레이아웃이다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 개발자가 정한 고정 비율의 나란한 배치 | [Grid](grid.md), [Stack](stack.md) |
21
+ | 옆에서 열고 닫는 보조 패널 | [SidePanel](side-panel.md), [Sidebar](sidebar.md) |
22
+ | 패널 접기(collapse), 분리선 여러 개 | 없음(계약이 넣지 않았다) |
23
+ | 범위 안의 값 하나 고르기 | [Slider](slider.md) |
24
+ | Native 화면 | 없음 |
25
+
26
+ ## 공개 이름과 import
27
+
28
+ | 이름 | 역할 | Web | Native |
29
+ | --- | --- | --- | --- |
30
+ | `Splitter` | 기본 | `@hjmds/react`, `/splitter` | — |
31
+
32
+ ## 최소 사용 예
33
+
34
+ ```tsx
35
+ // Web
36
+ import { Splitter } from "@hjmds/react/splitter";
37
+
38
+ <Splitter
39
+ label={t("editor.resizeTree")}
40
+ min={15}
41
+ max={60}
42
+ step={5}
43
+ value={treeWidth}
44
+ onValueChange={setTreeWidth}
45
+ onValueChangeEnd={persistTreeWidth}
46
+ getValueText={(v) => t("editor.percent", { value: v })}
47
+ primaryPane={<FileTree />}
48
+ secondaryPane={<Editor />}
49
+ />
50
+ ```
51
+
52
+ Native: 없음.
53
+
54
+ ## 축과 기본값
55
+
56
+ | prop | 값 | 기본값 | 설명 |
57
+ | --- | --- | --- | --- |
58
+ | `label`·`min`·`max`·`primaryPane`·`secondaryPane` | — | — (필수) | — |
59
+ | `step` | `number` | `1` | — |
60
+ | `axis` | `horizontal` · `vertical` | `horizontal` | `horizontal`은 패널이 좌우로 나란하고 분리선이 세로, `vertical`은 위아래 |
61
+ | `value`/`defaultValue` | `number` | 둘 다 없으면 `min` | `primaryPane`이 값만큼, `secondaryPane`이 나머지를 차지한다 |
62
+ | `onValueChange` | `(value: number) => void` | — | 드래그 중 매번 |
63
+ | `onValueChangeEnd` | `(value: number) => void` | — | 드래그를 놓을 때와 값이 실제로 바뀐 키보드 step마다 한 번 |
64
+ | `getValueText` | `(value: number) => string` | — | 분리선의 `aria-valuetext`. 없으면 숫자만 읽힌다 |
65
+ | `disabled` | `boolean` | `false` | — |
66
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치(margin·width·flex 계열·`alignSelf`) |
67
+ | `className`, `style` | `string`, `CSSProperties` | — | 루트에 붙는다. `style`과 `layoutStyle`이 겹치면 `layoutStyle`이 이긴다 |
68
+
69
+ ## 배치
70
+
71
+ | 항목 | 값 | 근거 |
72
+ | --- | --- | --- |
73
+ | 크기 | `primaryPane`이 값만큼 고정 폭(`flex: 0 0`), `secondaryPane`이 나머지를 채운다. 분리선은 보이는 선 1(`stroke.default`, `border` 색), 잡는 영역 44(`control.minTouchTarget`) | `splitterRecipe.separator`, `styles.css` `.hjm-splitter*`, `react/src/splitter.tsx` |
74
+ | 간격 | 패널 사이 간격은 분리선의 44 영역이 대신하므로 패널에 따로 바깥 여백을 더하지 않는다 | `styles.css` `.hjm-splitter__separator` |
75
+ | 순서·정렬 | 넓은 Web 화면의 본문 영역을 둘로 나눈다. `horizontal`은 primary가 시작(왼쪽), `vertical`은 위 | `styles.css` `.hjm-splitter` |
76
+ | 고정·스크롤 | 두 패널은 각자 스크롤한다(`overflow: auto`). 부모가 높이를 정해야 세로 스크롤이 생긴다. 내용이 넘칠 때만 그 패널이 Tab 정지점(`tabIndex=0`)이 되어 키보드로 스크롤할 수 있고, 넘치지 않으면 정지점이 없다 | `styles.css` `.hjm-splitter__pane`, `splitter.tsx` `useScrollableTabStop` |
77
+ | 좁은 폭·큰 글자 | `min`은 좁은 쪽 패널 내용이 깨지지 않는 폭으로 정한다. 폭이 `breakpoint.medium`(600) 아래인 화면에서는 Splitter를 그리지 말고 [Stack](stack.md)으로 위아래로 쌓거나 한 패널만 보인다(HJM은 자동 전환하지 않는다) | `foundations.ts` `breakpoint` |
78
+
79
+ ```text
80
+ axis="horizontal"(기본)
81
+ ┌──────────────┬┬──────────────────────────┐
82
+ │ primaryPane ││ secondaryPane │
83
+ │ (값만큼 고정)││ (나머지, 각자 스크롤) │
84
+ │ ││ │
85
+ └──────────────┴┴──────────────────────────┘
86
+ └┘ 분리선: 선 1 · 잡는 영역 44
87
+ ```
88
+
89
+ ## 꼭 지킬 것
90
+
91
+ - 크기 저장은 `onValueChangeEnd`에서 한다. 경계값에서 더 누른 키는 end를 보내지 않는다.
92
+ - `label`과 `getValueText`는 i18n 문구로 준다. 단위(%·px) 표현은 제품 소유다.
93
+ - 분리선 두께·44px hit target·핸들 모양은 HJM 소유다. 배치는 `layoutStyle`로 하고 분리선을 덮지 않는다.
94
+ - 좁은 화면(모바일 Web)에서 쓸지는 제품이 판단한다. 계약은 데스크톱 패턴으로 정의한다.
95
+
96
+ ## 함정
97
+
98
+ - 키보드는 pane 축 방향키만 쓴다(가로면 좌/우, 세로면 위/아래). 다른 방향키와 PageUp/PageDown은 pane 콘텐츠의 것이다.
99
+ - 패널의 Tab 정지점은 넘침에 따라 생기고 사라진다(ResizeObserver·MutationObserver로 다시 잰다). 테스트에서 패널 `tabIndex`를 고정값으로 기대하지 않는다.
100
+ 패널에는 접근성 이름이 없다(이름 prop은 breaking 변경이라 두지 않았다, `splitter.tsx` 주석). 미게시(1.12.1 이후) 변경이며 1.12.1까지는 패널이 포커스를 받지 않았다.
101
+ - RTL에서는 ArrowLeft가 increment다. 드래그 거리도 같은 기준으로 잰다.
102
+ - primary pane 폭은 `value`가 아니라 `(value-min)/(max-min)` 비율로 그려진다. `value=min`이면 primary pane이 0%, `max`면 100%다.
103
+ 예를 들어 `min=15, max=60, value=30`은 33%로 그려진다. 원하는 실제 폭 범위에 맞춰 `min`/`max`를 정하고 화면에서 확인한다.
@@ -0,0 +1,93 @@
1
+ # Stack
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Flex·Space와의 관계](../../layout-primitives.md), `src/component-recipes.ts`(`stackRecipe`)
9
+ - 스토리북: `배포/컴포넌트/레이아웃/가로·세로 배치`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 자식들을 한 방향으로 늘어놓고 사이 간격을 토큰으로 맞출 때 쓴다. 폼 필드 세로 나열, 버튼 두 개의
14
+ 가로 배치, 카드 안 제목·설명 묶음이 여기에 속한다. antd `Flex`·`Space`에 해당하는 자리도 Stack이다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 2차원 열·반응형 칸 | [Grid](grid.md) |
21
+ | 높이가 제각각인 카드 벽 | [Masonry](masonry.md) |
22
+ | 콘텐츠 최대 폭·좌우 여백 | [Container](container.md) |
23
+ | 배경·테두리·radius가 있는 상자 | [Surface](surface.md), [Card](card.md) |
24
+ | 항목 사이 구분선 | Stack 안에 [Divider](divider.md) |
25
+ | 헤더·사이드바·본문 화면 골격 | [Layout](layout.md) |
26
+ | 제목이 있는 본문 묶음 | [Section](section.md) |
27
+
28
+ ## 공개 이름과 import
29
+
30
+ | 이름 | 역할 | Web | Native |
31
+ | --- | --- | --- | --- |
32
+ | `Stack` | 기본 | `@hjmds/react`, `/layout` | `@hjmds/react-native`, `/primitives` |
33
+
34
+ ## 최소 사용 예
35
+
36
+ ```tsx
37
+ // Web
38
+ import { Button } from "@hjmds/react/actions";
39
+ import { Stack } from "@hjmds/react/layout";
40
+
41
+ <Stack axis="inline" gap="xs" justify="end">
42
+ <Button tone="secondary" onClick={cancel}>{t("common.cancel")}</Button>
43
+ <Button onClick={save}>{t("common.save")}</Button>
44
+ </Stack>
45
+ ```
46
+
47
+ ```tsx
48
+ // Native
49
+ import { Heading } from "@hjmds/react-native/heading";
50
+ import { Stack, Text } from "@hjmds/react-native/primitives";
51
+
52
+ <Stack gap="sm">
53
+ <Heading level="level4" semanticLevel={2}>{t("profile.title")}</Heading>
54
+ <Text tone="muted">{t("profile.description")}</Text>
55
+ </Stack>
56
+ ```
57
+
58
+ ## 축과 기본값
59
+
60
+ | prop | 값 | 기본값 | 설명 |
61
+ | --- | --- | --- | --- |
62
+ | `axis` | `block` · `inline` | `block` | `block` 세로, `inline` 가로 |
63
+ | `gap` | `xxs`(4) · `xs`(8) · `sm`(12) · `md`(16) · `lg`(20) · `xl`(24) · `xxl`(32) · `xxxl`(40) | `md` | spacing 토큰 |
64
+ | `align` | `start` · `center` · `end` · `stretch` | `stretch` | — |
65
+ | `justify` | `start` · `center` · `end` · `between` | `start` | — |
66
+ | `wrap` | `boolean` | `false` | — |
67
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | Stack 자신의 바깥 여백·폭·flex·`alignSelf` |
68
+
69
+ ## 배치
70
+
71
+ | 항목 | 값 | 근거 |
72
+ | --- | --- | --- |
73
+ | 크기 | — | — |
74
+ | 간격 | 형제 사이 간격만 정한다. 화면 섹션 사이 `xl` 24(`layout.sectionGap`), 섹션 안 내용 사이 `md` 16(`layout.contentGap`, 기본), 관련 묶음(라벨과 값, 나란한 버튼) `sm` 12·`xs` 8. 이 밖의 숫자를 새로 만들지 않는다. 바깥 여백은 부모(화면 여백 `layout.pagePadding` compact 16 · regular 20 · spacious 24)가 정한다 | `stackRecipe`, `foundations.ts` `layout` |
75
+ | 순서·정렬 | 가로로 버튼을 나열할 때 주 행동 순서는 [Button](button.md#배치)을 따른다 | — |
76
+ | 고정·스크롤 | — | — |
77
+ | 좁은 폭·큰 글자 | 가로(`axis="inline"`)로 버튼·칩을 나열해 좁은 폭에서 넘치면 `wrap`을 켠다 | `stackRecipe.defaults.wrap` |
78
+
79
+ ## 꼭 지킬 것
80
+
81
+ - 간격은 `gap` 토큰으로만 정한다. 자식마다 margin을 붙여 간격을 만들지 않는다.
82
+ - Stack 자체의 배치(바깥 여백·폭·flex)는 `layoutStyle`로 한다. Stack은 배경·테두리를 갖지 않으므로
83
+ 상자가 필요하면 Surface로 감싼다.
84
+ - 시각 순서는 DOM/자식 순서와 같게 둔다. 순서를 뒤집는 CSS로 읽기 순서를 바꾸지 않는다.
85
+
86
+ ## 플랫폼 차이
87
+
88
+ | 항목 | Web | Native |
89
+ | --- | --- | --- |
90
+ | `gap` 타입 | 토큰만 | 토큰 또는 숫자 |
91
+ | 방향 | CSS 상속 | provider `environment.direction`을 `direction`으로 적용 |
92
+ | import 경로 | `/layout` | `/primitives` |
93
+ | ref | `forwardRef`(`div`) | 없음 |
@@ -0,0 +1,123 @@
1
+ # Statistic
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Statistic](../../statistic.md), [optional adapters](../../optional-adapters.md), `src/component-recipes.ts`(`statisticRecipe`)
9
+ - 스토리북: `배포/컴포넌트/데이터 표시/수치 표시`, `배포/컴포넌트/데이터 표시/움직이는 수치`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 라벨이 붙은 **숫자 지표 하나**(또는 여러 개)를 보여 줄 때 쓴다. 오늘 기록 수, 승률, 잔액,
14
+ 전주 대비 증감처럼 값·단위·추세·보조 설명이 한 묶음인 자리다. 여러 지표를 나란히 두면 `StatisticGroup`을 쓴다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 항목 이름과 값이 짝인 속성 목록(숫자가 주인공이 아님) | [DescriptionList](description-list.md) |
21
+ | 진행률·완료 비율 | [Progress](progress.md) |
22
+ | 아이콘·탭 옆의 작은 개수 | [CounterBadge](counter-badge.md), [Badge](badge.md) |
23
+ | 행·열이 있는 수치 표 | [DataTable](data-table.md) |
24
+ | 기간별 활동 밀도 | [ActivityHeatmap](activity-heatmap.md) |
25
+ | 누르면 이동하는 지표 | 바깥을 [Link](link.md)·[Button](button.md)으로 감싼다(Statistic은 interactive하지 않다) |
26
+
27
+ ## 공개 이름과 import
28
+
29
+ | 이름 | 역할 | Web | Native |
30
+ | --- | --- | --- | --- |
31
+ | `Statistic` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
32
+ | `StatisticGroup` | 동반(1~4열 묶음) | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
33
+ | `AnimatedStatistic` | 확장(값 변화 모션, optional-extension) | `/statistic-motion` | `/statistic-motion` |
34
+
35
+ `AnimatedStatistic`은 granular subpath로만 import 된다. Web은 optional peer `@number-flow/react` 0.6.2를
36
+ 앱에 설치해야 한다. Native는 추가 peer 없이 HJM `ContentTransition`(RN `Animated`)으로 전체 지표를 전환한다.
37
+
38
+ ## 최소 사용 예
39
+
40
+ ```tsx
41
+ // Web
42
+ import { StatisticGroup } from "@hjmds/react/display";
43
+
44
+ <StatisticGroup
45
+ label={t("stats.weekly")}
46
+ descriptor={{
47
+ columns: 2,
48
+ items: [
49
+ { id: "letters", label: t("stats.letters"), value: formatNumber(sent), suffix: t("stats.unitLetters") },
50
+ {
51
+ id: "streak", label: t("stats.streak"), value: formatNumber(streak),
52
+ trend: { direction: "up", tone: "success", label: t("stats.trendUp") },
53
+ },
54
+ ],
55
+ }}
56
+ />
57
+ ```
58
+
59
+ ```tsx
60
+ // Native
61
+ import { Statistic } from "@hjmds/react-native/data-display";
62
+
63
+ <Statistic
64
+ presentation="surface"
65
+ descriptor={{ id: "balance", label: t("wallet.balance"), value: formatCurrency(balance) }}
66
+ />
67
+ ```
68
+
69
+ ## 축과 기본값
70
+
71
+ | prop | 값 | 기본값 | 설명 |
72
+ | --- | --- | --- | --- |
73
+ | `descriptor` | `id`·`label`·`value`(포맷이 끝난 문자열) 필수, `prefix`·`suffix`·`hint`·`trend` 선택 | — | 빈 문자열은 `TypeError` |
74
+ | `trend.direction` | `up` · `down` · `flat` | — | — |
75
+ | `trend.tone` | `neutral` · `success` · `warning` · `danger` | `neutral` | 보이는 `trend.label` 필수 |
76
+ | `density` | `comfortable` · `compact` | `comfortable` | 값 크기 `heading` · `title` |
77
+ | `presentation` | `plain` · `surface` | `plain` | `surface`는 테두리 상자 |
78
+ | `columns`(`StatisticGroup`) | 1~4 | 3 | 폭이 좁거나 글자가 크면 renderer가 1열까지 줄인다 |
79
+ | `value`(`AnimatedStatistic`) | 유한한 숫자 | — (필수) | `locale` 필수, `format`(`Intl.NumberFormatOptions`) |
80
+ | `animated`(`AnimatedStatistic`) | `boolean` | `true` | — |
81
+ | `contextLabel` | `string` | — | 접근성 이름 앞에 붙는 맥락. `StatisticGroup`은 group `label`을 넘긴다 |
82
+ | `composeAccessibilityLabel` | `(input: { contextLabel?, descriptor, valueText }) => string` | 기본 어순 | `descriptor`는 trend tone 기본값이 채워진 descriptor, `valueText`는 prefix·value·suffix를 이은 문자열 |
83
+ | `renderTrendMark` | `(props: { name: "trendUp" \| "trendDown" \| "trendFlat", color, size }) => ReactNode` | 내장 표시 | 추세 아이콘을 제품 아이콘으로 바꾼다. `color`는 Web `"currentColor"`, Native trend tone 색 |
84
+ | `renderValue`(Web) | `(value: string) => ReactNode` | — | 보이는 값만 바꾼다. 접근성 값은 descriptor 그대로 |
85
+ | `availableWidth`(Native `StatisticGroup`) | `number` | 측정 폭 | 열 수 계산에 쓸 폭 |
86
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
87
+ | Native `style`·`labelStyle`·`valueStyle`·`affixStyle`·`trendStyle`·`hintStyle`, 그룹 `style`·`itemStyle` | `StyleProp` | — | deprecated — `layoutStyle` 또는 `density`·`presentation`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
88
+
89
+ ## 배치
90
+
91
+ | 항목 | 값 | 근거 |
92
+ | --- | --- | --- |
93
+ | 크기 | `presentation="surface"`의 안쪽 여백: `comfortable` `spacing.md` 16 · `compact` `spacing.sm` 12(두 플랫폼). 테두리 `stroke.default` 1, radius `md` 12 | `statisticRecipe.density`·`presentations`, `styles.css` `.hjm-statistic[data-presentation="surface"]` |
94
+ | 글자 | 라벨 `label` 12 semibold(`compact` `caption` 11), 값 `heading` 24 heavy(`compact` `title` 18), prefix·suffix `body` 14 semibold 본문색, hint `caption`, 추세 `caption` bold(두 플랫폼). 2026-10-06까지 Web은 라벨 본문 14 medium, 값은 밀도와 관계없이 18, prefix·suffix는 흐린색이었다(1.12.1 이후 미게시) | `statisticRecipe.density`·`label`·`value`·`affix`·`hint`·`trend`, `.hjm-statistic__*` |
95
+ | 간격 | 그룹 간격 `statisticRecipe.group.gap` = `spacing.xs` 8. 지표 안 줄 간격(라벨·값·추세) `comfortable` `spacing.xs` 8 · `compact` `spacing.xxs` 4(두 플랫폼) | `styles.css` `.hjm-statistic`·`.hjm-statistic-group`, `statisticRecipe` |
96
+ | 순서·정렬 | 화면 위쪽 요약 영역이나 카드 안에 둔다. 지표 여러 개는 [Grid](grid.md)를 직접 짜지 말고 `StatisticGroup`으로 묶는다 | — |
97
+ | 고정·스크롤 | — | — |
98
+ | 좁은 폭·큰 글자 | 그룹 열 수 `columns`(1~4, 기본 3). Web은 폭 600 미만(`breakpoint.medium`)에서 1열. Native는 항목 폭이 120×글자 배율(`statisticRecipe.group.minItemWidth`) 아래면 열을 줄인다. 값은 줄바꿈된다(자르지 않음). 긴 통화 값이 들어갈 칸이면 열 수를 줄인다 | `styles.css` `@media (max-width: 599.98px)`, `react-native/src/data-display.tsx` |
99
+
100
+ ## 꼭 지킬 것
101
+
102
+ - 숫자·통화·단위 포맷은 제품이 한다. HJM은 계산·포맷하지 않고 받은 `value` 문자열을 그대로 그린다.
103
+ - 모든 문구(label·trend label·hint·group label)는 i18n 키로 넣는다. 추세는 색만으로 알리지 않는다.
104
+ - `direction`과 `tone`은 따로 정한다. 증가가 나쁜 지표는 `up` + `danger`다([계약](../../statistic.md)).
105
+ - 접근성 이름은 기본으로 `[contextLabel, label, 값, trend label, hint]`를 이어 만든다. 어순을 바꿔야 하면
106
+ `composeAccessibilityLabel`을 쓴다. `StatisticGroup`은 group `label`을 각 항목의 `contextLabel`로 넘긴다.
107
+ - 배치는 `layoutStyle`로만 한다. Native의 deprecated `style`·`*Style`·`itemStyle`로 recipe 색·굵기를 덮지 않는다.
108
+
109
+ ## 플랫폼 차이
110
+
111
+ | 항목 | Web | Native |
112
+ | --- | --- | --- |
113
+ | 값 렌더 교체 | `renderValue`(접근성 값은 descriptor 유지) | 없음 |
114
+ | 그룹 폭 | CSS grid, 좁은 화면에서 1열 | `availableWidth` 또는 측정 폭, 최소 항목 폭 120×글자 배율로 열 수 감소 |
115
+ | 슬롯 스타일 prop | 없음(`className`, 배치는 `layoutStyle`) | `style`·`*Style`, 그룹 `itemStyle`(모두 deprecated, 배치는 `layoutStyle`) |
116
+ | `AnimatedStatistic` 모션 | 숫자 자리 단위 NumberFlow | 지표 전체 `rise` 전환 |
117
+ | `renderTrendMark` color | `"currentColor"` | trend tone을 푼 색 문자열 |
118
+
119
+ ## 함정
120
+
121
+ - Web `AnimatedStatistic`은 reduced motion, RTL, 라틴 숫자가 아닌 numbering system, `ar`·`fa`·`he`·`ur` locale,
122
+ `scientific`·`engineering` 표기에서는 애니메이션 없이 `Intl` 결과 문자열만 그린다.
123
+ - `AnimatedStatistic`의 `descriptor`에는 `value`를 넣지 않는다. 값은 `value` prop의 숫자로 받는다.
@@ -0,0 +1,110 @@
1
+ # Steps
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Steps](../../steps.md), [StepPlayer](../../step-player.md), `src/steps.ts`(`stepsRecipe`)
9
+ - 스토리북: `배포/컴포넌트/탐색/단계 탐색`, `배포/컴포넌트/상태와 알림/단계별 진행 표시`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 여러 단계로 된 **선형 흐름에서 지금 어디인지** 보여 줄 때 쓴다. 온보딩(환영 → 구단 → 알림 → 완료),
14
+ 가입·주문 처리 단계가 여기에 속한다. 읽기 전용이며 단계를 눌러 이동하지 않는다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 단계 이름 없이 진행률만 | [Progress](progress.md) |
21
+ | 시간 순 사건 기록 | [Timeline](timeline.md) |
22
+ | 같은 수준의 화면 사이 이동 | [Tabs](tabs.md), [SegmentedControl](segmented-control.md) |
23
+ | 페이지 번호 이동 | [Pagination](pagination.md) |
24
+ | 화면을 가리키며 하는 기능 안내 | [Tour](tour.md) |
25
+ | 흐름이 끝난 뒤의 결과 | [Result](result.md) (또는 `currentStepStatus: "complete"`) |
26
+
27
+ ## 공개 이름과 import
28
+
29
+ | 이름 | 역할 | Web | Native |
30
+ | --- | --- | --- | --- |
31
+ | `Steps` | 기본 | `@hjmds/react`, `/navigation`, `/steps` | `@hjmds/react-native`, `/steps` |
32
+ | `StepPlayer` | 확장(재생형 단계 소개, optional-extension) | `/step-player` | `/step-player` |
33
+
34
+ `StepPlayer`는 granular subpath로만 import 된다. Steps·Progress·Button·Stack만 합성하므로 추가 peer는 없다.
35
+
36
+ ## 최소 사용 예
37
+
38
+ ```tsx
39
+ // Web
40
+ import { Steps } from "@hjmds/react/steps";
41
+
42
+ <Steps
43
+ descriptor={{
44
+ currentStepId: "team",
45
+ steps: [
46
+ { id: "welcome", label: t("onboarding.welcome") },
47
+ { id: "team", label: t("onboarding.team") },
48
+ { id: "alerts", label: t("onboarding.alerts") },
49
+ ],
50
+ }}
51
+ statusLabels={{
52
+ pending: t("steps.pending"), current: t("steps.current"),
53
+ complete: t("steps.complete"), error: t("steps.error"),
54
+ }}
55
+ composeAccessibleName={({ position, total, label }) => t("steps.name", { position, total, label })}
56
+ />
57
+ ```
58
+
59
+ ```tsx
60
+ // Native — props가 같다
61
+ import { Steps } from "@hjmds/react-native/steps";
62
+
63
+ <Steps descriptor={descriptor} statusLabels={statusLabels} composeAccessibleName={composeStepName} />
64
+ ```
65
+
66
+ ## 축과 기본값
67
+
68
+ | prop | 값 | 기본값 | 설명 |
69
+ | --- | --- | --- | --- |
70
+ | `descriptor.currentStepId` | 단계 `id` | — (필수) | 상태는 단계별로 넘기지 않는다. 앞은 `complete`, 커서는 지정 상태, 뒤는 `pending`이 유도된다 |
71
+ | `descriptor.currentStepStatus` | `current` · `error` · `complete` | `current` | 커서 단계의 상태 |
72
+ | `descriptor.steps` | 단계 배열 | — (필수) | 2개 이상, `id`는 고유하고 앞뒤 공백이 없어야 한다. 어기면 `RangeError`/`TypeError` |
73
+ | `descriptor.steps[]` | `{ id, label, description? }` | — | — |
74
+ | `statusLabels` | `{ pending, current, complete, error }`(모두 `string`) | — (필수) | 상태를 읽는 문구 |
75
+ | `composeAccessibleName` | `(info: { position: number, total: number, label: string }) => string` | — (필수) | `position`은 1부터 |
76
+ | `renderMark` | `(status: StepStatus, position: number) => ReactNode` | `✓`·`!`·번호 | 원 안 표시를 바꾼다 |
77
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
78
+ | `style`(Native) | `StyleProp<ViewStyle>` | — | deprecated — `layoutStyle`. 개발 모드에서 한 번 경고하고 다음 major에서 제거된다 |
79
+ | `StepPlayer` `progress`·`playing` | `number`(0~1)·`boolean` | — (필수) | 호스트 소유 |
80
+ | `StepPlayer` `onPlayingChange`·`onReplay` | `(playing: boolean) => void`·`() => void` | — (필수) | 재생/일시정지·다시 보기 요청 |
81
+ | `StepPlayer` `labels` | `{ play, pause, replay, progress }` | — (필수) | — |
82
+
83
+ 방향·clickable·variant 축은 없다(계약에서 배제). 항상 가로 한 줄이다.
84
+
85
+ ## 배치
86
+
87
+ | 항목 | 값 | 근거 |
88
+ | --- | --- | --- |
89
+ | 크기 | 단계마다 같은 폭(`minmax(0, 1fr)`, Native `flex: 1`). 원(indicator) 24(`glyph.md`), 연결선 1(`stroke.default`), 현재·오류 단계의 원 테두리 2(`stroke.strong`) | `stepsRecipe`, `styles.css` `.hjm-steps*`, `react-native/src/steps.tsx` |
90
+ | 간격 | 단계 사이·원과 글자 사이 `spacing.xs` 8(`stepsRecipe.gap`) | `stepsRecipe.gap` |
91
+ | 순서·정렬 | 흐름 화면의 맨 위(TopBar 아래, 본문 위)에 가로 한 줄. 화면 아래 행동 버튼과 떨어뜨리고 탭처럼 누르게 두지 않는다 | — |
92
+ | 고정·스크롤 | — | — |
93
+ | 좁은 폭·큰 글자 | 라벨·설명은 줄바꿈된다(`overflow-wrap: anywhere`). 좁은 폭에서 라벨이 세 줄 이상이 되면 라벨을 줄이거나 [Progress](progress.md)로 바꾼다. 세로 배치 축은 없다 | `styles.css` `.hjm-steps__label` |
94
+
95
+ ## 꼭 지킬 것
96
+
97
+ - `statusLabels` 네 개와 `composeAccessibleName`은 필수다. "3단계 중 2단계" 같은 어순은 제품이 i18n으로 조립한다.
98
+ - 이전 단계로 가는 행동은 Steps 밖의 [Button](button.md)으로 둔다.
99
+ - `StepPlayer`는 타이머가 없다. `progress`(0~1)·`playing`·커서를 호스트가 소유하고, `onPlayingChange`·`onReplay`에서
100
+ 직접 상태를 바꾼다. `labels`(`play`·`pause`·`replay`·`progress`)는 비면 `TypeError`, `progress`가 범위를 벗어나면 `RangeError`다.
101
+ - 실제 비동기 작업의 완료를 `StepPlayer` 시계로 추정하지 않는다([계약](../../step-player.md)).
102
+
103
+ ## 플랫폼 차이
104
+
105
+ | 항목 | Web | Native |
106
+ | --- | --- | --- |
107
+ | 구조 | `<ol>`, 현재 단계 `aria-current="step"`, 상태 문구는 숨은 텍스트 | `accessibilityRole="summary"`, 단계마다 이름 + `accessibilityHint`에 상태 문구 |
108
+ | 스타일 | `className`·`style`(HTML 속성), 배치는 `layoutStyle` | 배치는 `layoutStyle`(`style`은 deprecated) |
109
+ | ref | `forwardRef`(`ol`) | 없음 |
110
+ | import 경로 | root, `/navigation`, `/steps` | root, `/steps`(`/navigation` 없음) |
@@ -0,0 +1,91 @@
1
+ # Surface
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: `src/base-recipes.ts`(`surfaceRecipe`·`surfaceDefaults`·`surfaceGeometry`)
9
+ - 스토리북: `배포/컴포넌트/레이아웃/배경 영역`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 배경·테두리·radius를 가진 **의미 없는 상자**가 필요할 때 쓴다. 다른 컴포넌트로 표현되지 않는
14
+ 패널, 요약 영역, 이미지를 둥근 모서리 안에 자르는 틀이 여기에 속한다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 제목·본문·행동이 있는 콘텐츠 카드, 누르는 카드 | [Card](card.md) |
21
+ | 간격만 필요하고 상자는 필요 없음 | [Stack](stack.md) |
22
+ | 상태·경고를 알리는 상자 | [Notice](notice.md) |
23
+ | 설정·목록 행 | [ListRow](list-row.md), [List](list.md) |
24
+ | 화면 위에 뜨는 층 | [Sheet](sheet.md), [Dialog](dialog.md), [Popover](popover.md) |
25
+ | 콘텐츠 최대 폭·좌우 여백 | [Container](container.md) |
26
+
27
+ ## 공개 이름과 import
28
+
29
+ | 이름 | 역할 | Web | Native |
30
+ | --- | --- | --- | --- |
31
+ | `Surface` | 기본 | `@hjmds/react`, `/layout` | `@hjmds/react-native`, `/primitives` |
32
+
33
+ ## 최소 사용 예
34
+
35
+ ```tsx
36
+ // Web
37
+ import { Surface } from "@hjmds/react/layout";
38
+
39
+ <Surface as="section" padding="md" aria-label={t("order.summary")}>
40
+ {children}
41
+ </Surface>
42
+ ```
43
+
44
+ ```tsx
45
+ // Native
46
+ import { Surface } from "@hjmds/react-native/primitives";
47
+
48
+ <Surface tone="raised" padding="md" layoutStyle={{ marginTop: 16 }}>
49
+ {children}
50
+ </Surface>
51
+ ```
52
+
53
+ ## 축과 기본값
54
+
55
+ | prop | 값 | 기본값 | 설명 |
56
+ | --- | --- | --- | --- |
57
+ | `tone` | `default` · `raised` · `sunken` · `accent` · `subtle` | `default` | `raised`는 그림자, `accent`는 primary 색 테두리. 모든 tone의 배경은 테마 `bg`이고 회색 채움 대신 테두리·그림자로 위계를 나타낸다 |
58
+ | `padding` | `none` 또는 spacing 토큰(`xxs`~`xxxl`) | `none` | — |
59
+ | `radius` | `sm` · `md` · `lg` · `xl` · `full` | `lg`(16) | — |
60
+ | `bordered` | `boolean` | 생략 시 테두리 그림 | 모든 tone이 `borderAlways`. `bordered={false}`로만 끈다 |
61
+ | 자식 자르기 | — | `raised` 외 자름 | `raised`를 뺀 tone은 둥근 모서리 밖으로 넘친 자식을 자른다. `raised`는 그림자가 잘리지 않게 자르지 않는다 |
62
+ | `as`(Web) | `div` · `section` · `article` | `div` | — |
63
+ | `layoutStyle` | `HjmCompositionStyleProp` | — | Surface 자신의 바깥 여백·폭·flex·`alignSelf`. Web·Native 모두 |
64
+
65
+ ## 배치
66
+
67
+ | 항목 | 값 | 근거 |
68
+ | --- | --- | --- |
69
+ | 크기 | 폭은 부모를 따르고 높이는 내용이 정한다. 기본 radius `lg` 16, 테두리 1(`border` 색) | `styles.css` `.hjm-surface`, `surfaceRecipe` |
70
+ | 간격 | 기본 `padding`이 `none`이므로 안쪽 여백을 꼭 준다. 카드 안 내용은 `md` 16, 촘촘한 묶음은 `sm` 12. 여러 Surface를 세로로 쌓을 때는 [Stack](stack.md)으로 `md` 16(같은 묶음) 또는 `xl` 24(섹션 사이) | `surfaceDefaults`, `foundations.ts` `layout` |
71
+ | 순서·정렬 | 화면 여백 안에 놓는 카드·묶음 상자다. Surface 안에 Surface를 다시 겹치지 않고 안쪽 구분은 [Divider](divider.md)나 간격으로 한다 | — |
72
+ | 고정·스크롤 | — | — |
73
+ | 좁은 폭·큰 글자 | 긴 글자는 줄바꿈된다(`overflow-wrap: anywhere`) | `styles.css` `.hjm-surface` |
74
+
75
+ ## 꼭 지킬 것
76
+
77
+ - 배치는 `layoutStyle`로만 한다. 배경·테두리 색·radius·그림자를 덮지 않는다.
78
+ 색은 제품 테마 토큰이 정하고, Surface마다 브랜드 색을 하드코딩하지 않는다.
79
+ - 안쪽 여백은 `padding` 토큰으로 정한다(기본이 `none`이라 내용이 테두리에 붙는다).
80
+ - Surface는 role을 갖지 않는다. 영역에 이름이 필요하면 Web은 `as="section"`과 라벨을 함께 준다.
81
+ - `raised` 안에 이미지를 넣으면 모서리가 잘리지 않는다. 모서리 자르기가 필요하면 다른 tone을 쓴다.
82
+
83
+ ## 플랫폼 차이
84
+
85
+ | 항목 | Web | Native |
86
+ | --- | --- | --- |
87
+ | `style` | 받음(`layoutStyle`이 이김) | 타입에서 제외 |
88
+ | 요소 선택 | `as` | 없음(`View`) |
89
+ | 그림자(`raised`) | CSS | `elevation: 4`와 iOS shadow |
90
+ | ref | `forwardRef` | 없음 |
91
+ | import 경로 | `/layout` | `/primitives` |