@robr0/design-system 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +15 -6
  2. package/charts.d.ts +1 -0
  3. package/charts.js +2 -0
  4. package/components/AnchorNav/AnchorNav.css +74 -0
  5. package/components/AnchorNav/AnchorNav.d.ts +7 -0
  6. package/components/AnchorNav/AnchorNav.js +74 -21
  7. package/components/Banner/Banner.css +110 -0
  8. package/components/Banner/Banner.d.ts +36 -0
  9. package/components/Banner/Banner.js +59 -0
  10. package/components/Button/Button.css +51 -0
  11. package/components/Button/Button.d.ts +2 -2
  12. package/components/Chart/Chart.css +4 -1
  13. package/components/CircularButton/CircularButton.css +46 -0
  14. package/components/CircularButton/CircularButton.d.ts +13 -2
  15. package/components/CircularButton/CircularButton.js +19 -16
  16. package/components/CommandPalette/CommandPalette.css +26 -1
  17. package/components/CommandPalette/CommandPalette.js +11 -7
  18. package/components/Composer/Composer.css +18 -0
  19. package/components/Composer/Composer.d.ts +4 -2
  20. package/components/Composer/Composer.js +1 -0
  21. package/components/ContributionGraph/ContributionGraph.css +28 -1
  22. package/components/ContributionGraph/ContributionGraph.d.ts +9 -1
  23. package/components/ContributionGraph/ContributionGraph.js +66 -42
  24. package/components/EmptyState/EmptyState.css +5 -0
  25. package/components/Figure/Figure.css +14 -4
  26. package/components/Figure/Figure.js +11 -20
  27. package/components/FunnelChart/FunnelChart.css +11 -35
  28. package/components/FunnelChart/FunnelChart.d.ts +26 -15
  29. package/components/FunnelChart/FunnelChart.js +82 -34
  30. package/components/Gauge/Gauge.css +39 -12
  31. package/components/Gauge/Gauge.d.ts +16 -8
  32. package/components/Gauge/Gauge.js +78 -62
  33. package/components/HoverCard/HoverCard.css +97 -0
  34. package/components/HoverCard/HoverCard.d.ts +29 -0
  35. package/components/HoverCard/HoverCard.js +83 -0
  36. package/components/ImageCompare/ImageCompare.css +112 -0
  37. package/components/ImageCompare/ImageCompare.d.ts +40 -0
  38. package/components/ImageCompare/ImageCompare.js +134 -0
  39. package/components/LinkList/LinkList.d.ts +3 -1
  40. package/components/LinkList/LinkList.js +4 -3
  41. package/components/Meter/Meter.css +98 -0
  42. package/components/Meter/Meter.d.ts +36 -0
  43. package/components/Meter/Meter.js +48 -0
  44. package/components/NotificationCenter/NotificationCenter.css +11 -3
  45. package/components/ProgressBar/ProgressBar.css +1 -1
  46. package/components/Rating/Rating.css +74 -0
  47. package/components/Rating/Rating.d.ts +41 -0
  48. package/components/Rating/Rating.js +124 -0
  49. package/components/SegmentedControl/SegmentedControl.css +23 -0
  50. package/components/SegmentedControl/SegmentedControl.d.ts +3 -1
  51. package/components/SegmentedControl/SegmentedControl.js +3 -1
  52. package/components/Slider/Slider.css +1 -1
  53. package/components/Slider/Slider.js +1 -1
  54. package/components/SourceTrail/SourceTrail.css +209 -0
  55. package/components/SourceTrail/SourceTrail.d.ts +49 -0
  56. package/components/SourceTrail/SourceTrail.js +108 -0
  57. package/components/Sparkline/Sparkline.css +26 -2
  58. package/components/Sparkline/Sparkline.d.ts +5 -3
  59. package/components/Sparkline/Sparkline.js +32 -2
  60. package/components/Spinner/Spinner.css +1 -1
  61. package/components/SplitButton/SplitButton.css +93 -0
  62. package/components/SplitButton/SplitButton.d.ts +43 -0
  63. package/components/SplitButton/SplitButton.js +67 -0
  64. package/components/StreamingText/StreamingText.d.ts +27 -9
  65. package/components/StreamingText/StreamingText.js +17 -29
  66. package/components/StreamingText/useStreamReveal.d.ts +70 -0
  67. package/components/StreamingText/useStreamReveal.js +121 -0
  68. package/components/ToggleGroup/ToggleGroup.css +17 -0
  69. package/components/ToggleGroup/ToggleGroup.d.ts +2 -0
  70. package/components/ToggleGroup/ToggleGroup.js +2 -0
  71. package/components/ToolCall/ToolCall.css +7 -2
  72. package/components/Tooltip/Tooltip.css +24 -7
  73. package/components/Tooltip/Tooltip.d.ts +2 -1
  74. package/components/UsageCard/UsageCard.css +30 -0
  75. package/components/UsageCard/UsageCard.d.ts +48 -0
  76. package/components/UsageCard/UsageCard.js +54 -0
  77. package/components/registry.d.ts +8 -2
  78. package/components/registry.json +89 -14
  79. package/components/registry.json.d.ts +89 -14
  80. package/components/registry.json.js +1 -1
  81. package/index.d.ts +8 -1
  82. package/index.js +19 -2
  83. package/package.json +5 -1
  84. package/tokens/motion.d.ts +9 -4
  85. package/tokens/motion.js +5 -1
  86. package/tokens/registry.json +4 -0
  87. package/tokens/registry.json.d.ts +4 -0
  88. package/tokens/registry.json.js +1 -1
  89. package/tokens/tokens-dark.css +11 -2
  90. package/tokens/tokens-light.css +25 -11
  91. package/tokens/tokens-primitives.css +3 -0
@@ -1,25 +1,34 @@
1
1
  import { default as React } from 'react';
2
2
  /** One stage of the funnel, ordered widest first. */
3
3
  export interface FunnelStage {
4
- /** Stage name, read into the chart's accessible label. */
4
+ /** Stage name, shown beside the stage and read into the chart's accessible label. */
5
5
  label: string;
6
- /** Raw count for the stage — drives the bar's height as a share of the first stage. */
6
+ /** Raw count for the stage — drives the stage's width as a share of the first stage. */
7
7
  value: number;
8
8
  /**
9
- * Preformatted value ("96.4K") for the accessible label and any consumer
10
- * legend. Falls back to the raw `value`, locale-formatted.
9
+ * Preformatted value ("96.4K") for the tooltip and the accessible label.
10
+ * Falls back to the raw `value`, locale-formatted.
11
11
  */
12
12
  displayValue?: string;
13
13
  }
14
14
  /** Props owned by FunnelChart itself — everything else falls through to the root div. */
15
15
  type FunnelChartOwnProps = {
16
- /** Ordered stages, first stage widest. Each later stage's height is its share of the first. */
16
+ /** Ordered stages, first stage widest. Each later stage's width is its share of the first. */
17
17
  data: FunnelStage[];
18
- /** Chart height in pixels. */
18
+ /** Chart title, in the shared chart header. */
19
+ title?: string;
20
+ /** Description text below the title. */
21
+ subtitle?: string;
22
+ /** Strip the card chrome (border, padding, fill) when the chart sits inside another panel that supplies the surface */
23
+ bare?: boolean;
24
+ /** Chart area height in pixels. */
19
25
  height?: number;
26
+ /** Shows each stage's name beside its band. Turn off when a legend under the chart already carries the names. */
27
+ showLabels?: boolean;
20
28
  /**
21
- * Floor percentage for a stage's height, so steep drop-offs stay readable.
22
- * Shares are clamped to the range from this floor up to 100.
29
+ * Floor percentage from the stepped-bar rendering this chart used to have.
30
+ * @deprecated The funnel now draws true trapezoids sized by value, so a
31
+ * height floor no longer applies; the prop is ignored.
23
32
  */
24
33
  minStageShare?: number;
25
34
  /** Additional CSS classes */
@@ -28,13 +37,15 @@ type FunnelChartOwnProps = {
28
37
  export interface FunnelChartProps extends FunnelChartOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof FunnelChartOwnProps | 'children'> {
29
38
  }
30
39
  /**
31
- * FunnelChart — ordered stages as side-by-side vertical bars whose heights
32
- * step down with each stage's share of the first, each carrying a centred
33
- * percentage pill. Pure JSX and CSS computed from props: no recharts, no
34
- * hooks, no browser APIs, so it deliberately omits 'use client' and renders
35
- * from a Server Component. Degenerate data stays safe: an empty array
36
- * renders an empty root, and a zero or negative first value draws every
37
- * stage at the floor height.
40
+ * FunnelChart — ordered stages as a centred funnel of trapezoid bands, built
41
+ * on Recharts' native Funnel so the stages sweep in on load and share the
42
+ * family's tooltip and palette. Each band's width is its stage's share of the
43
+ * first, the honest recharts geometry: the taper itself is the conversion
44
+ * story, and the glass tooltip carries each stage's reading and percentage.
45
+ * It wears the chart family's card chrome (title, subtitle, padding) and
46
+ * takes `bare` to drop it inside a Panel, like every other chart. The whole
47
+ * funnel is summarised in one accessible label, stage by stage. Degenerate
48
+ * data stays safe: an empty array renders an empty chart area.
38
49
  */
39
50
  export declare const FunnelChart: React.ForwardRefExoticComponent<FunnelChartProps & React.RefAttributes<HTMLDivElement>>;
40
51
  export {};
@@ -1,45 +1,93 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import React from "react";
2
+ import React, { useCallback } from "react";
3
+ import { ResponsiveContainer, FunnelChart as FunnelChart$1, Tooltip, Funnel, LabelList } from "recharts";
4
+ import { getChartSeriesColors } from "../Chart/palette.js";
5
+ import "../Chart/Chart.css";
3
6
  import "./FunnelChart.css";
4
- const SERIES_COUNT = 7;
7
+ function getCSSVar(name, fallback) {
8
+ return `var(${name}, ${fallback})`;
9
+ }
10
+ function FunnelTooltip({ active, payload }) {
11
+ if (!active || !payload?.length) return null;
12
+ const entry = payload[0];
13
+ const stage = entry.payload;
14
+ return /* @__PURE__ */ jsxs("div", { className: "ds-chart__tooltip", children: [
15
+ /* @__PURE__ */ jsx("div", { className: "ds-chart__tooltip-label", children: stage?.name }),
16
+ /* @__PURE__ */ jsxs("div", { className: "ds-chart__tooltip-row", children: [
17
+ /* @__PURE__ */ jsx(
18
+ "span",
19
+ {
20
+ className: "ds-chart__tooltip-dot",
21
+ style: { backgroundColor: stage?.fill }
22
+ }
23
+ ),
24
+ /* @__PURE__ */ jsx("span", { className: "ds-chart__tooltip-name", children: stage?.share !== void 0 ? `${stage.share}%` : "" }),
25
+ /* @__PURE__ */ jsx("span", { className: "ds-chart__tooltip-value", children: stage?.displayValue ?? entry.value?.toLocaleString() })
26
+ ] })
27
+ ] });
28
+ }
5
29
  const FunnelChart = React.forwardRef(
6
- ({ data, height = 190, minStageShare = 16, className = "", style, ...rest }, ref) => {
30
+ ({
31
+ data,
32
+ title,
33
+ subtitle,
34
+ bare = false,
35
+ height = 190,
36
+ showLabels = true,
37
+ minStageShare,
38
+ className = "",
39
+ ...rest
40
+ }, ref) => {
7
41
  const baseClass = "ds-funnel-chart";
8
- const classes = [baseClass, className].filter(Boolean).join(" ");
42
+ const chartClass = "ds-chart";
43
+ const classes = [chartClass, bare && `${chartClass}--bare`, baseClass, className].filter(Boolean).join(" ");
44
+ const seriesColors = getChartSeriesColors();
45
+ const textSecondary = getCSSVar("--color-text-secondary", "#A2A2A2");
46
+ const surfaceColor = getCSSVar("--color-bg-container-primary", "#FFFFFF");
9
47
  const firstValue = data.length > 0 ? data[0].value : 0;
10
- const clampShare = (share) => Math.min(Math.max(share, minStageShare), 100);
11
48
  const stages = data.map((stage, i) => ({
12
- ...stage,
13
- share: clampShare(firstValue > 0 ? stage.value / firstValue * 100 : 0),
14
- color: `var(--color-chart-series-${i % SERIES_COUNT + 1})`
49
+ name: stage.label,
50
+ value: stage.value,
51
+ displayValue: stage.displayValue,
52
+ share: firstValue > 0 ? Math.round(stage.value / firstValue * 100) : 0,
53
+ fill: seriesColors[i % seriesColors.length]
15
54
  }));
16
- const ariaLabel = stages.length > 0 ? `Funnel: ${stages.map((s) => `${s.label} ${s.displayValue ?? s.value.toLocaleString()}`).join(", ")}` : "Funnel, no data";
17
- return /* @__PURE__ */ jsx(
18
- "div",
19
- {
20
- ...rest,
21
- ref,
22
- className: classes,
23
- style: { ...style, height },
24
- role: "img",
25
- "aria-label": ariaLabel,
26
- children: stages.map((stage, i) => /* @__PURE__ */ jsx(
27
- "div",
28
- {
29
- className: `${baseClass}__stage`,
30
- style: {
31
- "--funnel-stage-size": stage.share,
32
- "--funnel-stage-color": stage.color
33
- },
34
- children: /* @__PURE__ */ jsxs("span", { className: `${baseClass}__pct`, children: [
35
- Math.round(firstValue > 0 ? stage.value / firstValue * 100 : 0),
36
- "%"
37
- ] })
38
- },
39
- `${stage.label}-${i}`
40
- ))
41
- }
55
+ const ariaLabel = stages.length > 0 ? `Funnel: ${stages.map((s) => `${s.name} ${s.displayValue ?? s.value.toLocaleString()}`).join(", ")}` : "Funnel, no data";
56
+ const renderTooltip = useCallback(
57
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
58
+ (props) => /* @__PURE__ */ jsx(FunnelTooltip, { ...props }),
59
+ []
42
60
  );
61
+ return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
62
+ (title || subtitle) && /* @__PURE__ */ jsx("div", { className: `${chartClass}__header`, children: /* @__PURE__ */ jsxs("div", { className: `${chartClass}__header-text`, children: [
63
+ title && /* @__PURE__ */ jsx("h3", { className: `${chartClass}__title`, children: title }),
64
+ subtitle && /* @__PURE__ */ jsx("p", { className: `${chartClass}__subtitle`, children: subtitle })
65
+ ] }) }),
66
+ /* @__PURE__ */ jsx("div", { className: `${chartClass}__body`, role: "img", "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height, children: /* @__PURE__ */ jsxs(FunnelChart$1, { margin: { top: 4, right: 4, bottom: 4, left: 4 }, children: [
67
+ /* @__PURE__ */ jsx(Tooltip, { content: renderTooltip }),
68
+ /* @__PURE__ */ jsx(
69
+ Funnel,
70
+ {
71
+ dataKey: "value",
72
+ nameKey: "name",
73
+ data: stages,
74
+ lastShapeType: "rectangle",
75
+ stroke: surfaceColor,
76
+ strokeWidth: 2,
77
+ children: showLabels && /* @__PURE__ */ jsx(
78
+ LabelList,
79
+ {
80
+ position: "right",
81
+ dataKey: "name",
82
+ fill: textSecondary,
83
+ stroke: "none",
84
+ fontSize: 12
85
+ }
86
+ )
87
+ }
88
+ )
89
+ ] }) }) })
90
+ ] });
43
91
  }
44
92
  );
45
93
  FunnelChart.displayName = "FunnelChart";
@@ -1,20 +1,34 @@
1
1
  /* ============================================
2
2
  GAUGE COMPONENT
3
3
  Radial dial for a single bounded reading.
4
- Colour flows through `color` the value arc
5
- and reading draw with currentColor, so a tone
6
- is one custom property swap.
4
+ Card chrome, header, and padding come from
5
+ the shared chart styles (Chart.css) this
6
+ file owns the dial alone. Colour flows
7
+ through `color`: the value arc draws with
8
+ currentColor, so a tone is one custom
9
+ property swap.
7
10
  ============================================ */
8
11
 
9
- /* Base */
12
+ /* Base — the dial centres in the shared chart body */
10
13
 
11
- .ds-gauge {
14
+ .ds-gauge__body {
15
+ display: flex;
16
+ justify-content: center;
17
+ }
18
+
19
+ /* Unlike the fluid recharts bodies, the dial has intrinsic size — a bare
20
+ gauge shrink-wraps it so it sits inline in a Stat row or tile. */
21
+ .ds-gauge.ds-chart--bare {
22
+ width: auto;
23
+ display: inline-flex;
24
+ }
25
+
26
+ .ds-gauge__dial {
12
27
  position: relative;
13
28
  display: inline-flex;
14
29
  align-items: center;
15
30
  justify-content: center;
16
31
  flex-shrink: 0;
17
- color: var(--color-action-primary-bg);
18
32
  }
19
33
 
20
34
  .ds-gauge__svg {
@@ -34,6 +48,16 @@
34
48
  stroke: currentColor;
35
49
  stroke-linecap: round;
36
50
  transition: stroke-dasharray var(--motion-duration-slow) var(--motion-ease-emphasized);
51
+ /* Sweep in from an empty dial on mount — the single keyframe animates to
52
+ the element's own dasharray, so one rule serves every reading. The
53
+ duration tokens collapse under reduced motion, guarding this for free. */
54
+ animation: ds-gauge-sweep var(--motion-duration-slower) var(--motion-ease-emphasized);
55
+ }
56
+
57
+ @keyframes ds-gauge-sweep {
58
+ from {
59
+ stroke-dasharray: 0 100;
60
+ }
37
61
  }
38
62
 
39
63
  .ds-gauge__center {
@@ -48,12 +72,15 @@
48
72
  pointer-events: none;
49
73
  }
50
74
 
75
+ /* The centre pair mirrors the chart family's radial centre label:
76
+ a light sub-display numeral over a caption. */
77
+
51
78
  .ds-gauge__value {
52
- font-family: var(--font-heading-3-family);
53
- font-size: var(--font-heading-3-size);
54
- font-weight: var(--font-heading-3-weight);
55
- line-height: var(--font-heading-3-line-height);
56
- letter-spacing: var(--font-heading-3-letter-spacing);
79
+ font-family: var(--font-sub-display-family);
80
+ font-size: var(--font-sub-display-size);
81
+ font-weight: var(--font-sub-display-weight);
82
+ line-height: var(--font-sub-display-line-height);
83
+ letter-spacing: var(--font-sub-display-letter-spacing);
57
84
  color: var(--color-text-primary);
58
85
  }
59
86
 
@@ -69,7 +96,7 @@
69
96
  /* Tones */
70
97
 
71
98
  .ds-gauge--accent {
72
- color: var(--color-action-primary-bg);
99
+ color: var(--color-chart-series-1);
73
100
  }
74
101
 
75
102
  /* The status hues come from the border tokens, not the text tokens: the
@@ -15,9 +15,9 @@ type GaugeOwnProps = {
15
15
  /** Upper bound of the dial. */
16
16
  max?: number;
17
17
  /**
18
- * Colour role for the value arc and reading. Ignored while a threshold
19
- * matches thresholds exist so the dial recolours itself as the reading
20
- * crosses them.
18
+ * Colour role for the value arc. `accent` (the default) follows the chart
19
+ * palette's lead colour. Ignored while a threshold matches thresholds
20
+ * exist so the dial recolours itself as the reading crosses them.
21
21
  */
22
22
  tone?: 'accent' | 'positive' | 'warning' | 'error' | 'neutral';
23
23
  /**
@@ -35,7 +35,13 @@ type GaugeOwnProps = {
35
35
  * accessible name, e.g. "CPU usage".
36
36
  */
37
37
  label?: string;
38
- /** Rendered diameter in pixels. The arc geometry scales with it. */
38
+ /** Chart title, in the shared chart header. */
39
+ title?: string;
40
+ /** Description text below the title. */
41
+ subtitle?: string;
42
+ /** Strip the card chrome (border, padding, fill) when the chart sits inside another panel that supplies the surface */
43
+ bare?: boolean;
44
+ /** Rendered dial diameter in pixels. The arc geometry scales with it. */
39
45
  size?: number;
40
46
  /** Arc thickness in pixels. */
41
47
  strokeWidth?: number;
@@ -47,10 +53,12 @@ export interface GaugeProps extends GaugeOwnProps, Omit<React.ComponentPropsWith
47
53
  /**
48
54
  * Gauge — a radial dial for a single bounded reading: capacity, usage, a
49
55
  * score against a target. Pure SVG computed from props — no charting library,
50
- * no hooks — so it renders from a Server Component and drops straight into a
51
- * Panel or Stat row. Thresholds recolour the dial through the status roles as
52
- * the reading crosses them, and the arc animates between readings via a CSS
53
- * transition. Announced as a `meter` with the label as its accessible name.
56
+ * no hooks — so it renders from a Server Component. It wears the chart
57
+ * family's card chrome (title, subtitle, padding) and takes `bare` to drop
58
+ * it inside a Panel, like every other chart. Thresholds recolour the dial
59
+ * through the status roles as the reading crosses them, and the arc animates
60
+ * between readings via a CSS transition. The dial is announced as a `meter`
61
+ * with the label as its accessible name.
54
62
  */
55
63
  export declare const Gauge: React.ForwardRefExoticComponent<GaugeProps & React.RefAttributes<HTMLDivElement>>;
56
64
  export {};
@@ -1,5 +1,6 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import React from "react";
3
+ import "../Chart/Chart.css";
3
4
  import "./Gauge.css";
4
5
  const SWEEP_UNITS = 75;
5
6
  const Gauge = React.forwardRef(
@@ -12,81 +13,96 @@ const Gauge = React.forwardRef(
12
13
  showValue = true,
13
14
  formatValue,
14
15
  label,
16
+ title,
17
+ subtitle,
18
+ bare = false,
15
19
  size = 120,
16
- strokeWidth = 10,
20
+ strokeWidth = 12,
17
21
  className = "",
18
22
  ...rest
19
23
  }, ref) => {
20
24
  const baseClass = "ds-gauge";
25
+ const chartClass = "ds-chart";
21
26
  const range = max - min;
22
27
  const fraction = range > 0 ? Math.min(Math.max((value - min) / range, 0), 1) : 0;
23
28
  const activeThreshold = thresholds?.filter((t) => value >= t.value).sort((a, b) => b.value - a.value)[0];
24
29
  const resolvedTone = activeThreshold?.tone ?? tone;
25
- const classes = [baseClass, `${baseClass}--${resolvedTone}`, className].filter(Boolean).join(" ");
30
+ const classes = [
31
+ chartClass,
32
+ bare && `${chartClass}--bare`,
33
+ baseClass,
34
+ `${baseClass}--${resolvedTone}`,
35
+ className
36
+ ].filter(Boolean).join(" ");
26
37
  const radius = (size - strokeWidth) / 2;
27
38
  const center = size / 2;
28
39
  const valueUnits = Math.round(SWEEP_UNITS * fraction * 100) / 100;
29
40
  const displayValue = formatValue ? formatValue(value) : String(Math.round(value));
30
- return /* @__PURE__ */ jsxs(
31
- "div",
32
- {
33
- ...rest,
34
- ref,
35
- className: classes,
36
- style: { width: size, height: size, ...rest.style },
37
- role: "meter",
38
- "aria-valuenow": value,
39
- "aria-valuemin": min,
40
- "aria-valuemax": max,
41
- "aria-valuetext": displayValue,
42
- "aria-label": label ?? rest["aria-label"],
43
- children: [
44
- /* @__PURE__ */ jsxs(
45
- "svg",
46
- {
47
- className: `${baseClass}__svg`,
48
- viewBox: `0 0 ${size} ${size}`,
49
- width: size,
50
- height: size,
51
- xmlns: "http://www.w3.org/2000/svg",
52
- "aria-hidden": "true",
53
- children: [
54
- /* @__PURE__ */ jsx(
55
- "circle",
56
- {
57
- className: `${baseClass}__track`,
58
- cx: center,
59
- cy: center,
60
- r: radius,
61
- pathLength: 100,
62
- strokeWidth,
63
- strokeDasharray: `${SWEEP_UNITS} ${100 - SWEEP_UNITS}`,
64
- transform: `rotate(135 ${center} ${center})`
65
- }
66
- ),
67
- valueUnits > 0 && /* @__PURE__ */ jsx(
68
- "circle",
69
- {
70
- className: `${baseClass}__arc`,
71
- cx: center,
72
- cy: center,
73
- r: radius,
74
- pathLength: 100,
75
- strokeWidth,
76
- strokeDasharray: `${valueUnits} ${100 - valueUnits}`,
77
- transform: `rotate(135 ${center} ${center})`
78
- }
79
- )
80
- ]
81
- }
82
- ),
83
- (showValue || label) && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__center`, children: [
84
- showValue && /* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, children: displayValue }),
85
- label && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label })
86
- ] })
87
- ]
88
- }
89
- );
41
+ const { "aria-label": restAriaLabel, ...restProps } = rest;
42
+ return /* @__PURE__ */ jsxs("div", { ...restProps, ref, className: classes, children: [
43
+ (title || subtitle) && /* @__PURE__ */ jsx("div", { className: `${chartClass}__header`, children: /* @__PURE__ */ jsxs("div", { className: `${chartClass}__header-text`, children: [
44
+ title && /* @__PURE__ */ jsx("h3", { className: `${chartClass}__title`, children: title }),
45
+ subtitle && /* @__PURE__ */ jsx("p", { className: `${chartClass}__subtitle`, children: subtitle })
46
+ ] }) }),
47
+ /* @__PURE__ */ jsx("div", { className: `${chartClass}__body ${baseClass}__body`, children: /* @__PURE__ */ jsxs(
48
+ "div",
49
+ {
50
+ className: `${baseClass}__dial`,
51
+ style: { width: size, height: size },
52
+ role: "meter",
53
+ "aria-valuenow": value,
54
+ "aria-valuemin": min,
55
+ "aria-valuemax": max,
56
+ "aria-valuetext": displayValue,
57
+ "aria-label": label ?? restAriaLabel,
58
+ children: [
59
+ /* @__PURE__ */ jsxs(
60
+ "svg",
61
+ {
62
+ className: `${baseClass}__svg`,
63
+ viewBox: `0 0 ${size} ${size}`,
64
+ width: size,
65
+ height: size,
66
+ xmlns: "http://www.w3.org/2000/svg",
67
+ "aria-hidden": "true",
68
+ children: [
69
+ /* @__PURE__ */ jsx(
70
+ "circle",
71
+ {
72
+ className: `${baseClass}__track`,
73
+ cx: center,
74
+ cy: center,
75
+ r: radius,
76
+ pathLength: 100,
77
+ strokeWidth,
78
+ strokeDasharray: `${SWEEP_UNITS} ${100 - SWEEP_UNITS}`,
79
+ transform: `rotate(135 ${center} ${center})`
80
+ }
81
+ ),
82
+ valueUnits > 0 && /* @__PURE__ */ jsx(
83
+ "circle",
84
+ {
85
+ className: `${baseClass}__arc`,
86
+ cx: center,
87
+ cy: center,
88
+ r: radius,
89
+ pathLength: 100,
90
+ strokeWidth,
91
+ strokeDasharray: `${valueUnits} ${100 - valueUnits}`,
92
+ transform: `rotate(135 ${center} ${center})`
93
+ }
94
+ )
95
+ ]
96
+ }
97
+ ),
98
+ (showValue || label) && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__center`, children: [
99
+ showValue && /* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, children: displayValue }),
100
+ label && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label })
101
+ ] })
102
+ ]
103
+ }
104
+ ) })
105
+ ] });
90
106
  }
91
107
  );
92
108
  Gauge.displayName = "Gauge";
@@ -0,0 +1,97 @@
1
+ /* ============================================
2
+ HOVER CARD COMPONENT
3
+ Rich preview panel on hover / focus
4
+ ============================================ */
5
+
6
+ .ds-hover-card {
7
+ position: relative;
8
+ display: inline-flex;
9
+ }
10
+
11
+ /* ============================================
12
+ PANEL
13
+ Dresses like the other floating panels:
14
+ page-primary fill, hairline, radius-sm,
15
+ floating shadow (see Popover).
16
+ ============================================ */
17
+
18
+ .ds-hover-card__panel {
19
+ /* Rendered as a <span> so the card survives inside a <p>; absolute
20
+ positioning already computes display to block, stated here anyway. */
21
+ display: block;
22
+ position: absolute;
23
+ z-index: 50;
24
+ min-width: 240px;
25
+ max-width: 320px;
26
+ padding: var(--padding-md);
27
+ border-radius: var(--radius-sm);
28
+ background-color: var(--color-bg-page-primary);
29
+ border: var(--border-xs) solid var(--color-bg-container-border);
30
+ box-shadow: var(--shadow-floating);
31
+ color: var(--color-text-primary);
32
+ font-family: var(--font-paragraph-sm-family);
33
+ font-size: var(--font-paragraph-sm-size);
34
+ font-weight: var(--font-paragraph-sm-weight);
35
+ line-height: var(--font-paragraph-sm-line-height);
36
+ /* visibility keeps the hidden panel out of the accessibility tree and
37
+ tab order; opacity + transform carry the entrance. */
38
+ visibility: hidden;
39
+ opacity: 0;
40
+ transition:
41
+ opacity var(--motion-duration-fast) var(--motion-ease-standard),
42
+ transform var(--motion-duration-fast) var(--motion-ease-standard),
43
+ visibility var(--motion-duration-fast) var(--motion-ease-standard);
44
+ }
45
+
46
+ .ds-hover-card__panel--visible {
47
+ visibility: visible;
48
+ opacity: 1;
49
+ }
50
+
51
+ /* ============================================
52
+ POSITIONS
53
+ ============================================ */
54
+
55
+ .ds-hover-card__panel--top {
56
+ bottom: 100%;
57
+ left: 50%;
58
+ transform: translateX(-50%) translateY(4px);
59
+ margin-bottom: var(--gap-xs);
60
+ }
61
+
62
+ .ds-hover-card__panel--top.ds-hover-card__panel--visible {
63
+ transform: translateX(-50%) translateY(0);
64
+ }
65
+
66
+ .ds-hover-card__panel--bottom {
67
+ top: 100%;
68
+ left: 50%;
69
+ transform: translateX(-50%) translateY(-4px);
70
+ margin-top: var(--gap-xs);
71
+ }
72
+
73
+ .ds-hover-card__panel--bottom.ds-hover-card__panel--visible {
74
+ transform: translateX(-50%) translateY(0);
75
+ }
76
+
77
+ .ds-hover-card__panel--left {
78
+ right: 100%;
79
+ top: 50%;
80
+ transform: translateY(-50%) translateX(4px);
81
+ margin-right: var(--gap-xs);
82
+ }
83
+
84
+ .ds-hover-card__panel--left.ds-hover-card__panel--visible {
85
+ transform: translateY(-50%) translateX(0);
86
+ }
87
+
88
+ .ds-hover-card__panel--right {
89
+ left: 100%;
90
+ top: 50%;
91
+ transform: translateY(-50%) translateX(-4px);
92
+ margin-left: var(--gap-xs);
93
+ }
94
+
95
+ .ds-hover-card__panel--right.ds-hover-card__panel--visible {
96
+ transform: translateY(-50%) translateX(0);
97
+ }
@@ -0,0 +1,29 @@
1
+ import { default as React } from 'react';
2
+ type HoverCardOwnProps = {
3
+ /** Trigger element */
4
+ children?: React.ReactNode;
5
+ /** Panel content — arbitrary elements, unlike Tooltip's plain text. In running text inside a `<p>`, keep it phrasing-level (spans): a block element would end the paragraph mid-parse. */
6
+ content: React.ReactNode;
7
+ /** Preferred position */
8
+ position?: 'top' | 'bottom' | 'left' | 'right';
9
+ /** Delay before showing (in ms) */
10
+ showDelay?: number;
11
+ /** Delay before hiding (in ms) */
12
+ hideDelay?: number;
13
+ /** Additional CSS classes */
14
+ className?: string;
15
+ };
16
+ export interface HoverCardProps extends HoverCardOwnProps, Omit<React.ComponentPropsWithoutRef<'span'>, keyof HoverCardOwnProps> {
17
+ }
18
+ /**
19
+ * Rich preview panel that opens from hover or focus — a profile card behind
20
+ * a username, a component summary behind a link. Where Tooltip names a
21
+ * control in a line of text, HoverCard previews a destination; its panel is
22
+ * interactive, stays open while the pointer is inside it, and dresses like
23
+ * the other floating panels (Popover, the menus).
24
+ *
25
+ * The hidden panel is `visibility: hidden`, so it stays out of the
26
+ * accessibility tree and tab order until shown. Escape dismisses it.
27
+ */
28
+ export declare const HoverCard: React.ForwardRefExoticComponent<HoverCardProps & React.RefAttributes<HTMLSpanElement>>;
29
+ export {};