@robr0/design-system 0.14.0 → 0.16.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 +11 -8
- package/behaviors/focusable.d.ts +26 -0
- package/behaviors/focusable.js +18 -0
- package/behaviors/useFocusScope.d.ts +38 -0
- package/behaviors/useFocusScope.js +82 -0
- package/behaviors/useLayer.d.ts +43 -0
- package/behaviors/useLayer.js +55 -0
- package/behaviors/useMounted.d.ts +7 -0
- package/behaviors/useMounted.js +14 -0
- package/behaviors/useScrollLock.d.ts +55 -0
- package/behaviors/useScrollLock.js +59 -0
- package/charts.d.ts +1 -0
- package/charts.js +2 -0
- package/components/AgentPlan/AgentPlan.css +12 -3
- package/components/AiButton/AiButton.css +327 -0
- package/components/AiButton/AiButton.d.ts +42 -0
- package/components/AiButton/AiButton.js +140 -16
- package/components/AlertDialog/AlertDialog.js +13 -58
- 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 +54 -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/Combobox/Combobox.js +2 -1
- package/components/CommandPalette/CommandPalette.css +36 -8
- package/components/CommandPalette/CommandPalette.js +9 -54
- package/components/Composer/Composer.css +72 -0
- package/components/Composer/Composer.d.ts +25 -6
- package/components/Composer/Composer.js +8 -0
- package/components/ContributionGraph/ContributionGraph.css +21 -0
- package/components/ContributionGraph/ContributionGraph.js +18 -10
- package/components/Dialog/Dialog.js +9 -62
- package/components/Drawer/Drawer.js +9 -62
- package/components/Dropdown/Dropdown.js +2 -1
- package/components/DropdownMenu/DropdownMenu.js +11 -3
- 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/ModelPicker/ModelPicker.js +2 -1
- package/components/Popover/Popover.js +3 -0
- package/components/ProgressBar/ProgressBar.css +1 -1
- package/components/SegmentedControl/SegmentedControl.css +27 -2
- 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/TimePicker/TimePicker.js +2 -1
- 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 +16 -3
- package/components/Tooltip/Tooltip.css +24 -7
- package/components/Tooltip/Tooltip.d.ts +2 -1
- package/components/Tooltip/Tooltip.js +6 -0
- 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 +43 -16
- package/components/registry.json.d.ts +43 -16
- package/components/registry.json.js +1 -1
- package/index.d.ts +2 -1
- package/index.js +4 -2
- package/package.json +5 -1
- package/tokens/motion.d.ts +10 -4
- package/tokens/motion.js +7 -1
- package/tokens/registry.json +10 -0
- package/tokens/registry.json.d.ts +10 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +22 -2
- package/tokens/tokens-light.css +38 -11
- package/tokens/tokens-primitives.css +3 -0
|
@@ -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
|
/* ============================================
|
|
@@ -60,9 +60,10 @@ const ModelPicker = React.forwardRef(
|
|
|
60
60
|
}
|
|
61
61
|
}, []);
|
|
62
62
|
useEffect(() => {
|
|
63
|
+
if (!isOpen) return;
|
|
63
64
|
document.addEventListener("mousedown", handleClickOutside);
|
|
64
65
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
65
|
-
}, [handleClickOutside]);
|
|
66
|
+
}, [isOpen, handleClickOutside]);
|
|
66
67
|
const handleTriggerKeyDown = (e) => {
|
|
67
68
|
if (disabled) return;
|
|
68
69
|
switch (e.key) {
|
|
@@ -27,6 +27,9 @@ const Popover = ({
|
|
|
27
27
|
}
|
|
28
28
|
onOpenChange?.(next);
|
|
29
29
|
}, [isControlled, onOpenChange]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
return () => clearTimeout(hoverTimeoutRef.current);
|
|
32
|
+
}, []);
|
|
30
33
|
useEffect(() => {
|
|
31
34
|
if (!isOpen || trigger !== "click") return;
|
|
32
35
|
const handler = (e) => {
|
|
@@ -55,12 +55,14 @@
|
|
|
55
55
|
transition: color var(--motion-duration-base) var(--motion-ease-standard);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/* Icon
|
|
58
|
+
/* Icon — same idle tier as the label, so an icon never reads dimmer than
|
|
59
|
+
the text beside it, and an icon-only segment (the site's theme toggle)
|
|
60
|
+
holds label-strength contrast in both modes. */
|
|
59
61
|
.ds-segmented-control__icon {
|
|
60
62
|
flex-shrink: 0;
|
|
61
63
|
--icon-size: var(--icon-size-md);
|
|
62
64
|
line-height: 1;
|
|
63
|
-
color: var(--color-
|
|
65
|
+
color: var(--color-text-secondary);
|
|
64
66
|
transition: color var(--motion-duration-base) var(--motion-ease-standard);
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -92,6 +94,29 @@
|
|
|
92
94
|
background-color: var(--color-action-primary-bg-hover);
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
/* ============================================
|
|
98
|
+
NEUTRAL VARIANT
|
|
99
|
+
The active pill fills grey instead of the
|
|
100
|
+
action teal — for sets where the selection
|
|
101
|
+
should not carry the action colour's weight.
|
|
102
|
+
============================================ */
|
|
103
|
+
|
|
104
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active {
|
|
105
|
+
background-color: var(--color-action-neutral-bg);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active .ds-segmented-control__label {
|
|
109
|
+
color: var(--color-action-neutral-text);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active .ds-segmented-control__icon {
|
|
113
|
+
color: var(--color-action-neutral-text);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.ds-segmented-control--neutral .ds-segmented-control__segment--active:hover {
|
|
117
|
+
background-color: var(--color-action-neutral-bg-hover);
|
|
118
|
+
}
|
|
119
|
+
|
|
95
120
|
/* ============================================
|
|
96
121
|
DISABLED STATE
|
|
97
122
|
============================================ */
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
SOURCE TRAIL COMPONENT
|
|
3
|
+
The research trail under an agent's answer:
|
|
4
|
+
a count header with a disclosure over the
|
|
5
|
+
sources it opened, in order.
|
|
6
|
+
|
|
7
|
+
Shares the chat furniture's 24px shell with
|
|
8
|
+
ToolCall — a collapsed trail reads as one
|
|
9
|
+
more row in the run, not a different species.
|
|
10
|
+
============================================ */
|
|
11
|
+
|
|
12
|
+
.ds-source-trail {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
width: 100%;
|
|
16
|
+
background-color: var(--color-bg-container-primary);
|
|
17
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
18
|
+
border-radius: var(--radius-xl);
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ============================================
|
|
23
|
+
HEADER
|
|
24
|
+
============================================ */
|
|
25
|
+
|
|
26
|
+
.ds-source-trail__header {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: var(--gap-sm);
|
|
30
|
+
width: 100%;
|
|
31
|
+
padding: var(--padding-sm) var(--padding-lg);
|
|
32
|
+
background: none;
|
|
33
|
+
border: none;
|
|
34
|
+
/* Buttons default to the UA's buttontext, not the inherited colour, so
|
|
35
|
+
anything in the header that inherits would read black in both themes. */
|
|
36
|
+
color: var(--color-text-primary);
|
|
37
|
+
text-align: left;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.ds-source-trail__header:hover {
|
|
43
|
+
background-color: var(--color-bg-container-secondary);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.ds-source-trail__header:focus-visible {
|
|
47
|
+
outline: 2px solid var(--color-action-primary-bg);
|
|
48
|
+
outline-offset: -2px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ds-source-trail__indicator {
|
|
52
|
+
display: inline-flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
flex: none;
|
|
56
|
+
color: var(--color-text-secondary);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ds-source-trail__indicator .material-symbols-rounded {
|
|
60
|
+
--icon-size: var(--icon-size-sm);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ds-source-trail__heading {
|
|
64
|
+
flex: 1 1 auto;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
text-overflow: ellipsis;
|
|
68
|
+
white-space: nowrap;
|
|
69
|
+
color: var(--color-text-primary);
|
|
70
|
+
font-family: var(--font-paragraph-sm-em-family);
|
|
71
|
+
font-size: var(--font-paragraph-sm-em-size);
|
|
72
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
73
|
+
line-height: var(--font-paragraph-sm-em-line-height);
|
|
74
|
+
letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ds-source-trail__chevron {
|
|
78
|
+
--icon-size: var(--icon-size-sm);
|
|
79
|
+
|
|
80
|
+
flex: none;
|
|
81
|
+
color: var(--color-icon-secondary);
|
|
82
|
+
transition: transform var(--motion-duration-slow) var(--motion-ease-standard);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.ds-source-trail--open .ds-source-trail__chevron {
|
|
86
|
+
transform: rotate(180deg);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* ============================================
|
|
90
|
+
PANEL
|
|
91
|
+
============================================ */
|
|
92
|
+
|
|
93
|
+
.ds-source-trail__panel {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-rows: 0fr;
|
|
96
|
+
visibility: hidden;
|
|
97
|
+
transition:
|
|
98
|
+
grid-template-rows var(--motion-duration-slow) var(--motion-ease-standard),
|
|
99
|
+
visibility var(--motion-duration-slow) var(--motion-ease-standard);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ds-source-trail--open .ds-source-trail__panel {
|
|
103
|
+
grid-template-rows: 1fr;
|
|
104
|
+
visibility: visible;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.ds-source-trail__list {
|
|
108
|
+
overflow: hidden;
|
|
109
|
+
min-height: 0;
|
|
110
|
+
margin: 0;
|
|
111
|
+
padding: 0;
|
|
112
|
+
list-style: none;
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.ds-source-trail--open .ds-source-trail__list {
|
|
118
|
+
padding: var(--padding-sm) var(--padding-lg) var(--padding-md);
|
|
119
|
+
border-top: var(--border-xs) solid var(--color-divider);
|
|
120
|
+
gap: var(--gap-sm);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ============================================
|
|
124
|
+
ROWS
|
|
125
|
+
============================================ */
|
|
126
|
+
|
|
127
|
+
.ds-source-trail__row {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: var(--gap-sm);
|
|
131
|
+
min-width: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Streaming: an appended row eases in. Pure CSS, so the global
|
|
135
|
+
reduced-motion guard collapses the duration tokens. */
|
|
136
|
+
.ds-source-trail--streaming .ds-source-trail__row {
|
|
137
|
+
animation: ds-source-trail-enter var(--motion-duration-slow) var(--motion-ease-standard);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@keyframes ds-source-trail-enter {
|
|
141
|
+
from {
|
|
142
|
+
opacity: 0;
|
|
143
|
+
transform: translateY(4px);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.ds-source-trail__row-indicator {
|
|
148
|
+
display: inline-flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
flex: none;
|
|
152
|
+
color: var(--color-text-secondary);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.ds-source-trail__row-indicator .material-symbols-rounded {
|
|
156
|
+
--icon-size: var(--icon-size-sm);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.ds-source-trail__row--pending .ds-source-trail__row-indicator {
|
|
160
|
+
color: var(--color-text-tertiary);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.ds-source-trail__row--active .ds-source-trail__row-indicator {
|
|
164
|
+
color: var(--color-status-info-icon);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.ds-source-trail__row--done .ds-source-trail__row-indicator {
|
|
168
|
+
color: var(--color-status-positive-icon);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* A pending source has not been opened yet, so its chip sits quieter */
|
|
172
|
+
.ds-source-trail__row--pending {
|
|
173
|
+
opacity: 0.6;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.ds-source-trail__detail {
|
|
177
|
+
flex: 1 1 auto;
|
|
178
|
+
min-width: 0;
|
|
179
|
+
overflow: hidden;
|
|
180
|
+
text-overflow: ellipsis;
|
|
181
|
+
white-space: nowrap;
|
|
182
|
+
color: var(--color-text-tertiary);
|
|
183
|
+
font-family: var(--font-paragraph-sm-family);
|
|
184
|
+
font-size: var(--font-paragraph-sm-size);
|
|
185
|
+
font-weight: var(--font-paragraph-sm-weight);
|
|
186
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
187
|
+
letter-spacing: var(--font-paragraph-sm-letter-spacing);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Screen-reader-only status text, matching AgentPlan's row announcement */
|
|
191
|
+
.ds-source-trail__sr {
|
|
192
|
+
position: absolute;
|
|
193
|
+
width: 1px;
|
|
194
|
+
height: 1px;
|
|
195
|
+
padding: 0;
|
|
196
|
+
margin: -1px;
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
clip: rect(0, 0, 0, 0);
|
|
199
|
+
white-space: nowrap;
|
|
200
|
+
border: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Icons follow the state colour set on their parents, even against a
|
|
204
|
+
consumer stylesheet that colours .material-symbols-rounded globally.
|
|
205
|
+
The chevron is excluded — it carries its own colour rule, which this
|
|
206
|
+
higher-specificity rule would otherwise silently override. */
|
|
207
|
+
.ds-source-trail .material-symbols-rounded:not(.ds-source-trail__chevron) {
|
|
208
|
+
color: inherit;
|
|
209
|
+
}
|