@robr0/design-system 0.11.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.
- package/README.md +4 -4
- package/charts.d.ts +1 -0
- package/charts.js +2 -0
- package/components/AppLayout/AppLayout.d.ts +7 -1
- package/components/AppLayout/AppLayout.js +2 -1
- package/components/AppSidebar/AppSidebar.css +104 -40
- package/components/AppSidebar/AppSidebar.d.ts +14 -1
- package/components/AppSidebar/AppSidebar.js +26 -17
- package/components/Badge/Badge.css +4 -5
- package/components/Chart/AreaChart.d.ts +5 -1
- package/components/Chart/AreaChart.js +5 -4
- package/components/Chart/BarChart.d.ts +3 -1
- package/components/Chart/BarChart.js +3 -3
- package/components/Chart/Chart.css +45 -0
- package/components/Chart/ComboChart.d.ts +40 -0
- package/components/Chart/ComboChart.js +142 -0
- package/components/Chart/LineChart.d.ts +3 -1
- package/components/Chart/LineChart.js +3 -3
- package/components/Chart/PieChart.d.ts +3 -1
- package/components/Chart/PieChart.js +3 -3
- package/components/Chart/RadarChart.d.ts +3 -1
- package/components/Chart/RadarChart.js +3 -3
- package/components/Chart/RadialChart.d.ts +7 -1
- package/components/Chart/RadialChart.js +51 -43
- package/components/Chart/ScatterChart.d.ts +3 -1
- package/components/Chart/ScatterChart.js +3 -3
- package/components/Chart/StackedBarChart.d.ts +3 -1
- package/components/Chart/StackedBarChart.js +3 -3
- package/components/Chart/Treemap.d.ts +3 -1
- package/components/Chart/Treemap.js +3 -3
- package/components/Chart/palette.js +1 -3
- package/components/FunnelChart/FunnelChart.css +41 -0
- package/components/FunnelChart/FunnelChart.d.ts +40 -0
- package/components/FunnelChart/FunnelChart.js +48 -0
- package/components/LegendTile/LegendTile.css +60 -0
- package/components/LegendTile/LegendTile.d.ts +30 -0
- package/components/LegendTile/LegendTile.js +21 -0
- package/components/Panel/Panel.css +26 -0
- package/components/Panel/Panel.d.ts +22 -0
- package/components/Panel/Panel.js +19 -0
- package/components/Stat/Stat.css +35 -2
- package/components/Stat/Stat.d.ts +3 -1
- package/components/Stat/Stat.js +7 -1
- package/components/registry.json +33 -0
- package/components/registry.json.d.ts +33 -0
- package/components/registry.json.js +1 -1
- package/index.d.ts +3 -0
- package/index.js +6 -0
- package/package.json +1 -1
- package/tokens/registry.json +4 -1
- package/tokens/registry.json.d.ts +4 -1
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +3 -0
- package/tokens/tokens-light.css +11 -0
- package/tokens/tokens-primitives.css +2 -0
- package/tokens/tokens-typography.css +12 -5
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ The **live site** is the portfolio built on the design system. **Storybook** is
|
|
|
18
18
|
| Part | Description |
|
|
19
19
|
|---|---|
|
|
20
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 -->
|
|
21
|
+
| **Design system** (`/src`) | <!-- component-count -->109<!-- /component-count --> React components, a three-tier token architecture, dark mode, a WebGL2 ambient background that themes itself from your tokens, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
|
|
22
22
|
| **AI layer** (`ai` components in `/src`, chat in `/website`, `/evals`) | A site-wide chat that answers questions about the work: built from the library's own `ai` components, grounded in a corpus generated from the site's published content, and scored by a golden-set eval. |
|
|
23
23
|
|
|
24
24
|
---
|
|
@@ -28,7 +28,7 @@ The **live site** is the portfolio built on the design system. **Storybook** is
|
|
|
28
28
|
### Components
|
|
29
29
|
|
|
30
30
|
<!-- component-list:start -->
|
|
31
|
-
Accordion · Agent plan · Agent status · AI button · Alert · Alert dialog · Anchor nav · App layout · App sidebar · Area chart · Avatar · Badge · Bar chart · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Globe · Input · Instructions · Interrupt card · Kbd · Line chart · Link list · Map callout · Map legend · Message actions · Message card · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Pie chart · Pin input · Popover · Progress bar · Prompt suggestions · Prose · Quote · Radar chart · Radial chart · Radio button · Reasoning · Scatter chart · Section title · Segmented control · Selection card · Shader field · Skeleton · Slider · Source chip · Sparkline · Spinner · Stacked bar chart · Stat · Stepper · Swatch · Table · Tabs · Tag input · Textarea · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Tooltip · Tree view · Treemap
|
|
31
|
+
Accordion · Agent plan · Agent status · AI button · Alert · Alert dialog · Anchor nav · App layout · App sidebar · Area chart · Avatar · Badge · Bar chart · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Funnel chart · Globe · Input · Instructions · Interrupt card · Kbd · Legend tile · Line chart · Link list · Map callout · Map legend · Message actions · Message card · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Popover · Progress bar · Prompt suggestions · Prose · Quote · Radar chart · Radial chart · Radio button · Reasoning · Scatter chart · Section title · Segmented control · Selection card · Shader field · Skeleton · Slider · Source chip · Sparkline · Spinner · Stacked bar chart · Stat · Stepper · Swatch · Table · Tabs · Tag input · Textarea · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Tooltip · Tree view · Treemap
|
|
32
32
|
<!-- component-list:end -->
|
|
33
33
|
|
|
34
34
|
### Ambient background
|
|
@@ -151,10 +151,10 @@ Answers are measured, not assumed. `evals/chat` holds a golden set that runs thr
|
|
|
151
151
|
|
|
152
152
|
## Quality & CI
|
|
153
153
|
|
|
154
|
-
Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: lint + library build, story tests, Storybook build, and website lint + build. The story tests render every Storybook story in headless Chromium via Vitest and run an axe accessibility audit on each, so a violation fails the build exactly like a render error. The same checklist runs locally with one command:
|
|
154
|
+
Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: lint + library build, story tests, Storybook build, and website lint + build followed by the two built-HTML checks (rendered spacing, chat-corpus coverage). The story tests render every Storybook story in headless Chromium via Vitest and run an axe accessibility audit on each, so a violation fails the build exactly like a render error. The same checklist runs locally with one command:
|
|
155
155
|
|
|
156
156
|
```bash
|
|
157
|
-
npm run verify # lint + library type-check + package build + story tests + Storybook build + website lint + build
|
|
157
|
+
npm run verify # lint + library type-check + package build + story tests + Storybook build + website lint + build + built-HTML checks
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
CI also guards against documentation drift: generated surfaces (this README's component count and list, the website's skills pages, the published blueprint copies of the root markdown specs) 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.
|
package/charts.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export * from './components/Chart/AreaChart';
|
|
7
7
|
export * from './components/Chart/BarChart';
|
|
8
|
+
export * from './components/Chart/ComboChart';
|
|
8
9
|
export * from './components/Chart/LineChart';
|
|
9
10
|
export * from './components/Chart/PieChart';
|
|
10
11
|
export * from './components/Chart/RadarChart';
|
package/charts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AreaChart } from "./components/Chart/AreaChart.js";
|
|
2
2
|
import { BarChart } from "./components/Chart/BarChart.js";
|
|
3
|
+
import { ComboChart } from "./components/Chart/ComboChart.js";
|
|
3
4
|
import { LineChart } from "./components/Chart/LineChart.js";
|
|
4
5
|
import { PieChart } from "./components/Chart/PieChart.js";
|
|
5
6
|
import { RadarChart } from "./components/Chart/RadarChart.js";
|
|
@@ -10,6 +11,7 @@ import { Treemap } from "./components/Chart/Treemap.js";
|
|
|
10
11
|
export {
|
|
11
12
|
AreaChart,
|
|
12
13
|
BarChart,
|
|
14
|
+
ComboChart,
|
|
13
15
|
LineChart,
|
|
14
16
|
PieChart,
|
|
15
17
|
RadarChart,
|
|
@@ -17,6 +17,12 @@ export interface AppLayoutProps {
|
|
|
17
17
|
logo?: React.ReactNode;
|
|
18
18
|
/** Page content — centred in the main area */
|
|
19
19
|
children: React.ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Colour scheme: 'dark' pins the layout to the dark theme (the historical
|
|
22
|
+
* behaviour and the default); 'inherit' drops the pin so the layout
|
|
23
|
+
* follows the surrounding data-theme like any other component.
|
|
24
|
+
*/
|
|
25
|
+
theme?: 'dark' | 'inherit';
|
|
20
26
|
/** Additional CSS classes on outer wrapper */
|
|
21
27
|
className?: string;
|
|
22
28
|
}
|
|
@@ -24,4 +30,4 @@ export interface AppLayoutProps {
|
|
|
24
30
|
* AppLayout — full-page template with a collapsible sidebar
|
|
25
31
|
* and a centred content area.
|
|
26
32
|
*/
|
|
27
|
-
export declare const AppLayout: ({ sections, profile, activeKey, activeSubKey, defaultExpanded, logoText, logo, children, className, }: AppLayoutProps) => React.JSX.Element;
|
|
33
|
+
export declare const AppLayout: ({ sections, profile, activeKey, activeSubKey, defaultExpanded, logoText, logo, children, theme, className, }: AppLayoutProps) => React.JSX.Element;
|
|
@@ -12,6 +12,7 @@ const AppLayout = ({
|
|
|
12
12
|
logoText,
|
|
13
13
|
logo,
|
|
14
14
|
children,
|
|
15
|
+
theme = "dark",
|
|
15
16
|
className = ""
|
|
16
17
|
}) => {
|
|
17
18
|
const baseClass = "ds-app-layout";
|
|
@@ -24,7 +25,7 @@ const AppLayout = ({
|
|
|
24
25
|
expanded ? `${baseClass}--expanded` : "",
|
|
25
26
|
className
|
|
26
27
|
].filter(Boolean).join(" ");
|
|
27
|
-
return /* @__PURE__ */ jsxs("div", { className: classes, "data-theme": "dark", children: [
|
|
28
|
+
return /* @__PURE__ */ jsxs("div", { className: classes, "data-theme": theme === "dark" ? "dark" : void 0, children: [
|
|
28
29
|
/* @__PURE__ */ jsx(
|
|
29
30
|
AppSidebar,
|
|
30
31
|
{
|
|
@@ -27,6 +27,24 @@
|
|
|
27
27
|
width: 280px;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/* ============================================
|
|
31
|
+
FLOATING VARIANT — the rail as a glass card
|
|
32
|
+
inset from the viewport edges (the same
|
|
33
|
+
recipe as the site chat panel)
|
|
34
|
+
============================================ */
|
|
35
|
+
.ds-app-sidebar--floating {
|
|
36
|
+
top: var(--ds-sidebar-float-inset, 20px);
|
|
37
|
+
left: var(--ds-sidebar-float-inset, 20px);
|
|
38
|
+
height: calc(100dvh - var(--ds-sidebar-float-inset, 20px) * 2);
|
|
39
|
+
background-color: var(--color-bg-glass);
|
|
40
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
41
|
+
border-radius: var(--radius-xl);
|
|
42
|
+
box-shadow: var(--shadow-floating);
|
|
43
|
+
/* The glass token is deliberately translucent; this blur is what keeps
|
|
44
|
+
content behind the rail readable through it. */
|
|
45
|
+
backdrop-filter: blur(24px);
|
|
46
|
+
}
|
|
47
|
+
|
|
30
48
|
/* ============================================
|
|
31
49
|
TOP SECTION — logo + nav
|
|
32
50
|
============================================ */
|
|
@@ -52,15 +70,17 @@
|
|
|
52
70
|
flex-shrink: 0;
|
|
53
71
|
}
|
|
54
72
|
|
|
73
|
+
/* Expand/collapse choreography, here and on every row below: the rail's
|
|
74
|
+
clipping width and the text's opacity fade carry the whole transition.
|
|
75
|
+
Inner layout stays constant - text keeps its natural width with
|
|
76
|
+
min-width: 0 so it truncates under the sweeping rail instead of holding
|
|
77
|
+
rows at full width (width: 0 to auto cannot interpolate, and a gap that
|
|
78
|
+
snaps mid-glide reads as a bounce). */
|
|
55
79
|
.ds-app-sidebar__logo-inner {
|
|
56
80
|
display: flex;
|
|
57
81
|
align-items: center;
|
|
58
|
-
gap: 0;
|
|
59
|
-
min-width: 0;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
.ds-app-sidebar--expanded .ds-app-sidebar__logo-inner {
|
|
63
82
|
gap: var(--gap-lg);
|
|
83
|
+
min-width: 0;
|
|
64
84
|
}
|
|
65
85
|
|
|
66
86
|
.ds-app-sidebar__logo-icon {
|
|
@@ -77,14 +97,14 @@
|
|
|
77
97
|
letter-spacing: var(--font-paragraph-em-letter-spacing);
|
|
78
98
|
color: var(--color-text-primary);
|
|
79
99
|
white-space: nowrap;
|
|
80
|
-
width: 0;
|
|
100
|
+
min-width: 0;
|
|
101
|
+
flex-shrink: 1;
|
|
81
102
|
overflow: hidden;
|
|
82
103
|
opacity: 0;
|
|
83
104
|
transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
84
105
|
}
|
|
85
106
|
|
|
86
107
|
.ds-app-sidebar--expanded .ds-app-sidebar__logo-text {
|
|
87
|
-
width: auto;
|
|
88
108
|
opacity: 1;
|
|
89
109
|
}
|
|
90
110
|
|
|
@@ -102,7 +122,8 @@
|
|
|
102
122
|
padding: 0;
|
|
103
123
|
overflow: hidden;
|
|
104
124
|
opacity: 0;
|
|
105
|
-
|
|
125
|
+
/* Both widths are explicit (0 and 24px), so the width glides too. */
|
|
126
|
+
transition: opacity var(--motion-duration-base) var(--motion-ease-standard), width var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
106
127
|
}
|
|
107
128
|
|
|
108
129
|
.ds-app-sidebar--expanded .ds-app-sidebar__toggle {
|
|
@@ -123,10 +144,8 @@
|
|
|
123
144
|
flex-direction: column;
|
|
124
145
|
gap: var(--gap-xs);
|
|
125
146
|
flex-shrink: 0;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.ds-app-sidebar--expanded .ds-app-sidebar__nav {
|
|
147
|
+
/* Stretch in both states: rows fill the rail and the collapsed icon is
|
|
148
|
+
centred by the row's own padding, so alignment never snaps. */
|
|
130
149
|
align-items: stretch;
|
|
131
150
|
}
|
|
132
151
|
|
|
@@ -156,8 +175,14 @@
|
|
|
156
175
|
.ds-app-sidebar__btn {
|
|
157
176
|
display: flex;
|
|
158
177
|
align-items: center;
|
|
159
|
-
justify-content:
|
|
178
|
+
justify-content: space-between;
|
|
160
179
|
height: 40px;
|
|
180
|
+
min-width: 0;
|
|
181
|
+
/* The same inset in both states: in the collapsed 64px rail the 12px
|
|
182
|
+
margins leave a 40px row, so the hover pill is a perfect circle clear
|
|
183
|
+
of the rail edges, with the icon centred (12 + 8 + 24 + 8 + 12 = 64);
|
|
184
|
+
expanded, these are the pill's edge inset. Nothing to transition. */
|
|
185
|
+
margin: 0 var(--padding-sm-md);
|
|
161
186
|
padding: var(--padding-sm);
|
|
162
187
|
border-radius: var(--radius-full);
|
|
163
188
|
background: transparent;
|
|
@@ -174,11 +199,6 @@
|
|
|
174
199
|
/* Inset via margin + reduced padding (12 + 8 = the 20px the label sat at
|
|
175
200
|
before), so the pill floats off the rail edges while the icon and label
|
|
176
201
|
keep their exact x-positions against the category labels above. */
|
|
177
|
-
.ds-app-sidebar--expanded .ds-app-sidebar__btn {
|
|
178
|
-
margin: 0 var(--padding-sm-md);
|
|
179
|
-
padding: var(--padding-sm);
|
|
180
|
-
justify-content: space-between;
|
|
181
|
-
}
|
|
182
202
|
|
|
183
203
|
.ds-app-sidebar__btn:hover {
|
|
184
204
|
background-color: var(--color-action-passive-bg-hover);
|
|
@@ -191,14 +211,10 @@
|
|
|
191
211
|
.ds-app-sidebar__btn-left {
|
|
192
212
|
display: flex;
|
|
193
213
|
align-items: center;
|
|
194
|
-
gap:
|
|
214
|
+
gap: var(--gap-sm);
|
|
195
215
|
min-width: 0;
|
|
196
216
|
}
|
|
197
217
|
|
|
198
|
-
.ds-app-sidebar--expanded .ds-app-sidebar__btn-left {
|
|
199
|
-
gap: 8px;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
218
|
.ds-app-sidebar__btn-icon {
|
|
203
219
|
--icon-size: var(--icon-size-md);
|
|
204
220
|
color: var(--color-icon-primary);
|
|
@@ -213,29 +229,48 @@
|
|
|
213
229
|
letter-spacing: var(--font-paragraph-em-letter-spacing);
|
|
214
230
|
color: var(--color-text-primary);
|
|
215
231
|
white-space: nowrap;
|
|
216
|
-
width: 0;
|
|
232
|
+
min-width: 0;
|
|
233
|
+
flex-shrink: 1;
|
|
217
234
|
overflow: hidden;
|
|
218
235
|
opacity: 0;
|
|
219
236
|
transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
220
237
|
}
|
|
221
238
|
|
|
222
239
|
.ds-app-sidebar--expanded .ds-app-sidebar__btn-label {
|
|
223
|
-
|
|
240
|
+
opacity: 1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.ds-app-sidebar__btn-badge {
|
|
244
|
+
font-family: var(--font-caption-family);
|
|
245
|
+
font-size: var(--font-caption-size);
|
|
246
|
+
font-weight: var(--font-paragraph-sm-em-weight);
|
|
247
|
+
line-height: var(--font-caption-line-height);
|
|
248
|
+
color: var(--color-text-secondary);
|
|
249
|
+
background-color: var(--color-bg-container-tertiary);
|
|
250
|
+
border-radius: var(--radius-full);
|
|
251
|
+
padding: var(--padding-xxxs) var(--padding-sm);
|
|
252
|
+
min-width: 0;
|
|
253
|
+
flex-shrink: 1;
|
|
254
|
+
overflow: hidden;
|
|
255
|
+
opacity: 0;
|
|
256
|
+
transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.ds-app-sidebar--expanded .ds-app-sidebar__btn-badge {
|
|
224
260
|
opacity: 1;
|
|
225
261
|
}
|
|
226
262
|
|
|
227
263
|
.ds-app-sidebar__btn-chevron {
|
|
228
264
|
font-size: 24px;
|
|
229
265
|
color: var(--color-icon-secondary);
|
|
230
|
-
|
|
231
|
-
|
|
266
|
+
min-width: 0;
|
|
267
|
+
flex-shrink: 1;
|
|
232
268
|
overflow: hidden;
|
|
233
269
|
transition: transform var(--motion-duration-slow) var(--motion-ease-standard), opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
234
270
|
opacity: 0;
|
|
235
271
|
}
|
|
236
272
|
|
|
237
273
|
.ds-app-sidebar--expanded .ds-app-sidebar__btn-chevron {
|
|
238
|
-
width: auto;
|
|
239
274
|
opacity: 1;
|
|
240
275
|
}
|
|
241
276
|
|
|
@@ -323,31 +358,60 @@
|
|
|
323
358
|
white-space: nowrap;
|
|
324
359
|
}
|
|
325
360
|
|
|
361
|
+
/* ============================================
|
|
362
|
+
CONSUMER SLOTS + BOTTOM WRAPPER
|
|
363
|
+
Slots keep their height in both states so the
|
|
364
|
+
rail's layout never snaps; collapsed they fade
|
|
365
|
+
out and stop taking clicks.
|
|
366
|
+
============================================ */
|
|
367
|
+
.ds-app-sidebar__bottom {
|
|
368
|
+
display: flex;
|
|
369
|
+
flex-direction: column;
|
|
370
|
+
gap: var(--gap-md);
|
|
371
|
+
min-width: 0;
|
|
372
|
+
flex-shrink: 0;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.ds-app-sidebar__top-slot,
|
|
376
|
+
.ds-app-sidebar__footer-slot {
|
|
377
|
+
padding: 0 var(--padding-md);
|
|
378
|
+
min-width: 0;
|
|
379
|
+
overflow: hidden;
|
|
380
|
+
opacity: 0;
|
|
381
|
+
pointer-events: none;
|
|
382
|
+
transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.ds-app-sidebar--expanded .ds-app-sidebar__top-slot,
|
|
386
|
+
.ds-app-sidebar--expanded .ds-app-sidebar__footer-slot {
|
|
387
|
+
opacity: 1;
|
|
388
|
+
pointer-events: auto;
|
|
389
|
+
}
|
|
390
|
+
|
|
326
391
|
/* ============================================
|
|
327
392
|
PROFILE
|
|
328
393
|
============================================ */
|
|
329
394
|
.ds-app-sidebar__profile {
|
|
330
395
|
display: flex;
|
|
331
396
|
align-items: center;
|
|
332
|
-
justify-content:
|
|
333
|
-
|
|
397
|
+
justify-content: space-between;
|
|
398
|
+
min-width: 0;
|
|
399
|
+
/* Centres the 40px avatar in the collapsed 64px rail; glides to the
|
|
400
|
+
expanded inset below. */
|
|
401
|
+
padding: 0 var(--padding-sm-md);
|
|
334
402
|
flex-shrink: 0;
|
|
403
|
+
transition: padding var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
335
404
|
}
|
|
336
405
|
|
|
337
406
|
.ds-app-sidebar--expanded .ds-app-sidebar__profile {
|
|
338
|
-
justify-content: space-between;
|
|
339
407
|
padding: 0 var(--padding-lg);
|
|
340
408
|
}
|
|
341
409
|
|
|
342
410
|
.ds-app-sidebar__profile-left {
|
|
343
411
|
display: flex;
|
|
344
412
|
align-items: center;
|
|
345
|
-
gap: 0;
|
|
346
|
-
min-width: 0;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.ds-app-sidebar--expanded .ds-app-sidebar__profile-left {
|
|
350
413
|
gap: 10px;
|
|
414
|
+
min-width: 0;
|
|
351
415
|
}
|
|
352
416
|
|
|
353
417
|
.ds-app-sidebar__avatar {
|
|
@@ -369,14 +433,13 @@
|
|
|
369
433
|
display: flex;
|
|
370
434
|
flex-direction: column;
|
|
371
435
|
min-width: 0;
|
|
372
|
-
|
|
436
|
+
flex-shrink: 1;
|
|
373
437
|
overflow: hidden;
|
|
374
438
|
opacity: 0;
|
|
375
439
|
transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
376
440
|
}
|
|
377
441
|
|
|
378
442
|
.ds-app-sidebar--expanded .ds-app-sidebar__profile-info {
|
|
379
|
-
width: auto;
|
|
380
443
|
opacity: 1;
|
|
381
444
|
}
|
|
382
445
|
|
|
@@ -416,11 +479,12 @@
|
|
|
416
479
|
width: 0;
|
|
417
480
|
overflow: hidden;
|
|
418
481
|
opacity: 0;
|
|
419
|
-
|
|
482
|
+
/* Both widths are explicit (0 and 24px), so the width glides too. */
|
|
483
|
+
transition: opacity var(--motion-duration-base) var(--motion-ease-standard), width var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
420
484
|
}
|
|
421
485
|
|
|
422
486
|
.ds-app-sidebar--expanded .ds-app-sidebar__profile-more {
|
|
423
|
-
width:
|
|
487
|
+
width: 24px;
|
|
424
488
|
opacity: 1;
|
|
425
489
|
}
|
|
426
490
|
|
|
@@ -20,6 +20,8 @@ export interface AppSidebarItem {
|
|
|
20
20
|
onClick?: () => void;
|
|
21
21
|
/** Renders the item as a real link to this URL instead of a button. Ignored when the item has children, whose row is the accordion toggle */
|
|
22
22
|
href?: string;
|
|
23
|
+
/** Trailing count pill, e.g. unread items; hidden while the rail is collapsed */
|
|
24
|
+
badge?: string | number;
|
|
23
25
|
/** Sub-items — turns this into an accordion */
|
|
24
26
|
children?: AppSidebarSubItem[];
|
|
25
27
|
}
|
|
@@ -54,6 +56,17 @@ export interface AppSidebarProps {
|
|
|
54
56
|
onExpandedChange?: (expanded: boolean) => void;
|
|
55
57
|
/** Callback when profile more button clicked */
|
|
56
58
|
onProfileMore?: () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Floats the rail off the viewport edges as a glass card: inset with
|
|
61
|
+
* rounded corners, the translucent glass fill over a backdrop blur, and
|
|
62
|
+
* the floating shadow. The inset defaults to 20px and is overridable via
|
|
63
|
+
* the --ds-sidebar-float-inset custom property.
|
|
64
|
+
*/
|
|
65
|
+
floating?: boolean;
|
|
66
|
+
/** Rendered under the logo row, above the nav; fades out while collapsed */
|
|
67
|
+
topSlot?: React.ReactNode;
|
|
68
|
+
/** Rendered above the profile block; fades out while collapsed */
|
|
69
|
+
footerSlot?: React.ReactNode;
|
|
57
70
|
/** Additional CSS classes */
|
|
58
71
|
className?: string;
|
|
59
72
|
/** Logo element — defaults to built-in robr0 logo */
|
|
@@ -68,4 +81,4 @@ export interface AppSidebarProps {
|
|
|
68
81
|
* **Expanded** (280 px): labels, category headings, accordion
|
|
69
82
|
* sub-items with tree-line connectors, and a profile section.
|
|
70
83
|
*/
|
|
71
|
-
export declare const AppSidebar: ({ sections, profile, activeKey, activeSubKey, defaultExpanded, expanded: controlledExpanded, onExpandedChange, onProfileMore, className, logo, logoText, }: AppSidebarProps) => React.JSX.Element;
|
|
84
|
+
export declare const AppSidebar: ({ sections, profile, activeKey, activeSubKey, defaultExpanded, expanded: controlledExpanded, onExpandedChange, onProfileMore, floating, topSlot, footerSlot, className, logo, logoText, }: AppSidebarProps) => React.JSX.Element;
|
|
@@ -118,6 +118,9 @@ const AppSidebar = ({
|
|
|
118
118
|
expanded: controlledExpanded,
|
|
119
119
|
onExpandedChange,
|
|
120
120
|
onProfileMore,
|
|
121
|
+
floating = false,
|
|
122
|
+
topSlot,
|
|
123
|
+
footerSlot,
|
|
121
124
|
className = "",
|
|
122
125
|
logo,
|
|
123
126
|
logoText = "robr0"
|
|
@@ -145,6 +148,7 @@ const AppSidebar = ({
|
|
|
145
148
|
const classes = [
|
|
146
149
|
baseClass,
|
|
147
150
|
isExpanded ? `${baseClass}--expanded` : "",
|
|
151
|
+
floating ? `${baseClass}--floating` : "",
|
|
148
152
|
className
|
|
149
153
|
].filter(Boolean).join(" ");
|
|
150
154
|
return /* @__PURE__ */ jsxs("nav", { className: classes, "aria-label": "App navigation", children: [
|
|
@@ -181,6 +185,7 @@ const AppSidebar = ({
|
|
|
181
185
|
}
|
|
182
186
|
)
|
|
183
187
|
] }),
|
|
188
|
+
topSlot && /* @__PURE__ */ jsx("div", { className: `${baseClass}__top-slot`, children: topSlot }),
|
|
184
189
|
sections.map((section, si) => /* @__PURE__ */ jsxs("div", { className: `${baseClass}__nav`, children: [
|
|
185
190
|
section.category && /* @__PURE__ */ jsx("div", { className: `${baseClass}__category`, children: section.category }),
|
|
186
191
|
section.items.map((item) => {
|
|
@@ -197,6 +202,7 @@ const AppSidebar = ({
|
|
|
197
202
|
/* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-icon material-symbols-rounded`, children: item.icon }),
|
|
198
203
|
/* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-label`, children: item.label })
|
|
199
204
|
] }),
|
|
205
|
+
item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: `${baseClass}__btn-badge`, children: item.badge }),
|
|
200
206
|
hasChildren && /* @__PURE__ */ jsx(
|
|
201
207
|
"span",
|
|
202
208
|
{
|
|
@@ -279,23 +285,26 @@ const AppSidebar = ({
|
|
|
279
285
|
})
|
|
280
286
|
] }, si))
|
|
281
287
|
] }),
|
|
282
|
-
|
|
283
|
-
/* @__PURE__ */
|
|
284
|
-
|
|
285
|
-
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__profile-
|
|
286
|
-
/* @__PURE__ */ jsx("
|
|
287
|
-
/* @__PURE__ */
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
288
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__bottom`, children: [
|
|
289
|
+
footerSlot && /* @__PURE__ */ jsx("div", { className: `${baseClass}__footer-slot`, children: footerSlot }),
|
|
290
|
+
profile && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__profile`, children: [
|
|
291
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__profile-left`, children: [
|
|
292
|
+
/* @__PURE__ */ jsx("div", { className: `${baseClass}__avatar`, children: profile.avatarUrl && /* @__PURE__ */ jsx("img", { src: profile.avatarUrl, alt: profile.name }) }),
|
|
293
|
+
/* @__PURE__ */ jsxs("div", { className: `${baseClass}__profile-info`, children: [
|
|
294
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__profile-name`, children: profile.name }),
|
|
295
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__profile-email`, children: profile.email })
|
|
296
|
+
] })
|
|
297
|
+
] }),
|
|
298
|
+
/* @__PURE__ */ jsx(
|
|
299
|
+
"button",
|
|
300
|
+
{
|
|
301
|
+
className: `${baseClass}__profile-more`,
|
|
302
|
+
onClick: onProfileMore,
|
|
303
|
+
"aria-label": "Profile options",
|
|
304
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-rounded", children: "more_horiz" })
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
] })
|
|
299
308
|
] })
|
|
300
309
|
] });
|
|
301
310
|
};
|
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -4,8 +4,7 @@ import { ResponsiveContainer, AreaChart as AreaChart$1, CartesianGrid, XAxis, YA
|
|
|
4
4
|
import { getChartSeriesColors } from "./palette.js";
|
|
5
5
|
import "./Chart.css";
|
|
6
6
|
function getCSSVar(name, fallback) {
|
|
7
|
-
|
|
8
|
-
return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
|
|
7
|
+
return `var(${name}, ${fallback})`;
|
|
9
8
|
}
|
|
10
9
|
function AreaTooltip({ active, payload, label }) {
|
|
11
10
|
if (!active || !payload?.length) return null;
|
|
@@ -27,10 +26,12 @@ const AreaChart = ({
|
|
|
27
26
|
subtitle,
|
|
28
27
|
summaryItems,
|
|
29
28
|
height = 350,
|
|
29
|
+
showLegend = true,
|
|
30
|
+
bare = false,
|
|
30
31
|
className = ""
|
|
31
32
|
}) => {
|
|
32
33
|
const baseClass = "ds-chart";
|
|
33
|
-
const classes = [baseClass, className].filter(Boolean).join(" ");
|
|
34
|
+
const classes = [baseClass, bare ? `${baseClass}--bare` : "", className].filter(Boolean).join(" ");
|
|
34
35
|
const textSecondary = getCSSVar("--color-text-secondary", "#A2A2A2");
|
|
35
36
|
const gridColor = getCSSVar("--color-divider", "#232323");
|
|
36
37
|
const seriesColors = getChartSeriesColors();
|
|
@@ -91,7 +92,7 @@ const AreaChart = ({
|
|
|
91
92
|
},
|
|
92
93
|
s.dataKey
|
|
93
94
|
)),
|
|
94
|
-
series.length > 1 && /* @__PURE__ */ jsx(Legend, { wrapperStyle: { color: textSecondary, fontSize: 12 } })
|
|
95
|
+
showLegend && series.length > 1 && /* @__PURE__ */ jsx(Legend, { wrapperStyle: { color: textSecondary, fontSize: 12 } })
|
|
95
96
|
] }) }) })
|
|
96
97
|
] });
|
|
97
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
|
-
|
|
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");
|