@seekalfred/primitives 0.7.3 → 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.
Files changed (91) hide show
  1. package/README.md +60 -1
  2. package/dist/alert/alert.d.ts +2 -2
  3. package/dist/alert-dialog/alert-dialog.d.ts +14 -0
  4. package/dist/alert-dialog/alert-dialog.js +51 -26
  5. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  6. package/dist/announcement-bar/announcement-bar.d.ts +15 -4
  7. package/dist/announcement-bar/announcement-bar.js +31 -6
  8. package/dist/announcement-bar/announcement-bar.js.map +1 -1
  9. package/dist/avatar/avatar.d.ts +7 -0
  10. package/dist/avatar/avatar.js +33 -12
  11. package/dist/avatar/avatar.js.map +1 -1
  12. package/dist/badge/badge.d.ts +1 -1
  13. package/dist/bar-chart/bar-chart-series.d.ts +75 -0
  14. package/dist/bar-chart/bar-chart-series.js +401 -0
  15. package/dist/bar-chart/bar-chart-series.js.map +1 -0
  16. package/dist/bar-chart/bar-chart.d.ts +25 -1
  17. package/dist/bar-chart/bar-chart.js +26 -1
  18. package/dist/bar-chart/bar-chart.js.map +1 -1
  19. package/dist/bar-chart/index.d.ts +2 -1
  20. package/dist/button/button.d.ts +2 -2
  21. package/dist/chip-group/chip-group.d.ts +11 -0
  22. package/dist/chip-group/chip-group.js +50 -34
  23. package/dist/chip-group/chip-group.js.map +1 -1
  24. package/dist/chip-group/index.d.ts +2 -2
  25. package/dist/copy-button/copy-button.d.ts +19 -3
  26. package/dist/copy-button/copy-button.js +7 -4
  27. package/dist/copy-button/copy-button.js.map +1 -1
  28. package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
  29. package/dist/file-drop-zone/file-drop-zone.js +26 -8
  30. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  31. package/dist/gauge/gauge.d.ts +18 -1
  32. package/dist/gauge/gauge.js +35 -21
  33. package/dist/gauge/gauge.js.map +1 -1
  34. package/dist/gauge/index.d.ts +2 -2
  35. package/dist/index.d.ts +8 -7
  36. package/dist/index.js +4 -4
  37. package/dist/input/input.d.ts +7 -0
  38. package/dist/input/input.js +3 -1
  39. package/dist/input/input.js.map +1 -1
  40. package/dist/lib/field-shell.js +2 -6
  41. package/dist/lib/field-shell.js.map +1 -1
  42. package/dist/lib/required-marker.js +20 -0
  43. package/dist/lib/required-marker.js.map +1 -0
  44. package/dist/line-chart/index.d.ts +2 -2
  45. package/dist/line-chart/line-chart.d.ts +105 -3
  46. package/dist/line-chart/line-chart.js +183 -44
  47. package/dist/line-chart/line-chart.js.map +1 -1
  48. package/dist/money-input/index.d.ts +2 -2
  49. package/dist/money-input/money-input.d.ts +14 -0
  50. package/dist/money-input/money-input.js +17 -7
  51. package/dist/money-input/money-input.js.map +1 -1
  52. package/dist/morph-select/morph-select.d.ts +8 -1
  53. package/dist/morph-select/morph-select.js +8 -5
  54. package/dist/morph-select/morph-select.js.map +1 -1
  55. package/dist/multi-select/multi-select.d.ts +31 -0
  56. package/dist/multi-select/multi-select.js +122 -5
  57. package/dist/multi-select/multi-select.js.map +1 -1
  58. package/dist/progress/index.d.ts +2 -2
  59. package/dist/progress/index.js +2 -2
  60. package/dist/progress/progress.d.ts +53 -2
  61. package/dist/progress/progress.js +146 -36
  62. package/dist/progress/progress.js.map +1 -1
  63. package/dist/radio-group/radio-group.d.ts +1 -1
  64. package/dist/scrub-input/scrub-input.d.ts +11 -1
  65. package/dist/scrub-input/scrub-input.js +22 -10
  66. package/dist/scrub-input/scrub-input.js.map +1 -1
  67. package/dist/select/select.d.ts +9 -0
  68. package/dist/select/select.js +3 -2
  69. package/dist/select/select.js.map +1 -1
  70. package/dist/sparkline/sparkline.d.ts +27 -2
  71. package/dist/sparkline/sparkline.js +115 -18
  72. package/dist/sparkline/sparkline.js.map +1 -1
  73. package/dist/stat-card/index.d.ts +2 -2
  74. package/dist/stat-card/index.js +2 -2
  75. package/dist/stat-card/stat-card.d.ts +32 -1
  76. package/dist/stat-card/stat-card.js +52 -9
  77. package/dist/stat-card/stat-card.js.map +1 -1
  78. package/dist/styles.css +1 -1
  79. package/dist/table/index.d.ts +2 -2
  80. package/dist/table/index.js +2 -2
  81. package/dist/table/table.d.ts +114 -4
  82. package/dist/table/table.js +374 -94
  83. package/dist/table/table.js.map +1 -1
  84. package/dist/text-shimmer/text-shimmer.d.ts +18 -2
  85. package/dist/text-shimmer/text-shimmer.js +5 -2
  86. package/dist/text-shimmer/text-shimmer.js.map +1 -1
  87. package/dist/textarea/textarea.d.ts +7 -0
  88. package/dist/textarea/textarea.js +3 -1
  89. package/dist/textarea/textarea.js.map +1 -1
  90. package/dist/toast/toast.d.ts +2 -2
  91. package/package.json +3 -3
@@ -25,17 +25,34 @@ const avatarVariants = cva([
25
25
  "relative inline-flex shrink-0 items-center justify-center",
26
26
  "rounded-full",
27
27
  "box-border",
28
- "font-sans font-medium",
29
- "bg-muted text-foreground"
28
+ "font-sans font-medium"
30
29
  ], {
31
- variants: { size: {
32
- xs: "size-5 text-2xs leading-(--leading-none)",
33
- sm: "size-6 text-2xs leading-(--leading-none)",
34
- md: "size-8 text-sm leading-(--leading-none)",
35
- lg: "size-10 text-sm leading-(--leading-none)",
36
- xl: "size-16 text-base leading-(--leading-none)"
37
- } },
38
- defaultVariants: { size: "md" }
30
+ variants: {
31
+ size: {
32
+ xs: "size-5 text-2xs leading-(--leading-none)",
33
+ sm: "size-6 text-2xs leading-(--leading-none)",
34
+ md: "size-8 text-sm leading-(--leading-none)",
35
+ lg: "size-10 text-sm leading-(--leading-none)",
36
+ xl: "size-16 text-base leading-(--leading-none)"
37
+ },
38
+ /**
39
+ * The fill behind the initials. Every pair is one the token contrast
40
+ * contract already grades: foreground on muted, primary-foreground on
41
+ * primary, and for `brand` secondary-foreground and tertiary-foreground
42
+ * (both the same ink) on each end of the gradient. `brand` is the
43
+ * periwinkle-to-teal edge of a highlighted Card, laid as a fill: the
44
+ * assistant's avatar in a chat, beside `primary` for the person.
45
+ */
46
+ tone: {
47
+ neutral: "bg-muted text-foreground",
48
+ primary: "bg-primary text-primary-foreground",
49
+ brand: "bg-linear-to-r from-secondary to-tertiary text-secondary-foreground"
50
+ }
51
+ },
52
+ defaultVariants: {
53
+ size: "md",
54
+ tone: "neutral"
55
+ }
39
56
  });
40
57
  /** Where the presence dot sits and how big it is, per size. Static strings: Tailwind scans source text. */
41
58
  const dotPlace = {
@@ -45,7 +62,7 @@ const dotPlace = {
45
62
  lg: "bottom-0 end-0 size-3",
46
63
  xl: "bottom-1 end-1 size-3.5"
47
64
  };
48
- const Avatar = forwardRef(function Avatar({ className, size, src, alt, fallback, presence, ...props }, ref) {
65
+ const Avatar = forwardRef(function Avatar({ className, size, tone, src, alt, fallback, presence, ...props }, ref) {
49
66
  const [status, setStatus] = useState(src === void 0 ? "idle" : "loading");
50
67
  /**
51
68
  * Reset when src changes, during RENDER rather than in an effect.
@@ -80,7 +97,11 @@ const Avatar = forwardRef(function Avatar({ className, size, src, alt, fallback,
80
97
  ...props,
81
98
  "data-slot": "avatar",
82
99
  "data-status": status,
83
- className: cn(avatarVariants({ size }), className),
100
+ "data-tone": tone ?? "neutral",
101
+ className: cn(avatarVariants({
102
+ size,
103
+ tone
104
+ }), className),
84
105
  children: [
85
106
  showImage ? /* @__PURE__ */ jsx("img", {
86
107
  ref: imageRef,
@@ -1 +1 @@
1
- {"version":3,"file":"avatar.js","names":[],"sources":["../../src/avatar/avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n useCallback,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\n\n/**\n * Avatar, ported from the legacy shadcn/Radix component.\n *\n * React Aria ships no avatar, so the image load/error state machine is\n * re-implemented here -- see the status handling below, which is the part\n * Radix was actually providing.\n *\n * The three legacy parts (Avatar / AvatarImage / AvatarFallback) collapse\n * into one props-driven component. The census is unambiguous: 36 / 35 / 35\n * uses, always as the same triple, so the compound API bought nothing but\n * three imports and the chance to forget one.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n */\nexport const avatarVariants = cva(\n [\n \"relative inline-flex shrink-0 items-center justify-center\",\n \"rounded-full\",\n // box-border: this library's reset is OPT-IN, so a consumer may load\n // none and the browser default content-box applies. The Alert shipped\n // overflowing its container by 46px for exactly this reason.\n \"box-border\",\n // font-sans: initials are text, and an unstyled document renders text in\n // a serif. The accordion panel shipped that way.\n \"font-sans font-medium\",\n \"bg-muted text-foreground\",\n ],\n {\n variants: {\n // Sizes come from the census, not from taste: call sites reach for\n // h-5 / h-6 / h-8 / h-10 / h-16 through className because the legacy\n // component has no size prop. size-7 (2 uses) has no variant; those\n // sites pick sm or md.\n //\n // Deliberately NOT on the density scale. An avatar is content, not a\n // control -- it does not shrink when a table is compacted, and putting\n // it on --density-control-* would couple a portrait to a button's\n // rhythm. If that turns out wrong it becomes an --avatar-* scale.\n size: {\n xs: \"size-5 text-2xs leading-(--leading-none)\",\n sm: \"size-6 text-2xs leading-(--leading-none)\",\n md: \"size-8 text-sm leading-(--leading-none)\",\n lg: \"size-10 text-sm leading-(--leading-none)\",\n xl: \"size-16 text-base leading-(--leading-none)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/** Where the presence dot sits and how big it is, per size. Static strings: Tailwind scans source text. */\nconst dotPlace = {\n xs: \"bottom-0 end-0 size-1.5\",\n sm: \"bottom-0 end-0 size-2\",\n md: \"bottom-0 end-0 size-2.5\",\n lg: \"bottom-0 end-0 size-3\",\n xl: \"bottom-1 end-1 size-3.5\",\n} as const;\n\ntype Status = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\n/**\n * A user avatar.\n *\n * @example\n * <Avatar src={user.photoUrl} alt={user.name} fallback={initials(user.name)} />\n * <Avatar alt=\"\" fallback=\"JD\" />\n */\nexport interface AvatarProps\n extends Omit<ComponentPropsWithoutRef<\"span\">, \"children\">, VariantProps<typeof avatarVariants> {\n /** Image URL. Omit it to render the fallback alone. */\n src?: string;\n /**\n * A small dot at the corner: `online` or `offline`. It grows in when it appears,\n * shrinks away when it goes, and swaps when it changes. Its word is read to\n * assistive technology beside the name.\n */\n presence?: \"online\" | \"offline\";\n /**\n * Alternative text. **Required**, and `\"\"` is the right value when the\n * person's name already appears next to the avatar.\n *\n * Required on purpose: the census found 15 of 38 legacy `AvatarImage` uses\n * with no `alt` at all, which renders an `<img>` that fails axe's\n * `image-alt` rule. Making it required turns that into a decision the\n * caller makes rather than a default they fall into.\n */\n alt: string;\n /**\n * Shown until the image loads, and permanently if it fails. Usually\n * initials.\n */\n fallback?: ReactNode;\n}\n\nexport const Avatar = forwardRef<HTMLImageElement, AvatarProps>(function Avatar(\n { className, size, src, alt, fallback, presence, ...props },\n ref,\n) {\n const [status, setStatus] = useState<Status>(src === undefined ? \"idle\" : \"loading\");\n\n /**\n * Reset when src changes, during RENDER rather than in an effect.\n *\n * React's documented \"adjusting state when a prop changes\" pattern. An\n * effect that called setState here would be flagged by\n * react-hooks/set-state-in-effect, and rightly: it renders once with the\n * stale status before correcting itself.\n */\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (src !== renderedSrc) {\n setRenderedSrc(src);\n setStatus(src === undefined ? \"idle\" : \"loading\");\n }\n\n /**\n * Resolve the status from the DOM, not only from events.\n *\n * A cached or data-URL image is already decoded by the time React attaches\n * its handlers, so `onLoad` fires BEFORE any effect could run -- and an\n * effect that set \"loading\" would then clobber it and leave the avatar on\n * its fallback forever. A ref callback runs when the node attaches, which\n * is early enough to read `complete` and late enough to have a node. The\n * handlers below still cover the asynchronous case.\n */\n const measure = useCallback((node: HTMLImageElement | null) => {\n if (node?.complete) {\n setStatus(node.naturalWidth > 0 ? \"loaded\" : \"error\");\n } else if (node) {\n // Still downloading after mount: it fades in from a soft blur when it arrives. A photo that\n // is already decoded shows at once, so cached photos never fade.\n node.dataset.loading = \"\";\n }\n }, []);\n\n // Memoized, not called inline on the img below: mergeRefs returns a new\n // callback every call, and an inline call is a new callback every render --\n // React would detach and reattach both refs on every re-render instead of\n // only on mount, re-running measure's decode check each time.\n const imageRef = useMemo(() => mergeRefs(ref, measure), [ref, measure]);\n\n const showImage = src !== undefined && status !== \"error\";\n\n return (\n <span\n {...props}\n data-slot=\"avatar\"\n data-status={status}\n className={cn(avatarVariants({ size }), className)}\n >\n {showImage ? (\n <img\n // The forwarded ref is null while the fallback shows -- the img is\n // not rendered then -- matching measure's own scope, not a new\n // limitation.\n key={src}\n ref={imageRef}\n data-slot=\"avatar-image\"\n className=\"avatar-photo size-full rounded-[inherit] object-cover\"\n src={src}\n alt={alt}\n onLoad={(event) => {\n delete event.currentTarget.dataset.loading;\n setStatus(\"loaded\");\n }}\n onError={() => setStatus(\"error\")}\n />\n ) : null}\n {status === \"loaded\" ? null : (\n // aria-hidden: when the image is present the img carries the\n // accessible name, and when it is not the initials are decorative\n // shorthand for a name the surrounding UI already states.\n <span\n data-slot=\"avatar-fallback\"\n aria-hidden=\"true\"\n className={cn(\"select-none\", src !== undefined && \"motion-safe:animate-avatar-fallback-in\")}\n >\n {fallback}\n </span>\n )}\n {presence === undefined ? null : <span className=\"sr-only\">{presence}</span>}\n <SwapText\n swapKey={presence ?? \"\"}\n motion=\"dot\"\n className={cn(\"absolute z-10\", dotPlace[size ?? \"md\"])}\n >\n <span\n data-slot=\"avatar-presence\"\n data-presence={presence}\n aria-hidden=\"true\"\n className={cn(\n \"block size-full rounded-full border-(length:--border-width) border-background box-border\",\n presence === \"online\" ? \"bg-success-strong\" : \"bg-muted-foreground\",\n )}\n />\n </SwapText>\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,iBAAiB,IAC5B;CACE;CACA;CAIA;CAGA;CACA;AACF,GACA;CACE,UAAU,EAUR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;;AAGA,MAAM,WAAW;CACf,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAsCA,MAAa,SAAS,WAA0C,SAAS,OACvE,EAAE,WAAW,MAAM,KAAK,KAAK,UAAU,UAAU,GAAG,SACpD,KACA;CACA,MAAM,CAAC,QAAQ,aAAa,SAAiB,QAAQ,KAAA,IAAY,SAAS,SAAS;;;;;;;;;CAUnF,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,IAAI,QAAQ,aAAa;EACvB,eAAe,GAAG;EAClB,UAAU,QAAQ,KAAA,IAAY,SAAS,SAAS;CAClD;;;;;;;;;;;CAYA,MAAM,UAAU,aAAa,SAAkC;EAC7D,IAAI,MAAM,UACR,UAAU,KAAK,eAAe,IAAI,WAAW,OAAO;OAC/C,IAAI,MAGT,KAAK,QAAQ,UAAU;CAE3B,GAAG,CAAC,CAAC;CAML,MAAM,WAAW,cAAc,UAAU,KAAK,OAAO,GAAG,CAAC,KAAK,OAAO,CAAC;CAEtE,MAAM,YAAY,QAAQ,KAAA,KAAa,WAAW;CAElD,OACE,qBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,eAAa;EACb,WAAW,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,SAAS;EAJnD,UAAA;GAMG,YACC,oBAAC,OAAD;IAKE,KAAK;IACL,aAAU;IACV,WAAU;IACL;IACA;IACL,SAAS,UAAU;KACjB,OAAO,MAAM,cAAc,QAAQ;KACnC,UAAU,QAAQ;IACpB;IACA,eAAe,UAAU,OAAO;GACjC,GAXM,GAWN,IACC;GACH,WAAW,WAAW,OAIrB,oBAAC,QAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAW,GAAG,eAAe,QAAQ,KAAA,KAAa,wCAAwC;IAEzF,UAAA;GACG,CAAA;GAEP,aAAa,KAAA,IAAY,OAAO,oBAAC,QAAD;IAAM,WAAU;IAAW,UAAA;GAAe,CAAA;GAC3E,oBAAC,UAAD;IACE,SAAS,YAAY;IACrB,QAAO;IACP,WAAW,GAAG,iBAAiB,SAAS,QAAQ,KAAK;IAErD,UAAA,oBAAC,QAAD;KACE,aAAU;KACV,iBAAe;KACf,eAAY;KACZ,WAAW,GACT,4FACA,aAAa,WAAW,sBAAsB,qBAChD;IACD,CAAA;GACO,CAAA;EACN;;AAEV,CAAC"}
1
+ {"version":3,"file":"avatar.js","names":[],"sources":["../../src/avatar/avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n useCallback,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\n\n/**\n * Avatar, ported from the legacy shadcn/Radix component.\n *\n * React Aria ships no avatar, so the image load/error state machine is\n * re-implemented here -- see the status handling below, which is the part\n * Radix was actually providing.\n *\n * The three legacy parts (Avatar / AvatarImage / AvatarFallback) collapse\n * into one props-driven component. The census is unambiguous: 36 / 35 / 35\n * uses, always as the same triple, so the compound API bought nothing but\n * three imports and the chance to forget one.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n */\nexport const avatarVariants = cva(\n [\n \"relative inline-flex shrink-0 items-center justify-center\",\n \"rounded-full\",\n // box-border: this library's reset is OPT-IN, so a consumer may load\n // none and the browser default content-box applies. The Alert shipped\n // overflowing its container by 46px for exactly this reason.\n \"box-border\",\n // font-sans: initials are text, and an unstyled document renders text in\n // a serif. The accordion panel shipped that way.\n \"font-sans font-medium\",\n ],\n {\n variants: {\n // Sizes come from the census, not from taste: call sites reach for\n // h-5 / h-6 / h-8 / h-10 / h-16 through className because the legacy\n // component has no size prop. size-7 (2 uses) has no variant; those\n // sites pick sm or md.\n //\n // Deliberately NOT on the density scale. An avatar is content, not a\n // control -- it does not shrink when a table is compacted, and putting\n // it on --density-control-* would couple a portrait to a button's\n // rhythm. If that turns out wrong it becomes an --avatar-* scale.\n size: {\n xs: \"size-5 text-2xs leading-(--leading-none)\",\n sm: \"size-6 text-2xs leading-(--leading-none)\",\n md: \"size-8 text-sm leading-(--leading-none)\",\n lg: \"size-10 text-sm leading-(--leading-none)\",\n xl: \"size-16 text-base leading-(--leading-none)\",\n },\n /**\n * The fill behind the initials. Every pair is one the token contrast\n * contract already grades: foreground on muted, primary-foreground on\n * primary, and for `brand` secondary-foreground and tertiary-foreground\n * (both the same ink) on each end of the gradient. `brand` is the\n * periwinkle-to-teal edge of a highlighted Card, laid as a fill: the\n * assistant's avatar in a chat, beside `primary` for the person.\n */\n tone: {\n neutral: \"bg-muted text-foreground\",\n primary: \"bg-primary text-primary-foreground\",\n brand: \"bg-linear-to-r from-secondary to-tertiary text-secondary-foreground\",\n },\n },\n defaultVariants: { size: \"md\", tone: \"neutral\" },\n },\n);\n\n/** Where the presence dot sits and how big it is, per size. Static strings: Tailwind scans source text. */\nconst dotPlace = {\n xs: \"bottom-0 end-0 size-1.5\",\n sm: \"bottom-0 end-0 size-2\",\n md: \"bottom-0 end-0 size-2.5\",\n lg: \"bottom-0 end-0 size-3\",\n xl: \"bottom-1 end-1 size-3.5\",\n} as const;\n\ntype Status = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\n/**\n * A user avatar.\n *\n * @example\n * <Avatar src={user.photoUrl} alt={user.name} fallback={initials(user.name)} />\n * <Avatar alt=\"\" fallback=\"JD\" />\n */\nexport interface AvatarProps\n extends Omit<ComponentPropsWithoutRef<\"span\">, \"children\">, VariantProps<typeof avatarVariants> {\n /** Image URL. Omit it to render the fallback alone. */\n src?: string;\n /**\n * A small dot at the corner: `online` or `offline`. It grows in when it appears,\n * shrinks away when it goes, and swaps when it changes. Its word is read to\n * assistive technology beside the name.\n */\n presence?: \"online\" | \"offline\";\n /**\n * Alternative text. **Required**, and `\"\"` is the right value when the\n * person's name already appears next to the avatar.\n *\n * Required on purpose: the census found 15 of 38 legacy `AvatarImage` uses\n * with no `alt` at all, which renders an `<img>` that fails axe's\n * `image-alt` rule. Making it required turns that into a decision the\n * caller makes rather than a default they fall into.\n */\n alt: string;\n /**\n * Shown until the image loads, and permanently if it fails. Usually\n * initials.\n */\n fallback?: ReactNode;\n /**\n * The fill behind the fallback: `neutral` (the default, a muted grey),\n * `primary` (the accent fill) or `brand` (the periwinkle-to-teal gradient of a\n * highlighted Card). A loaded photo covers it. Mirrored to `data-tone`.\n */\n tone?: \"neutral\" | \"primary\" | \"brand\";\n}\n\nexport const Avatar = forwardRef<HTMLImageElement, AvatarProps>(function Avatar(\n { className, size, tone, src, alt, fallback, presence, ...props },\n ref,\n) {\n const [status, setStatus] = useState<Status>(src === undefined ? \"idle\" : \"loading\");\n\n /**\n * Reset when src changes, during RENDER rather than in an effect.\n *\n * React's documented \"adjusting state when a prop changes\" pattern. An\n * effect that called setState here would be flagged by\n * react-hooks/set-state-in-effect, and rightly: it renders once with the\n * stale status before correcting itself.\n */\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (src !== renderedSrc) {\n setRenderedSrc(src);\n setStatus(src === undefined ? \"idle\" : \"loading\");\n }\n\n /**\n * Resolve the status from the DOM, not only from events.\n *\n * A cached or data-URL image is already decoded by the time React attaches\n * its handlers, so `onLoad` fires BEFORE any effect could run -- and an\n * effect that set \"loading\" would then clobber it and leave the avatar on\n * its fallback forever. A ref callback runs when the node attaches, which\n * is early enough to read `complete` and late enough to have a node. The\n * handlers below still cover the asynchronous case.\n */\n const measure = useCallback((node: HTMLImageElement | null) => {\n if (node?.complete) {\n setStatus(node.naturalWidth > 0 ? \"loaded\" : \"error\");\n } else if (node) {\n // Still downloading after mount: it fades in from a soft blur when it arrives. A photo that\n // is already decoded shows at once, so cached photos never fade.\n node.dataset.loading = \"\";\n }\n }, []);\n\n // Memoized, not called inline on the img below: mergeRefs returns a new\n // callback every call, and an inline call is a new callback every render --\n // React would detach and reattach both refs on every re-render instead of\n // only on mount, re-running measure's decode check each time.\n const imageRef = useMemo(() => mergeRefs(ref, measure), [ref, measure]);\n\n const showImage = src !== undefined && status !== \"error\";\n\n return (\n <span\n {...props}\n data-slot=\"avatar\"\n data-status={status}\n data-tone={tone ?? \"neutral\"}\n className={cn(avatarVariants({ size, tone }), className)}\n >\n {showImage ? (\n <img\n // The forwarded ref is null while the fallback shows -- the img is\n // not rendered then -- matching measure's own scope, not a new\n // limitation.\n key={src}\n ref={imageRef}\n data-slot=\"avatar-image\"\n className=\"avatar-photo size-full rounded-[inherit] object-cover\"\n src={src}\n alt={alt}\n onLoad={(event) => {\n delete event.currentTarget.dataset.loading;\n setStatus(\"loaded\");\n }}\n onError={() => setStatus(\"error\")}\n />\n ) : null}\n {status === \"loaded\" ? null : (\n // aria-hidden: when the image is present the img carries the\n // accessible name, and when it is not the initials are decorative\n // shorthand for a name the surrounding UI already states.\n <span\n data-slot=\"avatar-fallback\"\n aria-hidden=\"true\"\n className={cn(\"select-none\", src !== undefined && \"motion-safe:animate-avatar-fallback-in\")}\n >\n {fallback}\n </span>\n )}\n {presence === undefined ? null : <span className=\"sr-only\">{presence}</span>}\n <SwapText\n swapKey={presence ?? \"\"}\n motion=\"dot\"\n className={cn(\"absolute z-10\", dotPlace[size ?? \"md\"])}\n >\n <span\n data-slot=\"avatar-presence\"\n data-presence={presence}\n aria-hidden=\"true\"\n className={cn(\n \"block size-full rounded-full border-(length:--border-width) border-background box-border\",\n presence === \"online\" ? \"bg-success-strong\" : \"bg-muted-foreground\",\n )}\n />\n </SwapText>\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,iBAAiB,IAC5B;CACE;CACA;CAIA;CAGA;AACF,GACA;CACE,UAAU;EAUR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;;;;;;;;;EASA,MAAM;GACJ,SAAS;GACT,SAAS;GACT,OAAO;EACT;CACF;CACA,iBAAiB;EAAE,MAAM;EAAM,MAAM;CAAU;AACjD,CACF;;AAGA,MAAM,WAAW;CACf,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AA4CA,MAAa,SAAS,WAA0C,SAAS,OACvE,EAAE,WAAW,MAAM,MAAM,KAAK,KAAK,UAAU,UAAU,GAAG,SAC1D,KACA;CACA,MAAM,CAAC,QAAQ,aAAa,SAAiB,QAAQ,KAAA,IAAY,SAAS,SAAS;;;;;;;;;CAUnF,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,IAAI,QAAQ,aAAa;EACvB,eAAe,GAAG;EAClB,UAAU,QAAQ,KAAA,IAAY,SAAS,SAAS;CAClD;;;;;;;;;;;CAYA,MAAM,UAAU,aAAa,SAAkC;EAC7D,IAAI,MAAM,UACR,UAAU,KAAK,eAAe,IAAI,WAAW,OAAO;OAC/C,IAAI,MAGT,KAAK,QAAQ,UAAU;CAE3B,GAAG,CAAC,CAAC;CAML,MAAM,WAAW,cAAc,UAAU,KAAK,OAAO,GAAG,CAAC,KAAK,OAAO,CAAC;CAEtE,MAAM,YAAY,QAAQ,KAAA,KAAa,WAAW;CAElD,OACE,qBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,eAAa;EACb,aAAW,QAAQ;EACnB,WAAW,GAAG,eAAe;GAAE;GAAM;EAAK,CAAC,GAAG,SAAS;EALzD,UAAA;GAOG,YACC,oBAAC,OAAD;IAKE,KAAK;IACL,aAAU;IACV,WAAU;IACL;IACA;IACL,SAAS,UAAU;KACjB,OAAO,MAAM,cAAc,QAAQ;KACnC,UAAU,QAAQ;IACpB;IACA,eAAe,UAAU,OAAO;GACjC,GAXM,GAWN,IACC;GACH,WAAW,WAAW,OAIrB,oBAAC,QAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAW,GAAG,eAAe,QAAQ,KAAA,KAAa,wCAAwC;IAEzF,UAAA;GACG,CAAA;GAEP,aAAa,KAAA,IAAY,OAAO,oBAAC,QAAD;IAAM,WAAU;IAAW,UAAA;GAAe,CAAA;GAC3E,oBAAC,UAAD;IACE,SAAS,YAAY;IACrB,QAAO;IACP,WAAW,GAAG,iBAAiB,SAAS,QAAQ,KAAK;IAErD,UAAA,oBAAC,QAAD;KACE,aAAU;KACV,iBAAe;KACf,eAAY;KACZ,WAAW,GACT,4FACA,aAAa,WAAW,sBAAsB,qBAChD;IACD,CAAA;GACO,CAAA;EACN;;AAEV,CAAC"}
@@ -36,7 +36,7 @@ import { ComponentPropsWithoutRef, ReactNode } from "react";
36
36
  * across every shadcn control.
37
37
  */
38
38
  export declare const badgeVariants: (props?: ({
39
- tone?: "primary" | "success" | "warning" | "danger" | "neutral" | "info" | null | undefined;
39
+ tone?: "neutral" | "warning" | "primary" | "success" | "info" | "danger" | null | undefined;
40
40
  appearance?: "outline" | "solid" | "soft" | null | undefined;
41
41
  size?: "sm" | "md" | "lg" | null | undefined;
42
42
  shape?: "rounded" | "pill" | null | undefined;
@@ -0,0 +1,75 @@
1
+ //#region src/bar-chart/bar-chart-series.d.ts
2
+ /**
3
+ * The series mode of BarChart: several measures per category, side by side
4
+ * (`layout="grouped"`) or stacked into one bar (`layout="stacked"`), on a
5
+ * categorical axis in data order, upright (`orientation="vertical"`) or as
6
+ * ranked rows (`orientation="horizontal"`). One series is a plain categorical
7
+ * or horizontal bar chart.
8
+ *
9
+ * Bars grow from the baseline the first time the chart is seen, a beat apart
10
+ * (no motion under reduced motion). Hover, drag or the arrow keys step through
11
+ * the categories: the active category keeps its colour while the rest step
12
+ * back, and the legend reads that category's values. The legend toggles a
13
+ * series off and on. A visually hidden table carries every value, and the plot
14
+ * is a slider over the categories.
15
+ */
16
+ export interface BarChartSeries {
17
+ /** Stable identity, the key of this series' number in each datum's `values`. */
18
+ key: string;
19
+ /** Name in the legend and the data table. */
20
+ label: string;
21
+ /** Any CSS colour. Defaults to chart-1 to chart-4 in series order, then neutral steps. */
22
+ color?: string;
23
+ }
24
+ export interface BarChartSeriesDatum {
25
+ /** Stable identity of the category. */
26
+ key: string;
27
+ /** Full label for the readout and screen readers, such as "Brand search". */
28
+ label: string;
29
+ /** Short label on the category axis. Defaults to `label`. */
30
+ axisLabel?: string;
31
+ /**
32
+ * One value per series key. A missing value (`undefined`, `null` or not a
33
+ * finite number) draws no bar and reads as `missingLabel`. Negative values
34
+ * are drawn as zero: bars rise from a zero baseline.
35
+ */
36
+ values: Record<string, number | null | undefined>;
37
+ }
38
+ export interface BarChartSeriesProps {
39
+ data: BarChartSeriesDatum[];
40
+ /** The measures. Passing `series` is what turns on this mode. */
41
+ series: BarChartSeries[];
42
+ /** What is measured, such as "Spend by campaign". Names the chart for assistive technology. */
43
+ label: string;
44
+ /** The range on show, such as "Sep 15–21, 2026". Read with the name; not printed. */
45
+ period?: string;
46
+ /** Series side by side in each category, or stacked into one bar. @default "grouped" */
47
+ layout?: "grouped" | "stacked";
48
+ /** Upright columns, or rows that grow from the inline start (ranked lists). @default "vertical" */
49
+ orientation?: "vertical" | "horizontal";
50
+ /** Unit after each value in the readout and the table, such as "min". */
51
+ unit?: string;
52
+ /**
53
+ * Plot height in pixels. Upright: @default 176. Horizontal: 32px per
54
+ * category by default.
55
+ */
56
+ height?: number;
57
+ /** Formats values in the legend readout and the data table. */
58
+ formatValue?: (value: number, series: BarChartSeries) => string;
59
+ /** Formats the value axis. Defaults to a compact number, such as 1.2K. */
60
+ formatTick?: (value: number) => string;
61
+ /** Header of the first column in the data table read by screen readers. @default "Category" */
62
+ categoryLabel?: string;
63
+ /** Read in place of a missing value. @default "No data" */
64
+ missingLabel?: string;
65
+ /** Series toggles above the plot. Shown by default when there is more than one series. */
66
+ legend?: boolean;
67
+ /** Controlled hidden series keys. */
68
+ hiddenSeries?: string[];
69
+ defaultHiddenSeries?: string[];
70
+ onHiddenSeriesChange?: (hidden: string[]) => void;
71
+ /** Extra classes for the root. Merged last. */
72
+ className?: string;
73
+ }
74
+ //#endregion
75
+ //# sourceMappingURL=bar-chart-series.d.ts.map
@@ -0,0 +1,401 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { mergeRefs } from "../lib/merge-refs.js";
4
+ import { prefersReducedMotion, useIsomorphicLayoutEffect } from "../lib/motion.js";
5
+ import { forwardRef, useEffect, useId, useMemo, useRef, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/bar-chart/bar-chart-series.tsx
8
+ /** Room past the top gridline, the share of a slot a category fills, the widest bar, the gap inside a group, the data-end radius, a row's default height. */
9
+ const TOP = 12;
10
+ const FILL = .62;
11
+ const MAX_BAR = 28;
12
+ const INNER_GAP = 2;
13
+ const RADIUS = 4;
14
+ const ROW = 32;
15
+ /** Stagger between categories on the first reveal, and its cap. */
16
+ const STAGGER_MS = 35;
17
+ const STAGGER_CAP_MS = 360;
18
+ const NEUTRAL = [
19
+ 56,
20
+ 42,
21
+ 32
22
+ ];
23
+ const GROUPED = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
24
+ const COMPACT = new Intl.NumberFormat("en-US", {
25
+ notation: "compact",
26
+ maximumFractionDigits: 1
27
+ });
28
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
29
+ const colorOf = (item, index) => item.color ?? (index < 4 ? `var(--color-chart-${index + 1})` : `color-mix(in oklab, var(--color-foreground) ${NEUTRAL[(index - 4) % NEUTRAL.length]}%, var(--color-surface))`);
30
+ /** The number to draw, or null when there is none. */
31
+ const numberOf = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
32
+ /** Clean gridlines: the smallest step of 1, 2, 3 or 5 that covers the data in four rows or fewer. */
33
+ function scaleFor(max) {
34
+ const safe = Math.max(max, 1);
35
+ const magnitude = 10 ** Math.floor(Math.log10(safe / 4));
36
+ for (const factor of [
37
+ 1,
38
+ 2,
39
+ 3,
40
+ 5,
41
+ 10
42
+ ]) {
43
+ const step = factor * magnitude;
44
+ const rows = Math.ceil(safe / step);
45
+ if (rows <= 4) return {
46
+ top: rows * step,
47
+ ticks: Array.from({ length: rows + 1 }, (_, row) => row * step)
48
+ };
49
+ }
50
+ return {
51
+ top: safe,
52
+ ticks: [0, safe]
53
+ };
54
+ }
55
+ /**
56
+ * One bar as a path in plot pixels: `start` along the category axis, `size`
57
+ * across it, from `from` to `to` along the value axis. Only the data end is
58
+ * rounded, and only when `round`.
59
+ */
60
+ function barPath(vertical, start, size, from, to, round, base) {
61
+ const f = (value) => value.toFixed(2);
62
+ const length = to - from;
63
+ if (size <= 0 || length <= .01) return "";
64
+ const r = round ? Math.min(RADIUS, size / 2, length) : 0;
65
+ if (vertical) {
66
+ const bottom = base - from;
67
+ const top = base - to;
68
+ const x = start;
69
+ return `M${f(x)} ${f(bottom)}V${f(top + r)}A${f(r)} ${f(r)} 0 0 1 ${f(x + r)} ${f(top)}H${f(x + size - r)}A${f(r)} ${f(r)} 0 0 1 ${f(x + size)} ${f(top + r)}V${f(bottom)}Z`;
70
+ }
71
+ const y = start;
72
+ return `M${f(from)} ${f(y)}H${f(to - r)}A${f(r)} ${f(r)} 0 0 1 ${f(to)} ${f(y + r)}V${f(y + size - r)}A${f(r)} ${f(r)} 0 0 1 ${f(to - r)} ${f(y + size)}H${f(from)}Z`;
73
+ }
74
+ const SeriesBarChart = forwardRef(function SeriesBarChart({ data, series, label, period, layout = "grouped", orientation = "vertical", unit = "", height: heightProp, formatValue = (value) => GROUPED.format(value), formatTick = (value) => COMPACT.format(value), categoryLabel = "Category", missingLabel = "No data", legend, hiddenSeries, defaultHiddenSeries = [], onHiddenSeriesChange, className }, ref) {
75
+ const figure = useRef(null);
76
+ const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
77
+ const plot = useRef(null);
78
+ const titleId = useId();
79
+ const vertical = orientation === "vertical";
80
+ const stacked = layout === "stacked";
81
+ const height = heightProp ?? (vertical ? 176 : Math.max(1, data.length) * ROW);
82
+ const suffix = unit ? ` ${unit}` : "";
83
+ const count = Math.max(data.length, 1);
84
+ const last = data.length - 1;
85
+ const [ownHidden, setOwnHidden] = useState(defaultHiddenSeries);
86
+ const hidden = hiddenSeries ?? ownHidden;
87
+ const toggle = (key) => {
88
+ const next = hidden.includes(key) ? hidden.filter((item) => item !== key) : [...hidden, key];
89
+ if (hiddenSeries === void 0) setOwnHidden(next);
90
+ onHiddenSeriesChange?.(next);
91
+ };
92
+ const visible = series.filter((item) => !hidden.includes(item.key));
93
+ const showLegend = legend ?? series.length > 1;
94
+ const peak = data.reduce((max, item) => {
95
+ const values = visible.map((line) => Math.max(0, numberOf(item.values[line.key]) ?? 0));
96
+ return Math.max(max, stacked ? values.reduce((sum, value) => sum + value, 0) : Math.max(0, ...values));
97
+ }, 0);
98
+ const { top, ticks } = useMemo(() => scaleFor(peak), [peak]);
99
+ const [active, setActive] = useState(null);
100
+ const index = active === null ? null : Math.min(active, last);
101
+ const scrubbing = index !== null && last >= 0;
102
+ const [width, setWidth] = useState(0);
103
+ useIsomorphicLayoutEffect(() => {
104
+ const node = plot.current;
105
+ if (!node) return;
106
+ const read = () => setWidth(node.clientWidth);
107
+ read();
108
+ if (typeof ResizeObserver === "undefined") return;
109
+ const observer = new ResizeObserver(read);
110
+ observer.observe(node);
111
+ return () => observer.disconnect();
112
+ }, []);
113
+ const [shown, setShown] = useState(false);
114
+ useEffect(() => {
115
+ const node = figure.current;
116
+ if (!node || prefersReducedMotion() || typeof IntersectionObserver === "undefined") {
117
+ setShown(true);
118
+ return;
119
+ }
120
+ const observer = new IntersectionObserver(([entry]) => {
121
+ if (!entry?.isIntersecting) return;
122
+ observer.disconnect();
123
+ setShown(true);
124
+ }, { threshold: .35 });
125
+ observer.observe(node);
126
+ return () => observer.disconnect();
127
+ }, []);
128
+ const along = vertical ? width : height;
129
+ const length = Math.max(0, vertical ? height - TOP : width - TOP);
130
+ const slot = along / count;
131
+ const groupSize = Math.min(slot * FILL, (stacked ? 1 : Math.max(1, visible.length)) * MAX_BAR);
132
+ const barSize = stacked ? groupSize : Math.max(0, (groupSize - INNER_GAP * (visible.length - 1)) / Math.max(1, visible.length));
133
+ const toPx = (value) => value / (top || 1) * length;
134
+ const step = Math.min(STAGGER_MS, STAGGER_CAP_MS / count);
135
+ const bars = data.flatMap((item, at) => {
136
+ const groupStart = at * slot + (slot - groupSize) / 2;
137
+ let stack = 0;
138
+ const values = series.map((line) => ({
139
+ line,
140
+ value: numberOf(item.values[line.key])
141
+ }));
142
+ const outermost = [...values].reverse().find(({ line, value }) => !hidden.includes(line.key) && (value ?? 0) > 0)?.line.key;
143
+ return values.flatMap(({ line, value }, seriesAt) => {
144
+ if (hidden.includes(line.key) || value === null || value <= 0 || !width) return [];
145
+ const place = visible.indexOf(line);
146
+ const start = stacked ? groupStart : groupStart + place * (barSize + INNER_GAP);
147
+ const from = stacked ? toPx(stack) : 0;
148
+ stack += value;
149
+ const to = stacked ? toPx(stack) : toPx(value);
150
+ const d = barPath(vertical, start, barSize, from, to, !stacked || line.key === outermost, height);
151
+ return d ? [{
152
+ key: `${item.key}:${line.key}`,
153
+ category: item.key,
154
+ series: line.key,
155
+ d,
156
+ color: colorOf(line, seriesAt),
157
+ at
158
+ }] : [];
159
+ });
160
+ });
161
+ const pointAt = (clientX, clientY) => {
162
+ const rect = plot.current?.getBoundingClientRect();
163
+ if (!rect) return 0;
164
+ const offset = vertical ? clientX - rect.left : clientY - rect.top;
165
+ const size = vertical ? rect.width : rect.height;
166
+ if (!size) return 0;
167
+ const rtl = vertical && plot.current && getComputedStyle(plot.current).direction === "rtl";
168
+ const at = Math.floor((rtl ? size - offset : offset) / size * count);
169
+ return clamp(at, 0, last);
170
+ };
171
+ const onKeyDown = (event) => {
172
+ if (event.key === "Escape" && scrubbing) {
173
+ event.preventDefault();
174
+ setActive(null);
175
+ return;
176
+ }
177
+ const from = index ?? -1;
178
+ const page = Math.min(5, count);
179
+ const next = {
180
+ ArrowLeft: from - 1,
181
+ ArrowUp: from - 1,
182
+ ArrowRight: index === null ? 0 : from + 1,
183
+ ArrowDown: index === null ? 0 : from + 1,
184
+ PageUp: from - page,
185
+ PageDown: from + page,
186
+ Home: 0,
187
+ End: last
188
+ }[event.key];
189
+ if (next === void 0 || last < 0) return;
190
+ event.preventDefault();
191
+ setActive(clamp(next, 0, last));
192
+ };
193
+ const read = (item, line) => {
194
+ const value = numberOf(item.values[line.key]);
195
+ return value === null ? missingLabel : `${formatValue(value, line)}${suffix}`;
196
+ };
197
+ const totalOf = (item) => visible.reduce((sum, line) => sum + (numberOf(item.values[line.key]) ?? 0), 0);
198
+ const reading = scrubbing ? data[index] : null;
199
+ const valueText = reading ? `${reading.label}: ${visible.map((line) => `${line.label} ${read(reading, line)}`).join(", ")}` : data.length ? `${data.length} ${categoryLabel.toLowerCase()}` : "No data";
200
+ const name = period ? `${label}, ${period}` : label;
201
+ const [tickHost, tickStyle] = vertical ? ["relative min-w-0 [grid-area:1/2]", (value) => ({ translate: `0 ${height - toPx(value)}px` })] : ["relative min-w-0 [grid-area:2/2]", (value) => ({ insetInlineStart: `${toPx(value)}px` })];
202
+ return /* @__PURE__ */ jsxs("figure", {
203
+ ref: refs,
204
+ "data-slot": "bar-chart",
205
+ "data-layout": layout,
206
+ "data-orientation": orientation,
207
+ "aria-labelledby": titleId,
208
+ className: cn("m-0 grid min-w-0 content-start gap-4 box-border font-sans text-foreground", className),
209
+ children: [
210
+ /* @__PURE__ */ jsx("figcaption", {
211
+ id: titleId,
212
+ className: "sr-only",
213
+ children: name
214
+ }),
215
+ showLegend ? /* @__PURE__ */ jsx("div", {
216
+ role: "group",
217
+ "aria-label": `${label} series`,
218
+ className: "-mx-2 flex min-w-0 flex-wrap gap-x-1 gap-y-0.5",
219
+ children: series.map((line, at) => /* @__PURE__ */ jsxs("button", {
220
+ type: "button",
221
+ "data-slot": "bar-chart-toggle",
222
+ "aria-pressed": !hidden.includes(line.key),
223
+ onClick: () => toggle(line.key),
224
+ style: { "--series": colorOf(line, at) },
225
+ className: "chart-legend-toggle",
226
+ children: [
227
+ /* @__PURE__ */ jsx("span", {
228
+ "aria-hidden": "true",
229
+ className: "bar-chart-swatch"
230
+ }),
231
+ /* @__PURE__ */ jsx("span", {
232
+ className: "chart-toggle-label",
233
+ children: line.label
234
+ }),
235
+ reading && !hidden.includes(line.key) ? /* @__PURE__ */ jsx("span", {
236
+ "data-slot": "bar-chart-toggle-value",
237
+ "aria-hidden": "true",
238
+ className: "tabular-nums text-foreground",
239
+ children: read(reading, line)
240
+ }) : null
241
+ ]
242
+ }, line.key))
243
+ }) : null,
244
+ /* @__PURE__ */ jsxs("div", {
245
+ "data-slot": "bar-chart-chart",
246
+ "data-scrubbing": scrubbing || void 0,
247
+ "data-shown": shown || void 0,
248
+ className: vertical ? "bar-chart-frame" : "bar-chart-frame-horizontal",
249
+ children: [
250
+ /* @__PURE__ */ jsx("div", {
251
+ "aria-hidden": "true",
252
+ "data-slot": "bar-chart-categories",
253
+ className: cn("relative min-w-0", vertical ? "overflow-hidden [grid-area:2/1]" : "[grid-area:1/1]"),
254
+ children: data.map((item, at) => /* @__PURE__ */ jsx("span", {
255
+ "data-category": item.key,
256
+ "data-active": scrubbing && index === at || void 0,
257
+ className: "bar-chart-category",
258
+ style: vertical ? {
259
+ insetInlineStart: `${at * slot}px`,
260
+ width: `${slot}px`
261
+ } : { height: `${slot}px` },
262
+ children: /* @__PURE__ */ jsx("span", {
263
+ className: "truncate",
264
+ children: item.axisLabel ?? item.label
265
+ })
266
+ }, item.key))
267
+ }),
268
+ /* @__PURE__ */ jsxs("div", {
269
+ ref: plot,
270
+ className: cn("relative min-w-0", vertical ? "[grid-area:1/1]" : "[grid-area:1/2]"),
271
+ style: { height },
272
+ children: [/* @__PURE__ */ jsxs("svg", {
273
+ width: "100%",
274
+ height,
275
+ "aria-hidden": "true",
276
+ focusable: "false",
277
+ className: "bar-chart-plot block overflow-hidden",
278
+ children: [
279
+ ticks.slice(1).map((value) => vertical ? /* @__PURE__ */ jsx("line", {
280
+ className: "line-chart-grid",
281
+ x1: 0,
282
+ x2: "100%",
283
+ y1: Math.round(height - toPx(value)) + .5,
284
+ y2: Math.round(height - toPx(value)) + .5
285
+ }, value) : /* @__PURE__ */ jsx("line", {
286
+ className: "line-chart-grid",
287
+ y1: 0,
288
+ y2: "100%",
289
+ x1: Math.round(toPx(value)) + .5,
290
+ x2: Math.round(toPx(value)) + .5
291
+ }, value)),
292
+ bars.map((item) => /* @__PURE__ */ jsx("path", {
293
+ "data-slot": "bar-chart-bar",
294
+ "data-category": item.category,
295
+ "data-series": item.series,
296
+ "data-active": scrubbing && index === item.at || void 0,
297
+ d: item.d,
298
+ className: "bar-chart-series-bar",
299
+ style: {
300
+ "--series": item.color,
301
+ transformOrigin: vertical ? `0 ${height}px` : "0 0",
302
+ transitionDelay: shown ? `0s, ${item.at * step}ms` : void 0
303
+ }
304
+ }, item.key)),
305
+ vertical ? /* @__PURE__ */ jsx("line", {
306
+ className: "line-chart-baseline",
307
+ x1: 0,
308
+ x2: "100%",
309
+ y1: height - .5,
310
+ y2: height - .5
311
+ }) : /* @__PURE__ */ jsx("line", {
312
+ className: "line-chart-baseline",
313
+ y1: 0,
314
+ y2: "100%",
315
+ x1: .5,
316
+ x2: .5
317
+ })
318
+ ]
319
+ }), /* @__PURE__ */ jsx("div", {
320
+ "data-slot": "bar-chart-scrubber",
321
+ role: "slider",
322
+ tabIndex: data.length ? 0 : -1,
323
+ "aria-label": `${label}, explore by ${categoryLabel.toLowerCase()}`,
324
+ "aria-orientation": vertical ? "horizontal" : "vertical",
325
+ "aria-valuemin": 1,
326
+ "aria-valuemax": Math.max(1, data.length),
327
+ "aria-valuenow": (index ?? 0) + 1,
328
+ "aria-valuetext": valueText,
329
+ onPointerDown: (event) => {
330
+ if (event.pointerType !== "mouse") event.currentTarget.setPointerCapture?.(event.pointerId);
331
+ setActive(pointAt(event.clientX, event.clientY));
332
+ },
333
+ onPointerMove: (event) => {
334
+ if (event.pointerType === "mouse" || event.buttons) setActive(pointAt(event.clientX, event.clientY));
335
+ },
336
+ onPointerUp: (event) => {
337
+ if (event.pointerType !== "mouse") setActive(null);
338
+ },
339
+ onPointerCancel: () => setActive(null),
340
+ onPointerLeave: (event) => {
341
+ if (event.pointerType === "mouse") setActive(null);
342
+ },
343
+ onKeyDown,
344
+ onBlur: () => setActive(null),
345
+ onFocus: (event) => {
346
+ if (event.currentTarget.matches(":focus-visible") && last >= 0) setActive((current) => current ?? 0);
347
+ },
348
+ className: "bar-chart-scrubber"
349
+ })]
350
+ }),
351
+ /* @__PURE__ */ jsx("div", {
352
+ "aria-hidden": "true",
353
+ "data-slot": "bar-chart-ticks",
354
+ className: tickHost,
355
+ children: ticks.map((value) => /* @__PURE__ */ jsx("span", {
356
+ "data-tick": value,
357
+ className: vertical ? "line-chart-tick" : "bar-chart-tick-horizontal",
358
+ style: tickStyle(value),
359
+ children: /* @__PURE__ */ jsx("span", {
360
+ className: cn("block text-xs leading-none tabular-nums text-muted-foreground", vertical ? "-translate-y-1/2" : ""),
361
+ children: formatTick(value)
362
+ })
363
+ }, value))
364
+ })
365
+ ]
366
+ }),
367
+ /* @__PURE__ */ jsx("div", {
368
+ className: "sr-only",
369
+ children: /* @__PURE__ */ jsxs("table", { children: [
370
+ /* @__PURE__ */ jsx("caption", { children: name }),
371
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
372
+ /* @__PURE__ */ jsx("th", {
373
+ scope: "col",
374
+ children: categoryLabel
375
+ }),
376
+ visible.map((line) => /* @__PURE__ */ jsx("th", {
377
+ scope: "col",
378
+ children: line.label
379
+ }, line.key)),
380
+ stacked && visible.length > 1 ? /* @__PURE__ */ jsx("th", {
381
+ scope: "col",
382
+ children: "Total"
383
+ }) : null
384
+ ] }) }),
385
+ /* @__PURE__ */ jsx("tbody", { children: data.map((item) => /* @__PURE__ */ jsxs("tr", { children: [
386
+ /* @__PURE__ */ jsx("th", {
387
+ scope: "row",
388
+ children: item.label
389
+ }),
390
+ visible.map((line) => /* @__PURE__ */ jsx("td", { children: read(item, line) }, line.key)),
391
+ stacked && visible.length > 1 ? /* @__PURE__ */ jsxs("td", { children: [formatValue(totalOf(item), visible[0]), suffix] }) : null
392
+ ] }, item.key)) })
393
+ ] })
394
+ })
395
+ ]
396
+ });
397
+ });
398
+ //#endregion
399
+ export { SeriesBarChart };
400
+
401
+ //# sourceMappingURL=bar-chart-series.js.map