@robr0/design-system 0.14.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 +8 -5
- 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/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 +21 -0
- package/components/ContributionGraph/ContributionGraph.js +18 -10
- package/components/Figure/Figure.css +14 -4
- package/components/Figure/Figure.js +11 -20
- package/components/FunnelChart/FunnelChart.css +11 -36
- package/components/FunnelChart/FunnelChart.d.ts +19 -16
- package/components/FunnelChart/FunnelChart.js +67 -39
- package/components/Gauge/Gauge.css +11 -1
- package/components/Meter/Meter.css +10 -1
- package/components/ProgressBar/ProgressBar.css +1 -1
- 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.js +2 -1
- package/components/Spinner/Spinner.css +1 -1
- package/components/SplitButton/SplitButton.d.ts +1 -1
- package/components/SplitButton/SplitButton.js +1 -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 +41 -14
- package/components/registry.json.d.ts +41 -14
- package/components/registry.json.js +1 -1
- package/index.d.ts +2 -1
- package/index.js +4 -2
- package/package.json +1 -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
|
@@ -80,15 +80,19 @@ const CommandPalette = ({
|
|
|
80
80
|
}
|
|
81
81
|
}, [open]);
|
|
82
82
|
useEffect(() => {
|
|
83
|
-
if (open)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
if (!open) return;
|
|
84
|
+
const block = (event) => {
|
|
85
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
86
|
+
if (target?.closest(`.${baseClass}__list`)) return;
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
};
|
|
89
|
+
document.addEventListener("wheel", block, { passive: false });
|
|
90
|
+
document.addEventListener("touchmove", block, { passive: false });
|
|
88
91
|
return () => {
|
|
89
|
-
document.
|
|
92
|
+
document.removeEventListener("wheel", block);
|
|
93
|
+
document.removeEventListener("touchmove", block);
|
|
90
94
|
};
|
|
91
|
-
}, [open]);
|
|
95
|
+
}, [open, baseClass]);
|
|
92
96
|
useEffect(() => {
|
|
93
97
|
if (!open) return;
|
|
94
98
|
const handleTrapKeyDown = (e) => {
|
|
@@ -140,6 +140,24 @@
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/* Working: while a response streams, the ring stays lit and keeps turning
|
|
144
|
+
whether or not the field holds focus — the visible half of the `streaming`
|
|
145
|
+
contract for glowing composers. The pairing is defined in design.md's
|
|
146
|
+
Composer section; streaming without aiGlow changes nothing. */
|
|
147
|
+
.ds-composer--ai-glow.ds-composer--streaming {
|
|
148
|
+
border-color: transparent;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.ds-composer--ai-glow.ds-composer--streaming::before {
|
|
152
|
+
opacity: 1;
|
|
153
|
+
animation-play-state: running;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.ds-composer--ai-glow.ds-composer--streaming::after {
|
|
157
|
+
opacity: 0.3;
|
|
158
|
+
animation-play-state: running;
|
|
159
|
+
}
|
|
160
|
+
|
|
143
161
|
/* Reduced motion: the ring and glow stay, the turning stops. */
|
|
144
162
|
@media (prefers-reduced-motion: reduce) {
|
|
145
163
|
.ds-composer--ai-glow::before,
|
|
@@ -21,7 +21,8 @@ type ComposerOwnProps = {
|
|
|
21
21
|
onSubmit?: (value: string) => void;
|
|
22
22
|
/**
|
|
23
23
|
* A response is streaming: the send button becomes a stop button, submit
|
|
24
|
-
* is blocked, and Enter is inert.
|
|
24
|
+
* is blocked, and Enter is inert. On a glowing composer (`aiGlow`) the
|
|
25
|
+
* gradient ring also stays lit and keeps turning while this is true.
|
|
25
26
|
*/
|
|
26
27
|
streaming?: boolean;
|
|
27
28
|
/** Fires when the stop button is pressed while `streaming`. */
|
|
@@ -32,7 +33,8 @@ type ComposerOwnProps = {
|
|
|
32
33
|
* While focused, the shell wears AiButton's slowly rotating gradient ring
|
|
33
34
|
* and glow in place of the plain selected border — the system's "a model
|
|
34
35
|
* answers here" signal, for composers whose messages are answered by one.
|
|
35
|
-
*
|
|
36
|
+
* While `streaming`, the ring stays lit and turning whether or not the
|
|
37
|
+
* field holds focus. Off by default.
|
|
36
38
|
*/
|
|
37
39
|
aiGlow?: boolean;
|
|
38
40
|
/**
|
|
@@ -79,6 +79,27 @@
|
|
|
79
79
|
width: 100%;
|
|
80
80
|
height: auto;
|
|
81
81
|
aspect-ratio: 1 / 1;
|
|
82
|
+
/* The year sweeps in left to right on mount: each column's delay steps by
|
|
83
|
+
a fraction of the entrance duration (--ds-contribution-week, set inline
|
|
84
|
+
per column). Token-composed, so reduced motion collapses the sweep. */
|
|
85
|
+
animation: ds-contribution-fade var(--motion-duration-slow) var(--motion-ease-standard)
|
|
86
|
+
backwards;
|
|
87
|
+
animation-delay: calc(
|
|
88
|
+
var(--ds-contribution-week, 0) * var(--motion-duration-slower) / 40
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@keyframes ds-contribution-fade {
|
|
93
|
+
from {
|
|
94
|
+
opacity: 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Hover lifts the day under the pointer, so the title tooltip has a visible
|
|
99
|
+
anchor. */
|
|
100
|
+
.ds-contribution-graph__week .ds-contribution-graph__cell:not(.ds-contribution-graph__cell--empty):hover {
|
|
101
|
+
outline: var(--border-xs) solid var(--color-text-tertiary);
|
|
102
|
+
outline-offset: calc(-1 * var(--border-xs));
|
|
82
103
|
}
|
|
83
104
|
|
|
84
105
|
.ds-contribution-graph__cell--empty {
|
|
@@ -112,16 +112,24 @@ const ContributionGraph = ({
|
|
|
112
112
|
style: { gridTemplateColumns: columnTemplate },
|
|
113
113
|
role: "img",
|
|
114
114
|
"aria-label": caption ?? "Contribution activity",
|
|
115
|
-
children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx(
|
|
116
|
+
"div",
|
|
117
|
+
{
|
|
118
|
+
className: "ds-contribution-graph__week",
|
|
119
|
+
style: { "--ds-contribution-week": weekIndex },
|
|
120
|
+
children: week.map(
|
|
121
|
+
(day, dayIndex) => day ? /* @__PURE__ */ jsx(
|
|
122
|
+
"div",
|
|
123
|
+
{
|
|
124
|
+
className: `ds-contribution-graph__cell ds-contribution-graph__cell--level-${day.level}`,
|
|
125
|
+
title: formatTooltip(day)
|
|
126
|
+
},
|
|
127
|
+
day.date
|
|
128
|
+
) : /* @__PURE__ */ jsx("div", { className: "ds-contribution-graph__cell ds-contribution-graph__cell--empty" }, `pad-${dayIndex}`)
|
|
129
|
+
)
|
|
130
|
+
},
|
|
131
|
+
weekIndex
|
|
132
|
+
))
|
|
125
133
|
}
|
|
126
134
|
)
|
|
127
135
|
] }),
|
|
@@ -47,15 +47,25 @@
|
|
|
47
47
|
ZOOMABLE (has onClick)
|
|
48
48
|
============================================ */
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
/* The zoom control is a real <button> wrapping the media (ARIA forbids
|
|
51
|
+
role="button" on <figure>), stripped back to a bare block. */
|
|
52
|
+
.ds-figure__zoom {
|
|
53
|
+
display: block;
|
|
54
|
+
width: 100%;
|
|
55
|
+
padding: 0;
|
|
56
|
+
border: 0;
|
|
57
|
+
background: none;
|
|
58
|
+
font: inherit;
|
|
51
59
|
cursor: zoom-in;
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
.ds-
|
|
62
|
+
.ds-figure__zoom:hover .ds-figure__media img {
|
|
55
63
|
opacity: 0.88;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
/* Inset ring: the button sits inside the figure's overflow clip, so an
|
|
67
|
+
outset outline would be cropped away. */
|
|
68
|
+
.ds-figure__zoom:focus-visible {
|
|
59
69
|
outline: 2px solid var(--color-action-primary-bg);
|
|
60
|
-
outline-offset: 2px;
|
|
70
|
+
outline-offset: -2px;
|
|
61
71
|
}
|
|
@@ -9,26 +9,17 @@ const Figure = ({
|
|
|
9
9
|
}) => {
|
|
10
10
|
const baseClass = "ds-figure";
|
|
11
11
|
const classes = [baseClass, onClick && `${baseClass}--zoomable`, className].filter(Boolean).join(" ");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
onKeyDown,
|
|
24
|
-
role: onClick ? "button" : void 0,
|
|
25
|
-
tabIndex: onClick ? 0 : void 0,
|
|
26
|
-
children: [
|
|
27
|
-
/* @__PURE__ */ jsx("div", { className: `${baseClass}__media`, children }),
|
|
28
|
-
caption && /* @__PURE__ */ jsx("figcaption", { className: `${baseClass}__caption`, children: caption })
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
);
|
|
12
|
+
const media = /* @__PURE__ */ jsx("div", { className: `${baseClass}__media`, children });
|
|
13
|
+
return /* @__PURE__ */ jsxs("figure", { className: classes, children: [
|
|
14
|
+
onClick ? (
|
|
15
|
+
// A real button rather than role="button" on the figure: ARIA
|
|
16
|
+
// forbids the button role on <figure>, and the element brings
|
|
17
|
+
// Enter/Space activation itself. Its accessible name is the
|
|
18
|
+
// child image's alt text.
|
|
19
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: `${baseClass}__zoom`, onClick, children: media })
|
|
20
|
+
) : media,
|
|
21
|
+
caption && /* @__PURE__ */ jsx("figcaption", { className: `${baseClass}__caption`, children: caption })
|
|
22
|
+
] });
|
|
32
23
|
};
|
|
33
24
|
export {
|
|
34
25
|
Figure
|
|
@@ -1,42 +1,17 @@
|
|
|
1
1
|
/* ============================================
|
|
2
2
|
FUNNEL CHART COMPONENT
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
Centred trapezoid funnel built on Recharts'
|
|
4
|
+
native Funnel. Card chrome, header, padding
|
|
5
|
+
and the tooltip surface all come from the
|
|
6
|
+
shared chart styles (Chart.css); colour
|
|
7
|
+
arrives per stage as a chart-series token
|
|
8
|
+
resolved live in SVG paint. This file owns
|
|
9
|
+
only what the shared styles cannot reach.
|
|
10
10
|
============================================ */
|
|
11
11
|
|
|
12
|
-
/*
|
|
13
|
-
|
|
12
|
+
/* The stage-name labels draw as SVG text, which inherits no typography from
|
|
13
|
+
the shared chart classes — pin them to the family's primary face. */
|
|
14
14
|
|
|
15
|
-
.ds-funnel-
|
|
16
|
-
|
|
17
|
-
align-items: center;
|
|
18
|
-
gap: var(--gap-xxs);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/* Parts */
|
|
22
|
-
|
|
23
|
-
.ds-funnel-chart__stage {
|
|
24
|
-
flex: 1;
|
|
25
|
-
min-width: 0;
|
|
26
|
-
height: calc(var(--funnel-stage-size) * 1%);
|
|
27
|
-
border-radius: var(--radius-sm);
|
|
28
|
-
background-color: var(--funnel-stage-color);
|
|
29
|
-
display: grid;
|
|
30
|
-
place-items: center;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.ds-funnel-chart__pct {
|
|
34
|
-
background-color: var(--color-bg-container-primary);
|
|
35
|
-
color: var(--color-text-primary);
|
|
36
|
-
border-radius: var(--radius-full);
|
|
37
|
-
padding: var(--padding-xxxs) var(--padding-xs);
|
|
38
|
-
font-family: var(--font-caption-family);
|
|
39
|
-
font-size: var(--font-caption-size);
|
|
40
|
-
font-weight: var(--font-paragraph-sm-em-weight);
|
|
41
|
-
line-height: var(--font-caption-line-height);
|
|
15
|
+
.ds-funnel-chart .recharts-label-list text {
|
|
16
|
+
font-family: var(--font-family-primary);
|
|
42
17
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
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
18
|
/** Chart title, in the shared chart header. */
|
|
19
19
|
title?: string;
|
|
@@ -23,9 +23,12 @@ type FunnelChartOwnProps = {
|
|
|
23
23
|
bare?: boolean;
|
|
24
24
|
/** Chart area height in pixels. */
|
|
25
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;
|
|
26
28
|
/**
|
|
27
|
-
* Floor percentage
|
|
28
|
-
*
|
|
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.
|
|
29
32
|
*/
|
|
30
33
|
minStageShare?: number;
|
|
31
34
|
/** Additional CSS classes */
|
|
@@ -34,15 +37,15 @@ type FunnelChartOwnProps = {
|
|
|
34
37
|
export interface FunnelChartProps extends FunnelChartOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof FunnelChartOwnProps | 'children'> {
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
37
|
-
* FunnelChart — ordered stages as
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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.
|
|
46
49
|
*/
|
|
47
50
|
export declare const FunnelChart: React.ForwardRefExoticComponent<FunnelChartProps & React.RefAttributes<HTMLDivElement>>;
|
|
48
51
|
export {};
|
|
@@ -1,8 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import React from "react";
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
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";
|
|
3
5
|
import "../Chart/Chart.css";
|
|
4
6
|
import "./FunnelChart.css";
|
|
5
|
-
|
|
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
|
+
}
|
|
6
29
|
const FunnelChart = React.forwardRef(
|
|
7
30
|
({
|
|
8
31
|
data,
|
|
@@ -10,55 +33,60 @@ const FunnelChart = React.forwardRef(
|
|
|
10
33
|
subtitle,
|
|
11
34
|
bare = false,
|
|
12
35
|
height = 190,
|
|
13
|
-
|
|
36
|
+
showLabels = true,
|
|
37
|
+
minStageShare,
|
|
14
38
|
className = "",
|
|
15
39
|
...rest
|
|
16
40
|
}, ref) => {
|
|
17
41
|
const baseClass = "ds-funnel-chart";
|
|
18
42
|
const chartClass = "ds-chart";
|
|
19
|
-
const classes = [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
className
|
|
24
|
-
].filter(Boolean).join(" ");
|
|
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");
|
|
25
47
|
const firstValue = data.length > 0 ? data[0].value : 0;
|
|
26
|
-
const clampShare = (share) => Math.min(Math.max(share, minStageShare), 100);
|
|
27
48
|
const stages = data.map((stage, i) => ({
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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]
|
|
31
54
|
}));
|
|
32
|
-
const ariaLabel = stages.length > 0 ? `Funnel: ${stages.map((s) => `${s.
|
|
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
|
+
[]
|
|
60
|
+
);
|
|
33
61
|
return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
|
|
34
62
|
(title || subtitle) && /* @__PURE__ */ jsx("div", { className: `${chartClass}__header`, children: /* @__PURE__ */ jsxs("div", { className: `${chartClass}__header-text`, children: [
|
|
35
63
|
title && /* @__PURE__ */ jsx("h3", { className: `${chartClass}__title`, children: title }),
|
|
36
64
|
subtitle && /* @__PURE__ */ jsx("p", { className: `${chartClass}__subtitle`, children: subtitle })
|
|
37
65
|
] }) }),
|
|
38
|
-
/* @__PURE__ */ jsx(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
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
|
+
] }) }) })
|
|
62
90
|
] });
|
|
63
91
|
}
|
|
64
92
|
);
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
.ds-gauge__track {
|
|
41
41
|
fill: none;
|
|
42
|
-
stroke: var(--color-bg-container-
|
|
42
|
+
stroke: var(--color-bg-container-tertiary);
|
|
43
43
|
stroke-linecap: round;
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -48,6 +48,16 @@
|
|
|
48
48
|
stroke: currentColor;
|
|
49
49
|
stroke-linecap: round;
|
|
50
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
|
+
}
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
.ds-gauge__center {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
width: 100%;
|
|
48
48
|
height: 8px;
|
|
49
49
|
border-radius: var(--radius-full);
|
|
50
|
-
background-color: var(--color-bg-container-
|
|
50
|
+
background-color: var(--color-bg-container-tertiary);
|
|
51
51
|
overflow: hidden;
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -59,6 +59,15 @@
|
|
|
59
59
|
height: 100%;
|
|
60
60
|
border-radius: var(--radius-full);
|
|
61
61
|
transition: width var(--motion-duration-base) var(--motion-ease-standard);
|
|
62
|
+
/* Grows to the current level on mount — the single keyframe animates to
|
|
63
|
+
the inline width, and the duration token collapses under reduced motion. */
|
|
64
|
+
animation: ds-meter-grow var(--motion-duration-slower) var(--motion-ease-emphasized);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@keyframes ds-meter-grow {
|
|
68
|
+
from {
|
|
69
|
+
width: 0;
|
|
70
|
+
}
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
/* ============================================
|
|
@@ -92,6 +92,29 @@
|
|
|
92
92
|
background-color: var(--color-action-primary-bg-hover);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/* ============================================
|
|
96
|
+
NEUTRAL VARIANT
|
|
97
|
+
The active pill fills grey instead of the
|
|
98
|
+
action teal — for sets where the selection
|
|
99
|
+
should not carry the action colour's weight.
|
|
100
|
+
============================================ */
|
|
101
|
+
|
|
102
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active {
|
|
103
|
+
background-color: var(--color-action-neutral-bg);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active .ds-segmented-control__label {
|
|
107
|
+
color: var(--color-action-neutral-text);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active .ds-segmented-control__icon {
|
|
111
|
+
color: var(--color-action-neutral-text);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active:hover {
|
|
115
|
+
background-color: var(--color-action-neutral-bg-hover);
|
|
116
|
+
}
|
|
117
|
+
|
|
95
118
|
/* ============================================
|
|
96
119
|
DISABLED STATE
|
|
97
120
|
============================================ */
|
|
@@ -18,6 +18,8 @@ export interface SegmentedControlProps {
|
|
|
18
18
|
onSegmentChange?: (value: string) => void;
|
|
19
19
|
/** Component size */
|
|
20
20
|
size?: 'default' | 'compact';
|
|
21
|
+
/** Visual treatment of the active segment — teal by default, `neutral` fills it grey */
|
|
22
|
+
variant?: 'primary' | 'neutral';
|
|
21
23
|
/** Full width — segments fill container */
|
|
22
24
|
fullWidth?: boolean;
|
|
23
25
|
/** Accessible label for the tablist */
|
|
@@ -30,4 +32,4 @@ export interface SegmentedControlProps {
|
|
|
30
32
|
* A horizontal set of mutually exclusive options with a
|
|
31
33
|
* pill-shaped active indicator. Supports icons and keyboard navigation.
|
|
32
34
|
*/
|
|
33
|
-
export declare const SegmentedControl: ({ segments, activeSegment, onSegmentChange, size, fullWidth, ariaLabel, className, }: SegmentedControlProps) => React.JSX.Element;
|
|
35
|
+
export declare const SegmentedControl: ({ segments, activeSegment, onSegmentChange, size, variant, fullWidth, ariaLabel, className, }: SegmentedControlProps) => React.JSX.Element;
|
|
@@ -8,15 +8,17 @@ const SegmentedControl = ({
|
|
|
8
8
|
activeSegment,
|
|
9
9
|
onSegmentChange,
|
|
10
10
|
size = "default",
|
|
11
|
+
variant = "primary",
|
|
11
12
|
fullWidth = false,
|
|
12
13
|
ariaLabel,
|
|
13
14
|
className = ""
|
|
14
15
|
}) => {
|
|
15
16
|
const baseClass = "ds-segmented-control";
|
|
16
17
|
const sizeClass = `${baseClass}--${size}`;
|
|
18
|
+
const variantClass = `${baseClass}--${variant}`;
|
|
17
19
|
const fullWidthClass = fullWidth ? `${baseClass}--full-width` : "";
|
|
18
20
|
const containerRef = useRef(null);
|
|
19
|
-
const classes = [baseClass, sizeClass, fullWidthClass, className].filter(Boolean).join(" ");
|
|
21
|
+
const classes = [baseClass, sizeClass, variantClass, fullWidthClass, className].filter(Boolean).join(" ");
|
|
20
22
|
const handleKeyDown = (e, idx) => {
|
|
21
23
|
const enabledSegments = segments.filter((s) => !s.disabled);
|
|
22
24
|
const currentEnabledIdx = enabledSegments.findIndex((s) => s.value === segments[idx].value);
|
|
@@ -44,7 +44,7 @@ const Slider = React.forwardRef(
|
|
|
44
44
|
onChange: handleChange,
|
|
45
45
|
"aria-label": ariaLabel || rest["aria-label"],
|
|
46
46
|
style: {
|
|
47
|
-
background: `linear-gradient(to right, var(--color-action-primary-bg) ${percentage}%, var(--color-bg-container-
|
|
47
|
+
background: `linear-gradient(to right, var(--color-action-primary-bg) ${percentage}%, var(--color-bg-container-tertiary) ${percentage}%)`,
|
|
48
48
|
...style
|
|
49
49
|
}
|
|
50
50
|
}
|