@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.
- package/README.md +15 -6
- package/charts.d.ts +1 -0
- package/charts.js +2 -0
- package/components/AnchorNav/AnchorNav.css +74 -0
- package/components/AnchorNav/AnchorNav.d.ts +7 -0
- package/components/AnchorNav/AnchorNav.js +74 -21
- package/components/Banner/Banner.css +110 -0
- package/components/Banner/Banner.d.ts +36 -0
- package/components/Banner/Banner.js +59 -0
- package/components/Button/Button.css +51 -0
- package/components/Button/Button.d.ts +2 -2
- package/components/Chart/Chart.css +4 -1
- package/components/CircularButton/CircularButton.css +46 -0
- package/components/CircularButton/CircularButton.d.ts +13 -2
- package/components/CircularButton/CircularButton.js +19 -16
- package/components/CommandPalette/CommandPalette.css +26 -1
- package/components/CommandPalette/CommandPalette.js +11 -7
- package/components/Composer/Composer.css +18 -0
- package/components/Composer/Composer.d.ts +4 -2
- package/components/Composer/Composer.js +1 -0
- package/components/ContributionGraph/ContributionGraph.css +28 -1
- package/components/ContributionGraph/ContributionGraph.d.ts +9 -1
- package/components/ContributionGraph/ContributionGraph.js +66 -42
- package/components/EmptyState/EmptyState.css +5 -0
- package/components/Figure/Figure.css +14 -4
- package/components/Figure/Figure.js +11 -20
- package/components/FunnelChart/FunnelChart.css +11 -35
- package/components/FunnelChart/FunnelChart.d.ts +26 -15
- package/components/FunnelChart/FunnelChart.js +82 -34
- package/components/Gauge/Gauge.css +39 -12
- package/components/Gauge/Gauge.d.ts +16 -8
- package/components/Gauge/Gauge.js +78 -62
- package/components/HoverCard/HoverCard.css +97 -0
- package/components/HoverCard/HoverCard.d.ts +29 -0
- package/components/HoverCard/HoverCard.js +83 -0
- package/components/ImageCompare/ImageCompare.css +112 -0
- package/components/ImageCompare/ImageCompare.d.ts +40 -0
- package/components/ImageCompare/ImageCompare.js +134 -0
- package/components/LinkList/LinkList.d.ts +3 -1
- package/components/LinkList/LinkList.js +4 -3
- package/components/Meter/Meter.css +98 -0
- package/components/Meter/Meter.d.ts +36 -0
- package/components/Meter/Meter.js +48 -0
- package/components/NotificationCenter/NotificationCenter.css +11 -3
- package/components/ProgressBar/ProgressBar.css +1 -1
- package/components/Rating/Rating.css +74 -0
- package/components/Rating/Rating.d.ts +41 -0
- package/components/Rating/Rating.js +124 -0
- package/components/SegmentedControl/SegmentedControl.css +23 -0
- package/components/SegmentedControl/SegmentedControl.d.ts +3 -1
- package/components/SegmentedControl/SegmentedControl.js +3 -1
- package/components/Slider/Slider.css +1 -1
- package/components/Slider/Slider.js +1 -1
- package/components/SourceTrail/SourceTrail.css +209 -0
- package/components/SourceTrail/SourceTrail.d.ts +49 -0
- package/components/SourceTrail/SourceTrail.js +108 -0
- package/components/Sparkline/Sparkline.css +26 -2
- package/components/Sparkline/Sparkline.d.ts +5 -3
- package/components/Sparkline/Sparkline.js +32 -2
- package/components/Spinner/Spinner.css +1 -1
- package/components/SplitButton/SplitButton.css +93 -0
- package/components/SplitButton/SplitButton.d.ts +43 -0
- package/components/SplitButton/SplitButton.js +67 -0
- package/components/StreamingText/StreamingText.d.ts +27 -9
- package/components/StreamingText/StreamingText.js +17 -29
- package/components/StreamingText/useStreamReveal.d.ts +70 -0
- package/components/StreamingText/useStreamReveal.js +121 -0
- package/components/ToggleGroup/ToggleGroup.css +17 -0
- package/components/ToggleGroup/ToggleGroup.d.ts +2 -0
- package/components/ToggleGroup/ToggleGroup.js +2 -0
- package/components/ToolCall/ToolCall.css +7 -2
- package/components/Tooltip/Tooltip.css +24 -7
- package/components/Tooltip/Tooltip.d.ts +2 -1
- package/components/UsageCard/UsageCard.css +30 -0
- package/components/UsageCard/UsageCard.d.ts +48 -0
- package/components/UsageCard/UsageCard.js +54 -0
- package/components/registry.d.ts +8 -2
- package/components/registry.json +89 -14
- package/components/registry.json.d.ts +89 -14
- package/components/registry.json.js +1 -1
- package/index.d.ts +8 -1
- package/index.js +19 -2
- package/package.json +5 -1
- package/tokens/motion.d.ts +9 -4
- package/tokens/motion.js +5 -1
- package/tokens/registry.json +4 -0
- package/tokens/registry.json.d.ts +4 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +11 -2
- package/tokens/tokens-light.css +25 -11
- 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
|
|
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
|
|
10
|
-
*
|
|
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
|
|
16
|
+
/** Ordered stages, first stage widest. Each later stage's width is its share of the first. */
|
|
17
17
|
data: FunnelStage[];
|
|
18
|
-
/** Chart
|
|
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
|
|
22
|
-
*
|
|
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
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
-
|
|
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
|
-
({
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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-
|
|
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-
|
|
53
|
-
font-size: var(--font-
|
|
54
|
-
font-weight: var(--font-
|
|
55
|
-
line-height: var(--font-
|
|
56
|
-
letter-spacing: var(--font-
|
|
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-
|
|
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
|
|
19
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
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 =
|
|
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 = [
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 {};
|