@hjmds/design-contracts 0.8.2

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 (353) hide show
  1. package/README.md +208 -0
  2. package/dist/alert-dialog.d.ts +102 -0
  3. package/dist/alert-dialog.d.ts.map +1 -0
  4. package/dist/alert-dialog.js +136 -0
  5. package/dist/alert-dialog.js.map +1 -0
  6. package/dist/base-recipes.d.ts +184 -0
  7. package/dist/base-recipes.d.ts.map +1 -0
  8. package/dist/base-recipes.js +129 -0
  9. package/dist/base-recipes.js.map +1 -0
  10. package/dist/behaviors.d.ts +1254 -0
  11. package/dist/behaviors.d.ts.map +1 -0
  12. package/dist/behaviors.js +972 -0
  13. package/dist/behaviors.js.map +1 -0
  14. package/dist/bottom-navigation-defaults.d.ts +13 -0
  15. package/dist/bottom-navigation-defaults.d.ts.map +1 -0
  16. package/dist/bottom-navigation-defaults.js +10 -0
  17. package/dist/bottom-navigation-defaults.js.map +1 -0
  18. package/dist/bottom-navigation.d.ts +114 -0
  19. package/dist/bottom-navigation.d.ts.map +1 -0
  20. package/dist/bottom-navigation.js +223 -0
  21. package/dist/bottom-navigation.js.map +1 -0
  22. package/dist/breadcrumb.d.ts +97 -0
  23. package/dist/breadcrumb.d.ts.map +1 -0
  24. package/dist/breadcrumb.js +99 -0
  25. package/dist/breadcrumb.js.map +1 -0
  26. package/dist/calendar.d.ts +285 -0
  27. package/dist/calendar.d.ts.map +1 -0
  28. package/dist/calendar.js +297 -0
  29. package/dist/calendar.js.map +1 -0
  30. package/dist/card.d.ts +39 -0
  31. package/dist/card.d.ts.map +1 -0
  32. package/dist/card.js +50 -0
  33. package/dist/card.js.map +1 -0
  34. package/dist/carousel.d.ts +180 -0
  35. package/dist/carousel.d.ts.map +1 -0
  36. package/dist/carousel.js +172 -0
  37. package/dist/carousel.js.map +1 -0
  38. package/dist/catalog.d.ts +7158 -0
  39. package/dist/catalog.d.ts.map +1 -0
  40. package/dist/catalog.js +220 -0
  41. package/dist/catalog.js.map +1 -0
  42. package/dist/collection.d.ts +123 -0
  43. package/dist/collection.d.ts.map +1 -0
  44. package/dist/collection.js +211 -0
  45. package/dist/collection.js.map +1 -0
  46. package/dist/color-references.d.ts +46 -0
  47. package/dist/color-references.d.ts.map +1 -0
  48. package/dist/color-references.js +39 -0
  49. package/dist/color-references.js.map +1 -0
  50. package/dist/colors.d.ts +56 -0
  51. package/dist/colors.d.ts.map +1 -0
  52. package/dist/colors.js +90 -0
  53. package/dist/colors.js.map +1 -0
  54. package/dist/command-palette.d.ts +240 -0
  55. package/dist/command-palette.d.ts.map +1 -0
  56. package/dist/command-palette.js +97 -0
  57. package/dist/command-palette.js.map +1 -0
  58. package/dist/component-contracts.d.ts +137 -0
  59. package/dist/component-contracts.d.ts.map +1 -0
  60. package/dist/component-contracts.js +53 -0
  61. package/dist/component-contracts.js.map +1 -0
  62. package/dist/component-definitions.d.ts +135 -0
  63. package/dist/component-definitions.d.ts.map +1 -0
  64. package/dist/component-definitions.js +142 -0
  65. package/dist/component-definitions.js.map +1 -0
  66. package/dist/component-recipes.d.ts +3583 -0
  67. package/dist/component-recipes.d.ts.map +1 -0
  68. package/dist/component-recipes.js +1503 -0
  69. package/dist/component-recipes.js.map +1 -0
  70. package/dist/component-references.d.ts +416 -0
  71. package/dist/component-references.d.ts.map +1 -0
  72. package/dist/component-references.js +140 -0
  73. package/dist/component-references.js.map +1 -0
  74. package/dist/content-state.d.ts +104 -0
  75. package/dist/content-state.d.ts.map +1 -0
  76. package/dist/content-state.js +116 -0
  77. package/dist/content-state.js.map +1 -0
  78. package/dist/counter-badge-recipe.d.ts +80 -0
  79. package/dist/counter-badge-recipe.d.ts.map +1 -0
  80. package/dist/counter-badge-recipe.js +44 -0
  81. package/dist/counter-badge-recipe.js.map +1 -0
  82. package/dist/counter-badge.d.ts +12 -0
  83. package/dist/counter-badge.d.ts.map +1 -0
  84. package/dist/counter-badge.js +21 -0
  85. package/dist/counter-badge.js.map +1 -0
  86. package/dist/data-table.d.ts +203 -0
  87. package/dist/data-table.d.ts.map +1 -0
  88. package/dist/data-table.js +182 -0
  89. package/dist/data-table.js.map +1 -0
  90. package/dist/date-picker.d.ts +268 -0
  91. package/dist/date-picker.d.ts.map +1 -0
  92. package/dist/date-picker.js +168 -0
  93. package/dist/date-picker.js.map +1 -0
  94. package/dist/description-list.d.ts +68 -0
  95. package/dist/description-list.d.ts.map +1 -0
  96. package/dist/description-list.js +85 -0
  97. package/dist/description-list.js.map +1 -0
  98. package/dist/design-system-provider.d.ts +80 -0
  99. package/dist/design-system-provider.d.ts.map +1 -0
  100. package/dist/design-system-provider.js +147 -0
  101. package/dist/design-system-provider.js.map +1 -0
  102. package/dist/evidence.d.ts +49 -0
  103. package/dist/evidence.d.ts.map +1 -0
  104. package/dist/evidence.js +133 -0
  105. package/dist/evidence.js.map +1 -0
  106. package/dist/file-picker.d.ts +183 -0
  107. package/dist/file-picker.d.ts.map +1 -0
  108. package/dist/file-picker.js +224 -0
  109. package/dist/file-picker.js.map +1 -0
  110. package/dist/floating-action-button.d.ts +143 -0
  111. package/dist/floating-action-button.d.ts.map +1 -0
  112. package/dist/floating-action-button.js +149 -0
  113. package/dist/floating-action-button.js.map +1 -0
  114. package/dist/form.d.ts +143 -0
  115. package/dist/form.d.ts.map +1 -0
  116. package/dist/form.js +206 -0
  117. package/dist/form.js.map +1 -0
  118. package/dist/foundations.d.ts +300 -0
  119. package/dist/foundations.d.ts.map +1 -0
  120. package/dist/foundations.js +238 -0
  121. package/dist/foundations.js.map +1 -0
  122. package/dist/grid.d.ts +75 -0
  123. package/dist/grid.d.ts.map +1 -0
  124. package/dist/grid.js +133 -0
  125. package/dist/grid.js.map +1 -0
  126. package/dist/icon-button-recipe.d.ts +99 -0
  127. package/dist/icon-button-recipe.d.ts.map +1 -0
  128. package/dist/icon-button-recipe.js +53 -0
  129. package/dist/icon-button-recipe.js.map +1 -0
  130. package/dist/icon.d.ts +41 -0
  131. package/dist/icon.d.ts.map +1 -0
  132. package/dist/icon.js +147 -0
  133. package/dist/icon.js.map +1 -0
  134. package/dist/image.d.ts +82 -0
  135. package/dist/image.d.ts.map +1 -0
  136. package/dist/image.js +100 -0
  137. package/dist/image.js.map +1 -0
  138. package/dist/index.d.ts +58 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +66 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/layout.d.ts +118 -0
  143. package/dist/layout.d.ts.map +1 -0
  144. package/dist/layout.js +118 -0
  145. package/dist/layout.js.map +1 -0
  146. package/dist/link.d.ts +57 -0
  147. package/dist/link.d.ts.map +1 -0
  148. package/dist/link.js +142 -0
  149. package/dist/link.js.map +1 -0
  150. package/dist/load-more.d.ts +64 -0
  151. package/dist/load-more.d.ts.map +1 -0
  152. package/dist/load-more.js +124 -0
  153. package/dist/load-more.js.map +1 -0
  154. package/dist/mentions.d.ts +67 -0
  155. package/dist/mentions.d.ts.map +1 -0
  156. package/dist/mentions.js +107 -0
  157. package/dist/mentions.js.map +1 -0
  158. package/dist/number-field.d.ts +208 -0
  159. package/dist/number-field.d.ts.map +1 -0
  160. package/dist/number-field.js +247 -0
  161. package/dist/number-field.js.map +1 -0
  162. package/dist/otp-field.d.ts +152 -0
  163. package/dist/otp-field.d.ts.map +1 -0
  164. package/dist/otp-field.js +117 -0
  165. package/dist/otp-field.js.map +1 -0
  166. package/dist/pagination.d.ts +181 -0
  167. package/dist/pagination.d.ts.map +1 -0
  168. package/dist/pagination.js +226 -0
  169. package/dist/pagination.js.map +1 -0
  170. package/dist/password-field.d.ts +187 -0
  171. package/dist/password-field.d.ts.map +1 -0
  172. package/dist/password-field.js +120 -0
  173. package/dist/password-field.js.map +1 -0
  174. package/dist/popover.d.ts +152 -0
  175. package/dist/popover.d.ts.map +1 -0
  176. package/dist/popover.js +137 -0
  177. package/dist/popover.js.map +1 -0
  178. package/dist/progress-recipe.d.ts +43 -0
  179. package/dist/progress-recipe.d.ts.map +1 -0
  180. package/dist/progress-recipe.js +16 -0
  181. package/dist/progress-recipe.js.map +1 -0
  182. package/dist/recipes.d.ts +31 -0
  183. package/dist/recipes.d.ts.map +1 -0
  184. package/dist/recipes.js +46 -0
  185. package/dist/recipes.js.map +1 -0
  186. package/dist/responsive.d.ts +27 -0
  187. package/dist/responsive.d.ts.map +1 -0
  188. package/dist/responsive.js +66 -0
  189. package/dist/responsive.js.map +1 -0
  190. package/dist/result.d.ts +111 -0
  191. package/dist/result.d.ts.map +1 -0
  192. package/dist/result.js +97 -0
  193. package/dist/result.js.map +1 -0
  194. package/dist/selection-helpers.d.ts +16 -0
  195. package/dist/selection-helpers.d.ts.map +1 -0
  196. package/dist/selection-helpers.js +52 -0
  197. package/dist/selection-helpers.js.map +1 -0
  198. package/dist/semantic-colors.d.ts +275 -0
  199. package/dist/semantic-colors.d.ts.map +1 -0
  200. package/dist/semantic-colors.js +84 -0
  201. package/dist/semantic-colors.js.map +1 -0
  202. package/dist/sheet.d.ts +51 -0
  203. package/dist/sheet.d.ts.map +1 -0
  204. package/dist/sheet.js +69 -0
  205. package/dist/sheet.js.map +1 -0
  206. package/dist/showcase.d.ts +153 -0
  207. package/dist/showcase.d.ts.map +1 -0
  208. package/dist/showcase.js +210 -0
  209. package/dist/showcase.js.map +1 -0
  210. package/dist/side-panel.d.ts +199 -0
  211. package/dist/side-panel.d.ts.map +1 -0
  212. package/dist/side-panel.js +111 -0
  213. package/dist/side-panel.js.map +1 -0
  214. package/dist/slider.d.ts +138 -0
  215. package/dist/slider.d.ts.map +1 -0
  216. package/dist/slider.js +150 -0
  217. package/dist/slider.js.map +1 -0
  218. package/dist/splitter.d.ts +113 -0
  219. package/dist/splitter.d.ts.map +1 -0
  220. package/dist/splitter.js +99 -0
  221. package/dist/splitter.js.map +1 -0
  222. package/dist/statistic.d.ts +41 -0
  223. package/dist/statistic.d.ts.map +1 -0
  224. package/dist/statistic.js +76 -0
  225. package/dist/statistic.js.map +1 -0
  226. package/dist/steps.d.ts +196 -0
  227. package/dist/steps.d.ts.map +1 -0
  228. package/dist/steps.js +160 -0
  229. package/dist/steps.js.map +1 -0
  230. package/dist/tag.d.ts +128 -0
  231. package/dist/tag.d.ts.map +1 -0
  232. package/dist/tag.js +93 -0
  233. package/dist/tag.js.map +1 -0
  234. package/dist/timeline.d.ts +147 -0
  235. package/dist/timeline.d.ts.map +1 -0
  236. package/dist/timeline.js +127 -0
  237. package/dist/timeline.js.map +1 -0
  238. package/dist/toast.d.ts +164 -0
  239. package/dist/toast.d.ts.map +1 -0
  240. package/dist/toast.js +529 -0
  241. package/dist/toast.js.map +1 -0
  242. package/dist/tokens.d.ts +9 -0
  243. package/dist/tokens.d.ts.map +1 -0
  244. package/dist/tokens.js +9 -0
  245. package/dist/tokens.js.map +1 -0
  246. package/dist/tooltip.d.ts +44 -0
  247. package/dist/tooltip.d.ts.map +1 -0
  248. package/dist/tooltip.js +88 -0
  249. package/dist/tooltip.js.map +1 -0
  250. package/dist/tour.d.ts +218 -0
  251. package/dist/tour.d.ts.map +1 -0
  252. package/dist/tour.js +211 -0
  253. package/dist/tour.js.map +1 -0
  254. package/dist/transfer-list.d.ts +207 -0
  255. package/dist/transfer-list.d.ts.map +1 -0
  256. package/dist/transfer-list.js +193 -0
  257. package/dist/transfer-list.js.map +1 -0
  258. package/dist/tree-select.d.ts +78 -0
  259. package/dist/tree-select.d.ts.map +1 -0
  260. package/dist/tree-select.js +132 -0
  261. package/dist/tree-select.js.map +1 -0
  262. package/dist/tree.d.ts +206 -0
  263. package/dist/tree.d.ts.map +1 -0
  264. package/dist/tree.js +223 -0
  265. package/dist/tree.js.map +1 -0
  266. package/dist/upload-item.d.ts +173 -0
  267. package/dist/upload-item.d.ts.map +1 -0
  268. package/dist/upload-item.js +156 -0
  269. package/dist/upload-item.js.map +1 -0
  270. package/dist/version.d.ts +3 -0
  271. package/dist/version.d.ts.map +1 -0
  272. package/dist/version.js +3 -0
  273. package/dist/version.js.map +1 -0
  274. package/docs/affix.md +63 -0
  275. package/docs/anchor.md +59 -0
  276. package/docs/ant-design-coverage.md +119 -0
  277. package/docs/app-provider.md +50 -0
  278. package/docs/app-rn-adoption.md +227 -0
  279. package/docs/architecture.md +320 -0
  280. package/docs/authoring-brief.md +87 -0
  281. package/docs/border-beam.md +66 -0
  282. package/docs/bottom-navigation.md +127 -0
  283. package/docs/breadcrumb.md +82 -0
  284. package/docs/calendar.md +154 -0
  285. package/docs/carousel.md +130 -0
  286. package/docs/cascader.md +93 -0
  287. package/docs/catalog-decision-status.md +306 -0
  288. package/docs/color-picker.md +72 -0
  289. package/docs/command-palette.md +116 -0
  290. package/docs/confirm-popover.md +93 -0
  291. package/docs/consistency-audit.md +383 -0
  292. package/docs/consumer-release-gate.md +90 -0
  293. package/docs/content-state.md +156 -0
  294. package/docs/context-panel.md +88 -0
  295. package/docs/cross-platform-core-normalization.md +118 -0
  296. package/docs/data-table.md +79 -0
  297. package/docs/date-picker.md +84 -0
  298. package/docs/description-list.md +75 -0
  299. package/docs/design-system-provider.md +139 -0
  300. package/docs/dropdown.md +78 -0
  301. package/docs/expansion-roadmap.md +285 -0
  302. package/docs/file-picker.md +59 -0
  303. package/docs/floating-action-button.md +111 -0
  304. package/docs/form.md +134 -0
  305. package/docs/generated/component-maturity.md +103 -0
  306. package/docs/generated/renderer-evidence.json +5597 -0
  307. package/docs/generated/renderer-evidence.md +132 -0
  308. package/docs/generated/showcase-manifest.json +3732 -0
  309. package/docs/icon.md +22 -0
  310. package/docs/identity.md +121 -0
  311. package/docs/image.md +84 -0
  312. package/docs/implementation-0.5.md +85 -0
  313. package/docs/layout-primitives.md +107 -0
  314. package/docs/layout.md +83 -0
  315. package/docs/library-reference-decisions.md +123 -0
  316. package/docs/link.md +67 -0
  317. package/docs/load-more.md +31 -0
  318. package/docs/mentions.md +82 -0
  319. package/docs/migration-0.2.md +108 -0
  320. package/docs/migration-0.3.md +72 -0
  321. package/docs/migration-0.5.md +82 -0
  322. package/docs/migration-0.6.md +197 -0
  323. package/docs/notification.md +54 -0
  324. package/docs/number-field.md +82 -0
  325. package/docs/otp-field.md +103 -0
  326. package/docs/pagination.md +139 -0
  327. package/docs/password-field.md +98 -0
  328. package/docs/popover.md +124 -0
  329. package/docs/promotion-candidates.md +169 -0
  330. package/docs/qr-code.md +69 -0
  331. package/docs/rating.md +58 -0
  332. package/docs/responsive-grid.md +90 -0
  333. package/docs/result.md +96 -0
  334. package/docs/showcase.md +97 -0
  335. package/docs/side-panel.md +71 -0
  336. package/docs/slider.md +79 -0
  337. package/docs/splitter.md +55 -0
  338. package/docs/statistic.md +16 -0
  339. package/docs/steps.md +115 -0
  340. package/docs/tag.md +57 -0
  341. package/docs/time-picker.md +88 -0
  342. package/docs/timeline.md +146 -0
  343. package/docs/toast.md +127 -0
  344. package/docs/tooltip.md +58 -0
  345. package/docs/tour.md +136 -0
  346. package/docs/transfer-list.md +94 -0
  347. package/docs/tree-select.md +101 -0
  348. package/docs/tree.md +123 -0
  349. package/docs/upload-item.md +68 -0
  350. package/docs/utility.md +53 -0
  351. package/docs/virtual-list.md +70 -0
  352. package/docs/watermark.md +58 -0
  353. package/package.json +402 -0
package/docs/link.md ADDED
@@ -0,0 +1,67 @@
1
+ # Link contract
2
+
3
+ `Link`는 callback을 실행하는 Button이 아니라 사용자가 복사하거나 새 탭에서 열 수 있는
4
+ **목적지**입니다. HJM은 label, 목적지, semantic icon만 공유하고 Web과 Native가 각 플랫폼의
5
+ 실제 navigation primitive로 번역합니다. BurnTok의 Web/RN 대화 destination 행에서 실제
6
+ navigation·접근성 계약을 검증했으므로 catalog status는 `beta`입니다.
7
+
8
+ ## 목적지
9
+
10
+ - `internal`: `/profile`, `?tab=stats`, `#details`처럼 앱 router가 해석하는 목적지
11
+ - `external`: `https`, `http`, `mailto`, `tel` absolute URL
12
+
13
+ `href`는 항상 필수입니다. `onClick`/`onPress`로 navigation을 대신하거나 optional href를
14
+ Button처럼 사용하는 API는 허용하지 않습니다. 인증 확인, retry, back, replace 같은 command는
15
+ Button과 제품 navigation workflow가 소유합니다.
16
+
17
+ ```ts
18
+ const profileLink = {
19
+ label: "프로필 보기",
20
+ destination: { kind: "internal", href: "/u/jimin" },
21
+ trailingIcon: { name: "chevronEnd" },
22
+ } satisfies LinkDescriptor;
23
+ ```
24
+
25
+ unavailable destination은 disabled Link로 만들지 않고 plain Text로 표시합니다. `visited`는 Web의
26
+ `:visited` pseudo-state이지 공통 application state가 아니며, download는 Web anchor attribute와
27
+ Native 파일 권한·저장·공유가 다른 별도 workflow이므로 공통 Link에 포함하지 않습니다.
28
+
29
+ ## Adaptive renderer
30
+
31
+ ### Web
32
+
33
+ - Next Link 또는 실제 `<a href>`를 사용합니다.
34
+ - Tab/Enter, modifier click, context menu, 주소 복사, 새 탭 열기를 browser에 남깁니다.
35
+ - SPA navigation을 사용하더라도 button으로 바꾸지 않습니다.
36
+ - inline variant는 색이 없어도 링크임을 알 수 있도록 항상 underline을 유지합니다.
37
+
38
+ ### React Native
39
+
40
+ - internal destination은 Expo Router Link 같은 실제 route primitive와 link role을 사용합니다.
41
+ - external destination은 `Linking` 계열 adapter로 열고 실패를 제품에 전달합니다.
42
+ - Pressable callback만으로 내부·외부 목적지를 하나의 button처럼 평준화하지 않습니다.
43
+
44
+ ## Icon과 접근성
45
+
46
+ label 또는 명시적 `accessibilityLabel`이 링크 이름을 소유합니다. 음성 제어에서 보이는 문구로
47
+ 대상을 찾을 수 있도록 별도 접근성 이름도 visible label을 포함해야 합니다. leading/trailing
48
+ icon은 HJM semantic name만 고릅니다. decorative 처리와 size/tone/weight는 `linkRecipe`가,
49
+ `chevronStart`와 `chevronEnd` 같은 논리 방향은 Icon registry가 소유합니다. caller가 raw size,
50
+ color, stroke, fixed direction을 넘겨 이 문법을 바꾸지 못합니다. arbitrary ReactNode와 중첩
51
+ button/link도 열지 않습니다.
52
+
53
+ core descriptor와 destination은 허용된 key만 받으며 renderer의 `className`, `target`, `replace`
54
+ 같은 플랫폼 prop을 섞지 않습니다. resolver는 검증된 label, canonical `{ kind, href }`, semantic
55
+ icon identity만 새 객체로 반환해 입력 객체의 숨은 확장 필드를 플랫폼 사이에 전달하지 않습니다.
56
+
57
+ standalone Link는 최소 44-unit target과 visible focus를 지킵니다. 문장 안 inline Link는 줄 높이를
58
+ 강제로 키우지 않되 underline과 native focus semantics를 유지합니다.
59
+
60
+ ## 첫 제품 검증
61
+
62
+ 첫 paired slice는 BurnTok 메시지 목록의 대화 destination입니다. 고정된 conversation-row
63
+ adapter가 기존 avatar·preview·time·unread 위계를 소유하고, root만 Web의 실제 Next anchor와
64
+ Native의 Expo Router `Link asChild`로 분기했습니다. Web modifier/context navigation,
65
+ Native link role·activation, 한 번만 읽히는 접근성 이름을 검증했으며 callback·임의 children·
66
+ style escape는 열지 않았습니다. rich card 전체를 base Link의 arbitrary children으로 열지 않고
67
+ 이후 linked-row/card adapter에서 같은 destination contract를 조합합니다.
@@ -0,0 +1,31 @@
1
+ # LoadMore contract
2
+
3
+ `LoadMore`는 목록 데이터나 cursor를 소유하지 않습니다. 이미 렌더된 항목을 유지한 채 다음
4
+ 페이지 요청의 footer 상태와 중복 요청 방지만 공통화합니다.
5
+
6
+ ```text
7
+ ready(requestKey) ─ request ─→ loading(requestKey)
8
+ ↑ ├─ success + next key → ready
9
+ ├──── retry ← error ←─────┤
10
+ └──────────── complete ←──┘
11
+ ```
12
+
13
+ - `requestKey`는 cursor나 offset을 제품 adapter가 stable string으로 만든 값입니다.
14
+ - `labels`는 load more/loading/retry/complete 네 상태의 현지화된 visible copy입니다. renderer가
15
+ 자체 문구나 영어 fallback을 만들지 않습니다.
16
+ - `createLoadMoreController`는 한 controller에서 요청 하나만 허용합니다. 같은 sentinel의 반복
17
+ 노출이나 RN `onEndReached` 중복 호출이 query를 두 번 실행하지 못합니다.
18
+ - `automatic` mode는 viewport sentinel을 사용할 수 있지만, keyboard와 screen reader 사용자를
19
+ 위한 manual fallback button을 함께 렌더링합니다. `manual` mode는 viewport 요청을 무시합니다.
20
+ - `ready`는 manual/viewport, `error`는 retry reason만 허용합니다. `loading`과 `complete`는 모든
21
+ 요청을 차단합니다.
22
+ - `onLoadMore`는 실제 query가 끝날 때 settle되는 Promise를 반드시 반환합니다. detached 요청이나
23
+ `void fetchNextPage()`는 gate를 조기에 풀어 같은 cursor가 중복 실행될 수 있어 거부합니다.
24
+ query promise가 성공하거나 실패하면 gate가 풀립니다. 오류 copy와 retry 상태는 제품 query가
25
+ 소유하며, 기존 collection item을 숨기지 않습니다.
26
+ - loading copy는 status로, 오류는 alert로 한 번만 발표합니다. retry/manual target은 44-unit
27
+ 이상이고 focus indicator를 유지합니다.
28
+
29
+ Web `IntersectionObserver`와 RN `onEndReached`는 감지 방식만 다르며 같은 controller와 state를
30
+ 사용합니다. 페이지 번호가 필요한 탐색은 `Pagination`, 사용자 의도 없이 계속 이어지는 긴
31
+ 목록은 `LoadMore`로 분리합니다.
@@ -0,0 +1,82 @@
1
+ # Mentions contract
2
+
3
+ ## 문제
4
+
5
+ 텍스트 입력 중 트리거 문자(`@`, `#`)를 만나면 후보 목록을 띄우고, 하나를 고르면 트리거부터
6
+ 현재 커서까지를 선택한 후보로 바꾸며 뒤에 공백 하나를 남긴다. Ant Design `Mentions`와
7
+ `direct` crosswalk를 따른다.
8
+
9
+ ## 이게 새 컴포넌트인가
10
+
11
+ 먼저 판정할 것: Mentions는 `TextArea` + `Menu`(또는 Combobox의 collection) 조합으로
12
+ 완결되는가, 새 계약이 필요한가.
13
+
14
+ **후보 목록 자체는 완결된다.** 후보를 필터링하고 화살표 키로 훑고 loading/empty/error를
15
+ 알리는 문제는 `behaviorRegistry.combobox`가 이미 소유한 문제와 다르지 않다 —
16
+ `comboboxRecipe`의 popover/listbox anatomy, `AsyncCollectionState`, IME 조합 중 미리
17
+ 필터링/커밋하지 않는다는 `"ime-composition-does-not-prematurely-filter-or-commit"` 시나리오
18
+ 전부 그대로 재사용된다. 이 부분에 대해서는 새 recipe나 새 behaviorRegistry entry를 만들지
19
+ 않는다.
20
+
21
+ **새 계약이 필요한 지점은 하나뿐이다.** 자유 텍스트 문자열 안에서 "지금 활성 트리거가
22
+ 있는가, 그 query는 무엇인가, 커밋 시 어느 범위를 무엇으로 바꾸는가"를 결정하는 문제는
23
+ TextArea에도 Combobox에도 없다 — Combobox는 입력창 전체가 곧 query이지 텍스트 중간의
24
+ 트리거를 찾지 않는다. 이 트리거 탐지·삽입 범위 계산이 `src/mentions.ts`가 담는 전부다.
25
+
26
+ ## 일반화한 계약
27
+
28
+ ### 트리거 탐지는 문자열과 커서 오프셋만 본다
29
+
30
+ `findActiveMentionTrigger(text, cursorPosition, triggers)`는 개별 키 입력 이벤트가 아니라
31
+ 현재 확정된 텍스트 값과 커서 위치만 읽는다. 커서에서 왼쪽으로 스캔하다 공백을 만나면
32
+ 즉시 포기(활성 트리거 없음)하고, 트리거 문자를 만나되 그 앞이 시작 위치나 공백이면
33
+ 활성 매치로 확정한다.
34
+
35
+ - **`"user@example.com"`처럼 트리거 앞이 공백/시작이 아니면 열리지 않는다** — Slack,
36
+ Discord, GitHub 등 모든 멘션 UI가 공유하는 "트리거는 토큰의 시작에서만 유효하다"는
37
+ 관례다. validator를 먼저 이 입력으로 시험한 이유가 이것이다: 순진한 구현은 문자열에
38
+ 트리거 문자가 있다는 것만으로 열어버리기 쉽다.
39
+ - **공백을 타이핑하면 이미 열린 멘션도 닫힌다** — query가 공백을 건너 무한히 늘어나는
40
+ 것을 막는다. 같은 관례.
41
+ - **트리거 바로 다음(빈 query)도 유효한 활성 매치다** — `"@"`만 입력한 순간에도 팝업이
42
+ 열려야 기본 후보 목록을 보여줄 수 있다.
43
+
44
+ ### 커서 오프셋 기반이라 한글 조합에 특별 처리가 필요 없다
45
+
46
+ 이 계약은 텍스트 값이 확정된 뒤에만 동작하므로, 조합 중인 자모(예: "ㅎ")도 그 순간 문자열
47
+ 안의 유효한 코드 포인트일 뿐이다. Combobox가 조합 중 필터링을 유예하는 것과 달리, Mentions는
48
+ 유예할 이유가 없다 — 렌더러가 시각적 안정성을 위해 `compositionend`까지 팝업 리포지션을
49
+ 미루는 것은 허용되지만, 이 계약이 요구하지는 않는다.
50
+
51
+ ### 삽입은 항상 트리거 문자를 정확히 한 번 포함한다
52
+
53
+ `resolveMentionInsertion(text, match, cursorPosition, insertedText)`는 트리거를 호출자가
54
+ 아니라 이 함수가 붙인다 — 잊거나 중복으로 붙이는 실수 자체를 불가능하게 만든다. 교체
55
+ 범위는 항상 `[match.triggerStart, cursorPosition)`이고, 삽입 뒤 공백 하나를 항상 더해
56
+ 사용자가 바로 다음 단어를 이어 칠 수 있게 한다.
57
+
58
+ ### 다중 트리거는 문자와 id가 모두 유일해야 한다
59
+
60
+ `MentionTriggerConfig`는 `@`(사용자 멘션), `#`(해시태그)처럼 여러 트리거를 동시에 지원할
61
+ 수 있게 하되(antd의 `prefix: string | string[]`가 이미 이 요구를 문서화하고 있다), 트리거
62
+ 문자 중복과 id 중복을 모두 거부한다 — 어느 후보 소스로 갈지 제품이 `triggerId`로 분기할
63
+ 수 있어야 하기 때문이다.
64
+
65
+ ## HJM 기본값
66
+
67
+ - 팝업 anatomy·recipe: `comboboxRecipe`를 그대로 재사용. 새 recipe 없음.
68
+ - 팝업 behavior: `behaviorRegistry.combobox`를 그대로 재사용. 새 behaviorRegistry entry
69
+ 없음. `mentionsBehaviorScenarios`는 트리거 탐지·삽입 슬라이스만 추가한다.
70
+ - 삽입 후 공백 하나: 고정 기본값, 옵션으로 빼지 않았다 — 측정된 대안 요구가 없다.
71
+
72
+ ## 플랫폼 번역
73
+
74
+ Web과 Native 모두 텍스트 값 + 커서 오프셋이라는 같은 입력으로 동작하므로 이 계약 자체는
75
+ 플랫폼 중립이다. Web `<textarea>`의 `selectionStart`, RN `TextInput`의 `onSelectionChange`가
76
+ 각각 `cursorPosition`을 공급하고, 렌더러가 팝업을 caret 근처에 앵커링하는 방법(Web
77
+ absolute position 계산 vs RN 측정된 caret rect)만 플랫폼별로 다르다 — 이건 Combobox
78
+ popover가 이미 겪는 것과 같은 종류의 렌더러 문제다.
79
+
80
+ ## 검증 화면
81
+
82
+ 아직 없음. `planned → beta` 승격은 실제 제품 vertical slice 이후 리드가 진행한다.
@@ -0,0 +1,108 @@
1
+ # v0.2 migration
2
+
3
+ `v0.2`는 기존 `THEMES`, foundations, Button/Surface/Field API를 유지하는 additive
4
+ release입니다. 소비 앱은 태그가 발행된 뒤 아래 순서로 전환합니다.
5
+
6
+ 1. 당시 package name인 `@hjm/design-system` Git dependency를 정확한 `#v0.2.0`으로 변경
7
+ 2. lockfile이 같은 HJM commit을 가리키는지 확인
8
+ 3. 제품 어댑터가 신규 foundation, recipe, type을 명시적으로 re-export
9
+ 4. Web token generator가 필요한 신규 foundation을 CSS variable로 변환
10
+ 5. 기존 앱 component renderer를 beta recipe에 연결
11
+ 6. typecheck, contrast test, Web/RN fixture와 실제 화면을 검증
12
+
13
+ ## 신규 foundation
14
+
15
+ - `easing`, `spring`, `motionPreset`와 Reduce Motion fallback
16
+ - `opacity`, `stateLayer`, `stroke`
17
+ - `layout`, `breakpoint`, `layer`
18
+ - `shadow.floating`, `shadow.overlay`, renderer-neutral `backdrop`
19
+ - `semanticColors`
20
+
21
+ ## 신규 공통 계약
22
+
23
+ - semantic color reference: `themeColor`, `accentColor`, `resolveColorReference`
24
+ - content/action: `textRecipe`, `iconButtonRecipe`, `chipRecipe`
25
+ - semantic icon/destination: `IconDescriptor`, `LinkDescriptor`, `LinkDestination`
26
+ - field affordance: accessible field label/hint colors, `searchFieldRecipe`
27
+ - selection/navigation: `selectionGroupRecipe`, `selectionControlRecipe`, typed Checkbox/RadioGroup
28
+ selection helpers, `segmentedControlRecipe`, `switchRecipe`, `tabsRecipe`
29
+ - data display: `badgeRecipe`, `counterBadgeRecipe`, `avatarRecipe`, `dividerRecipe`, `listRecipe`, `listRowRecipe`, `sectionRecipe`,
30
+ `StatisticDescriptor`, `resolveStatisticDescriptor`
31
+ - feedback: `noticeRecipe`, `emptyStateRecipe`, `skeletonRecipe`, `spinnerRecipe`, `progressRecipe`
32
+ - adaptive overlay: `dialogRecipe`, `alertDialogRecipe`, `sheetRecipe`, `toastRecipe`,
33
+ `createAlertDialogSession`, `SheetOpenState`, `SheetDismissPolicy`, `canDismissSheet`,
34
+ `createSheetLifecycle`, `ToastDescriptor`, `createToastSession`, `createToastStore`
35
+ - navigation feedback: `BottomNavigationDescriptor`, `LoadMoreState`, `createLoadMoreController`
36
+ - Web overlay planning: `TooltipDescriptor`, `TooltipOpenState`
37
+ - native layout: `topBarRecipe`, `bottomCtaRecipe`
38
+ - scope/maturity registry: `componentCatalog`, typed `recipeRegistry`
39
+ - renderer acceptance: `behaviorRegistry`, state-axis types, collection descriptors and selection models
40
+ - future collection controls: `validateCollection`, `flattenCollectionItems`,
41
+ `resolveCollectionItem`, `getCollectionNavigationTarget`,
42
+ `getCollectionTypeaheadMatch`, `reconcileSelectSelection`, `SelectOpenState`
43
+ - reusable visual fragments: focus indicator, field frame, floating surface, collection item
44
+ - planned/beta expansion recipes: Icon, Stack, Link, CheckboxGroup, RadioGroup, Accordion, Menu,
45
+ AlertDialog, Tooltip, Statistic, LoadMore, BottomNavigation
46
+
47
+ ## 호환성 메모
48
+
49
+ - `buttonRecipe`와 `fieldRecipe`에는 `slots`와 `defaults`가 추가됐습니다. 기존
50
+ `tones`, `sizes`, `states` 접근은 그대로 동작합니다.
51
+ - Field label, hint, placeholder는 필수 안내로 취급해 모든 기본 surface에서 AA를 지키는
52
+ `textBody`/`textMuted` 역할을 사용합니다. 검색의 아이콘·지우기 버튼·포커스 계약은
53
+ `searchFieldRecipe`로 분리했습니다.
54
+ - 숫자 알림은 상태 라벨용 `badgeRecipe`가 아니라 solid fill과 `99+` 상한을 가진
55
+ `counterBadgeRecipe`를 사용합니다.
56
+ - `surfaceRecipe`의 기존 index 접근은 그대로 유지합니다.
57
+ - light theme의 `danger` foreground는 tinted feedback surface에서도 WCAG AA를 지키도록
58
+ `#b71919`로 깊어졌습니다. `dangerFill`은 `#b91c1c`, `onDanger`는 white 계약을
59
+ 유지합니다.
60
+ - 신규 recipe는 beta입니다. 두 제품과 Web/RN renderer 검증 전에는 prop 이름을
61
+ stable API로 간주하지 않습니다.
62
+ - CheckboxGroup 값은 중복 없는 `ReadonlySet`이며 변경 때마다 새 Set을 emit합니다.
63
+ RadioGroup은 nullable single key를 사용합니다. 단독 Radio를 값 입력으로 사용하지 않고
64
+ RadioGroup item으로만 조합합니다.
65
+ - Native renderer에서 `required`, `readOnly`, `invalid`를 켤 때는 운영체제가 지원하는 공통
66
+ 접근성 state가 없으므로 현지화된 상태 문구도 함께 전달합니다. Group `required`는 개별
67
+ checkbox/radio의 required로 복제하지 않습니다.
68
+ - 제품 전용 의미는 계속 어댑터에 남깁니다. BurnTok의 `ai` accent와 Yajalal `live`
69
+ 같은 상태/색 이름은 코어로 옮기지 않습니다. 공통 `ai` 아이콘 이름은 특정 제품
70
+ 스타일이 아닌 일반 “AI 기능” 의미 역할입니다.
71
+ - 위험 작업은 화면에서 직접 Promise와 loading boolean을 조합하지 않고
72
+ `createAlertDialogSession`의 `idle/busy/error/closing/closed` 전이를 사용합니다. async confirm은
73
+ 현지화된 `fallbackErrorMessage`를 필수로 받고, 결과는 renderer exit 완료 후 정산합니다.
74
+ - Sheet의 `dismissible`은 visual `sheetRecipe.defaults`에서 제거되어
75
+ `sheetBehaviorDefaults`/`SheetDismissPolicy`로 이동했습니다. 기존 renderer는 outside,
76
+ Escape/Android back, busy, swipe를 각각 `canDismissSheet(reason, busy, policy)`로 판정하고
77
+ `onOpenChange(false, { reason: dismissReason })`를 전달해야 합니다. 기본 swipe는
78
+ 꺼져 있으며 gesture capability와 policy가 모두 활성화된 경우에만 handle을 표시합니다.
79
+ - controlled owner가 `open=false`로 닫는 것은 `programmatic` reason이며 busy 또는
80
+ `dismissible=false`여도 허용합니다. 후속 Dialog/AlertDialog는 Sheet exit/onDismiss가 끝난
81
+ 뒤 열어야 합니다.
82
+ - RN Android의 `Modal` 종료 시점을 `InteractionManager`로 추정하지 않습니다. successor surface를
83
+ 여는 Sheet는 native animation을 끄고 recipe 기반 enter/exit를 renderer가 소유한 뒤, exit 완료와
84
+ Modal host teardown 이후에 `createSheetLifecycle.completeDismiss`를 호출합니다.
85
+ - Select/Combobox adapter는 section 전체에서 item ID를 유일하게 유지하고 비어 있는
86
+ label/textValue/accessibility label을 거부해야 합니다. Select의 open state는 selection과
87
+ 별도 축이며, 사라진 key는 `reconcileSelectSelection`으로 정리합니다.
88
+ - collection item·section의 stable ID는 공백일 수 없습니다. typeahead는 locale-aware 검색을
89
+ 사용합니다. external Combobox는 raw `inputValue`와 제품이 정규화한 `queryValue`를 분리하고,
90
+ `resultQuery === queryValue`인 결과만 표시합니다. transient 결과에 선택 항목이 없으면 stable
91
+ key가 같은 `selectedItem` snapshot을 공급합니다. 공통 single/multiple selection도 controlled
92
+ 값과 default 값을 동시에 받을 수 없습니다.
93
+ - Select/Combobox renderer는 `selectRecipe`/`comboboxRecipe`를 사용합니다. Web은 popover/listbox,
94
+ Native는 Sheet/radio options로 적응하며 Menu role을 대신 사용하지 않습니다.
95
+ - Toast renderer는 화면별 local timeout 배열 대신 `createToastStore`를 하나만 둡니다. 기존
96
+ `toastRecipe.defaults.duration=4000`은 제거됐고 behavior의 기본 5000ms와 최소 5000ms로
97
+ 이동했습니다. action이 있는 Toast는 duration을 명시하지 않으면 persistent입니다.
98
+ - `toastRecipe`은 `surface`, `toneMark`, `title`, `description`, `action`, `close`, `viewport`,
99
+ `placements` anatomy로 나뉘며 `transition.enter/exit`은
100
+ `transition.web.enter/exit`과 `transition.native.enter/exit`으로 바뀌었습니다. 각 renderer는
101
+ exit 또는 Reduce Motion 즉시 종료 뒤 `store.completeExit(id)`를 호출해야 합니다.
102
+ - Toast의 같은 stable id는 기본 `update + preserve timer`로 제자리 갱신됩니다. queue는
103
+ bounded FIFO이고 pending overflow, action, close, timeout, programmatic close, provider teardown은
104
+ 모두 구체적인 `ToastDismissReason`을 한 번만 전달합니다. hover/focus/window/gesture pause와
105
+ announcement priority 번역은 [`toast.md`](./toast.md)의 renderer acceptance를 따릅니다.
106
+ - color resolver에는 제품 alias가 아니라 `statusAccents`와 `statusAccentFills`를
107
+ 분리해 전달합니다. 예를 들어 BurnTok `ai`는 제품 API에 남고 resolver에는 원래의
108
+ `info` status role이 들어가야 합니다.
@@ -0,0 +1,72 @@
1
+ # v0.3 migration
2
+
3
+ v0.2 → v0.3. **소비자가 손대야 할 것은 하나뿐이고**, 나머지는 전부 가산 변경이다.
4
+
5
+ ## 깨지는 변경 하나 — 열거해 둔 톤 목록
6
+
7
+ `buttonRecipe.tones`에 **`link`**가, `surfaceRecipe`에 **`subtle`**이 늘었다. 따라서
8
+ `SurfaceTone`·`ButtonTone` 유니언이 넓어진다.
9
+
10
+ - **읽기만 하는 코드는 영향 없다.**
11
+ - **톤 목록을 열거해 단정하는 테스트는 고쳐야 한다.** BurnTok의
12
+ `packages/design-system/src/index.test.ts`가 그 사례였다.
13
+ - **`SurfaceTone`/`ButtonTone`을 exhaustive switch로 다루는 renderer는 새 분기를 더해야 한다.**
14
+
15
+ ## 왜 늘었는가 — 제품이 먼저 찾은 답을 계약이 받았다
16
+
17
+ v0.3의 recipe 변경은 **새로 설계한 것이 아니라 제품이 이미 고쳐 쓰던 것**이다. `app-rn`은
18
+ 이 패키지의 recipe 31개를 손으로 옮겨 적은 사본으로 갖고 있었고(자기 주석에
19
+ `Temporary v0.1 bridge matching the HJM recipe exactly`라고 적어 두었다), 그중 10개는
20
+ 값이 달랐다. 확인해 보니 **다른 이유가 전부 실제 화면을 보고 고친 것**이었고 측정한 색까지
21
+ 근거로 적혀 있었다. 그래서 앱을 계약에 맞추는 대신 계약이 그 답을 받았다.
22
+
23
+ | 바뀐 값 | 이유 |
24
+ |---|---|
25
+ | `badgeRecipe.tones.brand.background` → `surfaceAlt` | 브랜드 틴트가 "선택됨"만 뜻하게 되어, 정적 배지가 줄지어 있으면 옵션 하나가 켜진 필터처럼 읽혔다 |
26
+ | `noticeRecipe.tones.info.background` → `surfaceAlt` | 정보 워시가 캔버스 위에서 `#DCE3F3`로 앉아 배너가 눌러야 할 컨트롤보다 위계가 높아졌다 |
27
+ | `segmentedControlRecipe.item.selected*` → 불투명 `surfaceAccent`/`contentBrand` | primary의 10% 워시는 배경에 따라 값이 변해(`#E8EFFB` / `#DCE5F3`) 같은 "선택됨"이 세 색으로 읽혔다 |
28
+ | `selectionControlRecipe.states.selectedBackground` → `surfaceAccent` | 위와 같은 이유(투명 워시는 배경을 탄다) |
29
+ | `chipRecipe.states.idle.border` → `border` | `textMuted`로 그리면 선택되지 않은 칩이 옆의 선택된 칩보다 무거워져 신호가 뒤집혔다 |
30
+ | `bottomCtaRecipe.background` → `surface` | 헤어라인 하나가 글자를 자르고 깨진 카드처럼 읽혔다. 그림자가 아니라 surface로 층을 쌓는다 |
31
+
32
+ ## 늘어난 축 (가산)
33
+
34
+ - `buttonRecipe.tones.link` — 구역 오류 복구처럼 **조용해야 하지만 나아갈 길로 읽혀야 하는**
35
+ 동작. 채움은 복구가 화면의 **유일한** 출구일 때만이다.
36
+ - `surfaceRecipe.subtle` — 알리는 면(설명 배너·등급 판·조용한 콜아웃)의 자리. 헤어라인을
37
+ 항상 그린다.
38
+ - `searchFieldRecipe.shapes` — 모양이 **축이 되었다**(`radius` 단일값을 대체). `fieldRecipe`와
39
+ 같은 키를 쓰므로 두 입력의 모양을 맞추거나 일부러 다르게 하는 것이 둘 다 표현된다.
40
+ - `selectionControlRecipe.presentations.grouped`, `selectionGroupRecipe`의 `grouped` 간격,
41
+ `surfaceRecipe.*.borderAlways`, `switchRecipe`의 disabled 색 6종과 `rowTwoLineMinHeight`,
42
+ `bottomCtaRecipe.shadow`, `badgeRecipe.tones.strong`.
43
+
44
+ ## 새 계약 (약 30개)
45
+
46
+ antd 6.6.0 reference inventory 73개 중 계약 완료가 27 → 56개가 되었다. 전부
47
+ `status: "planned"`이므로 **renderer 구현을 약속하지 않는다** — `docs/expansion-roadmap.md`의
48
+ maturity gate 그대로다. 목록은 `src/catalog.ts`와 `docs/ant-design-coverage.md`에 있다.
49
+
50
+ `content-state.ts`가 그중 실제 제품 결함을 닫은 첫 사례다. 상태가 **화면 전체를 대신하는지
51
+ 구역만 대신하는지**(`scope: "screen" | "region"`)가 강조 수준과 접근성 발표를 함께 정한다.
52
+ `app-rn`에서 화면 전체 오류가 보조 기술에 아무것도 알리지 않는 동안 구역 오류만 알리고 있던
53
+ 것을 이 축이 잡았다.
54
+
55
+ ## v0.3에서 남긴 제약 하나 — 쉬는 칩은 `surfaceAlt` 위에 놓지 않는다
56
+
57
+ `chipRecipe.states.idle.border`를 `content.secondary`에서 `border.default`로 옮긴 것(위 표)의
58
+ 부작용이다. **`border`와 `surfaceAlt`는 두 테마에서 정확히 같은 색이다**(light `#e5e8eb`,
59
+ dark `#1e293b`). 그래서 쉬는 칩을 `surfaceAlt` 배경 위에 놓으면 테두리가 사라지고, 칩의
60
+ 채움(`surface`)도 부모와 거의 같아져(`#f2f4f6` 대 `#e5e8eb`, 약 1.1:1) **칩의 경계가 보이지
61
+ 않는다.**
62
+
63
+ 이 변경을 받으면서 `test/contracts.test.ts`의 대비 단정에서 이 항목을 뺐다. 그 단정은
64
+ "쉬는 칩 테두리가 `surfaceAlt` 위에서 3:1 이상"을 요구했는데, **쉬는 칩의 테두리는 공용
65
+ 헤어라인이지 인터랙티브 경계가 아니다** — 선택된 칩의 테두리(`content.brand`)가 그 역할을
66
+ 한다. 즉 단정의 전제가 틀렸다.
67
+
68
+ **그러나 제약은 남는다.** 확인 결과 `app-rn`에서 칩을 쓰는 8개 화면 중 `surfaceAlt` 배경을
69
+ 쓰는 곳은 없어서 지금은 안전하다(`surfaceAlt`는 라이브·라인업·경기상세에서만 쓰이고 그
70
+ 화면들에는 칩이 없다). 그 조합이 생기면 **칩의 presentation을 `surface`가 아닌 것으로
71
+ 바꾸거나 부모 배경을 옮겨야 한다** — 테두리 색을 되돌리는 것은 답이 아니다. 되돌리면
72
+ 선택되지 않은 칩이 옆의 선택된 칩보다 무거워져서 필터 줄의 유일한 신호가 다시 뒤집힌다.
@@ -0,0 +1,82 @@
1
+ # v0.5 migration
2
+
3
+ v0.4 → v0.5는 기존 foundation/recipe 런타임 값을 유지하는 additive release입니다. 다만
4
+ catalog status를 그대로 열거하거나 Showcase route 존재를 renderer 증거로 사용한 소비자는
5
+ 아래 두 가지를 확인해야 합니다.
6
+
7
+ ## 소비자가 확인할 변경
8
+
9
+ ### DesignSystemProvider maturity
10
+
11
+ `DesignSystemProvider`는 `beta`에서 `planned + contract-ready`로 정정됐습니다. 환경 resolver가
12
+ 존재한다는 사실만으로 실제 Web/RN Context adapter가 검증됐다고 볼 수 없기 때문입니다.
13
+ catalog status를 exhaustive하게 단정하는 테스트는 새 값을 반영해야 합니다. 공개 환경 API는
14
+ 삭제되지 않았습니다.
15
+
16
+ ### Ant Design coverage 명칭
17
+
18
+ `summarizeAntDesignCoverage()`의 renderer를 암시하던 필드는 deprecated alias로 유지되며,
19
+ 새 코드는 maturity 전용 필드를 사용합니다.
20
+
21
+ | 기존 alias | 새 필드 | 실제 의미 |
22
+ | --- | --- | --- |
23
+ | `fullyPreviewable` | `fullyMature` | 모든 HJM target이 stable/beta |
24
+ | `partiallyPreviewable` | `partiallyMature` | target 일부만 stable/beta |
25
+ | `contractOnly` | `plannedOnly` | target이 모두 planned |
26
+
27
+ 실제 Web preview 수는 Showcase evidence registry에서 별도로 읽습니다.
28
+
29
+ ## 신규 foundation
30
+
31
+ - `fontFamily.ui | code`
32
+ - `fontWeight.regular | medium | semibold | bold | heavy`
33
+ - `letterSpacing.tight | normal | wide`
34
+ - `numeric.proportional | tabular`
35
+ - `heading.level1 ... level5`
36
+
37
+ 기존 `typography` key와 런타임 숫자·weight 값은 유지됩니다. recipe 내부 weight도 같은 foundation
38
+ 값을 참조하므로 화면 변화 없이 단일 출처가 됩니다.
39
+
40
+ ## Provider 계약
41
+
42
+ `resolveDesignSystemEnvironment()`는 기존 호출을 유지하면서 선택적인 parent/system signal을
43
+ 받습니다.
44
+
45
+ ```ts
46
+ const value = resolveDesignSystemProviderValue(
47
+ { direction: "rtl" },
48
+ {
49
+ parent: parentValue.environment,
50
+ systemTheme: "dark",
51
+ systemTextScale: 1.25,
52
+ systemReducedMotion: true,
53
+ },
54
+ );
55
+
56
+ // resolveColorReference에 그대로 전달
57
+ value.palette;
58
+ ```
59
+
60
+ 축별 우선순위는 `explicit input → resolved parent → system signal → HJM default`입니다.
61
+ `parent`는 반드시 이미 해석된 light/dark 환경이어야 하며 `theme: "system"`을 허용하지 않습니다.
62
+ 임의 theme/component token override와 React/RN Context는 계속 코어 범위 밖입니다.
63
+
64
+ ## Showcase와 CI
65
+
66
+ - 91개 canonical route를 Web reference, contract-only, Web unsupported로 분리합니다.
67
+ - planned route는 구현된 것처럼 보이는 JSX를 렌더링하지 않습니다.
68
+ - Home/Explorer 수치는 실제 evidence registry에서 계산합니다.
69
+ - Storybook manager와 preview가 foundation token을 사용합니다.
70
+ - token-boundary 검사와 static classification 검사가 Showcase check에 포함됩니다.
71
+ - Ant Design reference는 6.6.1로 고정됩니다. v0.7부터 외부 registry drift 검사는 자동 CI가
72
+ 아니라 필요할 때 실행하는 `pnpm reference:antd:verify`로 단순화되었습니다.
73
+ - GitHub Pages workflow는 Node 24 기반 action major를 사용합니다.
74
+
75
+ ## 권장 전환 순서
76
+
77
+ 1. package/tag를 `v0.5.0`으로 고정
78
+ 2. Provider adapter가 OS 신호를 한 번만 측정하고 새 resolver에 전달하는지 확인
79
+ 3. 로컬 raw font weight 대신 `fontWeight` foundation 사용
80
+ 4. AntD coverage UI가 deprecated previewable alias를 renderer 수치로 표시하지 않는지 확인
81
+ 5. Web/RN 제품 fixture에서 light/dark, RTL, 200% text, Reduce Motion 검증
82
+ 6. `pnpm check`와 제품별 renderer/접근성 테스트 실행
@@ -0,0 +1,197 @@
1
+ # v0.6 migration
2
+
3
+ v0.6은 renderer가 없는 계약 패키지의 역할을 이름에서 분명히 하고, Web/RN 앱이 필요한
4
+ graph만 가져가도록 package boundary를 나누는 breaking release입니다. package name은
5
+ `@hjm/design-system`에서 `@hjmds/design-contracts`로 변경됩니다. 이전 이름 alias나 호환
6
+ wrapper는 제공하지 않습니다.
7
+
8
+ 같은 release에서 공식 renderer도 monorepo package로 제공됩니다. Web은 `@hjmds/react`,
9
+ React Native는 `@hjmds/react-native`를 선택하고 contracts와 같은 tag를 고정합니다.
10
+
11
+ ## 1. dependency와 import를 원자적으로 변경
12
+
13
+ dependency와 source/test/Storybook/build script의 import를 같은 변경에서 전환합니다.
14
+
15
+ ```diff
16
+ - "@hjm/design-system": "git+https://github.com/jim1286/hjm-design-system.git#v0.5.2"
17
+ + "@hjmds/design-contracts": "git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/design-contracts"
18
+ ```
19
+
20
+ 기존 root symbol은 유지되지만 package specifier가 바뀌므로 모든 import를 갱신해야 합니다.
21
+
22
+ ```diff
23
+ - import { spacing, typography } from "@hjm/design-system";
24
+ + import { spacing, typography } from "@hjmds/design-contracts/foundations";
25
+ ```
26
+
27
+ ## 2. 앱 runtime은 granular subpath 사용
28
+
29
+ - 토큰 전체: `@hjmds/design-contracts/tokens`
30
+ - foundation만: `@hjmds/design-contracts/foundations`
31
+ - palette만: `@hjmds/design-contracts/colors`
32
+ - window class와 responsive value: `@hjmds/design-contracts/responsive`
33
+ - Grid descriptor와 geometry: `@hjmds/design-contracts/grid`
34
+ - 공통 recipe: `@hjmds/design-contracts/recipes`
35
+ - Button·Surface·Field 최소 recipe: `@hjmds/design-contracts/recipes/base`
36
+ - 공통 anatomy/style contract: `@hjmds/design-contracts/contracts`
37
+ - 현재 contract 버전만: `@hjmds/design-contracts/version`
38
+ - 단일 상태·validator: `@hjmds/design-contracts/components/<name>`
39
+
40
+ 예를 들어 Toast adapter는 전체 root 대신 다음처럼 가져옵니다.
41
+
42
+ ```ts
43
+ import { createToastStore } from "@hjmds/design-contracts/components/toast";
44
+ import { resolveContentStateAnnouncement } from "@hjmds/design-contracts/components/content-state";
45
+ ```
46
+
47
+ `/recipes/all`, `/behaviors`, `/catalog`, `/evidence`, `/showcase`는 전체 registry 또는 CI
48
+ metadata가 필요한 도구용입니다. JS를 실행하지 않는 CI는
49
+ `@hjmds/design-contracts/manifest.json`과
50
+ `@hjmds/design-contracts/renderer-evidence.json`을 읽을 수 있습니다. RN 화면 runtime에서
51
+ root나 tooling entry를 사용하면 Metro가 불필요한 계약 graph를 따라갈 수 있습니다.
52
+
53
+ ## 3. renderer 설치
54
+
55
+ Web 앱:
56
+
57
+ ```bash
58
+ pnpm add \
59
+ '@hjmds/design-contracts@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/design-contracts' \
60
+ '@hjmds/react@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/react'
61
+ ```
62
+
63
+ React Native 앱은 Web package 이름을 유지한 채 path만 바꾸지 말고, Native renderer 이름과
64
+ path를 함께 지정합니다.
65
+
66
+ ```bash
67
+ pnpm add \
68
+ '@hjmds/design-contracts@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/design-contracts' \
69
+ '@hjmds/react-native@git+https://github.com/jim1286/hjm-design-system.git#v0.6.0&path:/packages/react-native'
70
+ ```
71
+
72
+ renderer는 contracts를 peer dependency로 요구하므로 두 항목을 모두 명시해야 합니다.
73
+
74
+ ## 4. renderer의 localized copy를 명시적으로 주입
75
+
76
+ v0.6 renderer는 한국어 또는 영어 문구를 내부 default로 만들지 않습니다. 아래 prop은
77
+ 사용자에게 보이거나 screen reader가 읽는 제품 copy이므로 필수가 되었고, 누락하면
78
+ TypeScript가 migration 지점을 표시합니다.
79
+
80
+ - Web: `SearchField.clearLabel`, `Select.placeholder`,
81
+ `Select.emptySelectionLabel`, `Combobox.emptyMessage`,
82
+ `Combobox.loadingMessage`, `Combobox.selectionRequiredMessage`,
83
+ `Dialog.closeLabel`, `Sheet.closeLabel`, `Table.emptyState`
84
+ - React Native: `Form.fallbackErrorMessage`, `SearchField.clearLabel`,
85
+ `SearchField.busyLabel`, `Select.placeholder`, `Select.dismissLabel`,
86
+ `Combobox.emptyMessage`, `Combobox.loadingMessage`, `Combobox.clearLabel`,
87
+ `Combobox.dismissLabel`, `Menu.dismissLabel`, `Dialog.closeLabel`,
88
+ `Sheet.closeLabel`
89
+
90
+ 앱의 i18n catalog에서 각 값을 공급합니다. 선택 목록·결과·패널 region 이름처럼 기존
91
+ control label에서 중립적으로 유도할 수 있는 이름은 optional prop으로 남으며, renderer는
92
+ 번역 suffix를 덧붙이지 않습니다.
93
+
94
+ ```tsx
95
+ <SearchField label={t("search.label")} clearLabel={t("search.clear")} />
96
+ <Dialog title={t("settings.title")} closeLabel={t("settings.close")} trigger={trigger} />
97
+ ```
98
+
99
+ ## 5. Web/RN core component API 정규화
100
+
101
+ Text, Surface, Stack, Button, Tag, Card는 두 renderer에서 같은 semantic axis와 기본값을
102
+ 사용합니다. 신규 코드는 다음 canonical API를 사용합니다.
103
+
104
+ ```diff
105
+ - <Stack direction="row" />
106
+ + <Stack axis="inline" />
107
+
108
+ - <Button label={t("save")} />
109
+ + <Button>{t("save")}</Button>
110
+
111
+ - <Tag label={t("new")} />
112
+ + <Tag>{t("new")}</Tag>
113
+
114
+ - <Surface tone="brand" />
115
+ + <Surface tone="accent" />
116
+
117
+ - <Grid descriptor={{ columns: { compact: 2 } }} />
118
+ + <Grid columns={{ compact: 2 }} />
119
+
120
+ - <IconButton accessibilityLabel={t("close")} icon={<Close />} tone="link" />
121
+ + <IconButton label={t("close")} tone="ghost"><Close /></IconButton>
122
+ ```
123
+
124
+ 위 Native 호출은 0.6에서도 deprecated alias로 동작합니다. `Stack.direction`,
125
+ `Button.label`, `Tag.label`, Surface의 `sunken`/`brand`, `Grid.descriptor`, IconButton의
126
+ `accessibilityLabel`/`icon`/`tone="link"` 제거는 major release에서만
127
+ 진행합니다. 하지만 새 예제·Storybook·제품 코드는 alias를 사용하지 않아야 합니다.
128
+
129
+ Button의 `loading`은 이제 `disabled`와 다른 상태입니다. 중복 activation은 막지만
130
+ 포커스를 제거하지 않고 busy 상태를 보조 기술에 발표합니다. 로딩 중 포커스가 다른 곳으로
131
+ 강제로 이동한다고 가정한 테스트가 있다면 focus 유지 기대값으로 바꿉니다.
132
+
133
+ Card는 더 이상 Native의 단순 Surface alias가 아닙니다. 양쪽에서 `media`, `title`,
134
+ `description`, `children`, `actions`, `selected` anatomy를 공유하며 계약은
135
+ `@hjmds/design-contracts/components/card`와 `cardRecipe`에서 가져옵니다. 전체 정규화 표와
136
+ 호환 범위는 [`cross-platform-core-normalization.md`](./cross-platform-core-normalization.md)를
137
+ 참고합니다.
138
+
139
+ ## 6. 실험적 NumberField·Slider renderer
140
+
141
+ Web과 Native renderer에 `NumberField`와 `./number-field` granular entry가 추가됐습니다.
142
+ 값은 `number | null`이고 편집 중 문자열은 blur/submit 전까지 별도 draft로 유지합니다.
143
+ 두 플랫폼 모두 증감 action의 현지화된 이름을 필수로 받습니다.
144
+
145
+ ```tsx
146
+ <NumberField
147
+ label={t("partySize.label")}
148
+ decrementLabel={t("partySize.decrement")}
149
+ incrementLabel={t("partySize.increment")}
150
+ min={1}
151
+ max={8}
152
+ defaultValue={2}
153
+ />
154
+ ```
155
+
156
+ 현재 parser는 locale-neutral ASCII decimal/exponent만 지원합니다. 통화·단위·grouping separator,
157
+ locale별 decimal separator를 자동 지원한다고 가정하지 마세요. 실제 제품 numeric-input
158
+ vertical slice가 아직 없으므로 catalog surface는 `planned`이며, 이 API는 0.6에서 먼저
159
+ 검증하는 experimental renderer입니다.
160
+
161
+ `Slider`와 `./slider` entry도 두 renderer에 추가됐습니다. NumberField와 같은 min-origin
162
+ clamp/snap resolver를 사용하며, drag/keyboard 중 `onValueChange`와 interaction 종료 시
163
+ `onValueChangeEnd`를 분리합니다. Web은 native range input의 keyboard/pointer semantics를
164
+ 사용하고, Native는 dependency-free responder와 `adjustable` action을 사용합니다.
165
+
166
+ ```tsx
167
+ <Slider
168
+ label={t("score.label")}
169
+ min={0}
170
+ max={100}
171
+ step={5}
172
+ defaultValue={50}
173
+ getValueText={(value) => t("score.value", { value })}
174
+ onValueChange={setDraftScore}
175
+ onValueChangeEnd={saveScore}
176
+ />
177
+ ```
178
+
179
+ Native에서는 위 props에 제품이 번역한 `decrementLabel`과 `incrementLabel`도 필수입니다.
180
+ 첫 slice는 horizontal single-thumb만 지원합니다. range/multi-thumb, vertical orientation,
181
+ marks, nonlinear scale은 실제 제품 요구와 fixture가 생기기 전까지 열지 않습니다. Slider도
182
+ 실제 product numeric flow 증거 전에는 catalog Web/Native surface가 `planned`입니다.
183
+
184
+ ## 7. 설치·번들 검증
185
+
186
+ 1. lockfile을 다시 생성합니다.
187
+ 2. 이전 package name 또는 중복 dependency가 남지 않았는지 검사합니다.
188
+ 3. Web production build와 RN Metro/Hermes export를 모두 실행합니다.
189
+ 4. 이 저장소에서는 import graph budget까지 포함하는 `pnpm check`를 실행합니다.
190
+
191
+ ```bash
192
+ rg '@hjm/design-system' src test package.json
193
+ pnpm check
194
+ ```
195
+
196
+ 첫 검색은 결과가 없어야 합니다. `v0.5.2`와 이전 tag에는 이전 package name이 들어 있으므로,
197
+ 새 package name으로 바꾼 뒤 예전 tag를 계속 가리키는 조합은 설치할 수 없습니다.