@seekalfred/primitives 0.7.1 → 0.7.4
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.
- package/README.md +255 -5
- package/dist/alert/alert.d.ts +49 -4
- package/dist/alert/alert.js +66 -38
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +31 -4
- package/dist/alert-dialog/alert-dialog.js +75 -26
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/announcement-bar/announcement-bar.d.ts +15 -4
- package/dist/announcement-bar/announcement-bar.js +31 -6
- package/dist/announcement-bar/announcement-bar.js.map +1 -1
- package/dist/avatar/avatar.d.ts +7 -0
- package/dist/avatar/avatar.js +33 -12
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart-series.d.ts +75 -0
- package/dist/bar-chart/bar-chart-series.js +401 -0
- package/dist/bar-chart/bar-chart-series.js.map +1 -0
- package/dist/bar-chart/bar-chart.d.ts +25 -1
- package/dist/bar-chart/bar-chart.js +26 -1
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/index.d.ts +2 -1
- package/dist/button/button.d.ts +1 -1
- package/dist/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- package/dist/card/card.js.map +1 -1
- package/dist/carousel/carousel.d.ts +21 -0
- package/dist/carousel/carousel.js +34 -8
- package/dist/carousel/carousel.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +11 -0
- package/dist/chip-group/chip-group.js +50 -34
- package/dist/chip-group/chip-group.js.map +1 -1
- package/dist/chip-group/index.d.ts +2 -2
- package/dist/copy-button/copy-button.d.ts +37 -5
- package/dist/copy-button/copy-button.js +35 -11
- package/dist/copy-button/copy-button.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +22 -0
- package/dist/donut-chart/donut-chart.js +35 -18
- package/dist/donut-chart/donut-chart.js.map +1 -1
- package/dist/donut-chart/index.d.ts +2 -2
- package/dist/empty-state/empty-state.d.ts +21 -0
- package/dist/empty-state/empty-state.js +16 -4
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
- package/dist/file-drop-zone/file-drop-zone.js +26 -8
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +4 -1
- package/dist/filter-bar/filter-bar.js +53 -9
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +18 -1
- package/dist/gauge/gauge.js +35 -21
- package/dist/gauge/gauge.js.map +1 -1
- package/dist/gauge/index.d.ts +2 -2
- package/dist/index.d.ts +15 -14
- package/dist/index.js +9 -9
- package/dist/input/input.d.ts +7 -0
- package/dist/input/input.js +7 -5
- package/dist/input/input.js.map +1 -1
- package/dist/lib/field-shell.js +6 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +22 -1
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/required-marker.js +20 -0
- package/dist/lib/required-marker.js.map +1 -0
- package/dist/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts +144 -2
- package/dist/line-chart/line-chart.js +314 -46
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -2
- package/dist/money-input/money-input.d.ts +36 -1
- package/dist/money-input/money-input.js +30 -14
- package/dist/money-input/money-input.js.map +1 -1
- package/dist/morph-select/morph-select.d.ts +26 -1
- package/dist/morph-select/morph-select.js +28 -6
- package/dist/morph-select/morph-select.js.map +1 -1
- package/dist/multi-select/index.d.ts +2 -2
- package/dist/multi-select/index.js +2 -2
- package/dist/multi-select/multi-select.d.ts +74 -2
- package/dist/multi-select/multi-select.js +213 -39
- package/dist/multi-select/multi-select.js.map +1 -1
- package/dist/password-field/password-field.d.ts +16 -0
- package/dist/password-field/password-field.js +37 -8
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/popover/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +39 -2
- package/dist/popover/popover.js +29 -2
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/index.js +2 -2
- package/dist/progress/progress.d.ts +68 -3
- package/dist/progress/progress.js +156 -39
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +109 -9
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.js +4 -3
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/scrub-input.d.ts +11 -1
- package/dist/scrub-input/scrub-input.js +22 -10
- package/dist/scrub-input/scrub-input.js.map +1 -1
- package/dist/search-field/search-field.d.ts +22 -1
- package/dist/search-field/search-field.js +22 -7
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +10 -1
- package/dist/select/select.js +11 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +21 -9
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.js.map +1 -1
- package/dist/sparkline/sparkline.d.ts +27 -2
- package/dist/sparkline/sparkline.js +115 -18
- package/dist/sparkline/sparkline.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +65 -1
- package/dist/stat-card/stat-card.js +98 -25
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +115 -5
- package/dist/table/table.js +374 -94
- package/dist/table/table.js.map +1 -1
- package/dist/text-shimmer/text-shimmer.d.ts +18 -2
- package/dist/text-shimmer/text-shimmer.js +5 -2
- package/dist/text-shimmer/text-shimmer.js.map +1 -1
- package/dist/textarea/textarea.d.ts +7 -0
- package/dist/textarea/textarea.js +3 -1
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/time-picker/time-picker.d.ts +36 -1
- package/dist/time-picker/time-picker.js +33 -5
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/dist/usage-meter/index.d.ts +2 -2
- package/dist/usage-meter/usage-meter.d.ts +30 -0
- package/dist/usage-meter/usage-meter.js +56 -38
- package/dist/usage-meter/usage-meter.js.map +1 -1
- package/package.json +3 -3
|
@@ -6,15 +6,18 @@ import { SeparatorProps } from "react-aria-components";
|
|
|
6
6
|
* Census: 7 files, 6 uses, one of them `orientation="vertical"`. No other
|
|
7
7
|
* prop is ever passed.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
9
|
+
* The legacy defaulted to `decorative: true` and therefore rendered
|
|
10
|
+
* `role="none"`, so every divider in the app was invisible to assistive
|
|
11
|
+
* technology. Here the default is the opposite, which is worth saying out
|
|
12
|
+
* loud rather than burying: React Aria renders an `<hr>` for a horizontal
|
|
13
|
+
* separator, whose implicit role IS separator. A visible line between two
|
|
14
|
+
* groups of content is a separator; announcing it is the correct behaviour.
|
|
15
|
+
*
|
|
16
|
+
* `decorative` is the opt-in for the other case: a rule between inline
|
|
17
|
+
* metadata ("12 posts | 3 drafts") that only spaces items, where announcing
|
|
18
|
+
* "separator" between every pair is noise. React Aria has no equivalent and
|
|
19
|
+
* its Separator always carries a separator role, so a decorative one is a
|
|
20
|
+
* plain `aria-hidden` div drawn with the same classes.
|
|
18
21
|
*
|
|
19
22
|
* Every string in the cva blocks below is a STATIC literal. Tailwind scans
|
|
20
23
|
* source text, so a class assembled at runtime is invisible to it and would
|
|
@@ -33,6 +36,15 @@ export declare const separatorVariants: (props?: ({
|
|
|
33
36
|
interface SeparatorProps$1 extends Omit<SeparatorProps, "className" | "style"> {
|
|
34
37
|
/** @default "horizontal" */
|
|
35
38
|
orientation?: "horizontal" | "vertical";
|
|
39
|
+
/**
|
|
40
|
+
* Draw the line without announcing it: a plain `div` with
|
|
41
|
+
* `aria-hidden="true"` instead of a separator. For a rule that only spaces
|
|
42
|
+
* inline items (metadata, breadcrumbs-like rows), where a screen reader
|
|
43
|
+
* saying "separator" between each pair is noise. Leave it off for a line
|
|
44
|
+
* between two groups of content.
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
decorative?: boolean;
|
|
36
48
|
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
37
49
|
className?: string;
|
|
38
50
|
}
|
|
@@ -11,22 +11,25 @@ import { jsx } from "react/jsx-runtime";
|
|
|
11
11
|
* Census: 7 files, 6 uses, one of them `orientation="vertical"`. No other
|
|
12
12
|
* prop is ever passed.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
14
|
+
* The legacy defaulted to `decorative: true` and therefore rendered
|
|
15
|
+
* `role="none"`, so every divider in the app was invisible to assistive
|
|
16
|
+
* technology. Here the default is the opposite, which is worth saying out
|
|
17
|
+
* loud rather than burying: React Aria renders an `<hr>` for a horizontal
|
|
18
|
+
* separator, whose implicit role IS separator. A visible line between two
|
|
19
|
+
* groups of content is a separator; announcing it is the correct behaviour.
|
|
20
|
+
*
|
|
21
|
+
* `decorative` is the opt-in for the other case: a rule between inline
|
|
22
|
+
* metadata ("12 posts | 3 drafts") that only spaces items, where announcing
|
|
23
|
+
* "separator" between every pair is noise. React Aria has no equivalent and
|
|
24
|
+
* its Separator always carries a separator role, so a decorative one is a
|
|
25
|
+
* plain `aria-hidden` div drawn with the same classes.
|
|
23
26
|
*
|
|
24
27
|
* Every string in the cva blocks below is a STATIC literal. Tailwind scans
|
|
25
28
|
* source text, so a class assembled at runtime is invisible to it and would
|
|
26
29
|
* simply never be emitted.
|
|
27
30
|
*/
|
|
28
31
|
const separatorVariants = cva([
|
|
29
|
-
"shrink-0 bg-
|
|
32
|
+
"shrink-0 bg-separator border-none",
|
|
30
33
|
"m-0",
|
|
31
34
|
"box-border"
|
|
32
35
|
], {
|
|
@@ -36,7 +39,26 @@ const separatorVariants = cva([
|
|
|
36
39
|
} },
|
|
37
40
|
defaultVariants: { orientation: "horizontal" }
|
|
38
41
|
});
|
|
39
|
-
|
|
42
|
+
/** Props a decorative separator does not forward to its div (see below). */
|
|
43
|
+
const DECORATIVE_DROPPED = [
|
|
44
|
+
"elementType",
|
|
45
|
+
"slot",
|
|
46
|
+
"render",
|
|
47
|
+
"aria-label",
|
|
48
|
+
"aria-labelledby"
|
|
49
|
+
];
|
|
50
|
+
const Separator$1 = forwardRef(function Separator$2({ className, orientation = "horizontal", decorative = false, ...props }, ref) {
|
|
51
|
+
if (decorative) {
|
|
52
|
+
const rest = { ...props };
|
|
53
|
+
for (const key of DECORATIVE_DROPPED) delete rest[key];
|
|
54
|
+
return /* @__PURE__ */ jsx("div", {
|
|
55
|
+
ref,
|
|
56
|
+
...rest,
|
|
57
|
+
"aria-hidden": "true",
|
|
58
|
+
"data-slot": "separator",
|
|
59
|
+
className: cn(separatorVariants({ orientation }), className)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
40
62
|
return /* @__PURE__ */ jsx(Separator, {
|
|
41
63
|
ref,
|
|
42
64
|
...props,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"separator.js","names":["Separator","AriaSeparator"],"sources":["../../src/separator/separator.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef } from \"react\";\nimport {\n Separator as AriaSeparator,\n type SeparatorProps as AriaSeparatorProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Separator, ported from the legacy component onto React Aria's Separator.\n *\n * Census: 7 files, 6 uses, one of them `orientation=\"vertical\"`. No other\n * prop is ever passed.\n *\n *
|
|
1
|
+
{"version":3,"file":"separator.js","names":["Separator","AriaSeparator"],"sources":["../../src/separator/separator.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type Ref } from \"react\";\nimport {\n Separator as AriaSeparator,\n type SeparatorProps as AriaSeparatorProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Separator, ported from the legacy component onto React Aria's Separator.\n *\n * Census: 7 files, 6 uses, one of them `orientation=\"vertical\"`. No other\n * prop is ever passed.\n *\n * The legacy defaulted to `decorative: true` and therefore rendered\n * `role=\"none\"`, so every divider in the app was invisible to assistive\n * technology. Here the default is the opposite, which is worth saying out\n * loud rather than burying: React Aria renders an `<hr>` for a horizontal\n * separator, whose implicit role IS separator. A visible line between two\n * groups of content is a separator; announcing it is the correct behaviour.\n *\n * `decorative` is the opt-in for the other case: a rule between inline\n * metadata (\"12 posts | 3 drafts\") that only spaces items, where announcing\n * \"separator\" between every pair is noise. React Aria has no equivalent and\n * its Separator always carries a separator role, so a decorative one is a\n * plain `aria-hidden` div drawn with the same classes.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const separatorVariants = cva(\n [\n // border-none, not border-0. `border-0` sets the WIDTH to zero but leaves\n // border-style at solid, so the computed style still claims a border that\n // simply measures nothing -- the same bookkeeping the Checkbox parity run\n // turned up on the legacy input. `border-none` says what is true.\n // The separator role, not `border`: `border` is the outline of a card or\n // field, `separator` the hairline between rows and groups inside one.\n \"shrink-0 bg-separator border-none\",\n // The user-agent <hr> ships a block margin and a 1px inset border, and\n // React Aria renders an <hr> for the horizontal case. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none -- unreset,\n // every divider is a grooved double line with space around it.\n \"m-0\",\n \"box-border\",\n ],\n {\n variants: {\n orientation: {\n // h-px / w-px, not the legacy h-[1px] / w-[1px]. Those are bracket\n // values that check:hardcoding rejects, and `px` is a first-class\n // Tailwind step rather than an arbitrary one.\n horizontal: \"h-px w-full\",\n vertical: \"h-full w-px\",\n },\n },\n defaultVariants: { orientation: \"horizontal\" },\n },\n);\n\n/**\n * A visual divider between two groups of content.\n *\n * @example\n * <Separator />\n * <Separator orientation=\"vertical\" />\n */\nexport interface SeparatorProps extends Omit<AriaSeparatorProps, \"className\" | \"style\"> {\n /** @default \"horizontal\" */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * Draw the line without announcing it: a plain `div` with\n * `aria-hidden=\"true\"` instead of a separator. For a rule that only spaces\n * inline items (metadata, breadcrumbs-like rows), where a screen reader\n * saying \"separator\" between each pair is noise. Leave it off for a line\n * between two groups of content.\n * @default false\n */\n decorative?: boolean;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** Props a decorative separator does not forward to its div (see below). */\nconst DECORATIVE_DROPPED = [\n \"elementType\",\n \"slot\",\n \"render\",\n \"aria-label\",\n \"aria-labelledby\",\n] as const;\n\nexport const Separator = forwardRef<HTMLElement, SeparatorProps>(function Separator(\n { className, orientation = \"horizontal\", decorative = false, ...props },\n ref,\n) {\n if (decorative) {\n // Everything else the caller passed (id, data-*, handlers, dir...) reaches\n // the div, as it reaches React Aria's Separator below. Dropped: the props\n // that only mean something to React Aria (`elementType`, `slot`, `render`)\n // and a name, which an aria-hidden line has no use for.\n const rest = { ...props };\n for (const key of DECORATIVE_DROPPED) delete rest[key];\n return (\n <div\n ref={ref as Ref<HTMLDivElement>}\n {...(rest as Omit<typeof rest, (typeof DECORATIVE_DROPPED)[number]>)}\n aria-hidden=\"true\"\n data-slot=\"separator\"\n className={cn(separatorVariants({ orientation }), className)}\n />\n );\n }\n return (\n <AriaSeparator\n ref={ref}\n {...props}\n orientation={orientation}\n data-slot=\"separator\"\n className={cn(separatorVariants({ orientation }), className)}\n />\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,oBAAoB,IAC/B;CAOE;CAKA;CACA;AACF,GACA;CACE,UAAU,EACR,aAAa;EAIX,YAAY;EACZ,UAAU;CACZ,EACF;CACA,iBAAiB,EAAE,aAAa,aAAa;AAC/C,CACF;;AA0BA,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;AACF;AAEA,MAAaA,cAAY,WAAwC,SAASA,YACxE,EAAE,WAAW,cAAc,cAAc,aAAa,OAAO,GAAG,SAChE,KACA;CACA,IAAI,YAAY;EAKd,MAAM,OAAO,EAAE,GAAG,MAAM;EACxB,KAAK,MAAM,OAAO,oBAAoB,OAAO,KAAK;EAClD,OACE,oBAAC,OAAD;GACO;GACL,GAAK;GACL,eAAY;GACZ,aAAU;GACV,WAAW,GAAG,kBAAkB,EAAE,YAAY,CAAC,GAAG,SAAS;EAC5D,CAAA;CAEL;CACA,OACE,oBAACC,WAAD;EACO;EACL,GAAI;EACS;EACb,aAAU;EACV,WAAW,GAAG,kBAAkB,EAAE,YAAY,CAAC,GAAG,SAAS;CAC5D,CAAA;AAEL,CAAC"}
|
|
@@ -7,15 +7,24 @@
|
|
|
7
7
|
* keys scrub through the points: a cursor rides the line, the stretch after it
|
|
8
8
|
* dims, and the headline and context show that point.
|
|
9
9
|
*
|
|
10
|
+
* A missing point (`null`, `undefined` or not a finite number) is a gap: the
|
|
11
|
+
* line breaks around it and the scale ignores it. A point with missing
|
|
12
|
+
* neighbours on both sides is drawn as a dot.
|
|
13
|
+
*
|
|
10
14
|
* Plain SVG, plain CSS transitions and one small spring tween for the two
|
|
11
15
|
* things CSS cannot animate (the shape, and the cursor riding it). No chart or
|
|
12
16
|
* animation library.
|
|
13
17
|
*/
|
|
14
18
|
export declare const sparklineVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
15
19
|
export type SparklineTone = "accent" | "success" | "warning" | "danger";
|
|
20
|
+
export type SparklineSize = "sm" | "md";
|
|
16
21
|
export interface SparklineProps {
|
|
17
|
-
/**
|
|
18
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The series, oldest first. One value draws a flat line. A missing point
|
|
24
|
+
* (`null`, `undefined` or not a finite number) breaks the line, is left out
|
|
25
|
+
* of the scale and reads as `missingLabel` when scrubbed.
|
|
26
|
+
*/
|
|
27
|
+
data: (number | null | undefined)[];
|
|
19
28
|
/** What the line is. Its accessible name starts with it. */
|
|
20
29
|
label: string;
|
|
21
30
|
/** The headline figure, such as "1,284". Scrubbing replaces it with the scrubbed point. */
|
|
@@ -24,6 +33,22 @@ export interface SparklineProps {
|
|
|
24
33
|
change?: string;
|
|
25
34
|
/** @default "accent" */
|
|
26
35
|
tone?: SparklineTone;
|
|
36
|
+
/**
|
|
37
|
+
* Any CSS colour for the line, its fill and its dot, such as a series token
|
|
38
|
+
* (`var(--color-chart-2)`). Overrides the tone's line colour; the change text
|
|
39
|
+
* keeps the tone's legible colour.
|
|
40
|
+
*/
|
|
41
|
+
color?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Hides the caption (label, headline and change), leaving the line alone,
|
|
44
|
+
* for a table cell or a card that shows its own figures. The label still
|
|
45
|
+
* names the figure and the slider. @default false
|
|
46
|
+
*/
|
|
47
|
+
captionHidden?: boolean;
|
|
48
|
+
/** `"sm"` draws a thinner line and smaller dot, for a short line in a table cell. @default "md" */
|
|
49
|
+
size?: SparklineSize;
|
|
50
|
+
/** Read in place of a missing point's value. @default "No data" */
|
|
51
|
+
missingLabel?: string;
|
|
27
52
|
/** Aspect of the drawing; the line fills the width and keeps this ratio. @default 160 */
|
|
28
53
|
width?: number;
|
|
29
54
|
/** @default 52 */
|
|
@@ -16,14 +16,23 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
16
16
|
* keys scrub through the points: a cursor rides the line, the stretch after it
|
|
17
17
|
* dims, and the headline and context show that point.
|
|
18
18
|
*
|
|
19
|
+
* A missing point (`null`, `undefined` or not a finite number) is a gap: the
|
|
20
|
+
* line breaks around it and the scale ignores it. A point with missing
|
|
21
|
+
* neighbours on both sides is drawn as a dot.
|
|
22
|
+
*
|
|
19
23
|
* Plain SVG, plain CSS transitions and one small spring tween for the two
|
|
20
24
|
* things CSS cannot animate (the shape, and the cursor riding it). No chart or
|
|
21
25
|
* animation library.
|
|
22
26
|
*/
|
|
23
27
|
const sparklineVariants = cva("grid min-w-0 gap-3 m-0 box-border font-sans text-foreground");
|
|
24
28
|
/** Space kept above the highest and below the lowest point, and half the line width. */
|
|
25
|
-
const INSET =
|
|
29
|
+
const INSET = {
|
|
30
|
+
md: 5,
|
|
31
|
+
sm: 3
|
|
32
|
+
};
|
|
26
33
|
const CAP = 1.25;
|
|
34
|
+
/** Radius of a lone point's dot, about the line's width. */
|
|
35
|
+
const DOT = 2.5;
|
|
27
36
|
const GRID = Array.from({ length: 161 }, (_, index) => index / 160);
|
|
28
37
|
const GROUPED = new Intl.NumberFormat("en-US");
|
|
29
38
|
/** The first draw: one stroke, slow at both ends like a pen (480ms x 1.75). */
|
|
@@ -53,6 +62,31 @@ function curveFor(data) {
|
|
|
53
62
|
points.push([1, ys[last]]);
|
|
54
63
|
return points;
|
|
55
64
|
}
|
|
65
|
+
/** Fills each missing point straight between its present neighbours (or with the nearest one at the ends), so a gapped line still has a shape to draw and morph. */
|
|
66
|
+
function fillGaps(values) {
|
|
67
|
+
const present = values.flatMap((value, index) => value === null ? [] : [index]);
|
|
68
|
+
if (!present.length) return values.map(() => 0);
|
|
69
|
+
return values.map((value, index) => {
|
|
70
|
+
if (value !== null) return value;
|
|
71
|
+
const after = present.find((at) => at > index);
|
|
72
|
+
const before = present.findLast((at) => at < index);
|
|
73
|
+
if (before === void 0) return values[after];
|
|
74
|
+
if (after === void 0) return values[before];
|
|
75
|
+
return values[before] + (values[after] - values[before]) * (index - before) / (after - before);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** The stretches of x (0 to 1) where the line is drawn: from each present point to the next, split where missing points sit between. */
|
|
79
|
+
function runsFor(values) {
|
|
80
|
+
const last = values.length - 1;
|
|
81
|
+
const runs = [];
|
|
82
|
+
values.forEach((value, index) => {
|
|
83
|
+
if (value === null) return;
|
|
84
|
+
const run = runs[runs.length - 1];
|
|
85
|
+
if (run && values[index - 1] !== null && index > 0) run[1] = index / last;
|
|
86
|
+
else runs.push([index / last, index / last]);
|
|
87
|
+
});
|
|
88
|
+
return runs;
|
|
89
|
+
}
|
|
56
90
|
/** Height of the sampled line at x, straight between samples. */
|
|
57
91
|
function heightAt(shape, x) {
|
|
58
92
|
let low = 0;
|
|
@@ -69,8 +103,8 @@ function heightAt(shape, x) {
|
|
|
69
103
|
return x1 === x0 ? y1 : y0 + (y1 - y0) * (x - x0) / (x1 - x0);
|
|
70
104
|
}
|
|
71
105
|
/** 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) =>
|
|
106
|
+
function geometryOf(shape, w, h, at, inset) {
|
|
107
|
+
const toY = (y) => inset + (1 - y) * (h - inset * 2);
|
|
74
108
|
const pairs = shape.map(([x, y]) => `${(x * w).toFixed(2)},${toY(y).toFixed(2)}`);
|
|
75
109
|
const cx = at * w;
|
|
76
110
|
const cy = toY(heightAt(shape, at));
|
|
@@ -88,11 +122,15 @@ const TONE = {
|
|
|
88
122
|
warning: "text-warning-strong [--spark-stroke:var(--color-warning-strong)]",
|
|
89
123
|
danger: "text-destructive-strong [--spark-stroke:var(--color-destructive-strong)]"
|
|
90
124
|
};
|
|
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
|
|
125
|
+
const Sparkline = forwardRef(function Sparkline({ data, label, value, change, tone = "accent", color, captionHidden = false, size: scale = "md", missingLabel = "No data", width = 160, height = 52, labels, formatValue, area = true, interactive = true, className }, ref) {
|
|
126
|
+
const points = data.map((point) => typeof point === "number" && Number.isFinite(point) ? point : null);
|
|
127
|
+
const values = points.length > 1 ? points : [points[0] ?? null, points[0] ?? null];
|
|
128
|
+
const series = fillGaps(values);
|
|
93
129
|
const last = series.length - 1;
|
|
94
130
|
const key = series.join(",");
|
|
95
131
|
const target = useMemo(() => curveFor(key.split(",").map(Number)), [key]);
|
|
132
|
+
const gapped = values.includes(null);
|
|
133
|
+
const inset = INSET[scale];
|
|
96
134
|
const uid = `spark${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
97
135
|
const figure = useRef(null);
|
|
98
136
|
const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
|
|
@@ -102,7 +140,8 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
102
140
|
const cursorAt = useRef(1);
|
|
103
141
|
const box = useRef({
|
|
104
142
|
w: width,
|
|
105
|
-
h: height
|
|
143
|
+
h: height,
|
|
144
|
+
inset
|
|
106
145
|
});
|
|
107
146
|
const [size, setSize] = useState({
|
|
108
147
|
w: width,
|
|
@@ -120,18 +159,22 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
120
159
|
index,
|
|
121
160
|
dir: index === null || moved.index === null ? 1 : Math.sign(index - moved.index) || 1
|
|
122
161
|
});
|
|
123
|
-
const [first] = useState(() => geometryOf(target, width, height, 1));
|
|
162
|
+
const [first] = useState(() => geometryOf(target, width, height, 1, inset));
|
|
124
163
|
/** One writer for every drawn attribute, straight onto the elements: no re-render per frame. */
|
|
125
164
|
const paint = useCallback(() => {
|
|
126
165
|
const root = svg.current;
|
|
127
166
|
if (!root) return;
|
|
128
|
-
const { w, h } = box.current;
|
|
129
|
-
const g = geometryOf(shape.current, w, h, cursorAt.current);
|
|
167
|
+
const { w, h, inset: room } = box.current;
|
|
168
|
+
const g = geometryOf(shape.current, w, h, cursorAt.current, room);
|
|
130
169
|
const q = (name) => root.querySelector(`[data-part="${name}"]`);
|
|
131
170
|
q("line")?.setAttribute("points", g.line);
|
|
132
171
|
q("trail")?.setAttribute("d", g.trail);
|
|
133
172
|
q("area")?.setAttribute("d", g.fill);
|
|
134
173
|
q("dot")?.setAttribute("points", g.dot);
|
|
174
|
+
for (const node of root.querySelectorAll("[data-part=\"point\"]")) {
|
|
175
|
+
const y = heightAt(shape.current, Number(node.dataset.at));
|
|
176
|
+
node.setAttribute("cy", String(room + (1 - y) * (h - room * 2)));
|
|
177
|
+
}
|
|
135
178
|
q("lit")?.setAttribute("width", String(g.cx + CAP + 16));
|
|
136
179
|
q("dim")?.setAttribute("x", String(g.cx + CAP));
|
|
137
180
|
q("cursor")?.setAttribute("x1", String(g.cx));
|
|
@@ -144,6 +187,7 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
144
187
|
const w = node.clientWidth;
|
|
145
188
|
if (!w || Math.abs(box.current.w - w) < .5) return;
|
|
146
189
|
box.current = {
|
|
190
|
+
...box.current,
|
|
147
191
|
w,
|
|
148
192
|
h: w * height / width
|
|
149
193
|
};
|
|
@@ -152,7 +196,14 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
152
196
|
observer.observe(node);
|
|
153
197
|
return () => observer.disconnect();
|
|
154
198
|
}, [height, width]);
|
|
155
|
-
useIsomorphicLayoutEffect(
|
|
199
|
+
useIsomorphicLayoutEffect(() => {
|
|
200
|
+
box.current.inset = inset;
|
|
201
|
+
paint();
|
|
202
|
+
}, [
|
|
203
|
+
size,
|
|
204
|
+
inset,
|
|
205
|
+
paint
|
|
206
|
+
]);
|
|
156
207
|
useEffect(() => {
|
|
157
208
|
const node = figure.current;
|
|
158
209
|
if (!node) return;
|
|
@@ -202,12 +253,20 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
202
253
|
paint
|
|
203
254
|
]);
|
|
204
255
|
const scrubbing = index !== null;
|
|
205
|
-
const format = (at) => formatValue ? formatValue(
|
|
256
|
+
const format = (at) => values[at] === null ? missingLabel : formatValue ? formatValue(values[at], at) : GROUPED.format(values[at]);
|
|
206
257
|
const point = format(index ?? last);
|
|
207
258
|
const when = labels?.[index ?? last];
|
|
208
259
|
const headline = value === void 0 ? void 0 : scrubbing ? point : value;
|
|
209
260
|
const aside = index === null ? change ?? "" : value === void 0 ? [point, when].filter(Boolean).join(" · ") : when ?? `${index + 1} of ${last + 1}`;
|
|
210
|
-
const description = `${label}: ${value ??
|
|
261
|
+
const description = `${label}: ${value ?? values[last] ?? missingLabel}. ${change ?? ""}`.trim();
|
|
262
|
+
const runs = gapped ? runsFor(values) : [];
|
|
263
|
+
const gaps = gapped ? `url(#${uid}-gaps)` : void 0;
|
|
264
|
+
const toY = (y) => inset + (1 - y) * (size.h - inset * 2);
|
|
265
|
+
const lone = runs.filter(([from, to]) => from === to).map(([at]) => ({
|
|
266
|
+
at,
|
|
267
|
+
cx: at * size.w,
|
|
268
|
+
cy: toY(heightAt(shape.current, at))
|
|
269
|
+
}));
|
|
211
270
|
const pointAt = (clientX) => {
|
|
212
271
|
const rect = plot.current?.getBoundingClientRect();
|
|
213
272
|
return rect?.width ? Math.max(0, Math.min(last, Math.round((clientX - rect.left) / rect.width * last))) : last;
|
|
@@ -260,9 +319,10 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
260
319
|
"data-drawn": drawn || void 0,
|
|
261
320
|
"data-landed": landed || void 0,
|
|
262
321
|
"data-scrubbing": scrubbing || void 0,
|
|
322
|
+
"data-size": scale === "md" ? void 0 : scale,
|
|
263
323
|
"aria-label": description,
|
|
264
324
|
className: cn(sparklineVariants(), className),
|
|
265
|
-
children: [/* @__PURE__ */ jsxs("figcaption", {
|
|
325
|
+
children: [captionHidden ? null : /* @__PURE__ */ jsxs("figcaption", {
|
|
266
326
|
className: "flex min-w-0 items-baseline gap-2 text-sm text-text-secondary",
|
|
267
327
|
children: [
|
|
268
328
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -303,6 +363,10 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
303
363
|
"aria-hidden": "true",
|
|
304
364
|
focusable: "false",
|
|
305
365
|
className: cnAll("block h-auto w-full overflow-visible", TONE[tone]),
|
|
366
|
+
style: color ? {
|
|
367
|
+
color,
|
|
368
|
+
"--spark-stroke": color
|
|
369
|
+
} : void 0,
|
|
306
370
|
children: [
|
|
307
371
|
/* @__PURE__ */ jsxs("defs", { children: [
|
|
308
372
|
/* @__PURE__ */ jsx("clipPath", {
|
|
@@ -338,13 +402,35 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
338
402
|
height: size.h + 32,
|
|
339
403
|
style: { width: drawn ? size.w + 32 : 0 }
|
|
340
404
|
})
|
|
341
|
-
})
|
|
405
|
+
}),
|
|
406
|
+
gapped ? /* @__PURE__ */ jsx("mask", {
|
|
407
|
+
id: `${uid}-gaps`,
|
|
408
|
+
maskUnits: "userSpaceOnUse",
|
|
409
|
+
x: -16,
|
|
410
|
+
y: -16,
|
|
411
|
+
width: size.w + 32,
|
|
412
|
+
height: size.h + 32,
|
|
413
|
+
children: runs.map(([from, to]) => {
|
|
414
|
+
const reach = from === to ? DOT : CAP;
|
|
415
|
+
const x = from === 0 ? -16 : from * size.w - reach;
|
|
416
|
+
const end = to === 1 ? size.w + 16 : to * size.w + reach;
|
|
417
|
+
return /* @__PURE__ */ jsx("rect", {
|
|
418
|
+
"data-part": "run",
|
|
419
|
+
x,
|
|
420
|
+
y: -16,
|
|
421
|
+
width: end - x,
|
|
422
|
+
height: size.h + 32,
|
|
423
|
+
fill: "white"
|
|
424
|
+
}, from);
|
|
425
|
+
})
|
|
426
|
+
}) : null
|
|
342
427
|
] }),
|
|
343
428
|
area ? /* @__PURE__ */ jsx("path", {
|
|
344
429
|
"data-part": "area",
|
|
345
430
|
className: "sparkline-area",
|
|
346
431
|
d: first.fill,
|
|
347
|
-
clipPath: `url(#${uid}-fill)
|
|
432
|
+
clipPath: `url(#${uid}-fill)`,
|
|
433
|
+
mask: gaps
|
|
348
434
|
}) : null,
|
|
349
435
|
/* @__PURE__ */ jsx("line", {
|
|
350
436
|
className: "sparkline-baseline",
|
|
@@ -357,15 +443,25 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
357
443
|
"data-part": "trail",
|
|
358
444
|
className: "sparkline-trail",
|
|
359
445
|
d: first.trail,
|
|
360
|
-
clipPath: `url(#${uid}-dim)
|
|
446
|
+
clipPath: `url(#${uid}-dim)`,
|
|
447
|
+
mask: gaps
|
|
361
448
|
}),
|
|
362
449
|
/* @__PURE__ */ jsx("polyline", {
|
|
363
450
|
"data-part": "line",
|
|
364
451
|
className: "sparkline-line",
|
|
365
452
|
points: first.line,
|
|
366
453
|
pathLength: 1,
|
|
367
|
-
clipPath: `url(#${uid}-lit)
|
|
454
|
+
clipPath: `url(#${uid}-lit)`,
|
|
455
|
+
mask: gaps
|
|
368
456
|
}),
|
|
457
|
+
lone.map(({ at, cx, cy }) => /* @__PURE__ */ jsx("circle", {
|
|
458
|
+
"data-part": "point",
|
|
459
|
+
"data-at": at,
|
|
460
|
+
className: "sparkline-point",
|
|
461
|
+
cx,
|
|
462
|
+
cy,
|
|
463
|
+
r: DOT
|
|
464
|
+
}, cx)),
|
|
369
465
|
/* @__PURE__ */ jsx("line", {
|
|
370
466
|
"data-part": "cursor",
|
|
371
467
|
className: "sparkline-cursor",
|
|
@@ -377,7 +473,8 @@ const Sparkline = forwardRef(function Sparkline({ data, label, value, change, to
|
|
|
377
473
|
/* @__PURE__ */ jsx("polyline", {
|
|
378
474
|
"data-part": "dot",
|
|
379
475
|
className: "sparkline-dot",
|
|
380
|
-
points: first.dot
|
|
476
|
+
points: first.dot,
|
|
477
|
+
mask: gaps
|
|
381
478
|
})
|
|
382
479
|
]
|
|
383
480
|
})
|
|
@@ -1 +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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"}
|
|
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 CSSProperties,\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 * A missing point (`null`, `undefined` or not a finite number) is a gap: the\n * line breaks around it and the scale ignores it. A point with missing\n * neighbours on both sides is drawn as a dot.\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\";\nexport type SparklineSize = \"sm\" | \"md\";\n\nexport interface SparklineProps {\n /**\n * The series, oldest first. One value draws a flat line. A missing point\n * (`null`, `undefined` or not a finite number) breaks the line, is left out\n * of the scale and reads as `missingLabel` when scrubbed.\n */\n data: (number | null | undefined)[];\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 /**\n * Any CSS colour for the line, its fill and its dot, such as a series token\n * (`var(--color-chart-2)`). Overrides the tone's line colour; the change text\n * keeps the tone's legible colour.\n */\n color?: string;\n /**\n * Hides the caption (label, headline and change), leaving the line alone,\n * for a table cell or a card that shows its own figures. The label still\n * names the figure and the slider. @default false\n */\n captionHidden?: boolean;\n /** `\"sm\"` draws a thinner line and smaller dot, for a short line in a table cell. @default \"md\" */\n size?: SparklineSize;\n /** Read in place of a missing point's value. @default \"No data\" */\n missingLabel?: string;\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: Record<SparklineSize, number> = { md: 5, sm: 3 };\nconst CAP = 1.25;\n/** Radius of a lone point's dot, about the line's width. */\nconst DOT = 2.5;\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/** Fills each missing point straight between its present neighbours (or with the nearest one at the ends), so a gapped line still has a shape to draw and morph. */\nfunction fillGaps(values: (number | null)[]): number[] {\n const present = values.flatMap((value, index) => (value === null ? [] : [index]));\n if (!present.length) return values.map(() => 0);\n return values.map((value, index) => {\n if (value !== null) return value;\n const after = present.find((at) => at > index);\n const before = present.findLast((at) => at < index);\n if (before === undefined) return values[after!]!;\n if (after === undefined) return values[before]!;\n return (\n values[before]! + ((values[after]! - values[before]!) * (index - before)) / (after - before)\n );\n });\n}\n\n/** The stretches of x (0 to 1) where the line is drawn: from each present point to the next, split where missing points sit between. */\nfunction runsFor(values: (number | null)[]): [from: number, to: number][] {\n const last = values.length - 1;\n const runs: [number, number][] = [];\n values.forEach((value, index) => {\n if (value === null) return;\n const run = runs[runs.length - 1];\n if (run && values[index - 1] !== null && index > 0) run[1] = index / last;\n else runs.push([index / last, index / last]);\n });\n return runs;\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, inset: 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 color,\n captionHidden = false,\n size: scale = \"md\",\n missingLabel = \"No data\",\n width = 160,\n height = 52,\n labels,\n formatValue,\n area = true,\n interactive = true,\n className,\n },\n ref,\n) {\n const points = data.map((point) =>\n typeof point === \"number\" && Number.isFinite(point) ? point : null,\n );\n const values = points.length > 1 ? points : [points[0] ?? null, points[0] ?? null];\n const series = fillGaps(values);\n const last = series.length - 1;\n const key = series.join(\",\");\n const target = useMemo(() => curveFor(key.split(\",\").map(Number)), [key]);\n const gapped = values.includes(null);\n const inset = INSET[scale];\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, inset });\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, inset));\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, inset: room } = box.current;\n const g = geometryOf(shape.current, w, h, cursorAt.current, room);\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 // A lone point's dot rides the morphing line, at the height the line has under it.\n for (const node of root.querySelectorAll<SVGElement>('[data-part=\"point\"]')) {\n const y = heightAt(shape.current, Number(node.dataset.at));\n node.setAttribute(\"cy\", String(room + (1 - y) * (h - room * 2)));\n }\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 = { ...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(() => {\n box.current.inset = inset;\n paint();\n }, [size, inset, 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 values[at] === null\n ? missingLabel\n : formatValue\n ? formatValue(values[at]!, at)\n : GROUPED.format(values[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 ?? values[last] ?? missingLabel}. ${change ?? \"\"}`.trim();\n\n // Where the line is drawn, in the chart's pixels. Each stretch reaches half a line width past its end points so their round caps show; a lone point keeps room for its dot.\n const runs = gapped ? runsFor(values) : [];\n const gaps = gapped ? `url(#${uid}-gaps)` : undefined;\n const toY = (y: number) => inset + (1 - y) * (size.h - inset * 2);\n // At the height of the line on screen, not of its target: `paint` carries the dot from there with the morph.\n const lone = runs\n .filter(([from, to]) => from === to)\n .map(([at]) => ({ at, cx: at * size.w, cy: toY(heightAt(shape.current, at)) }));\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 data-size={scale === \"md\" ? undefined : scale}\n aria-label={description}\n className={cn(sparklineVariants(), className)}\n >\n {captionHidden ? null : (\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 )}\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 style={color ? ({ color, \"--spark-stroke\": color } as CSSProperties) : undefined}\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 {gapped ? (\n <mask\n id={`${uid}-gaps`}\n maskUnits=\"userSpaceOnUse\"\n x={-16}\n y={-16}\n width={size.w + 32}\n height={size.h + 32}\n >\n {runs.map(([from, to]) => {\n const reach = from === to ? DOT : CAP;\n const x = from === 0 ? -16 : from * size.w - reach;\n const end = to === 1 ? size.w + 16 : to * size.w + reach;\n return (\n <rect\n key={from}\n data-part=\"run\"\n x={x}\n y={-16}\n width={end - x}\n height={size.h + 32}\n fill=\"white\"\n />\n );\n })}\n </mask>\n ) : null}\n </defs>\n {area ? (\n <path\n data-part=\"area\"\n className=\"sparkline-area\"\n d={first.fill}\n clipPath={`url(#${uid}-fill)`}\n mask={gaps}\n />\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\n data-part=\"trail\"\n className=\"sparkline-trail\"\n d={first.trail}\n clipPath={`url(#${uid}-dim)`}\n mask={gaps}\n />\n <polyline\n data-part=\"line\"\n className=\"sparkline-line\"\n points={first.line}\n pathLength={1}\n clipPath={`url(#${uid}-lit)`}\n mask={gaps}\n />\n {lone.map(({ at, cx, cy }) => (\n <circle\n key={cx}\n data-part=\"point\"\n data-at={at}\n className=\"sparkline-point\"\n cx={cx}\n cy={cy}\n r={DOT}\n />\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. Inside a gap it is masked away. */}\n <polyline data-part=\"dot\" className=\"sparkline-dot\" points={first.dot} mask={gaps} />\n </svg>\n </div>\n </figure>\n );\n});\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { StatCard, StatCardProps, StatCardTone, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
-
export { StatCard, type StatCardProps, type StatCardTone, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
|
1
|
+
import { StatCard, StatCardChangeTone, StatCardProps, StatCardTone, StatCardTrend, statCardHeaderVariants, statCardIconVariants, statCardLabelVariants, statCardSkeletonVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
+
export { StatCard, type StatCardChangeTone, type StatCardProps, type StatCardTone, type StatCardTrend, statCardHeaderVariants, statCardIconVariants, statCardLabelVariants, statCardSkeletonVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
package/dist/stat-card/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { StatCard, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
-
export { StatCard, statCardHeaderVariants, statCardLabelVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|
|
1
|
+
import { StatCard, statCardHeaderVariants, statCardIconVariants, statCardLabelVariants, statCardSkeletonVariants, statCardSubVariants, statCardValueVariants, statCardVariants } from "./stat-card.js";
|
|
2
|
+
export { StatCard, statCardHeaderVariants, statCardIconVariants, statCardLabelVariants, statCardSkeletonVariants, statCardSubVariants, statCardValueVariants, statCardVariants };
|