@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
@@ -12,6 +12,10 @@
12
12
  line-height: var(--font-paragraph-sm-em-line-height);
13
13
  letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
14
14
  padding: var(--padding-xxxs) var(--padding-sm); /* Figma: 2px 8px */
15
+ /* Badges are borderless by decision (2026-08-26): the tinted fill
16
+ carries the status on its own, and the old per-variant borders sat
17
+ below 3:1 against their fills. The transparent border keeps the
18
+ geometry so the box does not shift against bordered neighbours. */
15
19
  border: var(--border-xs) solid transparent;
16
20
  border-radius: var(--radius-xs); /* Figma: 4px */
17
21
  white-space: nowrap;
@@ -23,7 +27,6 @@
23
27
 
24
28
  .ds-badge--info {
25
29
  background-color: var(--color-status-info-bg);
26
- border-color: var(--color-status-info-border);
27
30
  color: var(--color-status-info-text);
28
31
  }
29
32
 
@@ -33,7 +36,6 @@
33
36
 
34
37
  .ds-badge--positive {
35
38
  background-color: var(--color-status-positive-bg);
36
- border-color: var(--color-status-positive-border);
37
39
  color: var(--color-status-positive-text);
38
40
  }
39
41
 
@@ -43,7 +45,6 @@
43
45
 
44
46
  .ds-badge--warning {
45
47
  background-color: var(--color-status-warning-bg);
46
- border-color: var(--color-status-warning-border);
47
48
  color: var(--color-status-warning-text);
48
49
  }
49
50
 
@@ -53,7 +54,6 @@
53
54
 
54
55
  .ds-badge--error {
55
56
  background-color: var(--color-status-error-bg);
56
- border-color: var(--color-status-error-border);
57
57
  color: var(--color-status-error-text);
58
58
  }
59
59
 
@@ -63,6 +63,5 @@
63
63
 
64
64
  .ds-badge--neutral {
65
65
  background-color: var(--color-status-neutral-bg);
66
- border-color: var(--color-status-neutral-border);
67
66
  color: var(--color-status-neutral-text);
68
67
  }
@@ -76,7 +76,9 @@
76
76
  .ds-card--case-study {
77
77
  height: auto;
78
78
  min-width: 0;
79
- padding: 0;
79
+ /* The cover is inset, not full-bleed: 12px of card around a 12px-radius
80
+ cover keeps the corners concentric with the card's 24px shell. */
81
+ padding: var(--padding-sm-md);
80
82
  gap: 0;
81
83
  overflow: hidden;
82
84
  cursor: pointer;
@@ -103,6 +105,7 @@
103
105
  width: 100%;
104
106
  aspect-ratio: 940 / 480;
105
107
  overflow: hidden;
108
+ border-radius: var(--radius-md);
106
109
  background: var(--color-bg-container-secondary);
107
110
  }
108
111
 
@@ -142,7 +145,9 @@
142
145
  display: flex;
143
146
  flex-direction: column;
144
147
  gap: var(--gap-sm);
145
- padding: var(--padding-lg);
148
+ /* 8px on top of the card's 12px inset: text lands 20px from the card edge
149
+ (the old full-bleed body padding) and sits slightly inside the cover. */
150
+ padding: var(--padding-md) var(--padding-sm) var(--padding-sm-md);
146
151
  }
147
152
 
148
153
  .ds-card--case-study__eyebrow {
@@ -0,0 +1,106 @@
1
+ /* ============================================
2
+ CARD STACK COMPONENT
3
+ A deck showing one card, the rest peeking out
4
+ beneath. Forward flips lift the top card away;
5
+ backward flips drop it in from above. Depth
6
+ offsets are percentages of the card itself, so
7
+ the choreography scales with whatever card the
8
+ deck holds.
9
+ ============================================ */
10
+
11
+ /* Base */
12
+
13
+ .ds-card-stack {
14
+ /* How far each buried card peeks out below the one above it. */
15
+ --ds-card-stack-peek: var(--gap-md);
16
+
17
+ position: relative;
18
+ display: grid;
19
+ border-radius: var(--radius-xl);
20
+ padding-bottom: calc(var(--ds-card-stack-peek) * var(--ds-card-stack-depth, 0));
21
+ outline: none;
22
+ }
23
+
24
+ .ds-card-stack:focus-visible {
25
+ outline: var(--border-md) solid var(--color-action-primary-bg);
26
+ outline-offset: var(--padding-xxs);
27
+ }
28
+
29
+ /* Items — every card occupies the same cell; position is one transform. */
30
+
31
+ .ds-card-stack__item {
32
+ grid-area: 1 / 1;
33
+ /* An opaque page-colour backing behind each card: the system's cards sit on
34
+ translucent surfaces, and without this the buried cards read through the
35
+ top one. Rounded to Card's radius; a deck of differently shaped cards
36
+ repoints --ds-card-stack-card-radius. */
37
+ background: var(--color-bg-page-primary);
38
+ border-radius: var(--ds-card-stack-card-radius, var(--radius-xl));
39
+ transform-origin: 50% 100%;
40
+ transform: translateY(calc(var(--ds-card-stack-peek) * var(--ds-card-stack-pos, 0)))
41
+ scale(calc(1 - 0.04 * var(--ds-card-stack-pos, 0)));
42
+ transition:
43
+ transform var(--motion-duration-deliberate) var(--motion-ease-emphasized),
44
+ opacity var(--motion-duration-deliberate) var(--motion-ease-emphasized);
45
+ }
46
+
47
+ /* Deeper than the visible peek: parked at the last slot, faded out. */
48
+ .ds-card-stack__item--buried {
49
+ opacity: 0;
50
+ transform: translateY(calc(var(--ds-card-stack-peek) * var(--ds-card-stack-depth, 0)))
51
+ scale(calc(1 - 0.04 * var(--ds-card-stack-depth, 0)));
52
+ }
53
+
54
+ /* Flipped away with loop off: lifted out of view, above the deck. */
55
+ .ds-card-stack__item--gone {
56
+ opacity: 0;
57
+ transform: translateY(-12%) scale(0.98);
58
+ }
59
+
60
+ /* Flip choreography — the lift is a fraction of the card's own height. */
61
+
62
+ .ds-card-stack__item--leaving {
63
+ animation: ds-card-stack-leave var(--motion-duration-deliberate) var(--motion-ease-emphasized)
64
+ both;
65
+ }
66
+
67
+ .ds-card-stack__item--entering {
68
+ animation: ds-card-stack-enter var(--motion-duration-deliberate) var(--motion-ease-entrance)
69
+ both;
70
+ }
71
+
72
+ @keyframes ds-card-stack-leave {
73
+ from {
74
+ transform: translateY(0) rotate(0deg) scale(1);
75
+ opacity: 1;
76
+ }
77
+ to {
78
+ transform: translateY(-18%) rotate(-3deg) scale(0.98);
79
+ opacity: 0;
80
+ }
81
+ }
82
+
83
+ @keyframes ds-card-stack-enter {
84
+ from {
85
+ transform: translateY(-18%) rotate(-3deg) scale(0.98);
86
+ opacity: 0;
87
+ }
88
+ to {
89
+ transform: translateY(0) rotate(0deg) scale(1);
90
+ opacity: 1;
91
+ }
92
+ }
93
+
94
+ /* Screen-reader announcement of the deck position. */
95
+
96
+ .ds-card-stack__sr-only {
97
+ position: absolute;
98
+ width: 1px;
99
+ height: 1px;
100
+ padding: 0;
101
+ margin: -1px;
102
+ overflow: hidden;
103
+ clip: rect(0, 0, 0, 0);
104
+ white-space: nowrap;
105
+ border: 0;
106
+ }
@@ -0,0 +1,44 @@
1
+ import { default as React } from 'react';
2
+ /** Props owned by CardStack itself — everything else falls through to the root `<div>`. */
3
+ type CardStackOwnProps = {
4
+ /** The cards, in order. The first child starts on top. */
5
+ children?: React.ReactNode;
6
+ /**
7
+ * Controlled top card, as an index into `children`. Pair with
8
+ * `onIndexChange`. Omit to let the stack own its position.
9
+ */
10
+ index?: number;
11
+ /** Initial top card when uncontrolled. */
12
+ defaultIndex?: number;
13
+ /** Fires when the top card changes, with the new index. */
14
+ onIndexChange?: (index: number) => void;
15
+ /** How many card edges peek out behind the top card. */
16
+ peek?: number;
17
+ /** Wrap from the last card back to the first. Off, the stack stops at both ends. */
18
+ loop?: boolean;
19
+ /**
20
+ * Clicking the top card flips to the next. Clicks that land on a link or
21
+ * control inside the card are left alone, so a card can still carry its
22
+ * own actions.
23
+ */
24
+ advanceOnClick?: boolean;
25
+ /** Accessible name for the stack, e.g. "Open roles". */
26
+ label?: string;
27
+ /** Additional CSS classes */
28
+ className?: string;
29
+ };
30
+ export interface CardStackProps extends CardStackOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof CardStackOwnProps> {
31
+ }
32
+ /**
33
+ * CardStack — a deck of cards showing one at a time, the rest peeking out
34
+ * beneath it. Flipping forward lifts the top card up and away while the next
35
+ * one rises into place; flipping back drops the previous card in from above.
36
+ * The stack is the pattern, not the cards: any children work, Card and
37
+ * EntityCard are the intended fillings, and every card should share one size
38
+ * so the deck keeps its shape.
39
+ *
40
+ * Flip by clicking the top card, or with the arrow keys when the stack has
41
+ * focus. Cards behind the top one are inert until their turn.
42
+ */
43
+ export declare const CardStack: React.ForwardRefExoticComponent<CardStackProps & React.RefAttributes<HTMLDivElement>>;
44
+ export {};
@@ -0,0 +1,140 @@
1
+ "use client";
2
+ import { jsxs, jsx } from "react/jsx-runtime";
3
+ import React, { useState, useRef, useCallback } from "react";
4
+ import "./CardStack.css";
5
+ const INTERACTIVE_SELECTOR = 'a, button, input, select, textarea, summary, [role="button"], [role="link"], [tabindex]';
6
+ const NEXT_KEYS = /* @__PURE__ */ new Set(["ArrowRight", "ArrowDown", " ", "Enter"]);
7
+ const PREV_KEYS = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowUp"]);
8
+ const CardStack = React.forwardRef(
9
+ ({
10
+ children,
11
+ index: indexProp,
12
+ defaultIndex = 0,
13
+ onIndexChange,
14
+ peek = 2,
15
+ loop = true,
16
+ advanceOnClick = true,
17
+ label,
18
+ className = "",
19
+ onClick,
20
+ onKeyDown,
21
+ style,
22
+ ...rest
23
+ }, ref) => {
24
+ const baseClass = "ds-card-stack";
25
+ const items = React.Children.toArray(children);
26
+ const count = items.length;
27
+ const isControlled = indexProp !== void 0;
28
+ const [innerIndex, setInnerIndex] = useState(defaultIndex);
29
+ const index = Math.max(0, Math.min(isControlled ? indexProp : innerIndex, count - 1));
30
+ const [leaving, setLeaving] = useState(false);
31
+ const [entering, setEntering] = useState(false);
32
+ const pendingRef = useRef(null);
33
+ const commit = useCallback(
34
+ (next2) => {
35
+ const normalized = (next2 % count + count) % count;
36
+ if (!isControlled) setInnerIndex(normalized);
37
+ onIndexChange?.(normalized);
38
+ },
39
+ [count, isControlled, onIndexChange]
40
+ );
41
+ const next = useCallback(() => {
42
+ if (count < 2 || leaving || entering) return;
43
+ if (!loop && index >= count - 1) return;
44
+ pendingRef.current = index + 1;
45
+ setLeaving(true);
46
+ }, [count, leaving, entering, loop, index]);
47
+ const prev = useCallback(() => {
48
+ if (count < 2 || leaving || entering) return;
49
+ if (!loop && index <= 0) return;
50
+ commit(index - 1);
51
+ setEntering(true);
52
+ }, [count, leaving, entering, loop, index, commit]);
53
+ const handleTopAnimationEnd = (e) => {
54
+ if (e.target !== e.currentTarget) return;
55
+ if (leaving) {
56
+ setLeaving(false);
57
+ if (pendingRef.current !== null) commit(pendingRef.current);
58
+ pendingRef.current = null;
59
+ }
60
+ if (entering) setEntering(false);
61
+ };
62
+ const handleClick = (e) => {
63
+ onClick?.(e);
64
+ if (!advanceOnClick || e.defaultPrevented) return;
65
+ const target = e.target;
66
+ const control = target.closest(INTERACTIVE_SELECTOR);
67
+ if (control && control !== e.currentTarget) return;
68
+ next();
69
+ };
70
+ const handleKeyDown = (e) => {
71
+ onKeyDown?.(e);
72
+ if (e.defaultPrevented || e.target !== e.currentTarget) return;
73
+ if (NEXT_KEYS.has(e.key)) {
74
+ e.preventDefault();
75
+ next();
76
+ } else if (PREV_KEYS.has(e.key)) {
77
+ e.preventDefault();
78
+ prev();
79
+ }
80
+ };
81
+ const visiblePeek = Math.max(0, Math.min(peek, count - 1));
82
+ const classes = [baseClass, className].filter(Boolean).join(" ");
83
+ return /* @__PURE__ */ jsxs(
84
+ "div",
85
+ {
86
+ ...rest,
87
+ ref,
88
+ className: classes,
89
+ role: "group",
90
+ "aria-roledescription": "card stack",
91
+ "aria-label": label,
92
+ tabIndex: count > 1 ? 0 : void 0,
93
+ style: { ...style, "--ds-card-stack-depth": visiblePeek },
94
+ onClick: handleClick,
95
+ onKeyDown: handleKeyDown,
96
+ children: [
97
+ items.map((child, k) => {
98
+ const raw = k - index;
99
+ const pos = loop ? (raw % count + count) % count : raw;
100
+ const gone = pos < 0;
101
+ const buried = pos > visiblePeek;
102
+ const isTop = pos === 0;
103
+ const itemClasses = [
104
+ `${baseClass}__item`,
105
+ gone ? `${baseClass}__item--gone` : "",
106
+ buried ? `${baseClass}__item--buried` : "",
107
+ isTop && leaving ? `${baseClass}__item--leaving` : "",
108
+ isTop && entering ? `${baseClass}__item--entering` : ""
109
+ ].filter(Boolean).join(" ");
110
+ return /* @__PURE__ */ jsx(
111
+ "div",
112
+ {
113
+ className: itemClasses,
114
+ style: {
115
+ "--ds-card-stack-pos": gone ? 0 : Math.min(pos, visiblePeek),
116
+ zIndex: count - pos
117
+ },
118
+ inert: !isTop || void 0,
119
+ "aria-hidden": !isTop || void 0,
120
+ onAnimationEnd: isTop ? handleTopAnimationEnd : void 0,
121
+ children: child
122
+ },
123
+ k
124
+ );
125
+ }),
126
+ count > 1 && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__sr-only`, role: "status", children: [
127
+ "Card ",
128
+ index + 1,
129
+ " of ",
130
+ count
131
+ ] })
132
+ ]
133
+ }
134
+ );
135
+ }
136
+ );
137
+ CardStack.displayName = "CardStack";
138
+ export {
139
+ CardStack
140
+ };
@@ -110,3 +110,12 @@
110
110
  outline: 2px solid var(--color-action-primary-bg);
111
111
  outline-offset: 2px;
112
112
  }
113
+
114
+ /* Icons take the colour of the text around them. Explicit, not just
115
+ inherited by default: a consumer stylesheet that sets a colour on
116
+ .material-symbols-rounded itself (a common global default) would
117
+ otherwise override inheritance and detach the icon from the state
118
+ colours this component sets on its parents. */
119
+ .ds-carousel .material-symbols-rounded {
120
+ color: inherit;
121
+ }
@@ -1,14 +1,23 @@
1
1
  "use client";
2
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
3
- import React, { useState, useRef, useCallback, useEffect } from "react";
3
+ import React, { useState, useSyncExternalStore, useRef, useCallback, useEffect } from "react";
4
+ import { MOTION_AUTOPLAY_INTERVAL_MS } from "../../tokens/motion.js";
4
5
  import "./Carousel.css";
5
6
  import "../../fonts/material-symbols.css";
7
+ const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
8
+ const subscribeReducedMotion = (callback) => {
9
+ const query = window.matchMedia(REDUCED_MOTION_QUERY);
10
+ query.addEventListener("change", callback);
11
+ return () => query.removeEventListener("change", callback);
12
+ };
13
+ const getReducedMotion = () => window.matchMedia(REDUCED_MOTION_QUERY).matches;
14
+ const getServerReducedMotion = () => false;
6
15
  const Carousel = ({
7
16
  children,
8
17
  showDots = true,
9
18
  showArrows = true,
10
19
  autoPlay = false,
11
- autoPlayInterval = 5e3,
20
+ autoPlayInterval = MOTION_AUTOPLAY_INTERVAL_MS,
12
21
  loop = false,
13
22
  onSlideChange,
14
23
  className = ""
@@ -17,6 +26,11 @@ const Carousel = ({
17
26
  const slideCount = slides.length;
18
27
  const [activeIndex, setActiveIndex] = useState(0);
19
28
  const [paused, setPaused] = useState(false);
29
+ const reducedMotion = useSyncExternalStore(
30
+ subscribeReducedMotion,
31
+ getReducedMotion,
32
+ getServerReducedMotion
33
+ );
20
34
  const containerRef = useRef(null);
21
35
  const baseClass = "ds-carousel";
22
36
  const goTo = useCallback(
@@ -35,10 +49,10 @@ const Carousel = ({
35
49
  const goNext = useCallback(() => goTo(activeIndex + 1), [activeIndex, goTo]);
36
50
  const goPrev = useCallback(() => goTo(activeIndex - 1), [activeIndex, goTo]);
37
51
  useEffect(() => {
38
- if (!autoPlay || paused || slideCount <= 1) return;
52
+ if (!autoPlay || paused || reducedMotion || slideCount <= 1) return;
39
53
  const interval = setInterval(goNext, autoPlayInterval);
40
54
  return () => clearInterval(interval);
41
- }, [autoPlay, paused, autoPlayInterval, goNext, slideCount]);
55
+ }, [autoPlay, paused, reducedMotion, autoPlayInterval, goNext, slideCount]);
42
56
  const handleKeyDown = (e) => {
43
57
  if (e.key === "ArrowLeft") {
44
58
  e.preventDefault();
@@ -48,6 +62,21 @@ const Carousel = ({
48
62
  goNext();
49
63
  }
50
64
  };
65
+ const handleDotKeyDown = (e, index) => {
66
+ let target;
67
+ if (e.key === "ArrowRight") {
68
+ target = loop ? (index + 1) % slideCount : Math.min(index + 1, slideCount - 1);
69
+ } else if (e.key === "ArrowLeft") {
70
+ target = loop ? (index - 1 + slideCount) % slideCount : Math.max(index - 1, 0);
71
+ }
72
+ if (target === void 0) return;
73
+ e.preventDefault();
74
+ e.stopPropagation();
75
+ goTo(target);
76
+ const dotList = e.target.parentElement;
77
+ const dots = dotList?.querySelectorAll(`.${baseClass}__dot`);
78
+ dots?.[target]?.focus();
79
+ };
51
80
  const canGoPrev = loop || activeIndex > 0;
52
81
  const canGoNext = loop || activeIndex < slideCount - 1;
53
82
  const containerClasses = [baseClass, className].filter(Boolean).join(" ");
@@ -59,6 +88,8 @@ const Carousel = ({
59
88
  ref: containerRef,
60
89
  onMouseEnter: () => setPaused(true),
61
90
  onMouseLeave: () => setPaused(false),
91
+ onFocus: () => setPaused(true),
92
+ onBlur: () => setPaused(false),
62
93
  onKeyDown: handleKeyDown,
63
94
  role: "region",
64
95
  "aria-roledescription": "carousel",
@@ -111,8 +142,10 @@ const Carousel = ({
111
142
  {
112
143
  className: `${baseClass}__dot${index === activeIndex ? ` ${baseClass}__dot--active` : ""}`,
113
144
  onClick: () => goTo(index),
145
+ onKeyDown: (e) => handleDotKeyDown(e, index),
114
146
  role: "tab",
115
147
  "aria-selected": index === activeIndex,
148
+ tabIndex: index === activeIndex ? 0 : -1,
116
149
  "aria-label": `Go to slide ${index + 1}`,
117
150
  type: "button"
118
151
  },
@@ -26,6 +26,10 @@ export interface AreaChartProps {
26
26
  summaryItems?: ChartSummaryItem[];
27
27
  /** Chart area height in pixels */
28
28
  height?: number;
29
+ /** Show the legend under a multi-series chart */
30
+ showLegend?: boolean;
31
+ /** Strip the card chrome (border, padding, fill) when the chart sits inside another panel that supplies the surface */
32
+ bare?: boolean;
29
33
  /** Additional CSS classes on the wrapper */
30
34
  className?: string;
31
35
  }
@@ -33,4 +37,4 @@ export interface AreaChartProps {
33
37
  * Area chart built on Recharts with design-system tokens.
34
38
  * Supports multiple stacked or overlapping series with gradient fills.
35
39
  */
36
- export declare const AreaChart: ({ data, xKey, series, stacked, title, subtitle, summaryItems, height, className, }: AreaChartProps) => import("react").JSX.Element;
40
+ export declare const AreaChart: ({ data, xKey, series, stacked, title, subtitle, summaryItems, height, showLegend, bare, className, }: AreaChartProps) => import("react").JSX.Element;
@@ -1,10 +1,10 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useCallback } from "react";
3
3
  import { ResponsiveContainer, AreaChart as AreaChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Area, Legend } from "recharts";
4
+ import { getChartSeriesColors } from "./palette.js";
4
5
  import "./Chart.css";
5
6
  function getCSSVar(name, fallback) {
6
- if (typeof window === "undefined") return fallback;
7
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
7
+ return `var(${name}, ${fallback})`;
8
8
  }
9
9
  function AreaTooltip({ active, payload, label }) {
10
10
  if (!active || !payload?.length) return null;
@@ -26,13 +26,15 @@ const AreaChart = ({
26
26
  subtitle,
27
27
  summaryItems,
28
28
  height = 350,
29
+ showLegend = true,
30
+ bare = false,
29
31
  className = ""
30
32
  }) => {
31
33
  const baseClass = "ds-chart";
32
- const classes = [baseClass, className].filter(Boolean).join(" ");
34
+ const classes = [baseClass, bare ? `${baseClass}--bare` : "", className].filter(Boolean).join(" ");
33
35
  const textSecondary = getCSSVar("--color-text-secondary", "#A2A2A2");
34
36
  const gridColor = getCSSVar("--color-divider", "#232323");
35
- const defaultColor = getCSSVar("--color-action-primary-bg", "#0E6E8F");
37
+ const seriesColors = getChartSeriesColors();
36
38
  const renderTooltip = useCallback(
37
39
  /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
38
40
  (props) => /* @__PURE__ */ jsx(AreaTooltip, { ...props }),
@@ -50,8 +52,8 @@ const AreaChart = ({
50
52
  ] }, i)) })
51
53
  ] }),
52
54
  /* @__PURE__ */ jsx("div", { className: `${baseClass}__body`, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height, children: /* @__PURE__ */ jsxs(AreaChart$1, { data, margin: { top: 8, right: 4, bottom: 0, left: -12 }, children: [
53
- /* @__PURE__ */ jsx("defs", { children: series.map((s) => {
54
- const color = s.color || defaultColor;
55
+ /* @__PURE__ */ jsx("defs", { children: series.map((s, i) => {
56
+ const color = s.color || seriesColors[i % seriesColors.length];
55
57
  return /* @__PURE__ */ jsxs("linearGradient", { id: `area-gradient-${s.dataKey}`, x1: "0", y1: "0", x2: "0", y2: "1", children: [
56
58
  /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: 0.4 }),
57
59
  /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: color, stopOpacity: 0.05 })
@@ -76,13 +78,13 @@ const AreaChart = ({
76
78
  }
77
79
  ),
78
80
  /* @__PURE__ */ jsx(Tooltip, { content: renderTooltip }),
79
- series.map((s) => /* @__PURE__ */ jsx(
81
+ series.map((s, i) => /* @__PURE__ */ jsx(
80
82
  Area,
81
83
  {
82
84
  type: "monotone",
83
85
  dataKey: s.dataKey,
84
86
  name: s.label,
85
- stroke: s.color || defaultColor,
87
+ stroke: s.color || seriesColors[i % seriesColors.length],
86
88
  strokeWidth: 2,
87
89
  fill: `url(#area-gradient-${s.dataKey})`,
88
90
  fillOpacity: s.fillOpacity ?? 1,
@@ -90,7 +92,7 @@ const AreaChart = ({
90
92
  },
91
93
  s.dataKey
92
94
  )),
93
- series.length > 1 && /* @__PURE__ */ jsx(Legend, { wrapperStyle: { color: textSecondary, fontSize: 12 } })
95
+ showLegend && series.length > 1 && /* @__PURE__ */ jsx(Legend, { wrapperStyle: { color: textSecondary, fontSize: 12 } })
94
96
  ] }) }) })
95
97
  ] });
96
98
  };
@@ -23,6 +23,8 @@ export interface BarChartProps {
23
23
  barColor?: string;
24
24
  /** Chart area height in pixels */
25
25
  height?: number;
26
+ /** Strip the card chrome (border, padding, fill) when the chart sits inside another panel that supplies the surface */
27
+ bare?: boolean;
26
28
  /** Additional CSS classes on the wrapper */
27
29
  className?: string;
28
30
  }
@@ -31,4 +33,4 @@ export interface BarChartProps {
31
33
  * Part of the Chart component family — shares wrapper, header,
32
34
  * tooltip, and summary-stat styles with future chart types.
33
35
  */
34
- export declare const BarChart: ({ data, xKey, yKey, dataLabel, title, subtitle, summaryItems, barColor, height, className, }: BarChartProps) => import("react").JSX.Element;
36
+ export declare const BarChart: ({ data, xKey, yKey, dataLabel, title, subtitle, summaryItems, barColor, height, bare, className, }: BarChartProps) => import("react").JSX.Element;
@@ -3,8 +3,7 @@ import { useCallback } from "react";
3
3
  import { ResponsiveContainer, BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Bar } from "recharts";
4
4
  import "./Chart.css";
5
5
  function getCSSVar(name, fallback) {
6
- if (typeof window === "undefined") return fallback;
7
- return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
6
+ return `var(${name}, ${fallback})`;
8
7
  }
9
8
  function ChartTooltip({
10
9
  active,
@@ -33,10 +32,11 @@ const BarChart = ({
33
32
  summaryItems,
34
33
  barColor,
35
34
  height = 350,
35
+ bare = false,
36
36
  className = ""
37
37
  }) => {
38
38
  const baseClass = "ds-chart";
39
- const classes = [baseClass, className].filter(Boolean).join(" ");
39
+ const classes = [baseClass, bare ? `${baseClass}--bare` : "", className].filter(Boolean).join(" ");
40
40
  const resolvedBarColor = barColor || getCSSVar("--color-action-primary-bg", "#0E6E8F");
41
41
  const textSecondary = getCSSVar("--color-text-secondary", "#A2A2A2");
42
42
  const gridColor = getCSSVar("--color-divider", "#232323");
@@ -96,6 +96,51 @@
96
96
 
97
97
  .ds-chart__body {
98
98
  width: 100%;
99
+ /* Anchors the radial centre label overlay. */
100
+ position: relative;
101
+ }
102
+
103
+ /* ============================================
104
+ BARE MODE — the chart sits inside another
105
+ panel that supplies the surface, so its own
106
+ card chrome would double-frame it
107
+ ============================================ */
108
+
109
+ .ds-chart--bare {
110
+ border: none;
111
+ padding: 0;
112
+ background-color: transparent;
113
+ }
114
+
115
+ /* ============================================
116
+ RADIAL CENTRE LABEL — the gauge's headline
117
+ in the donut hole
118
+ ============================================ */
119
+
120
+ .ds-chart__radial-center {
121
+ position: absolute;
122
+ inset: 0;
123
+ display: grid;
124
+ place-content: center;
125
+ justify-items: center;
126
+ gap: var(--gap-xxs);
127
+ pointer-events: none;
128
+ }
129
+
130
+ .ds-chart__radial-center-value {
131
+ font-family: var(--font-sub-display-family);
132
+ font-size: var(--font-sub-display-size);
133
+ font-weight: var(--font-sub-display-weight);
134
+ line-height: var(--font-sub-display-line-height);
135
+ letter-spacing: var(--font-sub-display-letter-spacing);
136
+ color: var(--color-text-primary);
137
+ }
138
+
139
+ .ds-chart__radial-center-sub {
140
+ font-family: var(--font-caption-family);
141
+ font-size: var(--font-caption-size);
142
+ line-height: var(--font-caption-line-height);
143
+ color: var(--color-text-secondary);
99
144
  }
100
145
 
101
146
  /* ============================================