@teamofsilicons/silicon-ui 0.0.0-stage → 0.2.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 (354) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +81 -2
  3. package/check.mjs +236 -0
  4. package/components/accordion/accordion.d.ts +13 -0
  5. package/components/accordion/accordion.js +35 -0
  6. package/components/accordion/accordion.module.css +15 -0
  7. package/components/action-button/action-button.d.ts +11 -0
  8. package/components/action-button/action-button.js +57 -0
  9. package/components/action-button/action-button.module.css +15 -0
  10. package/components/activity-heatmap/activity-heatmap.d.ts +37 -0
  11. package/components/activity-heatmap/activity-heatmap.js +303 -0
  12. package/components/activity-heatmap/activity-heatmap.module.css +83 -0
  13. package/components/alert/alert.d.ts +13 -0
  14. package/components/alert/alert.js +79 -0
  15. package/components/alert/alert.module.css +15 -0
  16. package/components/animated-counter/animated-counter.d.ts +13 -0
  17. package/components/animated-counter/animated-counter.js +73 -0
  18. package/components/animated-counter/animated-counter.module.css +12 -0
  19. package/components/announcement-bar/announcement-bar.d.ts +53 -0
  20. package/components/announcement-bar/announcement-bar.js +187 -0
  21. package/components/announcement-bar/announcement-bar.module.css +60 -0
  22. package/components/avatar/avatar.d.ts +8 -0
  23. package/components/avatar/avatar.js +18 -0
  24. package/components/avatar/avatar.module.css +60 -0
  25. package/components/avatar-group/avatar-group.d.ts +13 -0
  26. package/components/avatar-group/avatar-group.js +24 -0
  27. package/components/avatar-group/avatar-group.module.css +27 -0
  28. package/components/badge/badge.d.ts +10 -0
  29. package/components/badge/badge.js +66 -0
  30. package/components/badge/badge.module.css +52 -0
  31. package/components/bar-chart/bar-chart.d.ts +32 -0
  32. package/components/bar-chart/bar-chart.js +327 -0
  33. package/components/bar-chart/bar-chart.module.css +49 -0
  34. package/components/billing-toggle/billing-toggle.d.ts +35 -0
  35. package/components/billing-toggle/billing-toggle.js +83 -0
  36. package/components/billing-toggle/billing-toggle.module.css +139 -0
  37. package/components/bottom-sheet/bottom-sheet.d.ts +27 -0
  38. package/components/bottom-sheet/bottom-sheet.js +386 -0
  39. package/components/bottom-sheet/bottom-sheet.module.css +69 -0
  40. package/components/breadcrumb/breadcrumb.d.ts +13 -0
  41. package/components/breadcrumb/breadcrumb.js +19 -0
  42. package/components/breadcrumb/breadcrumb.module.css +12 -0
  43. package/components/brush-chart/brush-chart.d.ts +45 -0
  44. package/components/brush-chart/brush-chart.js +426 -0
  45. package/components/brush-chart/brush-chart.module.css +69 -0
  46. package/components/button/button.d.ts +10 -0
  47. package/components/button/button.js +97 -0
  48. package/components/button/button.module.css +39 -0
  49. package/components/button-group/button-group.d.ts +59 -0
  50. package/components/button-group/button-group.js +280 -0
  51. package/components/button-group/button-group.module.css +87 -0
  52. package/components/button-link/button-link.d.ts +24 -0
  53. package/components/button-link/button-link.js +11 -0
  54. package/components/button-link/button-link.module.css +7 -0
  55. package/components/calendar/calendar.d.ts +22 -0
  56. package/components/calendar/calendar.js +253 -0
  57. package/components/calendar/calendar.module.css +51 -0
  58. package/components/card/card.d.ts +19 -0
  59. package/components/card/card.js +108 -0
  60. package/components/card/card.module.css +51 -0
  61. package/components/card-stack/card-stack.d.ts +34 -0
  62. package/components/card-stack/card-stack.js +291 -0
  63. package/components/card-stack/card-stack.module.css +63 -0
  64. package/components/carousel/carousel.d.ts +28 -0
  65. package/components/carousel/carousel.js +332 -0
  66. package/components/carousel/carousel.module.css +42 -0
  67. package/components/chat-thread/chat-thread.d.ts +135 -0
  68. package/components/chat-thread/chat-thread.js +406 -0
  69. package/components/chat-thread/chat-thread.module.css +242 -0
  70. package/components/checkbox/checkbox.d.ts +7 -0
  71. package/components/checkbox/checkbox.js +23 -0
  72. package/components/checkbox/checkbox.module.css +15 -0
  73. package/components/chip-group/chip-group.d.ts +24 -0
  74. package/components/chip-group/chip-group.js +196 -0
  75. package/components/chip-group/chip-group.module.css +38 -0
  76. package/components/code-block/code-block.d.ts +11 -0
  77. package/components/code-block/code-block.js +133 -0
  78. package/components/code-block/code-block.module.css +43 -0
  79. package/components/color-picker/color-picker.d.ts +40 -0
  80. package/components/color-picker/color-picker.js +449 -0
  81. package/components/color-picker/color-picker.module.css +111 -0
  82. package/components/combobox/combobox.d.ts +19 -0
  83. package/components/combobox/combobox.js +167 -0
  84. package/components/combobox/combobox.module.css +28 -0
  85. package/components/command-palette/command-palette.d.ts +20 -0
  86. package/components/command-palette/command-palette.js +151 -0
  87. package/components/command-palette/command-palette.module.css +94 -0
  88. package/components/comment-thread/comment-thread.d.ts +120 -0
  89. package/components/comment-thread/comment-thread.js +343 -0
  90. package/components/comment-thread/comment-thread.module.css +195 -0
  91. package/components/confirm-morph/confirm-morph.d.ts +65 -0
  92. package/components/confirm-morph/confirm-morph.js +355 -0
  93. package/components/confirm-morph/confirm-morph.module.css +146 -0
  94. package/components/context-menu/context-menu.d.ts +18 -0
  95. package/components/context-menu/context-menu.js +148 -0
  96. package/components/context-menu/context-menu.module.css +17 -0
  97. package/components/copy-button/copy-button.d.ts +10 -0
  98. package/components/copy-button/copy-button.js +69 -0
  99. package/components/copy-button/copy-button.module.css +21 -0
  100. package/components/countdown/countdown.d.ts +31 -0
  101. package/components/countdown/countdown.js +177 -0
  102. package/components/countdown/countdown.module.css +49 -0
  103. package/components/date-picker/date-picker.d.ts +17 -0
  104. package/components/date-picker/date-picker.js +100 -0
  105. package/components/date-picker/date-picker.module.css +34 -0
  106. package/components/date-range-picker/date-range-picker.d.ts +44 -0
  107. package/components/date-range-picker/date-range-picker.js +534 -0
  108. package/components/date-range-picker/date-range-picker.module.css +214 -0
  109. package/components/dialog/dialog.d.ts +11 -0
  110. package/components/dialog/dialog.js +52 -0
  111. package/components/dialog/dialog.module.css +36 -0
  112. package/components/donut-chart/donut-chart.d.ts +46 -0
  113. package/components/donut-chart/donut-chart.js +382 -0
  114. package/components/donut-chart/donut-chart.module.css +48 -0
  115. package/components/drawer/drawer.d.ts +20 -0
  116. package/components/drawer/drawer.js +111 -0
  117. package/components/drawer/drawer.module.css +97 -0
  118. package/components/dropdown-menu/dropdown-menu.d.ts +16 -0
  119. package/components/dropdown-menu/dropdown-menu.js +39 -0
  120. package/components/dropdown-menu/dropdown-menu.module.css +38 -0
  121. package/components/elastic-slider/elastic-slider.d.ts +39 -0
  122. package/components/elastic-slider/elastic-slider.js +261 -0
  123. package/components/elastic-slider/elastic-slider.module.css +40 -0
  124. package/components/empty-state/empty-state.d.ts +12 -0
  125. package/components/empty-state/empty-state.js +69 -0
  126. package/components/empty-state/empty-state.module.css +13 -0
  127. package/components/expandable-card/expandable-card.d.ts +13 -0
  128. package/components/expandable-card/expandable-card.js +89 -0
  129. package/components/expandable-card/expandable-card.module.css +22 -0
  130. package/components/expanding-button-group/expanding-button-group.d.ts +41 -0
  131. package/components/expanding-button-group/expanding-button-group.js +258 -0
  132. package/components/expanding-button-group/expanding-button-group.module.css +44 -0
  133. package/components/expanding-search/expanding-search.d.ts +38 -0
  134. package/components/expanding-search/expanding-search.js +242 -0
  135. package/components/expanding-search/expanding-search.module.css +52 -0
  136. package/components/file-dropzone/file-dropzone.d.ts +44 -0
  137. package/components/file-dropzone/file-dropzone.js +379 -0
  138. package/components/file-dropzone/file-dropzone.module.css +115 -0
  139. package/components/file-upload/file-upload.d.ts +22 -0
  140. package/components/file-upload/file-upload.js +192 -0
  141. package/components/file-upload/file-upload.module.css +49 -0
  142. package/components/filter-toolbar/filter-toolbar.d.ts +48 -0
  143. package/components/filter-toolbar/filter-toolbar.js +446 -0
  144. package/components/filter-toolbar/filter-toolbar.module.css +70 -0
  145. package/components/floating-button-group/floating-button-group.d.ts +51 -0
  146. package/components/floating-button-group/floating-button-group.js +215 -0
  147. package/components/floating-button-group/floating-button-group.module.css +74 -0
  148. package/components/gauge/gauge.d.ts +18 -0
  149. package/components/gauge/gauge.js +101 -0
  150. package/components/gauge/gauge.module.css +22 -0
  151. package/components/hold-to-confirm/hold-to-confirm.d.ts +25 -0
  152. package/components/hold-to-confirm/hold-to-confirm.js +165 -0
  153. package/components/hold-to-confirm/hold-to-confirm.module.css +20 -0
  154. package/components/hover-card/hover-card.d.ts +38 -0
  155. package/components/hover-card/hover-card.js +145 -0
  156. package/components/hover-card/hover-card.module.css +20 -0
  157. package/components/image-compare/image-compare.d.ts +27 -0
  158. package/components/image-compare/image-compare.js +267 -0
  159. package/components/image-compare/image-compare.module.css +25 -0
  160. package/components/in-view-title/in-view-title.d.ts +17 -0
  161. package/components/in-view-title/in-view-title.js +70 -0
  162. package/components/in-view-title/in-view-title.module.css +13 -0
  163. package/components/inline-edit/inline-edit.d.ts +27 -0
  164. package/components/inline-edit/inline-edit.js +315 -0
  165. package/components/inline-edit/inline-edit.module.css +73 -0
  166. package/components/input/input.d.ts +7 -0
  167. package/components/input/input.js +60 -0
  168. package/components/input/input.module.css +22 -0
  169. package/components/json-viewer/json-viewer.d.ts +48 -0
  170. package/components/json-viewer/json-viewer.js +463 -0
  171. package/components/json-viewer/json-viewer.module.css +92 -0
  172. package/components/line-chart/line-chart.d.ts +58 -0
  173. package/components/line-chart/line-chart.js +383 -0
  174. package/components/line-chart/line-chart.module.css +60 -0
  175. package/components/mention-input/mention-input.d.ts +72 -0
  176. package/components/mention-input/mention-input.js +417 -0
  177. package/components/mention-input/mention-input.module.css +134 -0
  178. package/components/metric-card/metric-card.d.ts +8 -0
  179. package/components/metric-card/metric-card.js +44 -0
  180. package/components/metric-card/metric-card.module.css +18 -0
  181. package/components/money-input/money-input.d.ts +48 -0
  182. package/components/money-input/money-input.js +503 -0
  183. package/components/money-input/money-input.module.css +77 -0
  184. package/components/morph-select/morph-select.d.ts +50 -0
  185. package/components/morph-select/morph-select.js +395 -0
  186. package/components/morph-select/morph-select.module.css +88 -0
  187. package/components/multi-select/multi-select.d.ts +18 -0
  188. package/components/multi-select/multi-select.js +105 -0
  189. package/components/multi-select/multi-select.module.css +37 -0
  190. package/components/notification-center/notification-center.d.ts +24 -0
  191. package/components/notification-center/notification-center.js +146 -0
  192. package/components/notification-center/notification-center.module.css +79 -0
  193. package/components/number-field/number-field.d.ts +41 -0
  194. package/components/number-field/number-field.js +455 -0
  195. package/components/number-field/number-field.module.css +71 -0
  196. package/components/otp-input/otp-input.d.ts +13 -0
  197. package/components/otp-input/otp-input.js +129 -0
  198. package/components/otp-input/otp-input.module.css +28 -0
  199. package/components/pagination/pagination.d.ts +7 -0
  200. package/components/pagination/pagination.js +60 -0
  201. package/components/pagination/pagination.module.css +16 -0
  202. package/components/password-field/password-field.d.ts +14 -0
  203. package/components/password-field/password-field.js +51 -0
  204. package/components/password-field/password-field.module.css +27 -0
  205. package/components/password-strength/password-strength.d.ts +107 -0
  206. package/components/password-strength/password-strength.js +161 -0
  207. package/components/password-strength/password-strength.module.css +57 -0
  208. package/components/phone-input/phone-input.d.ts +73 -0
  209. package/components/phone-input/phone-input.js +612 -0
  210. package/components/phone-input/phone-input.module.css +98 -0
  211. package/components/popover/popover.d.ts +6 -0
  212. package/components/popover/popover.js +19 -0
  213. package/components/popover/popover.module.css +32 -0
  214. package/components/progress/progress.d.ts +8 -0
  215. package/components/progress/progress.js +42 -0
  216. package/components/progress/progress.module.css +1 -0
  217. package/components/radio-cards/radio-cards.d.ts +35 -0
  218. package/components/radio-cards/radio-cards.js +110 -0
  219. package/components/radio-cards/radio-cards.module.css +84 -0
  220. package/components/radio-group/radio-group.d.ts +13 -0
  221. package/components/radio-group/radio-group.js +55 -0
  222. package/components/radio-group/radio-group.module.css +30 -0
  223. package/components/resizable-panels/resizable-panels.d.ts +37 -0
  224. package/components/resizable-panels/resizable-panels.js +338 -0
  225. package/components/resizable-panels/resizable-panels.module.css +40 -0
  226. package/components/rich-text-editor/rich-text-editor.d.ts +55 -0
  227. package/components/rich-text-editor/rich-text-editor.js +1733 -0
  228. package/components/rich-text-editor/rich-text-editor.module.css +201 -0
  229. package/components/ridgeline/ridgeline.d.ts +37 -0
  230. package/components/ridgeline/ridgeline.js +346 -0
  231. package/components/ridgeline/ridgeline.module.css +58 -0
  232. package/components/scroll-area/scroll-area.d.ts +40 -0
  233. package/components/scroll-area/scroll-area.js +153 -0
  234. package/components/scroll-area/scroll-area.module.css +79 -0
  235. package/components/search-field/search-field.d.ts +7 -0
  236. package/components/search-field/search-field.js +20 -0
  237. package/components/search-field/search-field.module.css +17 -0
  238. package/components/segmented-control/segmented-control.d.ts +16 -0
  239. package/components/segmented-control/segmented-control.js +56 -0
  240. package/components/segmented-control/segmented-control.module.css +15 -0
  241. package/components/select/select.d.ts +15 -0
  242. package/components/select/select.js +36 -0
  243. package/components/select/select.module.css +35 -0
  244. package/components/shortcut-recorder/shortcut-recorder.d.ts +108 -0
  245. package/components/shortcut-recorder/shortcut-recorder.js +380 -0
  246. package/components/shortcut-recorder/shortcut-recorder.module.css +85 -0
  247. package/components/signature-pad/signature-pad.d.ts +72 -0
  248. package/components/signature-pad/signature-pad.js +450 -0
  249. package/components/signature-pad/signature-pad.module.css +84 -0
  250. package/components/skeleton/skeleton.d.ts +13 -0
  251. package/components/skeleton/skeleton.js +53 -0
  252. package/components/skeleton/skeleton.module.css +11 -0
  253. package/components/slider/slider.d.ts +45 -0
  254. package/components/slider/slider.js +347 -0
  255. package/components/slider/slider.module.css +45 -0
  256. package/components/slope-chart/slope-chart.d.ts +38 -0
  257. package/components/slope-chart/slope-chart.js +183 -0
  258. package/components/slope-chart/slope-chart.module.css +51 -0
  259. package/components/slot-text/slot-text.d.ts +26 -0
  260. package/components/slot-text/slot-text.js +119 -0
  261. package/components/slot-text/slot-text.module.css +19 -0
  262. package/components/sortable-data-table/sortable-data-table.d.ts +38 -0
  263. package/components/sortable-data-table/sortable-data-table.js +240 -0
  264. package/components/sortable-data-table/sortable-data-table.module.css +116 -0
  265. package/components/sparkline/sparkline.d.ts +19 -0
  266. package/components/sparkline/sparkline.js +239 -0
  267. package/components/sparkline/sparkline.module.css +26 -0
  268. package/components/split-button/split-button.d.ts +19 -0
  269. package/components/split-button/split-button.js +33 -0
  270. package/components/split-button/split-button.module.css +51 -0
  271. package/components/stepper/stepper.d.ts +36 -0
  272. package/components/stepper/stepper.js +108 -0
  273. package/components/stepper/stepper.module.css +74 -0
  274. package/components/streamgraph/streamgraph.d.ts +51 -0
  275. package/components/streamgraph/streamgraph.js +376 -0
  276. package/components/streamgraph/streamgraph.module.css +55 -0
  277. package/components/swipe-actions/swipe-actions.d.ts +35 -0
  278. package/components/swipe-actions/swipe-actions.js +330 -0
  279. package/components/swipe-actions/swipe-actions.module.css +64 -0
  280. package/components/switch/switch.d.ts +7 -0
  281. package/components/switch/switch.js +45 -0
  282. package/components/switch/switch.module.css +12 -0
  283. package/components/tabs/tabs.d.ts +8 -0
  284. package/components/tabs/tabs.js +138 -0
  285. package/components/tabs/tabs.module.css +27 -0
  286. package/components/tag-input/tag-input.d.ts +15 -0
  287. package/components/tag-input/tag-input.js +150 -0
  288. package/components/tag-input/tag-input.module.css +34 -0
  289. package/components/text-morph/text-morph.d.ts +13 -0
  290. package/components/text-morph/text-morph.js +69 -0
  291. package/components/text-morph/text-morph.module.css +8 -0
  292. package/components/text-reveal/text-reveal.d.ts +14 -0
  293. package/components/text-reveal/text-reveal.js +20 -0
  294. package/components/text-reveal/text-reveal.module.css +13 -0
  295. package/components/text-shimmer/text-shimmer.d.ts +18 -0
  296. package/components/text-shimmer/text-shimmer.js +90 -0
  297. package/components/text-shimmer/text-shimmer.module.css +9 -0
  298. package/components/textarea/textarea.d.ts +7 -0
  299. package/components/textarea/textarea.js +56 -0
  300. package/components/textarea/textarea.module.css +22 -0
  301. package/components/theme-switch/theme-switch.d.ts +11 -0
  302. package/components/theme-switch/theme-switch.js +37 -0
  303. package/components/theme-switch/theme-switch.module.css +31 -0
  304. package/components/time-picker/time-picker.d.ts +13 -0
  305. package/components/time-picker/time-picker.js +95 -0
  306. package/components/time-picker/time-picker.module.css +23 -0
  307. package/components/timeline/timeline.d.ts +47 -0
  308. package/components/timeline/timeline.js +171 -0
  309. package/components/timeline/timeline.module.css +51 -0
  310. package/components/toast/toast.d.ts +8 -0
  311. package/components/toast/toast.js +118 -0
  312. package/components/toast/toast.module.css +100 -0
  313. package/components/toast-stack/toast-stack.d.ts +57 -0
  314. package/components/toast-stack/toast-stack.js +399 -0
  315. package/components/toast-stack/toast-stack.module.css +61 -0
  316. package/components/tooltip/tooltip.d.ts +8 -0
  317. package/components/tooltip/tooltip.js +62 -0
  318. package/components/tooltip/tooltip.module.css +18 -0
  319. package/components/tree-view/tree-view.d.ts +17 -0
  320. package/components/tree-view/tree-view.js +147 -0
  321. package/components/tree-view/tree-view.module.css +22 -0
  322. package/components/treemap/treemap.d.ts +35 -0
  323. package/components/treemap/treemap.js +375 -0
  324. package/components/treemap/treemap.module.css +59 -0
  325. package/components/usage-meter/usage-meter.d.ts +24 -0
  326. package/components/usage-meter/usage-meter.js +296 -0
  327. package/components/usage-meter/usage-meter.module.css +61 -0
  328. package/components/user-menu/user-menu.d.ts +62 -0
  329. package/components/user-menu/user-menu.js +316 -0
  330. package/components/user-menu/user-menu.module.css +97 -0
  331. package/components/waffle-chart/waffle-chart.d.ts +39 -0
  332. package/components/waffle-chart/waffle-chart.js +244 -0
  333. package/components/waffle-chart/waffle-chart.module.css +52 -0
  334. package/components.json +1330 -0
  335. package/eslint.js +53 -0
  336. package/lib/escape.d.ts +19 -0
  337. package/lib/escape.js +50 -0
  338. package/lib/media.d.ts +353 -0
  339. package/lib/media.js +65 -0
  340. package/lib/menu-highlight.d.ts +28 -0
  341. package/lib/menu-highlight.js +43 -0
  342. package/lib/motion-tokens.d.ts +60 -0
  343. package/lib/motion-tokens.js +25 -0
  344. package/lib/return-focus.d.ts +9 -0
  345. package/lib/return-focus.js +53 -0
  346. package/lib/silicon-provider.d.ts +60 -0
  347. package/lib/silicon-provider.js +47 -0
  348. package/lib/use-copy-feedback.d.ts +8 -0
  349. package/lib/use-copy-feedback.js +36 -0
  350. package/lib/use-today.d.ts +2 -0
  351. package/lib/use-today.js +29 -0
  352. package/package.json +76 -4
  353. package/styles.css +218 -0
  354. package/styles.css.d.ts +1 -0
@@ -0,0 +1,244 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
+ import { animate, motion, useInView, useMotionValue, useReducedMotion, useSpring, useTransform } from "motion/react";
5
+ import { motionTokens } from "../../lib/motion-tokens.js";
6
+ import styles from "./waffle-chart.module.css";
7
+ const { spring } = motionTokens;
8
+ const GAP = 3;
9
+ /** accentKey takes the first series and the next three categories the supporting series. Anything past that falls back to neutral steps that alternate in lightness. */
10
+ const SHADES = [40, 20, 30, 14];
11
+ /** A second, colour-free channel: neutral categories cycle through square, round, and framed cells, so neighbours differ in shape as well as lightness. */
12
+ const SHAPES = ["square", "round", "framed"];
13
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
14
+ const grouped = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
15
+ const subscribeNothing = () => () => { };
16
+ function useReducedMotionSafe() {
17
+ const hydrated = useSyncExternalStore(subscribeNothing, () => true, () => false);
18
+ return !!useReducedMotion() && hydrated;
19
+ }
20
+ /** Whole cells per category by largest remainder, so the counts always add up to the grid. */
21
+ function allocate(data, total, cells) {
22
+ if (total <= 0)
23
+ return data.map(() => 0);
24
+ const exact = data.map(item => Math.max(0, item.value) / total * cells);
25
+ const counts = exact.map(Math.floor);
26
+ let left = cells - counts.reduce((sum, count) => sum + count, 0);
27
+ const order = exact.map((value, index) => ({ index, rest: value - Math.floor(value) })).sort((a, b) => b.rest - a.rest);
28
+ for (let k = 0; left > 0 && k < order.length; k++, left--)
29
+ counts[order[k].index]++;
30
+ return counts;
31
+ }
32
+ /**
33
+ * Hands cells to categories while moving as few as possible: each category keeps the cells it already had,
34
+ * surplus cells join the categories that grew, and every block takes its cells in their previous order so paths never cross.
35
+ */
36
+ function reassign(previous, data, counts) {
37
+ const bySlot = [...previous].sort((a, b) => a.slot - b.slot);
38
+ const kept = new Map();
39
+ const pool = [];
40
+ const want = new Map(data.map((item, index) => [item.key, counts[index]]));
41
+ for (const cell of bySlot) {
42
+ const list = cell.key !== null ? kept.get(cell.key) ?? [] : [];
43
+ if (cell.key !== null && list.length < (want.get(cell.key) ?? 0)) {
44
+ list.push(cell);
45
+ kept.set(cell.key, list);
46
+ }
47
+ else
48
+ pool.push(cell);
49
+ }
50
+ const next = [];
51
+ let slot = 0;
52
+ data.forEach((item, index) => {
53
+ const own = kept.get(item.key) ?? [];
54
+ while (own.length < counts[index] && pool.length)
55
+ own.push(pool.shift());
56
+ own.sort((a, b) => a.slot - b.slot).forEach(cell => next.push({ id: cell.id, key: item.key, slot: slot++ }));
57
+ });
58
+ pool.forEach(cell => next.push({ id: cell.id, key: null, slot: slot++ }));
59
+ return next.sort((a, b) => a.id - b.id);
60
+ }
61
+ /** A share that rolls to its new value on a spring and holds its width with tabular figures. */
62
+ function Rolling({ value, decimals, reduced }) {
63
+ const shown = useMotionValue(value);
64
+ const text = useTransform(shown, current => `${current.toFixed(decimals)}%`);
65
+ useEffect(() => {
66
+ if (reduced) {
67
+ shown.jump(value);
68
+ return;
69
+ }
70
+ const controls = animate(shown, value, spring.smooth);
71
+ return () => controls.stop();
72
+ }, [reduced, shown, value]);
73
+ return _jsx(motion.span, { children: text });
74
+ }
75
+ export function WaffleChart({ data, label, unit = "", formatValue, rows = 10, columns = 10, accentKey, activeKey, defaultActiveKey = null, onActiveChange, legend = true, decimals = 0, emptyLabel = "No data", ref, className }) {
76
+ const reduced = useReducedMotionSafe();
77
+ const figure = useRef(null);
78
+ const grid = useRef(null);
79
+ const tip = useRef(null);
80
+ useImperativeHandle(ref, () => figure.current);
81
+ const inView = useInView(figure, { once: true, amount: .35 });
82
+ const count = Math.max(1, rows * columns);
83
+ const total = data.reduce((sum, item) => sum + Math.max(0, item.value), 0);
84
+ const empty = !data.length || total <= 0;
85
+ const accent = accentKey === undefined ? data[0]?.key ?? null : accentKey;
86
+ // Cells are persistent objects; each data change reassigns them so they travel instead of repainting.
87
+ const signature = `${count}|${data.map(item => `${item.key}:${item.value}`).join(",")}`;
88
+ const counts = useMemo(() => allocate(data, total, count), [signature]); // eslint-disable-line react-hooks/exhaustive-deps
89
+ const [layout, setLayout] = useState(() => ({ cells: reassign(Array.from({ length: count }, (_, id) => ({ id, key: null, slot: id })), data, counts), moved: new Set(), version: 0 }));
90
+ const [seen, setSeen] = useState(signature);
91
+ if (seen !== signature) {
92
+ setSeen(signature);
93
+ const base = Array.from({ length: count }, (_, id) => layout.cells[id] ?? { id, key: null, slot: id });
94
+ const next = reassign(base, data, counts);
95
+ setLayout({ cells: next, moved: new Set(next.filter(cell => base[cell.id].slot !== cell.slot || base[cell.id].key !== cell.key).map(cell => cell.id)), version: layout.version + 1 });
96
+ }
97
+ const { cells, moved, version } = layout;
98
+ const [ownActive, setOwnActive] = useState(defaultActiveKey);
99
+ const pinned = activeKey !== undefined ? activeKey : ownActive;
100
+ const setPinned = (key) => { if (activeKey === undefined)
101
+ setOwnActive(key); onActiveChange?.(key); };
102
+ const [pointer, setPointer] = useState(null);
103
+ const [preview, setPreview] = useState(null);
104
+ const focusKey = pointer?.key ?? preview ?? pinned;
105
+ const active = focusKey && data.some(item => item.key === focusKey) ? focusKey : null;
106
+ const [side, setSide] = useState(0);
107
+ useLayoutEffect(() => {
108
+ const node = grid.current;
109
+ if (!node)
110
+ return;
111
+ const read = () => setSide(node.clientWidth);
112
+ read();
113
+ if (typeof ResizeObserver === "undefined")
114
+ return;
115
+ const observer = new ResizeObserver(read);
116
+ observer.observe(node);
117
+ return () => observer.disconnect();
118
+ }, []);
119
+ const size = side ? (side - GAP * (columns - 1)) / columns : 0;
120
+ const height = size ? size * rows + GAP * (rows - 1) : 0;
121
+ // Column by column from the bottom-left corner, like a glass filling up.
122
+ const place = (slot) => ({ x: Math.floor(slot / rows) * (size + GAP), y: (rows - 1 - (slot % rows)) * (size + GAP) });
123
+ const colorOf = (key) => {
124
+ if (key === null)
125
+ return "transparent";
126
+ const index = data.findIndex(item => item.key === key), item = data[index];
127
+ if (!item)
128
+ return "transparent";
129
+ if (item.color)
130
+ return item.color;
131
+ if (key === accent)
132
+ return "var(--series-1)";
133
+ const rank = data.filter(entry => entry.key !== accent).findIndex(entry => entry.key === key);
134
+ return rank < 3 ? `var(--series-${rank + 2})` : `color-mix(in oklab, var(--foreground) ${SHADES[(rank - 3) % SHADES.length]}%, var(--surface))`;
135
+ };
136
+ const shapeOf = (key) => {
137
+ if (key === null || key === accent)
138
+ return "square";
139
+ const rank = data.filter(entry => entry.key !== accent).findIndex(entry => entry.key === key);
140
+ return rank < 0 ? "square" : SHAPES[rank % SHAPES.length];
141
+ };
142
+ // The tooltip glides between cells on its own spring and stays inside the figure.
143
+ const tipX = useMotionValue(0), tipY = useMotionValue(0);
144
+ const follow = { stiffness: 520, damping: 44, restDelta: .01 };
145
+ const glideX = useSpring(tipX, follow), glideY = useSpring(tipY, follow);
146
+ const wasShown = useRef(false);
147
+ useLayoutEffect(() => {
148
+ const bubble = tip.current, host = figure.current, board = grid.current;
149
+ if (!pointer || !bubble || !host || !board || !size) {
150
+ wasShown.current = false;
151
+ return;
152
+ }
153
+ const hostBox = host.getBoundingClientRect(), boardBox = board.getBoundingClientRect();
154
+ const { x, y } = place(pointer.slot);
155
+ const cx = boardBox.left - hostBox.left + x + size / 2, cy = boardBox.top - hostBox.top + y;
156
+ const tw = bubble.offsetWidth, th = bubble.offsetHeight;
157
+ let top = cy - th - 10;
158
+ if (top < 0)
159
+ top = cy + size + 10;
160
+ const left = clamp(cx - tw / 2, 0, Math.max(0, hostBox.width - tw));
161
+ tipX.set(left);
162
+ tipY.set(clamp(top, 0, Math.max(0, hostBox.height - th)));
163
+ if (!wasShown.current || reduced) {
164
+ glideX.jump(tipX.get());
165
+ glideY.jump(tipY.get());
166
+ }
167
+ wasShown.current = true;
168
+ });
169
+ const slotAt = (clientX, clientY) => {
170
+ const box = grid.current?.getBoundingClientRect();
171
+ if (!box || !size)
172
+ return null;
173
+ const col = clamp(Math.floor((clientX - box.left) / (size + GAP)), 0, columns - 1);
174
+ const row = clamp(Math.floor((clientY - box.top) / (size + GAP)), 0, rows - 1);
175
+ return col * rows + (rows - 1 - row);
176
+ };
177
+ const pointAt = (slot) => {
178
+ if (slot === null)
179
+ return null;
180
+ const cell = cells.find(item => item.slot === slot);
181
+ return cell?.key ? { key: cell.key, slot } : null;
182
+ };
183
+ const onPointerMove = (event) => { if (event.pointerType === "mouse" || event.buttons)
184
+ setPointer(pointAt(slotAt(event.clientX, event.clientY))); };
185
+ const onPointerDown = (event) => { if (event.pointerType !== "mouse")
186
+ event.currentTarget.setPointerCapture?.(event.pointerId); setPointer(pointAt(slotAt(event.clientX, event.clientY))); };
187
+ // Arrows walk the grid cell by cell; Page keys jump between categories.
188
+ const onKeyDown = (event) => {
189
+ if (empty)
190
+ return;
191
+ if (event.key === "Escape" && pointer) {
192
+ event.preventDefault();
193
+ setPointer(null);
194
+ return;
195
+ }
196
+ const filled = cells.filter(cell => cell.key !== null).length;
197
+ const at = pointer?.slot ?? 0;
198
+ const col = Math.floor(at / rows), row = at % rows;
199
+ const blocks = counts.map((size, index) => ({ start: counts.slice(0, index).reduce((sum, value) => sum + value, 0), size })).filter(block => block.size > 0);
200
+ const block = Math.max(0, blocks.findIndex(item => at >= item.start && at < item.start + item.size));
201
+ const moves = {
202
+ ArrowUp: () => col * rows + Math.min(rows - 1, row + 1),
203
+ ArrowDown: () => col * rows + Math.max(0, row - 1),
204
+ ArrowRight: () => Math.min(columns - 1, col + 1) * rows + row,
205
+ ArrowLeft: () => Math.max(0, col - 1) * rows + row,
206
+ PageDown: () => blocks[Math.min(blocks.length - 1, block + 1)].start,
207
+ PageUp: () => at > blocks[block].start ? blocks[block].start : blocks[Math.max(0, block - 1)].start,
208
+ Home: () => 0,
209
+ End: () => filled - 1,
210
+ };
211
+ const move = moves[event.key];
212
+ if (!move)
213
+ return;
214
+ event.preventDefault();
215
+ setPointer(pointAt(clamp(pointer ? move() : 0, 0, Math.max(0, filled - 1))));
216
+ };
217
+ const format = (value, item) => `${formatValue ? formatValue(value, item) : grouped.format(value)}${unit ? ` ${unit}` : ""}`;
218
+ const share = (item) => total > 0 ? Math.max(0, item.value) / total * 100 : 0;
219
+ const reading = pointer ? data.find(item => item.key === pointer.key) ?? null : null;
220
+ const announce = reading ? `${reading.label}, ${share(reading).toFixed(decimals)}%, ${format(reading.value, reading)}` : "";
221
+ const summary = empty ? `${label}. ${emptyLabel}.` : `${label}. ${data.map(item => `${item.label} ${share(item).toFixed(decimals)}%`).join(", ")}.`;
222
+ const shownX = reduced ? tipX : glideX, shownY = reduced ? tipY : glideY;
223
+ const settle = reduced ? { duration: 0 } : spring.morph;
224
+ return _jsxs("figure", { ref: figure, className: [styles.figure, className].filter(Boolean).join(" "), "aria-label": label, "data-legend": legend || undefined, children: [_jsxs("div", { className: styles.body, children: [_jsxs("div", { className: styles.board, children: [_jsx("div", { ref: grid, className: styles.grid, style: { height: height || undefined, aspectRatio: height ? undefined : `${columns} / ${rows}` }, "data-active": active ? true : undefined, role: "group", tabIndex: empty ? -1 : 0, "aria-roledescription": "waffle chart", "aria-label": `${label}. ${count} cells. Use arrow keys to move between cells, Page Up and Page Down to jump between categories.`, onPointerMove: onPointerMove, onPointerDown: onPointerDown, onPointerUp: event => { if (event.pointerType !== "mouse")
225
+ setPointer(null); }, onPointerCancel: () => setPointer(null), onPointerLeave: event => { if (event.pointerType === "mouse")
226
+ setPointer(null); }, onKeyDown: onKeyDown, onBlur: () => setPointer(null), onFocus: event => { if (event.currentTarget.matches(":focus-visible") && !empty)
227
+ setPointer(current => current ?? pointAt(0)); }, children: size > 0 && cells.map(cell => {
228
+ const { x, y } = place(cell.slot);
229
+ const travel = moved.has(cell.id) && !reduced;
230
+ const delay = reduced ? 0 : (cell.slot / count) * .32;
231
+ const on = cell.key !== null && cell.key === active;
232
+ const shown = inView || reduced;
233
+ // A travelling cell dips while it flies, so the flow reads as cells lifting out of one block and landing in another.
234
+ const dip = travel ? [1, .62 + (version % 2) * .001, 1] : 1;
235
+ return _jsx(motion.span, { className: styles.cell, "aria-hidden": "true", "data-on": on || undefined, "data-cursor": pointer?.slot === cell.slot || undefined, "data-empty": cell.key === null || undefined, "data-shape": shapeOf(cell.key), style: { width: size, height: size, "--cell": colorOf(cell.key), "--delay": `${delay}s` }, initial: reduced ? false : { x, y, scale: .4, opacity: 0 }, animate: shown ? { x, y, scale: dip, opacity: cell.key === null ? 0 : 1 } : { x, y, scale: .4, opacity: 0 }, transition: reduced ? { duration: 0 } : {
236
+ x: { ...settle, delay }, y: { ...settle, delay },
237
+ scale: travel ? { duration: .62, times: [0, .42, 1], ease: motionTokens.ease.inOut, delay } : { ...spring.snappy, delay: delay * .6 },
238
+ opacity: { duration: motionTokens.duration.standard, ease: motionTokens.ease.standard, delay: delay * .6 },
239
+ } }, cell.id);
240
+ }) }), !empty && _jsxs(motion.div, { ref: tip, className: styles.tooltip, "data-shown": pointer ? true : undefined, style: { x: shownX, y: shownY }, "aria-hidden": "true", children: [_jsxs("p", { className: styles.tipRow, children: [_jsx("span", { className: styles.tipSwatch, "data-shape": shapeOf(reading?.key ?? null), style: { "--cell": colorOf(reading?.key ?? null) } }), _jsx("span", { className: styles.tipName, children: reading?.label ?? "" }), _jsx("span", { className: styles.tipValue, children: reading ? `${share(reading).toFixed(decimals)}%` : "" })] }), _jsx("p", { className: styles.tipMeta, children: reading ? format(reading.value, reading) : "" })] }), empty && _jsx("p", { className: styles.message, children: emptyLabel })] }), legend && !empty && _jsx("ul", { className: styles.legend, "aria-label": `${label} categories`, onPointerLeave: () => setPreview(null), children: data.map(item => _jsx("li", { children: _jsxs("button", { type: "button", className: styles.item, "aria-pressed": pinned === item.key, "data-on": active === item.key || undefined, "data-dim": (active !== null && active !== item.key) || undefined, onClick: () => setPinned(pinned === item.key ? null : item.key), onPointerEnter: event => { if (event.pointerType === "mouse")
241
+ setPreview(item.key); }, onFocus: event => { if (event.currentTarget.matches(":focus-visible"))
242
+ setPreview(item.key); }, onBlur: () => setPreview(null), children: [_jsx("span", { className: styles.swatch, "data-shape": shapeOf(item.key), style: { "--cell": colorOf(item.key) }, "aria-hidden": "true" }), _jsx("span", { className: styles.itemLabel, children: item.label }), _jsx("span", { className: styles.itemShare, children: _jsx(Rolling, { value: share(item), decimals: decimals, reduced: reduced }) })] }) }, item.key)) })] }), _jsx("p", { className: styles.srOnly, "aria-live": "polite", "aria-atomic": "true", children: announce }), _jsx("p", { className: styles.srOnly, children: summary }), !empty && _jsx("div", { className: styles.srOnly, children: _jsxs("table", { children: [_jsx("caption", { children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Category" }), _jsx("th", { scope: "col", children: "Value" }), _jsx("th", { scope: "col", children: "Share" }), _jsx("th", { scope: "col", children: "Cells" })] }) }), _jsx("tbody", { children: data.map((item, index) => _jsxs("tr", { children: [_jsx("th", { scope: "row", children: item.label }), _jsx("td", { children: format(item.value, item) }), _jsxs("td", { children: [share(item).toFixed(decimals), "%"] }), _jsx("td", { children: counts[index] })] }, item.key)) })] }) })] });
243
+ }
244
+ export default WaffleChart;
@@ -0,0 +1,52 @@
1
+ .figure { position: relative; min-width: 0; margin: 0; color: var(--foreground); font-family: var(--font-body); letter-spacing: var(--tracking-body); container-type: inline-size; }
2
+
3
+ /* Series colors (--series-1 to --series-4) come from registry/foundation.css. */
4
+ .body { display: grid; min-width: 0; grid-template-columns: minmax(0, 1fr); align-items: center; gap: var(--space-section); }
5
+ @container (min-width: 420px) {
6
+ .figure[data-legend] .body { grid-template-columns: minmax(0, 232px) minmax(0, 1fr); gap: var(--space-section); }
7
+ }
8
+
9
+ .board { min-width: 0; }
10
+ .grid { position: relative; width: 100%; max-width: 300px; margin-inline: auto; outline: none; touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; cursor: default; }
11
+
12
+ /* Each cell is one unit. Its colour changes mid-flight with the same stagger as its travel. */
13
+ .cell { --inner: 0 0 0 0 transparent; --outer: 0 0 0 0 transparent; position: absolute; top: 0; left: 0; border-radius: 22%; background: var(--cell); box-shadow: var(--inner), var(--outer); will-change: transform; transition: background-color var(--duration-considered) var(--ease-standard) var(--delay, 0s), border-radius var(--duration-considered) var(--ease-standard) var(--delay, 0s), box-shadow var(--duration-considered) var(--ease-standard) var(--delay, 0s), opacity var(--duration-fast) var(--ease-standard); }
14
+ .grid[data-active] .cell:not([data-on]) { opacity: .26 !important; transition: background-color var(--duration-considered) var(--ease-standard) var(--delay, 0s), border-radius var(--duration-considered) var(--ease-standard) var(--delay, 0s), opacity var(--duration-standard) var(--ease-standard); }
15
+ .cell[data-cursor] { --outer: 0 0 0 2px var(--surface), 0 0 0 3.5px color-mix(in oklab, var(--foreground) 55%, transparent); }
16
+
17
+ /* Shape is the colour-free second channel: a cell morphs between these as it changes category. */
18
+ .cell[data-shape="round"], .swatch[data-shape="round"], .tipSwatch[data-shape="round"] { border-radius: 50%; corner-shape: round; }
19
+ .cell[data-shape="framed"] { --inner: inset 0 0 0 2.5px var(--cell), inset 0 0 0 3.25px var(--surface); }
20
+ .swatch[data-shape="framed"], .tipSwatch[data-shape="framed"] { box-shadow: inset 0 0 0 2px var(--cell), inset 0 0 0 3px var(--surface); }
21
+
22
+ .tooltip { position: absolute; top: 0; left: 0; z-index: 2; display: grid; min-width: 150px; max-width: min(240px, 100%); gap: 2px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface-raised); box-shadow: var(--shadow-floating); opacity: 0; pointer-events: none; scale: .96; transition: opacity var(--duration-fast) var(--ease-standard), scale var(--duration-fast) var(--ease-standard); }
23
+ .tooltip[data-shown] { opacity: 1; scale: 1; }
24
+ .tipRow { display: flex; min-width: 0; align-items: center; gap: 8px; margin: 0; font-size: var(--text-sm); line-height: 1.3; }
25
+ .tipSwatch { width: 10px; height: 10px; flex: none; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); background: var(--cell); transition: background-color var(--duration-fast) var(--ease-standard); }
26
+ .tipName { min-width: 0; flex: 1; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; white-space: nowrap; }
27
+ .tipValue { flex: none; margin-left: 8px; font-weight: 500; font-variant-numeric: tabular-nums; }
28
+ .tipMeta { margin: 0 0 0 18px; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); font-variant-numeric: tabular-nums; white-space: nowrap; }
29
+
30
+ .message { display: grid; min-height: 160px; place-items: center; margin: 0; color: var(--text-muted); font-size: var(--text-sm); }
31
+
32
+ /* The legend doubles as the reading: each share rolls to its new value. */
33
+ .legend { display: grid; min-width: 0; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 2px var(--space-related); margin: 0; padding: 0; list-style: none; }
34
+ @container (min-width: 420px) { .figure[data-legend] .legend { grid-template-columns: minmax(0, 1fr); } }
35
+ /* Below the side-by-side layout two columns would clip names like "Wind and solar", so the legend reads as one list. */
36
+ /* Stacked, the legend shares the grid's measure and its row padding hangs outside, so swatches and shares line up with the cell edges. */
37
+ @container (max-width: 419px) { .legend { grid-template-columns: minmax(0, 1fr); margin-inline: max(-10px, calc((100% - 320px) / 2)); } }
38
+ .item { display: grid; width: 100%; min-height: 36px; grid-template-columns: 10px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 4px 10px; border: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: var(--leading-body); text-align: left; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
39
+ .item:active { transform: scale(.98); }
40
+ .item[data-on] { color: var(--foreground); background: color-mix(in oklab, var(--foreground) 5%, transparent); }
41
+ .item[data-dim] { opacity: .5; }
42
+ .item[aria-pressed="true"] { color: var(--foreground); }
43
+ @media (hover: hover) and (pointer: fine) { .item:hover { color: var(--foreground); } }
44
+ .swatch { width: 10px; height: 10px; border-radius: var(--radius-detail); corner-shape: var(--corner-shape); background: var(--cell); }
45
+ .itemLabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
46
+ .itemShare { color: var(--foreground); font-size: var(--text-base); font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; min-width: 3.2em; }
47
+
48
+ .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
49
+
50
+ @media (prefers-reduced-motion: reduce) {
51
+ .cell, .grid[data-active] .cell:not([data-on]), .tooltip, .tipSwatch, .item { transition: none; }
52
+ }