@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,375 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
+ import { AnimatePresence, animate, motion, useInView, useMotionValue, useReducedMotion, useSpring } from "motion/react";
5
+ import { motionTokens } from "../../lib/motion-tokens.js";
6
+ import styles from "./treemap.module.css";
7
+ const { spring, ease, blur, duration } = motionTokens;
8
+ const physical = ({ visualDuration, bounce }, restDelta = .0005) => { const root = (2 * Math.PI) / (visualDuration * 1.2); return { type: "spring", stiffness: root * root, damping: 2 * (1 - bounce) * root, restDelta, restSpeed: restDelta * 2 }; };
9
+ const zoom = physical({ visualDuration: .62, bounce: .06 });
10
+ const reveal = physical({ visualDuration: .9, bounce: 0 });
11
+ const glide = physical(spring.snappy, .01);
12
+ const follow = { stiffness: glide.stiffness, damping: glide.damping, restDelta: .01 };
13
+ /** Top level branches take the four series hues in order; a fifth and beyond stay neutral rather than reuse a hue. */
14
+ const hueOf = (index) => index < 0 ? "var(--series-1)" : index < 4 ? `var(--series-${index + 1})` : "var(--foreground)";
15
+ const GAP = 2, PAD = 3, HEADER = 22, TIP = 14, STEPS = 5;
16
+ const grouped = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
17
+ const percent = new Intl.NumberFormat("en-US", { style: "percent", maximumFractionDigits: 1 });
18
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
19
+ const mix = (a, b, t) => a + (b - a) * t;
20
+ const inset = (r, top, side) => ({ x: r.x + side, y: r.y + top, w: Math.max(0, r.w - side * 2), h: Math.max(0, r.h - top - side) });
21
+ function flatten(root) {
22
+ const flat = new Map();
23
+ const visit = (node, depth, parent, path) => {
24
+ const kids = node.children ?? [];
25
+ let value = 0, weighted = 0, colored = 0;
26
+ if (kids.length)
27
+ for (const kid of kids) {
28
+ const r = visit(kid, depth + 1, node.id, depth === 0 ? [] : [...path, node.label]);
29
+ value += r.value;
30
+ weighted += r.weighted;
31
+ colored += r.colored;
32
+ }
33
+ else {
34
+ value = Math.max(0, node.value ?? 0);
35
+ if (node.color !== undefined && Number.isFinite(node.color)) {
36
+ weighted = node.color * value;
37
+ colored = value;
38
+ }
39
+ }
40
+ flat.set(node.id, { id: node.id, label: node.label, value, color: colored ? weighted / colored : null, depth, parent, children: kids.map(kid => kid.id), path: depth === 0 ? [node.label] : [...path, node.label] });
41
+ return { value, weighted, colored };
42
+ };
43
+ visit(root, 0, null, []);
44
+ return flat;
45
+ }
46
+ /** Squarified layout: rows of tiles along the short side, each row closed when adding a tile would make the worst aspect ratio worse. */
47
+ function squarify(items, rect, out) {
48
+ const list = items.filter(item => item.value > 0).sort((a, b) => b.value - a.value);
49
+ const total = list.reduce((sum, item) => sum + item.value, 0);
50
+ if (!total || rect.w <= 0 || rect.h <= 0) {
51
+ for (const item of items)
52
+ out.set(item.id, { x: rect.x, y: rect.y, w: 0, h: 0 });
53
+ return;
54
+ }
55
+ const scale = (rect.w * rect.h) / total;
56
+ let { x, y, w, h } = rect, i = 0;
57
+ const worst = (row, side) => { const sum = row.reduce((a, b) => a + b, 0), max = Math.max(...row), min = Math.min(...row); return Math.max((side * side * max) / (sum * sum), (sum * sum) / (side * side * min)); };
58
+ while (i < list.length) {
59
+ const side = Math.min(w, h), row = [list[i].value * scale];
60
+ let j = i + 1;
61
+ while (j < list.length) {
62
+ const next = [...row, list[j].value * scale];
63
+ if (worst(next, side) > worst(row, side))
64
+ break;
65
+ row.push(list[j].value * scale);
66
+ j++;
67
+ }
68
+ const sum = row.reduce((a, b) => a + b, 0);
69
+ if (w >= h) {
70
+ const cw = sum / h;
71
+ let cy = y;
72
+ for (let k = i; k < j; k++) {
73
+ const th = (list[k].value * scale) / cw;
74
+ out.set(list[k].id, { x, y: cy, w: cw, h: th });
75
+ cy += th;
76
+ }
77
+ x += cw;
78
+ w -= cw;
79
+ }
80
+ else {
81
+ const rh = sum / w;
82
+ let cx = x;
83
+ for (let k = i; k < j; k++) {
84
+ const tw = (list[k].value * scale) / rh;
85
+ out.set(list[k].id, { x: cx, y, w: tw, h: rh });
86
+ cx += tw;
87
+ }
88
+ y += rh;
89
+ h -= rh;
90
+ }
91
+ i = j;
92
+ }
93
+ for (const item of items)
94
+ if (!out.has(item.id))
95
+ out.set(item.id, { x: rect.x, y: rect.y, w: 0, h: 0 });
96
+ }
97
+ /** Rects of every descendant when `focus` fills the view: its children fill the view, and each branch holds its own children under a header. */
98
+ function layoutFrom(flat, focus, view) {
99
+ const out = new Map();
100
+ const nest = (id, rect, top) => {
101
+ const node = flat.get(id);
102
+ if (!node.children.length)
103
+ return;
104
+ const room = top ? rect : inset(rect, rect.h > 48 && rect.w > 64 ? HEADER : PAD, PAD);
105
+ const placed = new Map();
106
+ squarify(node.children.map(child => ({ id: child, value: flat.get(child).value })), room, placed);
107
+ for (const [child, r] of placed) {
108
+ const gapped = { x: r.x + GAP / 2, y: r.y + GAP / 2, w: Math.max(0, r.w - GAP), h: Math.max(0, r.h - GAP) };
109
+ out.set(child, gapped);
110
+ nest(child, gapped, false);
111
+ }
112
+ };
113
+ nest(focus, view, true);
114
+ return out;
115
+ }
116
+ /** The affine map that carries rect `from` onto rect `to`, applied to `r`. */
117
+ const carry = (r, from, to) => { const sx = to.w / (from.w || 1), sy = to.h / (from.h || 1); return { x: to.x + (r.x - from.x) * sx, y: to.y + (r.y - from.y) * sy, w: r.w * sx, h: r.h * sy }; };
118
+ const subscribeNothing = () => () => { };
119
+ function useReducedMotionSafe() {
120
+ const hydrated = useSyncExternalStore(subscribeNothing, () => true, () => false);
121
+ return !!useReducedMotion() && hydrated;
122
+ }
123
+ export function Treemap({ data, label, formatValue = value => grouped.format(value), colorLabel, formatColor = value => grouped.format(value), colorDomain, focus, defaultFocus, onFocusChange, height = 420, emptyLabel = "No data yet", ref, className }) {
124
+ const reduced = useReducedMotionSafe();
125
+ const figure = useRef(null);
126
+ const stage = useRef(null);
127
+ const tip = useRef(null);
128
+ const tiles = useRef(new Map());
129
+ useImperativeHandle(ref, () => figure.current);
130
+ const inView = useInView(figure, { once: true, amount: .25 });
131
+ const signature = JSON.stringify(data);
132
+ const flat = useMemo(() => flatten(data), [signature]); // eslint-disable-line react-hooks/exhaustive-deps
133
+ const root = flat.get(data.id);
134
+ const nodes = useMemo(() => [...flat.values()].filter(node => node.depth > 0), [flat]);
135
+ const [ownFocus, setOwnFocus] = useState(defaultFocus ?? data.id);
136
+ const wanted = flat.get(focus ?? ownFocus);
137
+ const center = wanted && wanted.children.length ? wanted : root;
138
+ const zoomTo = (id) => { if (focus === undefined)
139
+ setOwnFocus(id); onFocusChange?.(id); };
140
+ const [active, setActive] = useState(null);
141
+ const [pointer, setPointer] = useState(null);
142
+ const [width, setWidth] = useState(0);
143
+ useLayoutEffect(() => {
144
+ const node = stage.current;
145
+ if (!node || typeof ResizeObserver === "undefined")
146
+ return;
147
+ const observer = new ResizeObserver(() => setWidth(node.clientWidth));
148
+ observer.observe(node);
149
+ setWidth(node.clientWidth);
150
+ return () => observer.disconnect();
151
+ }, []);
152
+ const view = useMemo(() => ({ x: 0, y: 0, w: width, h: height }), [width, height]);
153
+ const target = useMemo(() => layoutFrom(flat, center.id, view), [flat, center.id, view]);
154
+ const levelOf = (node) => node.depth - center.depth;
155
+ // Color scale: a few neutral steps from light to dark, so shade reads as more without adding a hue.
156
+ const leaves = nodes.filter(node => !node.children.length && node.color !== null);
157
+ const [lo, hi] = colorDomain ?? (leaves.length ? [Math.min(...leaves.map(node => node.color)), Math.max(...leaves.map(node => node.color))] : [0, 1]);
158
+ const colored = colorLabel !== undefined && leaves.length > 0;
159
+ const shade = (value) => { if (!colored || value === null)
160
+ return 34; const t = clamp((value - lo) / (hi - lo || 1), 0, 1); return Math.round(14 + Math.round(t * (STEPS - 1)) / (STEPS - 1) * 44); };
161
+ const branchOf = useMemo(() => { const map = new Map(); root.children.forEach((id, index) => { const walk = (at) => { map.set(at, index); flat.get(at)?.children.forEach(walk); }; walk(id); }); return map; }, [flat, root]);
162
+ // Every tile keeps its on screen rect; a spring carries it to the new layout. Tiles entering or leaving ride the same zoom as the tile they belong to.
163
+ const drawn = useRef(new Map());
164
+ const levels = useRef(new Map());
165
+ const lastFocus = useRef(null);
166
+ const paint = () => {
167
+ for (const node of nodes) {
168
+ const el = tiles.current.get(node.id), d = drawn.current.get(node.id);
169
+ if (!el)
170
+ continue;
171
+ if (!d || d.o < .01 || d.w < .5 || d.h < .5) {
172
+ el.style.visibility = "hidden";
173
+ el.style.pointerEvents = "none";
174
+ continue;
175
+ }
176
+ el.style.visibility = "visible";
177
+ el.style.transform = `translate3d(${d.x.toFixed(2)}px, ${d.y.toFixed(2)}px, 0)`;
178
+ el.style.width = `${d.w.toFixed(2)}px`;
179
+ el.style.height = `${d.h.toFixed(2)}px`;
180
+ el.style.opacity = d.o.toFixed(3);
181
+ el.style.pointerEvents = d.o > .6 ? "auto" : "none";
182
+ const level = levels.current.get(node.id) ?? 0;
183
+ el.dataset.level = String(level);
184
+ el.dataset.room = d.w > 64 && d.h > 38 ? (d.h > 54 ? "full" : "name") : "none";
185
+ }
186
+ };
187
+ const paintRef = useRef(paint);
188
+ useLayoutEffect(() => { paintRef.current = paint; });
189
+ const shown = (inView || reduced) && width > 0;
190
+ useEffect(() => {
191
+ if (!shown)
192
+ return;
193
+ const prev = lastFocus.current;
194
+ const isUnder = (id, ancestor) => { for (let at = flat.get(id); at; at = at.parent ? flat.get(at.parent) : undefined)
195
+ if (at.id === ancestor)
196
+ return true; return false; };
197
+ const visible = (node) => { const l = node.depth - center.depth; return l >= 1 && l <= 2 && isUnder(node.id, center.id); };
198
+ const nextVisible = new Set(nodes.filter(visible).map(node => node.id));
199
+ const before = new Map(drawn.current);
200
+ const wasVisible = new Set([...before].filter(([, d]) => d.o > .5).map(([id]) => id));
201
+ // The pivot: the tile that grows to fill the view, or the view that shrinks back into its tile.
202
+ let toOld = null, toNew = null;
203
+ if (prev && prev.id !== center.id && before.size) {
204
+ if (isUnder(center.id, prev.id)) {
205
+ const inOld = prev.layout.get(center.id);
206
+ if (inOld) {
207
+ toOld = r => carry(r, view, inOld);
208
+ toNew = r => carry(r, inOld, view);
209
+ }
210
+ }
211
+ else if (isUnder(prev.id, center.id)) {
212
+ const inNew = target.get(prev.id);
213
+ if (inNew) {
214
+ toOld = r => carry(r, inNew, view);
215
+ toNew = r => carry(r, view, inNew);
216
+ }
217
+ }
218
+ }
219
+ const from = new Map(), to = new Map();
220
+ const first = !before.size;
221
+ for (const node of nodes) {
222
+ const goal = target.get(node.id), now = before.get(node.id);
223
+ const inNext = nextVisible.has(node.id), inPrev = wasVisible.has(node.id);
224
+ if (inNext && goal) {
225
+ to.set(node.id, { ...goal, o: 1 });
226
+ if (inPrev && now)
227
+ from.set(node.id, now);
228
+ else if (first)
229
+ from.set(node.id, { x: goal.x + goal.w / 2, y: goal.y + goal.h / 2, w: 0, h: 0, o: 0 });
230
+ else
231
+ from.set(node.id, { ...(toOld ? toOld(goal) : goal), o: 0 });
232
+ levels.current.set(node.id, levelOf(node));
233
+ }
234
+ else if (inPrev && now) {
235
+ from.set(node.id, now);
236
+ to.set(node.id, { ...(toNew ? toNew(now) : now), o: 0 });
237
+ }
238
+ }
239
+ for (const node of nodes)
240
+ if (!from.has(node.id))
241
+ drawn.current.set(node.id, { x: 0, y: 0, w: 0, h: 0, o: 0 });
242
+ lastFocus.current = { id: center.id, layout: target };
243
+ const finish = () => { for (const [id, d] of to)
244
+ drawn.current.set(id, d); paintRef.current(); };
245
+ if (reduced) {
246
+ finish();
247
+ return;
248
+ }
249
+ const controls = animate(0, 1, { ...(first ? reveal : zoom), onUpdate: t => {
250
+ for (const [id, a] of from) {
251
+ const b = to.get(id);
252
+ // On the first reveal, tiles grow in a quick wave from the largest.
253
+ const local = first ? clamp((t - Math.min(.3, (1 - b.w * b.h / (view.w * view.h || 1)) * .3)) / .7, 0, 1) : t;
254
+ drawn.current.set(id, { x: mix(a.x, b.x, local), y: mix(a.y, b.y, local), w: mix(a.w, b.w, local), h: mix(a.h, b.h, local), o: mix(a.o, b.o, local) });
255
+ }
256
+ paintRef.current();
257
+ }, onComplete: finish });
258
+ return () => controls.stop();
259
+ }, [shown, target, reduced]); // eslint-disable-line react-hooks/exhaustive-deps
260
+ useLayoutEffect(() => { paintRef.current(); });
261
+ const activeNode = active ? flat.get(active) ?? null : null;
262
+ const parentOf = (node) => node.parent ? flat.get(node.parent) : node;
263
+ const share = (node, of) => of.value ? node.value / of.value : 0;
264
+ // Tooltip: trails the pointer, or sits by the tile in focus, and stays inside the chart.
265
+ const tipX = useMotionValue(0), tipY = useMotionValue(0);
266
+ const tipSpringX = useSpring(tipX, follow), tipSpringY = useSpring(tipY, follow);
267
+ const wasOn = useRef(false);
268
+ useLayoutEffect(() => {
269
+ const bubble = tip.current;
270
+ if (!bubble || !activeNode || !width) {
271
+ wasOn.current = false;
272
+ return;
273
+ }
274
+ const r = target.get(activeNode.id);
275
+ const ax = pointer?.x ?? (r ? r.x + r.w / 2 : 0), ay = pointer?.y ?? (r ? r.y + Math.min(r.h / 2, 28) : 0);
276
+ const tw = bubble.offsetWidth, th = bubble.offsetHeight;
277
+ let left = ax + TIP;
278
+ if (left + tw > width)
279
+ left = ax - TIP - tw;
280
+ let top = ay + TIP;
281
+ if (top + th > height)
282
+ top = ay - TIP - th;
283
+ tipX.set(clamp(left, 0, Math.max(0, width - tw)));
284
+ tipY.set(clamp(top, 0, Math.max(0, height - th)));
285
+ if (!wasOn.current || reduced) {
286
+ tipSpringX.jump(tipX.get());
287
+ tipSpringY.jump(tipY.get());
288
+ }
289
+ wasOn.current = true;
290
+ });
291
+ // Keyboard: arrows hop to the nearest tile in that direction on the top level, Enter zooms in, Escape zooms out.
292
+ const topTiles = center.children.map(id => flat.get(id)).filter(node => node.value > 0);
293
+ const topOf = (node) => { let at = node; while (at && at.parent && at.parent !== center.id)
294
+ at = flat.get(at.parent) ?? null; return at && at.parent === center.id ? at : null; };
295
+ const onKeyDown = (event) => {
296
+ const current = topOf(activeNode);
297
+ const dirs = { ArrowRight: [1, 0], ArrowLeft: [-1, 0], ArrowDown: [0, 1], ArrowUp: [0, -1] };
298
+ if (event.key in dirs) {
299
+ event.preventDefault();
300
+ setPointer(null);
301
+ if (!current) {
302
+ setActive(topTiles[0]?.id ?? null);
303
+ return;
304
+ }
305
+ const [dx, dy] = dirs[event.key], o = target.get(current.id), ox = o.x + o.w / 2, oy = o.y + o.h / 2;
306
+ let best = null, score = Infinity;
307
+ for (const node of topTiles) {
308
+ if (node.id === current.id)
309
+ continue;
310
+ const r = target.get(node.id);
311
+ if (!r)
312
+ continue;
313
+ const vx = r.x + r.w / 2 - ox, vy = r.y + r.h / 2 - oy, along = vx * dx + vy * dy, across = Math.abs(vx * dy - vy * dx);
314
+ if (along <= 1)
315
+ continue;
316
+ const s = along + across * 1.5;
317
+ if (s < score) {
318
+ score = s;
319
+ best = node;
320
+ }
321
+ }
322
+ if (best)
323
+ setActive(best.id);
324
+ }
325
+ else if (event.key === "Home" || event.key === "End") {
326
+ event.preventDefault();
327
+ setPointer(null);
328
+ setActive((event.key === "Home" ? topTiles[0] : topTiles[topTiles.length - 1])?.id ?? null);
329
+ }
330
+ else if (event.key === "Enter" || event.key === " ") {
331
+ if (current?.children.length) {
332
+ event.preventDefault();
333
+ zoomTo(current.id);
334
+ setActive(current.children[0]);
335
+ }
336
+ }
337
+ else if (event.key === "Escape" || event.key === "Backspace") {
338
+ if (center.parent) {
339
+ event.preventDefault();
340
+ zoomTo(center.parent);
341
+ setActive(center.id);
342
+ }
343
+ else if (active) {
344
+ event.preventDefault();
345
+ setActive(null);
346
+ }
347
+ }
348
+ };
349
+ const onPointerMove = (event, id) => {
350
+ if (event.pointerType !== "mouse")
351
+ return;
352
+ const rect = stage.current?.getBoundingClientRect();
353
+ if (rect)
354
+ setPointer({ x: event.clientX - rect.left, y: event.clientY - rect.top });
355
+ setActive(id);
356
+ };
357
+ const onTileClick = (node) => { const top = topOf(node); if (top?.children.length)
358
+ zoomTo(top.id);
359
+ else if (!activeNode || activeNode.id !== node.id)
360
+ setActive(node.id); };
361
+ const trail = [];
362
+ for (let at = center; at; at = at.parent ? flat.get(at.parent) : undefined)
363
+ trail.unshift(at);
364
+ const empty = !root.value;
365
+ const describe = (node) => `${node.path.join(", ")}, ${formatValue(node.value)}, ${percent.format(share(node, parentOf(node)))} of ${parentOf(node).label}${colored && node.color !== null ? `, ${colorLabel} ${formatColor(node.color)}` : ""}`;
366
+ const focusTop = topOf(activeNode);
367
+ return _jsxs("figure", { ref: figure, className: [styles.figure, className].filter(Boolean).join(" "), "aria-label": label, children: [_jsxs("div", { className: styles.bar, children: [_jsx("nav", { className: styles.crumbs, "aria-label": `${label} path`, children: _jsx("ol", { className: styles.crumbList, children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: trail.map((node, index) => _jsxs(motion.li, { className: styles.crumb, layout: reduced ? false : "position", initial: { opacity: 0, x: reduced ? 0 : -6, filter: reduced ? "none" : `blur(${blur.subtle}px)` }, animate: { opacity: 1, x: 0, filter: "blur(0px)" }, exit: { opacity: 0, x: reduced ? 0 : -6, transition: { duration: duration.instant, ease: [...ease.exit] } }, transition: { ...spring.smooth }, children: [index > 0 && _jsx("span", { className: styles.crumbSep, "aria-hidden": "true", children: "/" }), index < trail.length - 1 ? _jsx("button", { type: "button", className: styles.crumbButton, onClick: () => { zoomTo(node.id); setActive(null); }, children: node.label }) : _jsx("span", { className: styles.crumbCurrent, "aria-current": "location", children: node.label })] }, node.id)) }) }) }), _jsx("p", { className: styles.total, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(motion.span, { className: styles.totalValue, initial: { opacity: 0, y: reduced ? 0 : 6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: reduced ? 0 : -6 }, transition: { duration: duration.standard, ease: [...ease.enter] }, children: formatValue(center.value) }, formatValue(center.value)) }) })] }), _jsxs("div", { ref: stage, className: styles.stage, style: { height }, "data-active": activeNode ? true : undefined, role: "group", tabIndex: empty ? -1 : 0, "aria-roledescription": "treemap", "aria-label": `${label}, ${center.label}. Arrow keys move between tiles, Enter zooms in, Escape zooms out.`, onKeyDown: onKeyDown, onBlur: () => setActive(null), onPointerLeave: () => { setActive(null); setPointer(null); }, children: [nodes.map(node => {
368
+ const top = topOf(node);
369
+ return _jsxs("div", { ref: element => { if (element)
370
+ tiles.current.set(node.id, element);
371
+ else
372
+ tiles.current.delete(node.id); }, className: styles.tile, "data-branch": node.children.length > 0 || undefined, "data-active": active === node.id || undefined, "data-lineage": focusTop?.id === node.id && active !== node.id || undefined, "data-dim": focusTop !== null && top !== null && focusTop.id !== top.id || undefined, style: { "--hue": hueOf(branchOf.get(node.id) ?? -1), "--fill": `color-mix(in oklab, ${hueOf(branchOf.get(node.id) ?? -1)} ${shade(node.color)}%, var(--surface))`, zIndex: node.depth }, onPointerMove: event => { event.stopPropagation(); onPointerMove(event, node.id); }, onClick: event => { event.stopPropagation(); onTileClick(node); }, children: [_jsx("span", { className: styles.tileLabel, children: node.label }), _jsx("span", { className: styles.tileValue, children: formatValue(node.value) })] }, node.id);
373
+ }), empty && _jsx("p", { className: styles.message, children: emptyLabel }), _jsx(motion.div, { ref: tip, className: styles.tooltip, style: { x: reduced ? tipX : tipSpringX, y: reduced ? tipY : tipSpringY }, "aria-hidden": "true", children: activeNode && _jsxs(_Fragment, { children: [_jsx("p", { className: styles.tipTitle, children: activeNode.path.slice(0, -1).join(" / ") || root.label }), _jsxs("p", { className: styles.tipValue, children: [_jsx("span", { className: styles.tipName, children: activeNode.label }), _jsx("span", { className: styles.tipAmount, children: formatValue(activeNode.value) })] }), _jsxs("p", { className: styles.tipNote, children: [percent.format(share(activeNode, parentOf(activeNode))), " of ", parentOf(activeNode).label] }), colored && activeNode.color !== null && _jsxs("p", { className: styles.tipNote, children: [colorLabel, " ", formatColor(activeNode.color)] })] }) })] }), colored && _jsxs("div", { className: styles.scale, "aria-hidden": "true", children: [_jsx("span", { className: styles.scaleLabel, children: colorLabel }), _jsx("span", { className: styles.scaleEnd, children: formatColor(lo) }), _jsx("span", { className: styles.steps, children: Array.from({ length: STEPS }, (_, i) => _jsx("span", { className: styles.step, style: { "--fill": `color-mix(in oklab, ${center.depth === 0 ? "var(--foreground)" : hueOf(branchOf.get(center.id) ?? -1)} ${Math.round(14 + (i / (STEPS - 1)) * 44)}%, var(--surface))` } }, i)) }), _jsx("span", { className: styles.scaleEnd, children: formatColor(hi) })] }), _jsx("p", { className: styles.srOnly, "aria-live": "polite", "aria-atomic": "true", children: activeNode ? describe(activeNode) : `${center.label}, ${formatValue(center.value)}` }), !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: "Path" }), _jsx("th", { scope: "col", children: "Value" }), _jsx("th", { scope: "col", children: "Share of parent" }), colored && _jsx("th", { scope: "col", children: colorLabel })] }) }), _jsx("tbody", { children: nodes.map(node => _jsxs("tr", { children: [_jsx("th", { scope: "row", children: node.path.join(" / ") }), _jsx("td", { children: formatValue(node.value) }), _jsx("td", { children: percent.format(share(node, parentOf(node))) }), colored && _jsx("td", { children: node.color === null ? "" : formatColor(node.color) })] }, node.id)) })] }) })] });
374
+ }
375
+ export default Treemap;
@@ -0,0 +1,59 @@
1
+ .figure { display: grid; min-width: 0; gap: var(--space-group); margin: 0; color: var(--foreground); font-family: var(--font-body); letter-spacing: var(--tracking-body); }
2
+
3
+ /* Series colors (--series-1 to --series-4) come from registry/foundation.css. */
4
+
5
+ /* Breadcrumb on the left, the total of what fills the view on the right. */
6
+ .bar { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: var(--space-group); }
7
+ /* Pull the trail back by the crumb padding, so the first label lines up with the tiles and the title. */
8
+ .crumbs { min-width: 0; margin-left: -4px; }
9
+ .crumbList { position: relative; display: flex; min-width: 0; min-height: 32px; flex-wrap: wrap; align-items: center; margin: 0; padding: 0; list-style: none; }
10
+ .crumb { display: inline-flex; align-items: center; min-width: 0; }
11
+ .crumbSep { padding: 0 4px; color: var(--border-strong); font-size: var(--text-sm); }
12
+ .crumbButton { min-height: 32px; padding: 0 6px; margin: 0 -2px; border: 0; border-radius: var(--radius-detail); background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: var(--leading-body); white-space: nowrap; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
13
+ .crumbButton:active { transform: scale(.97); }
14
+ .crumbCurrent { padding: 0 4px; color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); white-space: nowrap; }
15
+ @media (hover: hover) and (pointer: fine) { .crumbButton:hover { background: color-mix(in oklab, var(--foreground) 5%, transparent); color: var(--foreground); } }
16
+ .total { position: relative; display: inline-grid; flex: none; justify-items: end; overflow: hidden; margin: 0; color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); font-variant-numeric: tabular-nums; }
17
+ .totalValue { display: block; }
18
+
19
+ /* Tiles are absolutely placed and sized each frame, so text never scales while a tile grows or shrinks. */
20
+ .stage { position: relative; min-width: 0; overflow: hidden; border-radius: var(--radius-control); outline: none; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; }
21
+ .tile { position: absolute; top: 0; left: 0; display: flex; flex-direction: column; gap: 1px; overflow: hidden; padding: 6px 8px; border-radius: var(--radius-detail); background: var(--fill); color: var(--foreground); cursor: pointer; visibility: hidden; will-change: transform, width, height; transition: background-color var(--duration-considered) var(--ease-standard), color var(--duration-considered) var(--ease-standard), filter var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard); }
22
+ .tile[data-branch][data-level="1"] { padding: 3px 8px; border-radius: var(--radius-detail); background: color-mix(in oklab, var(--hue) 9%, var(--surface)); color: var(--foreground); cursor: zoom-in; }
23
+ .tileLabel, .tileValue { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--text-xs); line-height: 16px; opacity: 0; transition: opacity var(--duration-fast) var(--ease-standard); }
24
+ .tileLabel { font-weight: 500; }
25
+ .tileValue { opacity: 0; font-variant-numeric: tabular-nums; }
26
+ .tile[data-room="name"] .tileLabel, .tile[data-room="full"] .tileLabel, .tile[data-room="full"] .tileValue { opacity: 1; }
27
+ .tile[data-room="full"] .tileValue { opacity: .72; }
28
+ /* A branch on the top level prints its name and total on one header line. */
29
+ .tile[data-branch][data-level="1"] { flex-direction: row; align-items: baseline; justify-content: space-between; gap: 8px; }
30
+ .tile[data-branch][data-level="1"] .tileValue { flex: none; opacity: .6; }
31
+ .tile[data-branch][data-level="1"][data-room="none"] .tileLabel, .tile[data-branch][data-level="1"][data-room="none"] .tileValue { opacity: 0; }
32
+ .tile[data-branch][data-level="1"][data-room="name"] .tileValue { opacity: .6; }
33
+ .tile[data-level="2"] { padding: 5px 7px; }
34
+
35
+ .tile[data-active] { box-shadow: inset 0 0 0 2px var(--foreground); }
36
+ .tile[data-lineage] { box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--hue) 70%, transparent); }
37
+ .stage[data-active] .tile[data-dim] { filter: opacity(.5); }
38
+
39
+ .message { position: absolute; inset: 0; display: grid; place-items: center; margin: 0; color: var(--text-muted); font-size: var(--text-sm); pointer-events: none; }
40
+
41
+ .tooltip { position: absolute; top: 0; left: 0; z-index: 50; display: grid; min-width: 160px; max-width: min(240px, 100%); grid-template-columns: minmax(0, 1fr); gap: 2px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-control); 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); }
42
+ .stage[data-active] .tooltip { opacity: 1; scale: 1; }
43
+ .tipTitle { margin: 0; overflow: hidden; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); text-overflow: ellipsis; white-space: nowrap; }
44
+ .tipValue { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0; font-size: var(--text-sm); line-height: 1.35; }
45
+ .tipName { min-width: 0; overflow: hidden; color: var(--foreground); text-overflow: ellipsis; white-space: nowrap; }
46
+ .tipAmount { flex: none; color: var(--foreground); font-weight: 500; font-variant-numeric: tabular-nums; }
47
+ .tipNote { margin: 0; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); font-variant-numeric: tabular-nums; }
48
+
49
+ /* The color scale: stepped swatches from light to dark, never a gradient. */
50
+ .scale { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; gap: 8px; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); font-variant-numeric: tabular-nums; }
51
+ .scaleLabel { margin-right: 4px; color: var(--text-secondary); }
52
+ .steps { display: inline-flex; gap: 2px; }
53
+ .step { width: 18px; height: 10px; border-radius: var(--radius-detail); background: var(--fill); }
54
+
55
+ .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
56
+
57
+ @media (prefers-reduced-motion: reduce) {
58
+ .tile, .tileLabel, .tileValue, .tooltip, .crumbButton { transition: none; }
59
+ }
@@ -0,0 +1,24 @@
1
+ export interface UsageMeterSegment {
2
+ /** Stable identity. Keep it the same while the value changes, so the segment re-flows instead of being replaced. */
3
+ id: string;
4
+ label: string;
5
+ value: number;
6
+ }
7
+ /** Use a usage meter for a fixed allowance, such as storage or seats, when people need to see what takes the space and how close they are to the limit. */
8
+ export interface UsageMeterProps {
9
+ /** What is measured, such as "Workspace storage". */
10
+ label: string;
11
+ /** Used amounts in display order. Up to four read clearly. */
12
+ segments: UsageMeterSegment[];
13
+ /** The plan limit, in the same unit as the segments. */
14
+ limit: number;
15
+ unit?: string;
16
+ /** Digits after the decimal point. */
17
+ decimals?: number;
18
+ freeLabel?: string;
19
+ overLabel?: string;
20
+ /** Share of the limit at which the meter warns that it is almost full. */
21
+ warnAt?: number;
22
+ }
23
+ export declare function UsageMeter({ label, segments, limit, unit, decimals, freeLabel, overLabel, warnAt }: UsageMeterProps): import("react").JSX.Element;
24
+ export default UsageMeter;