@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/icon.md ADDED
@@ -0,0 +1,22 @@
1
+ # Icon contract
2
+
3
+ `Icon`은 Lucide 같은 외부 아이콘 패키지를 public API로 노출하지 않습니다. HJM의 semantic
4
+ name을 제품 adapter가 Web SVG 또는 React Native glyph로 번역해, 아이콘 패키지를 바꾸어도
5
+ 화면의 의미와 크기·색·stroke 문법은 유지합니다.
6
+
7
+ - 기본 icon은 장식입니다. 주변 label이나 button accessible name이 같은 의미를 이미 전달하면
8
+ Web은 accessibility tree에서 숨기고 RN은 `accessible={false}`로 렌더링합니다.
9
+ - icon 자체만 정보를 전달할 때만 `decorative: false`와 현지화된 `accessibilityLabel`을 함께
10
+ 사용합니다. 이 경우 대비가 약한 `decorative` tone은 허용하지 않습니다.
11
+ - icon-only action의 이름은 Icon이 아니라 바깥 `IconButton`/Link가 소유합니다. 예를 들어
12
+ 돋보기 모양을 “돋보기”라고 읽지 않고 button이 “검색” 동작을 읽습니다.
13
+ - `back`, `forward`, `chevronStart`, `chevronEnd`는 논리 방향이므로 RTL에서 mirror합니다.
14
+ 위/아래·상태·미디어 icon은 고정 방향입니다.
15
+ - renderer는 recipe의 glyph size, semantic tone, regular/strong stroke, round cap/join을 모두
16
+ 소비하고 임의 `size`, `color`, `strokeWidth`로 정체성을 우회하지 않습니다. 제품 팀 색처럼
17
+ 검증된 도메인 색은 제품 adapter가 별도 product icon contract로 제한합니다.
18
+ - `inverse` tone은 brand/danger처럼 `onPrimary` 대비가 검증된 어두운 fill 위에서만 사용합니다.
19
+ 일반 canvas/surface 위의 정보 icon에는 사용할 수 없습니다.
20
+
21
+ WAI의 기준처럼 주변 텍스트와 중복되는 그림은 장식으로 숨기고, 기능을 단독으로 전달하는
22
+ 그림은 모양 이름이 아니라 동작/목적을 접근성 이름으로 제공합니다.
@@ -0,0 +1,121 @@
1
+ # HJM Design Identity
2
+
3
+ ## 한 문장
4
+
5
+ > 조용한 화면 위에 중요한 순간만 선명하게.
6
+
7
+ HJM은 장식으로 브랜드를 증명하지 않습니다. 정보와 행동의 우선순위를 분명하게 만들고,
8
+ 사용자가 결정을 내려야 하는 순간에만 브랜드 색과 움직임을 집중합니다. BurnTok, Yajalal,
9
+ 그리고 앞으로 생길 제품은 서로 다른 콘텐츠를 다루더라도 같은 판단 기준을 공유합니다.
10
+
11
+ ## 네 가지 원칙
12
+
13
+ ### 1. 명확한 집중
14
+
15
+ - 한 영역에는 하나의 지배적인 행동만 둡니다.
16
+ - `primary` fill은 주요 행동에, 더 밝은 `contentBrand`는 focus·선택 indicator·현재 위치에
17
+ 씁니다. 둘을 나눠 dark surface에서도 상태 경계가 사라지지 않게 합니다.
18
+ - 제목, 본문, 보조 정보의 순서는 크기뿐 아니라 색과 여백으로도 구분합니다.
19
+ - 인터페이스가 콘텐츠보다 먼저 보이면 요소를 덜어냅니다.
20
+
21
+ ### 2. 부드러운 구조
22
+
23
+ - 배경과 surface의 작은 차이, 얇은 border, 충분한 여백으로 계층을 만듭니다.
24
+ - 강한 그림자는 떠 있어야 하는 요소에만 씁니다.
25
+ - `12 / 16 / 24 / full` radius를 중심으로 친근하지만 가볍지 않은 인상을 유지합니다.
26
+ - 일반 정보는 plain section과 list를 우선하고, 카드는 의미 있는 묶음에 사용합니다.
27
+
28
+ ### 3. 확실한 피드백
29
+
30
+ - pressed, focused, selected, loading, invalid, disabled 상태를 빠짐없이 정의합니다.
31
+ - 상태는 색 하나에만 의존하지 않고 형태, 아이콘, 문장 또는 접근성 상태를 함께 사용합니다.
32
+ - 오류 문장은 문제와 다음 행동을 알려줍니다.
33
+ - 모션은 상태 변화의 원인과 결과를 잇는 데만 사용합니다.
34
+
35
+ 선택 입력에서는 이 원칙을 `brand tint + 선명한 border + 형태 indicator`의 세 겹으로
36
+ 구현합니다. Checkbox는 check/dash, RadioGroup은 dot을 항상 보여 주므로 선택을 색만으로
37
+ 추측하게 하지 않습니다. 설명이 있는 결정은 넉넉한 `card` 행, 짧고 반복되는 목록은
38
+ `plain` 행으로 표현하지만 같은 focus·invalid·disabled 문법을 유지합니다.
39
+
40
+ ### 4. 플랫폼에 자연스럽게
41
+
42
+ - Web과 React Native는 의미, 명칭, 크기 계층, 접근성 기준을 공유합니다.
43
+ - 키보드, hover, focus trap, safe area, 뒤로가기, native picker는 플랫폼 관습을 따릅니다.
44
+ - 픽셀 복제보다 같은 우선순위와 같은 신뢰감을 목표로 합니다.
45
+ - 플랫폼 차이는 renderer가 소유하며 제품 화면이 조건문으로 흩어 구현하지 않습니다.
46
+
47
+ ## 시각 문법
48
+
49
+ ### Color
50
+
51
+ - 깨끗한 white canvas와 깊은 navy dark canvas가 기본입니다.
52
+ - HJM blue는 행동과 현재 상태를 드러내는 서명입니다.
53
+ - 그라디언트는 브랜드 마크, hero, 특별한 CTA에만 제한합니다.
54
+ - `text`, `textBody`, `textMuted`는 필수 정보에 사용할 수 있습니다.
55
+ - `textSub`, `textWeak`는 장식적·중복적인 정보에만 사용하며 필수 문장에는 쓰지 않습니다.
56
+ - 제품 상태는 먼저 `info / success / warning / attention`에 매핑하고, 제품 이름은 공용
57
+ 패키지로 올리지 않습니다.
58
+
59
+ ### Type
60
+
61
+ - 제목은 짧고 단단하게, 본문은 편안하게 읽히도록 합니다.
62
+ - 굵기는 정보 우선순위를 보완하며, 크기만 키워 계층을 만들지 않습니다.
63
+ - 숫자 비교가 중요한 화면은 renderer에서 tabular number를 적용합니다.
64
+ - 제품별 폰트 로딩은 앱이 소유하지만 typography의 크기·행간 계약은 공유합니다.
65
+
66
+ ### Shape and elevation
67
+
68
+ - 작은 제어는 `radius.md`, 묶음과 카드는 `radius.lg`, hero와 sheet는 `radius.xl`을
69
+ 기본으로 합니다.
70
+ - `raised`는 카드, `floating`은 menu·toast, `overlay`는 dialog처럼 실제로 떠 있는
71
+ 계층에 대응합니다.
72
+ - border와 surface 차이로 충분하면 shadow를 추가하지 않습니다.
73
+
74
+ ### Motion
75
+
76
+ - `120ms`: hover, press, 작은 색 변화
77
+ - `200ms`: 일반적인 열기·닫기와 상태 전환
78
+ - `320ms`: 큰 화면 요소와 맥락 전환
79
+ - Reduce Motion에서는 이동과 반복을 제거하고 즉시 전환 또는 짧은 opacity로 대체합니다.
80
+ - bounce와 spring은 공간 관계를 설명할 때만 사용합니다.
81
+
82
+ ## HJM답지 않은 패턴
83
+
84
+ - 같은 영역에 Primary 버튼을 여러 개 배치
85
+ - 모든 정보를 카드로 감싸거나 그림자로 분리
86
+ - 원시 hex, 임의의 간격, 임의의 radius를 화면에서 직접 선언
87
+ - 브랜드색을 장식 배경처럼 넓게 사용
88
+ - disabled만 제공하고 loading, focus, invalid 상태는 누락
89
+ - Web 동작을 RN에 그대로 복제하거나 native 관습을 이유 없이 Web에 강제
90
+ - 외부 라이브러리의 색상과 외형을 그대로 복사
91
+
92
+ ## 제품이 소유하는 것
93
+
94
+ 공용 시스템은 시각적 의미와 접근성 계약을 소유합니다. 제품은 도메인 의미와 콘텐츠를
95
+ 소유합니다.
96
+
97
+ | 공용 HJM 역할 | 제품 매핑 예시 |
98
+ | --- | --- |
99
+ | `info` | BurnTok `ai` |
100
+ | `success` | BurnTok `built`, Yajalal `win` |
101
+ | `warning` | BurnTok `rare` |
102
+ | `attention` | BurnTok `popular`, Yajalal `live` |
103
+ | product palette | KBO 구단 색상, 코드 미리보기 색상 |
104
+
105
+ 제품 매핑은 앱 또는 제품 어댑터에 남깁니다. HJM 코어에 제품명, 저장 키, 도메인 상태를
106
+ 추가하지 않습니다.
107
+
108
+ ## 참고 원칙
109
+
110
+ 다른 시스템에서는 외형이나 코드를 복사하지 않고 다음을 학습합니다.
111
+
112
+ - [Ant Design](https://ant.design/docs/spec/values/): 설계 가치, 토큰 계층, 컴포넌트 범위
113
+ - [Radix Primitives](https://www.radix-ui.com/primitives/docs/overview/introduction): compound anatomy와 접근성 행동
114
+ - [React Aria](https://react-spectrum.adobe.com/react-aria/getting-started.html): 키보드·터치·국제화 상호작용
115
+ - [Material](https://developer.android.com/develop/ui/compose/designsystems/custom): color·type·shape subsystem
116
+ - [Polaris](https://polaris-react.shopify.com/getting-started/components-lifecycle): 컴포넌트 생명주기
117
+ - [Tamagui](https://tamagui.dev/docs/intro/introduction): typed variant와 플랫폼 적응
118
+ - [TDS](https://developers-apps-in-toss.toss.im/design/components.html): 간결한 모바일 정보 계층과 협업 언어
119
+
120
+ TDS를 포함한 제3자 전용 자산·폰트·토큰·구현은 라이선스 범위를 확인하지 않고 포함하지
121
+ 않습니다. HJM은 공개된 원칙과 일반적인 사용 패턴만 독립적으로 재해석합니다.
package/docs/image.md ADDED
@@ -0,0 +1,84 @@
1
+ # Image contract
2
+
3
+ **문제.** 이 시스템에 이미지가 하나도 없었습니다. 사진 콘텐츠(선수 프로필 사진, FA 등급
4
+ 차트 이미지)를 레이아웃 밀림 없이, 로드 실패에도 의미를 잃지 않게 보여 주는 첫 계약입니다.
5
+
6
+ ## 대체 텍스트의 의무 — Icon과 같은 모양
7
+
8
+ `Icon`이 이미 같은 문제(장식 vs 정보)를 `decorative`/`accessibilityLabel` discriminated
9
+ union으로 풀었습니다. Image는 그 모양을 그대로 따릅니다.
10
+
11
+ ```ts
12
+ const avatar = {
13
+ src: "https://cdn.example.com/kbo/player-42.jpg",
14
+ width: 400,
15
+ height: 300,
16
+ } satisfies ImageDescriptor; // decorative — 옆 텍스트가 이미 선수 이름을 말한다
17
+
18
+ const chart = {
19
+ src: "https://cdn.example.com/kbo/grade-chart.png",
20
+ width: 800,
21
+ height: 450,
22
+ decorative: false,
23
+ accessibilityLabel: "2026시즌 FA 등급별 보상 규정 표",
24
+ } satisfies ImageDescriptor; // 정보 — 이미지 자체가 유일한 정보 전달 수단
25
+ ```
26
+
27
+ - 기본은 `decorative`입니다. 주변 caption이나 문장이 이미 같은 의미를 전달하면 화면에서
28
+ 중복 설명을 만들지 않습니다.
29
+ - 이미지 자체만 정보를 전달할 때는 `decorative: false`와 현지화된 `accessibilityLabel`이
30
+ **함께** 필요합니다. 하나만 있는 조합(장식인데 label이 있거나, 정보인데 label이 없는
31
+ 경우)은 validator가 거부합니다 — Icon의 `validateIconDescriptor`와 동일한 판단입니다.
32
+ - Icon과 달리 Image에는 `tone`이 없습니다. 사진은 semantic color로 물들지 않습니다.
33
+
34
+ ## 종횡비 — 레이아웃 밀림 방지
35
+
36
+ `width`/`height` intrinsic dimension이 필수입니다. `next/image`가 번들러 없이 못 여는
37
+ 문제(로드 전 자리 확보)를 여기서는 필수 필드로 강제합니다. `resolveImageAspectRatio(width,
38
+ height)`가 순수 계산을 담당하고, renderer는 로드 전에도 이 비율로 자리를 예약해 콘텐츠가
39
+ 갑자기 밀리지 않게 합니다. 두 값 모두 양의 유한수여야 하며 아니면 `RangeError`입니다.
40
+
41
+ ## 로드 상태와 실패 시 화면
42
+
43
+ `ImageLoadStatus`는 `idle | loading | loaded | error`입니다. `idle`/`loading` 동안은
44
+ `imageRecipe.placeholder`(중립 sunken 배경)를 보여 timer나 스켈레톤 애니메이션 없이도
45
+ 자리를 채웁니다. `error`에서는 `imageRecipe.fallback`이 중립 배경 위에 `error` semantic
46
+ icon을 보여 줍니다 — 실제 사진이 아니라 실패했다는 사실 자체를 전달합니다.
47
+
48
+ 가장 중요한 규칙은 접근성 이름의 연속성입니다. **정보 이미지가 로드에 실패해도 그
49
+ `accessibilityLabel`은 사라지지 않습니다.** `resolveImageFallbackAccessibilityLabel`이
50
+ 정보 이미지에는 원래 label을, 장식 이미지에는 `undefined`를 돌려줍니다 — "이미지를 표시할
51
+ 수 없습니다" 같은 일반 문구로 원래 의미를 덮어쓰지 않습니다. 시각 형태(사진 → fallback
52
+ 아이콘)만 바뀌고, 그 사진이 전달하던 의미는 로드 성공 여부와 무관하게 유지됩니다.
53
+
54
+ ## Preview(확대 보기)를 넣지 않는 이유
55
+
56
+ Ant Design `Image`의 클릭 확대·zoom·그룹 프리뷰는 이 계약에 없습니다. 그것은 표시가
57
+ 아니라 **overlay 행동**이고, 상태 축으로 보면 `open`/`escape`/`outside dismiss`처럼
58
+ Dialog·Sheet가 이미 소유한 문제입니다. 이 조합을 Image 자체에 넣으면 정적 표시
59
+ 컴포넌트가 조용히 자기 자신의 modal 수명주기를 갖게 됩니다. 확대 보기가 실제로
60
+ 필요해지면 별도 Web 전용 `ImagePreview` overlay가 기존 `Dialog`/`AnchoredOverlay` 계약을
61
+ 조합해서 풀 문제이지, `Image`가 escape hatch로 열 문제가 아닙니다.
62
+
63
+ ## 플랫폼 번역
64
+
65
+ `fit: cover | contain | fill`은 Web `object-fit`과 거의 그대로 대응합니다. RN
66
+ `resizeMode`는 `fill`에 해당하는 이름이 없어 `stretch`로 번역합니다 — `nativeResizeModes`가
67
+ 그 한 곳의 이름 차이를 소유하므로 제품이 각자 매핑표를 만들지 않습니다.
68
+
69
+ ## 현재 검증과 남은 증거
70
+
71
+ first-party Web/RN renderer가 추가되어 contract와 두 surface를 `beta`로 승격했다. Web은
72
+ intrinsic ratio 예약, 장식/정보 이미지의 alt 의미, load/error 상태, fallback 접근성 이름
73
+ 유지와 `next/image` 같은 framework adapter 경계를 SSR·browser test로 검증한다. Native도
74
+ 같은 `src`/`width`/`height` descriptor를 직접 resolve해 intrinsic ratio를 예약하고,
75
+ `nativeResizeModes`로 fit을 번역하며, 장식 기본값·정보 이미지의 fallback 이름 유지·built-in
76
+ fallback·`src` 변경 후 재시도를 component test로 검증한다. `sourceAdapter`와 `renderImage`
77
+ 경계로 bare RN의 `ImageSourcePropType` 및 `expo-image` 같은 optimized host를 연결할 수 있다.
78
+ 이전 RN `source` API는 마이그레이션 호환용 deprecated 경계일 뿐 intrinsic-size 계약의
79
+ 증거로 세지 않는다.
80
+
81
+ 이전 후보였던 야잘알 팀 엠블럼은 여전히 Avatar 성격이므로 Image의 제품 증거로 세지 않고,
82
+ BurnTok의 data URI 아이콘도 network failure 증거로 세지 않는다. 실제 network asset의
83
+ 404/재시도와 VoiceOver·TalkBack 접근성 이름 유지 증거는 stable 승격 전까지 명시적인
84
+ debt로 남는다.
@@ -0,0 +1,85 @@
1
+ # v0.5 implementation design
2
+
3
+ ## 목적
4
+
5
+ v0.5는 HJM 계약을 더 크게 보이게 만드는 릴리즈가 아니라, **카탈로그·토큰·Showcase가
6
+ 같은 사실을 말하게 만드는 릴리즈**다. Ant Design reference inventory는 범위 비교에만
7
+ 사용하고, 구현 증거가 없는 planned 항목을 실제 renderer처럼 표시하지 않는다.
8
+
9
+ ## 설계 결정
10
+
11
+ ### 1. 테마는 HJM light/dark 고정 계약이다
12
+
13
+ 코어는 제품이 임의의 색을 덮어쓰는 `Partial<ThemeColors>` API를 제공하지 않는다. 임의
14
+ override는 action/content 대비와 semantic role의 뜻을 동시에 깨뜨릴 수 있기 때문이다.
15
+ `DesignSystemProvider` 계약은 환경 입력을 해석하고, 해석된 theme에 해당하는 검증된
16
+ `THEMES`, `ACCENTS`, `accentFill` palette를 묶어 renderer에 전달한다.
17
+
18
+ 우선순위는 다음과 같다.
19
+
20
+ ```text
21
+ explicit input → parent provider → renderer가 측정한 system signal → HJM default
22
+ ```
23
+
24
+ React/RN Context와 OS 구독은 계속 제품 renderer가 소유한다. 코어는 값·기본값·병합·검증만
25
+ 소유한다.
26
+
27
+ ### 2. typography도 foundation token만 사용한다
28
+
29
+ 기존 size/line-height 역할에 UI/code font family, weight, letter spacing, numeric style,
30
+ heading scale을 추가한다. recipe 안의 `"600"`, `"700"` 같은 weight 복제는 foundation
31
+ reference로 이관한다. 기존 공개 값은 바꾸지 않는 additive migration이다.
32
+
33
+ ### 3. Showcase route와 renderer evidence를 분리한다
34
+
35
+ 모든 catalog 항목은 문서 route를 갖지만, route가 있다고 renderer가 있는 것은 아니다.
36
+
37
+ ```text
38
+ ContractStory
39
+ ├─ stable/beta + Web 지원 → Web reference renderer
40
+ ├─ planned/deprecated → Contract-only decision page
41
+ └─ Native-only → Web unsupported page
42
+ ```
43
+
44
+ Web renderer registry는 실제 recipe 객체를 참조해야 하며 fallback renderer를 허용하지 않는다.
45
+ Contract-only 화면은 anatomy/defaults/상태 축/behavior/roadmap/승격 근거만 보여주고 완성형
46
+ 컴포넌트 JSX를 렌더링하지 않는다. Native 증거는 소비 앱의 on-device fixture가 제공하기 전에는
47
+ 누락 상태로 남긴다.
48
+
49
+ ### 4. Showcase 자체가 첫 번째 Web token consumer다
50
+
51
+ Storybook decorator는 theme, spacing, radius, typography, glyph, motion, opacity, stroke,
52
+ control, layout, layer, backdrop, shadow를 CSS variable로 번역한다. manager chrome도 같은
53
+ foundation을 사용한다. token boundary verifier는 원시 색·그림자·모션·타입 값의 재유입을
54
+ 막고, 편집용 레이아웃 수치는 selector/property/value/reason이 명시된 예외만 허용한다.
55
+
56
+ Reduce Motion은 모든 animation을 같은 `0.001ms`로 덮지 않는다. renderer가 각
57
+ `motionPreset.reducedMotion`의 `instant | opacity | static` 전략을 번역한다.
58
+
59
+ ### 5. 외부 reference와 HJM 구현 상태는 별도 축이다
60
+
61
+ Ant Design snapshot은 버전과 category/name/lifecycle을 고정한다. 별도 scheduled workflow가
62
+ 최신 npm 버전과 pin의 drift를 알리되, 네트워크 상태 때문에 기본 push 검증을 불안정하게 만들지
63
+ 않는다. 73/73 tracking은 구현 완료 수치로 사용하지 않는다.
64
+
65
+ ## 이번 릴리즈의 완료 게이트
66
+
67
+ - canonical direction/textScale/reducedMotion/theme가 하나의 환경 계약으로 해석된다.
68
+ - Provider 값이 resolved environment와 검증된 color palette를 함께 제공한다.
69
+ - typography foundation과 recipe weight가 단일 출처다.
70
+ - planned story에는 interactive renderer DOM이 없다.
71
+ - Web 지원 stable/beta 항목과 renderer registry가 정확히 일치한다.
72
+ - Native-only 항목이 Web renderer 수치에 포함되지 않는다.
73
+ - Explorer 수치는 catalog status가 아니라 renderer/evidence registry에서 계산한다.
74
+ - manager와 preview가 HJM token을 사용한다.
75
+ - Ant Design reference drift를 독립적으로 점검할 수 있다.
76
+ - Slider/Form/일관성 문서의 알려진 drift가 해소된다.
77
+ - `pnpm check`, Showcase type/test/token/static 검증, Storybook production build가 통과한다.
78
+
79
+ ## 의도적으로 하지 않는 것
80
+
81
+ - Ant Design 외형, prop 이름, runtime dependency 복사
82
+ - 제품 증거 없는 planned → beta/stable 승격
83
+ - `AppProvider`, `BorderBeam`, `Utility`의 가짜 공개 컴포넌트화
84
+ - React/RN renderer를 플랫폼 중립 코어에 포함
85
+ - 제품별 locale, copy, storage, domain color를 공용 토큰에 포함
@@ -0,0 +1,107 @@
1
+ # Flex, Space, Masonry 판정과 Grid 공통 계약
2
+
3
+ ## 제약
4
+
5
+ `docs/architecture.md`가 이미 `Stack`에 대해 이 판정을 내려 뒀다:
6
+
7
+ > Stack은 반복되는 내부 flex를 감싸는 것만으로 제품 의미나 접근성 계약이 생기지
8
+ > 않으므로 실제 paired layout 요구가 나타날 때까지 planned recipe 이상으로
9
+ > 공개하지 않는다.
10
+
11
+ `Flex`·`Space`·`Masonry`에 대한 판정은 뒤집지 않는다. 다만 Grid는 여러
12
+ React/RN renderer가 서로 다른 breakpoint와 fallback을 발명하는 문제가 실제
13
+ 일반화 요구로 확인되어, **콘텐츠 semantic이 아니라 반응형 layout contract**로
14
+ 분리했다. 상세 API와 renderer 경계는 `docs/responsive-grid.md`에 있다. `Layout`과
15
+ `Splitter`는 각각 `docs/layout.md`, `docs/splitter.md`에 남긴다.
16
+
17
+ 판정 기준은 셋이다: (1) 제품 의미가 생기는가 (2) 접근성 계약이 생기는가 (3)
18
+ 플랫폼 번역이 성립하는가(Web과 Native가 각자 다른 기법으로 같은 결과를 내는
19
+ 공유 semantic이 있는가).
20
+
21
+ ## Flex — Stack과 사실상 같은 문제
22
+
23
+ antd `Flex`의 표면(`gap`, `vertical`/방향, `align`, `justify`, `wrap`)은 이미
24
+ `stackRecipe`(`src/component-recipes.ts`)의 `axis`/`gap`/`align`/`justify`/`wrap`과
25
+ 거의 1:1로 겹친다. 이것은 "비슷한 컴포넌트"가 아니라 **같은 문제를 두 번 계약할
26
+ 뻔한 자리**다 — Dropdown이 Menu와 같은 문제였던 것과 같은 종류의 중복
27
+ (`docs/dropdown.md`).
28
+
29
+ 1. 제품 의미: 없다. flex 컨테이너라는 사실 자체가 사용자에게 전달하는 뜻이
30
+ 없다 — Stack과 동일한 판정.
31
+ 2. 접근성 계약: 없다. DOM 순서가 시각 순서와 일치하는 한 스크린 리더는 flex
32
+ 컨테이너 유무를 신경 쓰지 않는다.
33
+ 3. 플랫폼 번역: Web flexbox와 RN flexbox는 애초에 같은 모델이라 "적응"이 필요
34
+ 없다 — 이것도 Stack의 결론과 같다.
35
+
36
+ **결론**: 별도 계약을 만들지 않는다. catalog의 `Flex` row는 리드 판단으로
37
+ `Stack`의 alias로 흡수하거나(Dropdown→Menu와 같은 처리), 최소한 이 문서를
38
+ 연결해 "Stack과 같은 판정"임을 표시한다.
39
+
40
+ ## Space — Stack + 선택적 구분선
41
+
42
+ antd `Space`(alias 후보 `Inline`)는 Stack과 같은 gap 래퍼에 항목 사이 구분선을
43
+ 자동으로 넣는 기능(`split`)이 더해진 것이다. 그런데 구분선은 이미
44
+ `Divider`(beta, `dividerRecipe`)로 존재한다 — Space가 하는 일은 "Stack처럼
45
+ 배치하고 그 사이에 이미 있는 Divider를 자동으로 끼워 넣는다"는 조합 편의이지,
46
+ 새 상태 축이나 새 접근성 개념이 아니다.
47
+
48
+ 1. 제품 의미: 없다 — Stack과 동일.
49
+ 2. 접근성 계약: 없다. 구분선은 `Divider`가 이미 `aria-hidden` 등 자기 계약을
50
+ 갖고 있고, Space가 그 위에 새로 얹을 규칙이 없다.
51
+ 3. 플랫폼 번역: Stack과 동일하게 성립하지 않을 이유가 없지만, 성립 여부를
52
+ 가를 만한 고유 표면 자체가 없다.
53
+
54
+ **결론**: 별도 계약을 만들지 않는다. `Space`는 Flex보다는 구분선 삽입이라는
55
+ 실제 차이가 있어 Stack의 단순 alias로 흡수하자고 권하지는 않는다 — catalog
56
+ row(`aliases: ["Inline"]`)는 이름 자리로 남기고 이 문서를 연결한다.
57
+
58
+ ## Grid — 데이터 semantic이 아닌 공통 responsive layout
59
+
60
+ antd의 24열 Row/Col API나 CSS 전용 `grid-template`를 그대로 공유하지 않는다.
61
+ 대신 실제 공통 축으로 확인된 `WindowClass`, sparse `ResponsiveValue`, 요청 열 수,
62
+ token gap, 최소 열 폭, row-major source order만 계약한다. Web은 CSS Grid로,
63
+ Native는 계산된 열 폭과 flexWrap으로 번역하므로 구현 기법은 달라도 결과 축은 같다.
64
+
65
+ Grid는 여전히 DataTable이 아니다. 표의 행/열 관계, header, sort, selection은
66
+ `docs/data-table.md`가 소유하고 Grid는 임의 child의 semantic을 만들지 않는다.
67
+ Masonry처럼 child를 높이별로 재정렬하는 packing도 포함하지 않는다.
68
+
69
+ ## Masonry — 순수 렌더링 알고리즘, 공유 semantic이 없다
70
+
71
+ Masonry(폭포수형 그리드)는 넷 중 유일하게 실제 구현 난이도가 있다(항목 높이를
72
+ 측정해 가장 짧은 열에 배치). 하지만 판정 기준은 난이도가 아니라 사용자 의미와
73
+ 접근성 계약이다.
74
+
75
+ 1. 제품 의미: 없다. 어떤 배치 알고리즘을 쓰든 콘텐츠의 뜻은 바뀌지 않는다.
76
+ 2. 접근성 계약: 없다. 시각 위치와 무관하게 낭독 순서는 논리적 DOM 순서를
77
+ 따라야 한다는 규칙은 Masonry 전용이 아니라 모든 레이아웃에 적용되는 일반
78
+ 원칙이다.
79
+ 3. 플랫폼 번역: **넷 중 가장 강하게 성립하지 않는다.** Web은 CSS
80
+ `grid-template-rows: masonry`(실험적) 또는 JS 컬럼 패킹을, RN은 순수 JS
81
+ 측정 기반 패킹을 쓴다 — 이름도 파라미터도 공유하지 않는 각 플랫폼의 렌더링
82
+ 기법이다. `docs/virtual-list.md`의 windowing 판정과 정확히 같은 이유로
83
+ 공유할 semantic 자체가 없다.
84
+
85
+ **결론**: 별도 계약을 만들지 않는다. catalog row는 그대로 두고 이 문서를
86
+ 연결한다.
87
+
88
+ ## 뒤집힐 조건
89
+
90
+ 다음 중 하나가 실제로 측정되면 해당 항목만 다시 연다 — 넷을 하나의 판정 세트로
91
+ 묶었다고 해서 항상 함께 뒤집히는 것은 아니다.
92
+
93
+ - **Flex/Space**: Stack이 먼저 `planned recipe`를 넘어 실제 접근성 계약이
94
+ 필요한 vertical slice를 얻는다면(`docs/architecture.md`가 예고한 "실제
95
+ paired layout 요구"), 그 계약이 Flex/Space에도 적용되는지 함께 재검토한다.
96
+ - **Masonry**: Web과 Native 렌더러가 같은 이름의 패킹 파라미터로 검증 가능한
97
+ 공유 semantic을 갖게 되거나(가능성 낮음), 스크린 리더 낭독 순서 문제가
98
+ Masonry에서만 특이하게 발생하는 실측 사례가 나오면 재검토한다.
99
+
100
+ ## 배선 명세 제안 (리드 적용)
101
+
102
+ - `Flex`: `Stack`의 alias로 흡수 권고(Dropdown→Menu 선례) — `{ name: "Stack",
103
+ ..., aliases: ["Flex"] }`로 합치고 별도 `Flex` row 제거. 대안: catalog row를
104
+ 그대로 두고 이 문서만 연결.
105
+ - `Space`, `Masonry`: catalog row를 그대로 두고 이 문서를 참고 링크로 연결한다.
106
+ - `Grid`: `gridRecipe`/responsive resolver를 연결하되, maturity 승격은 Web과 Native
107
+ 양쪽의 viewport·RTL·large-text evidence가 생긴 뒤 별도 gate로 진행한다.
package/docs/layout.md ADDED
@@ -0,0 +1,83 @@
1
+ # Layout contract
2
+
3
+ **문제.** 앱의 지속적인 구조 골격 — 헤더, 사이드바, 본문, 푸터가 어떻게 배치되고,
4
+ 스크린 리더 사용자가 반복되는 내비게이션을 건너뛸 수 있는지. antd `Layout`
5
+ (alias `AppShell`) → HJM `Layout`(`src/component-references.ts`,
6
+ `relationship: "direct"`).
7
+
8
+ **먼저 뺀 것 — 이미 다른 컴포넌트가 소유한다.** 헤더 크롬은 이미
9
+ `TopBar`(native, beta), 푸터 내비게이션은 이미 `BottomNavigation`(adaptive,
10
+ beta)이다. `Layout`이 그 콘텐츠나 상태를 다시 계약하면 두 곳이 같은 것을
11
+ 소유하게 된다 — DataTable이 Pagination/LoadMore를 소유하지 않고 합성하기로 한
12
+ 것과 같은 실수를 피한다. `Layout`은 **header/footer가 있다는 사실**만 알고
13
+ (`hasHeader?`/`hasFooter?`), 그 안의 내용은 모른다.
14
+
15
+ **판정 기준 통과 여부.**
16
+
17
+ - **제품 의미**: 있다. "이 화면이 지금 앱의 상시 골격 안에 있는가, 아니면 모달
18
+ 위에 떠 있는가"는 사용자에게 실제로 다른 뜻이다.
19
+ - **접근성 계약**: 있다 — 그리고 이게 Stack/Grid와 갈리는 지점이다. `main`
20
+ 랜드마크는 **정확히 하나**여야 하고, 헤더나 사이드바처럼 반복되는 내비게이션이
21
+ `main` 앞에 있으면 WCAG 2.4.1(Bypass Blocks)에 따라 skip link가 있어야 한다.
22
+ Web 전용 `validateLayoutWebDescriptor`가 이 규칙을 강제한다 — `hasHeader`나
23
+ `sidebar`가 있는데 `skipLinkLabel`이 없으면 던진다. 공통
24
+ `validateLayoutRegions`는 region/sidebar 구조만 검사하므로, bypass-link 개념이
25
+ 없는 Native에 Web 요구를 강제하지 않는다. 기존 `validateLayoutDescriptor`는
26
+ Web validator의 호환 alias다.
27
+ - **플랫폼 번역**: 성립하지만 비대칭적으로 성립한다. Web은 실제 랜드마크
28
+ 엘리먼트(`<header>`/`<nav>`/`<main>`/`<footer>`)가 있다. Native는 랜드마크
29
+ 개념 자체가 없다 — `accessibilityRole`은 heading/control용이지 페이지 영역용이
30
+ 아니다. 그래서 Native는 순서와 `accessibilityViewIsModal`(오버레이 사이드바일
31
+ 때)로 근사할 뿐, 진짜 랜드마크 parity는 없다. 이 비대칭을 감추지 않고
32
+ `native: { roles: [], states: [], actions: [] }`로 정직하게 비워 뒀다 —
33
+ BottomNavigation이 iOS의 불안정한 tab role에 대해 button+selected fallback을
34
+ 명시한 것과 같은 태도다.
35
+
36
+ **일반화한 계약 — 좁힌 이유.**
37
+
38
+ - 사이드바의 `role`(`navigation` | `complementary`)과 `mode`(`persistent` |
39
+ `overlay`)를 **독립된 축**으로 뒀다. 처음에는 `mode`에서 `role`을 유도하려
40
+ 했다(오버레이면 `complementary`, 상시 노출이면 `navigation`) — 그런데 그건
41
+ 틀린 논리다. `role`은 **내용의 의미**(주 내비게이션인가, 본문과 관련된 보조
42
+ 콘텐츠인가)이고 `mode`는 **표시 방식**(항상 보이는가, 열고 닫는 오버레이인가)이라
43
+ 서로 다른 축이다. 상시 노출된 필터 패널(`complementary` + `persistent`)도,
44
+ 오버레이로 여닫는 주 내비게이션(`navigation` + `overlay`, 좁은 화면의 햄버거
45
+ 메뉴)도 둘 다 실제로 존재하는 조합이다.
46
+ - `mode: "overlay"`일 때 그 열림/닫힘 생명주기는 **`SidePanel`을 그대로
47
+ 쓴다.** `Layout`은 `SheetOpenState`/`SidePanelOpenState` 모양의 새 controlled
48
+ 상태를 만들지 않는다 — `layoutBehavior.controlled`가 빈 배열인 이유다. 두
49
+ 곳이 "열려 있는가"를 각자 소유하면 반드시 갈린다는 것을 DataTable/SidePanel
50
+ 작업에서 이미 확인했다.
51
+ - **넣지 않은 것**: `main` 안의 임의 콘텐츠 배치(그건 Stack/Grid 계약을
52
+ 합성한다), persistent↔overlay 전환 class 자체(공통 breakpoint와
53
+ `ResponsiveValue`는 `docs/responsive-grid.md`를 쓰되 어느 class에서 모드를
54
+ 바꿀지는 제품이 선언한다), 사이드바 리사이즈(그건 [[splitter]]가 이미
55
+ 다루는 문제이고 필요하면 Layout이 Splitter를 합성한다).
56
+
57
+ **HJM 기본값.** Skip link는 평상시 숨어 있다가 키보드 포커스가 닿을 때만
58
+ 보인다(`visibility: "focus-only"`) — identity.md의 "조용한 화면 위에 중요한
59
+ 순간만 선명하게"를 그대로 따른 것으로, 마우스/터치 사용자에게는 매 화면 잡음이고
60
+ 키보드 사용자에게는 페이지 첫 랜드마크다. `main`의 `maxWidth`/좌우 padding은
61
+ 새 숫자를 만들지 않고 `foundations.ts`의 `layout.contentMaxWidth`/
62
+ `layout.pagePadding.regular`를 그대로 쓴다.
63
+
64
+ **플랫폼 번역.** Web: `banner`/`navigation`/`complementary`/`main`/`contentinfo`
65
+ 랜드마크 role. Native: 랜드마크 대응 없음 — 순서와 heading, 오버레이
66
+ 사이드바의 `accessibilityViewIsModal`로 같은 사용자 의도(지금 이 콘텐츠가
67
+ 상시 골격인가 임시로 뜬 것인가)를 다른 방식으로 전달한다.
68
+
69
+ Web의 `footer` slot은 `Layout`이 `<footer>` contentinfo를 정확히 한 번 소유한다.
70
+ `BottomNavigation`은 자체 루트를 이름 있는 `<nav>`로 렌더링하고 contentinfo를
71
+ 만들지 않으므로 `footer={<BottomNavigation ... />}` 합성 결과도 `<footer>` 하나와
72
+ `<nav>` 하나다. Native `Layout`은 같은 source order만 유지하며 skip link를
73
+ 렌더링하거나 요구하지 않는다. `skipLinkLabel` Native prop은 이전 호출부 호환을
74
+ 위해 deprecated 상태로만 남아 있다.
75
+
76
+ **현재 검증과 남은 증거.** 공통 descriptor를 직접 소비하는 first-party Web/RN renderer가
77
+ 추가되어 catalog와 두 surface를 `beta`로 승격했다. Web renderer는 실제
78
+ `header`/`nav|aside`/`main`/`footer` landmark, BottomNavigation과의 단일-landmark 합성,
79
+ skip-link focus 이동, persistent/overlay sidebar 합성을 SSR·browser test로 검증한다.
80
+ Native renderer는 같은 region 순서와 overlay
81
+ adapter를 유지하되 존재하지 않는 landmark role을 만들지 않는 기본 실행 증거를 제공한다.
82
+ 실제 product shell의 브라우저·VoiceOver·TalkBack 검증과 200% 글자 크기 증거는 stable
83
+ 승격 전까지 명시적인 debt로 남는다.
@@ -0,0 +1,123 @@
1
+ # Library reference decisions
2
+
3
+ HJM은 다른 UI 라이브러리의 외형이나 public prop 이름을 복제하지 않습니다. 이 문서는
4
+ 여러 시스템에서 반복해서 검증된 **문제 분해 방식**을 어떤 HJM 계약으로 번역했는지 기록합니다.
5
+ 구현을 추가할 때는 이 표를 그대로 베끼지 않고 `의미 → 공통 계약 → 플랫폼 번역 → 실행 증거`
6
+ 순서로 판단합니다.
7
+
8
+ ## 참고한 시스템과 흡수한 원칙
9
+
10
+ | 시스템 | 공식 자료 | HJM에 흡수하는 원칙 | 흡수하지 않는 것 |
11
+ | --- | --- | --- | --- |
12
+ | React Aria | [Button](https://react-aria.adobe.com/Button), [NumberField](https://react-aria.adobe.com/NumberField), [Slider](https://react-aria.adobe.com/Slider) | 입력 modality와 무관한 action 의미, controlled/uncontrolled 축, locale-aware number semantics, keyboard·focus·ARIA acceptance | React Aria 런타임 의존성, DOM 전용 composition을 Native에 강제 |
13
+ | Radix Primitives | [Introduction](https://www.radix-ui.com/primitives/docs/overview/introduction), [Composition](https://www.radix-ui.com/primitives/docs/guides/composition), [Styling](https://www.radix-ui.com/primitives/docs/guides/styling) | 상태별 data axis, focus/dismiss 책임, compound anatomy, 소비자 element에 prop/ref를 안전하게 전달하는 합성 규칙 | `asChild`를 모든 컴포넌트의 기본 API로 공개, Radix의 DOM 구조 복제 |
14
+ | Chakra UI | [Card](https://chakra-ui.com/docs/components/card), [Slot recipes](https://chakra-ui.com/docs/theming/slot-recipes), [Recipes](https://chakra-ui.com/docs/theming/customization/recipes) | 여러 부분을 가진 컴포넌트의 명시적 slot anatomy, typed size/variant/default recipe | Chakra token 이름, 시각 variant, 스타일 엔진 의존성 |
15
+ | Tamagui | [Button](https://tamagui.dev/ui/button), [Card](https://tamagui.dev/ui/card), [Variants](https://tamagui.dev/docs/core/variants), [Themes](https://tamagui.dev/docs/core/theme) | Web/RN이 같은 semantic intent와 typed variant를 공유하고 각 플랫폼 primitive로 번역, parent size/tone을 slot에 전파 | Tamagui compiler·theme runtime, 무제한 style prop surface |
16
+ | MUI | [Stack](https://mui.com/material-ui/react-stack/), [Button](https://mui.com/material-ui/react-button/) | Stack은 한 축의 간격과 정렬만 책임지고 Grid와 역할을 분리, action hierarchy를 제한된 variant로 표현 | Material 외형, `sx`와 같은 임의 스타일 탈출구를 공용 계약으로 노출 |
17
+ | React Native | [Accessibility](https://reactnative.dev/docs/accessibility), [TextInput](https://reactnative.dev/docs/next/textinput) | adjustable control의 increment/decrement action, accessibility value/state, platform keyboard와 font scaling을 Native acceptance에 포함 | OS별 지원 차이를 Web ARIA와 동일하다고 추정, keyboard type만으로 입력 검증을 대체 |
18
+ | Ant Design | [Components](https://ant.design/components/overview/), [DatePicker](https://ant.design/components/date-picker/) | 넓은 제품 범위를 누락 탐지용 reference inventory로 사용, 입력·data display·navigation의 enterprise gap 확인 | Ant component API·CSS·runtime dependency, coverage 수치를 구현 완료로 해석 |
19
+
20
+ ## Cross-platform parity 원칙
21
+
22
+ `shared`는 같은 픽셀을 그린다는 뜻이 아니라 같은 의미와 위계를 보장한다는 뜻입니다.
23
+ 다음은 Web과 Native에서 동일해야 합니다.
24
+
25
+ - component의 semantic name과 책임
26
+ - tone, size, availability, validation 같은 public axis와 기본값
27
+ - multi-part component의 slot 이름과 콘텐츠 순서
28
+ - controlled/uncontrolled 상태 전이와 callback 의미
29
+ - 접근성 결과: 이름, 역할, 상태, action, 오류·설명 연결
30
+ - recipe가 가리키는 semantic color, spacing, radius, typography token
31
+
32
+ 다음은 플랫폼 adapter에 남길 수 있습니다.
33
+
34
+ - DOM의 `as`, native element attribute, form submission
35
+ - Native의 `style`, `hitSlop`, safe-area, platform accessibility action
36
+ - hover와 focus-visible처럼 해당 플랫폼에만 존재하는 interaction state
37
+ - 같은 intent를 Web popover와 Native modal sheet로 표현하는 adaptive component
38
+
39
+ 플랫폼 전용 escape hatch가 공통 semantic API를 바꾸면 안 됩니다. 예를 들어 Web Button의
40
+ `className`이나 Native Button의 `hitSlop` 때문에 tone/size/content API가 달라지는 것은 허용하지
41
+ 않습니다.
42
+
43
+ ## 정규화한 기본 문법
44
+
45
+ ### 콘텐츠
46
+
47
+ - 가시 콘텐츠는 가능한 한 두 renderer 모두 `children`으로 받습니다.
48
+ - 보이는 label과 접근성 이름이 같으면 renderer가 이름을 재작성하지 않습니다.
49
+ - 별도 접근성 이름이 필요한 icon-only action만 현지화된 label을 필수로 받습니다.
50
+ - 복합 action은 `start`와 `end`처럼 논리 방향의 slot을 사용하고 `left`/`right`를 공통 API에
51
+ 넣지 않습니다.
52
+
53
+ ### variant와 state
54
+
55
+ - variant는 제품 의미를 말합니다. raw color나 CSS/native style 값을 variant로 받지 않습니다.
56
+ - 같은 variant는 같은 emphasis hierarchy를 가집니다. 플랫폼마다 이름만 같은 별도 색 조합을
57
+ 만들지 않습니다.
58
+ - disabled, busy, invalid, selected는 색뿐 아니라 역할·상태·indicator로 드러냅니다.
59
+ - busy/pending action은 중복 activation을 막되 포커스를 제거하지 않습니다. 상태 변화는
60
+ `aria-busy`/Native accessibility state와 progress indicator로 발표합니다.
61
+ - recipe default를 renderer에서 다시 하드코딩하지 않습니다. 공통 resolver나 recipe 값을 직접
62
+ 소비합니다.
63
+
64
+ ### anatomy
65
+
66
+ Card처럼 여러 부분이 있는 컴포넌트는 `root`, `header`, `title`, `description`, `body`, `media`,
67
+ `footer`/`actions`처럼 책임이 있는 slot을 계약에 둡니다. Web만 title prop을 소유하고 Native는
68
+ children-only Surface alias가 되는 구조는 `shared`가 아닙니다.
69
+
70
+ ## 확장 우선순위
71
+
72
+ ### 1. NumberField
73
+
74
+ NumberField를 Slider보다 먼저 구현합니다. NumberField는 일반 폼에 직접 쓰이고, 같은
75
+ `min/max/step` 판단이 다음 Slider의 기반이 됩니다. 최소 acceptance는 다음과 같습니다.
76
+
77
+ - `number | null` value와 입력 중인 text를 구분
78
+ - finite `min < max`, positive step, clamp와 decimal-safe snap
79
+ - controlled/uncontrolled value가 같은 callback 의미를 가짐
80
+ - Web ArrowUp/ArrowDown과 stepper가 같은 resolver를 소비
81
+ - Native increment/decrement accessibility action과 버튼이 같은 resolver를 소비
82
+ - disabled/readOnly/invalid/description/error가 Field와 같은 연결 규칙을 사용
83
+ - stepper의 이름은 renderer가 번역하지 않고 소비자가 현지화된 copy를 제공
84
+ - 입력 중 IME/composition과 유효하지 않은 부분 문자열을 즉시 숫자 `0`으로 바꾸지 않음
85
+
86
+ React Aria는 locale formatting/parsing, 여러 numbering system, IME, mobile keyboard, step/clamp,
87
+ floating-point 보정까지 NumberField의 핵심 문제로 다룹니다. HJM 첫 slice는 기존
88
+ renderer-neutral numeric resolver를 사용하되, 지원하지 않는 locale parsing 기능을 제공한다고
89
+ 주장하지 않습니다. locale-aware parser가 들어오기 전에는 명시된 decimal input grammar만
90
+ 허용하고 문서에 범위를 적습니다.
91
+
92
+ ### 2. Slider
93
+
94
+ Slider는 NumberField의 range resolver를 재사용하되 다음 문제를 별도로 닫은 뒤 승격합니다.
95
+
96
+ - drag 중 change와 interaction 종료 commit callback 분리
97
+ - keyboard 방향, Home/End/PageUp/PageDown과 RTL
98
+ - Native gesture measurement와 최소 touch target
99
+ - formatted value announcement와 visible value cue
100
+ - single thumb를 먼저 검증하고 range slider를 추측으로 열지 않음
101
+
102
+ ### 3. breadth expansion
103
+
104
+ 공통 numeric input 뒤에는 이미 한쪽 renderer가 있는 작은 gap을 먼저 닫습니다. Web의
105
+ Text/Divider/Section/Notice/Progress/Skeleton, Native의 Checkbox/CheckboxGroup/Select/LoadMore/Menu,
106
+ 그리고 양쪽의 Avatar/Spinner가 후보입니다. 다만 public export가 있다는 이유만으로 beta가
107
+ 되지는 않습니다. 공통 API 정규화, canonical default proof, Storybook registration을 모두
108
+ 통과해야 surface maturity를 올립니다.
109
+
110
+ ## 승격 규칙
111
+
112
+ 외부 라이브러리가 기능을 제공한다는 사실은 HJM evidence가 아닙니다. 각 surface는 다음 순서를
113
+ 거칩니다.
114
+
115
+ 1. renderer-neutral recipe와 behavior/resolver
116
+ 2. Web 또는 Native public renderer와 granular export
117
+ 3. 실제 render를 수행하는 canonical default proof
118
+ 4. Storybook registration과 catalog/generated manifest 동기화
119
+ 5. keyboard, RTL, large text, accessibility, device 같은 추가 scenario별 실행 proof
120
+
121
+ 이 순서가 끝나지 않은 구현은 public experimental renderer일 수는 있어도 catalog surface는
122
+ `planned`로 유지합니다. 참고 라이브러리의 문서나 테스트 결과를 HJM scenario 증거로 대신하지
123
+ 않습니다.