@robr0/design-system 0.10.0 → 0.12.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 (125) hide show
  1. package/README.md +9 -8
  2. package/charts.d.ts +1 -0
  3. package/charts.js +2 -0
  4. package/components/AgentPlan/AgentPlan.css +14 -3
  5. package/components/AiButton/AiButton.css +1 -1
  6. package/components/Alert/Alert.css +9 -0
  7. package/components/AlertDialog/AlertDialog.css +3 -1
  8. package/components/AlertDialog/AlertDialog.js +2 -3
  9. package/components/AppLayout/AppLayout.d.ts +7 -1
  10. package/components/AppLayout/AppLayout.js +2 -1
  11. package/components/AppSidebar/AppSidebar.css +113 -40
  12. package/components/AppSidebar/AppSidebar.d.ts +14 -1
  13. package/components/AppSidebar/AppSidebar.js +26 -17
  14. package/components/Badge/Badge.css +4 -5
  15. package/components/Card/Card.css +7 -2
  16. package/components/CardStack/CardStack.css +106 -0
  17. package/components/CardStack/CardStack.d.ts +44 -0
  18. package/components/CardStack/CardStack.js +140 -0
  19. package/components/Carousel/Carousel.css +9 -0
  20. package/components/Carousel/Carousel.js +37 -4
  21. package/components/Chart/AreaChart.d.ts +5 -1
  22. package/components/Chart/AreaChart.js +11 -9
  23. package/components/Chart/BarChart.d.ts +3 -1
  24. package/components/Chart/BarChart.js +3 -3
  25. package/components/Chart/Chart.css +45 -0
  26. package/components/Chart/ComboChart.d.ts +40 -0
  27. package/components/Chart/ComboChart.js +142 -0
  28. package/components/Chart/LineChart.d.ts +3 -1
  29. package/components/Chart/LineChart.js +7 -6
  30. package/components/Chart/PieChart.d.ts +3 -1
  31. package/components/Chart/PieChart.js +5 -18
  32. package/components/Chart/RadarChart.d.ts +3 -1
  33. package/components/Chart/RadarChart.js +8 -7
  34. package/components/Chart/RadialChart.d.ts +7 -1
  35. package/components/Chart/RadialChart.js +53 -58
  36. package/components/Chart/ScatterChart.d.ts +3 -1
  37. package/components/Chart/ScatterChart.js +6 -16
  38. package/components/Chart/StackedBarChart.d.ts +3 -1
  39. package/components/Chart/StackedBarChart.js +5 -18
  40. package/components/Chart/Treemap.d.ts +3 -1
  41. package/components/Chart/Treemap.js +6 -20
  42. package/components/Chart/palette.d.ts +1 -0
  43. package/components/Chart/palette.js +17 -0
  44. package/components/ChatMarker/ChatMarker.css +9 -0
  45. package/components/ChatMessage/ChatMessage.css +16 -0
  46. package/components/ChatThread/ChatThread.js +2 -2
  47. package/components/Chip/Chip.css +9 -0
  48. package/components/CodeBlock/CodeBlock.css +10 -1
  49. package/components/CodeBlock/CodeBlock.js +2 -1
  50. package/components/ColorPicker/ColorPicker.js +10 -0
  51. package/components/Combobox/Combobox.css +9 -0
  52. package/components/CommandPalette/CommandPalette.css +9 -0
  53. package/components/CommandPalette/CommandPalette.js +31 -5
  54. package/components/Composer/Composer.css +118 -0
  55. package/components/Composer/Composer.d.ts +7 -0
  56. package/components/Composer/Composer.js +7 -1
  57. package/components/ContactCard/ContactCard.css +9 -0
  58. package/components/ContextMenu/ContextMenu.js +39 -3
  59. package/components/DatePicker/DatePicker.js +16 -0
  60. package/components/Dialog/Dialog.css +3 -1
  61. package/components/Dialog/Dialog.js +5 -4
  62. package/components/DocumentChip/DocumentChip.css +9 -0
  63. package/components/Dropdown/Dropdown.js +22 -3
  64. package/components/DropdownMenu/DropdownMenu.js +43 -6
  65. package/components/EmptyState/EmptyState.css +9 -0
  66. package/components/FileInput/FileInput.css +9 -0
  67. package/components/FunnelChart/FunnelChart.css +41 -0
  68. package/components/FunnelChart/FunnelChart.d.ts +40 -0
  69. package/components/FunnelChart/FunnelChart.js +48 -0
  70. package/components/Globe/Globe.css +155 -0
  71. package/components/Globe/Globe.d.ts +96 -0
  72. package/components/Globe/Globe.js +415 -0
  73. package/components/InterruptCard/InterruptCard.css +27 -17
  74. package/components/InterruptCard/InterruptCard.d.ts +1 -1
  75. package/components/InterruptCard/InterruptCard.js +1 -1
  76. package/components/LegendTile/LegendTile.css +60 -0
  77. package/components/LegendTile/LegendTile.d.ts +30 -0
  78. package/components/LegendTile/LegendTile.js +21 -0
  79. package/components/MapCallout/MapCallout.css +46 -0
  80. package/components/MapCallout/MapCallout.d.ts +29 -0
  81. package/components/MapCallout/MapCallout.js +17 -0
  82. package/components/MapLegend/MapLegend.css +88 -0
  83. package/components/MapLegend/MapLegend.d.ts +39 -0
  84. package/components/MapLegend/MapLegend.js +68 -0
  85. package/components/MessageActions/MessageActions.css +9 -0
  86. package/components/MessageCard/MessageCard.css +68 -16
  87. package/components/MessageCard/MessageCard.d.ts +1 -1
  88. package/components/MessageCard/MessageCard.js +6 -4
  89. package/components/ModelPicker/ModelPicker.css +4 -1
  90. package/components/ModelPicker/ModelPicker.js +43 -1
  91. package/components/NotificationCenter/NotificationCenter.css +9 -0
  92. package/components/NotificationCenter/NotificationCenter.js +26 -10
  93. package/components/NumberInput/NumberInput.css +9 -0
  94. package/components/Panel/Panel.css +26 -0
  95. package/components/Panel/Panel.d.ts +22 -0
  96. package/components/Panel/Panel.js +19 -0
  97. package/components/Popover/Popover.css +3 -1
  98. package/components/Popover/Popover.js +2 -1
  99. package/components/SourceChip/SourceChip.css +9 -0
  100. package/components/Stat/Stat.css +44 -2
  101. package/components/Stat/Stat.d.ts +3 -1
  102. package/components/Stat/Stat.js +7 -1
  103. package/components/Stepper/Stepper.css +9 -0
  104. package/components/TagInput/TagInput.css +9 -0
  105. package/components/TimePicker/TimePicker.js +4 -1
  106. package/components/Toast/Toast.css +32 -11
  107. package/components/Toast/Toast.js +5 -4
  108. package/components/ToggleGroup/ToggleGroup.css +9 -0
  109. package/components/ToolCall/ToolCall.css +18 -6
  110. package/components/Tooltip/Tooltip.js +20 -16
  111. package/components/registry.json +70 -0
  112. package/components/registry.json.d.ts +70 -0
  113. package/components/registry.json.js +2 -2
  114. package/index.d.ts +7 -0
  115. package/index.js +14 -0
  116. package/package.json +5 -1
  117. package/tokens/motion.d.ts +23 -0
  118. package/tokens/motion.js +14 -0
  119. package/tokens/registry.json +21 -1
  120. package/tokens/registry.json.d.ts +21 -1
  121. package/tokens/registry.json.js +1 -1
  122. package/tokens/tokens-dark.css +14 -0
  123. package/tokens/tokens-light.css +22 -0
  124. package/tokens/tokens-primitives.css +2 -0
  125. package/tokens/tokens-typography.css +32 -5
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
- import React, { useState } from "react";
3
+ import React, { useState, useId } from "react";
4
4
  import { EmptyState } from "../EmptyState/EmptyState.js";
5
5
  import "./NotificationCenter.css";
6
6
  import "../../fonts/material-symbols.css";
@@ -42,6 +42,11 @@ const NotificationCenter = React.forwardRef(
42
42
  const isTabControlled = activeTab !== void 0;
43
43
  const [uncontrolledTab, setUncontrolledTab] = useState(defaultTab ?? tabs?.[0]?.value);
44
44
  const currentTab = isTabControlled ? activeTab : uncontrolledTab;
45
+ const idBase = useId();
46
+ const panelId = `${idBase}-panel`;
47
+ const tabIdFor = (index) => `${idBase}-tab-${index}`;
48
+ const activeTabIndex = tabs?.findIndex((tab) => tab.value === currentTab) ?? -1;
49
+ const hasTabs = !!tabs && tabs.length > 0;
45
50
  const selectTab = (value) => {
46
51
  if (!isTabControlled) setUncontrolledTab(value);
47
52
  onTabChange?.(value);
@@ -80,21 +85,23 @@ const NotificationCenter = React.forwardRef(
80
85
  /* @__PURE__ */ jsxs("header", { className: `${baseClass}__header`, children: [
81
86
  /* @__PURE__ */ jsxs("div", { className: `${baseClass}__heading`, children: [
82
87
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__title`, children: title }),
83
- unreadCount !== void 0 && unreadCount > 0 && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__unread`, children: [
88
+ unreadCount !== void 0 && unreadCount > 0 && /* @__PURE__ */ jsxs("span", { className: `${baseClass}__unread`, "aria-live": "polite", children: [
84
89
  unreadCount,
85
90
  " unread"
86
91
  ] })
87
92
  ] }),
88
93
  onMarkAllRead && /* @__PURE__ */ jsx("button", { type: "button", className: `${baseClass}__mark-all`, onClick: onMarkAllRead, children: markAllLabel })
89
94
  ] }),
90
- tabs && tabs.length > 0 && /* @__PURE__ */ jsx("div", { className: `${baseClass}__tabs`, role: "tablist", "aria-label": `${title} filters`, children: tabs.map((tab, index) => {
95
+ hasTabs && /* @__PURE__ */ jsx("div", { className: `${baseClass}__tabs`, role: "tablist", "aria-label": `${title} filters`, children: tabs.map((tab, index) => {
91
96
  const isActive = tab.value === currentTab;
92
97
  return /* @__PURE__ */ jsxs(
93
98
  "button",
94
99
  {
95
100
  type: "button",
101
+ id: tabIdFor(index),
96
102
  role: "tab",
97
103
  "aria-selected": isActive,
104
+ "aria-controls": panelId,
98
105
  tabIndex: isActive ? 0 : -1,
99
106
  className: [
100
107
  `${baseClass}__tab`,
@@ -110,15 +117,24 @@ const NotificationCenter = React.forwardRef(
110
117
  tab.value
111
118
  );
112
119
  }) }),
113
- /* @__PURE__ */ jsx("div", { className: `${baseClass}__list`, children: hasItems ? children : emptyState ?? /* @__PURE__ */ jsx(
114
- EmptyState,
120
+ /* @__PURE__ */ jsx(
121
+ "div",
115
122
  {
116
- icon: "notifications",
117
- title: "Nothing new",
118
- description: "Notifications will collect here as they arrive.",
119
- size: "compact"
123
+ className: `${baseClass}__list`,
124
+ role: hasTabs ? "tabpanel" : void 0,
125
+ id: hasTabs ? panelId : void 0,
126
+ "aria-labelledby": hasTabs && activeTabIndex >= 0 ? tabIdFor(activeTabIndex) : void 0,
127
+ children: hasItems ? children : emptyState ?? /* @__PURE__ */ jsx(
128
+ EmptyState,
129
+ {
130
+ icon: "notifications",
131
+ title: "Nothing new",
132
+ description: "Notifications will collect here as they arrive.",
133
+ size: "compact"
134
+ }
135
+ )
120
136
  }
121
- ) })
137
+ )
122
138
  ] });
123
139
  }
124
140
  );
@@ -152,3 +152,12 @@
152
152
  .ds-numberinput--compact .ds-numberinput__stepper {
153
153
  padding: 0 var(--padding-sm); /* 8px */
154
154
  }
155
+
156
+ /* Icons take the colour of the text around them. Explicit, not just
157
+ inherited by default: a consumer stylesheet that sets a colour on
158
+ .material-symbols-rounded itself (a common global default) would
159
+ otherwise override inheritance and detach the icon from the state
160
+ colours this component sets on its parents. */
161
+ .ds-numberinput .material-symbols-rounded {
162
+ color: inherit;
163
+ }
@@ -0,0 +1,26 @@
1
+ /* ============================================
2
+ PANEL COMPONENT
3
+ The plain dashboard surface
4
+ ============================================ */
5
+
6
+ .ds-panel {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: var(--gap-md);
10
+ min-width: 0;
11
+ background-color: var(--color-bg-container-primary);
12
+ border-radius: var(--radius-xl);
13
+ padding: var(--padding-lg);
14
+ }
15
+
16
+ /* ============================================
17
+ PADDING VARIANTS
18
+ ============================================ */
19
+
20
+ .ds-panel--compact {
21
+ padding: var(--padding-md);
22
+ }
23
+
24
+ .ds-panel--flush {
25
+ padding: 0;
26
+ }
@@ -0,0 +1,22 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by Panel itself; everything else falls through to the <div>. */
3
+ type PanelOwnProps = {
4
+ /** Interior padding: 'default' uses --padding-lg, 'compact' uses --padding-md, 'none' removes it */
5
+ padding?: 'default' | 'compact' | 'none';
6
+ /** Additional CSS classes */
7
+ className?: string;
8
+ /** Panel content */
9
+ children?: React.ReactNode;
10
+ };
11
+ export interface PanelProps extends PanelOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof PanelOwnProps> {
12
+ }
13
+ /**
14
+ * Panel component: the plain dashboard surface.
15
+ * A rounded container on the primary container background with no border and
16
+ * no shadow: just the surface, ready for whatever a dashboard region holds.
17
+ * Content stacks vertically with a medium gap.
18
+ *
19
+ * Forwards a ref to the `<div>` element and spreads unrecognised props onto it.
20
+ */
21
+ export declare const Panel: React.ForwardRefExoticComponent<PanelProps & React.RefAttributes<HTMLDivElement>>;
22
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./Panel.css";
4
+ const Panel = React.forwardRef(
5
+ ({ padding = "default", className = "", children, ...rest }, ref) => {
6
+ const baseClass = "ds-panel";
7
+ const classes = [
8
+ baseClass,
9
+ padding === "compact" ? `${baseClass}--compact` : "",
10
+ padding === "none" ? `${baseClass}--flush` : "",
11
+ className
12
+ ].filter(Boolean).join(" ");
13
+ return /* @__PURE__ */ jsx("div", { ...rest, ref, className: classes, children });
14
+ }
15
+ );
16
+ Panel.displayName = "Panel";
17
+ export {
18
+ Panel
19
+ };
@@ -35,13 +35,15 @@
35
35
 
36
36
  /* Hidden by default */
37
37
  opacity: 0;
38
+ visibility: hidden; /* keeps panel content out of the tab order when closed */
38
39
  pointer-events: none;
39
40
  transform: scale(0.96);
40
- transition: opacity var(--motion-duration-base) var(--motion-ease-standard), transform var(--motion-duration-base) var(--motion-ease-standard);
41
+ transition: opacity var(--motion-duration-base) var(--motion-ease-standard), transform var(--motion-duration-base) var(--motion-ease-standard), visibility var(--motion-duration-base) var(--motion-ease-standard);
41
42
  }
42
43
 
43
44
  .ds-popover__panel--open {
44
45
  opacity: 1;
46
+ visibility: visible;
45
47
  pointer-events: auto;
46
48
  transform: scale(1);
47
49
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import React, { useState, useRef, useCallback, useEffect } from "react";
4
+ import { MOTION_HOVER_HIDE_DELAY_MS } from "../../tokens/motion.js";
4
5
  import "./Popover.css";
5
6
  const INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["button", "a", "input", "select", "textarea", "summary"]);
6
7
  const canCarryPopoverSemantics = (el) => typeof el.type === "string" ? INTERACTIVE_TAGS.has(el.type) : true;
@@ -59,7 +60,7 @@ const Popover = ({
59
60
  };
60
61
  const handleMouseLeave = () => {
61
62
  if (trigger === "hover") {
62
- hoverTimeoutRef.current = setTimeout(() => setOpen(false), 150);
63
+ hoverTimeoutRef.current = setTimeout(() => setOpen(false), MOTION_HOVER_HIDE_DELAY_MS);
63
64
  }
64
65
  };
65
66
  const baseClass = "ds-popover";
@@ -100,3 +100,12 @@
100
100
  outline: 2px solid var(--color-action-primary-bg);
101
101
  outline-offset: 2px;
102
102
  }
103
+
104
+ /* Icons take the colour of the text around them. Explicit, not just
105
+ inherited by default: a consumer stylesheet that sets a colour on
106
+ .material-symbols-rounded itself (a common global default) would
107
+ otherwise override inheritance and detach the icon from the state
108
+ colours this component sets on its parents. */
109
+ .ds-source-chip .material-symbols-rounded {
110
+ color: inherit;
111
+ }
@@ -65,14 +65,56 @@
65
65
  line-height: 1;
66
66
  }
67
67
 
68
+ /* The trend tokens are theme-split for contrast: deep status inks on
69
+ light surfaces, the mint/coral accents on dark ones. */
68
70
  .ds-stat__delta--up {
69
- color: var(--color-core-accent-mint);
71
+ color: var(--color-trend-up);
70
72
  }
71
73
 
72
74
  .ds-stat__delta--down {
73
- color: var(--color-core-accent-coral);
75
+ color: var(--color-trend-down);
74
76
  }
75
77
 
76
78
  .ds-stat__delta--neutral {
77
79
  color: var(--color-text-tertiary);
78
80
  }
81
+
82
+ /* ============================================
83
+ INLINE DELTA — delta to the right of the
84
+ value, bottom-aligned to its baseline
85
+ ============================================ */
86
+
87
+ .ds-stat--delta-inline {
88
+ display: grid;
89
+ grid-template-columns: auto 1fr;
90
+ grid-template-areas:
91
+ 'value delta'
92
+ 'label label';
93
+ column-gap: var(--gap-sm);
94
+ row-gap: var(--gap-xxs);
95
+ }
96
+
97
+ .ds-stat--delta-inline .ds-stat__value {
98
+ grid-area: value;
99
+ }
100
+
101
+ .ds-stat--delta-inline .ds-stat__label {
102
+ grid-area: label;
103
+ }
104
+
105
+ .ds-stat--delta-inline .ds-stat__delta {
106
+ grid-area: delta;
107
+ align-self: end;
108
+ justify-self: start;
109
+ /* Optical baseline: compensates the display value's half-leading. */
110
+ margin-bottom: var(--gap-xs);
111
+ }
112
+
113
+ /* Icons take the colour of the text around them. Explicit, not just
114
+ inherited by default: a consumer stylesheet that sets a colour on
115
+ .material-symbols-rounded itself (a common global default) would
116
+ otherwise override inheritance and detach the icon from the state
117
+ colours this component sets on its parents. */
118
+ .ds-stat .material-symbols-rounded {
119
+ color: inherit;
120
+ }
@@ -7,6 +7,8 @@ export interface StatProps {
7
7
  delta?: string;
8
8
  /** Direction of the delta — colours it and adds an arrow */
9
9
  trend?: 'up' | 'down' | 'neutral';
10
+ /** Where the delta sits: stacked below the label, or inline to the right of the value, bottom-aligned */
11
+ deltaPlacement?: 'stacked' | 'inline';
10
12
  /** Stat size */
11
13
  size?: 'default' | 'large';
12
14
  /** Additional CSS classes */
@@ -16,4 +18,4 @@ export interface StatProps {
16
18
  * Stat component — a single headline metric with a label and an
17
19
  * optional trend delta. Compose several in a row for a metrics band.
18
20
  */
19
- export declare const Stat: ({ value, label, delta, trend, size, className, }: StatProps) => import("react").JSX.Element;
21
+ export declare const Stat: ({ value, label, delta, trend, deltaPlacement, size, className, }: StatProps) => import("react").JSX.Element;
@@ -11,11 +11,17 @@ const Stat = ({
11
11
  label,
12
12
  delta,
13
13
  trend = "neutral",
14
+ deltaPlacement = "stacked",
14
15
  size = "default",
15
16
  className = ""
16
17
  }) => {
17
18
  const baseClass = "ds-stat";
18
- const classes = [baseClass, `${baseClass}--${size}`, className].filter(Boolean).join(" ");
19
+ const classes = [
20
+ baseClass,
21
+ `${baseClass}--${size}`,
22
+ deltaPlacement === "inline" ? `${baseClass}--delta-inline` : "",
23
+ className
24
+ ].filter(Boolean).join(" ");
19
25
  return /* @__PURE__ */ jsxs("div", { className: classes, children: [
20
26
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, children: value }),
21
27
  /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label }),
@@ -205,3 +205,12 @@ button.ds-stepper__content:hover .ds-stepper__label {
205
205
  height: auto;
206
206
  transform: translateX(-50%);
207
207
  }
208
+
209
+ /* Icons take the colour of the text around them. Explicit, not just
210
+ inherited by default: a consumer stylesheet that sets a colour on
211
+ .material-symbols-rounded itself (a common global default) would
212
+ otherwise override inheritance and detach the icon from the state
213
+ colours this component sets on its parents. */
214
+ .ds-stepper .material-symbols-rounded {
215
+ color: inherit;
216
+ }
@@ -158,3 +158,12 @@
158
158
  line-height: var(--font-paragraph-sm-line-height);
159
159
  letter-spacing: var(--font-paragraph-sm-letter-spacing);
160
160
  }
161
+
162
+ /* Icons take the colour of the text around them. Explicit, not just
163
+ inherited by default: a consumer stylesheet that sets a colour on
164
+ .material-symbols-rounded itself (a common global default) would
165
+ otherwise override inheritance and detach the icon from the state
166
+ colours this component sets on its parents. */
167
+ .ds-taginput .material-symbols-rounded {
168
+ color: inherit;
169
+ }
@@ -158,6 +158,7 @@ const TimePicker = React.forwardRef(
158
158
  focusedEl?.scrollIntoView({ block: "nearest" });
159
159
  }
160
160
  }, [focusedIndex, isOpen]);
161
+ const activeOptionId = isOpen && focusedIndex >= 0 && timeOptions[focusedIndex] ? `${inputId}-option-${focusedIndex}` : void 0;
161
162
  return /* @__PURE__ */ jsxs(
162
163
  Field,
163
164
  {
@@ -181,7 +182,8 @@ const TimePicker = React.forwardRef(
181
182
  className: `${baseClass}__trigger`,
182
183
  "aria-expanded": isOpen,
183
184
  "aria-haspopup": "listbox",
184
- "aria-controls": `${inputId}-listbox`,
185
+ "aria-controls": isOpen ? `${inputId}-listbox` : void 0,
186
+ "aria-activedescendant": activeOptionId,
185
187
  "aria-describedby": helperText ? `${inputId}-helper` : rest["aria-describedby"],
186
188
  "aria-invalid": error || void 0,
187
189
  disabled,
@@ -211,6 +213,7 @@ const TimePicker = React.forwardRef(
211
213
  children: timeOptions.map((option, index) => /* @__PURE__ */ jsxs(
212
214
  "li",
213
215
  {
216
+ id: `${inputId}-option-${index}`,
214
217
  className: [
215
218
  `${baseClass}__option`,
216
219
  option.value === selectedValue ? `${baseClass}__option--selected` : "",
@@ -1,11 +1,23 @@
1
1
  /* ============================================
2
2
  TOAST CONTAINER
3
3
  Fixed position stack for notifications
4
+
5
+ Consumer hooks (custom properties with fallbacks, so the defaults hold
6
+ until a consumer sets them): a page that keeps its own fixed chrome in a
7
+ corner (a chat launcher, a cookie bar, a docked panel) sets the matching
8
+ inset so toasts stack clear of it rather than on top of it, and raises
9
+ --ds-toast-z above its own overlay layers. Toasts are transient feedback
10
+ and must be seen; a launcher can be covered for five seconds.
11
+ --ds-toast-z stacking order (default 200, the library's top)
12
+ --ds-toast-inset-top gap from the top edge for top-* placements
13
+ --ds-toast-inset-bottom gap from the bottom edge for bottom-* placements
14
+ --ds-toast-inset-left gap from the left edge for *-left placements
15
+ --ds-toast-inset-right gap from the right edge for *-right placements
4
16
  ============================================ */
5
17
 
6
18
  .ds-toast-container {
7
19
  position: fixed;
8
- z-index: 200;
20
+ z-index: var(--ds-toast-z, 200);
9
21
  display: flex;
10
22
  flex-direction: column;
11
23
  gap: var(--gap-sm); /* 8px */
@@ -14,38 +26,38 @@
14
26
  }
15
27
 
16
28
  .ds-toast-container--top-right {
17
- top: 0;
18
- right: 0;
29
+ top: var(--ds-toast-inset-top, 0);
30
+ right: var(--ds-toast-inset-right, 0);
19
31
  align-items: flex-end;
20
32
  }
21
33
 
22
34
  .ds-toast-container--top-left {
23
- top: 0;
24
- left: 0;
35
+ top: var(--ds-toast-inset-top, 0);
36
+ left: var(--ds-toast-inset-left, 0);
25
37
  align-items: flex-start;
26
38
  }
27
39
 
28
40
  .ds-toast-container--bottom-right {
29
- bottom: 0;
30
- right: 0;
41
+ bottom: var(--ds-toast-inset-bottom, 0);
42
+ right: var(--ds-toast-inset-right, 0);
31
43
  align-items: flex-end;
32
44
  }
33
45
 
34
46
  .ds-toast-container--bottom-left {
35
- bottom: 0;
36
- left: 0;
47
+ bottom: var(--ds-toast-inset-bottom, 0);
48
+ left: var(--ds-toast-inset-left, 0);
37
49
  align-items: flex-start;
38
50
  }
39
51
 
40
52
  .ds-toast-container--top-center {
41
- top: 0;
53
+ top: var(--ds-toast-inset-top, 0);
42
54
  left: 50%;
43
55
  transform: translateX(-50%);
44
56
  align-items: center;
45
57
  }
46
58
 
47
59
  .ds-toast-container--bottom-center {
48
- bottom: 0;
60
+ bottom: var(--ds-toast-inset-bottom, 0);
49
61
  left: 50%;
50
62
  transform: translateX(-50%);
51
63
  align-items: center;
@@ -340,3 +352,12 @@
340
352
  transform: scaleX(0);
341
353
  }
342
354
  }
355
+
356
+ /* Icons take the colour of the text around them. Explicit, not just
357
+ inherited by default: a consumer stylesheet that sets a colour on
358
+ .material-symbols-rounded itself (a common global default) would
359
+ otherwise override inheritance and detach the icon from the state
360
+ colours this component sets on its parents. */
361
+ .ds-toast .material-symbols-rounded {
362
+ color: inherit;
363
+ }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import { useState, useSyncExternalStore, useCallback, useContext, createContext, useRef, useEffect } from "react";
4
+ import { MOTION_AUTO_DISMISS_MS } from "../../tokens/motion.js";
4
5
  import ReactDOM from "react-dom";
5
6
  import "./Toast.css";
6
7
  import "../../fonts/material-symbols.css";
@@ -62,8 +63,7 @@ const ToastItem = ({
62
63
  "div",
63
64
  {
64
65
  className: classes,
65
- role: "status",
66
- "aria-live": variant === "error" || variant === "warning" ? "assertive" : "polite",
66
+ role: variant === "error" || variant === "warning" ? "alert" : void 0,
67
67
  onMouseEnter: () => setPauseTimer(true),
68
68
  onMouseLeave: () => setPauseTimer(false),
69
69
  onFocus: () => setPauseTimer(true),
@@ -119,7 +119,7 @@ const ToastProvider = ({
119
119
  ...data,
120
120
  id,
121
121
  variant: data.variant || "info",
122
- duration: data.duration ?? 5e3,
122
+ duration: data.duration ?? MOTION_AUTO_DISMISS_MS,
123
123
  dismissible: data.dismissible ?? true
124
124
  };
125
125
  setToasts((prev) => {
@@ -150,6 +150,7 @@ const ToastProvider = ({
150
150
  className: containerClasses,
151
151
  role: "region",
152
152
  "aria-label": "Notifications",
153
+ "aria-live": "polite",
153
154
  children: toasts.map((t) => /* @__PURE__ */ jsx(
154
155
  ToastItem,
155
156
  {
@@ -159,7 +160,7 @@ const ToastProvider = ({
159
160
  variant: t.variant || "info",
160
161
  dismissible: t.dismissible ?? true,
161
162
  icon: t.icon,
162
- duration: t.duration ?? 5e3,
163
+ duration: t.duration ?? MOTION_AUTO_DISMISS_MS,
163
164
  onDismiss: dismiss
164
165
  },
165
166
  t.id
@@ -82,3 +82,12 @@
82
82
  opacity: 0.4;
83
83
  pointer-events: none;
84
84
  }
85
+
86
+ /* Icons take the colour of the text around them. Explicit, not just
87
+ inherited by default: a consumer stylesheet that sets a colour on
88
+ .material-symbols-rounded itself (a common global default) would
89
+ otherwise override inheritance and detach the icon from the state
90
+ colours this component sets on its parents. */
91
+ .ds-toggle-group .material-symbols-rounded {
92
+ color: inherit;
93
+ }
@@ -20,7 +20,10 @@
20
20
  width: 100%;
21
21
  background-color: var(--color-bg-container-primary);
22
22
  border: var(--border-xs) solid var(--color-bg-container-border);
23
- border-radius: var(--radius-md);
23
+ /* The chat furniture shares one 24px shell with MessageCard and the
24
+ bubbles; a closed row rounds to a capsule the way a one-line bubble
25
+ does, echoing the system's pill buttons. */
26
+ border-radius: var(--radius-xl);
24
27
  overflow: hidden;
25
28
  }
26
29
 
@@ -33,7 +36,8 @@
33
36
  align-items: center;
34
37
  gap: var(--gap-sm);
35
38
  width: 100%;
36
- padding: var(--padding-sm) var(--padding-md);
39
+ /* 20px sides — the card family's shared content inset. */
40
+ padding: var(--padding-sm) var(--padding-lg);
37
41
  background: none;
38
42
  border: none;
39
43
  text-align: left;
@@ -181,7 +185,7 @@
181
185
  }
182
186
 
183
187
  .ds-tool-call--open .ds-tool-call__content {
184
- padding: var(--padding-md);
188
+ padding: var(--padding-md) var(--padding-lg);
185
189
  border-top: var(--border-xs) solid var(--color-divider);
186
190
  }
187
191
 
@@ -202,8 +206,16 @@
202
206
  .ds-tool-call__actions {
203
207
  display: flex;
204
208
  align-items: center;
205
- justify-content: flex-end;
209
+ justify-content: flex-start;
206
210
  gap: var(--gap-sm);
207
- padding: var(--padding-sm-md) var(--padding-md);
208
- border-top: var(--border-xs) solid var(--color-divider);
211
+ padding: var(--padding-sm) var(--padding-lg) var(--padding-md);
212
+ }
213
+
214
+ /* Icons take the colour of the text around them. Explicit, not just
215
+ inherited by default: a consumer stylesheet that sets a colour on
216
+ .material-symbols-rounded itself (a common global default) would
217
+ 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 {
220
+ color: inherit;
209
221
  }
@@ -1,13 +1,14 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
- import { useState, useRef, useId, isValidElement, cloneElement } from "react";
3
+ import { useState, useRef, useId, useEffect, isValidElement, cloneElement } from "react";
4
4
  import "./Tooltip.css";
5
+ import { MOTION_HOVER_SHOW_DELAY_MS, MOTION_HOVER_HIDE_DELAY_MS } from "../../tokens/motion.js";
5
6
  const Tooltip = ({
6
7
  children,
7
8
  content,
8
9
  position = "top",
9
- showDelay = 300,
10
- hideDelay = 150,
10
+ showDelay = MOTION_HOVER_SHOW_DELAY_MS,
11
+ hideDelay = MOTION_HOVER_HIDE_DELAY_MS,
11
12
  className = ""
12
13
  }) => {
13
14
  const [visible, setVisible] = useState(false);
@@ -23,6 +24,18 @@ const Tooltip = ({
23
24
  clearTimeout(showTimeoutRef.current);
24
25
  hideTimeoutRef.current = setTimeout(() => setVisible(false), hideDelay);
25
26
  };
27
+ useEffect(() => {
28
+ if (!visible) return;
29
+ const onKeyDown = (e) => {
30
+ if (e.key === "Escape") {
31
+ clearTimeout(showTimeoutRef.current);
32
+ clearTimeout(hideTimeoutRef.current);
33
+ setVisible(false);
34
+ }
35
+ };
36
+ document.addEventListener("keydown", onKeyDown);
37
+ return () => document.removeEventListener("keydown", onKeyDown);
38
+ }, [visible]);
26
39
  const trigger = isValidElement(children) ? cloneElement(children, {
27
40
  "aria-describedby": tooltipId
28
41
  }) : children;
@@ -42,19 +55,10 @@ const Tooltip = ({
42
55
  onBlur: hide,
43
56
  children: [
44
57
  trigger,
45
- /* @__PURE__ */ jsxs(
46
- "span",
47
- {
48
- className: panelClasses,
49
- role: "tooltip",
50
- id: tooltipId,
51
- "aria-hidden": !visible,
52
- children: [
53
- content,
54
- /* @__PURE__ */ jsx("span", { className: `${baseClass}__arrow` })
55
- ]
56
- }
57
- )
58
+ /* @__PURE__ */ jsxs("span", { className: panelClasses, role: "tooltip", id: tooltipId, children: [
59
+ content,
60
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__arrow` })
61
+ ] })
58
62
  ]
59
63
  }
60
64
  );