@hjmds/design-contracts 1.12.1 → 1.13.1

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 +26 -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 +31 -0
  16. package/dist/component-recipes.d.ts.map +1 -1
  17. package/dist/component-recipes.js +20 -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 +378 -0
  67. package/docs/sheet.md +21 -0
  68. package/docs/splitter.md +8 -2
  69. package/docs/theming.md +36 -29
  70. package/docs/toggle-group.md +13 -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 +107 -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 +221 -0
  169. package/docs/usage/components/section.md +111 -0
  170. package/docs/usage/components/segmented-control.md +146 -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 +157 -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 +279 -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,111 @@
1
+ # DateRangePicker
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [DateRange](../../date-range.md), 격자는 [Calendar](../../calendar.md), 규칙 함수 `src/date-range.ts`
9
+ - 스토리북: `배포/컴포넌트/입력/기간 선택`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 시작~끝 날짜 **구간**을 고를 때 쓴다(통계 기간, 예약, 검색 필터). 필드 트리거나 오버레이 없이 달력 격자를
14
+ 그 자리에 펼쳐 둔다. 클릭 규칙은 계약이 정한다: 비었거나 완성된 구간에서 누르면 새로 시작하고,
15
+ 고르는 중에 시작보다 앞선 날을 누르면 두 값을 바꿔 담는다([표](../../date-range.md)).
16
+
17
+ ## 쓰지 않을 때
18
+
19
+ | 상황 | 대신 쓸 것 |
20
+ | --- | --- |
21
+ | 날짜 하나 | [DatePicker](date-picker.md) |
22
+ | 달력을 보여 주기만 하거나 날짜 하나를 인라인으로 고름 | [Calendar](calendar.md) |
23
+ | 접힌 필드에서 눌러 여는 구간 선택 | 없음. 필요하면 [Sheet](sheet.md)·[Popover](popover.md) 안에 제품이 합성한다 |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `DateRangePicker` | 기본 | `@hjmds/react`, `/date-range` | `@hjmds/react-native`, `/date-range` |
30
+
31
+ ## 최소 사용 예
32
+
33
+ 격자 `cells`·`weekdayLabels`·`todayDate`와 `monthLabel`은 제품이 자기 시계·로캘로 만든다.
34
+
35
+ ```tsx
36
+ // Web
37
+ import { DateRangePicker } from "@hjmds/react/date-range";
38
+
39
+ <DateRangePicker
40
+ descriptor={{
41
+ grid: { cells, weekdayLabels, todayDate },
42
+ monthLabel: formatMonth(month),
43
+ focusedMonth: month,
44
+ onFocusedMonthChange: (next) => setMonth(next),
45
+ }}
46
+ value={range}
47
+ onValueChange={setRange}
48
+ previousMonth={{ month: prevMonthOf(month), label: t("calendar.previousMonth") }}
49
+ nextMonth={{ month: nextMonthOf(month), label: t("calendar.nextMonth") }}
50
+ composeAccessibleName={({ date, isToday }) =>
51
+ t(isToday ? "calendar.cell.today" : "calendar.cell.default", { date: formatDate(date) })}
52
+ rangeLabels={{ start: t("range.start"), end: t("range.end"), between: t("range.between") }}
53
+ />
54
+ ```
55
+
56
+ ```tsx
57
+ // Native
58
+ import { DateRangePicker } from "@hjmds/react-native/date-range";
59
+
60
+ <DateRangePicker
61
+ descriptor={descriptor /* Web과 같은 모양 */}
62
+ value={range}
63
+ onValueChange={setRange}
64
+ composeAccessibleName={composeCellName}
65
+ rangeLabels={{ start: t("range.start"), end: t("range.end"), between: t("range.between") }}
66
+ />
67
+ ```
68
+
69
+ ## 축과 기본값
70
+
71
+ | prop | 값 | 기본값 | 설명 |
72
+ | --- | --- | --- | --- |
73
+ | `value` / `defaultValue` | `{ start: string \| null, end: string \| null }`(ISO `YYYY-MM-DD`) | `{ start: null, end: null }` | 제어 또는 비제어 중 하나. `start`만 있는 상태가 "고르는 중"이다. 별도 플래그는 없다 |
74
+ | `onValueChange` | `(value: DateRangeValue) => void` | — | 누를 때마다 다음 구간(`{ start, end }`)을 알린다 |
75
+ | `descriptor` | `{ grid, monthLabel, focusedMonth \| defaultFocusedMonth, onFocusedMonthChange? }` | — (필수) | Calendar descriptor에서 선택 필드를 뺀 모양 |
76
+ | `descriptor.onFocusedMonthChange` | `(month: string, reason: "previous" \| "next" \| "jump") => void` | — | 없으면 이동 버튼이 비활성 |
77
+ | `composeAccessibleName` | `(info: { date, isToday, isSelected, disabled, content? }) => string` | — (필수) | 구간 접미사는 컴포넌트가 붙인다 |
78
+ | `rangeLabels` | `{ start: string, end: string, between: string }` | — (필수) | 셀 이름 뒤에 붙는 구간 위치 접미사 |
79
+ | `previousMonth` · `nextMonth` | `{ month: string, label: string }` | — | 없으면 이동 버튼이 없다 |
80
+ | `renderCellContent` | `(date: string) => ReactNode` | — | 날짜 아래 보조 표시 |
81
+ | Web `layoutStyle` | 배치 전용 style | — | 루트 배치 |
82
+
83
+ ## 배치
84
+
85
+ | 항목 | 값 | 근거 |
86
+ | --- | --- | --- |
87
+ | 크기 | 날짜 셀은 `medium` `control.minTouchTarget` 44, `large` `glyph.xxl` 44로 같고 7열이다. Web 격자는 최소 폭 7×44 = 308을 잡는다. 월 이동 버튼 44 | `src/calendar.ts`(`calendarRecipe`), `packages/react/src/styles.css` `.hjm-calendar*` |
88
+ | 간격 | 월 머리 안 간격·세로 간격 `spacing.xs` 8. Native 구간 점은 날짜 아래 `spacing.xxs` 4, 점 지름 구간 안 4·시작·끝 6. 스토리는 격자 아래 문구를 `Stack gap="md"`(16)로 띄운다 | `packages/react-native/src/date-range.tsx`, `showcase/web/src/patterns/DateRange.stories.tsx` |
89
+ | 순서·정렬 | 오버레이 없이 본문(폼·필터 영역·[Section](section.md)) 안에 격자를 펼쳐 둔다. 위에서 월 머리(이전 · 월 이름 · 다음) → 요일 → 날짜 격자. 선택 결과 문구와 제출 버튼은 격자 **아래**에 둔다(스토리는 `role="status"` 문구). 제출 버튼은 `isCompleteDateRange(value)`가 참일 때만 활성이다 | 같은 파일 |
90
+ | 고정·스크롤 | 본문과 함께 스크롤한다(오버레이 없음) | — |
91
+ | 좁은 폭·큰 글자 | Web 격자 최소 폭 308보다 좁은 컨테이너에 두지 않는다 | `packages/react/src/styles.css` `.hjm-calendar__grid` |
92
+
93
+ ## 꼭 지킬 것
94
+
95
+ - 제출 버튼은 `isCompleteDateRange(value)`(`@hjmds/design-contracts/components/date-range`)가 참일 때만 연다.
96
+ - `end`만 있거나 `end`가 `start`보다 앞선 값을 넘기면 `TypeError`/`RangeError`가 난다.
97
+ - `rangeLabels`와 셀 이름(`composeAccessibleName`)은 i18n 키로 만든다. 구간 위치 접미사는 컴포넌트가
98
+ 셀 이름 뒤에 `, `로 붙인다.
99
+ - 달을 넘기면 제품이 새 달의 `cells`와 `monthLabel`을 다시 만들어 넘긴다.
100
+
101
+ ## 플랫폼 차이
102
+
103
+ | 항목 | Web | Native |
104
+ | --- | --- | --- |
105
+ | 구간 표시 | 셀 `data-range` band, 마우스 hover로 끝 미리보기 | 날짜 아래 점(고르는 중에는 반투명)과 셀 이름 |
106
+ | 배치 | `layoutStyle`(그 밖에 `className`) | 없음(감싸는 View로 배치) |
107
+
108
+ ## 함정
109
+
110
+ - `renderCellContent`는 Calendar와 달리 셀 객체가 아니라 날짜 문자열(`date`)만 받는다.
111
+ - `previousMonth`/`nextMonth`를 넘겨도 `descriptor.onFocusedMonthChange`가 없으면 이동 버튼이 비활성이다.
@@ -0,0 +1,103 @@
1
+ # DescriptionList
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [DescriptionList](../../description-list.md), recipe `descriptionListRecipe`(`src/description-list.ts`)
9
+ - 스토리북: `배포/컴포넌트/데이터 표시/설명 목록`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 라벨-값 쌍의 묶음을 보여 줄 때 쓴다. 프로필 정보, 계약·등급 조건, 주문 상세처럼 값이 임의 길이의
14
+ 텍스트인 읽기 전용 정보가 여기에 속한다. 화면 폭과 글자 배율에 따라 2열에서 1열로 접는 판정은
15
+ HJM이 소유한다.
16
+
17
+ ## 쓰지 않을 때
18
+
19
+ | 상황 | 대신 쓸 것 |
20
+ | --- | --- |
21
+ | 짧은 숫자 지표·추세 | [Statistic](statistic.md) |
22
+ | 누르면 이동·편집하는 행 | [ListRow](list-row.md) |
23
+ | 정렬·여러 열이 있는 표 | [DataTable](data-table.md)(Web) |
24
+ | 입력 폼 | [Form](form.md), [Field](field.md) |
25
+
26
+ ## 공개 이름과 import
27
+
28
+ | 이름 | 역할 | Web | Native |
29
+ | --- | --- | --- | --- |
30
+ | `DescriptionList` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
31
+
32
+ ## 최소 사용 예
33
+
34
+ ```tsx
35
+ // Web
36
+ import { DescriptionList } from "@hjmds/react/display";
37
+
38
+ <DescriptionList
39
+ items={[
40
+ { id: "grade", label: t("contract.grade"), value: formattedGrade },
41
+ { id: "seasons", label: t("contract.seasons"), value: formattedSeasons },
42
+ ]}
43
+ />
44
+ ```
45
+
46
+ ```tsx
47
+ // Native
48
+ import { DescriptionList } from "@hjmds/react-native/data-display";
49
+
50
+ <DescriptionList
51
+ label={t("contract.summary")}
52
+ descriptor={{
53
+ items: [
54
+ { id: "grade", label: t("contract.grade"), value: formattedGrade },
55
+ { id: "seasons", label: t("contract.seasons"), value: formattedSeasons },
56
+ ],
57
+ }}
58
+ />
59
+ ```
60
+
61
+ ## 축과 기본값
62
+
63
+ | prop | 값 | 기본값 | 설명 |
64
+ | --- | --- | --- | --- |
65
+ | `columns` | `1` · `2` | `2` | 요청값일 뿐이며 resolver가 폭·`textScale`을 보고 1열로 접을 수 있다 |
66
+ | 항목 | `{ id, label: string, value: string }` | — (필수) | 셋 다 빈 문자열이면 안 된다. 항목이 없거나 `id`가 겹치면 예외가 난다 |
67
+ | Native `label` | `string` | — (필수) | 목록 전체의 접근성 이름 |
68
+ | Native `availableWidth` | 양수 | 측정값 | 주면 측정보다 우선 |
69
+ | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`·`itemStyle`은 deprecated |
70
+
71
+ 콜백 prop은 없다.
72
+
73
+ ## 배치
74
+
75
+ | 항목 | 값 | 근거 |
76
+ | --- | --- | --- |
77
+ | 크기 | 항목 최소 폭 160 × `textScale` | `src/description-list.ts`(`descriptionListRecipe.group.minItemWidth`) |
78
+ | 간격 | 항목 사이 가로·세로 `spacing.sm` 12. 각 항목의 라벨–값 `spacing.xxs` 4 | `src/description-list.ts`, `packages/react/src/styles.css` `.hjm-description-list*`, `packages/react-native/src/data-display.tsx` |
79
+ | 순서·정렬 | [Card](card.md)·[Section](section.md) 본문 안에 둔다. 각 항목은 라벨 위·값 아래. 항목은 `items` 순서대로 행 우선으로 채운다(왼쪽 → 오른쪽, 위 → 아래) | 같은 파일 |
80
+ | 고정·스크롤 | — | — |
81
+ | 좁은 폭·큰 글자 | 폭이 `2 × 160 × textScale + 12` 미만이면 1열로 접는다(배율 1이면 332, 배율 1.6이면 524). 값이 길면 줄바꿈한다(Web `overflow-wrap: anywhere`) | `src/description-list.ts`(열 판정 함수), `packages/react/src/styles.css` |
82
+
83
+ ## 꼭 지킬 것
84
+
85
+ - `value`는 제품이 locale·단위까지 포맷한 문자열로 넘긴다. HJM은 값을 포맷하지 않는다.
86
+ - 큰 글자 대응으로 화면에서 `fontScale`을 보고 `columns={1}`을 넘기지 않는다. 접힘은 resolver가 한다
87
+ ([계약](../../description-list.md#큰-글자에서의-전환--resolver가-소유)).
88
+ - 라벨은 i18n 키로 넣는다. Native는 목록 전체의 접근성 이름 `label`이 필수다.
89
+ - 배치는 `layoutStyle`로만 한다(Web·Native). 색·글자 스타일을 덮지 않는다. Native `style`·`itemStyle`은 deprecated — 배치는 `layoutStyle`, 열 배치는 `descriptor.columns`로 옮긴다.
90
+
91
+ ## 플랫폼 차이
92
+
93
+ | 항목 | Web | Native |
94
+ | --- | --- | --- |
95
+ | 데이터 전달 | `items`, `columns` 평면 prop | `descriptor={{ items, columns }}` |
96
+ | 목록 이름 | 없음(`dl`/`dt`/`dd` 의미 구조) | `label` 필수, `accessibilityRole="list"` |
97
+ | 폭 측정 | 요소 폭 측정 | `availableWidth`가 우선, 없으면 `onLayout` 측정, 첫 프레임은 창 폭 |
98
+ | 배치 prop | `layoutStyle`(`style`과 합친다) | `layoutStyle`(`style`·`itemStyle`은 deprecated) |
99
+
100
+ ## 함정
101
+
102
+ - Native에서 `availableWidth`에 0 이하·NaN을 넘기면 `RangeError`가 난다.
103
+ - Native는 각 쌍을 `"라벨, 값"` 하나의 접근성 노드로 읽는다. 라벨 문구에 쉼표·값을 반복하지 않는다.
@@ -0,0 +1,124 @@
1
+ # DesignSystemProvider
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [DesignSystemProvider](../../design-system-provider.md), [브랜드 경계](../../brand-boundary.md)(브랜드 규칙 단일 원본), [테마 주입](../../theming.md), [팔레트 결정](../../theme-palette.md)
9
+ - 스토리북: `배포/컴포넌트/기반 기능/디자인 시스템 설정`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 앱 루트에 한 번 둔다. theme(light/dark/system)·방향·글자 배율·reduced motion을 해석하고, 제품 브랜드색을
14
+ HJM semantic token 위에 얹는 **유일한 진입점**이다. Native HJM 컴포넌트는 테마를 이 Provider에서 읽으므로
15
+ Provider 없이 렌더하면 예외가 난다. 화면 일부의 밀도·테마만 바꿀 때는 중첩 Provider를 둔다.
16
+
17
+ ## 쓰지 않을 때
18
+
19
+ | 상황 | 대신 쓸 것 |
20
+ | --- | --- |
21
+ | 브랜드색을 컴포넌트마다 넣고 싶다 | Provider `brandPalette` 한 번(컴포넌트 `style`로 칠하지 않는다) |
22
+ | 컴포넌트 하나만 촘촘하게 | 해당 컴포넌트의 `density` prop |
23
+ | 명령형으로 Dialog·Sheet 열기 | [Dialog](dialog.md)의 `OverlayStackProvider`(Web) |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `HjmProvider` | 기본 — Web | `@hjmds/react`, `/provider` | 없음 |
30
+ | `HjmNativeProvider` | 기본 — Native | 없음 | `@hjmds/react-native`, `/provider` |
31
+
32
+ 테마 값은 Web `useHjmTheme()`, Native `useHjmNativeTheme()`로 읽는다(둘 다 Provider 밖에서는 예외).
33
+
34
+ ## 최소 사용 예
35
+
36
+ ```tsx
37
+ // Web
38
+ import "@hjmds/react/styles.css";
39
+ import { HjmProvider, type HjmBrandPalette } from "@hjmds/react/provider";
40
+
41
+ // 제품이 정한 브랜드 key만 넘긴다. "#…"는 제품 docs/DESIGN.md의 값 자리이며 HJM 기본값이 아니다.
42
+ const PRODUCT_BRAND_PALETTE = {
43
+ light: { primary: "#…", contentBrand: "#…" },
44
+ dark: { primary: "#…", contentBrand: "#…" },
45
+ } satisfies HjmBrandPalette;
46
+
47
+ <HjmProvider theme="system" brandPalette={PRODUCT_BRAND_PALETTE}>
48
+ <App />
49
+ </HjmProvider>
50
+ ```
51
+
52
+ ```tsx
53
+ // Native — SafeAreaProvider 안. PRODUCT_BRAND_PALETTE는 Web과 같은 제품 값(HjmNativeBrandPalette)
54
+ import { HjmNativeProvider } from "@hjmds/react-native/provider";
55
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
56
+
57
+ const insets = useSafeAreaInsets();
58
+
59
+ <HjmNativeProvider theme="system" brandPalette={PRODUCT_BRAND_PALETTE} safeAreaInsets={insets}>
60
+ <App />
61
+ </HjmNativeProvider>
62
+ ```
63
+
64
+ ```ts
65
+ // 제품 테스트: 팔레트를 바꿀 때마다 돈다
66
+ import { checkBrandPaletteContrast } from "@hjmds/design-contracts/palette-contrast";
67
+ expect(checkBrandPaletteContrast(PRODUCT_BRAND_PALETTE)).toEqual({ light: [], dark: [] });
68
+ ```
69
+
70
+ ## 축과 기본값
71
+
72
+ | prop | 값 | 기본값 | 설명 |
73
+ | --- | --- | --- | --- |
74
+ | `theme` | `system` · `light` · `dark` | `system` | — |
75
+ | `direction` | `ltr` · `rtl` | `ltr` | — |
76
+ | `textScale` | 연속값 | `1` | — |
77
+ | `reducedMotion` | `true` · `false` | `false` | — |
78
+ | `minimumVisualTarget` | `true` · `false` | `false` | — |
79
+ | `brandPalette` | `{ light?, dark? }` | — | 각각 `ThemeColors` 17개 key 중 필요한 것만 넘긴다(부분 병합). 상태 강조색은 덮을 수 없다. 중첩 Provider는 가장 가까운 상위의 값을 물려받는다 |
80
+ | `value` | `DesignSystemProviderValue`(`resolveDesignSystemProviderValue` 결과) | — | 테스트·스토리·임베딩용. 환경 prop·`brandPalette`와 함께 쓸 수 없고(타입이 막는다), 주면 OS theme·모션 관찰과 상위 `brandPalette` 상속이 멈춘다 |
81
+ | `safeAreaInsets`(Native) | `{ top?, right?, bottom?, left? }`(pt) | `{}` | 보통 `useSafeAreaInsets()` 결과. 중첩 Provider는 가장 가까운 상위 값을 물려받는다 |
82
+
83
+ - 이벤트·콜백 prop은 없다. 해석된 값은 Web `useHjmTheme()`, Native `useHjmNativeTheme()`로 읽는다.
84
+ - `layoutStyle`을 받지 않는다(Web `HjmProvider`는 `layoutStyle` 제외 15개 중 하나). Web 루트 `div`의 표면 처리는 `host`로 정한다.
85
+
86
+ - 주지 않은 축은 상위 Provider → OS 신호 → 기본값 순이다.
87
+
88
+ ## 배치
89
+
90
+ | 항목 | 값 | 근거 |
91
+ | --- | --- | --- |
92
+ | 크기 | 크기·여백을 더하지 않는다. Web은 `div.hjm-root`를 그리고, Native는 렌더 요소가 없다(Context만) | `packages/react/src/provider.tsx`, `packages/react/src/styles.css` `.hjm-root`, `packages/react-native/src/provider.tsx` |
93
+ | 간격 | — | — |
94
+ | 순서·정렬 | 앱 루트에 한 번, 모든 HJM 컴포넌트·포털보다 바깥에 둔다. Native는 `useSafeAreaInsets()`를 쓰므로 `SafeAreaProvider` 안쪽에 둔다. 중첩 Provider는 밀도·테마를 바꿀 영역 둘레에만 둔다 | — |
95
+ | 고정·스크롤 | Web `host="surface"`(기본)는 배경색·글자색·UI 글꼴을 칠한다. 문서 루트가 이미 표면을 칠하면 `host="contents"`(`display: contents`)로 레이아웃에서 빠진다. Native의 화면 여백·안전 영역 배치는 화면 골격이 한다 | `packages/react/src/styles.css` `.hjm-root[data-host]` |
96
+ | 좁은 폭·큰 글자 | — | — |
97
+
98
+ ## 꼭 지킬 것
99
+
100
+ - 제품 브랜드는 `brandPalette` prop으로만 넣는다. 전체 `value`를 손으로 조립하는 것은 테스트·임베딩용이다
101
+ ([브랜드 경계 §1](../../brand-boundary.md#1-지원하는-경로는-brandpalette-하나다)).
102
+ - **Showcase·Theme Studio의 예시 색·자산·테마를 제품 기본값으로 복사하지 않는다**(2026-10-05 규칙). 색은 제품 목적과
103
+ 기존 디자인에서 정해 `brandPalette`의 semantic key로 연결하고, 로고·이미지·문구는 각 컴포넌트의 공개 슬롯으로 넘긴다.
104
+ - 모든 브랜드 팔레트는 `checkBrandPaletteContrast` 결과가 빈 배열이어야 한다(MUST).
105
+ - `.hjm-*` 클래스나 `--hjm-*` 변수를 제품 CSS로 재정의하지 않는다. semantic key로 표현되지 않으면 계약 공백으로 올린다.
106
+ - 제3자 브랜드 색(소셜 로그인)은 테마가 아니다. [AuthProviderButton](auth-provider-button.md)이 소유한다.
107
+
108
+ ## 플랫폼 차이
109
+
110
+ | 항목 | Web | Native |
111
+ | --- | --- | --- |
112
+ | `density`(`comfortable` 기본 · `compact`) | 있음 | 없음 |
113
+ | `host`(`surface` 기본 · `contents`) | 있음. 문서 루트가 이미 표면을 칠하면 `contents` | 없음 |
114
+ | `systemTheme` 고정(SSR·테스트) | 있음 | 없음(`useColorScheme`) |
115
+ | `safeAreaInsets` | 없음 | 있음. Sheet·DatePicker·Select·Combobox가 기본 여백으로 쓴다 |
116
+ | 요소 | `div.hjm-root`(CSS 변수·`dir`·`data-theme`) | 렌더 요소 없음(Context만) |
117
+ | stylesheet | `@hjmds/react/styles.css` import 필요 | 해당 없음 |
118
+
119
+ ## 함정
120
+
121
+ - [테마 주입](../../theming.md)은 2026-10-06에 `brandPalette` prop 경로로 정정됐다. 그 이전 사본이나 1.4식 제품 코드
122
+ (BurnTok `ThemeProvider.tsx`)의 `value` 조립을 새 제품의 출발점으로 복사하지 않는다.
123
+ - Native는 OS reduce-motion 값이 오기 전 첫 프레임을 reduced motion으로 취급한다.
124
+ - Native `textScale`을 명시하면 HJM이 배율을 한 번만 적용하는 controlled 모드가 된다. OS 배율과 곱하지 않는다.
@@ -0,0 +1,176 @@
1
+ # Dialog
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: [Dialog](../../dialog.md), [명령형 오버레이](../../overlay-stack.md), recipe `dialogRecipe`(`src/component-recipes.ts`)
9
+ - 스토리북: `배포/컴포넌트/오버레이/대화상자`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 화면 흐름을 잠시 멈추고 사용자의 주의가 필요한 **짧은 작업**에 쓴다. 이름 바꾸기, 짧은 입력·설정,
14
+ 설명과 함께 고르는 행동처럼 본문(children)에 제품 내용이 들어가는 모달이다.
15
+
16
+ ### Dialog · AlertDialog · Sheet 고르기
17
+
18
+ | 질문 | 고를 것 |
19
+ | --- | --- |
20
+ | 알림 한 줄 또는 "할까요?" 확인만 받는다(삭제·나가기 확인 포함) | [AlertDialog](alert-dialog.md) — `request`로 문구·`mode`(`alert`/`confirm`)·`tone`만 넘기고 중복 확인·busy·오류를 계약이 처리 |
21
+ | 제목·설명 아래 제품 본문(입력·선택)이 있는 짧은 작업 | **Dialog** |
22
+ | 목록·선택지가 길거나 높이를 단계로 바꾸거나 화면 아래/옆에서 올라와야 한다 | [Sheet](sheet.md) — `placement`·`size`·`detents` |
23
+
24
+ 확인 문구와 버튼 두 개를 Dialog로 직접 조립하지 않는다. 그것은 AlertDialog다.
25
+
26
+ ## 쓰지 않을 때
27
+
28
+ | 상황 | 대신 쓸 것 |
29
+ | --- | --- |
30
+ | 결과 알림, 자동으로 사라지는 메시지 | [Toast](toast.md) |
31
+ | 화면 안에 남는 안내 | [Notice](notice.md) |
32
+ | 트리거 옆에 뜨는 비모달 내용 | [Popover](popover.md) |
33
+ | 화면 옆 보조 패널 | [SidePanel](side-panel.md) |
34
+
35
+ ## 공개 이름과 import
36
+
37
+ | 이름 | 역할 | Web | Native |
38
+ | --- | --- | --- | --- |
39
+ | `Dialog` | 기본 | `@hjmds/react`, `/overlays` | `@hjmds/react-native`, `/overlays` |
40
+ | `OverlayStackProvider` | 확장 — 명령형 열기(`useDialog`·`useSheet`·`useOverlayStack`) | `@hjmds/react`, `/overlay-stack` | 없음 |
41
+
42
+ ## 최소 사용 예
43
+
44
+ ```tsx
45
+ // Web
46
+ import { Button } from "@hjmds/react/actions";
47
+ import { Dialog } from "@hjmds/react/overlays";
48
+
49
+ <Dialog
50
+ open={open}
51
+ onOpenChange={(next) => setOpen(next)}
52
+ title={t("album.rename.title")}
53
+ description={t("album.rename.description")}
54
+ closeLabel={t("common.close")}
55
+ busy={saving}
56
+ footer={
57
+ <>
58
+ <Button tone="secondary" onClick={() => setOpen(false)} disabled={saving}>{t("common.cancel")}</Button>
59
+ <Button onClick={save} loading={saving}>{t("common.save")}</Button>
60
+ </>
61
+ }
62
+ >
63
+ {body /* 제품 본문: TextField 등 */}
64
+ </Dialog>
65
+ ```
66
+
67
+ ```tsx
68
+ // Native
69
+ import { Dialog } from "@hjmds/react-native/overlays";
70
+
71
+ <Dialog
72
+ open={open}
73
+ onOpenChange={setOpen}
74
+ onActionError={() => setSaveError(t("album.rename.failed"))}
75
+ title={t("album.rename.title")}
76
+ description={t("album.rename.description")}
77
+ closeLabel={t("common.close")}
78
+ busy={saving}
79
+ primaryAction={{ label: t("common.save"), onPress: save }}
80
+ secondaryAction={{ label: t("common.cancel"), onPress: () => undefined, tone: "secondary" }}
81
+ >
82
+ {body /* 제품 본문 */}
83
+ </Dialog>
84
+ ```
85
+
86
+ Native `save`는 저장 Promise를 반환한다. Dialog가 완료를 기다리고 성공했을 때 `close-action`으로 닫으므로
87
+ `onPress`에서 따로 `setOpen(false)`를 부르지 않는다. 실패는 Promise를 거절하고 `onActionError`에서 본문 오류로
88
+ 표시한다. `void save()`로 반환값을 버리거나 오류를 잡아 성공으로 반환하면 성공으로 처리되므로 피한다.
89
+ 이벤트 밖에서 시작한 요청은 외부 `busy`로 연결한다. 이전 즉시 닫힘 대응 예제가 현재 Promise 계약과 충돌해
90
+ 2026-10-06 독립 재구현 검증에서 교정했다.
91
+
92
+ ```tsx
93
+ // Web 명령형: 앱 루트에 <OverlayStackProvider>를 두고
94
+ import { useDialog } from "@hjmds/react/overlay-stack";
95
+
96
+ const openDialog = useDialog();
97
+ const handle = openDialog({ title: t("album.rename.title"), closeLabel: t("common.close"), children: body });
98
+ await handle.closed; // portal 제거·초점 복구 뒤. 다음 오버레이는 여기서 연다
99
+ ```
100
+
101
+ ## 축과 기본값
102
+
103
+ | prop | 값 | 기본값 | 설명 |
104
+ | --- | --- | --- | --- |
105
+ | `size` | `small` · `medium` · `large` | `medium` | Native recipe 최대 폭 320 · 420 · 640. Web 최대 폭 28rem · 36rem · 48rem(아래 배치 표) |
106
+ | `dismissible` | `true` · `false` | `true` | `false`면 닫기 버튼과 바깥·Escape/back 닫기가 사라진다 |
107
+ | `busy` | `true` · `false` | `false` | 비동기 작업 중에는 `open`을 유지하고 `busy`로 반복 행동과 닫기를 막는다 |
108
+ | `closeLabel` | 현지화 문자열 | — | 양쪽 필수. 닫기 버튼의 접근성 이름 |
109
+ | `open`·`defaultOpen` | `boolean` | `false` | 제어형은 `open`+`onOpenChange`. Web 비제어형은 `trigger`가 필수다 |
110
+ | `onOpenChange` | `(open: boolean, detail: { reason }) => void` | — | Web `reason`: `"trigger"`·`"close-action"`·`"escape"`·`"outside"`. Native: `"close-action"`·`"back"`·`"outside"`. `busy`이거나 `dismissible={false}`면 닫기 요청을 보내지 않는다 |
111
+ | `onDismissComplete`(Web) | `(detail: { reason: "close-action" \| "escape" \| "outside" \| "programmatic" }) => void` | — | 한 번 열릴 때마다 한 번, portal 제거·초점 복구 뒤. 다음 오버레이는 여기서 연다 |
112
+ | `trigger`(Web) | `ReactElement`(버튼 요소) | — | 누르면 `reason: "trigger"`로 열린다 |
113
+ | `primaryAction`·`secondaryAction`(Native) | `{ label, onPress: () => void \| Promise<void>, tone?, disabled?, accessibilityHint? }` | — | 동기 함수는 같은 누름에서 닫힌다. Promise를 반환하면 기다리는 동안 누른 버튼이 loading이 되고 행동·닫기·back·바깥 닫기가 모두 막히며, 성공하면 `close-action`으로 닫힌다. 던지거나 거절되면 열린 채 행동이 다시 켜지고 `onActionError`를 호출한다. 외부 `busy`도 반복 행동·닫기를 막는다. 미게시(1.12.1 이후) — 1.12.1은 Promise를 무시하고 바로 닫았다 |
114
+ | `onActionError`(Native) | `(error: unknown) => void` | — | action 실패를 본문 오류·재시도로 연결한다. 오류를 처리하는 동안 초안을 보존한다. 없으면 개발 빌드에서 실패마다 `console.error`를 남긴다(중복 제거 없음). 미게시(1.12.1 이후) |
115
+ | `children`(Native) | `ReactNode` | — | 고정된 제목 줄 아래 스크롤 본문(`ScrollView`) 안에 들어간다. `FlatList`·`SectionList`를 넣지 않는다 |
116
+ | `contentStyle`(Native) | 배치 key만(`HjmCompositionStyleProp`) | — | 대화상자 상자의 배치. 높이는 `size`로 |
117
+ | `useDialog()`(Web) | `(request) => { closed: Promise<void>, close(): void }` | — | request는 `open`·`defaultOpen`·`onOpenChange`·`trigger`를 뺀 DialogProps. `OverlayStackProvider` 안에서만 |
118
+
119
+ - Dialog는 `layoutStyle`을 받지 않는다(Web 제외 15개 중 하나 — 포털로 가운데 그려져 배치할 흐름 안 루트가 없다).
120
+
121
+ ## 배치
122
+
123
+ | 항목 | 값 | 근거 |
124
+ | --- | --- | --- |
125
+ | 크기 | 폭(렌더 값): Native는 recipe 최대 폭 `small` 320 · `medium` 420 · `large` 640. Web은 `small` 28rem · `medium` 36rem · `large` 48rem이며 뷰포트 폭 − 32를 넘지 않는다. 닫기 버튼 44×44(`control.minTouchTarget`) | `src/component-recipes.ts`(`dialogRecipe`), `packages/react/src/styles.css` `.hjm-dialog*` |
126
+ | 간격 | 화면 가장자리와 최소 `spacing.md` 16. Web: 머리 위·좌우 `spacing.lg` 20, 본문 사방 `spacing.lg` 20, footer 좌우·아래 `spacing.lg` 20, footer 버튼 간격 `spacing.sm` 12. Native: 안쪽 `small` `spacing.lg` 20 · `medium`/`large` `spacing.xl` 24, 영역 사이 `spacing.md` 16, 제목–설명 `spacing.xs` 8, 머리–닫기 `spacing.sm` 12, 버튼 간격 `spacing.sm` 12 | `packages/react/src/styles.css` `.hjm-overlay`, `packages/react-native/src/overlays.tsx`(`Dialog`, `OverlayActions`) |
127
+ | 순서·정렬 | 화면 가운데에 뜨고 뒤는 scrim이 덮는다. 위에서 머리(제목·설명 \| 닫기) → 본문 → 행동 영역. 닫기 버튼은 머리 끝. 행동 순서는 **보조 → 주**. Web footer는 끝 정렬. Native는 두 버튼을 같은 폭으로 나란히 둔다. 버튼은 둘까지 둔다(Native `primaryAction`·`secondaryAction`). 파괴 행동은 `tone: "danger"`이지만 확인만 받는다면 [AlertDialog](alert-dialog.md)다 | 같은 파일 |
128
+ | 고정·스크롤 | Web은 대화상자 전체 높이가 뷰포트 − 32이고 넘치면 대화상자 안에서 스크롤한다. Native는 safe area 안의 가용 높이를 제한하고 머리 줄(제목·닫기)을 스크롤 밖에 고정한다. 설명과 본문은 함께 내부 ScrollView(`DialogScrollBody`)에서 전체 폭으로 스크롤하고, 행동도 스크롤 밖에 유지한다. 그래서 긴 본문을 스크롤해도 닫기에 닿는다. 목록 탐색처럼 긴 작업은 [Sheet](sheet.md)를 선택한다 | `packages/react-native/src/overlays.tsx` `Dialog`·`DialogScrollBody` |
129
+ | 좁은 폭·큰 글자 | Web footer는 넘치면 줄바꿈한다. Native는 `textScale ≥ 1.6`이거나 창 폭 < 480이면 버튼을 세로로 쌓고, 이때 보조가 위·주 행동이 아래다 | `packages/react-native/src/overlays.tsx` |
130
+
131
+ ```text
132
+ ┌ scrim ──────────────────────────────────────┐
133
+ │ ← spacing.md 16 → │
134
+ │ ┌ Dialog (medium) ───────────────────┐ │
135
+ │ │ 앨범 이름 바꾸기 [×] │ ← 머리, 닫기 44(Native는 고정)
136
+ │ │ 설명 문구 │ ┐
137
+ │ │ ┌ 본문(제품): TextField ─────────┐ │ │ Native 스크롤 영역
138
+ │ │ └────────────────────────────────┘ │ ┘
139
+ │ │ [취소] [저장] │ ← 보조 → 주, 끝 정렬(Web), 스크롤 밖 고정
140
+ │ └────────────────────────────────────┘ │
141
+ └─────────────────────────────────────────────┘
142
+ Native, 큰 글자 또는 폭 < 480: [ 취소 ]
143
+ [ 저장 ] ← 주 행동이 아래
144
+ ```
145
+
146
+ ## 꼭 지킬 것
147
+
148
+ - Native `primaryAction`·`secondaryAction`은 Promise 완료 후 닫힌다. 처리 중 중복 실행·닫기를 막고, 거부되면 그대로 유지한다. `onActionError(error)`에서 제품이 지역화 오류와 재시도를 표시한다. 단순 호출을 성공으로 간주하면 초안이 유실되므로 완료를 기다린다(2026-10-06 지침 대조에서 발견).
149
+
150
+ - 제목·설명·버튼·`closeLabel`은 i18n 키로 넣는다. 긴 문구는 자르지 않는다(계약이 줄바꿈·스크롤을 보장).
151
+ - 다음 오버레이는 닫힘 완료 뒤에 연다. Web은 `onDismissComplete` 또는 `handle.closed`를 쓰고 0ms 타이머로 추측하지 않는다.
152
+ `OverlayStackProvider`는 한 번에 하나만 연다.
153
+ - Native에서 `title`에 요소를 넘기면 `accessibilityTitle`이 필수다.
154
+ - Native `children`에 `FlatList`·`SectionList`를 넣지 않는다. 본문이 이미 ScrollView 안이라 가상화가 풀리고 경고가 난다.
155
+ 긴 목록은 [Sheet](sheet.md)나 `map`으로 그린 짧은 목록을 쓴다.
156
+ - Native에서 저장처럼 실패할 수 있는 action은 `onActionError`를 넘긴다. 단발성 작업에 Promise를 반환하면 완료까지 닫히지 않으므로, 즉시 닫으려면 아무것도 반환하지 않는다.
157
+ - 배치 변경은 Native `contentStyle`(layout 전용)만 쓴다. 높이는 `size`로 정한다. Web은 `className`만 받으며 시각 override에 쓰지 않는다.
158
+
159
+ ## 플랫폼 차이
160
+
161
+ | 항목 | Web | Native |
162
+ | --- | --- | --- |
163
+ | 행동 영역 | `footer`(ReactNode) | `primaryAction`·`secondaryAction`(`OverlayAction`) |
164
+ | 트리거 | `trigger` 요소 | 없음(`open` 제어 또는 `defaultOpen`) |
165
+ | `title`·`description` | ReactNode | `title`은 string 또는 요소+`accessibilityTitle`, `description`은 string. 설명은 닫기 옆이 아니라 본문과 같이 스크롤한다 |
166
+ | 닫힘 완료 신호 | `onDismissComplete` | 없음 |
167
+ | 초점 | `initialFocusRef`·`returnFocusRef` | `returnFocusRef` |
168
+ | 모달 층·마운트 | `modalPriority`, `portalContainer` | 없음. RN `Modal` prop(`onShow` 등)을 그대로 받음 |
169
+ | 명령형 API | `OverlayStackProvider` | 없음 |
170
+
171
+ ## 함정
172
+
173
+ - Native action은 반환된 Promise가 성공한 뒤 닫기를 요청한다. 거절되면 열린 상태를 유지하며 `onActionError`에서 제품 오류 문구를 연결한다. 처리 중 중복 누름과 이전 요청의 늦은 닫힘을 차단한다.
174
+ - Native `DialogProps`는 RN `Modal` props(`style` 포함)를 그대로 받는다. 배치는 `contentStyle`로만 준다.
175
+ - Native는 제어형과 비제어형을 렌더 중에 바꾸면 예외가 난다.
176
+ - Native 기본 렌더러 예제는 실제 저장 서버가 없는 동기 완료 예시다. 제품에서는 저장 Promise를 반환하며, 예제의 영문 고정 문구는 제품 i18n 키로 치환한다. 닫기는 action이 요청하는 `close-action`이 맡는다.
@@ -0,0 +1,89 @@
1
+ # Divider
2
+
3
+ - 단계: 컴포넌트
4
+ - 상태: 배포
5
+ - 지원: Web · Native
6
+ - 적용: 1.12.1
7
+ - 검토일: 2026-10-06
8
+ - 근거: recipe `dividerRecipe`(`src/component-recipes.ts`)
9
+ - 스토리북: `배포/컴포넌트/레이아웃/구분선`
10
+
11
+ ## 언제 쓰나
12
+
13
+ 서로 다른 내용 묶음 사이에 얇은 구분선이 필요할 때 쓴다. 가로선은 블록 사이, 세로선은
14
+ 한 줄 안의 항목 사이(예: 툴바의 무리 구분)에 쓴다. 색은 `border` semantic 역할이다.
15
+
16
+ ## 쓰지 않을 때
17
+
18
+ | 상황 | 대신 쓸 것 |
19
+ | --- | --- |
20
+ | 목록 행 사이 구분선 | [List](list.md)의 `separator`(행마다 Divider를 끼우지 않는다) |
21
+ | 제목이 있는 내용 묶음 | [Section](section.md) |
22
+ | 단순 간격 | [Stack](stack.md)의 `gap` |
23
+ | 카드처럼 테두리로 감싼 묶음 | [Card](card.md), [Surface](surface.md) |
24
+
25
+ ## 공개 이름과 import
26
+
27
+ | 이름 | 역할 | Web | Native |
28
+ | --- | --- | --- | --- |
29
+ | `Divider` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
30
+
31
+ ## 최소 사용 예
32
+
33
+ ```tsx
34
+ // Web
35
+ import { Divider } from "@hjmds/react/display";
36
+
37
+ <Divider />
38
+ <Divider orientation="vertical" decorative />
39
+ ```
40
+
41
+ ```tsx
42
+ // Native
43
+ import { spacing } from "@hjmds/design-contracts/foundations";
44
+ import { Divider } from "@hjmds/react-native/data-display";
45
+
46
+ <Divider />
47
+ <Divider orientation="vertical" inset={spacing.xs} />
48
+ ```
49
+
50
+ ## 축과 기본값
51
+
52
+ | prop | 값 | 기본값 | 설명 |
53
+ | --- | --- | --- | --- |
54
+ | `orientation` | `horizontal` · `vertical` | `horizontal` | 가로선·세로선 |
55
+ | `inset` | Web `none` · `start` · `both` / Native 숫자(pt, 토큰 값으로 넘긴다) | Web `none`(0) / Native 0 | Web `start`·`both`는 `spacing.md`. Native는 가로선이면 좌우, 세로선이면 위아래 여백이 된다 |
56
+ | Web `decorative` | `boolean` | `false` | `true`면 `role="presentation"`·`aria-hidden` |
57
+ | `layoutStyle` | 배치 전용 style | — | 루트 배치. Native `style`은 deprecated |
58
+
59
+ 콜백 prop은 없다.
60
+
61
+ ## 배치
62
+
63
+ | 항목 | 값 | 근거 |
64
+ | --- | --- | --- |
65
+ | 크기 | 두께 1(`stroke.subtle`), 색 `border.default`. 가로선은 부모 폭을 채우고 세로선은 부모 높이로 늘어난다. Web 세로선은 최소 높이 44(`control.minTouchTarget`)를 가진다 | `component-recipes.ts` `dividerRecipe`, `styles.css` `.hjm-divider` |
66
+ | 간격 | Web `inset="start"`는 시작 쪽만, `both`는 양쪽을 `spacing.md`(16)만큼 비운다. 위아래 간격은 Divider가 아니라 감싸는 [Stack](stack.md)의 `gap`으로 준다. Divider 자체 margin은 0이다 | `styles.css` `.hjm-divider`, `react-native/src/data-display.tsx` |
67
+ | 순서·정렬 | 목록 아이콘 열과 맞출 때 `start`를 쓴다. 툴바의 세로선은 무리 사이에 하나만 둔다. 양 끝에는 두지 않는다 | `styles.css` `.hjm-divider` |
68
+ | 고정·스크롤 | — | — |
69
+ | 좁은 폭·큰 글자 | — | — |
70
+
71
+ ## 꼭 지킬 것
72
+
73
+ - 색·두께를 `style`/`className`으로 바꾸지 않는다. 구분선 색은 제품 테마의 `border` key로 바뀐다.
74
+ 배치는 `layoutStyle`로 한다. Native `style`은 deprecated — `layoutStyle` 또는 `orientation`·`inset`으로 옮긴다.
75
+ - Web에서 의미가 없는 장식선은 `decorative`로 표시한다(`role="presentation"`, `aria-hidden`).
76
+ 기본은 `role="separator"`다.
77
+
78
+ ## 플랫폼 차이
79
+
80
+ | 항목 | Web | Native |
81
+ | --- | --- | --- |
82
+ | `inset` 타입 | `"none" \| "start" \| "both"` | `number`(0 이상) |
83
+ | 접근성 | `separator`(기본) 또는 `decorative` | 항상 `accessible={false}`, `decorative` prop 없음 |
84
+ | 요소 | 가로 `hr`, 세로 `div` | `View` |
85
+
86
+ ## 함정
87
+
88
+ - Native `inset`에 음수·NaN을 넘기면 `RangeError`가 난다.
89
+ - Native Divider는 테마를 읽으므로 `HjmNativeProvider` 밖에서 렌더하면 예외가 난다.