@seekalfred/primitives 0.6.0 → 0.7.1

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 (322) hide show
  1. package/README.md +19 -11
  2. package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
  3. package/dist/activity-heatmap/activity-heatmap.js +699 -0
  4. package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
  5. package/dist/activity-heatmap/index.d.ts +2 -0
  6. package/dist/activity-heatmap/index.js +2 -0
  7. package/dist/alert/alert.d.ts +5 -1
  8. package/dist/alert/alert.js +68 -7
  9. package/dist/alert/alert.js.map +1 -1
  10. package/dist/alert-dialog/alert-dialog.d.ts +45 -18
  11. package/dist/alert-dialog/alert-dialog.js +128 -9
  12. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  13. package/dist/alert-dialog/index.d.ts +2 -2
  14. package/dist/alert-dialog/index.js +2 -2
  15. package/dist/animated-counter/animated-counter.d.ts +38 -0
  16. package/dist/animated-counter/animated-counter.js +230 -0
  17. package/dist/animated-counter/animated-counter.js.map +1 -0
  18. package/dist/animated-counter/index.d.ts +2 -0
  19. package/dist/animated-counter/index.js +2 -0
  20. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  21. package/dist/announcement-bar/announcement-bar.js +364 -0
  22. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  23. package/dist/announcement-bar/index.d.ts +2 -0
  24. package/dist/announcement-bar/index.js +2 -0
  25. package/dist/badge/badge.d.ts +1 -1
  26. package/dist/bar-chart/bar-chart.d.ts +51 -0
  27. package/dist/bar-chart/bar-chart.js +565 -0
  28. package/dist/bar-chart/bar-chart.js.map +1 -0
  29. package/dist/bar-chart/index.d.ts +2 -0
  30. package/dist/bar-chart/index.js +2 -0
  31. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  32. package/dist/breadcrumb/breadcrumb.js +12 -3
  33. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  34. package/dist/brush-chart/brush-chart.d.ts +64 -0
  35. package/dist/brush-chart/brush-chart.js +1056 -0
  36. package/dist/brush-chart/brush-chart.js.map +1 -0
  37. package/dist/brush-chart/index.d.ts +2 -0
  38. package/dist/brush-chart/index.js +2 -0
  39. package/dist/button/button.d.ts +2 -2
  40. package/dist/button/button.js +31 -21
  41. package/dist/button/button.js.map +1 -1
  42. package/dist/button-group/button-group.js +3 -4
  43. package/dist/button-group/button-group.js.map +1 -1
  44. package/dist/card/card.js +1 -1
  45. package/dist/card/card.js.map +1 -1
  46. package/dist/card-stack/card-stack.d.ts +54 -0
  47. package/dist/card-stack/card-stack.js +492 -0
  48. package/dist/card-stack/card-stack.js.map +1 -0
  49. package/dist/card-stack/index.d.ts +2 -0
  50. package/dist/card-stack/index.js +2 -0
  51. package/dist/carousel/carousel.d.ts +56 -0
  52. package/dist/carousel/carousel.js +389 -0
  53. package/dist/carousel/carousel.js.map +1 -0
  54. package/dist/carousel/index.d.ts +2 -0
  55. package/dist/carousel/index.js +2 -0
  56. package/dist/checkbox/checkbox.js +4 -4
  57. package/dist/checkbox/checkbox.js.map +1 -1
  58. package/dist/chip-group/chip-group.d.ts +37 -0
  59. package/dist/chip-group/chip-group.js +148 -0
  60. package/dist/chip-group/chip-group.js.map +1 -0
  61. package/dist/chip-group/index.d.ts +2 -0
  62. package/dist/chip-group/index.js +2 -0
  63. package/dist/countdown/countdown.d.ts +45 -0
  64. package/dist/countdown/countdown.js +298 -0
  65. package/dist/countdown/countdown.js.map +1 -0
  66. package/dist/countdown/index.d.ts +2 -0
  67. package/dist/countdown/index.js +2 -0
  68. package/dist/date-picker/picker-parts.js +1 -1
  69. package/dist/date-picker/picker-parts.js.map +1 -1
  70. package/dist/dialog/dialog.js +1 -1
  71. package/dist/dialog/dialog.js.map +1 -1
  72. package/dist/donut-chart/donut-chart.d.ts +64 -0
  73. package/dist/donut-chart/donut-chart.js +576 -0
  74. package/dist/donut-chart/donut-chart.js.map +1 -0
  75. package/dist/donut-chart/index.d.ts +2 -0
  76. package/dist/donut-chart/index.js +2 -0
  77. package/dist/empty-state/empty-state.d.ts +21 -10
  78. package/dist/empty-state/empty-state.js +32 -26
  79. package/dist/empty-state/empty-state.js.map +1 -1
  80. package/dist/empty-state/index.d.ts +2 -2
  81. package/dist/empty-state/index.js +2 -2
  82. package/dist/file-drop-zone/file-drop-zone.js +1 -1
  83. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  84. package/dist/filter-bar/filter-bar.d.ts +47 -26
  85. package/dist/filter-bar/filter-bar.js +272 -156
  86. package/dist/filter-bar/filter-bar.js.map +1 -1
  87. package/dist/gauge/gauge.d.ts +45 -0
  88. package/dist/gauge/gauge.js +219 -0
  89. package/dist/gauge/gauge.js.map +1 -0
  90. package/dist/gauge/index.d.ts +2 -0
  91. package/dist/gauge/index.js +2 -0
  92. package/dist/in-view-title/in-view-title.d.ts +33 -0
  93. package/dist/in-view-title/in-view-title.js +119 -0
  94. package/dist/in-view-title/in-view-title.js.map +1 -0
  95. package/dist/in-view-title/index.d.ts +2 -0
  96. package/dist/in-view-title/index.js +2 -0
  97. package/dist/index.d.ts +49 -6
  98. package/dist/index.js +48 -8
  99. package/dist/lib/cn.js +11 -1
  100. package/dist/lib/cn.js.map +1 -1
  101. package/dist/lib/field-message.js +28 -10
  102. package/dist/lib/field-message.js.map +1 -1
  103. package/dist/lib/field-surface.js +9 -8
  104. package/dist/lib/field-surface.js.map +1 -1
  105. package/dist/lib/flag-emoji.js +50 -0
  106. package/dist/lib/flag-emoji.js.map +1 -0
  107. package/dist/lib/flip.js +11 -3
  108. package/dist/lib/flip.js.map +1 -1
  109. package/dist/lib/menu-highlight.js +64 -0
  110. package/dist/lib/menu-highlight.js.map +1 -0
  111. package/dist/lib/motion.js +9 -2
  112. package/dist/lib/motion.js.map +1 -1
  113. package/dist/lib/rolling-value.js +1 -1
  114. package/dist/lib/rolling-value.js.map +1 -1
  115. package/dist/lib/spring-tween.js +500 -0
  116. package/dist/lib/spring-tween.js.map +1 -0
  117. package/dist/lib/swap-text.js +3 -1
  118. package/dist/lib/swap-text.js.map +1 -1
  119. package/dist/line-chart/index.d.ts +2 -0
  120. package/dist/line-chart/index.js +2 -0
  121. package/dist/line-chart/line-chart.d.ts +76 -0
  122. package/dist/line-chart/line-chart.js +611 -0
  123. package/dist/line-chart/line-chart.js.map +1 -0
  124. package/dist/mention-input/index.d.ts +2 -0
  125. package/dist/mention-input/index.js +2 -0
  126. package/dist/mention-input/mention-input.d.ts +98 -0
  127. package/dist/mention-input/mention-input.js +576 -0
  128. package/dist/mention-input/mention-input.js.map +1 -0
  129. package/dist/menu/menu.d.ts +2 -0
  130. package/dist/menu/menu.js +23 -12
  131. package/dist/menu/menu.js.map +1 -1
  132. package/dist/money-input/index.d.ts +2 -0
  133. package/dist/money-input/index.js +2 -0
  134. package/dist/money-input/money-input.d.ts +65 -0
  135. package/dist/money-input/money-input.js +607 -0
  136. package/dist/money-input/money-input.js.map +1 -0
  137. package/dist/morph-select/index.d.ts +2 -0
  138. package/dist/morph-select/index.js +2 -0
  139. package/dist/morph-select/morph-select.d.ts +71 -0
  140. package/dist/morph-select/morph-select.js +693 -0
  141. package/dist/morph-select/morph-select.js.map +1 -0
  142. package/dist/multi-select/index.d.ts +2 -0
  143. package/dist/multi-select/index.js +2 -0
  144. package/dist/multi-select/multi-select.d.ts +36 -0
  145. package/dist/multi-select/multi-select.js +152 -0
  146. package/dist/multi-select/multi-select.js.map +1 -0
  147. package/dist/number-field/index.d.ts +2 -0
  148. package/dist/number-field/index.js +2 -0
  149. package/dist/number-field/number-field.d.ts +42 -0
  150. package/dist/number-field/number-field.js +199 -0
  151. package/dist/number-field/number-field.js.map +1 -0
  152. package/dist/onboarding-checklist/index.d.ts +2 -0
  153. package/dist/onboarding-checklist/index.js +2 -0
  154. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  155. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  156. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  157. package/dist/password-field/password-field.js +2 -2
  158. package/dist/password-field/password-field.js.map +1 -1
  159. package/dist/phone-input/index.d.ts +3 -0
  160. package/dist/phone-input/index.js +3 -0
  161. package/dist/phone-input/phone-countries.d.ts +29 -0
  162. package/dist/phone-input/phone-countries.js +170 -0
  163. package/dist/phone-input/phone-countries.js.map +1 -0
  164. package/dist/phone-input/phone-input.d.ts +66 -0
  165. package/dist/phone-input/phone-input.js +440 -0
  166. package/dist/phone-input/phone-input.js.map +1 -0
  167. package/dist/popover/popover.d.ts +12 -0
  168. package/dist/popover/popover.js +31 -7
  169. package/dist/popover/popover.js.map +1 -1
  170. package/dist/progress/progress.js +1 -1
  171. package/dist/radio-group/radio-group.js +1 -1
  172. package/dist/radio-group/radio-group.js.map +1 -1
  173. package/dist/reactions/index.d.ts +2 -0
  174. package/dist/reactions/index.js +2 -0
  175. package/dist/reactions/reactions.d.ts +50 -0
  176. package/dist/reactions/reactions.js +315 -0
  177. package/dist/reactions/reactions.js.map +1 -0
  178. package/dist/rich-text-editor/index.d.ts +3 -0
  179. package/dist/rich-text-editor/index.js +2 -0
  180. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  181. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  182. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  183. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  184. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  185. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  186. package/dist/rich-text-editor/rich-text-html.js +174 -0
  187. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  188. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  189. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  190. package/dist/ridgeline/index.d.ts +2 -0
  191. package/dist/ridgeline/index.js +2 -0
  192. package/dist/ridgeline/ridgeline.d.ts +55 -0
  193. package/dist/ridgeline/ridgeline.js +701 -0
  194. package/dist/ridgeline/ridgeline.js.map +1 -0
  195. package/dist/scroll-area/index.d.ts +2 -2
  196. package/dist/scroll-area/index.js +2 -2
  197. package/dist/scroll-area/scroll-area.d.ts +68 -31
  198. package/dist/scroll-area/scroll-area.js +208 -59
  199. package/dist/scroll-area/scroll-area.js.map +1 -1
  200. package/dist/scrub-input/evaluate.d.ts +14 -0
  201. package/dist/scrub-input/evaluate.js +72 -0
  202. package/dist/scrub-input/evaluate.js.map +1 -0
  203. package/dist/scrub-input/index.d.ts +3 -0
  204. package/dist/scrub-input/index.js +3 -0
  205. package/dist/scrub-input/scrub-input.d.ts +74 -0
  206. package/dist/scrub-input/scrub-input.js +486 -0
  207. package/dist/scrub-input/scrub-input.js.map +1 -0
  208. package/dist/search-field/search-field.d.ts +2 -1
  209. package/dist/search-field/search-field.js +4 -4
  210. package/dist/search-field/search-field.js.map +1 -1
  211. package/dist/select/select.d.ts +3 -1
  212. package/dist/select/select.js +20 -8
  213. package/dist/select/select.js.map +1 -1
  214. package/dist/separator/separator.d.ts +1 -1
  215. package/dist/sheet/sheet.js +2 -1
  216. package/dist/sheet/sheet.js.map +1 -1
  217. package/dist/shortcut-recorder/index.d.ts +3 -0
  218. package/dist/shortcut-recorder/index.js +3 -0
  219. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  220. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  221. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  222. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  223. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  224. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  225. package/dist/slider/index.d.ts +2 -2
  226. package/dist/slider/slider.d.ts +46 -14
  227. package/dist/slider/slider.js +151 -67
  228. package/dist/slider/slider.js.map +1 -1
  229. package/dist/slope-chart/index.d.ts +2 -0
  230. package/dist/slope-chart/index.js +2 -0
  231. package/dist/slope-chart/slope-chart.d.ts +54 -0
  232. package/dist/slope-chart/slope-chart.js +568 -0
  233. package/dist/slope-chart/slope-chart.js.map +1 -0
  234. package/dist/sparkline/index.d.ts +2 -0
  235. package/dist/sparkline/index.js +2 -0
  236. package/dist/sparkline/sparkline.d.ts +46 -0
  237. package/dist/sparkline/sparkline.js +390 -0
  238. package/dist/sparkline/sparkline.js.map +1 -0
  239. package/dist/split-bar/index.d.ts +2 -0
  240. package/dist/split-bar/index.js +2 -0
  241. package/dist/split-bar/split-bar.d.ts +62 -0
  242. package/dist/split-bar/split-bar.js +513 -0
  243. package/dist/split-bar/split-bar.js.map +1 -0
  244. package/dist/split-button/split-button.d.ts +1 -1
  245. package/dist/split-button/split-button.js +12 -11
  246. package/dist/split-button/split-button.js.map +1 -1
  247. package/dist/stat-card/index.d.ts +2 -2
  248. package/dist/stat-card/index.js +2 -2
  249. package/dist/stat-card/stat-card.d.ts +24 -4
  250. package/dist/stat-card/stat-card.js +114 -17
  251. package/dist/stat-card/stat-card.js.map +1 -1
  252. package/dist/stepper/index.d.ts +2 -0
  253. package/dist/stepper/index.js +2 -0
  254. package/dist/stepper/stepper.d.ts +51 -0
  255. package/dist/stepper/stepper.js +215 -0
  256. package/dist/stepper/stepper.js.map +1 -0
  257. package/dist/streamgraph/index.d.ts +2 -0
  258. package/dist/streamgraph/index.js +2 -0
  259. package/dist/streamgraph/streamgraph.d.ts +70 -0
  260. package/dist/streamgraph/streamgraph.js +613 -0
  261. package/dist/streamgraph/streamgraph.js.map +1 -0
  262. package/dist/styles.css +1 -1
  263. package/dist/swipe-actions/index.d.ts +2 -0
  264. package/dist/swipe-actions/index.js +2 -0
  265. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  266. package/dist/swipe-actions/swipe-actions.js +552 -0
  267. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  268. package/dist/switch/switch.js +2 -2
  269. package/dist/switch/switch.js.map +1 -1
  270. package/dist/table/index.d.ts +2 -2
  271. package/dist/table/index.js +2 -2
  272. package/dist/table/table.d.ts +56 -4
  273. package/dist/table/table.js +340 -165
  274. package/dist/table/table.js.map +1 -1
  275. package/dist/tabs/tabs.js +2 -2
  276. package/dist/tabs/tabs.js.map +1 -1
  277. package/dist/tag-input/index.d.ts +2 -0
  278. package/dist/tag-input/index.js +2 -0
  279. package/dist/tag-input/tag-input.d.ts +40 -0
  280. package/dist/tag-input/tag-input.js +269 -0
  281. package/dist/tag-input/tag-input.js.map +1 -0
  282. package/dist/text-morph/index.d.ts +2 -0
  283. package/dist/text-morph/index.js +2 -0
  284. package/dist/text-morph/text-morph.d.ts +23 -0
  285. package/dist/text-morph/text-morph.js +21 -0
  286. package/dist/text-morph/text-morph.js.map +1 -0
  287. package/dist/text-reveal/index.d.ts +2 -0
  288. package/dist/text-reveal/index.js +2 -0
  289. package/dist/text-reveal/text-reveal.d.ts +27 -0
  290. package/dist/text-reveal/text-reveal.js +51 -0
  291. package/dist/text-reveal/text-reveal.js.map +1 -0
  292. package/dist/text-shimmer/index.d.ts +2 -0
  293. package/dist/text-shimmer/index.js +2 -0
  294. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  295. package/dist/text-shimmer/text-shimmer.js +29 -0
  296. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  297. package/dist/time-picker/time-picker.js +2 -2
  298. package/dist/time-picker/time-picker.js.map +1 -1
  299. package/dist/toast/index.d.ts +2 -2
  300. package/dist/toast/index.js +2 -2
  301. package/dist/toast/toast.d.ts +6 -0
  302. package/dist/toast/toast.js +82 -40
  303. package/dist/toast/toast.js.map +1 -1
  304. package/dist/tooltip/tooltip.d.ts +9 -2
  305. package/dist/tooltip/tooltip.js +7 -7
  306. package/dist/tooltip/tooltip.js.map +1 -1
  307. package/dist/treemap/index.d.ts +2 -0
  308. package/dist/treemap/index.js +2 -0
  309. package/dist/treemap/treemap.d.ts +53 -0
  310. package/dist/treemap/treemap.js +699 -0
  311. package/dist/treemap/treemap.js.map +1 -0
  312. package/dist/usage-meter/index.d.ts +2 -0
  313. package/dist/usage-meter/index.js +2 -0
  314. package/dist/usage-meter/usage-meter.d.ts +50 -0
  315. package/dist/usage-meter/usage-meter.js +440 -0
  316. package/dist/usage-meter/usage-meter.js.map +1 -0
  317. package/dist/waffle-chart/index.d.ts +2 -0
  318. package/dist/waffle-chart/index.js +2 -0
  319. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  320. package/dist/waffle-chart/waffle-chart.js +426 -0
  321. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  322. package/package.json +165 -3
@@ -0,0 +1,46 @@
1
+ //#region src/sparkline/sparkline.d.ts
2
+ /**
3
+ * Sparkline: a small trend line with its label, an optional headline value and
4
+ * a line of context (the change). The line draws itself left to right the first
5
+ * time it is seen, the fill follows the pen, and the end dot lands. New data
6
+ * morphs the line from the shape on screen. Hovering, dragging or the arrow
7
+ * keys scrub through the points: a cursor rides the line, the stretch after it
8
+ * dims, and the headline and context show that point.
9
+ *
10
+ * Plain SVG, plain CSS transitions and one small spring tween for the two
11
+ * things CSS cannot animate (the shape, and the cursor riding it). No chart or
12
+ * animation library.
13
+ */
14
+ export declare const sparklineVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
15
+ export type SparklineTone = "accent" | "success" | "warning" | "danger";
16
+ export interface SparklineProps {
17
+ /** The series, oldest first. One value draws a flat line. */
18
+ data: number[];
19
+ /** What the line is. Its accessible name starts with it. */
20
+ label: string;
21
+ /** The headline figure, such as "1,284". Scrubbing replaces it with the scrubbed point. */
22
+ value?: string;
23
+ /** Context beside the headline, such as "+12% this week". Coloured by `tone`. */
24
+ change?: string;
25
+ /** @default "accent" */
26
+ tone?: SparklineTone;
27
+ /** Aspect of the drawing; the line fills the width and keeps this ratio. @default 160 */
28
+ width?: number;
29
+ /** @default 52 */
30
+ height?: number;
31
+ /** One label per point, such as a date. It replaces the change while that point is scrubbed. */
32
+ labels?: string[];
33
+ /** Formats a scrubbed point for the headline. Defaults to a grouped number. */
34
+ formatValue?: (value: number, index: number) => string;
35
+ /** A faint fill under the line. @default true */
36
+ area?: boolean;
37
+ /** Pointer and keyboard scrubbing through the points. @default true */
38
+ interactive?: boolean;
39
+ /** Extra classes for the root. Merged last. */
40
+ className?: string;
41
+ }
42
+ /** The first draw: one stroke, slow at both ends like a pen (480ms x 1.75). */
43
+ export declare const SPARKLINE_DRAW_MS = 840;
44
+ export declare const Sparkline: import("react").ForwardRefExoticComponent<SparklineProps & import("react").RefAttributes<HTMLElement>>;
45
+ //#endregion
46
+ //# sourceMappingURL=sparkline.d.ts.map
@@ -0,0 +1,390 @@
1
+ "use client";
2
+ import { cn, cnAll } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
4
+ import { prefersReducedMotion, useIsomorphicLayoutEffect } from "../lib/motion.js";
5
+ import { SwapText } from "../lib/swap-text.js";
6
+ import { SMOOTH, SNAPPY, tween } from "../lib/spring-tween.js";
7
+ import { cva } from "class-variance-authority";
8
+ import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ //#region src/sparkline/sparkline.tsx
11
+ /**
12
+ * Sparkline: a small trend line with its label, an optional headline value and
13
+ * a line of context (the change). The line draws itself left to right the first
14
+ * time it is seen, the fill follows the pen, and the end dot lands. New data
15
+ * morphs the line from the shape on screen. Hovering, dragging or the arrow
16
+ * keys scrub through the points: a cursor rides the line, the stretch after it
17
+ * dims, and the headline and context show that point.
18
+ *
19
+ * Plain SVG, plain CSS transitions and one small spring tween for the two
20
+ * things CSS cannot animate (the shape, and the cursor riding it). No chart or
21
+ * animation library.
22
+ */
23
+ const sparklineVariants = cva("grid min-w-0 gap-3 m-0 box-border font-sans text-foreground");
24
+ /** Space kept above the highest and below the lowest point, and half the line width. */
25
+ const INSET = 5;
26
+ const CAP = 1.25;
27
+ const GRID = Array.from({ length: 161 }, (_, index) => index / 160);
28
+ const GROUPED = new Intl.NumberFormat("en-US");
29
+ /** The first draw: one stroke, slow at both ends like a pen (480ms x 1.75). */
30
+ const SPARKLINE_DRAW_MS = 840;
31
+ /** A smooth line through every point, sampled densely in unit space (x and y 0 to 1, y up). Monotone cubic tangents keep it from swinging past the data. */
32
+ function curveFor(data) {
33
+ const min = Math.min(...data);
34
+ const span = Math.max(...data) - min;
35
+ const ys = data.map((value) => span ? (value - min) / span : .5);
36
+ const last = ys.length - 1;
37
+ const step = 1 / last;
38
+ const slopes = ys.slice(1).map((y, index) => (y - ys[index]) / step);
39
+ const tangents = ys.map((_, index) => index === 0 || index === last ? slopes[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);
40
+ if (last > 1) {
41
+ tangents[0] = (3 * slopes[0] - tangents[1]) / 2;
42
+ tangents[last] = (3 * slopes[last - 1] - tangents[last - 1]) / 2;
43
+ }
44
+ const samples = Math.max(1, Math.min(16, Math.round(128 / last)));
45
+ const points = [];
46
+ for (let index = 0; index < last; index++) for (let sample = 0; sample < samples; sample++) {
47
+ const t = sample / samples;
48
+ const t2 = t * t;
49
+ const t3 = t2 * t;
50
+ const y = (2 * t3 - 3 * t2 + 1) * ys[index] + (t3 - 2 * t2 + t) * step * tangents[index] + (3 * t2 - 2 * t3) * ys[index + 1] + (t3 - t2) * step * tangents[index + 1];
51
+ points.push([(index + t) * step, Math.min(1, Math.max(0, y))]);
52
+ }
53
+ points.push([1, ys[last]]);
54
+ return points;
55
+ }
56
+ /** Height of the sampled line at x, straight between samples. */
57
+ function heightAt(shape, x) {
58
+ let low = 0;
59
+ let high = shape.length - 1;
60
+ if (x <= shape[low][0]) return shape[low][1];
61
+ if (x >= shape[high][0]) return shape[high][1];
62
+ while (high - low > 1) {
63
+ const middle = low + high >> 1;
64
+ if (shape[middle][0] < x) low = middle;
65
+ else high = middle;
66
+ }
67
+ const [x0, y0] = shape[low];
68
+ const [x1, y1] = shape[high];
69
+ return x1 === x0 ? y1 : y0 + (y1 - y0) * (x - x0) / (x1 - x0);
70
+ }
71
+ /** Every drawn string for one frame, in the chart's own pixels so strokes and dots stay round at any width. */
72
+ function geometryOf(shape, w, h, at) {
73
+ const toY = (y) => INSET + (1 - y) * (h - 10);
74
+ const pairs = shape.map(([x, y]) => `${(x * w).toFixed(2)},${toY(y).toFixed(2)}`);
75
+ const cx = at * w;
76
+ const cy = toY(heightAt(shape, at));
77
+ return {
78
+ line: pairs.join(" "),
79
+ trail: `M${pairs.join("L")}`,
80
+ fill: `M${pairs.join("L")}L${w},${h}L0,${h}Z`,
81
+ dot: `${cx},${cy} ${cx},${cy}`,
82
+ cx
83
+ };
84
+ }
85
+ const TONE = {
86
+ accent: "text-primary-strong [--spark-stroke:var(--color-primary)]",
87
+ success: "text-success-strong [--spark-stroke:var(--color-success-strong)]",
88
+ warning: "text-warning-strong [--spark-stroke:var(--color-warning-strong)]",
89
+ danger: "text-destructive-strong [--spark-stroke:var(--color-destructive-strong)]"
90
+ };
91
+ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, tone = "accent", width = 160, height = 52, labels, formatValue, area = true, interactive = true, className }, ref) {
92
+ const series = data.length > 1 ? data : [data[0] ?? 0, data[0] ?? 0];
93
+ const last = series.length - 1;
94
+ const key = series.join(",");
95
+ const target = useMemo(() => curveFor(key.split(",").map(Number)), [key]);
96
+ const uid = `spark${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
97
+ const figure = useRef(null);
98
+ const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
99
+ const plot = useRef(null);
100
+ const svg = useRef(null);
101
+ const shape = useRef(target);
102
+ const cursorAt = useRef(1);
103
+ const box = useRef({
104
+ w: width,
105
+ h: height
106
+ });
107
+ const [size, setSize] = useState({
108
+ w: width,
109
+ h: height
110
+ });
111
+ const [drawn, setDrawn] = useState(false);
112
+ const [landed, setLanded] = useState(false);
113
+ const [active, setActive] = useState(null);
114
+ const index = active === null ? null : Math.min(active, last);
115
+ const [moved, setMoved] = useState({
116
+ index,
117
+ dir: 1
118
+ });
119
+ if (moved.index !== index) setMoved({
120
+ index,
121
+ dir: index === null || moved.index === null ? 1 : Math.sign(index - moved.index) || 1
122
+ });
123
+ const [first] = useState(() => geometryOf(target, width, height, 1));
124
+ /** One writer for every drawn attribute, straight onto the elements: no re-render per frame. */
125
+ const paint = useCallback(() => {
126
+ const root = svg.current;
127
+ if (!root) return;
128
+ const { w, h } = box.current;
129
+ const g = geometryOf(shape.current, w, h, cursorAt.current);
130
+ const q = (name) => root.querySelector(`[data-part="${name}"]`);
131
+ q("line")?.setAttribute("points", g.line);
132
+ q("trail")?.setAttribute("d", g.trail);
133
+ q("area")?.setAttribute("d", g.fill);
134
+ q("dot")?.setAttribute("points", g.dot);
135
+ q("lit")?.setAttribute("width", String(g.cx + CAP + 16));
136
+ q("dim")?.setAttribute("x", String(g.cx + CAP));
137
+ q("cursor")?.setAttribute("x1", String(g.cx));
138
+ q("cursor")?.setAttribute("x2", String(g.cx));
139
+ }, []);
140
+ useIsomorphicLayoutEffect(() => {
141
+ const node = plot.current;
142
+ if (!node || typeof ResizeObserver === "undefined") return;
143
+ const observer = new ResizeObserver(() => {
144
+ const w = node.clientWidth;
145
+ if (!w || Math.abs(box.current.w - w) < .5) return;
146
+ box.current = {
147
+ w,
148
+ h: w * height / width
149
+ };
150
+ setSize(box.current);
151
+ });
152
+ observer.observe(node);
153
+ return () => observer.disconnect();
154
+ }, [height, width]);
155
+ useIsomorphicLayoutEffect(paint, [size, paint]);
156
+ useEffect(() => {
157
+ const node = figure.current;
158
+ if (!node) return;
159
+ if (prefersReducedMotion() || typeof IntersectionObserver === "undefined") {
160
+ setDrawn(true);
161
+ setLanded(true);
162
+ return;
163
+ }
164
+ let timer = 0;
165
+ const observer = new IntersectionObserver(([entry]) => {
166
+ if (!entry?.isIntersecting) return;
167
+ observer.disconnect();
168
+ setDrawn(true);
169
+ timer = window.setTimeout(() => setLanded(true), 840);
170
+ }, { threshold: .5 });
171
+ observer.observe(node);
172
+ return () => {
173
+ observer.disconnect();
174
+ window.clearTimeout(timer);
175
+ };
176
+ }, []);
177
+ useEffect(() => {
178
+ const from = shape.current;
179
+ if (from === target) return;
180
+ const xs = [.../* @__PURE__ */ new Set([...GRID, ...target.map(([x]) => x)])].sort((a, b) => a - b);
181
+ const start = xs.map((x) => heightAt(from, x));
182
+ const goal = xs.map((x) => heightAt(target, x));
183
+ return tween(SMOOTH, (p) => {
184
+ shape.current = xs.map((x, at) => [x, start[at] + (goal[at] - start[at]) * p]);
185
+ paint();
186
+ }, () => {
187
+ shape.current = target;
188
+ paint();
189
+ });
190
+ }, [paint, target]);
191
+ useEffect(() => {
192
+ const from = cursorAt.current;
193
+ const to = index === null ? 1 : index / last;
194
+ if (from === to) return;
195
+ return tween(SNAPPY, (p) => {
196
+ cursorAt.current = from + (to - from) * p;
197
+ paint();
198
+ });
199
+ }, [
200
+ index,
201
+ last,
202
+ paint
203
+ ]);
204
+ const scrubbing = index !== null;
205
+ const format = (at) => formatValue ? formatValue(series[at], at) : GROUPED.format(series[at]);
206
+ const point = format(index ?? last);
207
+ const when = labels?.[index ?? last];
208
+ const headline = value === void 0 ? void 0 : scrubbing ? point : value;
209
+ const aside = index === null ? change ?? "" : value === void 0 ? [point, when].filter(Boolean).join(" · ") : when ?? `${index + 1} of ${last + 1}`;
210
+ const description = `${label}: ${value ?? series[last]}. ${change ?? ""}`.trim();
211
+ const pointAt = (clientX) => {
212
+ const rect = plot.current?.getBoundingClientRect();
213
+ return rect?.width ? Math.max(0, Math.min(last, Math.round((clientX - rect.left) / rect.width * last))) : last;
214
+ };
215
+ const onKeyDown = (event) => {
216
+ if (event.key === "Escape" && scrubbing) {
217
+ event.preventDefault();
218
+ setActive(null);
219
+ return;
220
+ }
221
+ const from = index ?? last;
222
+ const page = Math.max(1, Math.round((last + 1) / 6));
223
+ const next = {
224
+ ArrowLeft: from - 1,
225
+ ArrowDown: from - 1,
226
+ ArrowRight: from + 1,
227
+ ArrowUp: from + 1,
228
+ PageDown: from - page,
229
+ PageUp: from + page,
230
+ Home: 0,
231
+ End: last
232
+ }[event.key];
233
+ if (next === void 0) return;
234
+ event.preventDefault();
235
+ setActive(Math.max(0, Math.min(last, next)));
236
+ };
237
+ const scrub = interactive ? {
238
+ role: "slider",
239
+ tabIndex: 0,
240
+ "aria-label": `${label}, explore values`,
241
+ "aria-valuemin": 1,
242
+ "aria-valuemax": last + 1,
243
+ "aria-valuenow": (index ?? last) + 1,
244
+ "aria-valuetext": `${when ?? (index === null ? "Latest" : `Point ${index + 1} of ${last + 1}`)}: ${point}`,
245
+ onPointerDown: (event) => setActive(pointAt(event.clientX)),
246
+ onPointerMove: (event) => {
247
+ if (event.pointerType === "mouse" || event.buttons) setActive(pointAt(event.clientX));
248
+ },
249
+ onPointerUp: (event) => {
250
+ if (event.pointerType !== "mouse") setActive(null);
251
+ },
252
+ onPointerLeave: () => setActive(null),
253
+ onPointerCancel: () => setActive(null),
254
+ onBlur: () => setActive(null),
255
+ onKeyDown
256
+ } : {};
257
+ return /* @__PURE__ */ jsxs("figure", {
258
+ ref: refs,
259
+ "data-slot": "sparkline",
260
+ "data-drawn": drawn || void 0,
261
+ "data-landed": landed || void 0,
262
+ "data-scrubbing": scrubbing || void 0,
263
+ "aria-label": description,
264
+ className: cn(sparklineVariants(), className),
265
+ children: [/* @__PURE__ */ jsxs("figcaption", {
266
+ className: "flex min-w-0 items-baseline gap-2 text-sm text-text-secondary",
267
+ children: [
268
+ /* @__PURE__ */ jsx("span", {
269
+ "data-slot": "sparkline-label",
270
+ className: "min-w-0 truncate",
271
+ children: label
272
+ }),
273
+ headline === void 0 ? null : /* @__PURE__ */ jsx("strong", {
274
+ "data-slot": "sparkline-value",
275
+ className: "ms-auto text-lg font-medium tabular-nums text-foreground",
276
+ children: /* @__PURE__ */ jsx(SwapText, {
277
+ swapKey: headline,
278
+ dir: moved.dir,
279
+ motion: "rise",
280
+ children: headline
281
+ })
282
+ }),
283
+ /* @__PURE__ */ jsx("small", {
284
+ "data-slot": "sparkline-change",
285
+ className: cnAll("flex-none text-xs font-medium tabular-nums sparkline-change", scrubbing ? "text-text-secondary" : TONE[tone]),
286
+ children: /* @__PURE__ */ jsx(SwapText, {
287
+ swapKey: aside,
288
+ dir: moved.dir,
289
+ motion: "rise",
290
+ children: aside
291
+ })
292
+ })
293
+ ]
294
+ }), /* @__PURE__ */ jsx("div", {
295
+ ref: plot,
296
+ "data-slot": "sparkline-plot",
297
+ "data-interactive": interactive || void 0,
298
+ className: "sparkline-plot",
299
+ ...scrub,
300
+ children: /* @__PURE__ */ jsxs("svg", {
301
+ ref: svg,
302
+ viewBox: `0 0 ${size.w} ${size.h}`,
303
+ "aria-hidden": "true",
304
+ focusable: "false",
305
+ className: cnAll("block h-auto w-full overflow-visible", TONE[tone]),
306
+ children: [
307
+ /* @__PURE__ */ jsxs("defs", { children: [
308
+ /* @__PURE__ */ jsx("clipPath", {
309
+ id: `${uid}-lit`,
310
+ clipPathUnits: "userSpaceOnUse",
311
+ children: /* @__PURE__ */ jsx("rect", {
312
+ "data-part": "lit",
313
+ x: -16,
314
+ y: -16,
315
+ width: first.cx + CAP + 16,
316
+ height: size.h + 32
317
+ })
318
+ }),
319
+ /* @__PURE__ */ jsx("clipPath", {
320
+ id: `${uid}-dim`,
321
+ clipPathUnits: "userSpaceOnUse",
322
+ children: /* @__PURE__ */ jsx("rect", {
323
+ "data-part": "dim",
324
+ x: first.cx + CAP,
325
+ y: -16,
326
+ width: size.w + 32,
327
+ height: size.h + 32
328
+ })
329
+ }),
330
+ /* @__PURE__ */ jsx("clipPath", {
331
+ id: `${uid}-fill`,
332
+ clipPathUnits: "userSpaceOnUse",
333
+ children: /* @__PURE__ */ jsx("rect", {
334
+ "data-part": "reveal",
335
+ className: "sparkline-reveal",
336
+ x: -16,
337
+ y: -16,
338
+ height: size.h + 32,
339
+ style: { width: drawn ? size.w + 32 : 0 }
340
+ })
341
+ })
342
+ ] }),
343
+ area ? /* @__PURE__ */ jsx("path", {
344
+ "data-part": "area",
345
+ className: "sparkline-area",
346
+ d: first.fill,
347
+ clipPath: `url(#${uid}-fill)`
348
+ }) : null,
349
+ /* @__PURE__ */ jsx("line", {
350
+ className: "sparkline-baseline",
351
+ x1: "0",
352
+ y1: size.h - .5,
353
+ x2: size.w,
354
+ y2: size.h - .5
355
+ }),
356
+ /* @__PURE__ */ jsx("path", {
357
+ "data-part": "trail",
358
+ className: "sparkline-trail",
359
+ d: first.trail,
360
+ clipPath: `url(#${uid}-dim)`
361
+ }),
362
+ /* @__PURE__ */ jsx("polyline", {
363
+ "data-part": "line",
364
+ className: "sparkline-line",
365
+ points: first.line,
366
+ pathLength: 1,
367
+ clipPath: `url(#${uid}-lit)`
368
+ }),
369
+ /* @__PURE__ */ jsx("line", {
370
+ "data-part": "cursor",
371
+ className: "sparkline-cursor",
372
+ x1: first.cx,
373
+ x2: first.cx,
374
+ y1: 0,
375
+ y2: size.h
376
+ }),
377
+ /* @__PURE__ */ jsx("polyline", {
378
+ "data-part": "dot",
379
+ className: "sparkline-dot",
380
+ points: first.dot
381
+ })
382
+ ]
383
+ })
384
+ })]
385
+ });
386
+ });
387
+ //#endregion
388
+ export { SPARKLINE_DRAW_MS, Sparkline, sparklineVariants };
389
+
390
+ //# sourceMappingURL=sparkline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sparkline.js","names":[],"sources":["../../src/sparkline/sparkline.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { SMOOTH, SNAPPY, tween } from \"../lib/spring-tween.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Sparkline: a small trend line with its label, an optional headline value and\n * a line of context (the change). The line draws itself left to right the first\n * time it is seen, the fill follows the pen, and the end dot lands. New data\n * morphs the line from the shape on screen. Hovering, dragging or the arrow\n * keys scrub through the points: a cursor rides the line, the stretch after it\n * dims, and the headline and context show that point.\n *\n * Plain SVG, plain CSS transitions and one small spring tween for the two\n * things CSS cannot animate (the shape, and the cursor riding it). No chart or\n * animation library.\n */\nexport const sparklineVariants = cva(\"grid min-w-0 gap-3 m-0 box-border font-sans text-foreground\");\n\nexport type SparklineTone = \"accent\" | \"success\" | \"warning\" | \"danger\";\n\nexport interface SparklineProps {\n /** The series, oldest first. One value draws a flat line. */\n data: number[];\n /** What the line is. Its accessible name starts with it. */\n label: string;\n /** The headline figure, such as \"1,284\". Scrubbing replaces it with the scrubbed point. */\n value?: string;\n /** Context beside the headline, such as \"+12% this week\". Coloured by `tone`. */\n change?: string;\n /** @default \"accent\" */\n tone?: SparklineTone;\n /** Aspect of the drawing; the line fills the width and keeps this ratio. @default 160 */\n width?: number;\n /** @default 52 */\n height?: number;\n /** One label per point, such as a date. It replaces the change while that point is scrubbed. */\n labels?: string[];\n /** Formats a scrubbed point for the headline. Defaults to a grouped number. */\n formatValue?: (value: number, index: number) => string;\n /** A faint fill under the line. @default true */\n area?: boolean;\n /** Pointer and keyboard scrubbing through the points. @default true */\n interactive?: boolean;\n /** Extra classes for the root. Merged last. */\n className?: string;\n}\n\ntype Point = [number, number];\n\n/** Space kept above the highest and below the lowest point, and half the line width. */\nconst INSET = 5;\nconst CAP = 1.25;\nconst GRID = Array.from({ length: 161 }, (_, index) => index / 160);\nconst GROUPED = new Intl.NumberFormat(\"en-US\");\n/** The first draw: one stroke, slow at both ends like a pen (480ms x 1.75). */\nexport const SPARKLINE_DRAW_MS = 840;\n\n/** A smooth line through every point, sampled densely in unit space (x and y 0 to 1, y up). Monotone cubic tangents keep it from swinging past the data. */\nfunction curveFor(data: number[]): Point[] {\n const min = Math.min(...data);\n const span = Math.max(...data) - min;\n const ys = data.map((value) => (span ? (value - min) / span : 0.5));\n const last = ys.length - 1;\n const step = 1 / last;\n const slopes = ys.slice(1).map((y, index) => (y - ys[index]!) / step);\n const tangents = ys.map((_, index) =>\n index === 0 || index === last\n ? slopes[0]!\n : (Math.sign(slopes[index - 1]!) + Math.sign(slopes[index]!)) *\n Math.min(\n Math.abs(slopes[index - 1]!),\n Math.abs(slopes[index]!),\n Math.abs(slopes[index - 1]! + slopes[index]!) / 4,\n ) || 0,\n );\n if (last > 1) {\n tangents[0] = (3 * slopes[0]! - tangents[1]!) / 2;\n tangents[last] = (3 * slopes[last - 1]! - tangents[last - 1]!) / 2;\n }\n const samples = Math.max(1, Math.min(16, Math.round(128 / last)));\n const points: Point[] = [];\n for (let index = 0; index < last; index++) {\n for (let sample = 0; sample < samples; sample++) {\n const t = sample / samples;\n const t2 = t * t;\n const t3 = t2 * t;\n const y =\n (2 * t3 - 3 * t2 + 1) * ys[index]! +\n (t3 - 2 * t2 + t) * step * tangents[index]! +\n (3 * t2 - 2 * t3) * ys[index + 1]! +\n (t3 - t2) * step * tangents[index + 1]!;\n points.push([(index + t) * step, Math.min(1, Math.max(0, y))]);\n }\n }\n points.push([1, ys[last]!]);\n return points;\n}\n\n/** Height of the sampled line at x, straight between samples. */\nfunction heightAt(shape: Point[], x: number) {\n let low = 0;\n let high = shape.length - 1;\n if (x <= shape[low]![0]) return shape[low]![1];\n if (x >= shape[high]![0]) return shape[high]![1];\n while (high - low > 1) {\n const middle = (low + high) >> 1;\n if (shape[middle]![0] < x) low = middle;\n else high = middle;\n }\n const [x0, y0] = shape[low]!;\n const [x1, y1] = shape[high]!;\n return x1 === x0 ? y1 : y0 + ((y1 - y0) * (x - x0)) / (x1 - x0);\n}\n\n/** Every drawn string for one frame, in the chart's own pixels so strokes and dots stay round at any width. */\nfunction geometryOf(shape: Point[], w: number, h: number, at: number) {\n const toY = (y: number) => INSET + (1 - y) * (h - INSET * 2);\n const pairs = shape.map(([x, y]) => `${(x * w).toFixed(2)},${toY(y).toFixed(2)}`);\n const cx = at * w;\n const cy = toY(heightAt(shape, at));\n return {\n line: pairs.join(\" \"),\n trail: `M${pairs.join(\"L\")}`,\n fill: `M${pairs.join(\"L\")}L${w},${h}L0,${h}Z`,\n dot: `${cx},${cy} ${cx},${cy}`,\n cx,\n };\n}\n\nconst TONE: Record<SparklineTone, string> = {\n accent: \"text-primary-strong [--spark-stroke:var(--color-primary)]\",\n success: \"text-success-strong [--spark-stroke:var(--color-success-strong)]\",\n warning: \"text-warning-strong [--spark-stroke:var(--color-warning-strong)]\",\n danger: \"text-destructive-strong [--spark-stroke:var(--color-destructive-strong)]\",\n};\n\nexport const Sparkline = forwardRef<HTMLElement, SparklineProps>(function Sparkline(\n {\n data,\n label,\n value,\n change,\n tone = \"accent\",\n width = 160,\n height = 52,\n labels,\n formatValue,\n area = true,\n interactive = true,\n className,\n },\n ref,\n) {\n const series = data.length > 1 ? data : [data[0] ?? 0, data[0] ?? 0];\n const last = series.length - 1;\n const key = series.join(\",\");\n const target = useMemo(() => curveFor(key.split(\",\").map(Number)), [key]);\n const uid = `spark${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n\n const figure = useRef<HTMLElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, figure), [ref]);\n const plot = useRef<HTMLDivElement>(null);\n const svg = useRef<SVGSVGElement>(null);\n const shape = useRef(target);\n const cursorAt = useRef(1);\n const box = useRef({ w: width, h: height });\n const [size, setSize] = useState({ w: width, h: height });\n const [drawn, setDrawn] = useState(false);\n const [landed, setLanded] = useState(false);\n const [active, setActive] = useState<number | null>(null);\n const index = active === null ? null : Math.min(active, last);\n const [moved, setMoved] = useState({ index, dir: 1 });\n if (moved.index !== index) {\n setMoved({\n index,\n dir: index === null || moved.index === null ? 1 : Math.sign(index - moved.index) || 1,\n });\n }\n\n // The first frame, also what the server renders.\n const [first] = useState(() => geometryOf(target, width, height, 1));\n\n /** One writer for every drawn attribute, straight onto the elements: no re-render per frame. */\n const paint = useCallback(() => {\n const root = svg.current;\n if (!root) return;\n const { w, h } = box.current;\n const g = geometryOf(shape.current, w, h, cursorAt.current);\n const q = (name: string) => root.querySelector<SVGElement>(`[data-part=\"${name}\"]`);\n q(\"line\")?.setAttribute(\"points\", g.line);\n q(\"trail\")?.setAttribute(\"d\", g.trail);\n q(\"area\")?.setAttribute(\"d\", g.fill);\n q(\"dot\")?.setAttribute(\"points\", g.dot);\n q(\"lit\")?.setAttribute(\"width\", String(g.cx + CAP + 16));\n q(\"dim\")?.setAttribute(\"x\", String(g.cx + CAP));\n q(\"cursor\")?.setAttribute(\"x1\", String(g.cx));\n q(\"cursor\")?.setAttribute(\"x2\", String(g.cx));\n }, []);\n\n // The chart draws in its own pixels: measure the width, keep the height in proportion.\n useIsomorphicLayoutEffect(() => {\n const node = plot.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n const w = node.clientWidth;\n if (!w || Math.abs(box.current.w - w) < 0.5) return;\n box.current = { w, h: (w * height) / width };\n setSize(box.current);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, [height, width]);\n useIsomorphicLayoutEffect(paint, [size, paint]);\n\n // The line draws once it is half in view; the fill follows the pen, then the end dot lands.\n useEffect(() => {\n const node = figure.current;\n if (!node) return;\n if (prefersReducedMotion() || typeof IntersectionObserver === \"undefined\") {\n setDrawn(true);\n setLanded(true);\n return;\n }\n let timer = 0;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting) return;\n observer.disconnect();\n setDrawn(true);\n timer = window.setTimeout(() => setLanded(true), SPARKLINE_DRAW_MS);\n },\n { threshold: 0.5 },\n );\n observer.observe(node);\n return () => {\n observer.disconnect();\n window.clearTimeout(timer);\n };\n }, []);\n\n // New data morphs the line from the shape on screen, sampled on shared x positions so a series that gains or loses points still flows.\n useEffect(() => {\n const from = shape.current;\n if (from === target) return;\n const xs = [...new Set([...GRID, ...target.map(([x]) => x)])].sort((a, b) => a - b);\n const start = xs.map((x) => heightAt(from, x));\n const goal = xs.map((x) => heightAt(target, x));\n return tween(\n SMOOTH,\n (p) => {\n shape.current = xs.map((x, at): Point => [x, start[at]! + (goal[at]! - start[at]!) * p]);\n paint();\n },\n () => {\n shape.current = target;\n paint();\n },\n );\n }, [paint, target]);\n\n // The cursor springs to the scrubbed point and rides the line; leaving sends it home to the latest value.\n useEffect(() => {\n const from = cursorAt.current;\n const to = index === null ? 1 : index / last;\n if (from === to) return;\n return tween(SNAPPY, (p) => {\n cursorAt.current = from + (to - from) * p;\n paint();\n });\n }, [index, last, paint]);\n\n const scrubbing = index !== null;\n const format = (at: number) =>\n formatValue ? formatValue(series[at]!, at) : GROUPED.format(series[at]!);\n const point = format(index ?? last);\n const when = labels?.[index ?? last];\n const headline = value === undefined ? undefined : scrubbing ? point : value;\n const aside =\n index === null\n ? (change ?? \"\")\n : value === undefined\n ? [point, when].filter(Boolean).join(\" · \")\n : (when ?? `${index + 1} of ${last + 1}`);\n const description = `${label}: ${value ?? series[last]}. ${change ?? \"\"}`.trim();\n\n const pointAt = (clientX: number) => {\n const rect = plot.current?.getBoundingClientRect();\n return rect?.width\n ? Math.max(0, Math.min(last, Math.round(((clientX - rect.left) / rect.width) * last)))\n : last;\n };\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"Escape\" && scrubbing) {\n event.preventDefault();\n setActive(null);\n return;\n }\n const from = index ?? last;\n const page = Math.max(1, Math.round((last + 1) / 6));\n const next = (\n {\n ArrowLeft: from - 1,\n ArrowDown: from - 1,\n ArrowRight: from + 1,\n ArrowUp: from + 1,\n PageDown: from - page,\n PageUp: from + page,\n Home: 0,\n End: last,\n } as Record<string, number>\n )[event.key];\n if (next === undefined) return;\n event.preventDefault();\n setActive(Math.max(0, Math.min(last, next)));\n };\n const scrub = interactive\n ? {\n role: \"slider\",\n tabIndex: 0,\n \"aria-label\": `${label}, explore values`,\n \"aria-valuemin\": 1,\n \"aria-valuemax\": last + 1,\n \"aria-valuenow\": (index ?? last) + 1,\n \"aria-valuetext\": `${when ?? (index === null ? \"Latest\" : `Point ${index + 1} of ${last + 1}`)}: ${point}`,\n onPointerDown: (event: PointerEvent<HTMLDivElement>) => setActive(pointAt(event.clientX)),\n onPointerMove: (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType === \"mouse\" || event.buttons) setActive(pointAt(event.clientX));\n },\n onPointerUp: (event: PointerEvent<HTMLDivElement>) => {\n if (event.pointerType !== \"mouse\") setActive(null);\n },\n onPointerLeave: () => setActive(null),\n onPointerCancel: () => setActive(null),\n onBlur: () => setActive(null),\n onKeyDown,\n }\n : {};\n\n return (\n <figure\n ref={refs}\n data-slot=\"sparkline\"\n data-drawn={drawn || undefined}\n data-landed={landed || undefined}\n data-scrubbing={scrubbing || undefined}\n aria-label={description}\n className={cn(sparklineVariants(), className)}\n >\n <figcaption className=\"flex min-w-0 items-baseline gap-2 text-sm text-text-secondary\">\n <span data-slot=\"sparkline-label\" className=\"min-w-0 truncate\">\n {label}\n </span>\n {headline === undefined ? null : (\n <strong\n data-slot=\"sparkline-value\"\n className=\"ms-auto text-lg font-medium tabular-nums text-foreground\"\n >\n <SwapText swapKey={headline} dir={moved.dir} motion=\"rise\">\n {headline}\n </SwapText>\n </strong>\n )}\n <small\n data-slot=\"sparkline-change\"\n // The scrubbed date is context, not a change, so it turns neutral.\n className={cnAll(\n \"flex-none text-xs font-medium tabular-nums sparkline-change\",\n scrubbing ? \"text-text-secondary\" : TONE[tone],\n )}\n >\n <SwapText swapKey={aside} dir={moved.dir} motion=\"rise\">\n {aside}\n </SwapText>\n </small>\n </figcaption>\n <div\n ref={plot}\n data-slot=\"sparkline-plot\"\n data-interactive={interactive || undefined}\n className=\"sparkline-plot\"\n {...scrub}\n >\n <svg\n ref={svg}\n viewBox={`0 0 ${size.w} ${size.h}`}\n aria-hidden=\"true\"\n focusable=\"false\"\n className={cnAll(\"block h-auto w-full overflow-visible\", TONE[tone])}\n >\n <defs>\n <clipPath id={`${uid}-lit`} clipPathUnits=\"userSpaceOnUse\">\n <rect data-part=\"lit\" x={-16} y={-16} width={first.cx + CAP + 16} height={size.h + 32} />\n </clipPath>\n <clipPath id={`${uid}-dim`} clipPathUnits=\"userSpaceOnUse\">\n <rect data-part=\"dim\" x={first.cx + CAP} y={-16} width={size.w + 32} height={size.h + 32} />\n </clipPath>\n <clipPath id={`${uid}-fill`} clipPathUnits=\"userSpaceOnUse\">\n <rect\n data-part=\"reveal\"\n className=\"sparkline-reveal\"\n x={-16}\n y={-16}\n height={size.h + 32}\n style={{ width: drawn ? size.w + 32 : 0 }}\n />\n </clipPath>\n </defs>\n {area ? (\n <path data-part=\"area\" className=\"sparkline-area\" d={first.fill} clipPath={`url(#${uid}-fill)`} />\n ) : null}\n <line\n className=\"sparkline-baseline\"\n x1=\"0\"\n y1={size.h - 0.5}\n x2={size.w}\n y2={size.h - 0.5}\n />\n {/* While scrubbing, the stretch after the cursor dims, as if the later points have not happened yet. */}\n <path data-part=\"trail\" className=\"sparkline-trail\" d={first.trail} clipPath={`url(#${uid}-dim)`} />\n <polyline\n data-part=\"line\"\n className=\"sparkline-line\"\n points={first.line}\n pathLength={1}\n clipPath={`url(#${uid}-lit)`}\n />\n <line data-part=\"cursor\" className=\"sparkline-cursor\" x1={first.cx} x2={first.cx} y1={0} y2={size.h} />\n {/* A zero-length round-capped stroke is a true circle in the line's colour; it rests on the latest point and becomes the scrub handle. */}\n <polyline data-part=\"dot\" className=\"sparkline-dot\" points={first.dot} />\n </svg>\n </div>\n </figure>\n );\n});\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { SplitBar, SplitBarProps, SplitSegment } from "./split-bar.js";
2
+ export { SplitBar, type SplitBarProps, type SplitSegment };
@@ -0,0 +1,2 @@
1
+ import { SplitBar } from "./split-bar.js";
2
+ export { SplitBar };
@@ -0,0 +1,62 @@
1
+ import { CSSProperties } from "react";
2
+ //#region src/split-bar/split-bar.d.ts
3
+ /**
4
+ * SplitBar: one 100% bar split into shares, for traffic sources, budgets,
5
+ * storage or votes. Resting on a segment (or arrowing onto it) widens it a little
6
+ * (a small one widens enough to show its own label) while the others give way
7
+ * proportionally and lose their colour, and the readout above names it: share,
8
+ * value and unit.
9
+ *
10
+ * A label sits inside its segment when it fits, as name and percent or percent
11
+ * alone, and otherwise moves to a lane under the bar on a soft leader line,
12
+ * pushed apart so labels never overlap. The legend switches segments off and on:
13
+ * an off segment collapses in place and the rest reflow to fill the bar, their
14
+ * percentages rolling digit by digit to the new shares. New data does the same,
15
+ * so switching a period is one continuous reflow. The last visible segment
16
+ * cannot be switched off: its legend item shakes and the reason is announced.
17
+ *
18
+ * Segment edges, leader lines and outside labels are written straight to the DOM
19
+ * by one small spring tween (a new target mid flight retargets from where the
20
+ * edges are); name, fit and colour changes are CSS transitions.
21
+ */
22
+ export interface SplitSegment {
23
+ /** Stable identity. Segments are matched by id when the data changes. */
24
+ id: string;
25
+ label: string;
26
+ /** Shares are each value over the total of the visible segments; zero and negative values are left out. */
27
+ value: number;
28
+ /** Any CSS colour. Defaults to chart-1 to chart-4 in order, then two neutrals meant for catch-all shares. */
29
+ color?: string;
30
+ }
31
+ export interface SplitBarProps {
32
+ /** The shares in display order. */
33
+ segments: readonly SplitSegment[];
34
+ /** Heading above the bar and the accessible name unless `label` is set. */
35
+ title?: string;
36
+ /** One line under the title. */
37
+ description?: string;
38
+ /** Number format for values in the readout, such as currency for a budget. Shares always read as percentages with one decimal. */
39
+ format?: Intl.NumberFormatOptions;
40
+ /** Word after values in the readout, such as "sessions". */
41
+ unit?: string;
42
+ /** Label of the readout when nothing is highlighted. @default "Total" */
43
+ totalLabel?: string;
44
+ /** Ids of segments that are switched off (controlled). */
45
+ hidden?: readonly string[];
46
+ /** Ids switched off at first (uncontrolled). */
47
+ defaultHidden?: readonly string[];
48
+ /** Called with the new list when the legend or the keyboard switches a segment. */
49
+ onHiddenChange?: (hidden: string[]) => void;
50
+ /** The legend. Each item toggles its segment and highlights it on hover. @default true */
51
+ legend?: boolean;
52
+ /** Formatting locale. Fixed by default so server and client render the same digits. @default "en-US" */
53
+ locale?: string;
54
+ /** Accessible name. Defaults to the title. */
55
+ label?: string;
56
+ /** Extra classes for the root. Merged last. */
57
+ className?: string;
58
+ style?: CSSProperties;
59
+ }
60
+ export declare const SplitBar: import("react").ForwardRefExoticComponent<SplitBarProps & import("react").RefAttributes<HTMLElement>>;
61
+ //#endregion
62
+ //# sourceMappingURL=split-bar.d.ts.map