@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.
- package/README.md +9 -8
- package/charts.d.ts +1 -0
- package/charts.js +2 -0
- package/components/AgentPlan/AgentPlan.css +14 -3
- package/components/AiButton/AiButton.css +1 -1
- package/components/Alert/Alert.css +9 -0
- package/components/AlertDialog/AlertDialog.css +3 -1
- package/components/AlertDialog/AlertDialog.js +2 -3
- package/components/AppLayout/AppLayout.d.ts +7 -1
- package/components/AppLayout/AppLayout.js +2 -1
- package/components/AppSidebar/AppSidebar.css +113 -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/Card/Card.css +7 -2
- package/components/CardStack/CardStack.css +106 -0
- package/components/CardStack/CardStack.d.ts +44 -0
- package/components/CardStack/CardStack.js +140 -0
- package/components/Carousel/Carousel.css +9 -0
- package/components/Carousel/Carousel.js +37 -4
- package/components/Chart/AreaChart.d.ts +5 -1
- package/components/Chart/AreaChart.js +11 -9
- 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 +7 -6
- package/components/Chart/PieChart.d.ts +3 -1
- package/components/Chart/PieChart.js +5 -18
- package/components/Chart/RadarChart.d.ts +3 -1
- package/components/Chart/RadarChart.js +8 -7
- package/components/Chart/RadialChart.d.ts +7 -1
- package/components/Chart/RadialChart.js +53 -58
- package/components/Chart/ScatterChart.d.ts +3 -1
- package/components/Chart/ScatterChart.js +6 -16
- package/components/Chart/StackedBarChart.d.ts +3 -1
- package/components/Chart/StackedBarChart.js +5 -18
- package/components/Chart/Treemap.d.ts +3 -1
- package/components/Chart/Treemap.js +6 -20
- package/components/Chart/palette.d.ts +1 -0
- package/components/Chart/palette.js +17 -0
- package/components/ChatMarker/ChatMarker.css +9 -0
- package/components/ChatMessage/ChatMessage.css +16 -0
- package/components/ChatThread/ChatThread.js +2 -2
- package/components/Chip/Chip.css +9 -0
- package/components/CodeBlock/CodeBlock.css +10 -1
- package/components/CodeBlock/CodeBlock.js +2 -1
- package/components/ColorPicker/ColorPicker.js +10 -0
- package/components/Combobox/Combobox.css +9 -0
- package/components/CommandPalette/CommandPalette.css +9 -0
- package/components/CommandPalette/CommandPalette.js +31 -5
- package/components/Composer/Composer.css +118 -0
- package/components/Composer/Composer.d.ts +7 -0
- package/components/Composer/Composer.js +7 -1
- package/components/ContactCard/ContactCard.css +9 -0
- package/components/ContextMenu/ContextMenu.js +39 -3
- package/components/DatePicker/DatePicker.js +16 -0
- package/components/Dialog/Dialog.css +3 -1
- package/components/Dialog/Dialog.js +5 -4
- package/components/DocumentChip/DocumentChip.css +9 -0
- package/components/Dropdown/Dropdown.js +22 -3
- package/components/DropdownMenu/DropdownMenu.js +43 -6
- package/components/EmptyState/EmptyState.css +9 -0
- package/components/FileInput/FileInput.css +9 -0
- package/components/FunnelChart/FunnelChart.css +41 -0
- package/components/FunnelChart/FunnelChart.d.ts +40 -0
- package/components/FunnelChart/FunnelChart.js +48 -0
- package/components/Globe/Globe.css +155 -0
- package/components/Globe/Globe.d.ts +96 -0
- package/components/Globe/Globe.js +415 -0
- package/components/InterruptCard/InterruptCard.css +27 -17
- package/components/InterruptCard/InterruptCard.d.ts +1 -1
- package/components/InterruptCard/InterruptCard.js +1 -1
- package/components/LegendTile/LegendTile.css +60 -0
- package/components/LegendTile/LegendTile.d.ts +30 -0
- package/components/LegendTile/LegendTile.js +21 -0
- package/components/MapCallout/MapCallout.css +46 -0
- package/components/MapCallout/MapCallout.d.ts +29 -0
- package/components/MapCallout/MapCallout.js +17 -0
- package/components/MapLegend/MapLegend.css +88 -0
- package/components/MapLegend/MapLegend.d.ts +39 -0
- package/components/MapLegend/MapLegend.js +68 -0
- package/components/MessageActions/MessageActions.css +9 -0
- package/components/MessageCard/MessageCard.css +68 -16
- package/components/MessageCard/MessageCard.d.ts +1 -1
- package/components/MessageCard/MessageCard.js +6 -4
- package/components/ModelPicker/ModelPicker.css +4 -1
- package/components/ModelPicker/ModelPicker.js +43 -1
- package/components/NotificationCenter/NotificationCenter.css +9 -0
- package/components/NotificationCenter/NotificationCenter.js +26 -10
- package/components/NumberInput/NumberInput.css +9 -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/Popover/Popover.css +3 -1
- package/components/Popover/Popover.js +2 -1
- package/components/SourceChip/SourceChip.css +9 -0
- package/components/Stat/Stat.css +44 -2
- package/components/Stat/Stat.d.ts +3 -1
- package/components/Stat/Stat.js +7 -1
- package/components/Stepper/Stepper.css +9 -0
- package/components/TagInput/TagInput.css +9 -0
- package/components/TimePicker/TimePicker.js +4 -1
- package/components/Toast/Toast.css +32 -11
- package/components/Toast/Toast.js +5 -4
- package/components/ToggleGroup/ToggleGroup.css +9 -0
- package/components/ToolCall/ToolCall.css +18 -6
- package/components/Tooltip/Tooltip.js +20 -16
- package/components/registry.json +70 -0
- package/components/registry.json.d.ts +70 -0
- package/components/registry.json.js +2 -2
- package/index.d.ts +7 -0
- package/index.js +14 -0
- package/package.json +5 -1
- package/tokens/motion.d.ts +23 -0
- package/tokens/motion.js +14 -0
- package/tokens/registry.json +21 -1
- package/tokens/registry.json.d.ts +21 -1
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +14 -0
- package/tokens/tokens-light.css +22 -0
- package/tokens/tokens-primitives.css +2 -0
- package/tokens/tokens-typography.css +32 -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 · 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 · Input · Instructions · Interrupt card · Kbd · Line chart · Link list · 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
|
|
@@ -50,7 +50,7 @@ Eight parameters describe the look, and a composition is a table of token names
|
|
|
50
50
|
|
|
51
51
|
It asks two things of you, both deliberate. It fills a positioned ancestor you provide, because where a background sits is a layout decision rather than a rendering one. And it never decides what to paint instead of itself: it reports `pending`, `active` or `unavailable`, and one fallback covers every way it can fail (no WebGL2, a blocked or lost GPU context, a renderer that stalls before its first frame, or `enabled={false}` as a kill switch). `pending` is the state worth handling. Paint neither layer while the context comes up, or the swap a frame later reads as two backgrounds loading in sequence.
|
|
52
52
|
|
|
53
|
-
It
|
|
53
|
+
It checks `prefers-reduced-motion` in JavaScript rather than through the motion tokens, because the CSS guard cannot see a JS-driven loop. Set the preference and it draws a single static frame.
|
|
54
54
|
|
|
55
55
|
The background behind this site is that component, with eight blurred CSS discs kept painted underneath as its fallback. The **[component page](https://robertritacca.com/components/shader-field)** is a live demo, and the **[setup guide](https://robertritacca.com/docs/get-started)** has the wiring.
|
|
56
56
|
|
|
@@ -97,7 +97,7 @@ import { BarChart, LineChart } from '@robr0/design-system/charts';
|
|
|
97
97
|
}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Icons use a bundled Material Symbols Rounded variable font (woff2),
|
|
100
|
+
Icons use a bundled Material Symbols Rounded variable font (woff2): components import it themselves, so they need no extra setup. If you render raw `.material-symbols-rounded` spans of your own, import `@robr0/design-system/fonts/material-symbols.css` once. 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`.
|
|
101
101
|
|
|
102
102
|
### Token architecture
|
|
103
103
|
|
|
@@ -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.
|
|
@@ -164,11 +164,12 @@ CI also guards against documentation drift: generated surfaces (this README's co
|
|
|
164
164
|
## Running locally
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
|
+
npm install # once, at the root: the website is an npm workspace, so this installs both
|
|
168
|
+
|
|
167
169
|
# Storybook (the library's dev sandbox)
|
|
168
170
|
npm run storybook # http://localhost:6006
|
|
169
171
|
|
|
170
|
-
# Portfolio + documentation website
|
|
171
|
-
npm install
|
|
172
|
+
# Portfolio + documentation website
|
|
172
173
|
npm run dev --workspace website # http://localhost:3000
|
|
173
174
|
```
|
|
174
175
|
|
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,
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
width: 100%;
|
|
17
17
|
background-color: var(--color-bg-container-primary);
|
|
18
18
|
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
19
|
-
|
|
19
|
+
/* The chat furniture's shared 24px shell — see ToolCall. */
|
|
20
|
+
border-radius: var(--radius-xl);
|
|
20
21
|
overflow: hidden;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -29,7 +30,8 @@
|
|
|
29
30
|
align-items: center;
|
|
30
31
|
gap: var(--gap-sm);
|
|
31
32
|
width: 100%;
|
|
32
|
-
|
|
33
|
+
/* 20px sides — the card family's shared content inset. */
|
|
34
|
+
padding: var(--padding-sm) var(--padding-lg);
|
|
33
35
|
background: none;
|
|
34
36
|
border: none;
|
|
35
37
|
text-align: left;
|
|
@@ -121,7 +123,7 @@
|
|
|
121
123
|
display: flex;
|
|
122
124
|
flex-direction: column;
|
|
123
125
|
margin: 0;
|
|
124
|
-
padding: var(--padding-sm-md) var(--padding-
|
|
126
|
+
padding: var(--padding-sm-md) var(--padding-lg);
|
|
125
127
|
list-style: none;
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -229,3 +231,12 @@
|
|
|
229
231
|
white-space: nowrap;
|
|
230
232
|
border: 0;
|
|
231
233
|
}
|
|
234
|
+
|
|
235
|
+
/* Icons take the colour of the text around them. Explicit, not just
|
|
236
|
+
inherited by default: a consumer stylesheet that sets a colour on
|
|
237
|
+
.material-symbols-rounded itself (a common global default) would
|
|
238
|
+
otherwise override inheritance and detach the icon from the state
|
|
239
|
+
colours this component sets on its parents. */
|
|
240
|
+
.ds-agent-plan .material-symbols-rounded {
|
|
241
|
+
color: inherit;
|
|
242
|
+
}
|
|
@@ -250,3 +250,12 @@
|
|
|
250
250
|
.ds-alert--neutral .ds-alert__dismiss {
|
|
251
251
|
color: var(--color-status-neutral-text);
|
|
252
252
|
}
|
|
253
|
+
|
|
254
|
+
/* Icons take the colour of the text around them. Explicit, not just
|
|
255
|
+
inherited by default: a consumer stylesheet that sets a colour on
|
|
256
|
+
.material-symbols-rounded itself (a common global default) would
|
|
257
|
+
otherwise override inheritance and detach the icon from the state
|
|
258
|
+
colours this component sets on its parents. */
|
|
259
|
+
.ds-alert .material-symbols-rounded {
|
|
260
|
+
color: inherit;
|
|
261
|
+
}
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
align-items: center;
|
|
12
12
|
justify-content: center;
|
|
13
13
|
opacity: 0;
|
|
14
|
+
visibility: hidden; /* keeps panel controls out of the tab order when closed */
|
|
14
15
|
pointer-events: none;
|
|
15
|
-
transition: opacity var(--motion-duration-base) var(--motion-ease-standard);
|
|
16
|
+
transition: opacity var(--motion-duration-base) var(--motion-ease-standard), visibility var(--motion-duration-base) var(--motion-ease-standard);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
.ds-alert-dialog--open {
|
|
19
20
|
opacity: 1;
|
|
21
|
+
visibility: visible;
|
|
20
22
|
pointer-events: auto;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -7,6 +7,7 @@ import "./AlertDialog.css";
|
|
|
7
7
|
import "../../fonts/material-symbols.css";
|
|
8
8
|
const emptySubscribe = () => () => {
|
|
9
9
|
};
|
|
10
|
+
const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
10
11
|
const AlertDialog = ({
|
|
11
12
|
open,
|
|
12
13
|
onOpenChange,
|
|
@@ -53,9 +54,7 @@ const AlertDialog = ({
|
|
|
53
54
|
return;
|
|
54
55
|
}
|
|
55
56
|
if (e.key === "Tab" && panel) {
|
|
56
|
-
const focusable = panel.querySelectorAll(
|
|
57
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
58
|
-
);
|
|
57
|
+
const focusable = panel.querySelectorAll(FOCUSABLE);
|
|
59
58
|
if (focusable.length === 0) return;
|
|
60
59
|
const first = focusable[0];
|
|
61
60
|
const last = focusable[focusable.length - 1];
|
|
@@ -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
|
|
|
@@ -442,3 +506,12 @@
|
|
|
442
506
|
padding: var(--padding-lg) 0;
|
|
443
507
|
}
|
|
444
508
|
}
|
|
509
|
+
|
|
510
|
+
/* Icons take the colour of the text around them. Explicit, not just
|
|
511
|
+
inherited by default: a consumer stylesheet that sets a colour on
|
|
512
|
+
.material-symbols-rounded itself (a common global default) would
|
|
513
|
+
otherwise override inheritance and detach the icon from the state
|
|
514
|
+
colours this component sets on its parents. */
|
|
515
|
+
.ds-app-sidebar .material-symbols-rounded {
|
|
516
|
+
color: inherit;
|
|
517
|
+
}
|
|
@@ -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
|
};
|