@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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export type SourceTrailStatus = 'pending' | 'active' | 'done';
|
|
3
|
+
/** One source the agent opened while answering. */
|
|
4
|
+
export interface SourceTrailItem {
|
|
5
|
+
/** The source name, passed through to the row's SourceChip. */
|
|
6
|
+
title: string;
|
|
7
|
+
/** Optional link to the source — the chip renders as an anchor. */
|
|
8
|
+
href?: string;
|
|
9
|
+
/** Leading icon for the chip — a Material Symbol name (string) or custom element (ReactNode). */
|
|
10
|
+
icon?: string | React.ReactNode;
|
|
11
|
+
/** Where the visit has got to. Drives the row's indicator; defaults to done. */
|
|
12
|
+
status?: SourceTrailStatus;
|
|
13
|
+
/** Supporting line, e.g. what the source contributed to the answer. */
|
|
14
|
+
detail?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Props owned by SourceTrail itself — everything else falls through to the root element. */
|
|
17
|
+
type SourceTrailOwnProps = {
|
|
18
|
+
/** The sources, in the order the agent opened them. */
|
|
19
|
+
items: SourceTrailItem[];
|
|
20
|
+
/**
|
|
21
|
+
* Header text. Left unset, it is computed from the items —
|
|
22
|
+
* "Reading 3 sources" while any is still pending or active,
|
|
23
|
+
* "Read 3 sources" once every visit has settled.
|
|
24
|
+
*/
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Still researching: newly appended rows animate in. */
|
|
27
|
+
streaming?: boolean;
|
|
28
|
+
/** Open state for controlled use. Pair with `onOpenChange`. */
|
|
29
|
+
open?: boolean;
|
|
30
|
+
/** Open state for uncontrolled use. */
|
|
31
|
+
defaultOpen?: boolean;
|
|
32
|
+
/** Fires whenever the panel opens or closes. */
|
|
33
|
+
onOpenChange?: (open: boolean) => void;
|
|
34
|
+
/** Additional CSS classes */
|
|
35
|
+
className?: string;
|
|
36
|
+
};
|
|
37
|
+
export interface SourceTrailProps extends SourceTrailOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof SourceTrailOwnProps> {
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* SourceTrail is the research trail under an agent's answer: the sources it
|
|
41
|
+
* opened, in order, each with a status and an optional note on what it
|
|
42
|
+
* contributed. SourceChip is the atom; this is the list — a count header
|
|
43
|
+
* with a disclosure, so a long trail collapses to one line.
|
|
44
|
+
*
|
|
45
|
+
* While `streaming`, appended rows animate in (pure CSS, so the global
|
|
46
|
+
* reduced-motion guard covers it) and the active row carries a spinner.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SourceTrail: React.ForwardRefExoticComponent<SourceTrailProps & React.RefAttributes<HTMLDivElement>>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import React, { useId, useState, useCallback } from "react";
|
|
4
|
+
import { SourceChip } from "../SourceChip/SourceChip.js";
|
|
5
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
6
|
+
import "./SourceTrail.css";
|
|
7
|
+
import "../../fonts/material-symbols.css";
|
|
8
|
+
const STATUS_ICONS = {
|
|
9
|
+
pending: "circle",
|
|
10
|
+
done: "check_circle"
|
|
11
|
+
};
|
|
12
|
+
const STATUS_TEXT = {
|
|
13
|
+
pending: "Not opened yet",
|
|
14
|
+
active: "Reading now",
|
|
15
|
+
done: "Read"
|
|
16
|
+
};
|
|
17
|
+
const SourceTrail = React.forwardRef(
|
|
18
|
+
({
|
|
19
|
+
items,
|
|
20
|
+
title,
|
|
21
|
+
streaming = false,
|
|
22
|
+
open,
|
|
23
|
+
defaultOpen = true,
|
|
24
|
+
onOpenChange,
|
|
25
|
+
className = "",
|
|
26
|
+
...rest
|
|
27
|
+
}, ref) => {
|
|
28
|
+
const baseClass = "ds-source-trail";
|
|
29
|
+
const id = useId();
|
|
30
|
+
const panelId = `${id}-panel`;
|
|
31
|
+
const triggerId = `${id}-trigger`;
|
|
32
|
+
const isControlled = open !== void 0;
|
|
33
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
|
34
|
+
const isOpen = isControlled ? open : uncontrolledOpen;
|
|
35
|
+
const toggle = useCallback(() => {
|
|
36
|
+
const next = !isOpen;
|
|
37
|
+
if (!isControlled) setUncontrolledOpen(next);
|
|
38
|
+
onOpenChange?.(next);
|
|
39
|
+
}, [isOpen, isControlled, onOpenChange]);
|
|
40
|
+
const unsettled = items.some(
|
|
41
|
+
(item) => item.status === "pending" || item.status === "active"
|
|
42
|
+
);
|
|
43
|
+
const count = items.length;
|
|
44
|
+
const heading = title ?? (unsettled || streaming ? `Reading ${count} source${count === 1 ? "" : "s"}` : `Read ${count} source${count === 1 ? "" : "s"}`);
|
|
45
|
+
const classes = [
|
|
46
|
+
baseClass,
|
|
47
|
+
isOpen ? `${baseClass}--open` : "",
|
|
48
|
+
streaming ? `${baseClass}--streaming` : "",
|
|
49
|
+
className
|
|
50
|
+
].filter(Boolean).join(" ");
|
|
51
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
|
|
52
|
+
/* @__PURE__ */ jsxs(
|
|
53
|
+
"button",
|
|
54
|
+
{
|
|
55
|
+
type: "button",
|
|
56
|
+
id: triggerId,
|
|
57
|
+
className: `${baseClass}__header`,
|
|
58
|
+
"aria-expanded": isOpen,
|
|
59
|
+
"aria-controls": panelId,
|
|
60
|
+
onClick: toggle,
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__indicator`, "aria-hidden": "true", children: unsettled || streaming ? /* @__PURE__ */ jsx(Spinner, { size: "sm", variant: "inherit" }) : /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: "travel_explore" }) }),
|
|
63
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__heading`, children: heading }),
|
|
64
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__chevron material-symbols-rounded`, "aria-hidden": "true", children: "expand_more" })
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
className: `${baseClass}__panel`,
|
|
72
|
+
id: panelId,
|
|
73
|
+
role: "region",
|
|
74
|
+
"aria-labelledby": triggerId,
|
|
75
|
+
children: /* @__PURE__ */ jsx("ol", { className: `${baseClass}__list`, children: items.map((item, index) => {
|
|
76
|
+
const status = item.status ?? "done";
|
|
77
|
+
return /* @__PURE__ */ jsxs(
|
|
78
|
+
"li",
|
|
79
|
+
{
|
|
80
|
+
className: `${baseClass}__row ${baseClass}__row--${status}`,
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__row-indicator`, "aria-hidden": "true", children: status === "active" ? /* @__PURE__ */ jsx(Spinner, { size: "sm", variant: "inherit" }) : /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: STATUS_ICONS[status] }) }),
|
|
83
|
+
/* @__PURE__ */ jsx(
|
|
84
|
+
SourceChip,
|
|
85
|
+
{
|
|
86
|
+
title: item.title,
|
|
87
|
+
href: item.href,
|
|
88
|
+
icon: item.icon,
|
|
89
|
+
target: item.href ? "_blank" : void 0,
|
|
90
|
+
rel: item.href ? "noopener noreferrer" : void 0
|
|
91
|
+
}
|
|
92
|
+
),
|
|
93
|
+
item.detail && /* @__PURE__ */ jsx("span", { className: `${baseClass}__detail`, children: item.detail }),
|
|
94
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__sr`, children: STATUS_TEXT[status] })
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
`${item.title}-${index}`
|
|
98
|
+
);
|
|
99
|
+
}) })
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
] });
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
SourceTrail.displayName = "SourceTrail";
|
|
106
|
+
export {
|
|
107
|
+
SourceTrail
|
|
108
|
+
};
|
|
@@ -17,20 +17,44 @@
|
|
|
17
17
|
|
|
18
18
|
/* Parts */
|
|
19
19
|
|
|
20
|
+
/* The line draws itself on mount, the way the recharts lines do: the path is
|
|
21
|
+
normalised to 100 dash units (pathLength on the SVG side), and the offset
|
|
22
|
+
retreats from fully hidden to zero. The fill and end dot fade in behind
|
|
23
|
+
and after it. All durations are tokens, so reduced motion collapses the
|
|
24
|
+
whole sequence to the finished state for free. */
|
|
25
|
+
|
|
20
26
|
.ds-sparkline__line {
|
|
21
27
|
fill: none;
|
|
22
28
|
stroke: currentColor;
|
|
23
29
|
stroke-linecap: round;
|
|
24
30
|
stroke-linejoin: round;
|
|
31
|
+
stroke-dasharray: 100;
|
|
32
|
+
animation: ds-sparkline-draw var(--motion-duration-slower) var(--motion-ease-emphasized);
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
.ds-sparkline__fill {
|
|
28
36
|
fill: currentColor;
|
|
29
37
|
opacity: 0.15;
|
|
38
|
+
animation: ds-sparkline-fade var(--motion-duration-slower) var(--motion-ease-standard);
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
.ds-sparkline__dot {
|
|
33
42
|
fill: currentColor;
|
|
43
|
+
/* Waits out the draw, then pops in where the line ends. */
|
|
44
|
+
animation: ds-sparkline-fade var(--motion-duration-slow) var(--motion-ease-standard)
|
|
45
|
+
var(--motion-duration-slower) backwards;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@keyframes ds-sparkline-draw {
|
|
49
|
+
from {
|
|
50
|
+
stroke-dashoffset: 100;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@keyframes ds-sparkline-fade {
|
|
55
|
+
from {
|
|
56
|
+
opacity: 0;
|
|
57
|
+
}
|
|
34
58
|
}
|
|
35
59
|
|
|
36
60
|
/* Tones */
|
|
@@ -40,11 +64,11 @@
|
|
|
40
64
|
}
|
|
41
65
|
|
|
42
66
|
.ds-sparkline--positive {
|
|
43
|
-
color: var(--color-
|
|
67
|
+
color: var(--color-trend-up);
|
|
44
68
|
}
|
|
45
69
|
|
|
46
70
|
.ds-sparkline--negative {
|
|
47
|
-
color: var(--color-
|
|
71
|
+
color: var(--color-trend-down);
|
|
48
72
|
}
|
|
49
73
|
|
|
50
74
|
.ds-sparkline--neutral {
|
|
@@ -10,7 +10,7 @@ type SplitButtonOwnProps = {
|
|
|
10
10
|
/** Menu entries for the alternative actions */
|
|
11
11
|
items: DropdownMenuEntry[];
|
|
12
12
|
/** Visual treatment, shared by both segments */
|
|
13
|
-
variant?: 'primary' | 'secondary';
|
|
13
|
+
variant?: 'primary' | 'secondary' | 'neutral';
|
|
14
14
|
/** Component size */
|
|
15
15
|
size?: 'default' | 'compact';
|
|
16
16
|
/** Disables both segments */
|
|
@@ -101,9 +101,10 @@ const TimePicker = React.forwardRef(
|
|
|
101
101
|
}
|
|
102
102
|
}, []);
|
|
103
103
|
useEffect(() => {
|
|
104
|
+
if (!isOpen) return;
|
|
104
105
|
document.addEventListener("mousedown", handleClickOutside);
|
|
105
106
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
106
|
-
}, [handleClickOutside]);
|
|
107
|
+
}, [isOpen, handleClickOutside]);
|
|
107
108
|
const handleKeyDown = (e) => {
|
|
108
109
|
if (disabled) return;
|
|
109
110
|
switch (e.key) {
|
|
@@ -62,6 +62,23 @@
|
|
|
62
62
|
color: var(--color-action-primary-text);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/* ============================================
|
|
66
|
+
NEUTRAL VARIANT
|
|
67
|
+
Active items fill grey instead of the action
|
|
68
|
+
teal — for sets where the selection should
|
|
69
|
+
not carry the action colour's weight.
|
|
70
|
+
============================================ */
|
|
71
|
+
|
|
72
|
+
.ds-toggle-group--neutral .ds-toggle-group__item--active {
|
|
73
|
+
background-color: var(--color-action-neutral-bg);
|
|
74
|
+
color: var(--color-action-neutral-text);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.ds-toggle-group--neutral .ds-toggle-group__item--active:hover {
|
|
78
|
+
background-color: var(--color-action-neutral-bg-hover);
|
|
79
|
+
color: var(--color-action-neutral-text);
|
|
80
|
+
}
|
|
81
|
+
|
|
65
82
|
/* ============================================
|
|
66
83
|
COMPACT
|
|
67
84
|
============================================ */
|
|
@@ -17,6 +17,8 @@ type ToggleGroupOwnProps = {
|
|
|
17
17
|
multiple?: boolean;
|
|
18
18
|
/** Component size */
|
|
19
19
|
size?: 'default' | 'compact';
|
|
20
|
+
/** Visual treatment of active items — teal by default, `neutral` fills them grey */
|
|
21
|
+
variant?: 'primary' | 'neutral';
|
|
20
22
|
/** Whether the whole group is disabled */
|
|
21
23
|
disabled?: boolean;
|
|
22
24
|
/** Called with the next selection — a string when single, an array when `multiple` */
|
|
@@ -8,6 +8,7 @@ const ToggleGroup = React.forwardRef(
|
|
|
8
8
|
value = [],
|
|
9
9
|
multiple = false,
|
|
10
10
|
size = "default",
|
|
11
|
+
variant = "primary",
|
|
11
12
|
disabled = false,
|
|
12
13
|
onValueChange,
|
|
13
14
|
onChange,
|
|
@@ -19,6 +20,7 @@ const ToggleGroup = React.forwardRef(
|
|
|
19
20
|
const classes = [
|
|
20
21
|
baseClass,
|
|
21
22
|
`${baseClass}--${size}`,
|
|
23
|
+
`${baseClass}--${variant}`,
|
|
22
24
|
disabled ? `${baseClass}--disabled` : "",
|
|
23
25
|
className
|
|
24
26
|
].filter(Boolean).join(" ");
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
============================================ */
|
|
14
14
|
|
|
15
15
|
.ds-tool-call {
|
|
16
|
+
/* Two colour channels per status: -text for the status word (tuned for
|
|
17
|
+
text contrast) and -icon for the indicator glyph, which needs a hue
|
|
18
|
+
that survives a plain surface — see the status token comment. */
|
|
16
19
|
--ds-tool-call-color: var(--color-text-secondary);
|
|
20
|
+
--ds-tool-call-icon-color: var(--color-icon-secondary);
|
|
17
21
|
|
|
18
22
|
display: flex;
|
|
19
23
|
flex-direction: column;
|
|
@@ -40,6 +44,9 @@
|
|
|
40
44
|
padding: var(--padding-sm) var(--padding-lg);
|
|
41
45
|
background: none;
|
|
42
46
|
border: none;
|
|
47
|
+
/* Buttons default to the UA's buttontext, not the inherited colour, so
|
|
48
|
+
anything in the header that inherits would read black in both themes. */
|
|
49
|
+
color: var(--color-text-primary);
|
|
43
50
|
text-align: left;
|
|
44
51
|
cursor: pointer;
|
|
45
52
|
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
@@ -68,7 +75,7 @@
|
|
|
68
75
|
align-items: center;
|
|
69
76
|
justify-content: center;
|
|
70
77
|
flex: none;
|
|
71
|
-
color: var(--ds-tool-call-color);
|
|
78
|
+
color: var(--ds-tool-call-icon-color);
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
.ds-tool-call__indicator .material-symbols-rounded {
|
|
@@ -147,18 +154,22 @@
|
|
|
147
154
|
|
|
148
155
|
.ds-tool-call--pending {
|
|
149
156
|
--ds-tool-call-color: var(--color-status-warning-text);
|
|
157
|
+
--ds-tool-call-icon-color: var(--color-status-warning-icon);
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
.ds-tool-call--running {
|
|
153
161
|
--ds-tool-call-color: var(--color-status-info-text);
|
|
162
|
+
--ds-tool-call-icon-color: var(--color-status-info-icon);
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
.ds-tool-call--success {
|
|
157
166
|
--ds-tool-call-color: var(--color-status-positive-text);
|
|
167
|
+
--ds-tool-call-icon-color: var(--color-status-positive-icon);
|
|
158
168
|
}
|
|
159
169
|
|
|
160
170
|
.ds-tool-call--error {
|
|
161
171
|
--ds-tool-call-color: var(--color-status-error-text);
|
|
172
|
+
--ds-tool-call-icon-color: var(--color-status-error-icon);
|
|
162
173
|
}
|
|
163
174
|
|
|
164
175
|
/* ============================================
|
|
@@ -215,7 +226,9 @@
|
|
|
215
226
|
inherited by default: a consumer stylesheet that sets a colour on
|
|
216
227
|
.material-symbols-rounded itself (a common global default) would
|
|
217
228
|
otherwise override inheritance and detach the icon from the state
|
|
218
|
-
colours this component sets on its parents.
|
|
219
|
-
|
|
229
|
+
colours this component sets on its parents. The chevron is excluded —
|
|
230
|
+
it is the one icon that carries its own colour rule, which this
|
|
231
|
+
higher-specificity rule would otherwise silently override. */
|
|
232
|
+
.ds-tool-call .material-symbols-rounded:not(.ds-tool-call__chevron) {
|
|
220
233
|
color: inherit;
|
|
221
234
|
}
|
|
@@ -17,8 +17,14 @@
|
|
|
17
17
|
z-index: 50;
|
|
18
18
|
padding: var(--padding-xs) var(--padding-sm); /* 6px 8px */
|
|
19
19
|
border-radius: var(--radius-xs); /* 4px */
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/* The floating surface, same recipe as Popover: page background (the
|
|
21
|
+
container fill is semi-transparent), hairline border, floating shadow.
|
|
22
|
+
Same-polarity in both themes — light bubble on light, dark on dark —
|
|
23
|
+
softer than the old inverse fill. */
|
|
24
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
25
|
+
background-color: var(--color-bg-page-primary);
|
|
26
|
+
color: var(--color-text-primary);
|
|
27
|
+
box-shadow: var(--shadow-floating);
|
|
22
28
|
font-family: var(--font-paragraph-sm-family);
|
|
23
29
|
font-size: var(--font-paragraph-sm-size);
|
|
24
30
|
font-weight: var(--font-paragraph-sm-weight);
|
|
@@ -85,38 +91,49 @@
|
|
|
85
91
|
ARROW
|
|
86
92
|
============================================ */
|
|
87
93
|
|
|
94
|
+
/* The arrow is a rotated square in the panel's fill. It overlaps the panel
|
|
95
|
+
by a pixel more than before so its opaque fill covers the panel border
|
|
96
|
+
behind it, and draws the hairline itself on its two outward edges only. */
|
|
88
97
|
.ds-tooltip__arrow {
|
|
89
98
|
position: absolute;
|
|
90
99
|
width: 8px;
|
|
91
100
|
height: 8px;
|
|
92
|
-
background-color: var(--color-bg-
|
|
101
|
+
background-color: var(--color-bg-page-primary);
|
|
93
102
|
transform: rotate(45deg);
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
/* Arrow — top position (arrow points down) */
|
|
97
106
|
.ds-tooltip__panel--top .ds-tooltip__arrow {
|
|
98
|
-
bottom: -
|
|
107
|
+
bottom: -5px;
|
|
99
108
|
left: 50%;
|
|
100
109
|
margin-left: -4px;
|
|
110
|
+
border-right: var(--border-xs) solid var(--color-bg-container-border);
|
|
111
|
+
border-bottom: var(--border-xs) solid var(--color-bg-container-border);
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
/* Arrow — bottom position (arrow points up) */
|
|
104
115
|
.ds-tooltip__panel--bottom .ds-tooltip__arrow {
|
|
105
|
-
top: -
|
|
116
|
+
top: -5px;
|
|
106
117
|
left: 50%;
|
|
107
118
|
margin-left: -4px;
|
|
119
|
+
border-top: var(--border-xs) solid var(--color-bg-container-border);
|
|
120
|
+
border-left: var(--border-xs) solid var(--color-bg-container-border);
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
/* Arrow — left position (arrow points right) */
|
|
111
124
|
.ds-tooltip__panel--left .ds-tooltip__arrow {
|
|
112
|
-
right: -
|
|
125
|
+
right: -5px;
|
|
113
126
|
top: 50%;
|
|
114
127
|
margin-top: -4px;
|
|
128
|
+
border-top: var(--border-xs) solid var(--color-bg-container-border);
|
|
129
|
+
border-right: var(--border-xs) solid var(--color-bg-container-border);
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
/* Arrow — right position (arrow points left) */
|
|
118
133
|
.ds-tooltip__panel--right .ds-tooltip__arrow {
|
|
119
|
-
left: -
|
|
134
|
+
left: -5px;
|
|
120
135
|
top: 50%;
|
|
121
136
|
margin-top: -4px;
|
|
137
|
+
border-bottom: var(--border-xs) solid var(--color-bg-container-border);
|
|
138
|
+
border-left: var(--border-xs) solid var(--color-bg-container-border);
|
|
122
139
|
}
|
|
@@ -16,6 +16,7 @@ export interface TooltipProps {
|
|
|
16
16
|
/**
|
|
17
17
|
* Tooltip component for contextual text labels.
|
|
18
18
|
* Appears on hover or focus with a short delay.
|
|
19
|
-
*
|
|
19
|
+
* Rides the system's floating surface — page background, hairline border,
|
|
20
|
+
* floating shadow — so the bubble stays light in light theme and dark in dark.
|
|
20
21
|
*/
|
|
21
22
|
export declare const Tooltip: ({ children, content, position, showDelay, hideDelay, className, }: TooltipProps) => import("react").JSX.Element;
|
|
@@ -24,6 +24,12 @@ const Tooltip = ({
|
|
|
24
24
|
clearTimeout(showTimeoutRef.current);
|
|
25
25
|
hideTimeoutRef.current = setTimeout(() => setVisible(false), hideDelay);
|
|
26
26
|
};
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
return () => {
|
|
29
|
+
clearTimeout(showTimeoutRef.current);
|
|
30
|
+
clearTimeout(hideTimeoutRef.current);
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
27
33
|
useEffect(() => {
|
|
28
34
|
if (!visible) return;
|
|
29
35
|
const onKeyDown = (e) => {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
USAGE CARD COMPONENT
|
|
3
|
+
An agent's budgets at a glance — meter rows
|
|
4
|
+
with reset captions, in the chart card chrome
|
|
5
|
+
============================================ */
|
|
6
|
+
|
|
7
|
+
/* Base — the card chrome itself comes from ds-chart */
|
|
8
|
+
|
|
9
|
+
.ds-usage-card__items {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--gap-lg);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.ds-usage-card__item {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--gap-xxs);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Reset caption — sits under the bar's trailing edge */
|
|
22
|
+
|
|
23
|
+
.ds-usage-card__reset {
|
|
24
|
+
align-self: flex-end;
|
|
25
|
+
font-family: var(--font-caption-family);
|
|
26
|
+
font-size: var(--font-caption-size);
|
|
27
|
+
font-weight: var(--font-caption-weight);
|
|
28
|
+
line-height: var(--font-caption-line-height);
|
|
29
|
+
color: var(--color-text-secondary);
|
|
30
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** One budgeted quantity in the card — a meter row with an optional reset caption. */
|
|
3
|
+
export interface UsageItem {
|
|
4
|
+
/** What is being budgeted, e.g. the context window or a weekly limit */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Current level */
|
|
7
|
+
value: number;
|
|
8
|
+
/** Upper bound of the budget; defaults to 100 */
|
|
9
|
+
max?: number;
|
|
10
|
+
/** Readout override replacing the default percentage, e.g. a token count */
|
|
11
|
+
valueText?: string;
|
|
12
|
+
/** Caption under the bar's trailing edge, e.g. when the limit resets */
|
|
13
|
+
resetLabel?: string;
|
|
14
|
+
/** Status override; left unset, the fill recolours through the card's thresholds */
|
|
15
|
+
variant?: 'info' | 'positive' | 'warning' | 'error' | 'neutral';
|
|
16
|
+
}
|
|
17
|
+
/** Props owned by UsageCard itself — everything else falls through to the root node. */
|
|
18
|
+
type UsageCardOwnProps = {
|
|
19
|
+
/** The budgets to show, one meter row each */
|
|
20
|
+
items: UsageItem[];
|
|
21
|
+
/** Card title in the shared chart-chrome header */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** Supporting line under the title */
|
|
24
|
+
subtitle?: string;
|
|
25
|
+
/** Fractions of an item's max where its fill recolours to warning and then error */
|
|
26
|
+
thresholds?: {
|
|
27
|
+
warning?: number;
|
|
28
|
+
error?: number;
|
|
29
|
+
};
|
|
30
|
+
/** Chrome off (no border, padding, or fill) for use inside a panel that supplies the surface */
|
|
31
|
+
bare?: boolean;
|
|
32
|
+
/** Additional CSS classes */
|
|
33
|
+
className?: string;
|
|
34
|
+
};
|
|
35
|
+
export interface UsageCardProps extends UsageCardOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof UsageCardOwnProps> {
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An agent's budgets at a glance — the context window plus any number of
|
|
39
|
+
* plan or rate limits, one Meter row each, with reset captions. Wears the
|
|
40
|
+
* chart family's card chrome and takes `bare` for use inside a panel.
|
|
41
|
+
*
|
|
42
|
+
* Each fill recolours as its level crosses the card's thresholds (info
|
|
43
|
+
* until warning, then warning, then error), mirroring Gauge's threshold
|
|
44
|
+
* idea; an item's own `variant` overrides the derivation. Purely
|
|
45
|
+
* presentational (no 'use client'), so it renders from a Server Component.
|
|
46
|
+
*/
|
|
47
|
+
export declare const UsageCard: React.ForwardRefExoticComponent<UsageCardProps & React.RefAttributes<HTMLDivElement>>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Meter } from "../Meter/Meter.js";
|
|
4
|
+
import "../Chart/Chart.css";
|
|
5
|
+
import "./UsageCard.css";
|
|
6
|
+
const DEFAULT_THRESHOLDS = { warning: 0.8, error: 0.95 };
|
|
7
|
+
const UsageCard = React.forwardRef(
|
|
8
|
+
({
|
|
9
|
+
items,
|
|
10
|
+
title,
|
|
11
|
+
subtitle,
|
|
12
|
+
thresholds,
|
|
13
|
+
bare = false,
|
|
14
|
+
className = "",
|
|
15
|
+
...rest
|
|
16
|
+
}, ref) => {
|
|
17
|
+
const baseClass = "ds-usage-card";
|
|
18
|
+
const limits = { ...DEFAULT_THRESHOLDS, ...thresholds };
|
|
19
|
+
const variantFor = (item) => {
|
|
20
|
+
if (item.variant) return item.variant;
|
|
21
|
+
const max = item.max ?? 100;
|
|
22
|
+
const fraction = max > 0 ? Math.max(0, Math.min(1, item.value / max)) : 0;
|
|
23
|
+
if (fraction >= limits.error) return "error";
|
|
24
|
+
if (fraction >= limits.warning) return "warning";
|
|
25
|
+
return "info";
|
|
26
|
+
};
|
|
27
|
+
const classes = [baseClass, "ds-chart", bare && "ds-chart--bare", className].filter(Boolean).join(" ");
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, children: [
|
|
29
|
+
(title || subtitle) && /* @__PURE__ */ jsx("div", { className: "ds-chart__header", children: /* @__PURE__ */ jsxs("div", { className: "ds-chart__header-text", children: [
|
|
30
|
+
title && /* @__PURE__ */ jsx("span", { className: "ds-chart__title", children: title }),
|
|
31
|
+
subtitle && /* @__PURE__ */ jsx("span", { className: "ds-chart__subtitle", children: subtitle })
|
|
32
|
+
] }) }),
|
|
33
|
+
/* @__PURE__ */ jsx("div", { className: `${baseClass}__items`, children: items.map((item, index) => /* @__PURE__ */ jsxs("div", { className: `${baseClass}__item`, children: [
|
|
34
|
+
/* @__PURE__ */ jsx(
|
|
35
|
+
Meter,
|
|
36
|
+
{
|
|
37
|
+
label: item.label,
|
|
38
|
+
value: item.value,
|
|
39
|
+
max: item.max ?? 100,
|
|
40
|
+
valueText: item.valueText,
|
|
41
|
+
showValue: true,
|
|
42
|
+
variant: variantFor(item),
|
|
43
|
+
size: "compact"
|
|
44
|
+
}
|
|
45
|
+
),
|
|
46
|
+
item.resetLabel && /* @__PURE__ */ jsx("span", { className: `${baseClass}__reset`, children: item.resetLabel })
|
|
47
|
+
] }, `${item.label}-${index}`)) })
|
|
48
|
+
] });
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
UsageCard.displayName = "UsageCard";
|
|
52
|
+
export {
|
|
53
|
+
UsageCard
|
|
54
|
+
};
|
package/components/registry.d.ts
CHANGED
|
@@ -18,8 +18,12 @@
|
|
|
18
18
|
* deliberately false so consumers can render them from a React
|
|
19
19
|
* Server Component.
|
|
20
20
|
* folder set only when the implementation lives in a shared folder
|
|
21
|
-
* rather than one named after the component — the
|
|
22
|
-
*
|
|
21
|
+
* rather than one named after the component — the recharts
|
|
22
|
+
* wrapper set lives in Chart/. Omitted everywhere else.
|
|
23
|
+
* recharts true when the component's module imports recharts, so it
|
|
24
|
+
* exports from the charts barrel rather than the main one
|
|
25
|
+
* (recharts is an optional peer). Held to the actual imports
|
|
26
|
+
* by scripts/generate-library-barrel.mjs on every build.
|
|
23
27
|
*
|
|
24
28
|
* `docOnlyHelpers` names the internal documentation helpers that are
|
|
25
29
|
* deliberately excluded from the public count.
|
|
@@ -47,6 +51,8 @@ export interface ComponentMeta {
|
|
|
47
51
|
client: boolean;
|
|
48
52
|
/** Folder under src/components when it differs from `name` (shared folders like Chart) */
|
|
49
53
|
folder?: string;
|
|
54
|
+
/** True when the module imports recharts and so exports from the charts barrel, not the main one */
|
|
55
|
+
recharts?: boolean;
|
|
50
56
|
}
|
|
51
57
|
export interface ComponentCategoryMeta {
|
|
52
58
|
/** Category id — the value each component's `category` field carries, and the website route segment under /components */
|