@hjmds/design-contracts 1.17.2 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/dist/action-session.d.ts +16 -0
  2. package/dist/action-session.d.ts.map +1 -1
  3. package/dist/action-session.js +18 -0
  4. package/dist/action-session.js.map +1 -1
  5. package/dist/activity-chip.d.ts +127 -0
  6. package/dist/activity-chip.d.ts.map +1 -0
  7. package/dist/activity-chip.js +65 -0
  8. package/dist/activity-chip.js.map +1 -0
  9. package/dist/base-recipes.d.ts +1 -1
  10. package/dist/base-recipes.d.ts.map +1 -1
  11. package/dist/base-recipes.js +2 -1
  12. package/dist/base-recipes.js.map +1 -1
  13. package/dist/behaviors.d.ts +9 -6
  14. package/dist/behaviors.d.ts.map +1 -1
  15. package/dist/behaviors.js +5 -3
  16. package/dist/behaviors.js.map +1 -1
  17. package/dist/bottom-navigation.d.ts +16 -0
  18. package/dist/bottom-navigation.d.ts.map +1 -1
  19. package/dist/bottom-navigation.js +18 -0
  20. package/dist/bottom-navigation.js.map +1 -1
  21. package/dist/card.d.ts +4 -0
  22. package/dist/card.d.ts.map +1 -1
  23. package/dist/card.js +4 -1
  24. package/dist/card.js.map +1 -1
  25. package/dist/carousel.d.ts +3 -0
  26. package/dist/carousel.d.ts.map +1 -1
  27. package/dist/carousel.js.map +1 -1
  28. package/dist/catalog.d.ts +80 -9
  29. package/dist/catalog.d.ts.map +1 -1
  30. package/dist/component-recipes.d.ts +74 -3
  31. package/dist/component-recipes.d.ts.map +1 -1
  32. package/dist/component-recipes.js +38 -6
  33. package/dist/component-recipes.js.map +1 -1
  34. package/dist/content-transition.d.ts +1 -1
  35. package/dist/content-transition.d.ts.map +1 -1
  36. package/dist/content-transition.js +2 -2
  37. package/dist/content-transition.js.map +1 -1
  38. package/dist/field-group.d.ts +14 -0
  39. package/dist/field-group.d.ts.map +1 -1
  40. package/dist/field-group.js +9 -0
  41. package/dist/field-group.js.map +1 -1
  42. package/dist/form.d.ts.map +1 -1
  43. package/dist/form.js +10 -3
  44. package/dist/form.js.map +1 -1
  45. package/dist/icon-button-recipe.d.ts +1 -5
  46. package/dist/icon-button-recipe.d.ts.map +1 -1
  47. package/dist/icon-button-recipe.js +2 -1
  48. package/dist/icon-button-recipe.js.map +1 -1
  49. package/dist/index.d.ts +3 -2
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +2 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/screen-patterns.d.ts +10 -0
  54. package/dist/screen-patterns.d.ts.map +1 -1
  55. package/dist/screen-patterns.js +17 -3
  56. package/dist/screen-patterns.js.map +1 -1
  57. package/dist/toast-liquid.d.ts +1 -1
  58. package/dist/toast-liquid.d.ts.map +1 -1
  59. package/dist/toast-liquid.js +3 -2
  60. package/dist/toast-liquid.js.map +1 -1
  61. package/dist/toast.d.ts +29 -2
  62. package/dist/toast.d.ts.map +1 -1
  63. package/dist/toast.js +59 -3
  64. package/dist/toast.js.map +1 -1
  65. package/dist/version.d.ts +1 -1
  66. package/dist/version.js +1 -1
  67. package/dist/version.js.map +1 -1
  68. package/docs/action-session.md +2 -0
  69. package/docs/activity-chip.md +34 -0
  70. package/docs/asset.md +7 -0
  71. package/docs/bottom-navigation.md +16 -1
  72. package/docs/brand-boundary.md +3 -3
  73. package/docs/collection-rail.md +4 -0
  74. package/docs/compound-controls.md +8 -3
  75. package/docs/consumer-policy.md +39 -120
  76. package/docs/generated/component-maturity.md +1 -1
  77. package/docs/generated/renderer-evidence.json +3 -3
  78. package/docs/generated/renderer-evidence.md +1 -1
  79. package/docs/generated/showcase-manifest.json +1 -1
  80. package/docs/gravity-letters.md +2 -0
  81. package/docs/grid-reveal.md +2 -0
  82. package/docs/layout.md +2 -2
  83. package/docs/load-more.md +5 -0
  84. package/docs/migration-native-legacy-removal.md +30 -17
  85. package/docs/optional-adapters.md +4 -0
  86. package/docs/screen-patterns.md +23 -1
  87. package/docs/toast.md +64 -2
  88. package/docs/usage/README.md +248 -243
  89. package/docs/usage/SELECTION.md +43 -0
  90. package/docs/usage/STANDARD.md +22 -10
  91. package/docs/usage/components/accordion.md +5 -3
  92. package/docs/usage/components/activity-chip.md +109 -0
  93. package/docs/usage/components/agreement.md +6 -9
  94. package/docs/usage/components/alert-dialog.md +12 -6
  95. package/docs/usage/components/asset.md +10 -4
  96. package/docs/usage/components/auth-provider-button.md +2 -2
  97. package/docs/usage/components/avatar.md +8 -6
  98. package/docs/usage/components/badge.md +4 -4
  99. package/docs/usage/components/bottom-cta.md +4 -4
  100. package/docs/usage/components/bottom-info.md +2 -2
  101. package/docs/usage/components/bottom-navigation.md +18 -6
  102. package/docs/usage/components/button.md +6 -3
  103. package/docs/usage/components/card.md +28 -7
  104. package/docs/usage/components/carousel.md +12 -4
  105. package/docs/usage/components/chat-message.md +10 -2
  106. package/docs/usage/components/chat-screen.md +5 -5
  107. package/docs/usage/components/checkbox-group.md +1 -1
  108. package/docs/usage/components/checkbox.md +2 -2
  109. package/docs/usage/components/chip.md +2 -2
  110. package/docs/usage/components/code-block.md +3 -4
  111. package/docs/usage/components/collapsible.md +6 -4
  112. package/docs/usage/components/combobox.md +2 -2
  113. package/docs/usage/components/command-palette.md +1 -1
  114. package/docs/usage/components/comment-thread-screen.md +7 -4
  115. package/docs/usage/components/container.md +1 -1
  116. package/docs/usage/components/content-transition.md +3 -2
  117. package/docs/usage/components/context-menu.md +2 -2
  118. package/docs/usage/components/counter-badge.md +3 -3
  119. package/docs/usage/components/data-table.md +1 -1
  120. package/docs/usage/components/date-picker.md +4 -4
  121. package/docs/usage/components/description-list.md +3 -3
  122. package/docs/usage/components/design-system-provider.md +2 -2
  123. package/docs/usage/components/dialog.md +14 -3
  124. package/docs/usage/components/divider.md +2 -2
  125. package/docs/usage/components/editor-screen.md +5 -3
  126. package/docs/usage/components/effect-surface.md +1 -1
  127. package/docs/usage/components/empty-state.md +6 -6
  128. package/docs/usage/components/field.md +5 -3
  129. package/docs/usage/components/file-picker.md +6 -3
  130. package/docs/usage/components/form.md +1 -1
  131. package/docs/usage/components/heading.md +3 -3
  132. package/docs/usage/components/icon-button.md +6 -3
  133. package/docs/usage/components/icon.md +1 -1
  134. package/docs/usage/components/image.md +5 -4
  135. package/docs/usage/components/keyboard-dock.md +1 -1
  136. package/docs/usage/components/layout.md +1 -1
  137. package/docs/usage/components/link.md +2 -2
  138. package/docs/usage/components/list-detail-screen.md +6 -6
  139. package/docs/usage/components/list-row.md +13 -4
  140. package/docs/usage/components/list.md +5 -4
  141. package/docs/usage/components/masonry.md +2 -1
  142. package/docs/usage/components/media-selection-screen.md +11 -5
  143. package/docs/usage/components/mentions.md +2 -2
  144. package/docs/usage/components/menu.md +3 -2
  145. package/docs/usage/components/menubar.md +2 -2
  146. package/docs/usage/components/message-composer.md +3 -3
  147. package/docs/usage/components/moderation-screen.md +3 -2
  148. package/docs/usage/components/notice.md +2 -2
  149. package/docs/usage/components/notification-inbox-screen.md +1 -1
  150. package/docs/usage/components/notification-item.md +2 -2
  151. package/docs/usage/components/number-field.md +3 -3
  152. package/docs/usage/components/otp-field.md +1 -1
  153. package/docs/usage/components/overview-screen.md +1 -1
  154. package/docs/usage/components/permission-screen.md +1 -1
  155. package/docs/usage/components/photo-source-sheet.md +1 -1
  156. package/docs/usage/components/popover.md +2 -2
  157. package/docs/usage/components/profile-screen.md +1 -1
  158. package/docs/usage/components/progress.md +2 -2
  159. package/docs/usage/components/qr-code.md +2 -2
  160. package/docs/usage/components/radio-group.md +1 -2
  161. package/docs/usage/components/radio.md +1 -2
  162. package/docs/usage/components/result.md +1 -1
  163. package/docs/usage/components/saved-items-screen.md +4 -2
  164. package/docs/usage/components/screen-layout.md +4 -3
  165. package/docs/usage/components/search-field.md +1 -1
  166. package/docs/usage/components/search-screen.md +4 -4
  167. package/docs/usage/components/section.md +3 -3
  168. package/docs/usage/components/segmented-control.md +4 -4
  169. package/docs/usage/components/select.md +2 -3
  170. package/docs/usage/components/settings-screen.md +18 -56
  171. package/docs/usage/components/sheet.md +10 -5
  172. package/docs/usage/components/sidebar.md +1 -1
  173. package/docs/usage/components/skeleton.md +1 -1
  174. package/docs/usage/components/skip-nav.md +1 -1
  175. package/docs/usage/components/slider.md +2 -2
  176. package/docs/usage/components/spinner.md +2 -2
  177. package/docs/usage/components/splitter.md +1 -1
  178. package/docs/usage/components/statistic.md +5 -4
  179. package/docs/usage/components/steps.md +2 -2
  180. package/docs/usage/components/surface.md +2 -3
  181. package/docs/usage/components/switch.md +1 -1
  182. package/docs/usage/components/tabs.md +4 -4
  183. package/docs/usage/components/tag.md +2 -2
  184. package/docs/usage/components/tags-input.md +4 -4
  185. package/docs/usage/components/text-area.md +5 -3
  186. package/docs/usage/components/text.md +85 -4
  187. package/docs/usage/components/timeline.md +2 -2
  188. package/docs/usage/components/toast.md +41 -13
  189. package/docs/usage/components/toggle-group.md +3 -3
  190. package/docs/usage/components/tooltip.md +1 -1
  191. package/docs/usage/components/top-bar.md +5 -4
  192. package/docs/usage/components/top.md +3 -3
  193. package/docs/usage/components/transfer-list.md +5 -4
  194. package/docs/usage/components/upload-item.md +3 -3
  195. package/docs/usage/components/virtual-list.md +1 -0
  196. package/docs/usage/compositions/action-feedback.md +42 -6
  197. package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
  198. package/docs/usage/compositions/action-recovery-save.md +19 -23
  199. package/docs/usage/compositions/action-recovery-undo.md +6 -1
  200. package/docs/usage/compositions/activity-status.md +168 -0
  201. package/docs/usage/compositions/collection-detail.md +2 -0
  202. package/docs/usage/compositions/command-records.md +15 -5
  203. package/docs/usage/compositions/common-message.md +4 -2
  204. package/docs/usage/compositions/common-notification.md +5 -3
  205. package/docs/usage/compositions/compound-controls.md +13 -5
  206. package/docs/usage/compositions/content-transition-comparison.md +2 -2
  207. package/docs/usage/compositions/date-entry.md +10 -5
  208. package/docs/usage/compositions/date-time-selection.md +3 -3
  209. package/docs/usage/compositions/design-profile-comparison.md +7 -5
  210. package/docs/usage/compositions/disclosure.md +1 -1
  211. package/docs/usage/compositions/document-resource.md +10 -3
  212. package/docs/usage/compositions/expo-interactions.md +3 -2
  213. package/docs/usage/compositions/family-drawer.md +9 -4
  214. package/docs/usage/compositions/field-group.md +7 -1
  215. package/docs/usage/compositions/floating-action-button.md +18 -18
  216. package/docs/usage/compositions/illustrated-outcome.md +3 -2
  217. package/docs/usage/compositions/input-sheet.md +6 -2
  218. package/docs/usage/compositions/interaction-adapters.md +1 -1
  219. package/docs/usage/compositions/interaction-flow-apply.md +6 -3
  220. package/docs/usage/compositions/interaction-flow-draft.md +5 -2
  221. package/docs/usage/compositions/interaction-flow-search.md +5 -2
  222. package/docs/usage/compositions/live-list.md +7 -5
  223. package/docs/usage/compositions/navigation-bar-collection.md +8 -7
  224. package/docs/usage/compositions/optional-adapters.md +1 -1
  225. package/docs/usage/compositions/origin-dialog.md +5 -5
  226. package/docs/usage/compositions/paper-surface.md +1 -1
  227. package/docs/usage/compositions/photo-source.md +1 -1
  228. package/docs/usage/compositions/purpose-input-comment.md +5 -3
  229. package/docs/usage/compositions/purpose-input-message.md +5 -3
  230. package/docs/usage/compositions/reference-first.md +63 -42
  231. package/docs/usage/compositions/reference-review.md +60 -50
  232. package/docs/usage/compositions/reference-settings.md +60 -50
  233. package/docs/usage/compositions/selection-scope.md +48 -35
  234. package/docs/usage/compositions/stea-order-progress.md +1 -0
  235. package/docs/usage/compositions/stea-otp-verify.md +5 -3
  236. package/docs/usage/compositions/stea-pixel-empty.md +3 -3
  237. package/docs/usage/compositions/time-selection.md +6 -5
  238. package/docs/usage/compositions/upload-recovery.md +10 -1
  239. package/docs/usage/compositions/video-dialog.md +10 -3
  240. package/docs/usage/compositions/web-additions.md +5 -5
  241. package/docs/usage/compositions/web-navigation.md +1 -1
  242. package/docs/usage/screens/common-chat.md +21 -8
  243. package/docs/usage/screens/common-comments.md +13 -1
  244. package/docs/usage/screens/common-inbox.md +19 -6
  245. package/docs/usage/screens/common-login.md +19 -7
  246. package/docs/usage/screens/common-profile.md +22 -9
  247. package/docs/usage/screens/common-saved.md +16 -2
  248. package/docs/usage/screens/common-search.md +24 -4
  249. package/docs/usage/screens/common-settings.md +22 -50
  250. package/docs/usage/screens/common-shell.md +14 -2
  251. package/docs/usage/screens/dashboard.md +52 -109
  252. package/docs/usage/screens/discovery-gallery.md +57 -148
  253. package/docs/usage/screens/flow-collection.md +38 -9
  254. package/docs/usage/screens/flow-editor.md +25 -7
  255. package/docs/usage/screens/flow-media.md +23 -7
  256. package/docs/usage/screens/flow-moderation.md +15 -2
  257. package/docs/usage/screens/flow-onboarding.md +3 -3
  258. package/docs/usage/screens/flow-permission.md +17 -3
  259. package/docs/usage/screens/landing.md +37 -20
  260. package/docs/usage/screens/mockup-studio.md +22 -7
  261. package/docs/usage/screens/notification-settings.md +18 -4
  262. package/docs/usage/screens/product-bento.md +18 -9
  263. package/docs/usage/screens/reference-comparison.md +29 -10
  264. package/docs/usage/tokens/color.md +6 -3
  265. package/docs/usage/tokens/elevation-opacity.md +4 -2
  266. package/docs/usage/tokens/font-roles.md +2 -2
  267. package/docs/usage/tokens/layers.md +3 -3
  268. package/docs/usage/tokens/motion.md +2 -2
  269. package/docs/usage/tokens/radius.md +4 -2
  270. package/docs/usage/tokens/spacing.md +3 -3
  271. package/docs/usage/tokens/theme-studio.md +2 -2
  272. package/docs/usage/tokens/typography-studio.md +14 -8
  273. package/docs/usage/tokens/typography.md +15 -5
  274. package/package.json +7 -1
@@ -3,7 +3,7 @@
3
3
  - 단계: 화면
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
6
+ - 적용: 구성 API 지침 참조
7
7
  - 검토일: 2026-10-08
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/화면/공통 화면/설정`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/화면/설정/앱 설정`
@@ -41,6 +41,18 @@ host: 남은 높이·safe area·키보드
41
41
  | 빈 | 실제 조회 0건 또는 아직 작성하지 않은 상태 안내 | 시작·조건 해제 등 맥락에 맞는 대안 |
42
42
  | 오류 | 저장 실패는 notice로 알리고 마지막 확인값으로 복구 | 실패 원인과 재시도 경로 제공 |
43
43
 
44
+
45
+ ### 즉시 저장과 일괄 저장 선택
46
+
47
+ 2026-10-13 전수 점검에서 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
48
+
49
+ | 상황 | 연결 | 유지·복구 |
50
+ | --- | --- | --- |
51
+ | 즉시 저장 | 각 Switch 행의 pending·오류 | 실패하면 마지막 확인값으로 복구하고 다른 설정은 유지한다 |
52
+ | 일괄 저장 | 하단 저장 + 별도의 draft/saved | 실패하면 초안을 유지한다. 즉시 저장 방식의 되돌리기를 그대로 적용하지 않는다 |
53
+ | 새로 받은 값 | 초안 수정 여부를 비교 | 편집 중 재조회 응답으로 초안을 덮지 않는다 |
54
+ | 파괴 행동 | 일반 설정 묶음 뒤 별도 영역 | 로그아웃·탈퇴를 일반 토글과 같은 즉시 실행으로 연결하지 않는다 |
55
+
44
56
  ## 사용하는 지침
45
57
 
46
58
  | 지침 | 쓰는 곳 |
@@ -51,57 +63,17 @@ host: 남은 높이·safe area·키보드
51
63
 
52
64
  ## 코드 골격
53
65
 
54
- ```tsx
55
- // Web
56
- import { SettingsScreen } from "@hjmds/react/screens";
57
- import { Switch } from "@hjmds/react/selection";
58
- import { ListRow } from "@hjmds/react/display";
59
-
60
- <SettingsScreen
61
- title={t("settings.title")}
62
- sections={[
63
- { id: "notifications", title: t("settings.notifications"), children: (
64
- <Switch
65
- presentation="row"
66
- label={t("settings.push.label")}
67
- description={t("settings.push.description")}
68
- checked={pushEnabled}
69
- onCheckedChange={setPushEnabled}
70
- />
71
- ) },
72
- { id: "account", title: t("settings.account"), children: (
73
- <ListRow title={t("settings.language")} description={currentLanguageName} onClick={openLanguageSheet} />
74
- ) },
75
- ]}
76
- />
77
- ```
66
+ 문서와 Storybook은 같은 실행 예제를 참조한다. 제품 데이터·문구·저장·라우팅은 제품에서 연결하고,
67
+ 아래 preview의 메모리 상태를 서버 저장으로 취급하지 않는다. 예제는 읽기용이며 제품에서 showcase를 import하지 않는다.
78
68
 
79
- ```tsx
80
- // Native
81
- import { SettingsScreen } from "@hjmds/react-native/screens";
82
- import { Switch } from "@hjmds/react-native/inputs";
83
- import { ListRow } from "@hjmds/react-native/data-display";
84
-
85
- <SettingsScreen
86
- title={t("settings.title")}
87
- sections={[
88
- { id: "notifications", title: t("settings.notifications"), children: (
89
- <Switch
90
- presentation="row"
91
- label={t("settings.push.label")}
92
- description={t("settings.push.description")}
93
- checked={pushEnabled}
94
- onCheckedChange={setPushEnabled}
95
- />
96
- ) },
97
- { id: "account", title: t("settings.account"), children: (
98
- <ListRow title={t("settings.language")} description={currentLanguageName} onPress={openLanguageSheet} />
99
- ) },
100
- ]}
101
- />
102
- ```
69
+ <!-- usage-example: Web ../../../../../showcase/web/src/patterns/settings-page-preview.tsx -->
70
+ - [Web 실행 예제](../../../../../showcase/web/src/patterns/settings-page-preview.tsx): 공개 SettingsScreen·행·Sheet 조합과 로컬 설정 상태.
71
+ <!-- usage-example: Native ../../../../../showcase/native/src/settings-page-preview.tsx -->
72
+ - [Native 실행 예제](../../../../../showcase/native/src/settings-page-preview.tsx): 같은 흐름의 Native 공개 API와 이벤트 연결.
103
73
 
104
- 콜백·데이터·지역화 함수는 제품에서 공급한다. 필수 props와 플랫폼별 차이는 위 API 지침에서 확인한다.
74
+ 설치 패키지에서 소스 링크를 열 수 없다면 [Web 원본](https://github.com/jim1286/hjm-design-system/blob/v1.17.1/showcase/web/src/patterns/settings-page-preview.tsx)과
75
+ [Native 원본](https://github.com/jim1286/hjm-design-system/blob/v1.17.1/showcase/native/src/settings-page-preview.tsx)을 참고한다.
76
+ 고정 버전 예제와 현재 checkout의 후속 변경은 구분하며 설치된 타입을 기준으로 사용한다.
105
77
 
106
78
  ## 큰 글자·다크·좁은 폭
107
79
 
@@ -3,8 +3,8 @@
3
3
  - 단계: 화면
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 미게시(1.12.1 이후)
7
- - 검토일: 2026-10-06
6
+ - 적용: 구성 API 지침 참조
7
+ - 검토일: 2026-10-13
8
8
  - 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/화면/공통 화면/화면 골격`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
9
9
  - 스토리북: `배포/화면/화면 틀과 도구/화면 골격과 상태`
10
10
 
@@ -41,6 +41,18 @@ host: 남은 높이·safe area·키보드
41
41
  | 빈 | 실제 조회 0건 또는 아직 작성하지 않은 상태 안내 | 시작·조건 해제 등 맥락에 맞는 대안 |
42
42
  | 오류 | 초기 로딩만 본문 교체; 갱신 실패는 notice로 본문 유지 | 실패 원인과 재시도 경로 제공 |
43
43
 
44
+
45
+ ### 상태를 바꾸는 기준
46
+
47
+ 2026-10-13 전수 점검에서 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
48
+
49
+ | 상황 | 연결 | 유지·복구 |
50
+ | --- | --- | --- |
51
+ | 첫 조회 | 데이터가 아직 없으면 state=loading; 실패하면 state=error + stateAction | 본문의 실제 남은 영역에서 중앙 정렬한다 |
52
+ | 재조회 | 기존 데이터가 있으면 ready + notice | 스크롤·선택·입력을 유지하고 재시도 버튼만 pending |
53
+ | 권한 제한 | restricted + stateAction | 서버 권한을 확인하기 전에는 민감한 children을 보이지 않는다 |
54
+ | 행동 진행 | footer 버튼에 pending | ScreenLayout은 footer를 자동 잠그지 않으므로 제품이 중복 요청을 막는다 |
55
+
44
56
  ## 사용하는 지침
45
57
 
46
58
  | 지침 | 쓰는 곳 |
@@ -4,7 +4,7 @@
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
6
  - 적용: 1.12.1
7
- - 검토일: 2026-10-06
7
+ - 검토일: 2026-10-13
8
8
  - 근거: `showcase/web/src/patterns/Dashboard.stories.tsx`, `showcase/native/src/Dashboard.stories.tsx`, `showcase/shared/dashboard-pattern.ts`, `showcase/native/src/pattern-status.tsx`, `packages/react/src/styles.css`(`.hjm-statistic`, `.hjm-text`)
9
9
  - 스토리북: `배포/화면/콘텐츠/대시보드`
10
10
 
@@ -24,7 +24,7 @@
24
24
  │ ① 머리 │
25
25
  │ 제목 Heading level3 · semanticLevel 1 │
26
26
  │ 소개 Text (Web as="p") │ 직계 요소 사이 spacing.xl 24
27
- │ [최근 7일] [9월] [8월] ← 기간 선택 │ ghost + selected, wrap gap sm 12
27
+ │ [최근 7일] [9월] [8월] ← 기간 선택 │ SegmentedControl pills, 단일 선택
28
28
  │ ② "2026-09-01 — 2026-09-30" (상태 알림) │
29
29
  │ (오류면 여기 Notice danger + [다시 시도]) │
30
30
  │ ┌ ③ 요약 Surface padding lg 20 ─────────────────────────┐ │
@@ -52,7 +52,7 @@
52
52
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
53
53
  | --- | --- | --- | --- |
54
54
  | 바깥 틀 | Web `main` > [Container](../components/container.md) `size="content"` `gutter` > Stack `gap="xl"`, 문서 스크롤 · Native `ScrollView` > Container `size="content"` `gutter` > Stack `gap="xl"` | 화면 전체, 세로 스크롤 하나 | 좌우 Container gutter: 폭 < 600 `compact` 16, 이상 `regular` 20([화면 여백](../tokens/layout.md)). Native ScrollView 위아래 `spacing.lg` 20(`paddingVertical`). 직계 요소 사이 `spacing.xl` 24. 위 안전 영역은 내비게이션 헤더·[TopBar](../components/top-bar.md) `safeAreaTop`이 맡고, 헤더 없이 띄울 때만 제품이 감싼다. 아래 고정 영역이 없어 inset은 스크롤로 지나간다. 입력이 없어 키보드 처리는 없다 |
55
- | ① 머리 | [Heading](../components/heading.md) `level="level3"` `semanticLevel={1}` · Text(소개) · Stack `axis="inline" wrap gap="sm"` > Button `ghost` | 맨 위 | 제목 24/32 heavy. 버튼 높이 44(`control.buttonHeight.medium`), 사이 `spacing.sm` 12 |
55
+ | ① 머리 | [Heading](../components/heading.md) `level="level3"` `semanticLevel={1}` · Text(소개) · SegmentedControl `presentation="pills"` | 맨 위 | 제목 24/32 heavy. 버튼 높이 44(`control.buttonHeight.medium`), 사이 `spacing.sm` 12 |
56
56
  | ② 기간 표시 | Text `role="status"`(Web) · `accessibilityLiveRegion="polite"` Text(Native) | ① 아래 | 위아래 24 |
57
57
  | ③ 요약 | Surface `padding="lg"` > Stack `gap="lg"` > AnimatedStatistic ×3 | ② 아래 | 안쪽 `spacing.lg` 20, 수치 사이 20, 라벨-값 `spacing.xxs` 4 |
58
58
  | ④ 활동 | Text `emphasis="strong"` · Button `ghost` · ActivityHeatmap `view` | ③ 아래, 세 요소가 바깥 Stack 직계 | 요소 사이 24 |
@@ -78,9 +78,9 @@
78
78
  | 상태 | 화면 모습 | 행동 |
79
79
  | --- | --- | --- |
80
80
  | 기본 | ①~⑤ 모두. AnimatedStatistic이 이전 값에서 새 값으로 굴러간다(모션 줄이기면 즉시) | 기간 선택·보기 전환 |
81
- | 로딩 | 스토리에 없음. ①② 그대로, ③ Surface 안에 수치 3개 모양의 [Skeleton](../components/skeleton.md)(라벨 `shape="text"` 40% + 값 `shape="block"` 30%, 사이 `spacing.xxs` 4), ④⑤는 그리지 않는다. 로딩 사실은 영역 단위로 한 번 알린다(Web 감싼 Surface `aria-busy`·`aria-label`, Native 첫 Skeleton `accessibilityLabel`) | 기간 선택은 그대로 둔다 |
82
- | 빈 | ③ 수치는 0, ④⑤ 자리에 EmptyState(제목·설명) | [9월 보기] 같은 기록 있는 기간으로 |
83
- | 오류 | 스토리에 없음. ② 바로 아래 [Notice](../components/notice.md) `tone="danger"`(Native `announcement="assertive"` — 기본 `none`). 문구는 원인별 키(`errorKey`: 네트워크 `dashboard.error.network`, 서버 `dashboard.error.server`). 이전 데이터가 있으면 ③④⑤를 유지하고, 첫 로딩 실패면 ③④⑤를 그리지 않는다. 다시 시도가 또 실패하면 같은 Notice를 같은 자리에 두고 버튼 `loading`만 푼다(Notice를 쌓지 않는다) | Notice `action` 다시 시도 1개(`tone="secondary" size="small"`, 요청 중 `loading`) |
81
+ | 로딩 | 동기 스토리에 없음. 제품 최초 조회는 ScreenLayout `state=loading`으로 본문 중앙 spinner 하나를 표시하고 라벨은 접근성 이름으로 전달한다. 재조회는 기존 본문을 유지한다 | 기간 선택 유지, 같은 요청 중복 실행 방지 |
82
+ | 빈 | 동기 스토리는 수치 0과 인라인 EmptyState를 시연한다. 제품의 전체 빈 상태는 ScreenLayout `state=empty`로 본문 중앙에 두고 기간 선택은 actions에 유지한다 | 기록 있는 기간으로 |
83
+ | 오류 | 스토리에 없음. ② 바로 아래 [Notice](../components/notice.md) `tone="danger"`(Native `announcement="assertive"` — 기본 `none`). 문구는 원인별 키(`errorKey`: 네트워크 `dashboard.error.network`, 서버 `dashboard.error.server`). 이전 데이터가 있으면 ③④⑤를 유지하고, 첫 로딩 실패면 ScreenLayout `state=error`와 `stateAction`으로 본문 중앙에 오류·재시도를 함께 둔다. 다시 시도가 또 실패하면 같은 Notice를 같은 자리에 두고 버튼 `loading`만 푼다(Notice를 쌓지 않는다) | Notice `action` 다시 시도 1개(`tone="secondary" size="small"`, 요청 중 `loading`) |
84
84
  | 기간 변경 | ② 기간 문구가 바뀌어 읽히고 ③④⑤가 같이 바뀐다 | — |
85
85
  | 날짜 값 모름 | 히트맵 값 `null`은 `formatDay`로 "확인되지 않음"이라 읽힌다. 0과 구분해 넘긴다 | — |
86
86
 
@@ -99,125 +99,67 @@
99
99
  | [ActivityHeatmap](../components/activity-heatmap.md) | ④ 날짜별 활동 |
100
100
  | [List](../components/list.md) · [ListRow](../components/list-row.md) | ⑤ 기록 |
101
101
  | [EmptyState](../components/empty-state.md) | 기록 없음 |
102
- | [Skeleton](../components/skeleton.md) | 로딩 |
102
+ | [Skeleton](../components/skeleton.md) | 로딩 | 동기 스토리에 없음. 제품 최초 조회는 ScreenLayout `state=loading`으로 본문 중앙 spinner 하나를 표시하고 라벨은 접근성 이름으로 전달한다. 재조회는 기존 본문을 유지한다 | 기간 선택 유지, 같은 요청 중복 실행 방지 |
103
103
  | [Notice](../components/notice.md) | 오류 |
104
104
  | [화면 여백과 너비](../tokens/layout.md) | gutter·최대 폭 |
105
105
  | [간격](../tokens/spacing.md) | `spacing.xxs`·`sm`·`lg`·`xl` |
106
106
 
107
107
  ## 코드 골격
108
108
 
109
- 수치 라벨·기간 목록·`formatDay` 문구는 제품 소유다. 상태마다 다른 문구는 키를 문자열로 조립하지 않고
110
- 상태→키 상수 표로 고른다. 그래야 상태 표와 키 목록이 한 곳에서 맞는다. 아래 두 예는 설치 버전 타입으로
111
- 검사했다(`period`·`data`·`error` 등 상태 값은 제품이 둔다).
109
+ 완성된 동기 fixture의 배치·기간 계산 원본은 `showcase/web/src/patterns/Dashboard.stories.tsx`,
110
+ `showcase/native/src/Dashboard.stories.tsx`, `showcase/shared/dashboard-pattern.ts`다.
111
+ 이 스토리는 공개 DashboardScreen API가 아니며 서버 최초 조회·재조회 실패를 구현한 증거도 아니다.
112
+ 제품은 아래 공개 ScreenLayout의 상태 경로를 사용하고 기존 요약·활동·목록을 children으로 둔다.
113
+ `periodControls`는 위에서 설명한 SegmentedControl, `summaryAndRecords`는 같은 기간 데이터로 만든 세 영역이다.
112
114
 
113
115
  ```tsx
114
116
  // Web
115
- import { Container, Stack, Surface, Text } from "@hjmds/react/layout";
116
- import { Heading } from "@hjmds/react/heading";
117
+ import { ScreenLayout } from "@hjmds/react/screens";
117
118
  import { Button } from "@hjmds/react/actions";
118
- import { SegmentedControl } from "@hjmds/react/selection";
119
- import { EmptyState, Notice, Skeleton } from "@hjmds/react/feedback";
120
- import { AnimatedStatistic } from "@hjmds/react/statistic-motion";
121
- import { ActivityHeatmap } from "@hjmds/react/activity-heatmap";
122
- import { List, ListRow } from "@hjmds/react/display";
123
- import { resolveWindowClass } from "@hjmds/design-contracts/responsive";
124
-
125
- type Period = "week" | "month" | "lastMonth";
126
- type LoadError = "network" | "server";
127
- const periods: readonly Period[] = ["week", "month", "lastMonth"];
128
- const periodKey = { week: "dashboard.period.week", month: "dashboard.period.month", lastMonth: "dashboard.period.lastMonth" } as const satisfies Record<Period, string>;
129
- const errorKey = { network: "dashboard.error.network", server: "dashboard.error.server" } as const satisfies Record<LoadError, string>;
130
- // 라벨은 지금 보기가 아니라 바뀔 보기를 말한다.
131
- const viewToggleKey = { grid: "dashboard.showList", list: "dashboard.showGrid" } as const;
132
- const gutter = resolveWindowClass(window.innerWidth) === "compact" ? "compact" : "regular";
133
-
134
- <main><Container size="content" gutter={gutter}><Stack gap="xl">
135
- <Heading level="level3" semanticLevel={1}>{t("dashboard.title")}</Heading>
136
- <Text as="p">{t("dashboard.intro")}</Text>
137
- <SegmentedControl label={t("dashboard.period")} presentation="pills"
138
- items={periods.map(id => ({ value: id, label: t(periodKey[id]) }))} value={period} onValueChange={setPeriod} />
139
- <Text role="status">{t("dashboard.range", range)}</Text>
140
- {error ? <Notice tone="danger" title={t(errorKey[error])}
141
- action={<Button tone="secondary" size="small" loading={retrying} onClick={retry}>{t("common.retry")}</Button>} /> : null}
142
- {loading && !data ? (
143
- <Surface padding="lg" aria-busy="true" aria-label={t("dashboard.loading")}><Stack gap="lg">
144
- {[0, 1, 2].map(i => <Stack key={i} gap="xxs"><Skeleton shape="text" width="40%" /><Skeleton shape="block" width="30%" /></Stack>)}
145
- </Stack></Surface>
146
- ) : data ? <>
147
- <Surface padding="lg"><Stack gap="lg">
148
- <AnimatedStatistic descriptor={{ id: "count", label: t("dashboard.count") }} value={data.count} locale={locale} />
149
- <AnimatedStatistic descriptor={{ id: "days", label: t("dashboard.activeDays") }} value={data.activeDays} locale={locale} />
150
- <AnimatedStatistic descriptor={{ id: "minutes", label: t("dashboard.minutes") }} value={data.minutes} locale={locale} />
151
- </Stack></Surface>
152
- {data.count ? <>
153
- <Text emphasis="strong">{t("dashboard.activity")}</Text>
154
- <Button tone="ghost" onClick={() => setList(v => !v)}>{t(viewToggleKey[list ? "list" : "grid"])}</Button>
155
- <ActivityHeatmap descriptor={data.heatmap} label={t("dashboard.activity")} formatDay={formatDay} view={list ? "list" : "grid"} />
156
- <List label={t("dashboard.records")}>{data.records.map(r =>
157
- <ListRow key={r.id} title={r.title} description={t("dashboard.recordMeta", { date: r.date, minutes: r.minutes })} />)}</List>
158
- </> : <EmptyState title={t("dashboard.empty.title")} description={t("dashboard.empty.body")}
159
- action={<Button onClick={() => setPeriod("month")}>{t("dashboard.reset")}</Button>} />}
160
- </> : null}
161
- </Stack></Container></main>
119
+ import { Notice } from "@hjmds/react/feedback";
120
+
121
+ const initial = data === undefined;
122
+ <ScreenLayout title={t("dashboard.title")} actions={periodControls}
123
+ state={initial
124
+ ? error ? { kind: "error", title: t(errorKey[error]) } : { kind: "loading", title: t("dashboard.loading") }
125
+ : data.count === 0 ? { kind: "empty", title: t("dashboard.empty.title") } : { kind: "ready" }}
126
+ stateAction={initial && error ? <Button loading={retrying} onClick={retry}>{t("common.retry")}</Button>
127
+ : !initial && data.count === 0 ? <Button onClick={() => setPeriod("month")}>{t("dashboard.reset")}</Button> : null}
128
+ notice={!initial && error ? <Notice tone="danger" title={t(errorKey[error])}
129
+ action={<Button tone="secondary" loading={retrying} onClick={retry}>{t("common.retry")}</Button>} /> : null}>
130
+ {summaryAndRecords}
131
+ </ScreenLayout>
162
132
  ```
163
133
 
164
134
  ```tsx
165
135
  // Native
166
- import { ScrollView, useWindowDimensions } from "react-native";
167
- import { spacing } from "@hjmds/design-contracts/foundations";
168
- import { resolveWindowClass } from "@hjmds/design-contracts/responsive";
169
- import { Container, Stack, Surface, Text } from "@hjmds/react-native/primitives";
170
- import { Heading } from "@hjmds/react-native/heading";
136
+ import { ScreenLayout } from "@hjmds/react-native/screens";
171
137
  import { Button } from "@hjmds/react-native/actions";
172
- import { SegmentedControl } from "@hjmds/react-native/inputs";
173
- import { EmptyState, Notice, Skeleton } from "@hjmds/react-native/feedback";
174
- import { AnimatedStatistic } from "@hjmds/react-native/statistic-motion";
175
- import { ActivityHeatmap } from "@hjmds/react-native/activity-heatmap";
176
- import { List, ListRow } from "@hjmds/react-native/data-display";
177
-
178
- // periods·periodKey·errorKey·viewToggleKey는 Web과 같다.
179
- const { width } = useWindowDimensions();
180
- const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
181
-
182
- <ScrollView contentContainerStyle={{ paddingVertical: spacing.lg }}>
183
- <Container size="content" gutter={gutter}><Stack gap="xl">
184
- <Heading level="level3" semanticLevel={1}>{t("dashboard.title")}</Heading>
185
- <Text>{t("dashboard.intro")}</Text>
186
- <SegmentedControl label={t("dashboard.period")} presentation="pills"
187
- items={periods.map(id => ({ value: id, label: t(periodKey[id]) }))} value={period} onValueChange={setPeriod} />
188
- <Text accessibilityLiveRegion="polite">{t("dashboard.range", range)}</Text>
189
- {error ? <Notice tone="danger" announcement="assertive" title={t(errorKey[error])}
190
- action={<Button tone="secondary" size="small" loading={retrying} onPress={retry}>{t("common.retry")}</Button>} /> : null}
191
- {loading && !data ? (
192
- <Surface padding="lg"><Stack gap="lg">
193
- {[0, 1, 2].map(i => <Stack key={i} gap="xxs">
194
- <Skeleton shape="text" width="40%" {...(i === 0 ? { accessibilityLabel: t("dashboard.loading") } : {})} />
195
- <Skeleton shape="block" width="30%" />
196
- </Stack>)}
197
- </Stack></Surface>
198
- ) : data ? <>
199
- <Surface padding="lg"><Stack gap="lg">
200
- <AnimatedStatistic descriptor={{ id: "count", label: t("dashboard.count") }} value={data.count} locale={locale} />
201
- {/* activeDays · minutes — Web과 같다 */}
202
- </Stack></Surface>
203
- {data.count ? <>
204
- <Text emphasis="strong">{t("dashboard.activity")}</Text>
205
- <Button tone="ghost" onPress={() => setList(v => !v)}>{t(viewToggleKey[list ? "list" : "grid"])}</Button>
206
- <ActivityHeatmap descriptor={data.heatmap} label={t("dashboard.activity")} formatDay={formatDay} view={list ? "list" : "grid"} />
207
- <List label={t("dashboard.records")}>{data.records.map(r =>
208
- <ListRow key={r.id} title={r.title} description={t("dashboard.recordMeta", { date: r.date, minutes: r.minutes })} />)}</List>
209
- </> : <EmptyState title={t("dashboard.empty.title")} description={t("dashboard.empty.body")}
210
- action={<Button onPress={() => setPeriod("month")}>{t("dashboard.reset")}</Button>} />}
211
- </> : null}
212
- </Stack></Container>
213
- </ScrollView>
138
+ import { Notice } from "@hjmds/react-native/feedback";
139
+
140
+ const initial = data === undefined;
141
+ <ScreenLayout title={t("dashboard.title")} actions={periodControls}
142
+ state={initial
143
+ ? error ? { kind: "error", title: t(errorKey[error]) } : { kind: "loading", title: t("dashboard.loading") }
144
+ : data.count === 0 ? { kind: "empty", title: t("dashboard.empty.title") } : { kind: "ready" }}
145
+ stateAction={initial && error ? <Button loading={retrying} onPress={retry}>{t("common.retry")}</Button>
146
+ : !initial && data.count === 0 ? <Button onPress={() => setPeriod("month")}>{t("dashboard.reset")}</Button> : null}
147
+ notice={!initial && error ? <Notice tone="danger" announcement="assertive" title={t(errorKey[error])}
148
+ action={<Button tone="secondary" loading={retrying} onPress={retry}>{t("common.retry")}</Button>} /> : null}>
149
+ {summaryAndRecords}
150
+ </ScreenLayout>
214
151
  ```
215
152
 
153
+ 최초 로딩·실패·빈 상태는 host가 확보한 본문 영역의 가로·세로 중앙에 둔다. 별도 ScrollView로
154
+ ScreenLayout을 감싸지 않는다. 재조회는 기존 데이터와 기간 설명을 유지하고 `ready` 상태에서
155
+ 해당 재조회 행동만 pending으로 표시한다. 새 기간 요청 중 이전 기간 자료를 새 기간 자료처럼 표시하지 않는다.
156
+ 요청 취소 또는 request key 대조로 오래된 응답이 최신 선택을 덮지 못하게 한다.
157
+
216
158
  ## 큰 글자·다크·좁은 폭
217
159
 
218
160
  | 조건 | 바뀌는 것 |
219
161
  | --- | --- |
220
- | 큰 글자(`textScale` 2) | 기간 선택은 큰 글자에서 세로로 쌓인다. Statistic 값은 Web에서 `type.title` 크기 × 글자 배율로 커지고 `overflow-wrap: anywhere`로 꺾인다. 긴 제목은 Heading이 줄바꿈한다 |
162
+ | 큰 글자(`textScale` 2) | 기간 선택 pills는 가로 순서를 유지하며 공간이 부족하면 감긴다. Statistic 값은 Web에서 `type.title` 크기 × 글자 배율로 커지고 `overflow-wrap: anywhere`로 꺾인다. 긴 제목은 Heading이 줄바꿈한다 |
221
163
  | 다크 | 테마 토큰만 쓰므로 바꿀 것이 없다 |
222
164
  | 좁은 폭 | 구조 동일, gutter가 `compact` 16으로 준다. ActivityHeatmap 격자가 좁으면 보기 전환으로 목록을 쓴다 |
223
165
  | 넓은 폭 Web | 한 열 유지, Container `content`가 1200으로 폭을 묶고 gutter `regular` 20 |
@@ -228,18 +170,19 @@ const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
228
170
  | --- | --- | --- |
229
171
  | 바깥 스크롤 | 문서 스크롤 | `ScrollView` 위아래 `spacing.lg` 20, 좌우는 안쪽 Container gutter |
230
172
  | 상단 안전 영역 | 브라우저 | 화면이 소유하지 않는다. 내비게이션 헤더 또는 [TopBar](../components/top-bar.md) `safeAreaTop`이 맡고, 헤더 없이 띄울 때만 제품이 감싼다 |
231
- | 기간 표시 알림 | `role="status"` | `accessibilityLiveRegion="polite"`(Android만), iOS는 제품이 직접 알림(함정 참고) |
232
- | 로딩 알림 | 감싼 영역 `aria-busy` + `aria-label`(Skeleton은 항상 `aria-hidden`) | 첫 Skeleton `accessibilityLabel` 하나 |
173
+ | 기간 표시 알림 | `role="status"` | 공개 StatusText(Android live region + iOS 변경 알림) |
174
+ | 로딩 알림 | ScreenLayout 본문 busy와 Spinner 접근성 이름 | ScreenLayout 내부 Spinner 접근성 이름 |
233
175
  | 오류 Notice 알림 | `tone="danger"`가 알림 영역 | `announcement="assertive"`를 명시(기본 `none`) |
234
176
  | EmptyState 기본 위아래 여백 | `spacing.xxl` 32(`styles.css`) | `spacing.xxxl` 40(`emptyStateRecipe.density.regular`) |
235
177
 
236
178
  ## 함정
237
179
 
238
180
  - ActivityHeatmap은 1~366일만 받는다(범위 밖이면 `RangeError`). 1년 넘는 기록은 제품이 기간을 나눈다.
239
- - 스토리의 `PatternStatus`는 showcase 전용이다. iOS 알림은 `AccessibilityInfo.announceForAccessibilityWithOptions(text, { queue: true })`로
240
- 제품이 두되, **기간 문구가 이전과 달라졌을 때만** 보내고 첫 렌더·같은 문구 재렌더·빈 문자열·앱이 active가 아닐 때(`AppState.currentState`)는
241
- 보내지 않는다(`showcase/native/src/pattern-status.tsx`).
181
+ - 기간·결과 문구는 공개 [StatusText](../components/text.md#statustext)를 쓴다. iOS에서는 active 중 바뀐 비어 있지 않은 문구만 알리고, 최초 안내·동일 문구·배경 변경은 재생하지 않는다.
242
182
  - 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
243
183
  Text의 기본 `emphasis="regular"`가 굵기를 덮어 24px 보통 굵기로 나오고(`.hjm-text[data-emphasis="regular"]`), Native는 제목 단계가 없다.
244
184
  [Text](../components/text.md) 규칙대로 새 제품은 Heading `level3`·`semanticLevel={1}`(24/32 heavy)을 쓴다.
245
185
  - 예제는 양 플랫폼 모두 Container로 감싼다. 예제의 compact gutter 16과 제품이 폭에 따라 선택하는 regular gutter 20을 구분한다.
186
+
187
+
188
+ 2026-10-13 행동 검수에서 제거/복구 뒤 키보드 문맥 유실을 확인해 보강했다. Web 빈 기간 복구는 선택된 기간 컨트롤로 초점을 돌린다. 기간을 바꿔도 목록/격자 보기 선택은 유지한다.