@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.
Files changed (147) hide show
  1. package/README.md +255 -5
  2. package/dist/alert/alert.d.ts +49 -4
  3. package/dist/alert/alert.js +66 -38
  4. package/dist/alert/alert.js.map +1 -1
  5. package/dist/alert/index.d.ts +2 -2
  6. package/dist/alert/index.js +2 -2
  7. package/dist/alert-dialog/alert-dialog.d.ts +31 -4
  8. package/dist/alert-dialog/alert-dialog.js +75 -26
  9. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  10. package/dist/announcement-bar/announcement-bar.d.ts +15 -4
  11. package/dist/announcement-bar/announcement-bar.js +31 -6
  12. package/dist/announcement-bar/announcement-bar.js.map +1 -1
  13. package/dist/avatar/avatar.d.ts +7 -0
  14. package/dist/avatar/avatar.js +33 -12
  15. package/dist/avatar/avatar.js.map +1 -1
  16. package/dist/badge/badge.d.ts +1 -1
  17. package/dist/bar-chart/bar-chart-series.d.ts +75 -0
  18. package/dist/bar-chart/bar-chart-series.js +401 -0
  19. package/dist/bar-chart/bar-chart-series.js.map +1 -0
  20. package/dist/bar-chart/bar-chart.d.ts +25 -1
  21. package/dist/bar-chart/bar-chart.js +26 -1
  22. package/dist/bar-chart/bar-chart.js.map +1 -1
  23. package/dist/bar-chart/index.d.ts +2 -1
  24. package/dist/button/button.d.ts +1 -1
  25. package/dist/calendar/calendar.d.ts +1 -1
  26. package/dist/card/card.d.ts +22 -1
  27. package/dist/card/card.js +60 -7
  28. package/dist/card/card.js.map +1 -1
  29. package/dist/carousel/carousel.d.ts +21 -0
  30. package/dist/carousel/carousel.js +34 -8
  31. package/dist/carousel/carousel.js.map +1 -1
  32. package/dist/chip-group/chip-group.d.ts +11 -0
  33. package/dist/chip-group/chip-group.js +50 -34
  34. package/dist/chip-group/chip-group.js.map +1 -1
  35. package/dist/chip-group/index.d.ts +2 -2
  36. package/dist/copy-button/copy-button.d.ts +37 -5
  37. package/dist/copy-button/copy-button.js +35 -11
  38. package/dist/copy-button/copy-button.js.map +1 -1
  39. package/dist/donut-chart/donut-chart.d.ts +22 -0
  40. package/dist/donut-chart/donut-chart.js +35 -18
  41. package/dist/donut-chart/donut-chart.js.map +1 -1
  42. package/dist/donut-chart/index.d.ts +2 -2
  43. package/dist/empty-state/empty-state.d.ts +21 -0
  44. package/dist/empty-state/empty-state.js +16 -4
  45. package/dist/empty-state/empty-state.js.map +1 -1
  46. package/dist/empty-state/index.d.ts +2 -2
  47. package/dist/empty-state/index.js +2 -2
  48. package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
  49. package/dist/file-drop-zone/file-drop-zone.js +26 -8
  50. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  51. package/dist/filter-bar/filter-bar.d.ts +4 -1
  52. package/dist/filter-bar/filter-bar.js +53 -9
  53. package/dist/filter-bar/filter-bar.js.map +1 -1
  54. package/dist/gauge/gauge.d.ts +18 -1
  55. package/dist/gauge/gauge.js +35 -21
  56. package/dist/gauge/gauge.js.map +1 -1
  57. package/dist/gauge/index.d.ts +2 -2
  58. package/dist/index.d.ts +15 -14
  59. package/dist/index.js +9 -9
  60. package/dist/input/input.d.ts +7 -0
  61. package/dist/input/input.js +7 -5
  62. package/dist/input/input.js.map +1 -1
  63. package/dist/lib/field-shell.js +6 -3
  64. package/dist/lib/field-shell.js.map +1 -1
  65. package/dist/lib/field-surface.js +22 -1
  66. package/dist/lib/field-surface.js.map +1 -1
  67. package/dist/lib/required-marker.js +20 -0
  68. package/dist/lib/required-marker.js.map +1 -0
  69. package/dist/lib/use-copy-feedback.js +17 -3
  70. package/dist/lib/use-copy-feedback.js.map +1 -1
  71. package/dist/line-chart/index.d.ts +2 -2
  72. package/dist/line-chart/line-chart.d.ts +144 -2
  73. package/dist/line-chart/line-chart.js +314 -46
  74. package/dist/line-chart/line-chart.js.map +1 -1
  75. package/dist/money-input/index.d.ts +2 -2
  76. package/dist/money-input/money-input.d.ts +36 -1
  77. package/dist/money-input/money-input.js +30 -14
  78. package/dist/money-input/money-input.js.map +1 -1
  79. package/dist/morph-select/morph-select.d.ts +26 -1
  80. package/dist/morph-select/morph-select.js +28 -6
  81. package/dist/morph-select/morph-select.js.map +1 -1
  82. package/dist/multi-select/index.d.ts +2 -2
  83. package/dist/multi-select/index.js +2 -2
  84. package/dist/multi-select/multi-select.d.ts +74 -2
  85. package/dist/multi-select/multi-select.js +213 -39
  86. package/dist/multi-select/multi-select.js.map +1 -1
  87. package/dist/password-field/password-field.d.ts +16 -0
  88. package/dist/password-field/password-field.js +37 -8
  89. package/dist/password-field/password-field.js.map +1 -1
  90. package/dist/popover/index.d.ts +2 -2
  91. package/dist/popover/index.js +2 -2
  92. package/dist/popover/popover.d.ts +39 -2
  93. package/dist/popover/popover.js +29 -2
  94. package/dist/popover/popover.js.map +1 -1
  95. package/dist/progress/index.d.ts +2 -2
  96. package/dist/progress/index.js +2 -2
  97. package/dist/progress/progress.d.ts +68 -3
  98. package/dist/progress/progress.js +156 -39
  99. package/dist/progress/progress.js.map +1 -1
  100. package/dist/radio-group/index.d.ts +2 -2
  101. package/dist/radio-group/index.js +2 -2
  102. package/dist/radio-group/radio-group.d.ts +33 -3
  103. package/dist/radio-group/radio-group.js +109 -9
  104. package/dist/radio-group/radio-group.js.map +1 -1
  105. package/dist/scroll-area/scroll-area.js +4 -3
  106. package/dist/scroll-area/scroll-area.js.map +1 -1
  107. package/dist/scrub-input/scrub-input.d.ts +11 -1
  108. package/dist/scrub-input/scrub-input.js +22 -10
  109. package/dist/scrub-input/scrub-input.js.map +1 -1
  110. package/dist/search-field/search-field.d.ts +22 -1
  111. package/dist/search-field/search-field.js +22 -7
  112. package/dist/search-field/search-field.js.map +1 -1
  113. package/dist/select/select.d.ts +10 -1
  114. package/dist/select/select.js +11 -8
  115. package/dist/select/select.js.map +1 -1
  116. package/dist/separator/separator.d.ts +21 -9
  117. package/dist/separator/separator.js +33 -11
  118. package/dist/separator/separator.js.map +1 -1
  119. package/dist/sparkline/sparkline.d.ts +27 -2
  120. package/dist/sparkline/sparkline.js +115 -18
  121. package/dist/sparkline/sparkline.js.map +1 -1
  122. package/dist/stat-card/index.d.ts +2 -2
  123. package/dist/stat-card/index.js +2 -2
  124. package/dist/stat-card/stat-card.d.ts +65 -1
  125. package/dist/stat-card/stat-card.js +98 -25
  126. package/dist/stat-card/stat-card.js.map +1 -1
  127. package/dist/styles.css +1 -1
  128. package/dist/table/index.d.ts +2 -2
  129. package/dist/table/index.js +2 -2
  130. package/dist/table/table.d.ts +115 -5
  131. package/dist/table/table.js +374 -94
  132. package/dist/table/table.js.map +1 -1
  133. package/dist/text-shimmer/text-shimmer.d.ts +18 -2
  134. package/dist/text-shimmer/text-shimmer.js +5 -2
  135. package/dist/text-shimmer/text-shimmer.js.map +1 -1
  136. package/dist/textarea/textarea.d.ts +7 -0
  137. package/dist/textarea/textarea.js +3 -1
  138. package/dist/textarea/textarea.js.map +1 -1
  139. package/dist/time-picker/time-picker.d.ts +36 -1
  140. package/dist/time-picker/time-picker.js +33 -5
  141. package/dist/time-picker/time-picker.js.map +1 -1
  142. package/dist/toast/toast.d.ts +2 -2
  143. package/dist/usage-meter/index.d.ts +2 -2
  144. package/dist/usage-meter/usage-meter.d.ts +30 -0
  145. package/dist/usage-meter/usage-meter.js +56 -38
  146. package/dist/usage-meter/usage-meter.js.map +1 -1
  147. package/package.json +3 -3
@@ -115,7 +115,7 @@ function Count({ value, format }) {
115
115
  const shown = useCountTo(fine ? value * 1e4 : value);
116
116
  return /* @__PURE__ */ jsx(Fragment$1, { children: format(fine ? shown / 1e4 : shown) });
117
117
  }
118
- const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", formatValue = (value) => GROUPED.format(value), totalLabel = "Total", size = 208, thickness = 24, groupBelow = .04, maxSegments = 6, otherLabel = "Other", activeKey, defaultActiveKey = null, onActiveChange, hiddenKeys, defaultHiddenKeys, onHiddenKeysChange, legendAction = "toggle", legend = true, emptyLabel = "No data yet", className }, ref) {
118
+ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", formatValue = (value) => GROUPED.format(value), totalLabel = "Total", center = "total", size = 208, thickness = 24, groupBelow = .04, maxSegments = 6, otherLabel = "Other", activeKey, defaultActiveKey = null, onActiveChange, hiddenKeys, defaultHiddenKeys, onHiddenKeysChange, legendAction = "toggle", legend = true, emptyLabel = "No data yet", className }, ref) {
119
119
  const figure = useRef(null);
120
120
  const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
121
121
  const legendId = useId();
@@ -167,7 +167,8 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
167
167
  onHiddenKeysChange?.(next);
168
168
  const item = slices.find((slice) => slice.key === key);
169
169
  const nextTotal = slices.reduce((sum, slice) => sum + (next.includes(slice.key) ? 0 : slice.value), 0);
170
- if (item) setAnnouncement(`${item.label} ${isHidden ? "shown" : "hidden"}. ${totalLabel} ${formatValue(nextTotal)}${suffix}.`);
170
+ const totalText = center === "total" ? ` ${totalLabel} ${formatValue(nextTotal)}${suffix}.` : "";
171
+ if (item) setAnnouncement(`${item.label} ${isHidden ? "shown" : "hidden"}.${totalText}`);
171
172
  };
172
173
  const c = size / 2;
173
174
  const outer = c - MARGIN;
@@ -320,7 +321,9 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
320
321
  if (key) select(selected === key ? null : key);
321
322
  };
322
323
  const format = (value) => `${formatValue(value)}${suffix}`;
323
- const describe = (item) => `${item.label}, ${format(item.value)}, ${shareText(item.value / (total || 1))}${item.members ? `. Includes ${item.members.map((member) => member.label).join(", ")}` : ""}`;
324
+ const showShare = center === "total";
325
+ const shareOf = (value) => showShare ? `, ${shareText(value / (total || 1))}` : "";
326
+ const describe = (item) => `${item.label}, ${format(item.value)}${shareOf(item.value)}${item.members ? `. Includes ${item.members.map((member) => member.label).join(", ")}` : ""}`;
324
327
  const rows = useRef([]);
325
328
  const step = (key, index, count) => {
326
329
  return {
@@ -367,20 +370,32 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
367
370
  const onLegendBlur = (event) => {
368
371
  if (!(event.relatedTarget instanceof Node && event.currentTarget.closest("ul")?.contains(event.relatedTarget))) setPreview(null);
369
372
  };
373
+ const rest = center === "total" ? {
374
+ label: totalLabel,
375
+ value: total
376
+ } : center === "none" ? {
377
+ label: " ",
378
+ value: ""
379
+ } : center;
370
380
  const identity = activeSlice ? activeSlice.key : "__total";
381
+ const custom = typeof center === "object" && total > 0;
371
382
  const readouts = [{
372
383
  key: "__total",
373
- label: total ? totalLabel : emptyLabel,
374
- value: total,
375
- meta: null
384
+ label: total ? rest.label : emptyLabel,
385
+ value: total ? rest.value : 0,
386
+ meta: null,
387
+ custom
376
388
  }, ...slices.map((item) => ({
377
389
  key: item.key,
378
390
  label: item.label,
379
391
  value: item.value,
380
- meta: item
392
+ meta: item,
393
+ custom: false
381
394
  }))];
382
395
  const activeIndex = Math.max(0, readouts.findIndex((item) => item.key === identity));
383
- const summary = total ? `${label}. ${totalLabel} ${format(total)}. ${visible.map(describe).join(". ")}.` : `${label}. ${emptyLabel}.`;
396
+ const restValue = (value) => typeof value === "number" ? format(value) : value;
397
+ const restText = center === "total" ? `${totalLabel} ${format(total)}. ` : center === "none" ? "" : `${[center.label, restValue(center.value)].filter(Boolean).join(" ")}. `;
398
+ const summary = total ? `${label}. ${restText}${visible.map(describe).join(". ")}.` : `${label}. ${emptyLabel}.`;
384
399
  const members = (item) => item.members ? item.members.length > 2 ? `${item.members.slice(0, 2).map((member) => member.label).join(", ")} and ${item.members.length - 2} more` : item.members.map((member) => member.label).join(" and ") : null;
385
400
  return /* @__PURE__ */ jsxs("figure", {
386
401
  ref: refs,
@@ -427,6 +442,7 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
427
442
  }, item.key))]
428
443
  }), /* @__PURE__ */ jsx("div", {
429
444
  "aria-hidden": "true",
445
+ "data-center": center === "total" || center === "none" ? center : "custom",
430
446
  className: "donut-chart-center",
431
447
  children: readouts.map((item, index) => /* @__PURE__ */ jsxs("span", {
432
448
  "data-place": Math.sign(index - activeIndex),
@@ -438,22 +454,22 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
438
454
  }),
439
455
  /* @__PURE__ */ jsx("span", {
440
456
  className: "block max-w-full min-h-8 truncate text-h3 font-medium tabular-nums",
441
- children: item.value || item.meta ? /* @__PURE__ */ jsx(Count, {
457
+ children: typeof item.value === "string" ? item.value || " " : item.value || item.meta || item.custom ? /* @__PURE__ */ jsx(Count, {
442
458
  value: item.value,
443
459
  format: formatValue
444
460
  }) : " "
445
461
  }),
446
462
  /* @__PURE__ */ jsx("span", {
447
463
  className: "block w-full min-h-5 truncate text-xs tabular-nums text-muted-foreground",
448
- children: item.meta ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
464
+ children: item.meta && showShare ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
449
465
  /* @__PURE__ */ jsx(Count, {
450
- value: total && !hidden.has(item.key) ? item.value / total : 0,
466
+ value: total && !hidden.has(item.key) ? item.meta.value / total : 0,
451
467
  format: shareText
452
468
  }),
453
- " of",
454
469
  " ",
470
+ "of ",
455
471
  totalLabel.toLowerCase()
456
- ] }) : unit || " "
472
+ ] }) : typeof item.value === "number" ? unit || " " : " "
457
473
  })
458
474
  ]
459
475
  }, item.key))
@@ -479,6 +495,7 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
479
495
  "data-active": item.key === current || void 0,
480
496
  "data-pinned": selected === item.key || void 0,
481
497
  "data-hidden": off || void 0,
498
+ "data-share": showShare ? void 0 : "none",
482
499
  style: { "--slice": colorFor(item) },
483
500
  onClick: () => legendAction === "toggle" ? setHidden(item.key) : select(selected === item.key ? null : item.key),
484
501
  onPointerEnter: (event) => {
@@ -513,14 +530,14 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
513
530
  format: formatValue
514
531
  })
515
532
  }),
516
- /* @__PURE__ */ jsx("span", {
533
+ showShare ? /* @__PURE__ */ jsx("span", {
517
534
  "aria-hidden": "true",
518
535
  className: "donut-chart-row-value justify-self-end font-medium tabular-nums",
519
536
  children: /* @__PURE__ */ jsx(Count, {
520
537
  value: off || !total ? 0 : item.value / total,
521
538
  format: shareText
522
539
  })
523
- })
540
+ }) : null
524
541
  ]
525
542
  })
526
543
  }, item.key);
@@ -548,10 +565,10 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
548
565
  scope: "col",
549
566
  children: "Value"
550
567
  }),
551
- /* @__PURE__ */ jsx("th", {
568
+ showShare ? /* @__PURE__ */ jsx("th", {
552
569
  scope: "col",
553
570
  children: "Share"
554
- })
571
+ }) : null
555
572
  ] }) }),
556
573
  /* @__PURE__ */ jsx("tbody", { children: visible.flatMap((item) => item.members ? item.members.map((member) => ({
557
574
  key: member.key,
@@ -563,7 +580,7 @@ const DonutChart = forwardRef(function DonutChart({ data, label, unit = "", form
563
580
  children: item.label
564
581
  }),
565
582
  /* @__PURE__ */ jsx("td", { children: format(item.value) }),
566
- /* @__PURE__ */ jsx("td", { children: shareText(item.value / total) })
583
+ showShare ? /* @__PURE__ */ jsx("td", { children: shareText(item.value / total) }) : null
567
584
  ] }, item.key)) })
568
585
  ] })
569
586
  }) : null
@@ -1 +1 @@
1
- {"version":3,"file":"donut-chart.js","names":[],"sources":["../../src/donut-chart/donut-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useCountTo } from \"../lib/count-to.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { live, retarget, REVEAL, SETTLE, SNAPPY, type Live } from \"../lib/spring-tween.js\";\n\n/**\n * DonutChart: a few parts of one whole, when the share of each part matters\n * more than a precise comparison.\n *\n * The ring sweeps open the first time it is seen: every segment leaves the top\n * together and each trailing edge follows a beat behind the one before. New\n * data, or a segment shown or hidden, moves every arc from where it is on\n * screen; a leaving segment closes where it sits. The active segment (hover,\n * focus, selection) slides out along its middle and thickens; the rest dim.\n * The centre rolls like a drum between the total and each segment, and every\n * number counts to its new value. Small parts fold into one \"Other\" segment.\n *\n * Plain SVG and CSS; one small spring tween moves the arcs. The legend is the\n * keyboard path (or the ring itself, without a legend), and a visually hidden\n * table carries every value.\n */\nexport interface DonutChartDatum {\n /** Stable identity. A segment whose key survives a data change keeps its place and colour. */\n key: string;\n label: string;\n value: number;\n /** Any CSS colour. Segments default to chart-1 to chart-4 in data order, then neutral steps. */\n color?: string;\n}\n\nexport interface DonutChartProps {\n data: DonutChartDatum[];\n /** What the whole is, such as \"Visits by source\". Names the chart for assistive technology. */\n label: string;\n /** Unit after values, such as \"visits\". */\n unit?: string;\n formatValue?: (value: number) => string;\n /** Centre label at rest, above the total. @default \"Total\" */\n totalLabel?: string;\n /** Diameter in pixels. The chart scales down to fit narrower containers. @default 208 */\n size?: number;\n /** Ring thickness in pixels. @default 24 */\n thickness?: number;\n /** Segments below this share of the total join \"Other\". @default 0.04 */\n groupBelow?: number;\n /** The most segments drawn, counting \"Other\". @default 6 */\n maxSegments?: number;\n /** @default \"Other\" */\n otherLabel?: string;\n /** Controlled selected segment key. Hover and focus preview other segments without changing it. */\n activeKey?: string | null;\n defaultActiveKey?: string | null;\n onActiveChange?: (key: string | null) => void;\n /** Controlled hidden segment keys. A hidden segment closes and the rest of the ring redistributes. */\n hiddenKeys?: string[];\n defaultHiddenKeys?: string[];\n onHiddenKeysChange?: (keys: string[]) => void;\n /** What clicking a legend row does: show or hide its segment, or pin it as selected. @default \"toggle\" */\n legendAction?: \"toggle\" | \"select\";\n /** The synced legend beside or below the ring. @default true */\n legend?: boolean;\n /** @default \"No data yet\" */\n emptyLabel?: string;\n /** Extra classes for the root. Merged last. */\n className?: string;\n}\n\ntype Slice = { key: string; label: string; value: number; color?: string; members?: DonutChartDatum[] };\ntype Arc = { start: number; end: number };\ntype Segment = { start: Live; end: Live; lift: Live };\n\nconst OTHER = \"__other\";\nconst TAU = Math.PI * 2;\n/** Neutral steps after the four series colours, and the quietest step for Other. */\nconst NEUTRAL = [56, 42, 32];\nconst seriesColor = (index: number) =>\n index < 4\n ? `var(--color-chart-${index + 1})`\n : `color-mix(in oklab, var(--color-foreground) ${NEUTRAL[(index - 4) % NEUTRAL.length]}%, var(--color-surface))`;\nconst OTHER_COLOR = \"color-mix(in oklab, var(--color-foreground) 26%, var(--color-surface))\";\n/** Room outside the ring for the lift; growth, margin, the gap between segments and their corner radius, in pixels. */\nconst LIFT = 4;\nconst GROW = 2;\nconst MARGIN = 7;\nconst GAP = 3;\nconst CORNER = 4;\n/** Stagger of the trailing edges on the first sweep. */\nconst STAGGER_MS = 35;\nconst GROUPED = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 1 });\nconst PERCENT = new Intl.NumberFormat(\"en-US\", { style: \"percent\", maximumFractionDigits: 0 });\nconst shareText = (share: number) => (share > 0 && share < 0.01 ? \"<1%\" : PERCENT.format(share));\nconst n2 = (value: number) => (Math.round(value * 100) / 100).toString();\n\n/** Small parts join one \"Other\" segment, but only when at least two would; grouping a single part hides it for nothing. */\nfunction sliceData(data: DonutChartDatum[], groupBelow: number, maxSegments: number, otherLabel: string): Slice[] {\n const parts = data.filter((item) => item.value > 0);\n const total = parts.reduce((sum, item) => sum + item.value, 0);\n if (!total) return [];\n const ranked = [...parts].sort((a, b) => b.value - a.value);\n const keep = new Set(\n ranked.filter((item, rank) => item.value / total >= groupBelow && rank < maxSegments - 1).map((item) => item.key),\n );\n const rest = parts.filter((item) => !keep.has(item.key));\n if (rest.length < 2) return parts.map((item) => ({ ...item }));\n return [\n ...parts.filter((item) => keep.has(item.key)).map((item) => ({ ...item })),\n { key: OTHER, label: otherLabel, value: rest.reduce((sum, item) => sum + item.value, 0), members: rest },\n ];\n}\n\n/** Turns of the ring for each slice in data order; a hidden slice closes to a point where it sits. */\nfunction layout(slices: Slice[], hidden: Set<string>) {\n const total = slices.reduce((sum, item) => sum + (hidden.has(item.key) ? 0 : item.value), 0) || 1;\n let at = 0;\n return new Map(\n slices.map((item) => {\n const arc = { start: at, end: at + (hidden.has(item.key) ? 0 : item.value / total) };\n at = arc.end;\n return [item.key, arc] as const;\n }),\n );\n}\n\n/** Half the width, in pixels, of a sector's outer edge once the gap is cut; below a couple of pixels a segment fades rather than drawing a hairline. */\nfunction breadth(R: number, t0: number, t1: number, gap: number) {\n const turns = t1 - t0;\n if (turns >= 1 - 1e-6) return Infinity;\n return Math.sin(Math.min(turns * Math.PI, Math.PI / 2)) * R - Math.min(gap / 2, ((1 - turns) * TAU * R) / 2);\n}\n\n/**\n * An annular sector between two turns of the ring, clockwise from the top, with\n * rounded corners. Its sides run parallel to the radius at a fixed distance, so\n * the gap between neighbours is the same width from the inner edge to the outer.\n * A thin sector narrows to a wedge and then to nothing, so a closing one never pops.\n */\nfunction sector(c: number, R: number, r: number, t0: number, t1: number, gap: number, corner: number) {\n const turns = t1 - t0;\n if (turns <= 1e-6) return \"\";\n const at = (radius: number, angle: number) => `${n2(c + radius * Math.cos(angle))} ${n2(c + radius * Math.sin(angle))}`;\n if (turns >= 1 - 1e-6) {\n return `M${at(R, -Math.PI / 2)}A${n2(R)} ${n2(R)} 0 1 1 ${at(R, Math.PI / 2)}A${n2(R)} ${n2(R)} 0 1 1 ${at(R, -Math.PI / 2)}ZM${at(r, -Math.PI / 2)}A${n2(r)} ${n2(r)} 0 1 0 ${at(r, Math.PI / 2)}A${n2(r)} ${n2(r)} 0 1 0 ${at(r, -Math.PI / 2)}Z`;\n }\n // Half the gap on each side; a lone segment closes its gap as the rest of the ring empties, so it becomes a whole ring without a jump.\n const p = Math.min(gap / 2, ((1 - turns) * TAU * R) / 2);\n const a0 = t0 * TAU - Math.PI / 2;\n const a1 = t1 * TAU - Math.PI / 2;\n const half = (a1 - a0) / 2;\n const s = Math.sin(Math.min(half, Math.PI / 2));\n if (s * R <= p + 1e-6) return \"\";\n const side = (t: number, angle: number, sign: number) =>\n `${n2(c + t * Math.cos(angle) - sign * p * Math.sin(angle))} ${n2(c + t * Math.sin(angle) + sign * p * Math.cos(angle))}`;\n const band = (R - r) / 2;\n const ro = Math.max(0, Math.min(corner, band, s >= 0.9999 ? Infinity : (s * R - p) / (1 + s)));\n const dO = Math.asin(Math.min(1, (p + ro) / (R - ro)));\n const tO = Math.sqrt(Math.max(0, (R - ro) ** 2 - (p + ro) ** 2));\n const bigO = a1 - a0 - 2 * dO > Math.PI ? 1 : 0;\n let d = `M${side(tO, a0, 1)}`;\n if (ro > 0.01) d += `A${n2(ro)} ${n2(ro)} 0 0 1 ${at(R, a0 + dO)}`;\n d += `A${n2(R)} ${n2(R)} 0 ${bigO} 1 ${at(R, a1 - dO)}`;\n if (ro > 0.01) d += `A${n2(ro)} ${n2(ro)} 0 0 1 ${side(tO, a1, -1)}`;\n if (s * r > p + 1e-6 || s >= 0.9999) {\n const ri = Math.max(0, Math.min(corner, band, s >= 0.9999 ? Infinity : (s * r - p) / (1 - s)));\n const dI = Math.asin(Math.min(1, (p + ri) / (r + ri)));\n const tI = Math.sqrt(Math.max(0, (r + ri) ** 2 - (p + ri) ** 2));\n const bigI = a1 - a0 - 2 * dI > Math.PI ? 1 : 0;\n d += `L${side(tI, a1, -1)}`;\n if (ri > 0.01) d += `A${n2(ri)} ${n2(ri)} 0 0 1 ${at(r, a1 - dI)}`;\n d += `A${n2(r)} ${n2(r)} 0 ${bigI} 0 ${at(r, a0 + dI)}`;\n if (ri > 0.01) d += `A${n2(ri)} ${n2(ri)} 0 0 1 ${side(tI, a0, 1)}`;\n } else d += `L${at(p / s, (a0 + a1) / 2)}`;\n return `${d}Z`;\n}\n\n/**\n * A number that counts to each new value on the smooth spring. useCountTo counts\n * in whole steps, so a fraction (a share) is counted in ten-thousandths.\n */\nfunction Count({ value, format }: { value: number; format: (value: number) => string }) {\n const fine = !Number.isInteger(value);\n const shown = useCountTo(fine ? value * 10000 : value);\n return <>{format(fine ? shown / 10000 : shown)}</>;\n}\n\nexport const DonutChart = forwardRef<HTMLElement, DonutChartProps>(function DonutChart(\n {\n data,\n label,\n unit = \"\",\n formatValue = (value) => GROUPED.format(value),\n totalLabel = \"Total\",\n size = 208,\n thickness = 24,\n groupBelow = 0.04,\n maxSegments = 6,\n otherLabel = \"Other\",\n activeKey,\n defaultActiveKey = null,\n onActiveChange,\n hiddenKeys,\n defaultHiddenKeys,\n onHiddenKeysChange,\n legendAction = \"toggle\",\n legend = true,\n emptyLabel = \"No data yet\",\n className,\n },\n ref,\n) {\n const figure = useRef<HTMLElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, figure), [ref]);\n const legendId = useId();\n const slices = sliceData(data, groupBelow, maxSegments, otherLabel);\n\n const [ownHidden, setOwnHidden] = useState<string[]>(defaultHiddenKeys ?? []);\n const hiddenList = hiddenKeys ?? ownHidden;\n const hidden = new Set(hiddenList.filter((key) => slices.some((item) => item.key === key)));\n const visible = slices.filter((item) => !hidden.has(item.key));\n const total = visible.reduce((sum, item) => sum + item.value, 0);\n const signature = slices.map((item) => `${item.key}:${item.value}${hidden.has(item.key) ? \"h\" : \"\"}`).join(\"|\");\n const suffix = unit ? ` ${unit}` : \"\";\n\n // Colours follow a key from the first time it is seen, so a segment keeps its colour through every dataset.\n const [palette, setPalette] = useState(() => new Map(data.map((item, index) => [item.key, index])));\n if (data.some((item) => !palette.has(item.key))) {\n const next = new Map(palette);\n for (const item of data) if (!next.has(item.key)) next.set(item.key, next.size);\n setPalette(next);\n }\n const colorFor = (item: Slice) =>\n item.color ?? (item.key === OTHER ? OTHER_COLOR : seriesColor(palette.get(item.key) ?? 0));\n\n // Leaving segments stay drawn in their old place while they close, then drop out.\n const [drawn, setDrawn] = useState<Slice[]>(slices);\n const [seen, setSeen] = useState(signature);\n if (seen !== signature) {\n setSeen(signature);\n const next = [...slices];\n drawn.forEach((item, index) => {\n if (next.some((slice) => slice.key === item.key)) return;\n const before = drawn\n .slice(0, index)\n .reverse()\n .find((prior) => next.some((slice) => slice.key === prior.key));\n next.splice(before ? next.findIndex((slice) => slice.key === before.key) + 1 : 0, 0, { ...item, value: 0 });\n });\n setDrawn(next);\n }\n\n // Selection is controlled or not; hover and focus preview on top of it.\n const [ownActive, setOwnActive] = useState<string | null>(defaultActiveKey);\n const selected = activeKey !== undefined ? activeKey : ownActive;\n const [preview, setPreview] = useState<string | null>(null);\n const select = (key: string | null) => {\n if (activeKey === undefined) setOwnActive(key);\n onActiveChange?.(key);\n };\n const current = [preview, selected].find((key) => key && visible.some((item) => item.key === key)) ?? null;\n const activeSlice = visible.find((item) => item.key === current) ?? null;\n\n const [announcement, setAnnouncement] = useState(\"\");\n const setHidden = (key: string) => {\n const isHidden = hidden.has(key);\n if (!isHidden && visible.length <= 1) return;\n const next = isHidden ? hiddenList.filter((item) => item !== key) : [...hiddenList, key];\n if (hiddenKeys === undefined) setOwnHidden(next);\n onHiddenKeysChange?.(next);\n const item = slices.find((slice) => slice.key === key);\n const nextTotal = slices.reduce((sum, slice) => sum + (next.includes(slice.key) ? 0 : slice.value), 0);\n if (item) setAnnouncement(`${item.label} ${isHidden ? \"shown\" : \"hidden\"}. ${totalLabel} ${formatValue(nextTotal)}${suffix}.`);\n };\n\n // Geometry in the chart's own units; the SVG scales with its container.\n const c = size / 2;\n const outer = c - MARGIN;\n const inner = Math.max(8, outer - thickness);\n const corner = Math.min(CORNER, thickness / 4);\n const geometry = useRef({ c, outer, inner, corner });\n const segments = useRef(new Map<string, Segment>());\n const svg = useRef<SVGSVGElement>(null);\n\n /** One writer: every path's shape, opacity and lift, straight onto the elements. */\n const paint = useCallback(() => {\n const root = svg.current;\n if (!root) return;\n const { c, outer, inner, corner } = geometry.current;\n for (const node of root.querySelectorAll<SVGPathElement>(\"[data-key]\")) {\n const segment = segments.current.get(node.dataset.key!);\n if (!segment) {\n node.setAttribute(\"d\", \"\");\n continue;\n }\n const start = segment.start.value;\n const end = segment.end.value;\n const lift = Math.max(0, segment.lift.value);\n const R = outer + lift * GROW;\n node.setAttribute(\"d\", sector(c, R, inner - lift * GROW * 0.5, start, end, GAP, corner));\n node.setAttribute(\"fill-opacity\", n2(Math.min(1, Math.max(0, (breadth(R, start, end, GAP) - 1) / 3))));\n const middle = ((start + end) / 2) * TAU - Math.PI / 2;\n node.setAttribute(\"transform\", `translate(${n2(Math.cos(middle) * lift * LIFT)} ${n2(Math.sin(middle) * lift * LIFT)})`);\n }\n }, []);\n useIsomorphicLayoutEffect(() => {\n geometry.current = { c, outer, inner, corner };\n paint();\n });\n useEffect(\n () => () => {\n for (const segment of segments.current.values()) {\n segment.start.stop();\n segment.end.stop();\n segment.lift.stop();\n }\n },\n [],\n );\n\n // The ring sweeps once a third of it is in view.\n const [shown, setShown] = useState(false);\n useEffect(() => {\n const node = figure.current;\n if (!node || prefersReducedMotion() || typeof IntersectionObserver === \"undefined\") {\n setShown(true);\n return;\n }\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting) return;\n observer.disconnect();\n setShown(true);\n },\n { threshold: 0.35 },\n );\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const targets = useRef(new Map<string, Arc>());\n const latest = useRef({ drawn, slices });\n useIsomorphicLayoutEffect(() => {\n latest.current = { drawn, slices };\n });\n\n // New data, or a segment shown or hidden, moves every arc from wherever it is on screen.\n useEffect(() => {\n if (!shown) return;\n const { drawn: order, slices: now } = latest.current;\n const goal = layout(now, hidden);\n targets.current = goal;\n const first = segments.current.size === 0;\n const place = (index: number) => {\n const before = order\n .slice(0, index)\n .reverse()\n .find((item) => goal.has(item.key));\n return before ? goal.get(before.key)!.end : 0;\n };\n const drop = (key: string) => {\n if (latest.current.slices.some((item) => item.key === key)) return;\n segments.current.delete(key);\n setDrawn((list) => list.filter((item) => item.key !== key));\n };\n order.forEach((item, index) => {\n const leaving = !goal.has(item.key);\n const to = goal.get(item.key) ?? { start: place(index), end: place(index) };\n let segment = segments.current.get(item.key);\n if (!segment) {\n // A new segment opens from the edge of the one before it, as it is right now.\n const prior = order\n .slice(0, index)\n .reverse()\n .map((entry) => segments.current.get(entry.key))\n .find(Boolean);\n const from = first ? 0 : prior ? prior.end.value : 0;\n segment = { start: live(from), end: live(from), lift: live(0) };\n segments.current.set(item.key, segment);\n }\n if (first) {\n // One sweep: every segment leaves the top together, and each trailing edge follows a beat behind the one before.\n retarget(segment.start, to.start, REVEAL, paint);\n retarget(segment.end, to.end, REVEAL, paint, 40 + index * STAGGER_MS);\n return;\n }\n retarget(segment.start, to.start, SETTLE, paint);\n retarget(segment.end, to.end, SETTLE, paint, 0, leaving ? () => drop(item.key) : undefined);\n });\n paint();\n // The signature stands for the slices and the hidden set.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shown, signature, paint]);\n\n // The active segment slides out along its middle and thickens; the one it replaces settles back.\n useEffect(() => {\n for (const [key, segment] of segments.current) retarget(segment.lift, key === current ? 1 : 0, SNAPPY, paint);\n }, [current, drawn, shown, paint]);\n\n // One pointer handler for the whole ring: the angle picks the segment, so crossing a gap never drops the hover.\n const hit = (event: { clientX: number; clientY: number }) => {\n const box = svg.current?.getBoundingClientRect();\n if (!box?.width) return undefined;\n const scale = size / box.width;\n const x = (event.clientX - box.left) * scale - c;\n const y = (event.clientY - box.top) * scale - c;\n const radius = Math.hypot(x, y);\n if (radius < inner - 6 || radius > c) return null;\n let turn = (Math.atan2(y, x) + Math.PI / 2) / TAU;\n if (turn < 0) turn += 1;\n for (const [key, arc] of targets.current) {\n if (arc.end > arc.start && turn >= arc.start && turn < arc.end && visible.some((item) => item.key === key)) return key;\n }\n return undefined;\n };\n const onRingMove = (event: PointerEvent<SVGSVGElement>) => {\n if (event.pointerType !== \"mouse\") return;\n const key = hit(event);\n if (key !== undefined && key !== preview) setPreview(key);\n };\n const onRingClick = (event: MouseEvent<SVGSVGElement>) => {\n const key = hit(event);\n if (key) select(selected === key ? null : key);\n };\n\n const format = (value: number) => `${formatValue(value)}${suffix}`;\n const describe = (item: Slice) =>\n `${item.label}, ${format(item.value)}, ${shareText(item.value / (total || 1))}${\n item.members ? `. Includes ${item.members.map((member) => member.label).join(\", \")}` : \"\"\n }`;\n\n // Arrow keys walk the segments, in the legend or on the ring itself when there is no legend.\n const rows = useRef<(HTMLButtonElement | null)[]>([]);\n const step = (key: string, index: number, count: number) => {\n const moves: Record<string, number> = {\n ArrowDown: index + 1,\n ArrowRight: index + 1,\n ArrowUp: index - 1,\n ArrowLeft: index - 1,\n Home: 0,\n End: count - 1,\n };\n return moves[key];\n };\n const onLegendKey = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n if (event.key === \"Escape\" && selected) {\n event.preventDefault();\n select(null);\n return;\n }\n const next = step(event.key, index, slices.length);\n if (next === undefined) return;\n event.preventDefault();\n rows.current[(next + slices.length) % slices.length]?.focus();\n };\n const onRingKey = (event: KeyboardEvent<HTMLDivElement>) => {\n if (!visible.length) return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n setPreview(null);\n if (selected) select(null);\n return;\n }\n if ((event.key === \"Enter\" || event.key === \" \") && current) {\n event.preventDefault();\n select(selected === current ? null : current);\n return;\n }\n const index = visible.findIndex((item) => item.key === current);\n const next = step(\n event.key,\n index < 0 ? (event.key.endsWith(\"Up\") || event.key.endsWith(\"Left\") ? 0 : -1) : index,\n visible.length,\n );\n if (next === undefined) return;\n event.preventDefault();\n const item = visible[(next + visible.length) % visible.length]!;\n setPreview(item.key);\n setAnnouncement(describe(item));\n };\n const onLegendBlur = (event: FocusEvent<HTMLButtonElement>) => {\n if (!(event.relatedTarget instanceof Node && event.currentTarget.closest(\"ul\")?.contains(event.relatedTarget))) {\n setPreview(null);\n }\n };\n\n // One readout per segment plus the total; the active one rolls in from the side it sits on.\n const identity = activeSlice ? activeSlice.key : \"__total\";\n const readouts = [\n { key: \"__total\", label: total ? totalLabel : emptyLabel, value: total, meta: null as Slice | null },\n ...slices.map((item) => ({ key: item.key, label: item.label, value: item.value, meta: item })),\n ];\n const activeIndex = Math.max(0, readouts.findIndex((item) => item.key === identity));\n const summary = total ? `${label}. ${totalLabel} ${format(total)}. ${visible.map(describe).join(\". \")}.` : `${label}. ${emptyLabel}.`;\n const members = (item: Slice) =>\n item.members\n ? item.members.length > 2\n ? `${item.members\n .slice(0, 2)\n .map((member) => member.label)\n .join(\", \")} and ${item.members.length - 2} more`\n : item.members.map((member) => member.label).join(\" and \")\n : null;\n\n return (\n <figure\n ref={refs}\n data-slot=\"donut-chart\"\n aria-label={label}\n data-legend={legend || undefined}\n className={cn(\"donut-chart box-border font-sans text-foreground\", className)}\n >\n <div\n data-slot=\"donut-chart-ring\"\n className=\"donut-chart-ring\"\n style={{ width: size, \"--inner\": `${((inner * 2) / size) * 100}%` } as CSSProperties}\n tabIndex={legend ? undefined : 0}\n role={legend ? undefined : \"application\"}\n aria-label={legend ? undefined : `${label}. Use the arrow keys to read each segment.`}\n onKeyDown={legend ? undefined : onRingKey}\n onBlur={legend ? undefined : () => setPreview(null)}\n >\n <svg\n ref={svg}\n viewBox={`0 0 ${size} ${size}`}\n aria-hidden=\"true\"\n focusable=\"false\"\n onPointerMove={onRingMove}\n onPointerLeave={() => setPreview(null)}\n onClick={onRingClick}\n data-hovering={current !== null || undefined}\n className=\"block size-full overflow-visible data-[hovering]:cursor-pointer\"\n >\n <circle\n className=\"donut-chart-track\"\n cx={c}\n cy={c}\n r={(outer + inner) / 2}\n strokeWidth={outer - inner}\n data-idle={!shown || !total || undefined}\n />\n {drawn.map((item) => (\n <path\n key={item.key}\n data-key={item.key}\n data-active={item.key === current || undefined}\n data-dim={(current !== null && item.key !== current) || undefined}\n className=\"donut-chart-segment\"\n style={{ \"--slice\": colorFor(item) } as CSSProperties}\n />\n ))}\n </svg>\n <div aria-hidden=\"true\" className=\"donut-chart-center\">\n {readouts.map((item, index) => (\n <span\n key={item.key}\n data-place={Math.sign(index - activeIndex)}\n className=\"donut-chart-readout\"\n >\n <span className=\"block w-full min-h-5 truncate text-xs text-text-secondary\">{item.label}</span>\n <span className=\"block max-w-full min-h-8 truncate text-h3 font-medium tabular-nums\">\n {item.value || item.meta ? <Count value={item.value} format={formatValue} /> : \" \"}\n </span>\n <span className=\"block w-full min-h-5 truncate text-xs tabular-nums text-muted-foreground\">\n {item.meta ? (\n <>\n <Count value={total && !hidden.has(item.key) ? item.value / total : 0} format={shareText} /> of{\" \"}\n {totalLabel.toLowerCase()}\n </>\n ) : (\n unit || \" \"\n )}\n </span>\n </span>\n ))}\n </div>\n </div>\n {legend && slices.length > 0 ? (\n <ul\n id={legendId}\n aria-label={`${label}, segments${legendAction === \"toggle\" ? \". Press a segment to show or hide it\" : \"\"}`}\n onPointerLeave={() => setPreview(null)}\n className=\"m-0 -mb-0.5 grid list-none p-0\"\n >\n {slices.map((item, index) => {\n const off = hidden.has(item.key);\n return (\n <li key={item.key} className=\"overflow-hidden motion-safe:animate-fade-in\">\n <button\n ref={(node) => {\n rows.current[index] = node;\n }}\n type=\"button\"\n data-slot=\"donut-chart-row\"\n aria-pressed={legendAction === \"toggle\" ? !off : selected === item.key}\n aria-label={off ? `${item.label}, hidden` : describe(item)}\n data-active={item.key === current || undefined}\n data-pinned={selected === item.key || undefined}\n data-hidden={off || undefined}\n style={{ \"--slice\": colorFor(item) } as CSSProperties}\n onClick={() => (legendAction === \"toggle\" ? setHidden(item.key) : select(selected === item.key ? null : item.key))}\n onPointerEnter={(event) => {\n if (event.pointerType === \"mouse\") setPreview(off ? null : item.key);\n }}\n onFocus={(event) => {\n if (event.currentTarget.matches(\":focus-visible\")) setPreview(off ? null : item.key);\n }}\n onBlur={onLegendBlur}\n onKeyDown={(event) => onLegendKey(event, index)}\n className=\"donut-chart-row\"\n >\n <span aria-hidden=\"true\" className=\"donut-chart-dot\" />\n <span className=\"grid min-w-0\">\n <span className=\"donut-chart-row-label truncate\">{item.label}</span>\n {item.members ? <span className=\"truncate text-xs text-muted-foreground\">{members(item)}</span> : null}\n </span>\n <span aria-hidden=\"true\" className=\"donut-chart-row-value justify-self-end tabular-nums text-text-secondary\">\n <Count value={item.value} format={formatValue} />\n </span>\n <span aria-hidden=\"true\" className=\"donut-chart-row-value justify-self-end font-medium tabular-nums\">\n <Count value={off || !total ? 0 : item.value / total} format={shareText} />\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n <p className=\"sr-only\">{summary}</p>\n <p className=\"sr-only\" aria-live=\"polite\">\n {announcement}\n </p>\n {total > 0 ? (\n <div className=\"sr-only\">\n <table>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Segment</th>\n <th scope=\"col\">Value</th>\n <th scope=\"col\">Share</th>\n </tr>\n </thead>\n <tbody>\n {visible\n .flatMap((item) =>\n item.members\n ? item.members.map((member) => ({ key: member.key, label: `${member.label} (${otherLabel})`, value: member.value }))\n : [item],\n )\n .map((item) => (\n <tr key={item.key}>\n <th scope=\"row\">{item.label}</th>\n <td>{format(item.value)}</td>\n <td>{shareText(item.value / total)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : null}\n </figure>\n );\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"donut-chart.js","names":[],"sources":["../../src/donut-chart/donut-chart.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type FocusEvent,\n type KeyboardEvent,\n type MouseEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useCountTo } from \"../lib/count-to.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { live, retarget, REVEAL, SETTLE, SNAPPY, type Live } from \"../lib/spring-tween.js\";\n\n/**\n * DonutChart: a few parts of one whole, when the share of each part matters\n * more than a precise comparison.\n *\n * The ring sweeps open the first time it is seen: every segment leaves the top\n * together and each trailing edge follows a beat behind the one before. New\n * data, or a segment shown or hidden, moves every arc from where it is on\n * screen; a leaving segment closes where it sits. The active segment (hover,\n * focus, selection) slides out along its middle and thickens; the rest dim.\n * The centre rolls like a drum between the total and each segment, and every\n * number counts to its new value. Small parts fold into one \"Other\" segment.\n * Where the parts do not add up to anything (a rate such as CTR, CPC or ROAS\n * per source), `center` hides the total or puts a figure of the caller's own\n * at rest instead; the segment readouts still roll in on hover and focus.\n *\n * Plain SVG and CSS; one small spring tween moves the arcs. The legend is the\n * keyboard path (or the ring itself, without a legend), and a visually hidden\n * table carries every value.\n */\nexport interface DonutChartDatum {\n /** Stable identity. A segment whose key survives a data change keeps its place and colour. */\n key: string;\n label: string;\n value: number;\n /** Any CSS colour. Segments default to chart-1 to chart-4 in data order, then neutral steps. */\n color?: string;\n}\n\n/** A figure of the caller's own for the centre at rest, in place of the summed total. */\nexport interface DonutChartCenter {\n /** The line above the figure, such as \"Blended CTR\". */\n label: string;\n /**\n * The figure. A number is formatted with `formatValue` and counts to each new\n * value, with `unit` under it; a string is shown as written (`\"2.4%\"`).\n */\n value: number | string;\n}\n\nexport interface DonutChartProps {\n data: DonutChartDatum[];\n /** What the whole is, such as \"Visits by source\". Names the chart for assistive technology. */\n label: string;\n /** Unit after values, such as \"visits\". */\n unit?: string;\n formatValue?: (value: number) => string;\n /** Centre label at rest, above the total. @default \"Total\" */\n totalLabel?: string;\n /**\n * What the centre shows at rest. `\"total\"` sums the visible segments under\n * `totalLabel`. `\"none\"` leaves it empty, for parts whose sum means nothing\n * (CTR, CPC or ROAS by source). An object puts a figure of your own there\n * instead, such as the blended rate. Hovering or focusing a segment still\n * shows that segment's readout either way.\n * @default \"total\"\n */\n center?: \"total\" | \"none\" | DonutChartCenter;\n /** Diameter in pixels. The chart scales down to fit narrower containers. @default 208 */\n size?: number;\n /** Ring thickness in pixels. @default 24 */\n thickness?: number;\n /** Segments below this share of the total join \"Other\". @default 0.04 */\n groupBelow?: number;\n /** The most segments drawn, counting \"Other\". @default 6 */\n maxSegments?: number;\n /** @default \"Other\" */\n otherLabel?: string;\n /** Controlled selected segment key. Hover and focus preview other segments without changing it. */\n activeKey?: string | null;\n defaultActiveKey?: string | null;\n onActiveChange?: (key: string | null) => void;\n /** Controlled hidden segment keys. A hidden segment closes and the rest of the ring redistributes. */\n hiddenKeys?: string[];\n defaultHiddenKeys?: string[];\n onHiddenKeysChange?: (keys: string[]) => void;\n /** What clicking a legend row does: show or hide its segment, or pin it as selected. @default \"toggle\" */\n legendAction?: \"toggle\" | \"select\";\n /** The synced legend beside or below the ring. @default true */\n legend?: boolean;\n /** @default \"No data yet\" */\n emptyLabel?: string;\n /** Extra classes for the root. Merged last. */\n className?: string;\n}\n\ntype Slice = { key: string; label: string; value: number; color?: string; members?: DonutChartDatum[] };\ntype Arc = { start: number; end: number };\ntype Segment = { start: Live; end: Live; lift: Live };\n\nconst OTHER = \"__other\";\nconst TAU = Math.PI * 2;\n/** Neutral steps after the four series colours, and the quietest step for Other. */\nconst NEUTRAL = [56, 42, 32];\nconst seriesColor = (index: number) =>\n index < 4\n ? `var(--color-chart-${index + 1})`\n : `color-mix(in oklab, var(--color-foreground) ${NEUTRAL[(index - 4) % NEUTRAL.length]}%, var(--color-surface))`;\nconst OTHER_COLOR = \"color-mix(in oklab, var(--color-foreground) 26%, var(--color-surface))\";\n/** Room outside the ring for the lift; growth, margin, the gap between segments and their corner radius, in pixels. */\nconst LIFT = 4;\nconst GROW = 2;\nconst MARGIN = 7;\nconst GAP = 3;\nconst CORNER = 4;\n/** Stagger of the trailing edges on the first sweep. */\nconst STAGGER_MS = 35;\nconst GROUPED = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 1 });\nconst PERCENT = new Intl.NumberFormat(\"en-US\", { style: \"percent\", maximumFractionDigits: 0 });\nconst shareText = (share: number) => (share > 0 && share < 0.01 ? \"<1%\" : PERCENT.format(share));\nconst n2 = (value: number) => (Math.round(value * 100) / 100).toString();\n\n/** Small parts join one \"Other\" segment, but only when at least two would; grouping a single part hides it for nothing. */\nfunction sliceData(data: DonutChartDatum[], groupBelow: number, maxSegments: number, otherLabel: string): Slice[] {\n const parts = data.filter((item) => item.value > 0);\n const total = parts.reduce((sum, item) => sum + item.value, 0);\n if (!total) return [];\n const ranked = [...parts].sort((a, b) => b.value - a.value);\n const keep = new Set(\n ranked.filter((item, rank) => item.value / total >= groupBelow && rank < maxSegments - 1).map((item) => item.key),\n );\n const rest = parts.filter((item) => !keep.has(item.key));\n if (rest.length < 2) return parts.map((item) => ({ ...item }));\n return [\n ...parts.filter((item) => keep.has(item.key)).map((item) => ({ ...item })),\n { key: OTHER, label: otherLabel, value: rest.reduce((sum, item) => sum + item.value, 0), members: rest },\n ];\n}\n\n/** Turns of the ring for each slice in data order; a hidden slice closes to a point where it sits. */\nfunction layout(slices: Slice[], hidden: Set<string>) {\n const total = slices.reduce((sum, item) => sum + (hidden.has(item.key) ? 0 : item.value), 0) || 1;\n let at = 0;\n return new Map(\n slices.map((item) => {\n const arc = { start: at, end: at + (hidden.has(item.key) ? 0 : item.value / total) };\n at = arc.end;\n return [item.key, arc] as const;\n }),\n );\n}\n\n/** Half the width, in pixels, of a sector's outer edge once the gap is cut; below a couple of pixels a segment fades rather than drawing a hairline. */\nfunction breadth(R: number, t0: number, t1: number, gap: number) {\n const turns = t1 - t0;\n if (turns >= 1 - 1e-6) return Infinity;\n return Math.sin(Math.min(turns * Math.PI, Math.PI / 2)) * R - Math.min(gap / 2, ((1 - turns) * TAU * R) / 2);\n}\n\n/**\n * An annular sector between two turns of the ring, clockwise from the top, with\n * rounded corners. Its sides run parallel to the radius at a fixed distance, so\n * the gap between neighbours is the same width from the inner edge to the outer.\n * A thin sector narrows to a wedge and then to nothing, so a closing one never pops.\n */\nfunction sector(c: number, R: number, r: number, t0: number, t1: number, gap: number, corner: number) {\n const turns = t1 - t0;\n if (turns <= 1e-6) return \"\";\n const at = (radius: number, angle: number) => `${n2(c + radius * Math.cos(angle))} ${n2(c + radius * Math.sin(angle))}`;\n if (turns >= 1 - 1e-6) {\n return `M${at(R, -Math.PI / 2)}A${n2(R)} ${n2(R)} 0 1 1 ${at(R, Math.PI / 2)}A${n2(R)} ${n2(R)} 0 1 1 ${at(R, -Math.PI / 2)}ZM${at(r, -Math.PI / 2)}A${n2(r)} ${n2(r)} 0 1 0 ${at(r, Math.PI / 2)}A${n2(r)} ${n2(r)} 0 1 0 ${at(r, -Math.PI / 2)}Z`;\n }\n // Half the gap on each side; a lone segment closes its gap as the rest of the ring empties, so it becomes a whole ring without a jump.\n const p = Math.min(gap / 2, ((1 - turns) * TAU * R) / 2);\n const a0 = t0 * TAU - Math.PI / 2;\n const a1 = t1 * TAU - Math.PI / 2;\n const half = (a1 - a0) / 2;\n const s = Math.sin(Math.min(half, Math.PI / 2));\n if (s * R <= p + 1e-6) return \"\";\n const side = (t: number, angle: number, sign: number) =>\n `${n2(c + t * Math.cos(angle) - sign * p * Math.sin(angle))} ${n2(c + t * Math.sin(angle) + sign * p * Math.cos(angle))}`;\n const band = (R - r) / 2;\n const ro = Math.max(0, Math.min(corner, band, s >= 0.9999 ? Infinity : (s * R - p) / (1 + s)));\n const dO = Math.asin(Math.min(1, (p + ro) / (R - ro)));\n const tO = Math.sqrt(Math.max(0, (R - ro) ** 2 - (p + ro) ** 2));\n const bigO = a1 - a0 - 2 * dO > Math.PI ? 1 : 0;\n let d = `M${side(tO, a0, 1)}`;\n if (ro > 0.01) d += `A${n2(ro)} ${n2(ro)} 0 0 1 ${at(R, a0 + dO)}`;\n d += `A${n2(R)} ${n2(R)} 0 ${bigO} 1 ${at(R, a1 - dO)}`;\n if (ro > 0.01) d += `A${n2(ro)} ${n2(ro)} 0 0 1 ${side(tO, a1, -1)}`;\n if (s * r > p + 1e-6 || s >= 0.9999) {\n const ri = Math.max(0, Math.min(corner, band, s >= 0.9999 ? Infinity : (s * r - p) / (1 - s)));\n const dI = Math.asin(Math.min(1, (p + ri) / (r + ri)));\n const tI = Math.sqrt(Math.max(0, (r + ri) ** 2 - (p + ri) ** 2));\n const bigI = a1 - a0 - 2 * dI > Math.PI ? 1 : 0;\n d += `L${side(tI, a1, -1)}`;\n if (ri > 0.01) d += `A${n2(ri)} ${n2(ri)} 0 0 1 ${at(r, a1 - dI)}`;\n d += `A${n2(r)} ${n2(r)} 0 ${bigI} 0 ${at(r, a0 + dI)}`;\n if (ri > 0.01) d += `A${n2(ri)} ${n2(ri)} 0 0 1 ${side(tI, a0, 1)}`;\n } else d += `L${at(p / s, (a0 + a1) / 2)}`;\n return `${d}Z`;\n}\n\n/**\n * A number that counts to each new value on the smooth spring. useCountTo counts\n * in whole steps, so a fraction (a share) is counted in ten-thousandths.\n */\nfunction Count({ value, format }: { value: number; format: (value: number) => string }) {\n const fine = !Number.isInteger(value);\n const shown = useCountTo(fine ? value * 10000 : value);\n return <>{format(fine ? shown / 10000 : shown)}</>;\n}\n\nexport const DonutChart = forwardRef<HTMLElement, DonutChartProps>(function DonutChart(\n {\n data,\n label,\n unit = \"\",\n formatValue = (value) => GROUPED.format(value),\n totalLabel = \"Total\",\n center = \"total\",\n size = 208,\n thickness = 24,\n groupBelow = 0.04,\n maxSegments = 6,\n otherLabel = \"Other\",\n activeKey,\n defaultActiveKey = null,\n onActiveChange,\n hiddenKeys,\n defaultHiddenKeys,\n onHiddenKeysChange,\n legendAction = \"toggle\",\n legend = true,\n emptyLabel = \"No data yet\",\n className,\n },\n ref,\n) {\n const figure = useRef<HTMLElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, figure), [ref]);\n const legendId = useId();\n const slices = sliceData(data, groupBelow, maxSegments, otherLabel);\n\n const [ownHidden, setOwnHidden] = useState<string[]>(defaultHiddenKeys ?? []);\n const hiddenList = hiddenKeys ?? ownHidden;\n const hidden = new Set(hiddenList.filter((key) => slices.some((item) => item.key === key)));\n const visible = slices.filter((item) => !hidden.has(item.key));\n const total = visible.reduce((sum, item) => sum + item.value, 0);\n const signature = slices.map((item) => `${item.key}:${item.value}${hidden.has(item.key) ? \"h\" : \"\"}`).join(\"|\");\n const suffix = unit ? ` ${unit}` : \"\";\n\n // Colours follow a key from the first time it is seen, so a segment keeps its colour through every dataset.\n const [palette, setPalette] = useState(() => new Map(data.map((item, index) => [item.key, index])));\n if (data.some((item) => !palette.has(item.key))) {\n const next = new Map(palette);\n for (const item of data) if (!next.has(item.key)) next.set(item.key, next.size);\n setPalette(next);\n }\n const colorFor = (item: Slice) =>\n item.color ?? (item.key === OTHER ? OTHER_COLOR : seriesColor(palette.get(item.key) ?? 0));\n\n // Leaving segments stay drawn in their old place while they close, then drop out.\n const [drawn, setDrawn] = useState<Slice[]>(slices);\n const [seen, setSeen] = useState(signature);\n if (seen !== signature) {\n setSeen(signature);\n const next = [...slices];\n drawn.forEach((item, index) => {\n if (next.some((slice) => slice.key === item.key)) return;\n const before = drawn\n .slice(0, index)\n .reverse()\n .find((prior) => next.some((slice) => slice.key === prior.key));\n next.splice(before ? next.findIndex((slice) => slice.key === before.key) + 1 : 0, 0, { ...item, value: 0 });\n });\n setDrawn(next);\n }\n\n // Selection is controlled or not; hover and focus preview on top of it.\n const [ownActive, setOwnActive] = useState<string | null>(defaultActiveKey);\n const selected = activeKey !== undefined ? activeKey : ownActive;\n const [preview, setPreview] = useState<string | null>(null);\n const select = (key: string | null) => {\n if (activeKey === undefined) setOwnActive(key);\n onActiveChange?.(key);\n };\n const current = [preview, selected].find((key) => key && visible.some((item) => item.key === key)) ?? null;\n const activeSlice = visible.find((item) => item.key === current) ?? null;\n\n const [announcement, setAnnouncement] = useState(\"\");\n const setHidden = (key: string) => {\n const isHidden = hidden.has(key);\n if (!isHidden && visible.length <= 1) return;\n const next = isHidden ? hiddenList.filter((item) => item !== key) : [...hiddenList, key];\n if (hiddenKeys === undefined) setOwnHidden(next);\n onHiddenKeysChange?.(next);\n const item = slices.find((slice) => slice.key === key);\n const nextTotal = slices.reduce((sum, slice) => sum + (next.includes(slice.key) ? 0 : slice.value), 0);\n const totalText =\n center === \"total\" ? ` ${totalLabel} ${formatValue(nextTotal)}${suffix}.` : \"\";\n if (item) setAnnouncement(`${item.label} ${isHidden ? \"shown\" : \"hidden\"}.${totalText}`);\n };\n\n // Geometry in the chart's own units; the SVG scales with its container.\n const c = size / 2;\n const outer = c - MARGIN;\n const inner = Math.max(8, outer - thickness);\n const corner = Math.min(CORNER, thickness / 4);\n const geometry = useRef({ c, outer, inner, corner });\n const segments = useRef(new Map<string, Segment>());\n const svg = useRef<SVGSVGElement>(null);\n\n /** One writer: every path's shape, opacity and lift, straight onto the elements. */\n const paint = useCallback(() => {\n const root = svg.current;\n if (!root) return;\n const { c, outer, inner, corner } = geometry.current;\n for (const node of root.querySelectorAll<SVGPathElement>(\"[data-key]\")) {\n const segment = segments.current.get(node.dataset.key!);\n if (!segment) {\n node.setAttribute(\"d\", \"\");\n continue;\n }\n const start = segment.start.value;\n const end = segment.end.value;\n const lift = Math.max(0, segment.lift.value);\n const R = outer + lift * GROW;\n node.setAttribute(\"d\", sector(c, R, inner - lift * GROW * 0.5, start, end, GAP, corner));\n node.setAttribute(\"fill-opacity\", n2(Math.min(1, Math.max(0, (breadth(R, start, end, GAP) - 1) / 3))));\n const middle = ((start + end) / 2) * TAU - Math.PI / 2;\n node.setAttribute(\"transform\", `translate(${n2(Math.cos(middle) * lift * LIFT)} ${n2(Math.sin(middle) * lift * LIFT)})`);\n }\n }, []);\n useIsomorphicLayoutEffect(() => {\n geometry.current = { c, outer, inner, corner };\n paint();\n });\n useEffect(\n () => () => {\n for (const segment of segments.current.values()) {\n segment.start.stop();\n segment.end.stop();\n segment.lift.stop();\n }\n },\n [],\n );\n\n // The ring sweeps once a third of it is in view.\n const [shown, setShown] = useState(false);\n useEffect(() => {\n const node = figure.current;\n if (!node || prefersReducedMotion() || typeof IntersectionObserver === \"undefined\") {\n setShown(true);\n return;\n }\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting) return;\n observer.disconnect();\n setShown(true);\n },\n { threshold: 0.35 },\n );\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const targets = useRef(new Map<string, Arc>());\n const latest = useRef({ drawn, slices });\n useIsomorphicLayoutEffect(() => {\n latest.current = { drawn, slices };\n });\n\n // New data, or a segment shown or hidden, moves every arc from wherever it is on screen.\n useEffect(() => {\n if (!shown) return;\n const { drawn: order, slices: now } = latest.current;\n const goal = layout(now, hidden);\n targets.current = goal;\n const first = segments.current.size === 0;\n const place = (index: number) => {\n const before = order\n .slice(0, index)\n .reverse()\n .find((item) => goal.has(item.key));\n return before ? goal.get(before.key)!.end : 0;\n };\n const drop = (key: string) => {\n if (latest.current.slices.some((item) => item.key === key)) return;\n segments.current.delete(key);\n setDrawn((list) => list.filter((item) => item.key !== key));\n };\n order.forEach((item, index) => {\n const leaving = !goal.has(item.key);\n const to = goal.get(item.key) ?? { start: place(index), end: place(index) };\n let segment = segments.current.get(item.key);\n if (!segment) {\n // A new segment opens from the edge of the one before it, as it is right now.\n const prior = order\n .slice(0, index)\n .reverse()\n .map((entry) => segments.current.get(entry.key))\n .find(Boolean);\n const from = first ? 0 : prior ? prior.end.value : 0;\n segment = { start: live(from), end: live(from), lift: live(0) };\n segments.current.set(item.key, segment);\n }\n if (first) {\n // One sweep: every segment leaves the top together, and each trailing edge follows a beat behind the one before.\n retarget(segment.start, to.start, REVEAL, paint);\n retarget(segment.end, to.end, REVEAL, paint, 40 + index * STAGGER_MS);\n return;\n }\n retarget(segment.start, to.start, SETTLE, paint);\n retarget(segment.end, to.end, SETTLE, paint, 0, leaving ? () => drop(item.key) : undefined);\n });\n paint();\n // The signature stands for the slices and the hidden set.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [shown, signature, paint]);\n\n // The active segment slides out along its middle and thickens; the one it replaces settles back.\n useEffect(() => {\n for (const [key, segment] of segments.current) retarget(segment.lift, key === current ? 1 : 0, SNAPPY, paint);\n }, [current, drawn, shown, paint]);\n\n // One pointer handler for the whole ring: the angle picks the segment, so crossing a gap never drops the hover.\n const hit = (event: { clientX: number; clientY: number }) => {\n const box = svg.current?.getBoundingClientRect();\n if (!box?.width) return undefined;\n const scale = size / box.width;\n const x = (event.clientX - box.left) * scale - c;\n const y = (event.clientY - box.top) * scale - c;\n const radius = Math.hypot(x, y);\n if (radius < inner - 6 || radius > c) return null;\n let turn = (Math.atan2(y, x) + Math.PI / 2) / TAU;\n if (turn < 0) turn += 1;\n for (const [key, arc] of targets.current) {\n if (arc.end > arc.start && turn >= arc.start && turn < arc.end && visible.some((item) => item.key === key)) return key;\n }\n return undefined;\n };\n const onRingMove = (event: PointerEvent<SVGSVGElement>) => {\n if (event.pointerType !== \"mouse\") return;\n const key = hit(event);\n if (key !== undefined && key !== preview) setPreview(key);\n };\n const onRingClick = (event: MouseEvent<SVGSVGElement>) => {\n const key = hit(event);\n if (key) select(selected === key ? null : key);\n };\n\n const format = (value: number) => `${formatValue(value)}${suffix}`;\n // A share of the total only means something when the centre IS the total: with\n // `center=\"none\"` or a figure of the caller's own, the parts do not add up.\n const showShare = center === \"total\";\n const shareOf = (value: number) => (showShare ? `, ${shareText(value / (total || 1))}` : \"\");\n const describe = (item: Slice) =>\n `${item.label}, ${format(item.value)}${shareOf(item.value)}${\n item.members ? `. Includes ${item.members.map((member) => member.label).join(\", \")}` : \"\"\n }`;\n\n // Arrow keys walk the segments, in the legend or on the ring itself when there is no legend.\n const rows = useRef<(HTMLButtonElement | null)[]>([]);\n const step = (key: string, index: number, count: number) => {\n const moves: Record<string, number> = {\n ArrowDown: index + 1,\n ArrowRight: index + 1,\n ArrowUp: index - 1,\n ArrowLeft: index - 1,\n Home: 0,\n End: count - 1,\n };\n return moves[key];\n };\n const onLegendKey = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n if (event.key === \"Escape\" && selected) {\n event.preventDefault();\n select(null);\n return;\n }\n const next = step(event.key, index, slices.length);\n if (next === undefined) return;\n event.preventDefault();\n rows.current[(next + slices.length) % slices.length]?.focus();\n };\n const onRingKey = (event: KeyboardEvent<HTMLDivElement>) => {\n if (!visible.length) return;\n if (event.key === \"Escape\") {\n event.preventDefault();\n setPreview(null);\n if (selected) select(null);\n return;\n }\n if ((event.key === \"Enter\" || event.key === \" \") && current) {\n event.preventDefault();\n select(selected === current ? null : current);\n return;\n }\n const index = visible.findIndex((item) => item.key === current);\n const next = step(\n event.key,\n index < 0 ? (event.key.endsWith(\"Up\") || event.key.endsWith(\"Left\") ? 0 : -1) : index,\n visible.length,\n );\n if (next === undefined) return;\n event.preventDefault();\n const item = visible[(next + visible.length) % visible.length]!;\n setPreview(item.key);\n setAnnouncement(describe(item));\n };\n const onLegendBlur = (event: FocusEvent<HTMLButtonElement>) => {\n if (!(event.relatedTarget instanceof Node && event.currentTarget.closest(\"ul\")?.contains(event.relatedTarget))) {\n setPreview(null);\n }\n };\n\n // One readout per segment plus the one at rest; the active one rolls in from the side it sits on.\n // At rest: the total, nothing, or the caller's own figure (`center`); with no data, the\n // empty label.\n const rest =\n center === \"total\"\n ? { label: totalLabel, value: total as number | string }\n : center === \"none\"\n ? { label: \" \", value: \"\" }\n : center;\n const identity = activeSlice ? activeSlice.key : \"__total\";\n // A figure of the caller's own always shows, 0 included; the total shows only when there is one.\n const custom = typeof center === \"object\" && total > 0;\n const readouts = [\n {\n key: \"__total\",\n label: total ? rest.label : emptyLabel,\n value: total ? rest.value : 0,\n meta: null as Slice | null,\n custom,\n },\n ...slices.map((item) => ({\n key: item.key,\n label: item.label,\n value: item.value,\n meta: item,\n custom: false,\n })),\n ];\n const activeIndex = Math.max(0, readouts.findIndex((item) => item.key === identity));\n const restValue = (value: number | string) => (typeof value === \"number\" ? format(value) : value);\n // An empty custom figure leaves just its label, not \"Blended CTR . \".\n const restText =\n center === \"total\"\n ? `${totalLabel} ${format(total)}. `\n : center === \"none\"\n ? \"\"\n : `${[center.label, restValue(center.value)].filter(Boolean).join(\" \")}. `;\n const summary = total\n ? `${label}. ${restText}${visible.map(describe).join(\". \")}.`\n : `${label}. ${emptyLabel}.`;\n const members = (item: Slice) =>\n item.members\n ? item.members.length > 2\n ? `${item.members\n .slice(0, 2)\n .map((member) => member.label)\n .join(\", \")} and ${item.members.length - 2} more`\n : item.members.map((member) => member.label).join(\" and \")\n : null;\n\n return (\n <figure\n ref={refs}\n data-slot=\"donut-chart\"\n aria-label={label}\n data-legend={legend || undefined}\n className={cn(\"donut-chart box-border font-sans text-foreground\", className)}\n >\n <div\n data-slot=\"donut-chart-ring\"\n className=\"donut-chart-ring\"\n style={{ width: size, \"--inner\": `${((inner * 2) / size) * 100}%` } as CSSProperties}\n tabIndex={legend ? undefined : 0}\n role={legend ? undefined : \"application\"}\n aria-label={legend ? undefined : `${label}. Use the arrow keys to read each segment.`}\n onKeyDown={legend ? undefined : onRingKey}\n onBlur={legend ? undefined : () => setPreview(null)}\n >\n <svg\n ref={svg}\n viewBox={`0 0 ${size} ${size}`}\n aria-hidden=\"true\"\n focusable=\"false\"\n onPointerMove={onRingMove}\n onPointerLeave={() => setPreview(null)}\n onClick={onRingClick}\n data-hovering={current !== null || undefined}\n className=\"block size-full overflow-visible data-[hovering]:cursor-pointer\"\n >\n <circle\n className=\"donut-chart-track\"\n cx={c}\n cy={c}\n r={(outer + inner) / 2}\n strokeWidth={outer - inner}\n data-idle={!shown || !total || undefined}\n />\n {drawn.map((item) => (\n <path\n key={item.key}\n data-key={item.key}\n data-active={item.key === current || undefined}\n data-dim={(current !== null && item.key !== current) || undefined}\n className=\"donut-chart-segment\"\n style={{ \"--slice\": colorFor(item) } as CSSProperties}\n />\n ))}\n </svg>\n <div\n aria-hidden=\"true\"\n data-center={center === \"total\" || center === \"none\" ? center : \"custom\"}\n className=\"donut-chart-center\"\n >\n {readouts.map((item, index) => (\n <span\n key={item.key}\n data-place={Math.sign(index - activeIndex)}\n className=\"donut-chart-readout\"\n >\n <span className=\"block w-full min-h-5 truncate text-xs text-text-secondary\">{item.label}</span>\n <span className=\"block max-w-full min-h-8 truncate text-h3 font-medium tabular-nums\">\n {typeof item.value === \"string\" ? (\n item.value || \" \"\n ) : item.value || item.meta || item.custom ? (\n <Count value={item.value} format={formatValue} />\n ) : (\n \" \"\n )}\n </span>\n <span className=\"block w-full min-h-5 truncate text-xs tabular-nums text-muted-foreground\">\n {item.meta && showShare ? (\n <>\n <Count\n value={total && !hidden.has(item.key) ? item.meta.value / total : 0}\n format={shareText}\n />{\" \"}\n of {totalLabel.toLowerCase()}\n </>\n ) : typeof item.value === \"number\" ? (\n unit || \" \"\n ) : (\n \" \"\n )}\n </span>\n </span>\n ))}\n </div>\n </div>\n {legend && slices.length > 0 ? (\n <ul\n id={legendId}\n aria-label={`${label}, segments${legendAction === \"toggle\" ? \". Press a segment to show or hide it\" : \"\"}`}\n onPointerLeave={() => setPreview(null)}\n className=\"m-0 -mb-0.5 grid list-none p-0\"\n >\n {slices.map((item, index) => {\n const off = hidden.has(item.key);\n return (\n <li key={item.key} className=\"overflow-hidden motion-safe:animate-fade-in\">\n <button\n ref={(node) => {\n rows.current[index] = node;\n }}\n type=\"button\"\n data-slot=\"donut-chart-row\"\n aria-pressed={legendAction === \"toggle\" ? !off : selected === item.key}\n aria-label={off ? `${item.label}, hidden` : describe(item)}\n data-active={item.key === current || undefined}\n data-pinned={selected === item.key || undefined}\n data-hidden={off || undefined}\n data-share={showShare ? undefined : \"none\"}\n style={{ \"--slice\": colorFor(item) } as CSSProperties}\n onClick={() => (legendAction === \"toggle\" ? setHidden(item.key) : select(selected === item.key ? null : item.key))}\n onPointerEnter={(event) => {\n if (event.pointerType === \"mouse\") setPreview(off ? null : item.key);\n }}\n onFocus={(event) => {\n if (event.currentTarget.matches(\":focus-visible\")) setPreview(off ? null : item.key);\n }}\n onBlur={onLegendBlur}\n onKeyDown={(event) => onLegendKey(event, index)}\n className=\"donut-chart-row\"\n >\n <span aria-hidden=\"true\" className=\"donut-chart-dot\" />\n <span className=\"grid min-w-0\">\n <span className=\"donut-chart-row-label truncate\">{item.label}</span>\n {item.members ? <span className=\"truncate text-xs text-muted-foreground\">{members(item)}</span> : null}\n </span>\n <span aria-hidden=\"true\" className=\"donut-chart-row-value justify-self-end tabular-nums text-text-secondary\">\n <Count value={item.value} format={formatValue} />\n </span>\n {showShare ? (\n <span\n aria-hidden=\"true\"\n className=\"donut-chart-row-value justify-self-end font-medium tabular-nums\"\n >\n <Count value={off || !total ? 0 : item.value / total} format={shareText} />\n </span>\n ) : null}\n </button>\n </li>\n );\n })}\n </ul>\n ) : null}\n <p className=\"sr-only\">{summary}</p>\n <p className=\"sr-only\" aria-live=\"polite\">\n {announcement}\n </p>\n {total > 0 ? (\n <div className=\"sr-only\">\n <table>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Segment</th>\n <th scope=\"col\">Value</th>\n {showShare ? <th scope=\"col\">Share</th> : null}\n </tr>\n </thead>\n <tbody>\n {visible\n .flatMap((item) =>\n item.members\n ? item.members.map((member) => ({ key: member.key, label: `${member.label} (${otherLabel})`, value: member.value }))\n : [item],\n )\n .map((item) => (\n <tr key={item.key}>\n <th scope=\"row\">{item.label}</th>\n <td>{format(item.value)}</td>\n {showShare ? <td>{shareText(item.value / total)}</td> : null}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : null}\n </figure>\n );\n});\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { DonutChart, DonutChartDatum, DonutChartProps } from "./donut-chart.js";
2
- export { DonutChart, type DonutChartDatum, type DonutChartProps };
1
+ import { DonutChart, DonutChartCenter, DonutChartDatum, DonutChartProps } from "./donut-chart.js";
2
+ export { DonutChart, type DonutChartCenter, type DonutChartDatum, type DonutChartProps };
@@ -18,6 +18,12 @@ export declare const emptyStateVariants: (props?: import("class-variance-authori
18
18
  export declare const emptyStateTitleVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
19
19
  export declare const emptyStateTextVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
20
20
  export declare const emptyStateActionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
21
+ /**
22
+ * The illustration's frame. It centres what it is given and keeps an image or
23
+ * SVG inside the box at its own aspect ratio; the illustration's SIZE is the
24
+ * caller's, because the artwork sets it, not the empty state.
25
+ */
26
+ export declare const emptyStateImageVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
21
27
  /**
22
28
  * An empty list's message.
23
29
  *
@@ -45,6 +51,21 @@ export interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<"div">, "
45
51
  * swaps in place, and the same one re-rendering stays still. Mark it `aria-hidden`.
46
52
  */
47
53
  icon?: ReactNode;
54
+ /**
55
+ * An illustration above the icon and the title: an `<img>`, an inline SVG or any
56
+ * element. It is the caller's artwork, so the empty state does not hide it from
57
+ * assistive technology, and the caller names it:
58
+ * - an `<img>` takes an `alt`, or `alt=""` when it only decorates what the title
59
+ * already says;
60
+ * - an inline `<svg>` has no name of its own, so give it `role="img"` and an
61
+ * `aria-label`, or `aria-hidden="true"` when it is decorative.
62
+ *
63
+ * The slot reserves no space, so give the artwork its intrinsic size (`width` and
64
+ * `height` on an `<img>` or `<svg>`): without it the layout jumps when an image
65
+ * loads. Omitted from the DOM when absent; `false` (as in `cond && <img />`)
66
+ * counts as absent.
67
+ */
68
+ image?: ReactNode;
48
69
  /** A next step under the text: a button or a link. Omitted from the DOM when absent. */
49
70
  action?: ReactNode;
50
71
  /** Extra classes for the box. Merged last, so they win conflicts with ours. */
@@ -24,16 +24,23 @@ const emptyStateVariants = cva(["flex flex-col items-center px-5 py-12 text-cent
24
24
  const emptyStateTitleVariants = cva("m-0 mt-5 text-base leading-(--leading-normal) font-medium text-foreground");
25
25
  const emptyStateTextVariants = cva("mx-auto mt-0 mb-0 max-w-72 text-sm leading-(--leading-normal) text-pretty text-text-secondary");
26
26
  const emptyStateActionVariants = cva("mt-5 flex flex-wrap justify-center gap-3");
27
+ /**
28
+ * The illustration's frame. It centres what it is given and keeps an image or
29
+ * SVG inside the box at its own aspect ratio; the illustration's SIZE is the
30
+ * caller's, because the artwork sets it, not the empty state.
31
+ */
32
+ const emptyStateImageVariants = cva("flex max-w-full justify-center [&>img]:h-auto [&>img]:max-w-full [&>svg]:h-auto [&>svg]:max-w-full");
27
33
  /** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */
28
34
  function iconKey(icon) {
29
35
  if (!isValidElement(icon)) return "icon";
30
36
  const type = icon.type;
31
37
  return typeof type === "string" ? type : type.displayName ?? type.name ?? "icon";
32
38
  }
33
- const EmptyState = forwardRef(function EmptyState({ title, description, children, icon, action, className, ...props }, ref) {
39
+ const EmptyState = forwardRef(function EmptyState({ title, description, children, icon, image, action, className, ...props }, ref) {
34
40
  const frame = useRef(null);
35
41
  const copy = useRef(null);
36
42
  const message = description ?? children;
43
+ const hasImage = image != null && typeof image !== "boolean";
37
44
  const text = (value) => typeof value === "string" ? /* @__PURE__ */ jsx(SwapText, {
38
45
  swapKey: value,
39
46
  className: "block",
@@ -46,10 +53,15 @@ const EmptyState = forwardRef(function EmptyState({ title, description, children
46
53
  "data-slot": "empty-state",
47
54
  className: cn(emptyStateVariants(), className),
48
55
  children: [
56
+ !hasImage ? null : /* @__PURE__ */ jsx("div", {
57
+ "data-slot": "empty-state-image",
58
+ className: emptyStateImageVariants(),
59
+ children: image
60
+ }),
49
61
  icon === void 0 || icon === null ? null : /* @__PURE__ */ jsx("div", {
50
62
  "data-slot": "empty-state-icon",
51
63
  "aria-hidden": "true",
52
- className: "grid size-12 place-items-center rounded-circle border-(length:--border-width) border-border bg-muted text-text-secondary motion-safe:animate-empty-state-icon [&_svg]:size-6",
64
+ className: cnAll("grid size-12 place-items-center rounded-circle border-(length:--border-width) border-border bg-muted text-text-secondary motion-safe:animate-empty-state-icon [&_svg]:size-6", hasImage && "mt-5"),
53
65
  children: /* @__PURE__ */ jsx(SwapText, {
54
66
  swapKey: iconKey(icon),
55
67
  motion: "glyph",
@@ -70,7 +82,7 @@ const EmptyState = forwardRef(function EmptyState({ title, description, children
70
82
  children: text(title)
71
83
  }), message == null ? null : /* @__PURE__ */ jsx("p", {
72
84
  "data-slot": "empty-state-text",
73
- className: cnAll(emptyStateTextVariants(), title != null ? "mt-2" : icon != null && "mt-5"),
85
+ className: cnAll(emptyStateTextVariants(), title != null ? "mt-2" : (icon != null || hasImage) && "mt-5"),
74
86
  children: text(message)
75
87
  })]
76
88
  })
@@ -84,6 +96,6 @@ const EmptyState = forwardRef(function EmptyState({ title, description, children
84
96
  });
85
97
  });
86
98
  //#endregion
87
- export { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
99
+ export { EmptyState, emptyStateActionVariants, emptyStateImageVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
88
100
 
89
101
  //# sourceMappingURL=empty-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"empty-state.js","names":[],"sources":["../../src/empty-state/empty-state.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * EmptyState: what a list shows when it has nothing to show. An icon in a\n * circle, a short title, a one- or two-line description and an optional next\n * step, centred on the page surface with room to breathe.\n *\n * Presentational, so there is no React Aria base. It exists because every\n * list has an empty case, and without a shared component each screen picks its\n * own padding, border and alignment, so \"nothing here\" looks different on\n * every page.\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 emptyStateVariants = cva([\n \"flex flex-col items-center px-5 py-12 text-center\",\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n // Its own text colour too, so it reads the same on any page background.\n \"box-border font-sans text-foreground\",\n]);\n\nexport const emptyStateTitleVariants = cva(\n \"m-0 mt-5 text-base leading-(--leading-normal) font-medium text-foreground\",\n);\n\nexport const emptyStateTextVariants = cva(\n \"mx-auto mt-0 mb-0 max-w-72 text-sm leading-(--leading-normal) text-pretty text-text-secondary\",\n);\n\nexport const emptyStateActionVariants = cva(\"mt-5 flex flex-wrap justify-center gap-3\");\n\n/**\n * An empty list's message.\n *\n * @example\n * <EmptyState\n * icon={<Folder aria-hidden=\"true\" />}\n * title=\"No saved views\"\n * description=\"Save a set of filters as a view to come back to it later.\"\n * action={<Button>Create view</Button>}\n * />\n */\nexport interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\"> {\n /**\n * A short heading above the description. When it is set, `description` (or\n * `children`) is the supporting text under it.\n */\n title?: ReactNode;\n /** One or two lines of supporting text. `children` is the same slot and works the same way. */\n description?: ReactNode;\n /** Why the list is empty, in one short statement. The message when there is no `description`. */\n children?: ReactNode;\n /**\n * A glyph above the message, inside a circle. It settles in once when the state first appears\n * (small and transparent to rest over 480ms); a different icon component later\n * swaps in place, and the same one re-rendering stays still. Mark it `aria-hidden`.\n */\n icon?: ReactNode;\n /** A next step under the text: a button or a link. Omitted from the DOM when absent. */\n action?: ReactNode;\n /** Extra classes for the box. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nexport const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(function EmptyState(\n { title, description, children, icon, action, className, ...props },\n ref,\n) {\n const frame = useRef<HTMLDivElement>(null);\n const copy = useRef<HTMLDivElement>(null);\n const message = description ?? children;\n const text = (value: ReactNode) =>\n typeof value === \"string\" ? (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n ) : (\n value\n );\n // The copy column follows its height when the title or the message changes.\n useHeightFollow(\n frame,\n copy,\n `${typeof title === \"string\" ? title : \"\"}\\n${typeof message === \"string\" ? message : \"\"}`,\n );\n return (\n <div\n ref={ref}\n {...props}\n data-slot=\"empty-state\"\n className={cn(emptyStateVariants(), className)}\n >\n {icon === undefined || icon === null ? null : (\n <div\n data-slot=\"empty-state-icon\"\n aria-hidden=\"true\"\n className=\"grid size-12 place-items-center rounded-circle border-(length:--border-width) border-border bg-muted text-text-secondary motion-safe:animate-empty-state-icon [&_svg]:size-6\"\n >\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </div>\n )}\n <div ref={frame} data-slot=\"empty-state-copy\" className=\"height-follow w-full\">\n <div ref={copy} className=\"flow-root\">\n {title == null ? null : (\n <h3 data-slot=\"empty-state-title\" className={emptyStateTitleVariants()}>\n {text(title)}\n </h3>\n )}\n {message == null ? null : (\n <p\n data-slot=\"empty-state-text\"\n className={cnAll(\n emptyStateTextVariants(),\n // Under the title it sits close; straight under the icon it clears it.\n title != null ? \"mt-2\" : icon != null && \"mt-5\",\n )}\n >\n {text(message)}\n </p>\n )}\n </div>\n </div>\n {action == null ? null : (\n <div data-slot=\"empty-state-action\" className={emptyStateActionVariants()}>\n {action}\n </div>\n )}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,qBAAqB,IAAI,CACpC,qDAGA,sCACF,CAAC;AAED,MAAa,0BAA0B,IACrC,2EACF;AAEA,MAAa,yBAAyB,IACpC,+FACF;AAEA,MAAa,2BAA2B,IAAI,0CAA0C;;AAoCtF,SAAS,QAAQ,MAAiB;CAChC,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,KAAK,eAAe,KAAK,QAAQ;AAC7E;AAEA,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,OAAO,aAAa,UAAU,MAAM,QAAQ,WAAW,GAAG,SAC5D,KACA;CACA,MAAM,QAAQ,OAAuB,IAAI;CACzC,MAAM,OAAO,OAAuB,IAAI;CACxC,MAAM,UAAU,eAAe;CAC/B,MAAM,QAAQ,UACZ,OAAO,UAAU,WACf,oBAAC,UAAD;EAAU,SAAS;EAAO,WAAU;EACjC,UAAA;CACO,CAAA,IAEV;CAGJ,gBACE,OACA,MACA,GAAG,OAAO,UAAU,WAAW,QAAQ,GAAG,IAAI,OAAO,YAAY,WAAW,UAAU,IACxF;CACA,OACE,qBAAC,OAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAJ/C,UAAA;GAMG,SAAS,KAAA,KAAa,SAAS,OAAO,OACrC,oBAAC,OAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAU;IAEV,UAAA,oBAAC,UAAD;KAAU,SAAS,QAAQ,IAAI;KAAG,QAAO;KAAQ,WAAU;KACxD,UAAA;IACO,CAAA;GACP,CAAA;GAEP,oBAAC,OAAD;IAAK,KAAK;IAAO,aAAU;IAAmB,WAAU;IACtD,UAAA,qBAAC,OAAD;KAAK,KAAK;KAAM,WAAU;KAA1B,UAAA,CACG,SAAS,OAAO,OACf,oBAAC,MAAD;MAAI,aAAU;MAAoB,WAAW,wBAAwB;MAClE,UAAA,KAAK,KAAK;KACT,CAAA,GAEL,WAAW,OAAO,OACjB,oBAAC,KAAD;MACE,aAAU;MACV,WAAW,MACT,uBAAuB,GAEvB,SAAS,OAAO,SAAS,QAAQ,QAAQ,MAC3C;MAEC,UAAA,KAAK,OAAO;KACZ,CAAA,CAEF;;GACF,CAAA;GACJ,UAAU,OAAO,OAChB,oBAAC,OAAD;IAAK,aAAU;IAAqB,WAAW,yBAAyB;IACrE,UAAA;GACE,CAAA;EAEJ;;AAET,CAAC"}
1
+ {"version":3,"file":"empty-state.js","names":[],"sources":["../../src/empty-state/empty-state.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * EmptyState: what a list shows when it has nothing to show. An icon in a\n * circle, a short title, a one- or two-line description and an optional next\n * step, centred on the page surface with room to breathe.\n *\n * Presentational, so there is no React Aria base. It exists because every\n * list has an empty case, and without a shared component each screen picks its\n * own padding, border and alignment, so \"nothing here\" looks different on\n * every page.\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 emptyStateVariants = cva([\n \"flex flex-col items-center px-5 py-12 text-center\",\n // The reset in @seekalfred/styles is opt-in, so a consumer may load none.\n // Its own text colour too, so it reads the same on any page background.\n \"box-border font-sans text-foreground\",\n]);\n\nexport const emptyStateTitleVariants = cva(\n \"m-0 mt-5 text-base leading-(--leading-normal) font-medium text-foreground\",\n);\n\nexport const emptyStateTextVariants = cva(\n \"mx-auto mt-0 mb-0 max-w-72 text-sm leading-(--leading-normal) text-pretty text-text-secondary\",\n);\n\nexport const emptyStateActionVariants = cva(\"mt-5 flex flex-wrap justify-center gap-3\");\n\n/**\n * The illustration's frame. It centres what it is given and keeps an image or\n * SVG inside the box at its own aspect ratio; the illustration's SIZE is the\n * caller's, because the artwork sets it, not the empty state.\n */\nexport const emptyStateImageVariants = cva(\n \"flex max-w-full justify-center [&>img]:h-auto [&>img]:max-w-full [&>svg]:h-auto [&>svg]:max-w-full\",\n);\n\n/**\n * An empty list's message.\n *\n * @example\n * <EmptyState\n * icon={<Folder aria-hidden=\"true\" />}\n * title=\"No saved views\"\n * description=\"Save a set of filters as a view to come back to it later.\"\n * action={<Button>Create view</Button>}\n * />\n */\nexport interface EmptyStateProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\"> {\n /**\n * A short heading above the description. When it is set, `description` (or\n * `children`) is the supporting text under it.\n */\n title?: ReactNode;\n /** One or two lines of supporting text. `children` is the same slot and works the same way. */\n description?: ReactNode;\n /** Why the list is empty, in one short statement. The message when there is no `description`. */\n children?: ReactNode;\n /**\n * A glyph above the message, inside a circle. It settles in once when the state first appears\n * (small and transparent to rest over 480ms); a different icon component later\n * swaps in place, and the same one re-rendering stays still. Mark it `aria-hidden`.\n */\n icon?: ReactNode;\n /**\n * An illustration above the icon and the title: an `<img>`, an inline SVG or any\n * element. It is the caller's artwork, so the empty state does not hide it from\n * assistive technology, and the caller names it:\n * - an `<img>` takes an `alt`, or `alt=\"\"` when it only decorates what the title\n * already says;\n * - an inline `<svg>` has no name of its own, so give it `role=\"img\"` and an\n * `aria-label`, or `aria-hidden=\"true\"` when it is decorative.\n *\n * The slot reserves no space, so give the artwork its intrinsic size (`width` and\n * `height` on an `<img>` or `<svg>`): without it the layout jumps when an image\n * loads. Omitted from the DOM when absent; `false` (as in `cond && <img />`)\n * counts as absent.\n */\n image?: ReactNode;\n /** A next step under the text: a button or a link. Omitted from the DOM when absent. */\n action?: ReactNode;\n /** Extra classes for the box. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nexport const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(function EmptyState(\n { title, description, children, icon, image, action, className, ...props },\n ref,\n) {\n const frame = useRef<HTMLDivElement>(null);\n const copy = useRef<HTMLDivElement>(null);\n const message = description ?? children;\n // `cond && <img />` hands over `false` when the condition fails: absent, not\n // an empty slot with its spacing.\n const hasImage = image != null && typeof image !== \"boolean\";\n const text = (value: ReactNode) =>\n typeof value === \"string\" ? (\n <SwapText swapKey={value} className=\"block\">\n {value}\n </SwapText>\n ) : (\n value\n );\n // The copy column follows its height when the title or the message changes.\n useHeightFollow(\n frame,\n copy,\n `${typeof title === \"string\" ? title : \"\"}\\n${typeof message === \"string\" ? message : \"\"}`,\n );\n return (\n <div\n ref={ref}\n {...props}\n data-slot=\"empty-state\"\n className={cn(emptyStateVariants(), className)}\n >\n {!hasImage ? null : (\n <div data-slot=\"empty-state-image\" className={emptyStateImageVariants()}>\n {image}\n </div>\n )}\n {icon === undefined || icon === null ? null : (\n <div\n data-slot=\"empty-state-icon\"\n aria-hidden=\"true\"\n className={cnAll(\n \"grid size-12 place-items-center rounded-circle border-(length:--border-width) border-border bg-muted text-text-secondary motion-safe:animate-empty-state-icon [&_svg]:size-6\",\n // Under an illustration it clears it by the same step as the title.\n hasImage && \"mt-5\",\n )}\n >\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </div>\n )}\n <div ref={frame} data-slot=\"empty-state-copy\" className=\"height-follow w-full\">\n <div ref={copy} className=\"flow-root\">\n {title == null ? null : (\n <h3 data-slot=\"empty-state-title\" className={emptyStateTitleVariants()}>\n {text(title)}\n </h3>\n )}\n {message == null ? null : (\n <p\n data-slot=\"empty-state-text\"\n className={cnAll(\n emptyStateTextVariants(),\n // Under the title it sits close; straight under the icon or the\n // illustration it clears it.\n title != null ? \"mt-2\" : (icon != null || hasImage) && \"mt-5\",\n )}\n >\n {text(message)}\n </p>\n )}\n </div>\n </div>\n {action == null ? null : (\n <div data-slot=\"empty-state-action\" className={emptyStateActionVariants()}>\n {action}\n </div>\n )}\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,qBAAqB,IAAI,CACpC,qDAGA,sCACF,CAAC;AAED,MAAa,0BAA0B,IACrC,2EACF;AAEA,MAAa,yBAAyB,IACpC,+FACF;AAEA,MAAa,2BAA2B,IAAI,0CAA0C;;;;;;AAOtF,MAAa,0BAA0B,IACrC,oGACF;;AAmDA,SAAS,QAAQ,MAAiB;CAChC,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,KAAK,eAAe,KAAK,QAAQ;AAC7E;AAEA,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,OAAO,aAAa,UAAU,MAAM,OAAO,QAAQ,WAAW,GAAG,SACnE,KACA;CACA,MAAM,QAAQ,OAAuB,IAAI;CACzC,MAAM,OAAO,OAAuB,IAAI;CACxC,MAAM,UAAU,eAAe;CAG/B,MAAM,WAAW,SAAS,QAAQ,OAAO,UAAU;CACnD,MAAM,QAAQ,UACZ,OAAO,UAAU,WACf,oBAAC,UAAD;EAAU,SAAS;EAAO,WAAU;EACjC,UAAA;CACO,CAAA,IAEV;CAGJ,gBACE,OACA,MACA,GAAG,OAAO,UAAU,WAAW,QAAQ,GAAG,IAAI,OAAO,YAAY,WAAW,UAAU,IACxF;CACA,OACE,qBAAC,OAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAJ/C,UAAA;GAMG,CAAC,WAAW,OACX,oBAAC,OAAD;IAAK,aAAU;IAAoB,WAAW,wBAAwB;IACnE,UAAA;GACE,CAAA;GAEN,SAAS,KAAA,KAAa,SAAS,OAAO,OACrC,oBAAC,OAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAW,MACT,gLAEA,YAAY,MACd;IAEA,UAAA,oBAAC,UAAD;KAAU,SAAS,QAAQ,IAAI;KAAG,QAAO;KAAQ,WAAU;KACxD,UAAA;IACO,CAAA;GACP,CAAA;GAEP,oBAAC,OAAD;IAAK,KAAK;IAAO,aAAU;IAAmB,WAAU;IACtD,UAAA,qBAAC,OAAD;KAAK,KAAK;KAAM,WAAU;KAA1B,UAAA,CACG,SAAS,OAAO,OACf,oBAAC,MAAD;MAAI,aAAU;MAAoB,WAAW,wBAAwB;MAClE,UAAA,KAAK,KAAK;KACT,CAAA,GAEL,WAAW,OAAO,OACjB,oBAAC,KAAD;MACE,aAAU;MACV,WAAW,MACT,uBAAuB,GAGvB,SAAS,OAAO,UAAU,QAAQ,QAAQ,aAAa,MACzD;MAEC,UAAA,KAAK,OAAO;KACZ,CAAA,CAEF;;GACF,CAAA;GACJ,UAAU,OAAO,OAChB,oBAAC,OAAD;IAAK,aAAU;IAAqB,WAAW,yBAAyB;IACrE,UAAA;GACE,CAAA;EAEJ;;AAET,CAAC"}
@@ -1,2 +1,2 @@
1
- import { EmptyState, EmptyStateProps, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants } from "./empty-state.js";
2
- export { EmptyState, type EmptyStateProps, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
1
+ import { EmptyState, EmptyStateProps, emptyStateActionVariants, emptyStateImageVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants } from "./empty-state.js";
2
+ export { EmptyState, type EmptyStateProps, emptyStateActionVariants, emptyStateImageVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
@@ -1,2 +1,2 @@
1
- import { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants } from "./empty-state.js";
2
- export { EmptyState, emptyStateActionVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
1
+ import { EmptyState, emptyStateActionVariants, emptyStateImageVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants } from "./empty-state.js";
2
+ export { EmptyState, emptyStateActionVariants, emptyStateImageVariants, emptyStateTextVariants, emptyStateTitleVariants, emptyStateVariants };
@@ -18,14 +18,30 @@ import { ReactNode } from "react";
18
18
  * source text, so a class assembled at runtime is invisible to it and would
19
19
  * simply never be emitted.
20
20
  */
21
- export declare const fileDropZoneVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
22
- export declare const fileDropZonePromptVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
23
- export declare const fileDropZoneHintVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
21
+ export declare const fileDropZoneVariants: (props?: ({
22
+ size?: "sm" | "md" | null | undefined;
23
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
24
+ export declare const fileDropZonePromptVariants: (props?: ({
25
+ size?: "sm" | "md" | null | undefined;
26
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
27
+ export declare const fileDropZoneHintVariants: (props?: ({
28
+ size?: "sm" | "md" | null | undefined;
29
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
24
30
  /**
25
31
  * A place to drop or choose files.
26
32
  *
27
33
  * @example
28
34
  * <FileDropZone
35
+ * size="sm"
36
+ * label="Company logo"
37
+ * prompt="Drop a logo here"
38
+ * hint="PNG, JPG or SVG, up to 5 MB."
39
+ * acceptedFileTypes={["image/*"]}
40
+ * onFiles={(files) => upload(files[0])}
41
+ * />
42
+ *
43
+ * @example
44
+ * <FileDropZone
29
45
  * label="Contract"
30
46
  * prompt="Drop a contract here"
31
47
  * hint="PDF only, up to 20 MB."
@@ -63,6 +79,16 @@ export interface FileDropZoneProps {
63
79
  buttonLabel?: string;
64
80
  /** Take no file: the button is disabled and a drop is refused. */
65
81
  isDisabled?: boolean;
82
+ /**
83
+ * How much room the zone takes. `md` is the stacked zone (prompt, button and
84
+ * hint centred in a tall dashed area). `sm` is one row as tall as a medium
85
+ * field, for a form row such as a logo upload: the prompt, the button and the
86
+ * hint sit side by side, and when the row is narrow the hint truncates first.
87
+ * Same parts, same order, same keyboard path at both sizes.
88
+ *
89
+ * @default "md"
90
+ */
91
+ size?: "sm" | "md";
66
92
  /** Extra classes for the zone. Merged last, so they win conflicts with ours. */
67
93
  className?: string;
68
94
  }