@hjmds/design-contracts 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/THIRD_PARTY_NOTICES.md +58 -0
  2. package/dist/affix.d.ts +8 -0
  3. package/dist/affix.d.ts.map +1 -0
  4. package/dist/affix.js +7 -0
  5. package/dist/affix.js.map +1 -0
  6. package/dist/agreement.d.ts +3 -3
  7. package/dist/agreement.js +1 -1
  8. package/dist/agreement.js.map +1 -1
  9. package/dist/asset.d.ts +1 -1
  10. package/dist/asset.js +1 -1
  11. package/dist/asset.js.map +1 -1
  12. package/dist/base-recipes.d.ts +12 -12
  13. package/dist/base-recipes.d.ts.map +1 -1
  14. package/dist/base-recipes.js +15 -14
  15. package/dist/base-recipes.js.map +1 -1
  16. package/dist/behaviors.d.ts +1 -1
  17. package/dist/catalog.d.ts +445 -651
  18. package/dist/catalog.d.ts.map +1 -1
  19. package/dist/catalog.js +149 -101
  20. package/dist/catalog.js.map +1 -1
  21. package/dist/color-picker.d.ts +11 -0
  22. package/dist/color-picker.d.ts.map +1 -0
  23. package/dist/color-picker.js +20 -0
  24. package/dist/color-picker.js.map +1 -0
  25. package/dist/command-palette.d.ts +3 -3
  26. package/dist/component-contracts.d.ts +4 -4
  27. package/dist/component-contracts.d.ts.map +1 -1
  28. package/dist/component-contracts.js +5 -4
  29. package/dist/component-contracts.js.map +1 -1
  30. package/dist/component-definitions.d.ts +1 -9
  31. package/dist/component-definitions.d.ts.map +1 -1
  32. package/dist/component-definitions.js +1 -9
  33. package/dist/component-definitions.js.map +1 -1
  34. package/dist/component-recipes.d.ts +76 -83
  35. package/dist/component-recipes.d.ts.map +1 -1
  36. package/dist/component-recipes.js +29 -49
  37. package/dist/component-recipes.js.map +1 -1
  38. package/dist/component-references.d.ts +10 -25
  39. package/dist/component-references.d.ts.map +1 -1
  40. package/dist/component-references.js +5 -8
  41. package/dist/component-references.js.map +1 -1
  42. package/dist/data-table.d.ts +3 -3
  43. package/dist/data-table.js +1 -1
  44. package/dist/data-table.js.map +1 -1
  45. package/dist/date-picker.d.ts +2 -2
  46. package/dist/icon-button-recipe.d.ts +2 -2
  47. package/dist/icon-button-recipe.js +1 -1
  48. package/dist/icon-button-recipe.js.map +1 -1
  49. package/dist/image.d.ts +2 -2
  50. package/dist/image.js +2 -2
  51. package/dist/image.js.map +1 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/interaction-adapters.d.ts +53 -0
  57. package/dist/interaction-adapters.d.ts.map +1 -0
  58. package/dist/interaction-adapters.js +45 -0
  59. package/dist/interaction-adapters.js.map +1 -0
  60. package/dist/internal/thinking-orb/braid.d.ts +3 -0
  61. package/dist/internal/thinking-orb/braid.d.ts.map +1 -0
  62. package/dist/internal/thinking-orb/braid.js +47 -0
  63. package/dist/internal/thinking-orb/braid.js.map +1 -0
  64. package/dist/internal/thinking-orb/core.d.ts +60 -0
  65. package/dist/internal/thinking-orb/core.d.ts.map +1 -0
  66. package/dist/internal/thinking-orb/core.js +86 -0
  67. package/dist/internal/thinking-orb/core.js.map +1 -0
  68. package/dist/internal/thinking-orb/lattice.d.ts +5 -0
  69. package/dist/internal/thinking-orb/lattice.d.ts.map +1 -0
  70. package/dist/internal/thinking-orb/lattice.js +187 -0
  71. package/dist/internal/thinking-orb/lattice.js.map +1 -0
  72. package/dist/internal/thinking-orb/morph.d.ts +3 -0
  73. package/dist/internal/thinking-orb/morph.d.ts.map +1 -0
  74. package/dist/internal/thinking-orb/morph.js +127 -0
  75. package/dist/internal/thinking-orb/morph.js.map +1 -0
  76. package/dist/internal/thinking-orb/orbits.d.ts +3 -0
  77. package/dist/internal/thinking-orb/orbits.d.ts.map +1 -0
  78. package/dist/internal/thinking-orb/orbits.js +68 -0
  79. package/dist/internal/thinking-orb/orbits.js.map +1 -0
  80. package/dist/internal/thinking-orb/presets.d.ts +21 -0
  81. package/dist/internal/thinking-orb/presets.d.ts.map +1 -0
  82. package/dist/internal/thinking-orb/presets.js +77 -0
  83. package/dist/internal/thinking-orb/presets.js.map +1 -0
  84. package/dist/internal/thinking-orb/profiles.d.ts +8 -0
  85. package/dist/internal/thinking-orb/profiles.d.ts.map +1 -0
  86. package/dist/internal/thinking-orb/profiles.js +158 -0
  87. package/dist/internal/thinking-orb/profiles.js.map +1 -0
  88. package/dist/internal/thinking-orb/registry.d.ts +9 -0
  89. package/dist/internal/thinking-orb/registry.d.ts.map +1 -0
  90. package/dist/internal/thinking-orb/registry.js +27 -0
  91. package/dist/internal/thinking-orb/registry.js.map +1 -0
  92. package/dist/internal/thinking-orb/ribbon.d.ts +3 -0
  93. package/dist/internal/thinking-orb/ribbon.d.ts.map +1 -0
  94. package/dist/internal/thinking-orb/ribbon.js +87 -0
  95. package/dist/internal/thinking-orb/ribbon.js.map +1 -0
  96. package/dist/internal/thinking-orb/types.d.ts +15 -0
  97. package/dist/internal/thinking-orb/types.d.ts.map +1 -0
  98. package/dist/internal/thinking-orb/types.js +4 -0
  99. package/dist/internal/thinking-orb/types.js.map +1 -0
  100. package/dist/internal/thinking-orb/web.d.ts +3 -0
  101. package/dist/internal/thinking-orb/web.d.ts.map +1 -0
  102. package/dist/internal/thinking-orb/web.js +92 -0
  103. package/dist/internal/thinking-orb/web.js.map +1 -0
  104. package/dist/masonry.d.ts +17 -0
  105. package/dist/masonry.d.ts.map +1 -0
  106. package/dist/masonry.js +18 -0
  107. package/dist/masonry.js.map +1 -0
  108. package/dist/menubar.d.ts +3 -3
  109. package/dist/menubar.js +1 -1
  110. package/dist/menubar.js.map +1 -1
  111. package/dist/number-field.d.ts +2 -2
  112. package/dist/otp-field.d.ts +1 -1
  113. package/dist/password-field.d.ts +2 -2
  114. package/dist/popover.d.ts +7 -8
  115. package/dist/popover.d.ts.map +1 -1
  116. package/dist/popover.js +2 -0
  117. package/dist/popover.js.map +1 -1
  118. package/dist/qr-code-recipe.d.ts +7 -0
  119. package/dist/qr-code-recipe.d.ts.map +1 -0
  120. package/dist/qr-code-recipe.js +2 -0
  121. package/dist/qr-code-recipe.js.map +1 -0
  122. package/dist/qr-code.d.ts +16 -0
  123. package/dist/qr-code.d.ts.map +1 -0
  124. package/dist/qr-code.js +37 -0
  125. package/dist/qr-code.js.map +1 -0
  126. package/dist/semantic-colors.d.ts +1 -1
  127. package/dist/semantic-colors.js +1 -1
  128. package/dist/semantic-colors.js.map +1 -1
  129. package/dist/showcase.d.ts +5 -1
  130. package/dist/showcase.d.ts.map +1 -1
  131. package/dist/showcase.js +53 -12
  132. package/dist/showcase.js.map +1 -1
  133. package/dist/sidebar.d.ts +3 -3
  134. package/dist/sidebar.js +1 -1
  135. package/dist/sidebar.js.map +1 -1
  136. package/dist/tag.d.ts +4 -4
  137. package/dist/tag.js +3 -3
  138. package/dist/tag.js.map +1 -1
  139. package/dist/tags-input.d.ts +3 -3
  140. package/dist/tags-input.js +1 -1
  141. package/dist/tags-input.js.map +1 -1
  142. package/dist/text-formats.d.ts +2 -2
  143. package/dist/text-formats.js +2 -2
  144. package/dist/text-formats.js.map +1 -1
  145. package/dist/thinking-orb-recipe.d.ts +15 -0
  146. package/dist/thinking-orb-recipe.d.ts.map +1 -0
  147. package/dist/thinking-orb-recipe.js +9 -0
  148. package/dist/thinking-orb-recipe.js.map +1 -0
  149. package/dist/thinking-orb.d.ts +25 -0
  150. package/dist/thinking-orb.d.ts.map +1 -0
  151. package/dist/thinking-orb.js +40 -0
  152. package/dist/thinking-orb.js.map +1 -0
  153. package/dist/toast-liquid.d.ts +116 -0
  154. package/dist/toast-liquid.d.ts.map +1 -0
  155. package/dist/toast-liquid.js +103 -0
  156. package/dist/toast-liquid.js.map +1 -0
  157. package/dist/toast.d.ts +7 -1
  158. package/dist/toast.d.ts.map +1 -1
  159. package/dist/toast.js +5 -0
  160. package/dist/toast.js.map +1 -1
  161. package/dist/toggle-group.d.ts +2 -2
  162. package/dist/toggle-group.js +3 -3
  163. package/dist/toggle-group.js.map +1 -1
  164. package/dist/transfer-list.d.ts +3 -3
  165. package/dist/transfer-list.js +1 -1
  166. package/dist/transfer-list.js.map +1 -1
  167. package/dist/tree.d.ts +2 -2
  168. package/dist/version.d.ts +1 -1
  169. package/dist/version.js +1 -1
  170. package/dist/version.js.map +1 -1
  171. package/dist/virtual-list.d.ts +13 -0
  172. package/dist/virtual-list.d.ts.map +1 -0
  173. package/dist/virtual-list.js +13 -0
  174. package/dist/virtual-list.js.map +1 -0
  175. package/dist/watermark.d.ts +15 -0
  176. package/dist/watermark.d.ts.map +1 -0
  177. package/dist/watermark.js +11 -0
  178. package/dist/watermark.js.map +1 -0
  179. package/docs/affix.md +27 -63
  180. package/docs/anchor.md +5 -4
  181. package/docs/ant-design-coverage.md +8 -12
  182. package/docs/architecture.md +6 -2
  183. package/docs/authoring-brief.md +3 -2
  184. package/docs/bottom-navigation.md +7 -6
  185. package/docs/breadcrumb.md +5 -2
  186. package/docs/calendar.md +6 -3
  187. package/docs/carousel.md +2 -2
  188. package/docs/cascader.md +11 -0
  189. package/docs/catalog-cleanup.md +7 -0
  190. package/docs/catalog-decision-status.md +8 -3
  191. package/docs/catalog-freeze.json +9 -7
  192. package/docs/collapsible.md +12 -0
  193. package/docs/color-picker.md +30 -72
  194. package/docs/command-palette.md +5 -3
  195. package/docs/confirm-popover.md +5 -0
  196. package/docs/consumer-policy.md +29 -12
  197. package/docs/context-menu.md +12 -2
  198. package/docs/data-table.md +11 -6
  199. package/docs/date-picker.md +6 -4
  200. package/docs/date-range.md +4 -2
  201. package/docs/description-list.md +8 -9
  202. package/docs/dialog.md +39 -0
  203. package/docs/expansion-roadmap.md +11 -7
  204. package/docs/file-picker.md +5 -3
  205. package/docs/form.md +16 -4
  206. package/docs/generated/component-maturity.md +91 -99
  207. package/docs/generated/renderer-evidence.json +2043 -1059
  208. package/docs/generated/renderer-evidence.md +156 -145
  209. package/docs/generated/showcase-manifest.json +476 -625
  210. package/docs/layout.md +8 -9
  211. package/docs/library-gap-analysis.md +1 -1
  212. package/docs/masonry.md +11 -0
  213. package/docs/mentions.md +14 -7
  214. package/docs/optional-adapters.md +126 -0
  215. package/docs/otp-field.md +2 -1
  216. package/docs/pagination.md +4 -1
  217. package/docs/password-field.md +2 -1
  218. package/docs/popover.md +9 -6
  219. package/docs/promotion-candidates.md +2 -1
  220. package/docs/qr-code.md +11 -65
  221. package/docs/rating.md +5 -0
  222. package/docs/react-native-completion.md +1 -1
  223. package/docs/result.md +8 -5
  224. package/docs/screen-chrome.md +2 -1
  225. package/docs/sheet.md +8 -2
  226. package/docs/showcase.md +10 -2
  227. package/docs/side-panel.md +12 -6
  228. package/docs/sidebar.md +3 -0
  229. package/docs/splitter.md +6 -6
  230. package/docs/stable-core.md +92 -0
  231. package/docs/stable-promotion.md +47 -43
  232. package/docs/tags-input.md +5 -2
  233. package/docs/theme-palette.md +27 -0
  234. package/docs/thinking-orb.md +124 -0
  235. package/docs/time-picker.md +5 -0
  236. package/docs/timeline.md +2 -3
  237. package/docs/toast.md +6 -0
  238. package/docs/tooltip.md +2 -1
  239. package/docs/tour.md +14 -8
  240. package/docs/transfer-list.md +24 -21
  241. package/docs/tree-select.md +7 -1
  242. package/docs/tree.md +12 -7
  243. package/docs/upload-item.md +7 -3
  244. package/docs/virtual-list.md +10 -67
  245. package/docs/watermark.md +26 -58
  246. package/package.json +54 -3
  247. package/docs/app-provider.md +0 -50
  248. package/docs/border-beam.md +0 -66
  249. package/docs/chart.md +0 -33
  250. package/docs/utility.md +0 -53
@@ -1,70 +1,13 @@
1
- # VirtualList — 계약을 만들지 않는다
1
+ # VirtualList
2
2
 
3
- ## 제약
3
+ 2026-09-29: 사용자의 미구현 기능 완성 요청으로 고정 높이 행의 가상화 렌더러를 추가했다.
4
+ Web은 보이는 범위와 overscan만 마운트하고, Native는 FlatList에 위임한다. `rowHeight`와
5
+ `height`는 필수이며 가변 높이·페이지 가져오기를 이 API에 섞지 않는다. 콘텐츠와 글자
6
+ 배율에 맞는 행 높이는 호스트가 지정하고, 높이를 확정할 수 없으면 기존 List를 사용한다.
4
7
 
5
- `docs/ant-design-coverage.md`의 lifecycle 절이 이미 선언했다 — Ant Design 6.6.1이 기존
6
- `List`를 deprecated로 두고 `Listy`를 successor로 추가했지만, **HJM은 기존 비가상
7
- `List`와 planned `VirtualList`를 서로 다른 사용 문제로 유지한다.** 이 문서는 그 선언을
8
- 뒤집지 않는다. 두 catalog row(`List` beta, `VirtualList` planned + `aliases: ["Listy"]`)를
9
- 합치자는 이야기가 아니라, **`VirtualList`가 소유할 계약이 실제로 있는지**를 판정한다.
8
+ Web은 ArrowUp/Down/Home/End로 행을 탐색하고 실제 포커스를 옮긴다. 포커스된 항목은
9
+ 스크롤로 가시 범위를 벗어나도 마운트를 유지한다. 필터 결과가 줄면 스크롤 위치를
10
+ 새 범위로 제한한다. 전체 개수와 위치는 현재 전달된 배열 기준으로 표시한다.
10
11
 
11
- ## 판정: 지금은 아무것도 소유하지 않는다
12
-
13
- 가상화가 계약이 되려면 "Web과 Native가 각자 다른 구현 기법으로 같은 결과를 낸다"는
14
- 공통 semantic이 있어야 한다(`docs/architecture.md`의 `adaptive` 정의 그대로). 후보를
15
- 셋으로 나눠 각각 판정했다.
16
-
17
- ### 1. 항목 높이 추정(고정/가변) — 렌더러 기법이다
18
-
19
- Web windowing 라이브러리(react-window/virtua류)와 RN `FlatList`는 각자의 windowing
20
- 파라미터(`itemSize`/`overscan` vs `getItemLayout`/`windowSize`/`initialNumToRender`)를
21
- 갖지만 이름도, 단위도, 의미도 서로 대응하지 않는다. Slider의 `min`/`max`/`step`처럼
22
- 두 플랫폼이 같은 사용자 의미를 공유하는 것이 아니라, 각 플랫폼의 렌더링 성능 엔진이
23
- 내부적으로 요구하는 힌트일 뿐이다 — 공유할 semantic이 없다.
24
-
25
- ### 2. 스크롤 위치 복원 — 가상화 전용 문제가 아니다
26
-
27
- 가상화하지 않은 평범한 긴 목록도 스크롤 위치 복원이 필요하다(뒤로가기 후 원래 보던
28
- 위치로). 이 문제는 "가상화됐는가"와 독립이고, 스크롤을 소유한 컨테이너/렌더러의 몫이다
29
- — `VirtualList`라는 이름 아래 있어야 할 이유가 없다.
30
-
31
- ### 3. 총 개수의 낭독 — 실제로는 이미 있거나, 아직 없다
32
-
33
- 가상화된 목록은 DOM/네이티브 뷰 트리에 일부만 마운트되므로 스크린 리더가 전체 개수를
34
- 잘못 알 수 있다는 우려는 진짜다. 그런데 이 문제를 나눠보면:
35
-
36
- - 전체 데이터가 이미 메모리에 있는 경우(`items.length`를 제품이 이미 안다) — 총
37
- 개수는 이미 알려진 값이고, `aria-setsize`/`aria-posinset`류를 실제 배열 길이로
38
- 채우는 것은 렌더러가 창을 마운트할 때 하는 일이다. 이걸 계산하거나 유도할 판단이
39
- 없다 — Steps/Timeline/Carousel의 `position`/`total`처럼 커서 위치나 유효하지 않은
40
- 조합을 막는 로직이 있는 게 아니라, 그냥 `.length`를 읽는 일이다.
41
- - 전체 데이터가 아직 로딩 중인 경우(페이지네이션) — 이건 `src/load-more.ts`가 이미
42
- 소유한 문제다. `LoadMoreState`는 의도적으로 `totalCount`를 갖지 않는다 — 모르는
43
- 총합을 지어내는 것이 아무것도 말하지 않는 것보다 나쁘기 때문이다(`idle|loading|
44
- loadingMore|error|complete` 중 어느 것도 총 개수를 전제하지 않는다). `VirtualList`가
45
- 이 자리에 총 개수 축을 새로 만들면 LoadMore가 이미 내린 판단과 충돌한다.
46
-
47
- 즉 이 문제는 이미 있는 계약(`LoadMore`) 또는 렌더러가 이미 아는 값(`items.length`) 중
48
- 하나로 항상 환원된다 — `VirtualList`가 따로 소유할 몫이 남지 않는다.
49
-
50
- ## 결론
51
-
52
- 측정된 제품 요구도 없다 — 로드맵·메모리 어디에도 "List+LoadMore로는 렌더링 성능이
53
- 부족했다"는 vertical slice 기록이 없다. 야잘알의 긴 목록(팀 순위, 선수 검색 결과)은
54
- 이미 List/ListRow + LoadMore 조합으로 충분히 설계돼 있다.
55
-
56
- `src/virtual-list.ts`, `test/virtual-list.test.ts`는 만들지 않는다. catalog row(`{ name:
57
- "VirtualList", category: "data-display", platform: "adaptive", status: "planned",
58
- aliases: ["Listy"] }`)는 건드리지 않는다 — 이름 자리를 지우자는 것이 아니라, 지금
59
- 채울 계약이 없다는 것이다.
60
-
61
- ## 뒤집힐 조건
62
-
63
- 다음 중 하나가 실제로 측정되면 이 판정을 다시 연다.
64
-
65
- 1. 실제 제품에서 List+LoadMore로 렌더링 성능(프레임 드랍, 초기 마운트 시간)이 측정
66
- 가능하게 부족한 vertical slice가 나온다.
67
- 2. Web과 Native 렌더러가 **같은 이름의 파라미터**로 반응하는 windowing 설정이 두
68
- 플랫폼 모두에서 검증돼, 진짜 공유 semantic이 있다고 판단된다.
69
- 3. 총 개수를 알 수 없는 채로 가상화해야 하는 화면이 나와, `LoadMore`의 "총합을 지어내지
70
- 않는다"는 원칙과 다른 새 발화 규칙이 필요해진다.
12
+ 진입점: `@hjmds/react/virtual-list`, `@hjmds/react-native/virtual-list`.
13
+ 네트워크 로딩은 기존 LoadMore를 조합한다. Native 재활용·보조기술 스크롤은 FlatList가 소유한다.
package/docs/watermark.md CHANGED
@@ -1,58 +1,26 @@
1
- # Watermark — 계약을 만들지 않는다
2
-
3
- ## 문제로 제기된 것
4
-
5
- Ant Design `Watermark`는 콘텐츠 위에 반복되는 텍스트/이미지를 덮어 출처나 소유권을
6
- 표시합니다. 이 컴포넌트를 검토하며 나온 관찰은, 유일하게 성립할 수 있는 계약이
7
- **"덮개가 본문 낭독과 조작을 방해하면 안 된다"**는 것이고, 두 제품에 실제 수요가
8
- 없다면 만들지 않는다는 조건이었습니다.
9
-
10
- ## 판정: 만들지 않는다
11
-
12
- ### 1. 두 React 기반 제품 어디에도 실사용처가 없다
13
-
14
- `Yajalal RN`(`modules/app-rn/src`)과 `BurnTok`(`apps/web/src`,
15
- `packages/design-system/src`) 양쪽을 `watermark`로 전수 검색한 결과, 시각적으로 콘텐츠
16
- 위에 반복 마크를 덮는 코드는 없었습니다. 유일한 매치는
17
- `modules/app-rn/src/features/notification/state.ts:48`의 "seen watermark"로, 이는 "이
18
- 알림까지는 읽음 처리됐다"는 **읽음 커서**를 뜻하는 이름일 뿐 시각적 워터마크와 무관합니다.
19
- 공유 흐름(`LineupRouteScreen.tsx`, `PlayerCompareScreen.tsx`)도 `Share.share({ message })`로
20
- 텍스트만 공유하고, 이미지를 만들어 공유하는 경로 자체가 아직 없습니다.
21
-
22
- ### 2. 같은 개념이 다른 스택에는 있지만, 이 패키지가 서비스하는 층이 아니다
23
-
24
- Flutter 앱(`modules/app`)에는 실제 시각 워터마크가 있습니다 —
25
- `modules/app/lib/managers/watermark.manager.dart`, `modules/app/lib/common/sharing/
26
- app_name_watermark.dart`(공유 이미지에 앱 이름을 얹는 `AppNameWatermark`),
27
- `player_detail_info.dart`의 등번호 배경 워터마크, `contrast.dart`의
28
- `watermarkAlphaFor`(배경 명도에 따라 0.08/0.16 알파를 고르는 대비 계산)까지 갖췄습니다.
29
- 이는 "워터마크"라는 제품 개념이 회사 안에 이미 존재한다는 증거이지, **이 패키지가
30
- 계약하는 React/RN 스택**(`docs/architecture.md`: "React, React Native, DOM, Expo import는
31
- 코어에 들어오지 않는다")에서 검증된 vertical slice라는 증거는 아닙니다. Flutter 구현은
32
- 같은 렌더러 경계를 공유하지 않으므로 이 판정의 근거로 세지 않았습니다.
33
-
34
- ### 3. 유일하게 유효한 계약은 이미 있는 원칙의 적용일 뿐, 새 상태 축이 아니다
35
-
36
- `Icon`(`src/icon.ts`)과 `Image`(`src/image.ts`)가 이미 decorative/informative
37
- discriminated union으로 "장식용 콘텐츠는 접근성 트리에서 빠지고 상호작용을 가로채지
38
- 않는다"는 원칙을 확립해 두었습니다. 반복 마크가 본문 낭독·조작을 방해하면 안 된다는
39
- 관찰은 이 원칙을 "화면 전체를 덮는 장식 레이어"에 적용한 것일 뿐, Watermark만의 새
40
- 상태 축이나 접근성 개념이 아닙니다. 타일 밀도, 회전 각도, 캔버스/SVG 렌더링 방식은
41
- "이 패키지의 층에 속하는가" 질문에서 렌더러의 몫입니다 — 플랫폼 중립 계약이 검증할 수
42
- 있는 입력이 없습니다.
43
-
44
- ## 만들지 않은 것
45
-
46
- `src/watermark.ts`, `test/watermark.test.ts`는 없습니다.
47
- `componentCatalog`의 `{ name: "Watermark", category: "feedback", platform: "web",
48
- status: "planned" }` 행과 crosswalk의 `Watermark → Watermark` direct 관계
49
- (`src/component-references.ts:123`)는 건드리지 않습니다.
50
-
51
- ## 뒤집힐 조건
52
-
53
- 1. BurnTok 또는 Yajalal RN에 실제 이미지 워터마크 요구(스크린샷 유출 방지, 공유 이미지에
54
- 앱 마크 삽입 등)가 나타나는 vertical slice가 측정된다.
55
- 2. 그 슬라이스에서 "decorative overlay는 낭독·조작을 가로채지 않는다"는 기존 원칙 이상의
56
- 새 규칙(예: 워터마크 밀도가 본문 대비 요건과 충돌해 별도 최소 명도 대비 계산이
57
- 필요한 경우 — Flutter의 `watermarkAlphaFor`가 이미 이런 계산의 선례이니 이식 여부를
58
- 먼저 검토)이 필요해진다.
1
+ # Watermark — Web 반복 텍스트
2
+
3
+ 2026-09-30: 사용자의 구현 요청으로 기존 수요 대기 결정을 대체한다. 외부 이미지·canvas 의존성을
4
+ 추가하지 않고 inline SVG pattern으로 텍스트 타일을 반복한다. 문자열을 HTML이나 SVG 소스로
5
+ 삽입하는 대신 React text node를 사용하여 임의 마크업이 실행되지 않게 한다.
6
+
7
+ `@hjmds/react/watermark`의 `Watermark`는 `text`(문자열 또는 1–3줄 배열)와 `children`을 받는다.
8
+ 각 줄은 1–120글자다. `tileWidth`(기본 240, 최소 80), `tileHeight`(160, 최소 60),
9
+ `rotate`(-22°, -90~90), `opacity`(0.12, 0~1)를 조절할 수 있다. 기본 기하는 계약 recipe가 소유한다.
10
+ 긴 표식은 큰 타일을 지정한다. 텍스트 색상은 HJM textSub 토큰을 따른다.
11
+
12
+ 오버레이는 접근성 트리에서 제외하고 포인터·텍스트 선택을 가로채지 않는다. 중요한 출처/보안
13
+ 고지는 읽을 수 있는 본문으로도 제공한다. 여러 인스턴스는 고유 pattern ID를 사용한다.
14
+ SVG는 인쇄에도 포함되는 DOM 콘텐츠다. 인쇄 설정별 결과는 소비 환경에서 확인한다.
15
+ 이는 장식용 출처 표시이며 스크린샷 방지·삭제 방지·정보 유출 방지 기능이 아니다.
16
+ 이미지 워터마크와 다운로드 파일에 삽입하는 인코딩 기능은 제공하지 않는다.
17
+
18
+ ```tsx
19
+ <Watermark text={[t('document.draft'), projectName]}>
20
+ <DocumentPreview />
21
+ </Watermark>
22
+ ```
23
+
24
+ 계약: [watermark.ts](../src/watermark.ts). UI: [renderer](../../react/src/watermark.tsx).
25
+ 사용 예제: [Web additions](../../../showcase/web/src/patterns/WebAdditions.stories.tsx).
26
+ 검증: [계약 회귀](../test/web-additions.test.ts), [브라우저 동작](../../react/test/web-additions.browser.test.tsx).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hjmds/design-contracts",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -133,6 +133,12 @@
133
133
  "./consumer-policy.md": {
134
134
  "default": "./docs/consumer-policy.md"
135
135
  },
136
+ "./components/thinking-orb": {
137
+ "types": "./dist/thinking-orb.d.ts",
138
+ "react-native": "./dist/thinking-orb.js",
139
+ "import": "./dist/thinking-orb.js",
140
+ "default": "./dist/thinking-orb.js"
141
+ },
136
142
  "./components/agreement": {
137
143
  "types": "./dist/agreement.d.ts",
138
144
  "react-native": "./dist/agreement.js",
@@ -522,21 +528,66 @@
522
528
  "react-native": "./dist/showcase.js",
523
529
  "import": "./dist/showcase.js",
524
530
  "default": "./dist/showcase.js"
531
+ },
532
+ "./components/masonry": {
533
+ "types": "./dist/masonry.d.ts",
534
+ "react-native": "./dist/masonry.js",
535
+ "import": "./dist/masonry.js",
536
+ "default": "./dist/masonry.js"
537
+ },
538
+ "./components/virtual-list": {
539
+ "types": "./dist/virtual-list.d.ts",
540
+ "react-native": "./dist/virtual-list.js",
541
+ "import": "./dist/virtual-list.js",
542
+ "default": "./dist/virtual-list.js"
543
+ },
544
+ "./components/qr-code": {
545
+ "types": "./dist/qr-code.d.ts",
546
+ "react-native": "./dist/qr-code.js",
547
+ "import": "./dist/qr-code.js",
548
+ "default": "./dist/qr-code.js"
549
+ },
550
+ "./components/color-picker": {
551
+ "types": "./dist/color-picker.d.ts",
552
+ "react-native": "./dist/color-picker.js",
553
+ "import": "./dist/color-picker.js",
554
+ "default": "./dist/color-picker.js"
555
+ },
556
+ "./components/watermark": {
557
+ "types": "./dist/watermark.d.ts",
558
+ "react-native": "./dist/watermark.js",
559
+ "import": "./dist/watermark.js",
560
+ "default": "./dist/watermark.js"
561
+ },
562
+ "./components/affix": {
563
+ "types": "./dist/affix.d.ts",
564
+ "react-native": "./dist/affix.js",
565
+ "import": "./dist/affix.js",
566
+ "default": "./dist/affix.js"
567
+ },
568
+ "./components/interaction-adapters": {
569
+ "types": "./dist/interaction-adapters.d.ts",
570
+ "react-native": "./dist/interaction-adapters.js",
571
+ "import": "./dist/interaction-adapters.js",
572
+ "default": "./dist/interaction-adapters.js"
525
573
  }
526
574
  },
527
575
  "files": [
528
576
  "dist",
529
577
  "docs",
530
578
  "README.md",
531
- "LICENSE"
579
+ "LICENSE",
580
+ "THIRD_PARTY_NOTICES.md"
532
581
  ],
533
582
  "engines": {
534
583
  "node": ">=20"
535
584
  },
536
585
  "devDependencies": {
537
586
  "@types/node": "^24.13.3",
587
+ "jsqr": "1.4.0",
538
588
  "typescript": "^5.9.3",
539
- "vitest": "^4.1.11"
589
+ "vitest": "^4.1.11",
590
+ "qrcode-generator": "2.0.4"
540
591
  },
541
592
  "scripts": {
542
593
  "build": "tsc -p tsconfig.build.json",
@@ -1,50 +0,0 @@
1
- # AppProvider — 새 컴포넌트를 만들지 않는다
2
-
3
- ## 문제로 제기된 것
4
-
5
- antd `App`은 `message`/`notification`/`Modal`을 **명령형 훅**(`App.useApp()`)으로
6
- 어디서든 부를 수 있게 context를 심어 주는 컴포넌트다 — 정적 `Modal.confirm()` 같은
7
- 호출이 실제로는 테마·locale context에 접근할 수 있도록 antd가 마련한 배선이다.
8
-
9
- ## 판정: 런타임을 걷어내면 아무것도 남지 않는다
10
-
11
- `App`이 명령형으로 여는 세 가지는 이미 전부 다른 HJM 계약에 배정돼 있다.
12
-
13
- | antd `App`이 여는 것 | HJM 대응 | 상태 |
14
- | --- | --- | --- |
15
- | `message` | `Toast` | 이미 계약, `beta` |
16
- | `notification` | `Toast`(`docs/notification.md`가 이미 alias로 흡수) | 이미 계약 |
17
- | `Modal.confirm`/`Modal.info` 등 | `Dialog`/`AlertDialog`(`decomposed`, crosswalk 확정) | 이미 계약 |
18
-
19
- 즉 `message`/`notification`/`modal`이 **무엇을 보여주는지**는 이미 다 계약돼 있다.
20
- `App`이 실제로 더하는 것은 오직 하나 — "이미 계약된 그 표면을, 컴포넌트 트리 아무 곳에서나
21
- `useApp()`으로 명령형 호출할 수 있게 context에 인스턴스를 심어 둔다"는 **배선**이다. 그건
22
- 정의상 React Context + 특정 프레임워크의 훅 API이고, 이 패키지는 React도 RN도 import하지
23
- 않는다(`docs/architecture.md`). 새로운 상태 축도, 새로운 접근성 개념도, 새로운 시각
24
- recipe도 없다 — Toast/Dialog/AlertDialog가 이미 가진 것 이상으로 계약할 것이 없다.
25
-
26
- `DesignSystemProvider`(`docs/design-system-provider.md`)와 비교하면 차이가 분명하다:
27
- 그쪽은 런타임을 걷어내도 "테마·방향·배율·모션 선호"라는 **값 타입**이 남았다. `App`은
28
- 걷어내면 **값 타입조차 남지 않는다** — 남는 셋(Toast/Dialog/AlertDialog)이 이미 각자의
29
- 파일에서 완결된 계약이기 때문이다.
30
-
31
- ## 결론
32
-
33
- `src/app-provider.ts`, `test/app-provider.test.ts`는 만들지 않는다. 제품이 "어디서든
34
- Toast/AlertDialog를 부르고 싶다"는 요구를 실제로 갖게 되면, 그건 새 HJM 계약이 아니라
35
- **각 제품 renderer가 자기 프레임워크(React Context, RN 동등물)로 만드는 명령형 wrapper**
36
- 다 — Toast의 `createToastStore`/`createToastSession`이 이미 큐·타이머·중복 처리를
37
- 소유하고 있으므로 renderer는 그 세션에 접근하는 hook만 얹으면 된다.
38
-
39
- ## 판정이 뒤집힐 조건
40
-
41
- Toast/Dialog/AlertDialog 중 무엇으로도 표현할 수 없는 넷째 표면이 `App`에 새로 필요하다는
42
- 것이 확인되면(예: 화면 밖 push나 시스템 알림함처럼 `docs/notification.md`가 이미 배제
43
- 조건으로 남긴 것), 그때는 그 표면부터 별도로 계약하고 `App`의 배선 문제는 그 이후에
44
- 다시 본다.
45
-
46
- ## 배선 명세 (리드 참고)
47
-
48
- catalog의 `{ name: "AppProvider", category: "provider", platform: "adaptive", status:
49
- "planned", aliases: ["App"] }`(`src/catalog.ts:124`)는 그대로 둔다 — recipe/behavior가
50
- 없었고 지금도 없다. crosswalk의 `App → AppProvider`(`adapted`)도 바꿀 필요 없다.
@@ -1,66 +0,0 @@
1
- # BorderBeam — 새 컴포넌트를 만들지 않는다
2
-
3
- ## 정정
4
-
5
- 이 문서의 이전 판은 `BorderBeam`이 Ant Design 컴포넌트가 아니라 Magic UI/Aceternity
6
- 계열의 장식 컴포넌트가 crosswalk에 잘못 섞여 들어온 것이라고 주장했다. **그 주장은
7
- 사실이 아니었다** — antd의 실제 Components Overview를 직접 확인하지 않고 일반
8
- 웹검색만으로 판단한 결과였고, 검색 결과가 더 대중적인 Magic UI/Aceternity 쪽으로
9
- 쏠려 있어 antd가 별도로 같은 이름의 컴포넌트를 추가했을 가능성을 놓쳤다.
10
-
11
- 리드가 공식 Overview를 직접 확인해 정정했다: `BorderBeam`은 antd "Other" 섹션에
12
- 실재하는 컴포넌트다. `Watermark`도 crosswalk에서 빠지지 않았다 —
13
- `src/component-references.ts:123`에 `category: "feedback"`으로 이미 정확히 잡혀
14
- 있다(antd에서 Watermark는 Other가 아니라 Feedback 섹션이다). 섹션별 수(general 4 /
15
- layout 7 / navigation 7 / data-entry 18 / data-display 21 / feedback 11 / other 5 =
16
- 73)도 저장소 테스트의 단정과 전부 일치한다. `component-references.ts:127`의
17
- `BorderBeam` 행은 **정확하다** — 제거하지 않는다.
18
-
19
- ## 판정: 그럼에도 만들지 않는다
20
-
21
- crosswalk이 맞다는 것과 이 컴포넌트를 지금 만들 것인가는 별개 질문이다. `BorderBeam`이
22
- 실제로 antd에 있다 해도, 그 실체 — 사용자 행동에 반응하지 않는 **상시 반복 이동
23
- 애니메이션**(컨테이너 테두리를 따라 빛줄기가 도는 효과) — 은 `docs/identity.md`와
24
- 정면으로 충돌한다.
25
-
26
- - 첫 문장부터 "HJM은 장식으로 브랜드를 증명하지 않습니다."
27
- - Motion 원칙: "Reduce Motion에서는 이동과 반복을 제거하고 즉시 전환 또는 짧은 opacity로
28
- 대체합니다", "bounce와 spring은 공간 관계를 설명할 때만 사용합니다."
29
- - HJM답지 않은 패턴: "브랜드색을 장식 배경처럼 넓게 사용."
30
-
31
- 두 질문으로 검증했다:
32
-
33
- 1. **Reduce Motion에서 무엇이 남는가?** 이동 자체가 이 컴포넌트의 전부이므로, 이동을
34
- 제거하면 정적인 테두리 선(또는 아무것도) 만 남는다 — "즉시 전환"으로 대체할 상태
35
- 변화가 애초에 없다(열림/닫힘/포커스 같은 원인이 없다).
36
- 2. **장식이 없어도 화면이 같은 뜻을 전하는가?** 그렇다 — 이 컴포넌트는 어떤 상태·값·
37
- 선택도 표현하지 않는다. 있으나 없으나 화면이 말하는 내용은 똑같고, 차이는 순전히
38
- "화려함"뿐이다.
39
-
40
- 두 질문 모두 "이 장식은 정보를 나르지 않는다"로 귀결됐다 — identity가 정확히 배제하는
41
- 자리(장식으로 증명하는 브랜드, 원인 없는 반복 모션)다. 이 세 근거는 crosswalk 출처
42
- 문제와 무관하게 그대로 성립한다.
43
-
44
- ## 결론
45
-
46
- `src/border-beam.ts`, `test/border-beam.test.ts`는 만들지 않는다.
47
-
48
- ## 판정이 뒤집힐 조건
49
-
50
- BurnTok/Yajalal 중 하나가 실제로 "강조해야 하는 순간"(예: 실시간 방송 중임을 알리는
51
- 라이브 표시, 당첨/축하 모멘트)에 은은한 강조 테두리가 필요하다고 판단하면, 그건
52
- `BorderBeam`(상시 반복 장식)이 아니라 그 순간에 한정된 **의미 있는 강조 recipe**(예:
53
- `feedback.attention` 톤의 짧은 1회성 pulse, Toast의 `priority: high`처럼 특정 상태에만
54
- 묶인 것)로 다시 계약해야 한다.
55
-
56
- ## 배선 명세 (리드 적용)
57
-
58
- `src/component-references.ts:127`의 `BorderBeam` crosswalk 행은 **정확하므로 바꾸지
59
- 않는다.** `src/catalog.ts`의 `{ name: "BorderBeam", category: "utility", platform:
60
- "web", status: "planned" }` 행만 대상이다 — 이 행은 "만들 계획"을 뜻하는 `planned`인데
61
- 실제로는 "만들지 않기로 확정"이라 상태가 거짓말을 하고 있다. 이 불일치를 어떻게
62
- 표현할지는 `docs/catalog-decision-status.md`에서 별도로 다룬다.
63
-
64
- ## 출처
65
-
66
- - [Ant Design — Components Overview](https://ant.design/components/overview/)
package/docs/chart.md DELETED
@@ -1,33 +0,0 @@
1
- # Chart — 렌더러를 만들지 않는다
2
-
3
- **결정.** 막대·선·원 차트 렌더러를 HJM에 두지 않는다. 대신 **색·축·격자·범례 토큰**만
4
- `dataviz` 계약으로 고정하고, 그리는 일은 제품이 고른 라이브러리(Recharts·victory-native·
5
- d3 등)에 맡긴다.
6
-
7
- **왜.** 차트를 하나 그리기 시작하면 축 눈금, 툴팁, 애니메이션, 반응형 재계산, 키보드
8
- 탐색, 데이터 테이블 대체 표현까지 전부 따라온다. 그 전부를 이 패키지가 두 표면에서
9
- 유지하는 비용이 제품이 라이브러리 하나를 붙이는 비용보다 크다. 게다가 반쯤 만든 차트는
10
- 제품이 **우리 차트와 자기 차트 두 벌**을 갖게 만든다.
11
-
12
- **실제로 어긋난 것은 색이었다.** Spint 열지도와 Yajalal 통계가 각자 색을 골랐고, 같은
13
- 포트폴리오의 두 화면이 다른 파랑을 썼다. 그건 렌더러가 없어서가 아니라 **팔레트가 없어서**
14
- 생긴 문제다. 그래서 그 자리만 계약으로 막는다.
15
-
16
- ## 계약이 정하는 것
17
-
18
- - `datavizSeriesPalette` — light/dark 각 8색의 계열 팔레트. 상태색(info/success/warning/
19
- attention)은 **쓰지 않는다**: 뜻을 가진 색이라 "3번 계열"에 쓰면 사용자가 경고로 읽는다.
20
- - `resolveDatavizSeriesColor(theme, index)` — 8을 넘으면 **순환**한다. 색을 자동 생성하지
21
- 않는다. 생성색은 대비를 보장할 수 없고, 9번째 계열이 필요한 화면은 대개 표가 맞다.
22
- - `datavizChromeTokens` — 축선·축라벨·격자·범례·툴팁은 본문 토큰(`border`·`textMuted`·
23
- `textBody`·`surface`)을 그대로 쓴다. 차트 전용 회색을 따로 두면 같은 화면의 표·캡션과
24
- 어긋난다.
25
- - `datavizRedundantEncodings` / `validateDatavizEncoding` — 색만으로 계열을 구분하지
26
- 않는다(WCAG 1.4.1). 직접 라벨·패턴·마커 모양 중 **하나 이상**을 색과 함께 쓴다. 색각
27
- 이상과 흑백 인쇄에서 차트가 남는 조건이다.
28
-
29
- ## 계약이 정하지 않는 것
30
-
31
- 축 스케일, 보간, 툴팁 동작, 데이터 정렬, 빈 상태 문구. 전부 제품과 라이브러리의 몫이다.
32
- 이 결정을 뒤집으려면 "제품 두 곳 이상이 같은 차트 종류를 필요로 하고, 라이브러리 위임으로
33
- 해결되지 않는 접근성 문제가 실측으로 확인됐다"가 먼저 있어야 한다.
package/docs/utility.md DELETED
@@ -1,53 +0,0 @@
1
- # Utility — 새 컴포넌트를 만들지 않는다
2
-
3
- ## 이름이 이미 경고였다
4
-
5
- "유틸"은 컴포넌트가 아니다. 먼저 antd의 실제 `Util` 페이지가 무엇인지 확인했다(공식
6
- 문서 확인, 아래 출처). antd `Util`은 UI 컴포넌트가 아니라 **`theme.useToken()` 훅으로
7
- 현재 테마의 design token 값을 자신의 커스텀 컴포넌트에서 읽는 방법**을 설명하는 문서
8
- 페이지다.
9
-
10
- ## 판정: 이 패키지 자체가 이미 그 역할이다
11
-
12
- `useToken()`이 제공하는 것 — 현재 테마의 색·간격·타이포 값을 코드에서 직접 읽는 것 —
13
- 은 이 저장소에서는 **훅이 아니라 이미 export된 상수**로 존재한다: `spacing`, `radius`,
14
- `typography`, `glyph`, `stroke`, `semanticColors`, `THEMES`(`foundations.ts`,
15
- `semantic-colors.ts`, `colors.ts`). antd는 이 값들을 Context에 넣고 훅으로 꺼내야 하지만
16
- (런타임 테마 스위칭이 Context 기반이므로), 이 패키지는 애초에 런타임 의존성이 없어 그
17
- 값들을 정적 module import로 바로 쓴다 — **"컴포넌트에서 토큰 값을 읽는 방법"이라는 antd
18
- `Util` 페이지의 문제 자체가 이 패키지 구조에서는 발생하지 않는다.**
19
-
20
- 즉 `Utility`라는 이름 아래 모일 실체가 없다:
21
-
22
- - 새 값 타입 없음 — 토큰은 이미 `foundations.ts`/`semantic-colors.ts`/`colors.ts`에 있다.
23
- - 새 접근 패턴 없음 — `import { spacing } from "@hjmds/design-contracts"`가 이미 `useToken()`이
24
- 하는 일과 같은 결과를 낸다(테마별 실제 색 resolve는 `resolveColorReference`가 이미
25
- 한다).
26
- - 새 접근성·상태 축 없음 — 컴포넌트가 아니므로 애초에 대상이 아니다.
27
-
28
- `ContextPanel`(`docs/context-panel.md`)과 같은 자리다 — crosswalk에 실체가 없는 이름이
29
- 목록에 먼저 올라간 경우.
30
-
31
- ## 결론
32
-
33
- `src/utility.ts`, `test/utility.test.ts`는 만들지 않는다.
34
-
35
- ## 판정이 뒤집힐 조건
36
-
37
- antd `Util` 페이지가 이후 실제 유틸리티 함수(단순 토큰 읽기를 넘어서는, 예를 들어 이
38
- 패키지가 아직 갖지 않은 계산 헬퍼)를 추가하고 그중 플랫폼 중립적으로 유용한 것이
39
- 확인되면, 그건 `Utility`라는 이름의 새 컴포넌트가 아니라 **그 헬퍼가 속한 기존 모듈**
40
- (예: 숫자 관련이면 `number-field.ts`의 `clampToRange`류)에 함수로 추가하는 쪽을 권한다.
41
-
42
- ## 배선 명세 제안 (리드 적용)
43
-
44
- `ContextPanel`과 같은 방식을 권한다.
45
-
46
- **(a) 권고: catalog에서 제거.** `src/catalog.ts`의 `{ name: "Utility", category:
47
- "utility", platform: "web", status: "planned", aliases: ["Util"] }`(`src/catalog.ts:127`)
48
- 행을 지운다. `component-definitions.ts`의 예약 ID는 무해하니 남겨도 된다.
49
-
50
- **(b) 대안: catalog row 유지 + 이 문서만 링크.**
51
-
52
- 이 저작자 판단은 (a)다 — 대응할 실체가 없는 `planned` 행이 "곧 만들 컴포넌트"로
53
- 오인되는 비용이 검색 편의보다 크다고 봤다(`ContextPanel`과 같은 이유).