@robr0/design-system 0.3.0 → 0.4.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 (45) hide show
  1. package/README.md +30 -27
  2. package/components/AgentStatus/AgentStatus.css +436 -0
  3. package/components/AgentStatus/AgentStatus.d.ts +37 -0
  4. package/components/AgentStatus/AgentStatus.js +53 -0
  5. package/components/AgentStatus/AgentStatusPatterns.d.ts +17 -0
  6. package/components/AgentStatus/AgentStatusPatterns.js +20 -0
  7. package/components/AlertDialog/AlertDialog.css +5 -5
  8. package/components/Button/Button.d.ts +14 -2
  9. package/components/ButtonGroup/ButtonGroup.js +2 -9
  10. package/components/Checkbox/Checkbox.d.ts +17 -3
  11. package/components/CircularButton/CircularButton.d.ts +9 -3
  12. package/components/CircularButton/CircularButton.js +4 -2
  13. package/components/Combobox/Combobox.d.ts +10 -2
  14. package/components/ContextMenu/ContextMenu.css +0 -4
  15. package/components/DateInput/DateInput.d.ts +5 -1
  16. package/components/Dialog/Dialog.css +5 -5
  17. package/components/Dropdown/Dropdown.css +2 -4
  18. package/components/Dropdown/Dropdown.d.ts +10 -2
  19. package/components/DropdownMenu/DropdownMenu.css +2 -4
  20. package/components/FileInput/FileInput.d.ts +5 -1
  21. package/components/Input/Input.d.ts +5 -1
  22. package/components/LinkList/LinkList.d.ts +1 -0
  23. package/components/RadioButton/RadioButton.d.ts +17 -3
  24. package/components/Reasoning/Reasoning.css +152 -0
  25. package/components/Reasoning/Reasoning.d.ts +36 -0
  26. package/components/Reasoning/Reasoning.js +69 -0
  27. package/components/SelectionCard/SelectionCard.d.ts +5 -1
  28. package/components/Slider/Slider.d.ts +5 -1
  29. package/components/Textarea/Textarea.d.ts +5 -1
  30. package/components/Timeline/Timeline.d.ts +2 -0
  31. package/components/ToggleGroup/ToggleGroup.d.ts +10 -2
  32. package/components/ToggleSwitch/ToggleSwitch.d.ts +10 -2
  33. package/components/ToolCall/ToolCall.css +213 -0
  34. package/components/ToolCall/ToolCall.d.ts +43 -0
  35. package/components/ToolCall/ToolCall.js +89 -0
  36. package/components/registry.json +32 -7
  37. package/components/registry.json.d.ts +32 -7
  38. package/components/registry.json.js +2 -2
  39. package/index.d.ts +4 -0
  40. package/index.js +8 -0
  41. package/package.json +8 -4
  42. package/tokens/registry.json +1 -0
  43. package/tokens/registry.json.d.ts +1 -0
  44. package/tokens/registry.json.js +1 -1
  45. package/tokens/tokens-motion.css +3 -1
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
- # robr0 Portfolio + Design System
1
+ # robr0: portfolio + AI-ready design system
2
2
 
3
3
  [![CI](https://github.com/robritacca-dotcom/design-system/actions/workflows/ci.yml/badge.svg)](https://github.com/robritacca-dotcom/design-system/actions/workflows/ci.yml)
4
4
  <!-- npm-badge:start -->
5
5
  [![npm](https://img.shields.io/npm/v/@robr0%2Fdesign-system?logo=npm&color=CB3837)](https://www.npmjs.com/package/@robr0/design-system)
6
6
  <!-- npm-badge:end -->
7
7
 
8
- A personal portfolio site built on a custom React design system I designed and engineered from scratch. The DS isn't a side project it's the backbone every portfolio page is built on, ensuring the work is presented consistently and with the same craft standards I apply professionally.
8
+ A personal portfolio site built on an AI-ready React design system, designed and engineered from scratch. Claude Code builds the components from the written specs in this repo (`CLAUDE.md` for the rules, `design.md` for the design language, `content-design.md` for how every word reads), and generated registries keep this README and the docs site from drifting. The design system is the backbone every portfolio page is built on, so the work is presented consistently and to the same craft standards throughout.
9
9
 
10
10
  **[→ Live site](https://robertritacca.com/)** · **[→ Storybook](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)**
11
11
 
12
- The **live site** is the portfolio built on the design system. **Storybook** is the interactive component explorer for the design system itself two separate parts of this repo.
12
+ The **live site** is the portfolio built on the design system. **Storybook** is the interactive component explorer for the design system itself. They are two separate parts of this repo.
13
13
 
14
14
  ---
15
15
 
@@ -17,8 +17,8 @@ The **live site** is the portfolio built on the design system. **Storybook** is
17
17
 
18
18
  | Part | Description |
19
19
  |---|---|
20
- | **Portfolio website** (`/website`) | Next.js app with case studies, work history, writing, and about pages all built exclusively with the design system components below. **[Live site →](https://robertritacca.com/)** |
21
- | **Design system** (`/src`) | <!-- component-count -->61<!-- /component-count --> React components, a three-tier token architecture, dark mode, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
20
+ | **Portfolio website** (`/website`) | Next.js app with case studies, work history, writing, and about pages, all built exclusively with the design system components below. **[Live site →](https://robertritacca.com/)** |
21
+ | **Design system** (`/src`) | <!-- component-count -->64<!-- /component-count --> React components, a three-tier token architecture, dark mode, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
22
22
 
23
23
  ---
24
24
 
@@ -27,12 +27,12 @@ The **live site** is the portfolio built on the design system. **Storybook** is
27
27
  ### Components
28
28
 
29
29
  <!-- component-list:start -->
30
- Accordion · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Checkbox · Chip · Circular button · Code block · Colour picker · Combobox · Command palette · Contact card · Context menu · Contribution graph · Date input · Date picker · Dialog · Divider · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Field · Figure · File input · Input · Instructions · Kbd · Link list · Navigation · Pagination · Popover · Progress bar · Quote · Radio button · Section title · Segmented control · Selection card · Skeleton · Slider · Spinner · Stat · Swatch · Table · Tabs · Textarea · Timeline · Toast · Toggle group · Toggle switch · Tooltip
30
+ Accordion · Agent status · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Checkbox · Chip · Circular button · Code block · Colour picker · Combobox · Command palette · Contact card · Context menu · Contribution graph · Date input · Date picker · Dialog · Divider · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Field · Figure · File input · Input · Instructions · Kbd · Link list · Navigation · Pagination · Popover · Progress bar · Quote · Radio button · Reasoning · Section title · Segmented control · Selection card · Skeleton · Slider · Spinner · Stat · Swatch · Table · Tabs · Textarea · Timeline · Toast · Toggle group · Toggle switch · Tool call · Tooltip
31
31
  <!-- component-list:end -->
32
32
 
33
33
  ### Using the package
34
34
 
35
- The design system is published as [`@robr0/design-system`](https://www.npmjs.com/package/@robr0/design-system) (React 19+ is a peer dependency) the **[full setup guide](https://www.robertritacca.com/docs/get-started)** covers install, dark mode, fonts, and re-theming:
35
+ The design system is published as [`@robr0/design-system`](https://www.npmjs.com/package/@robr0/design-system) (React 19+ is a peer dependency). The **[full setup guide](https://robertritacca.com/docs/get-started)** covers install, dark mode, fonts, and re-theming:
36
36
 
37
37
  ```bash
38
38
  npm install @robr0/design-system
@@ -53,14 +53,14 @@ Deep imports work too (`@robr0/design-system/components/Button/Button`). Chart c
53
53
  import { BarChart, LineChart } from '@robr0/design-system/charts';
54
54
  ```
55
55
 
56
- **Theming and customization** happen through CSS variables no configuration API. Components are provider-free, with one exception: wrap your tree in `ToastProvider` if (and only if) you use the toast queue via `useToast`.
56
+ **Theming and customisation** happen through CSS variables; there is no configuration API. Components are provider-free, with one exception: wrap your tree in `ToastProvider` if (and only if) you use the toast queue via `useToast`.
57
57
 
58
58
  - **Dark mode**: set `data-theme="dark"` on the root element (light is the default).
59
59
  - **Font**: the whole type scale chains to one token. Load any font you like and override it:
60
60
  ```css
61
61
  :root { --font-family-primary: 'Inter', sans-serif; }
62
62
  ```
63
- - **Colors, radius, spacing**: every semantic token chains to a primitive, so overriding a primitive re-themes everything built on it:
63
+ - **Colours, radius, spacing**: every semantic token chains to a primitive, so overriding a primitive re-themes everything built on it:
64
64
  ```css
65
65
  :root {
66
66
  --primitive-teal-07: #7C3AED; /* your brand color becomes the action color */
@@ -68,11 +68,11 @@ import { BarChart, LineChart } from '@robr0/design-system/charts';
68
68
  }
69
69
  ```
70
70
 
71
- Icons use a bundled Material Symbols Rounded variable font (woff2) no extra setup. Nunito Sans, the system's default typeface, is intentionally *not* bundled: load it yourself (e.g. Google Fonts or `next/font`) or override `--font-family-primary`.
71
+ Icons use a bundled Material Symbols Rounded variable font (woff2), with no extra setup. Nunito Sans, the system's default typeface, is intentionally *not* bundled: load it yourself (e.g. Google Fonts or `next/font`) or override `--font-family-primary`.
72
72
 
73
73
  ### Token architecture
74
74
 
75
- Tokens flow in one direction primitives are never referenced directly in components:
75
+ Tokens flow in one direction, and primitives are never referenced directly in components:
76
76
 
77
77
  ```
78
78
  tokens-primitives.css --primitive-teal-07: #118AB2
@@ -82,40 +82,43 @@ tokens-light/dark.css --color-action-primary-bg: var(--primitive-teal-07)
82
82
  Component CSS background-color: var(--color-action-primary-bg)
83
83
  ```
84
84
 
85
- Dark mode is driven by `data-theme="dark"` on the root element no `prefers-color-scheme` queries in components.
85
+ Dark mode is driven by `data-theme="dark"` on the root element, with no `prefers-color-scheme` queries in components.
86
86
 
87
87
  ### Design principles
88
88
 
89
- - **Single typeface**: Nunito Sans (weight 300 for display, 600 for headings, 400/500 for body)
90
- - **White-floor UI**: depth comes from a container color ramp, not box shadows
91
- - **Teal is the action color**: primary buttons and focus rings only — never decorative
92
- - **Five status variants**: info, positive, warning, error, neutral — shared tokens across Badge, Alert, Toast, ProgressBar
93
- - **Material Symbols Rounded** for all icons
89
+ Stated as token roles on purpose what each role resolves to is the theme, and yours to override:
94
90
 
95
- Full spec in [`design.md`](design.md).
91
+ - **Style with semantic tokens, never raw values** — that is what makes one primitive override cascade through everything
92
+ - **One typeface**, hierarchy carried by weight contrast
93
+ - **The primary-action token means "click here"**: CTAs and focus only, never decoration
94
+ - **Shape is a per-element-type token**, not a per-instance choice
95
+ - **Five status roles** (info, positive, warning, error, neutral) shared by every status-bearing component
96
+ - **Depth is token-owned**: the container ramp plus the system's elevation tokens — components don't bring their own shadows
97
+
98
+ The defaults behind each role, and every component spec, live in [`design.md`](design.md).
96
99
 
97
100
  ---
98
101
 
99
102
  ## Tech
100
103
 
101
- - **React 19 + TypeScript** component library
102
- - **Vite 7** dev server and library build
103
- - **Next.js 16** portfolio site and DS documentation
104
- - **Storybook 10** component explorer
105
- - **Vitest + Playwright** every Storybook story runs as a render test in headless Chromium
106
- - **CSS custom properties** all theming via semantic tokens, no CSS-in-JS
104
+ - **React 19 + TypeScript**: component library
105
+ - **Vite 7**: dev server and library build
106
+ - **Next.js 16**: portfolio site and design system documentation
107
+ - **Storybook 10**: component explorer
108
+ - **Vitest + Playwright**: every Storybook story runs as a render test in headless Chromium
109
+ - **CSS custom properties**: all theming via semantic tokens, no CSS-in-JS
107
110
 
108
111
  ---
109
112
 
110
113
  ## Quality & CI
111
114
 
112
- Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: **lint + library build**, **story tests** (every Storybook story rendered in headless Chromium via Vitest), **Storybook build**, and **website lint + build**. The same checklist runs locally with one command:
115
+ Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: lint + library build, story tests (every Storybook story rendered in headless Chromium via Vitest), Storybook build, and website lint + build. The same checklist runs locally with one command:
113
116
 
114
117
  ```bash
115
118
  npm run verify # lint + library type-check + package build + story tests + Storybook build + website lint + build
116
119
  ```
117
120
 
118
- CI also guards against documentation drift: generated surfaces (this README's component count and list, the website's skills pages, the published CLAUDE.md/design.md blueprints) are rebuilt from their source registries on every build, and CI fails if the committed copies are stale. The numbers on the site are never hand-written.
121
+ CI also guards against documentation drift: generated surfaces (this README's component count and list, the website's skills pages, the published CLAUDE.md, design.md, and content-design.md blueprints) are rebuilt from their source registries on every build, and CI fails if the committed copies are stale. The numbers on the site are never hand-written.
119
122
 
120
123
  ---
121
124
 
@@ -125,7 +128,7 @@ CI also guards against documentation drift: generated surfaces (this README's co
125
128
  # Storybook (the library's dev sandbox)
126
129
  npm run storybook # http://localhost:6006
127
130
 
128
- # Portfolio + documentation website (npm workspace install once at the root)
131
+ # Portfolio + documentation website (npm workspace: install once at the root)
129
132
  npm install
130
133
  npm run dev --workspace website # http://localhost:3000
131
134
  ```
@@ -0,0 +1,436 @@
1
+ /* ============================================
2
+ AGENT STATUS COMPONENT
3
+ A dot-matrix indicator plus a line of status
4
+ text, reporting what an agent is doing now.
5
+
6
+ The matrix is a 4x3 grid of 12 dots, numbered
7
+ row-major:
8
+
9
+ 1 2 3 4
10
+ 5 6 7 8
11
+ 9 10 11 12
12
+
13
+ Every pattern is choreography over that same
14
+ grid: each dot is told which of the cycle's
15
+ twelve slots it lights in (--ds-agent-step),
16
+ and dots a pattern never lights opt out of the
17
+ animation entirely. One grid, one cycle length,
18
+ fifteen patterns — so changing pattern never
19
+ changes the indicator's footprint or rhythm.
20
+ ============================================ */
21
+
22
+ .ds-agent-status {
23
+ --ds-agent-color: var(--color-text-secondary);
24
+ --ds-agent-label-color: var(--color-text-secondary);
25
+ --ds-agent-dot-dim: 0.2;
26
+ --ds-agent-dot-rest: 0.82;
27
+ --ds-agent-cycle: var(--motion-duration-loop-matrix);
28
+ --ds-agent-slot: calc(var(--ds-agent-cycle) / 12);
29
+ --ds-agent-matrix-size: var(--icon-size-sm);
30
+
31
+ display: inline-flex;
32
+ align-items: center;
33
+ gap: var(--gap-sm);
34
+ font-family: var(--font-paragraph-sm-family);
35
+ font-size: var(--font-paragraph-sm-size);
36
+ font-weight: var(--font-paragraph-sm-weight);
37
+ line-height: var(--font-paragraph-sm-line-height);
38
+ letter-spacing: var(--font-paragraph-sm-letter-spacing);
39
+ }
40
+
41
+ /* ============================================
42
+ MATRIX
43
+ ============================================ */
44
+
45
+ .ds-agent-status__matrix {
46
+ display: grid;
47
+ grid-template-columns: repeat(4, 1fr);
48
+ gap: var(--gap-xxs);
49
+ flex: none;
50
+ width: var(--ds-agent-matrix-size);
51
+ }
52
+
53
+ .ds-agent-status__dot {
54
+ aspect-ratio: 1;
55
+ border-radius: var(--radius-full);
56
+ background-color: var(--ds-agent-color);
57
+ opacity: var(--ds-agent-dot-dim);
58
+ transform: scale(var(--ds-agent-dot-rest));
59
+ }
60
+
61
+ /* ============================================
62
+ LABEL
63
+ ============================================ */
64
+
65
+ .ds-agent-status__label {
66
+ color: var(--ds-agent-label-color);
67
+ min-width: 0;
68
+ }
69
+
70
+ /* A gradient between the secondary text colour the label already sits at and
71
+ the primary text colour, sweeping left to right: with an oversized
72
+ background image, background-position 100% shows its right edge, 0% its
73
+ left, so counting down walks the bright band across the text. */
74
+ .ds-agent-status__label--shimmer {
75
+ background-image: linear-gradient(
76
+ 100deg,
77
+ var(--ds-agent-label-color) 35%,
78
+ var(--color-text-primary) 50%,
79
+ var(--ds-agent-label-color) 65%
80
+ );
81
+ background-size: 250% 100%;
82
+ background-clip: text;
83
+ -webkit-background-clip: text;
84
+ color: transparent;
85
+ animation: ds-agent-shimmer var(--motion-duration-loop-shimmer)
86
+ var(--motion-ease-linear) infinite;
87
+ }
88
+
89
+ /* ============================================
90
+ STATES
91
+ The three working states are deliberately
92
+ monochrome. Colour is reserved for the states
93
+ where it carries meaning: waiting on a person,
94
+ finished, failed.
95
+ ============================================ */
96
+
97
+ .ds-agent-status--idle {
98
+ --ds-agent-color: var(--color-text-tertiary);
99
+ --ds-agent-label-color: var(--color-text-tertiary);
100
+ }
101
+
102
+ .ds-agent-status--waiting {
103
+ --ds-agent-color: var(--color-status-warning-text);
104
+ --ds-agent-label-color: var(--color-status-warning-text);
105
+ }
106
+
107
+ .ds-agent-status--done {
108
+ --ds-agent-color: var(--color-status-positive-text);
109
+ --ds-agent-label-color: var(--color-status-positive-text);
110
+ }
111
+
112
+ .ds-agent-status--error {
113
+ --ds-agent-color: var(--color-status-error-text);
114
+ --ds-agent-label-color: var(--color-status-error-text);
115
+ }
116
+
117
+ /* Terminal states fill the matrix solid — the shape itself reads as finished */
118
+ .ds-agent-status--done .ds-agent-status__dot,
119
+ .ds-agent-status--error .ds-agent-status__dot {
120
+ opacity: 1;
121
+ transform: none;
122
+ }
123
+
124
+ /* ============================================
125
+ SIZES
126
+ ============================================ */
127
+
128
+ .ds-agent-status--md {
129
+ --ds-agent-matrix-size: var(--icon-size-md);
130
+
131
+ gap: var(--gap-sm-md);
132
+ font-family: var(--font-paragraph-family);
133
+ font-size: var(--font-paragraph-size);
134
+ font-weight: var(--font-paragraph-weight);
135
+ line-height: var(--font-paragraph-line-height);
136
+ letter-spacing: var(--font-paragraph-letter-spacing);
137
+ }
138
+
139
+ /* ============================================
140
+ VARIANTS
141
+ ============================================ */
142
+
143
+ .ds-agent-status--bar {
144
+ display: flex;
145
+ width: 100%;
146
+ padding: var(--padding-xs) var(--padding-sm-md);
147
+ background-color: var(--color-bg-container-secondary);
148
+ border: var(--border-xs) solid var(--color-bg-container-border);
149
+ border-radius: var(--radius-md);
150
+ }
151
+
152
+ /* ============================================
153
+ ANIMATION ENGINE
154
+ Each dot blinks once per cycle, offset by the
155
+ slot its pattern assigns it.
156
+ ============================================ */
157
+
158
+ .ds-agent-status--animated .ds-agent-status__dot {
159
+ animation: ds-agent-dot var(--ds-agent-cycle) var(--motion-ease-standard) infinite;
160
+ animation-delay: calc(var(--ds-agent-step, 0) * var(--ds-agent-slot));
161
+ }
162
+
163
+ /* A dot ramps up over roughly one slot and falls away over three, so the
164
+ leading edge arrives as smoothly as the trail leaves. Popping straight to
165
+ full opacity reads as a strobe at this size. */
166
+ @keyframes ds-agent-dot {
167
+ 0% {
168
+ opacity: var(--ds-agent-dot-dim);
169
+ transform: scale(var(--ds-agent-dot-rest));
170
+ }
171
+
172
+ 10% {
173
+ opacity: 1;
174
+ transform: scale(1);
175
+ }
176
+
177
+ 35%,
178
+ 100% {
179
+ opacity: var(--ds-agent-dot-dim);
180
+ transform: scale(var(--ds-agent-dot-rest));
181
+ }
182
+ }
183
+
184
+ /* Fill sweep holds each column lit until the band has passed */
185
+ @keyframes ds-agent-dot-fill {
186
+ 0% {
187
+ opacity: var(--ds-agent-dot-dim);
188
+ transform: scale(var(--ds-agent-dot-rest));
189
+ }
190
+
191
+ 8%,
192
+ 74% {
193
+ opacity: 1;
194
+ transform: scale(1);
195
+ }
196
+
197
+ 86%,
198
+ 100% {
199
+ opacity: var(--ds-agent-dot-dim);
200
+ transform: scale(var(--ds-agent-dot-rest));
201
+ }
202
+ }
203
+
204
+ /* Breathe swells the whole matrix rather than travelling through it */
205
+ @keyframes ds-agent-dot-breathe {
206
+ 0%,
207
+ 100% {
208
+ opacity: var(--ds-agent-dot-dim);
209
+ transform: scale(var(--ds-agent-dot-rest));
210
+ }
211
+
212
+ 50% {
213
+ opacity: 1;
214
+ transform: scale(1);
215
+ }
216
+ }
217
+
218
+ @keyframes ds-agent-shimmer {
219
+ from {
220
+ background-position-x: 100%;
221
+ }
222
+
223
+ to {
224
+ background-position-x: 0%;
225
+ }
226
+ }
227
+
228
+ /* ============================================
229
+ PATTERNS
230
+ One block per pattern: the slot each dot
231
+ lights in, and the dots it leaves dark.
232
+ ============================================ */
233
+
234
+ /* Braille — a 2x3 cell rotating, the right half dark */
235
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
236
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 2; }
237
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 4; }
238
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 6; }
239
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 8; }
240
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 10; }
241
+
242
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(3),
243
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(4),
244
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(7),
245
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(8),
246
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(11),
247
+ .ds-agent-status--braille .ds-agent-status__dot:nth-child(12) {
248
+ animation: none;
249
+ }
250
+
251
+ /* Orbit — clockwise around the perimeter, core dark */
252
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
253
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 1; }
254
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 2; }
255
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 3; }
256
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 4; }
257
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 5; }
258
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 6; }
259
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 7; }
260
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 8; }
261
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 9; }
262
+
263
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(6),
264
+ .ds-agent-status--orbit .ds-agent-status__dot:nth-child(7) {
265
+ animation: none;
266
+ }
267
+
268
+ /* Breathe — every dot together, on its own swell */
269
+ .ds-agent-status--animated.ds-agent-status--breathe .ds-agent-status__dot {
270
+ animation-name: ds-agent-dot-breathe;
271
+ }
272
+
273
+ /* Snake — boustrophedon, one dot at a time */
274
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
275
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 1; }
276
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 2; }
277
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 3; }
278
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 4; }
279
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 5; }
280
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 6; }
281
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 7; }
282
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 8; }
283
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 9; }
284
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 10; }
285
+ .ds-agent-status--snake .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 11; }
286
+
287
+ /* Fill sweep — columns light and hold, a band wiping left to right */
288
+ .ds-agent-status--animated.ds-agent-status--fill-sweep .ds-agent-status__dot {
289
+ animation-name: ds-agent-dot-fill;
290
+ }
291
+
292
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n + 1) { --ds-agent-step: 0; }
293
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n + 2) { --ds-agent-step: 3; }
294
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n + 3) { --ds-agent-step: 6; }
295
+ .ds-agent-status--fill-sweep .ds-agent-status__dot:nth-child(4n) { --ds-agent-step: 9; }
296
+
297
+ /* Pulse — rings expanding from the core */
298
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(6),
299
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 0; }
300
+
301
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(2),
302
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(3),
303
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(5),
304
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(8),
305
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(10),
306
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 4; }
307
+
308
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(1),
309
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(4),
310
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(9),
311
+ .ds-agent-status--pulse .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 8; }
312
+
313
+ /* Columns — odd columns, then even */
314
+ .ds-agent-status--columns .ds-agent-status__dot:nth-child(odd) { --ds-agent-step: 0; }
315
+ .ds-agent-status--columns .ds-agent-status__dot:nth-child(even) { --ds-agent-step: 6; }
316
+
317
+ /* Checkerboard — the two parities alternating */
318
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(1),
319
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(3),
320
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(6),
321
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(8),
322
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(9),
323
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 0; }
324
+
325
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(2),
326
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(4),
327
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(5),
328
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(7),
329
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(10),
330
+ .ds-agent-status--checkerboard .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 6; }
331
+
332
+ /* Scan — a single column travelling left to right */
333
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n + 1) { --ds-agent-step: 0; }
334
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n + 2) { --ds-agent-step: 3; }
335
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n + 3) { --ds-agent-step: 6; }
336
+ .ds-agent-status--scan .ds-agent-status__dot:nth-child(4n) { --ds-agent-step: 9; }
337
+
338
+ /* Rain — drops falling down each column, columns out of phase */
339
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
340
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 2; }
341
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 4; }
342
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 4; }
343
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 6; }
344
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 8; }
345
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 2; }
346
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 4; }
347
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 6; }
348
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 6; }
349
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 8; }
350
+ .ds-agent-status--rain .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 10; }
351
+
352
+ /* Cascade — column by column, top to bottom */
353
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
354
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 1; }
355
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 2; }
356
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 3; }
357
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 4; }
358
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 5; }
359
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 6; }
360
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 7; }
361
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 8; }
362
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 9; }
363
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 10; }
364
+ .ds-agent-status--cascade .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 11; }
365
+
366
+ /* Sparkle — a fixed scatter, so it reads as random without being random */
367
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 7; }
368
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(2) { --ds-agent-step: 2; }
369
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(3) { --ds-agent-step: 10; }
370
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(4) { --ds-agent-step: 4; }
371
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 0; }
372
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 9; }
373
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(7) { --ds-agent-step: 5; }
374
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 11; }
375
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 3; }
376
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 8; }
377
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 1; }
378
+ .ds-agent-status--sparkle .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 6; }
379
+
380
+ /* Wave rows — whole rows breaking top to bottom */
381
+ .ds-agent-status--wave-rows .ds-agent-status__dot:nth-child(-n + 4) { --ds-agent-step: 0; }
382
+ .ds-agent-status--wave-rows .ds-agent-status__dot:nth-child(n + 5):nth-child(-n + 8) { --ds-agent-step: 4; }
383
+ .ds-agent-status--wave-rows .ds-agent-status__dot:nth-child(n + 9) { --ds-agent-step: 8; }
384
+
385
+ /* Helix — two strands crossing, everything off-strand dark */
386
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(1),
387
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 0; }
388
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(6) { --ds-agent-step: 3; }
389
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(3),
390
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 6; }
391
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(8) { --ds-agent-step: 9; }
392
+
393
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(2),
394
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(4),
395
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(5),
396
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(7),
397
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(10),
398
+ .ds-agent-status--helix .ds-agent-status__dot:nth-child(12) {
399
+ animation: none;
400
+ }
401
+
402
+ /* Diagonal swipe — anti-diagonals, top-left to bottom-right */
403
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(1) { --ds-agent-step: 0; }
404
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(2),
405
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(5) { --ds-agent-step: 2; }
406
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(3),
407
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(6),
408
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(9) { --ds-agent-step: 4; }
409
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(4),
410
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(7),
411
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(10) { --ds-agent-step: 6; }
412
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(8),
413
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(11) { --ds-agent-step: 8; }
414
+ .ds-agent-status--diagonal-swipe .ds-agent-status__dot:nth-child(12) { --ds-agent-step: 10; }
415
+
416
+ /* ============================================
417
+ REDUCED MOTION
418
+ The global guard collapses every animation to
419
+ its final frame, which for the matrix is dark.
420
+ Park it at a legible resting state instead and
421
+ let the label carry the meaning on its own.
422
+ ============================================ */
423
+
424
+ @media (prefers-reduced-motion: reduce) {
425
+ .ds-agent-status--animated .ds-agent-status__dot {
426
+ animation: none;
427
+ opacity: 0.55;
428
+ transform: none;
429
+ }
430
+
431
+ .ds-agent-status__label--shimmer {
432
+ background-image: none;
433
+ color: var(--ds-agent-label-color);
434
+ animation: none;
435
+ }
436
+ }
@@ -0,0 +1,37 @@
1
+ import { default as React } from 'react';
2
+ import { AgentStatusPattern, AgentStatusState } from './AgentStatusPatterns';
3
+ /** Props owned by AgentStatus itself — everything else falls through to the root element. */
4
+ type AgentStatusOwnProps = {
5
+ /** What the agent is doing. Drives the colour, the default label, and whether the matrix animates. */
6
+ state?: AgentStatusState;
7
+ /** Status text. Falls back to a default for the state. Ignored when `children` are given. */
8
+ label?: string;
9
+ /** Which dot-matrix choreography to run. */
10
+ pattern?: AgentStatusPattern;
11
+ /**
12
+ * Sweep a highlight across the label while the agent is active. Defaults to
13
+ * on for `thinking`, `working` and `waiting`, off for the resting states.
14
+ */
15
+ shimmer?: boolean;
16
+ /** Indicator and text scale. */
17
+ size?: 'sm' | 'md';
18
+ /** `inline` sits in a line of content; `bar` is a full-width row for the top of a panel. */
19
+ variant?: 'inline' | 'bar';
20
+ /** Additional CSS classes */
21
+ className?: string;
22
+ /** Status text, when it needs markup the `label` string cannot express. */
23
+ children?: React.ReactNode;
24
+ };
25
+ export interface AgentStatusProps extends AgentStatusOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof AgentStatusOwnProps> {
26
+ }
27
+ /**
28
+ * AgentStatus reports what an agent is doing right now: a dot-matrix indicator
29
+ * beside a line of status text.
30
+ *
31
+ * Colour carries meaning rather than decoration — the three working states are
32
+ * monochrome, and only `waiting`, `done` and `error` take a status colour.
33
+ * The whole thing is one polite live region, so a screen reader hears the state
34
+ * change without the caller wiring anything up.
35
+ */
36
+ export declare const AgentStatus: React.ForwardRefExoticComponent<AgentStatusProps & React.RefAttributes<HTMLDivElement>>;
37
+ export {};
@@ -0,0 +1,53 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./AgentStatus.css";
4
+ const DOT_COUNT = 12;
5
+ const DEFAULT_LABELS = {
6
+ idle: "Idle",
7
+ thinking: "Thinking",
8
+ working: "Working",
9
+ waiting: "Waiting for you",
10
+ done: "Done",
11
+ error: "Failed"
12
+ };
13
+ const ACTIVE_STATES = ["thinking", "working", "waiting"];
14
+ const AgentStatus = React.forwardRef(
15
+ ({
16
+ state = "thinking",
17
+ label,
18
+ pattern = "orbit",
19
+ shimmer,
20
+ size = "sm",
21
+ variant = "inline",
22
+ className = "",
23
+ children,
24
+ ...rest
25
+ }, ref) => {
26
+ const baseClass = "ds-agent-status";
27
+ const isActive = ACTIVE_STATES.includes(state);
28
+ const isShimmering = shimmer ?? isActive;
29
+ const classes = [
30
+ baseClass,
31
+ `${baseClass}--${state}`,
32
+ `${baseClass}--${pattern}`,
33
+ `${baseClass}--${size}`,
34
+ `${baseClass}--${variant}`,
35
+ isActive ? `${baseClass}--animated` : "",
36
+ className
37
+ ].filter(Boolean).join(" ");
38
+ return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, role: "status", children: [
39
+ /* @__PURE__ */ jsx("span", { className: `${baseClass}__matrix`, "aria-hidden": "true", children: Array.from({ length: DOT_COUNT }, (_, i) => /* @__PURE__ */ jsx("span", { className: `${baseClass}__dot` }, i)) }),
40
+ /* @__PURE__ */ jsx(
41
+ "span",
42
+ {
43
+ className: `${baseClass}__label${isShimmering ? ` ${baseClass}__label--shimmer` : ""}`,
44
+ children: children ?? label ?? DEFAULT_LABELS[state]
45
+ }
46
+ )
47
+ ] });
48
+ }
49
+ );
50
+ AgentStatus.displayName = "AgentStatus";
51
+ export {
52
+ AgentStatus
53
+ };