@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.
Files changed (62) hide show
  1. package/README.md +8 -5
  2. package/charts.d.ts +1 -0
  3. package/charts.js +2 -0
  4. package/components/AnchorNav/AnchorNav.css +74 -0
  5. package/components/AnchorNav/AnchorNav.d.ts +7 -0
  6. package/components/AnchorNav/AnchorNav.js +74 -21
  7. package/components/Button/Button.css +51 -0
  8. package/components/Button/Button.d.ts +2 -2
  9. package/components/Chart/Chart.css +4 -1
  10. package/components/CircularButton/CircularButton.css +46 -0
  11. package/components/CircularButton/CircularButton.d.ts +13 -2
  12. package/components/CircularButton/CircularButton.js +19 -16
  13. package/components/CommandPalette/CommandPalette.css +26 -1
  14. package/components/CommandPalette/CommandPalette.js +11 -7
  15. package/components/Composer/Composer.css +18 -0
  16. package/components/Composer/Composer.d.ts +4 -2
  17. package/components/Composer/Composer.js +1 -0
  18. package/components/ContributionGraph/ContributionGraph.css +21 -0
  19. package/components/ContributionGraph/ContributionGraph.js +18 -10
  20. package/components/Figure/Figure.css +14 -4
  21. package/components/Figure/Figure.js +11 -20
  22. package/components/FunnelChart/FunnelChart.css +11 -36
  23. package/components/FunnelChart/FunnelChart.d.ts +19 -16
  24. package/components/FunnelChart/FunnelChart.js +67 -39
  25. package/components/Gauge/Gauge.css +11 -1
  26. package/components/Meter/Meter.css +10 -1
  27. package/components/ProgressBar/ProgressBar.css +1 -1
  28. package/components/SegmentedControl/SegmentedControl.css +23 -0
  29. package/components/SegmentedControl/SegmentedControl.d.ts +3 -1
  30. package/components/SegmentedControl/SegmentedControl.js +3 -1
  31. package/components/Slider/Slider.css +1 -1
  32. package/components/Slider/Slider.js +1 -1
  33. package/components/SourceTrail/SourceTrail.css +209 -0
  34. package/components/SourceTrail/SourceTrail.d.ts +49 -0
  35. package/components/SourceTrail/SourceTrail.js +108 -0
  36. package/components/Sparkline/Sparkline.css +26 -2
  37. package/components/Sparkline/Sparkline.js +2 -1
  38. package/components/Spinner/Spinner.css +1 -1
  39. package/components/SplitButton/SplitButton.d.ts +1 -1
  40. package/components/SplitButton/SplitButton.js +1 -0
  41. package/components/ToggleGroup/ToggleGroup.css +17 -0
  42. package/components/ToggleGroup/ToggleGroup.d.ts +2 -0
  43. package/components/ToggleGroup/ToggleGroup.js +2 -0
  44. package/components/ToolCall/ToolCall.css +7 -2
  45. package/components/Tooltip/Tooltip.css +24 -7
  46. package/components/Tooltip/Tooltip.d.ts +2 -1
  47. package/components/UsageCard/UsageCard.css +30 -0
  48. package/components/UsageCard/UsageCard.d.ts +48 -0
  49. package/components/UsageCard/UsageCard.js +54 -0
  50. package/components/registry.d.ts +8 -2
  51. package/components/registry.json +41 -14
  52. package/components/registry.json.d.ts +41 -14
  53. package/components/registry.json.js +1 -1
  54. package/index.d.ts +2 -1
  55. package/index.js +4 -2
  56. package/package.json +1 -1
  57. package/tokens/registry.json +4 -0
  58. package/tokens/registry.json.d.ts +4 -0
  59. package/tokens/registry.json.js +1 -1
  60. package/tokens/tokens-dark.css +11 -2
  61. package/tokens/tokens-light.css +25 -11
  62. package/tokens/tokens-primitives.css +3 -0
@@ -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-text);
165
+ }
166
+
167
+ .ds-source-trail__row--done .ds-source-trail__row-indicator {
168
+ color: var(--color-status-positive-text);
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
+ }
@@ -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-status-positive-text);
67
+ color: var(--color-trend-up);
44
68
  }
45
69
 
46
70
  .ds-sparkline--negative {
47
- color: var(--color-status-error-text);
71
+ color: var(--color-trend-down);
48
72
  }
49
73
 
50
74
  .ds-sparkline--neutral {
@@ -92,7 +92,8 @@ const Sparkline = React.forwardRef(
92
92
  {
93
93
  className: `${baseClass}__line`,
94
94
  d: linePath,
95
- strokeWidth
95
+ strokeWidth,
96
+ pathLength: 100
96
97
  }
97
98
  ),
98
99
  showDot && lastPoint && /* @__PURE__ */ jsx(
@@ -39,7 +39,7 @@
39
39
  }
40
40
 
41
41
  .ds-spinner__track {
42
- stroke: var(--color-bg-container-secondary);
42
+ stroke: var(--color-bg-container-tertiary);
43
43
  }
44
44
 
45
45
  .ds-spinner__arc {
@@ -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 */
@@ -49,6 +49,7 @@ const SplitButton = React.forwardRef(
49
49
  {
50
50
  className: `${baseClass}__trigger`,
51
51
  icon: "keyboard_arrow_down",
52
+ tooltip: false,
52
53
  ariaLabel: menuLabel,
53
54
  variant,
54
55
  size,
@@ -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(" ");
@@ -40,6 +40,9 @@
40
40
  padding: var(--padding-sm) var(--padding-lg);
41
41
  background: none;
42
42
  border: none;
43
+ /* Buttons default to the UA's buttontext, not the inherited colour, so
44
+ anything in the header that inherits would read black in both themes. */
45
+ color: var(--color-text-primary);
43
46
  text-align: left;
44
47
  cursor: pointer;
45
48
  transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
@@ -215,7 +218,9 @@
215
218
  inherited by default: a consumer stylesheet that sets a colour on
216
219
  .material-symbols-rounded itself (a common global default) would
217
220
  otherwise override inheritance and detach the icon from the state
218
- colours this component sets on its parents. */
219
- .ds-tool-call .material-symbols-rounded {
221
+ colours this component sets on its parents. The chevron is excluded —
222
+ it is the one icon that carries its own colour rule, which this
223
+ higher-specificity rule would otherwise silently override. */
224
+ .ds-tool-call .material-symbols-rounded:not(.ds-tool-call__chevron) {
220
225
  color: inherit;
221
226
  }
@@ -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
- background-color: var(--color-bg-container-inverse);
21
- color: var(--color-text-on-inverse);
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-container-inverse);
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: -4px;
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: -4px;
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: -4px;
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: -4px;
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
- * Uses inverted colours (dark background in light theme).
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;
@@ -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
+ }