@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,58 @@
1
+ import { type Ref } from "react";
2
+ export interface LineChartSeries {
3
+ /** Stable identity. A series keeps its line across data changes, so switching ranges morphs the path instead of redrawing it. */
4
+ key: string;
5
+ /** Name in the legend, the tooltip, and the data table. */
6
+ label: string;
7
+ /** Any CSS color. Defaults to the accent for the first series, then quieter neutral steps. */
8
+ color?: string;
9
+ /** Draws a dashed stroke, useful for a previous period or a target. */
10
+ dashed?: boolean;
11
+ /** Fills the area under the line with a faint tint of its color. Defaults to true for the first series. */
12
+ area?: boolean;
13
+ }
14
+ export interface LineChartDatum {
15
+ /** Stable identity, such as an ISO date. */
16
+ key: string;
17
+ /** Full label for the tooltip and screen readers, such as "Tue, Sep 15". */
18
+ label: string;
19
+ /** Short label on the x axis, such as "Sep 15". Leave it out to keep the axis quiet at that point. */
20
+ axisLabel?: string;
21
+ /** One value per series key. A missing value counts as zero. */
22
+ values: Record<string, number | undefined>;
23
+ }
24
+ /** Use a line chart for one or more measures over time, when the shape of the trend matters more than any single value. */
25
+ export interface LineChartProps {
26
+ data: LineChartDatum[];
27
+ series: LineChartSeries[];
28
+ /** What is measured, such as "Signups". Names the chart for assistive technology. */
29
+ label: string;
30
+ /** Unit after each value in the tooltip, such as "ms". */
31
+ unit?: string;
32
+ /** Plot height in pixels. The width follows the container. */
33
+ height?: number;
34
+ /** Formats values in the tooltip and data table. */
35
+ formatValue?: (value: number, series: LineChartSeries) => string;
36
+ /** Formats the value axis. Defaults to a compact number, such as 1.2K. */
37
+ formatTick?: (value: number) => string;
38
+ /** Controlled hidden series keys. */
39
+ hiddenSeries?: string[];
40
+ defaultHiddenSeries?: string[];
41
+ onHiddenSeriesChange?: (hidden: string[]) => void;
42
+ /** Called as the crosshair moves, and with null when it leaves. Use it to drive a headline readout. */
43
+ onActiveChange?: (index: number | null, datum: LineChartDatum | null) => void;
44
+ /** Keeps the current lines on screen, dimmed, while the next range loads. */
45
+ loading?: boolean;
46
+ /** Shown when there are no points. */
47
+ emptyLabel?: string;
48
+ /** Series toggles above the plot. Shown by default when there is more than one series. */
49
+ legend?: boolean;
50
+ /** Header of the first column in the data table read by screen readers. */
51
+ categoryLabel?: string;
52
+ /** Smooth monotone curves that never swing past the data, or straight segments. */
53
+ curve?: "smooth" | "linear";
54
+ ref?: Ref<HTMLElement>;
55
+ className?: string;
56
+ }
57
+ export declare function LineChart({ data, series, label, unit, height, formatValue, formatTick, hiddenSeries, defaultHiddenSeries, onHiddenSeriesChange, onActiveChange, loading, emptyLabel, legend, categoryLabel, curve, ref, className }: LineChartProps): import("react").JSX.Element;
58
+ export default LineChart;
@@ -0,0 +1,383 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
+ import { AnimatePresence, animate, motion, useInView, useMotionValue, useMotionValueEvent, useReducedMotion, useSpring, useTransform } from "motion/react";
5
+ import { motionTokens } from "../../lib/motion-tokens.js";
6
+ import styles from "./line-chart.module.css";
7
+ const { spring, duration, ease } = motionTokens;
8
+ /** Motion drops inherited velocity on time-defined springs, so values that retarget mid-flight run the same springs written as stiffness and damping. */
9
+ const physical = ({ visualDuration, bounce }, restDelta = .001) => { const root = (2 * Math.PI) / (visualDuration * 1.2); return { type: "spring", stiffness: root * root, damping: 2 * (1 - bounce) * root, restDelta, restSpeed: restDelta * 2 }; };
10
+ const settle = physical(spring.smooth);
11
+ const glide = physical(spring.snappy, .0001);
12
+ const follow = { stiffness: glide.stiffness, damping: glide.damping, restDelta: .01 };
13
+ const draw = { duration: duration.considered * 1.75, ease: [...ease.inOut] };
14
+ const fadeFast = { duration: duration.instant, ease: [...ease.standard] };
15
+ /** Room above the top gridline, and the gap between the crosshair and its tooltip. */
16
+ const TOP = 12, GAP = 14;
17
+ const PALETTE = ["var(--accent)", "color-mix(in oklab, var(--foreground) 46%, var(--surface))", "color-mix(in oklab, var(--foreground) 26%, var(--surface))"];
18
+ const grouped = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
19
+ const compact = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 });
20
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
21
+ const colorOf = (series, index) => series.color ?? PALETTE[Math.min(index, PALETTE.length - 1)];
22
+ const clean = (value) => Number(value.toPrecision(12));
23
+ /** Clean gridlines: the smallest step of 1, 2, 2.5, or 5 that covers the data in four rows or fewer, from zero unless the data dips below it. */
24
+ function niceScale(low, high) {
25
+ const lo = Math.min(0, low), hi = Math.max(high, lo + 1);
26
+ const span = hi - lo, magnitude = 10 ** Math.floor(Math.log10(span / 4));
27
+ for (const factor of [1, 2, 2.5, 5, 10]) {
28
+ const step = factor * magnitude, bottom = Math.floor(lo / step) * step, top = Math.ceil(hi / step) * step;
29
+ if ((top - bottom) / step <= 4 + 1e-9) {
30
+ const ticks = [];
31
+ for (let value = bottom; value <= top + step / 2; value += step)
32
+ ticks.push(clean(value));
33
+ return { min: clean(bottom), max: clean(top), ticks };
34
+ }
35
+ }
36
+ return { min: lo, max: hi, ticks: [lo, hi] };
37
+ }
38
+ /** The line through every value, sampled densely on x from 0 to 1 in data units. Monotone cubic tangents keep a smooth curve from swinging past the data. */
39
+ function curveFor(values, smooth) {
40
+ if (values.length === 0)
41
+ return [];
42
+ if (values.length === 1)
43
+ return [[0, values[0]], [1, values[0]]];
44
+ const last = values.length - 1, step = 1 / last;
45
+ if (!smooth)
46
+ return values.map((value, index) => [index * step, value]);
47
+ const slopes = values.slice(1).map((value, index) => (value - values[index]) / step);
48
+ const tangents = values.map((_, index) => index === 0 || index === last ? 0 : (Math.sign(slopes[index - 1]) + Math.sign(slopes[index])) * Math.min(Math.abs(slopes[index - 1]), Math.abs(slopes[index]), Math.abs(slopes[index - 1] + slopes[index]) / 4) || 0);
49
+ if (last > 1) {
50
+ tangents[0] = (3 * slopes[0] - tangents[1]) / 2;
51
+ tangents[last] = (3 * slopes[last - 1] - tangents[last - 1]) / 2;
52
+ }
53
+ else {
54
+ tangents[0] = slopes[0];
55
+ tangents[1] = slopes[0];
56
+ }
57
+ const samples = Math.max(1, Math.min(16, Math.round(192 / last)));
58
+ const points = [];
59
+ for (let index = 0; index < last; index++) {
60
+ const low = Math.min(values[index], values[index + 1]), high = Math.max(values[index], values[index + 1]);
61
+ for (let sample = 0; sample < samples; sample++) {
62
+ const t = sample / samples, t2 = t * t, t3 = t2 * t;
63
+ const value = (2 * t3 - 3 * t2 + 1) * values[index] + (t3 - 2 * t2 + t) * step * tangents[index] + (3 * t2 - 2 * t3) * values[index + 1] + (t3 - t2) * step * tangents[index + 1];
64
+ points.push([(index + t) * step, clamp(value, low, high)]);
65
+ }
66
+ }
67
+ points.push([1, values[last]]);
68
+ return points;
69
+ }
70
+ /** Value of a sampled line at x, straight between samples. */
71
+ function valueAt(shape, x) {
72
+ if (!shape.length)
73
+ return 0;
74
+ let low = 0, high = shape.length - 1;
75
+ if (x <= shape[low][0])
76
+ return shape[low][1];
77
+ if (x >= shape[high][0])
78
+ return shape[high][1];
79
+ while (high - low > 1) {
80
+ const middle = (low + high) >> 1;
81
+ if (shape[middle][0] < x)
82
+ low = middle;
83
+ else
84
+ high = middle;
85
+ }
86
+ const [x0, y0] = shape[low], [x1, y1] = shape[high];
87
+ return x1 === x0 ? y1 : y0 + ((y1 - y0) * (x - x0)) / (x1 - x0);
88
+ }
89
+ /** Tracks are mutable animation state read by the painter, never by render. */
90
+ const update = (track, patch) => Object.assign(track, patch);
91
+ const grid = Array.from({ length: 241 }, (_, index) => index / 240);
92
+ const subscribeNothing = () => () => { };
93
+ /** Reduced motion is only known in the browser, so the first client render matches the server before it takes effect. */
94
+ function useReducedMotionSafe() {
95
+ const hydrated = useSyncExternalStore(subscribeNothing, () => true, () => false);
96
+ return !!useReducedMotion() && hydrated;
97
+ }
98
+ function useRowY(value, scale, height) {
99
+ return useTransform(() => { const span = scale.max.get() - scale.min.get() || 1; return Math.round(TOP + (1 - (value - scale.min.get()) / span) * (height - TOP)) + .5; });
100
+ }
101
+ /** A gridline rides the scale, so a taller range slides it down while new rows fade in above. */
102
+ function Gridline({ value, scale, height }) {
103
+ const y = useRowY(value, scale, height);
104
+ return _jsx(motion.line, { className: styles.grid, x1: 0, x2: "100%", y1: y, y2: y, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: fadeFast }, transition: { duration: duration.standard, ease: [...ease.standard] } });
105
+ }
106
+ function TickLabel({ value, scale, height, format }) {
107
+ const y = useRowY(value, scale, height);
108
+ return _jsx(motion.span, { className: styles.tick, style: { y }, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: fadeFast }, transition: { duration: duration.standard, ease: [...ease.standard] }, children: _jsx("span", { className: styles.tickText, children: format(value) }) });
109
+ }
110
+ /** Axis labels that fit: at most one per 76 pixels, counted back from the latest so the newest date always shows. */
111
+ function axisPicks(data, width) {
112
+ const labeled = data.flatMap((item, index) => item.axisLabel ? [index] : []);
113
+ if (!width || !labeled.length)
114
+ return [];
115
+ const room = Math.max(2, Math.floor(width / 76));
116
+ const stride = Math.ceil(labeled.length / room);
117
+ return labeled.reverse().filter((_, rank) => rank % stride === 0).reverse();
118
+ }
119
+ export function LineChart({ data, series, label, unit = "", height = 220, formatValue, formatTick = value => compact.format(value), hiddenSeries, defaultHiddenSeries, onHiddenSeriesChange, onActiveChange, loading = false, emptyLabel = "No data for this range", legend, categoryLabel = "Date", curve = "smooth", ref, className }) {
120
+ const reduced = useReducedMotionSafe();
121
+ const figure = useRef(null);
122
+ const plot = useRef(null);
123
+ const tip = useRef(null);
124
+ const clip = useRef(null);
125
+ const crosshair = useRef(null);
126
+ const lines = useRef(new Map());
127
+ const areas = useRef(new Map());
128
+ const dots = useRef(new Map());
129
+ const groups = useRef(new Map());
130
+ useImperativeHandle(ref, () => figure.current);
131
+ const inView = useInView(figure, { once: true, amount: .3 });
132
+ const uid = `line${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
133
+ const last = data.length - 1;
134
+ const empty = data.length === 0;
135
+ // Hidden series, controlled or not.
136
+ const [ownHidden, setOwnHidden] = useState(defaultHiddenSeries ?? []);
137
+ const hidden = hiddenSeries ?? ownHidden;
138
+ const toggle = (key) => {
139
+ const next = hidden.includes(key) ? hidden.filter(item => item !== key) : [...hidden, key];
140
+ if (hiddenSeries === undefined)
141
+ setOwnHidden(next);
142
+ onHiddenSeriesChange?.(next);
143
+ };
144
+ const visible = series.filter(item => !hidden.includes(item.key));
145
+ const showLegend = legend ?? series.length > 1;
146
+ // Each series becomes a dense curve in data units; the scale fits only the series on show.
147
+ const signature = `${curve}|${series.map(item => item.key).join(",")}|${data.map(item => `${item.key}:${series.map(line => item.values[line.key] ?? 0).join(",")}`).join(";")}`;
148
+ const targets = useMemo(() => new Map(series.map(line => [line.key, curveFor(data.map(item => item.values[line.key] ?? 0), curve === "smooth")])), [signature]); // eslint-disable-line react-hooks/exhaustive-deps
149
+ let low = Infinity, high = -Infinity;
150
+ for (const line of visible)
151
+ for (const item of data) {
152
+ const value = item.values[line.key] ?? 0;
153
+ low = Math.min(low, value);
154
+ high = Math.max(high, value);
155
+ }
156
+ const range = Number.isFinite(low) ? niceScale(low, high) : null;
157
+ const [steady, setSteady] = useState(() => range ?? niceScale(0, 100));
158
+ if (range && (range.min !== steady.min || range.max !== steady.max))
159
+ setSteady(range);
160
+ const scale = { min: useMotionValue(steady.min), max: useMotionValue(steady.max) };
161
+ // Scrubbing: a new range lets go of the crosshair.
162
+ const [active, setActive] = useState(null);
163
+ const [seenSignature, setSeenSignature] = useState(signature);
164
+ if (seenSignature !== signature) {
165
+ setSeenSignature(signature);
166
+ setActive(null);
167
+ }
168
+ const index = active === null || empty ? null : Math.min(active, last);
169
+ const scrubbing = index !== null;
170
+ const width = useRef(0);
171
+ const [plotWidth, setPlotWidth] = useState(0);
172
+ const tipSize = useRef({ w: 0, h: 0 });
173
+ const tracks = useRef(new Map());
174
+ const drawn = useMotionValue(0);
175
+ const cursor = useMotionValue(1);
176
+ const tipTargetX = useMotionValue(0), tipTargetY = useMotionValue(0);
177
+ const tipSpringX = useSpring(tipTargetX, follow), tipSpringY = useSpring(tipTargetY, follow);
178
+ const live = useRef({ series, hidden });
179
+ useLayoutEffect(() => { live.current = { series, hidden }; });
180
+ // One writer for every drawn attribute: morphs, toggles, the scale, the crosshair, and the measured size all repaint through here.
181
+ const paint = useCallback(() => {
182
+ const w = width.current;
183
+ if (!w)
184
+ return;
185
+ const lo = scale.min.get(), span = scale.max.get() - lo || 1;
186
+ const toY = (value) => TOP + (1 - (value - lo) / span) * (height - TOP);
187
+ const at = cursor.get();
188
+ const dotYs = [];
189
+ for (const line of live.current.series) {
190
+ const track = tracks.current.get(line.key);
191
+ if (!track)
192
+ continue;
193
+ const p = track.presence;
194
+ const y = (value) => toY(lo + (value - lo) * p);
195
+ const path = track.shape.length ? `M${track.shape.map(([x, value]) => `${(x * w).toFixed(1)},${y(value).toFixed(1)}`).join("L")}` : "";
196
+ lines.current.get(line.key)?.setAttribute("d", path);
197
+ areas.current.get(line.key)?.setAttribute("d", path ? `${path}L${w},${height}L0,${height}Z` : "");
198
+ const group = groups.current.get(line.key);
199
+ if (group)
200
+ group.style.opacity = String(p);
201
+ const dot = dots.current.get(line.key);
202
+ if (dot && track.shape.length) {
203
+ const cy = y(valueAt(track.shape, at));
204
+ dot.setAttribute("cx", (at * w).toFixed(1));
205
+ dot.setAttribute("cy", cy.toFixed(1));
206
+ if (p > .5)
207
+ dotYs.push(cy);
208
+ }
209
+ }
210
+ clip.current?.setAttribute("width", String(drawn.get() * (w + 16)));
211
+ const cx = Math.round(at * w) + .5;
212
+ crosshair.current?.setAttribute("x1", String(cx));
213
+ crosshair.current?.setAttribute("x2", String(cx));
214
+ // The tooltip sits beside the crosshair, flips sides before it would leave the plot, and centres on the points it describes.
215
+ const { w: tw, h: th } = tipSize.current;
216
+ let x = cx + GAP;
217
+ if (x + tw > w)
218
+ x = cx - GAP - tw;
219
+ tipTargetX.set(clamp(x, 0, Math.max(0, w - tw)));
220
+ const middle = dotYs.length ? dotYs.reduce((sum, value) => sum + value, 0) / dotYs.length : height / 2;
221
+ tipTargetY.set(clamp(middle - th / 2, 0, Math.max(0, height - th)));
222
+ }, [cursor, drawn, height, scale.max, scale.min, tipTargetX, tipTargetY]);
223
+ useMotionValueEvent(cursor, "change", paint);
224
+ useMotionValueEvent(drawn, "change", paint);
225
+ useMotionValueEvent(scale.min, "change", paint);
226
+ useMotionValueEvent(scale.max, "change", paint);
227
+ useLayoutEffect(paint);
228
+ // The plot draws in its own pixels: measure the width, follow resizes, and size the tooltip for its flip.
229
+ useLayoutEffect(() => {
230
+ const node = plot.current, bubble = tip.current;
231
+ if (!node)
232
+ return;
233
+ const read = () => { width.current = node.clientWidth; setPlotWidth(node.clientWidth); if (bubble)
234
+ tipSize.current = { w: bubble.offsetWidth, h: bubble.offsetHeight }; paint(); };
235
+ read();
236
+ if (typeof ResizeObserver === "undefined")
237
+ return;
238
+ const observer = new ResizeObserver(read);
239
+ observer.observe(node);
240
+ if (bubble)
241
+ observer.observe(bubble);
242
+ return () => observer.disconnect();
243
+ }, [paint]);
244
+ // New data morphs every line from the shape on screen, sampled on shared x positions so a range that gains or loses points still flows.
245
+ const morph = useRef(null);
246
+ useEffect(() => {
247
+ const moves = [];
248
+ for (const [key, target] of targets) {
249
+ const track = tracks.current.get(key);
250
+ if (!track) {
251
+ tracks.current.set(key, { shape: target, presence: live.current.hidden.includes(key) ? 0 : 1, fade: null });
252
+ continue;
253
+ }
254
+ if (!track.shape.length || !target.length || reduced) {
255
+ update(track, { shape: target });
256
+ continue;
257
+ }
258
+ const xs = [...new Set([...grid, ...target.map(([x]) => x)])].sort((a, b) => a - b);
259
+ moves.push({ track, xs, from: xs.map(x => valueAt(track.shape, x)), to: xs.map(x => valueAt(target, x)), target });
260
+ }
261
+ morph.current?.stop();
262
+ if (!moves.length) {
263
+ paint();
264
+ return;
265
+ }
266
+ morph.current = animate(0, 1, { ...settle, restDelta: .0005, onUpdate: progress => { for (const move of moves)
267
+ update(move.track, { shape: move.xs.map((x, at) => [x, move.from[at] + (move.to[at] - move.from[at]) * progress]) }); paint(); }, onComplete: () => { for (const move of moves)
268
+ update(move.track, { shape: move.target }); paint(); } });
269
+ return () => morph.current?.stop();
270
+ }, [paint, reduced, targets]);
271
+ // A hidden series flattens into the baseline as it fades; the ones that stay rescale around it.
272
+ const hiddenKey = hidden.join("|");
273
+ useEffect(() => {
274
+ for (const [key, track] of tracks.current) {
275
+ const to = hidden.includes(key) ? 0 : 1;
276
+ if (track.presence === to)
277
+ continue;
278
+ track.fade?.stop();
279
+ if (reduced) {
280
+ update(track, { presence: to });
281
+ continue;
282
+ }
283
+ update(track, { fade: animate(track.presence, to, { ...settle, onUpdate: value => { update(track, { presence: value }); paint(); } }) });
284
+ }
285
+ paint();
286
+ }, [hiddenKey, paint, reduced]); // eslint-disable-line react-hooks/exhaustive-deps
287
+ useEffect(() => {
288
+ if (reduced) {
289
+ scale.min.jump(steady.min);
290
+ scale.max.jump(steady.max);
291
+ return;
292
+ }
293
+ const controls = [animate(scale.min, steady.min, settle), animate(scale.max, steady.max, settle)];
294
+ return () => controls.forEach(control => control.stop());
295
+ }, [reduced, scale.max, scale.min, steady]);
296
+ // The lines draw left to right the first time the chart is seen with data.
297
+ useEffect(() => {
298
+ if (empty)
299
+ return;
300
+ if (reduced) {
301
+ drawn.jump(1);
302
+ return;
303
+ }
304
+ if (!inView || drawn.get() >= 1)
305
+ return;
306
+ const controls = animate(drawn, 1, draw);
307
+ return () => controls.stop();
308
+ }, [drawn, empty, inView, reduced]);
309
+ // The crosshair appears on the first scrubbed point and glides between the ones after it; the tooltip follows on its own spring.
310
+ const wasScrubbing = useRef(false);
311
+ useEffect(() => {
312
+ if (index === null) {
313
+ wasScrubbing.current = false;
314
+ return;
315
+ }
316
+ const to = last > 0 ? index / last : .5;
317
+ if (reduced || !wasScrubbing.current) {
318
+ cursor.jump(to);
319
+ tipSpringX.jump(tipTargetX.get());
320
+ tipSpringY.jump(tipTargetY.get());
321
+ }
322
+ wasScrubbing.current = true;
323
+ if (reduced)
324
+ return;
325
+ const controls = animate(cursor, to, glide);
326
+ return () => controls.stop();
327
+ }, [cursor, index, last, reduced, tipSpringX, tipSpringY, tipTargetX, tipTargetY]);
328
+ const onActive = useRef(onActiveChange);
329
+ useLayoutEffect(() => { onActive.current = onActiveChange; });
330
+ useEffect(() => { onActive.current?.(index, index === null ? null : data[index] ?? null); }, [index]); // eslint-disable-line react-hooks/exhaustive-deps
331
+ const pointAt = (clientX) => {
332
+ const rect = plot.current?.getBoundingClientRect();
333
+ if (!rect?.width || last < 1)
334
+ return Math.max(0, last);
335
+ return clamp(Math.round(((clientX - rect.left) / rect.width) * last), 0, last);
336
+ };
337
+ const onKeyDown = (event) => {
338
+ if (event.key === "Escape" && scrubbing) {
339
+ event.preventDefault();
340
+ setActive(null);
341
+ return;
342
+ }
343
+ const from = index ?? last + 1, page = Math.max(1, Math.round(data.length / 6));
344
+ const next = { ArrowLeft: from - 1, ArrowDown: from - 1, ArrowRight: index === null ? last : from + 1, ArrowUp: index === null ? last : from + 1, PageDown: from - page, PageUp: from + page, Home: 0, End: last }[event.key];
345
+ if (next === undefined || empty)
346
+ return;
347
+ event.preventDefault();
348
+ setActive(clamp(next, 0, last));
349
+ };
350
+ const onPointerDown = (event) => {
351
+ if (empty)
352
+ return;
353
+ if (event.pointerType !== "mouse")
354
+ event.currentTarget.setPointerCapture?.(event.pointerId);
355
+ setActive(pointAt(event.clientX));
356
+ };
357
+ const onPointerMove = (event) => { if (!empty && (event.pointerType === "mouse" || event.buttons))
358
+ setActive(pointAt(event.clientX)); };
359
+ const format = (value, line) => `${formatValue ? formatValue(value, line) : grouped.format(value)}${unit ? ` ${unit}` : ""}`;
360
+ const reading = index === null ? null : data[index];
361
+ const valueText = reading ? `${reading.label}: ${visible.map(line => `${line.label} ${format(reading.values[line.key] ?? 0, line)}`).join(", ")}` : empty ? emptyLabel : `${visible.length} of ${series.length} series shown`;
362
+ const summary = empty ? `${label}. ${emptyLabel}.` : `${label}, ${data[0].label} to ${data[last].label}. ${visible.map(line => `${line.label}, latest ${format(data[last].values[line.key] ?? 0, line)}`).join(". ")}.`;
363
+ const picks = axisPicks(data, plotWidth);
364
+ const allHidden = !empty && visible.length === 0;
365
+ const tipX = reduced ? tipTargetX : tipSpringX, tipY = reduced ? tipTargetY : tipSpringY;
366
+ return _jsxs("figure", { ref: figure, className: [styles.figure, className].filter(Boolean).join(" "), "aria-label": label, "aria-busy": loading || undefined, children: [showLegend && _jsx("div", { className: styles.legend, role: "group", "aria-label": `${label} series`, children: series.map((line, at) => _jsxs("button", { type: "button", className: styles.toggle, "aria-pressed": !hidden.includes(line.key), onClick: () => toggle(line.key), style: { "--series": colorOf(line, at) }, children: [_jsx("span", { className: styles.swatch, "data-dashed": line.dashed || undefined, "aria-hidden": "true" }), _jsx("span", { className: styles.toggleLabel, children: line.label })] }, line.key)) }), _jsxs("div", { className: styles.chart, "data-scrubbing": scrubbing || undefined, "data-loading": loading || undefined, children: [_jsxs("div", { ref: plot, className: styles.plot, style: { height }, role: "slider", tabIndex: empty ? -1 : 0, "aria-label": `${label}, explore by ${categoryLabel.toLowerCase()}`, "aria-orientation": "horizontal", "aria-valuemin": 1, "aria-valuemax": Math.max(1, data.length), "aria-valuenow": (index ?? last) + 1, "aria-valuetext": valueText, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: event => { if (event.pointerType !== "mouse")
367
+ setActive(null); }, onPointerCancel: () => setActive(null), onPointerLeave: event => { if (event.pointerType === "mouse")
368
+ setActive(null); }, onKeyDown: onKeyDown, onBlur: () => setActive(null), onFocus: event => { if (event.currentTarget.matches(":focus-visible") && !empty)
369
+ setActive(current => current ?? last); }, children: [_jsxs("svg", { className: styles.svg, width: "100%", height: height, "aria-hidden": "true", focusable: "false", children: [_jsx("defs", { children: _jsx("clipPath", { id: `${uid}-draw`, clipPathUnits: "userSpaceOnUse", children: _jsx("rect", { ref: clip, x: -8, y: -16, width: 0, height: height + 32 }) }) }), _jsx(AnimatePresence, { initial: false, children: steady.ticks.slice(1).map(value => _jsx(Gridline, { value: value, scale: scale, height: height }, value)) }), _jsx("line", { className: styles.baseline, x1: 0, x2: "100%", y1: height - .5, y2: height - .5 }), _jsx("line", { ref: crosshair, className: styles.crosshair, y1: TOP - 4, y2: height }), _jsx("g", { className: styles.series, clipPath: `url(#${uid}-draw)`, children: series.map((line, at) => _jsxs("g", { ref: node => { if (node)
370
+ groups.current.set(line.key, node);
371
+ else
372
+ groups.current.delete(line.key); }, style: { "--series": colorOf(line, at) }, children: [(line.area ?? at === 0) && _jsx("path", { ref: node => { if (node)
373
+ areas.current.set(line.key, node);
374
+ else
375
+ areas.current.delete(line.key); }, className: styles.area }), _jsx("path", { ref: node => { if (node)
376
+ lines.current.set(line.key, node);
377
+ else
378
+ lines.current.delete(line.key); }, className: styles.line, "data-dashed": line.dashed || undefined })] }, line.key)) }), series.map((line, at) => _jsx("circle", { ref: node => { if (node)
379
+ dots.current.set(line.key, node);
380
+ else
381
+ dots.current.delete(line.key); }, className: styles.dot, r: 4, "data-hidden": hidden.includes(line.key) || undefined, style: { "--series": colorOf(line, at) } }, line.key))] }), _jsxs(motion.div, { ref: tip, className: styles.tooltip, style: { x: tipX, y: tipY }, "aria-hidden": "true", children: [_jsx("p", { className: styles.tipTitle, children: (reading ?? data[last])?.label ?? "" }), visible.map(line => _jsxs("p", { className: styles.tipRow, style: { "--series": colorOf(line, series.indexOf(line)) }, children: [_jsx("span", { className: styles.tipSwatch, "data-dashed": line.dashed || undefined }), _jsx("span", { className: styles.tipName, children: line.label }), _jsx("span", { className: styles.tipValue, children: format((reading ?? data[last])?.values[line.key] ?? 0, line) })] }, line.key))] }), _jsxs(AnimatePresence, { initial: false, children: [(empty || allHidden) && !loading && _jsx(motion.p, { className: styles.message, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: fadeFast }, children: empty ? emptyLabel : "Choose a series to show" }, empty ? "empty" : "hidden"), loading && empty && _jsx(motion.span, { className: styles.skeleton, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: fadeFast }, "aria-hidden": "true" }, "skeleton")] })] }), _jsx("div", { className: styles.gutter, "aria-hidden": "true", children: _jsx(AnimatePresence, { initial: false, children: steady.ticks.map(value => _jsx(TickLabel, { value: value, scale: scale, height: height, format: formatTick }, value)) }) }), _jsx("div", { className: styles.axis, "aria-hidden": "true", children: _jsx(AnimatePresence, { initial: false, children: picks.map(at => { const share = last > 0 ? at / last : .5; return _jsx(motion.span, { className: styles.axisLabel, style: { left: `${share * 100}%`, translateX: `${-share * 100}%` }, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0, transition: fadeFast }, transition: { duration: duration.standard, ease: [...ease.standard] }, children: data[at].axisLabel }, `${data[at].key}:${data[at].axisLabel}`); }) }) })] }), _jsx("p", { className: styles.srOnly, children: summary }), !empty && _jsxs("table", { className: styles.srOnly, children: [_jsx("caption", { children: label }), _jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: categoryLabel }), series.map(line => _jsx("th", { scope: "col", children: line.label }, line.key))] }) }), _jsx("tbody", { children: data.map(item => _jsxs("tr", { children: [_jsx("th", { scope: "row", children: item.label }), series.map(line => _jsx("td", { children: format(item.values[line.key] ?? 0, line) }, line.key))] }, item.key)) })] }), _jsx("p", { className: styles.srOnly, "aria-live": "polite", "aria-atomic": "true", children: loading ? "Loading" : "" })] });
382
+ }
383
+ export default LineChart;
@@ -0,0 +1,60 @@
1
+ .figure { display: grid; min-width: 0; gap: var(--space-inset); margin: 0; color: var(--foreground); font-family: var(--font-body); letter-spacing: var(--tracking-body); }
2
+
3
+ /* Series toggles: a plain line swatch beside each name. A hidden series keeps its place, its swatch empties and its name steps back. */
4
+ .legend { display: flex; flex-wrap: wrap; gap: 2px 4px; min-width: 0; margin-inline: -8px; }
5
+ .toggle { display: inline-flex; min-height: 32px; align-items: center; gap: 8px; padding: 0 10px 0 8px; border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; color: var(--text-secondary); cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: var(--leading-body); -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); }
6
+ .toggle:active { transform: scale(.97); }
7
+ .toggle[aria-pressed="false"] { color: var(--text-muted); }
8
+ .toggleLabel { white-space: nowrap; transition: opacity var(--duration-fast) var(--ease-standard); }
9
+ .toggle[aria-pressed="false"] .toggleLabel { opacity: .72; text-decoration: line-through; text-decoration-color: color-mix(in oklab, currentColor 55%, transparent); }
10
+ .swatch { position: relative; width: 14px; height: 10px; flex: none; }
11
+ .swatch::before { position: absolute; top: 50%; left: 0; width: 100%; height: 2.5px; border-radius: var(--radius-full); corner-shape: round; background: var(--series); content: ""; transform: translateY(-50%) scaleX(1); transform-origin: left; transition: transform var(--duration-standard) var(--ease-spring), opacity var(--duration-fast) var(--ease-standard); }
12
+ .swatch[data-dashed]::before { background: repeating-linear-gradient(to right, var(--series) 0 4px, transparent 4px 7px); }
13
+ .toggle[aria-pressed="false"] .swatch::before { opacity: .45; transform: translateY(-50%) scaleX(.35); }
14
+ @media (hover: hover) and (pointer: fine) { .toggle:hover { background: color-mix(in oklab, var(--foreground) 5%, transparent); color: var(--foreground); } }
15
+
16
+ /* Plot, right-hand value axis, and the date axis beneath. */
17
+ .chart { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 48px; grid-template-rows: auto 26px; min-width: 0; }
18
+ .plot { position: relative; grid-area: 1 / 1; min-width: 0; outline: none; touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; }
19
+ .svg { display: block; overflow: visible; }
20
+ .grid { stroke: var(--border); stroke-width: 1; shape-rendering: crispEdges; }
21
+ .baseline { stroke: var(--border-strong); stroke-width: 1; shape-rendering: crispEdges; }
22
+ .crosshair { stroke: var(--border-strong); stroke-width: 1; shape-rendering: crispEdges; opacity: 0; transition: opacity var(--duration-instant) var(--ease-standard); }
23
+ .chart[data-scrubbing] .crosshair { opacity: 1; }
24
+
25
+ .series { transition: opacity var(--duration-standard) var(--ease-standard); }
26
+ .chart[data-loading] .series { opacity: .32; animation: breathe 1.4s var(--ease-in-out) infinite alternate; }
27
+ .line { fill: none; stroke: var(--series); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; transition: stroke var(--duration-standard) var(--ease-standard); }
28
+ .line[data-dashed] { stroke-dasharray: 4 5; stroke-width: 1.75; }
29
+ .area { fill: color-mix(in oklab, var(--series) 9%, transparent); stroke: none; }
30
+ .dot { fill: var(--series); stroke: var(--surface); stroke-width: 2; opacity: 0; transition: opacity var(--duration-instant) var(--ease-standard); }
31
+ .chart[data-scrubbing] .dot:not([data-hidden]) { opacity: 1; }
32
+
33
+ /* The tooltip floats over the plot, so it is the one layer here with a shadow. Springs carry its position; opacity and a small scale bring it in. */
34
+ .tooltip { position: absolute; top: 0; left: 0; z-index: 1; display: grid; min-width: 140px; max-width: min(220px, 100%); gap: 4px; padding: 10px 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); }
35
+ .chart[data-scrubbing] .tooltip { opacity: 1; scale: 1; }
36
+ .tipTitle { margin: 0 0 2px; color: var(--text-secondary); font-size: var(--text-xs); line-height: var(--leading-body); white-space: nowrap; font-variant-numeric: tabular-nums; }
37
+ .tipRow { display: flex; min-width: 0; align-items: center; gap: 8px; margin: 0; font-size: var(--text-sm); line-height: var(--leading-body); }
38
+ .tipSwatch { width: 10px; height: 2.5px; flex: none; border-radius: var(--radius-full); corner-shape: round; background: var(--series); }
39
+ .tipSwatch[data-dashed] { background: repeating-linear-gradient(to right, var(--series) 0 3px, transparent 3px 5px); }
40
+ .tipName { min-width: 0; flex: 1; overflow: hidden; color: var(--text-secondary); text-overflow: ellipsis; white-space: nowrap; }
41
+ .tipValue { flex: none; margin-left: 8px; color: var(--foreground); font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; }
42
+
43
+ .message { position: absolute; inset: 0; display: grid; place-items: center; margin: 0; color: var(--text-muted); font-size: var(--text-sm); pointer-events: none; }
44
+ /* First load with nothing to keep on screen: a calm band that breathes where the lines will be. */
45
+ .skeleton { position: absolute; inset: 30% 0 25%; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: linear-gradient(to bottom, transparent, color-mix(in oklab, var(--foreground) 5%, transparent)); animation: breathe 1.4s var(--ease-in-out) infinite alternate; }
46
+
47
+ .gutter { position: relative; grid-area: 1 / 2; min-width: 0; }
48
+ .tick { position: absolute; top: 0; left: 10px; display: block; height: 0; white-space: nowrap; pointer-events: none; }
49
+ .tickText { display: block; transform: translateY(-50%); color: var(--text-muted); font-size: var(--text-xs); line-height: 1; font-variant-numeric: tabular-nums; }
50
+
51
+ .axis { position: relative; grid-area: 2 / 1; min-width: 0; }
52
+ .axisLabel { position: absolute; top: 8px; color: var(--text-muted); font-size: var(--text-xs); line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums; }
53
+
54
+ .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
55
+
56
+ @keyframes breathe { from { opacity: .22; } to { opacity: .45; } }
57
+ @media (prefers-reduced-motion: reduce) {
58
+ .toggle, .swatch::before, .line, .tooltip, .crosshair, .dot, .series { transition: none; }
59
+ .chart[data-loading] .series, .skeleton { animation: none; }
60
+ }
@@ -0,0 +1,72 @@
1
+ export type MentionKind = "person" | "channel";
2
+ export interface MentionPerson {
3
+ id: string;
4
+ name: string;
5
+ /** One short line under the name, such as a job title. */
6
+ role?: string;
7
+ /** Portrait URL. Initials show when it is missing. */
8
+ avatar?: string;
9
+ }
10
+ export interface MentionChannel {
11
+ id: string;
12
+ name: string;
13
+ description?: string;
14
+ members?: number;
15
+ }
16
+ /** A mention inside `text`. `start` and `end` are character offsets; the text between them is `@label` or `#label`. */
17
+ export interface Mention {
18
+ kind: MentionKind;
19
+ id: string;
20
+ label: string;
21
+ start: number;
22
+ end: number;
23
+ }
24
+ export interface MentionValue {
25
+ text: string;
26
+ mentions: Mention[];
27
+ }
28
+ export interface MentionInputHandle {
29
+ focus: () => void;
30
+ /** Inserts text at the caret, as if typed. */
31
+ insert: (text: string) => void;
32
+ /** Types a trigger at the caret so the suggestions open, adding a space first when needed. */
33
+ openSuggestions: (kind: MentionKind) => void;
34
+ clear: () => void;
35
+ textarea: HTMLTextAreaElement | null;
36
+ }
37
+ export interface MentionInputProps {
38
+ value?: MentionValue;
39
+ defaultValue?: MentionValue;
40
+ onChange?: (value: MentionValue) => void;
41
+ /** People offered after `@`. Leave it out to turn person mentions off. */
42
+ people?: MentionPerson[];
43
+ /** Channels offered after `#`. Leave it out to turn channel mentions off. */
44
+ channels?: MentionChannel[];
45
+ /** Called when a suggestion becomes a mention. */
46
+ onMentionAdd?: (mention: Mention) => void;
47
+ /** With `submitOnEnter`, Enter submits and Shift+Enter adds a line. */
48
+ onSubmit?: (value: MentionValue) => void;
49
+ submitOnEnter?: boolean;
50
+ placeholder?: string;
51
+ minRows?: number;
52
+ /** The field grows to this many rows, then scrolls. */
53
+ maxRows?: number;
54
+ /** Where suggestions open. `auto` flips above when there is no room below. */
55
+ placement?: "auto" | "top" | "bottom";
56
+ maxSuggestions?: number;
57
+ disabled?: boolean;
58
+ name?: string;
59
+ id?: string;
60
+ "aria-label"?: string;
61
+ "aria-describedby"?: string;
62
+ className?: string;
63
+ }
64
+ export declare const mentionText: (kind: MentionKind, label: string) => string;
65
+ /** Turns a value into storage friendly text, `<@id>` for people and `<#id>` for channels by default. */
66
+ export declare function serializeMentions(value: MentionValue, format?: (mention: Mention) => string): string;
67
+ /**
68
+ * A textarea with `@` people and `#` channel mentions. Mentions are atomic: the caret steps over them and one Backspace removes the whole token.
69
+ * The value carries plain text plus a structured `mentions` array with offsets, ready to store or notify.
70
+ */
71
+ export declare const MentionInput: import("react").ForwardRefExoticComponent<MentionInputProps & import("react").RefAttributes<MentionInputHandle>>;
72
+ export default MentionInput;