@uxelle/skills 0.2.2 → 0.2.4
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/dist/index.js +40 -35
- package/index.json +40 -35
- package/package.json +1 -1
- package/skills/uxelle-components/Icon.md +4 -4
- package/skills/uxelle-components/NavLink.md +2 -2
- package/skills/uxelle-components/Navigation.md +33 -23
- package/skills/uxelle-components/NavigationSide.md +3 -3
- package/skills/uxelle-components/NavigationSideItem.md +4 -4
- package/skills/uxelle-components/NavigationSideSubItem.md +3 -3
- package/skills/uxelle-components/SKILL.md +6 -1
- package/skills/uxelle-components/StatTile.md +13 -42
- package/skills/uxelle-components/getting-started.md +38 -7
- package/skills/uxelle-design-harness/SKILL.md +6 -2
- package/skills/uxelle-design-harness/a2ui.md +9 -0
- package/skills/uxelle-design-harness/how-to-accessibility.md +1 -1
- package/skills/uxelle-design-harness/how-to-host.md +5 -1
- package/skills/uxelle-design-harness/how-to-page-layout.md +4 -2
- package/skills/uxelle-design-harness/recipe-app-chrome.md +102 -25
- package/skills/uxelle-design-harness/recipe-dashboard-overview.md +4 -2
- package/skills/uxelle-design-harness/recipe-navigation.md +433 -0
- package/skills/uxelle-design-harness/recipe-stat-callouts.md +6 -4
|
@@ -10,11 +10,11 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
|
|
|
10
10
|
|
|
11
11
|
| Prop | Type | Default | Description |
|
|
12
12
|
|------|------|---------|-------------|
|
|
13
|
-
| order | `"labelFirst" \| "valueFirst"` | `"labelFirst"
|
|
14
|
-
| label | `ReactNode` | `—` | Label in the label row. Occupies remaining width beside the info control.
|
|
15
|
-
| value | `ReactNode` | `—` | Headline figure in the value row.
|
|
13
|
+
| order | `"labelFirst" \| "valueFirst"` | `"labelFirst"` | Vertical order of the label row and the value row. |
|
|
14
|
+
| label | `ReactNode` | `—` | Label in the label row. Occupies remaining width beside the info control and stays on one line: overflow ellipsizes. Hover or focus a truncated label to read the full name in a tooltip. A string (or number) is wrapped in **Text** with `type="Body Medium"`; pass a `Text` node for a different type (truncation is still applied). When you pass a `Text` or other `ReactNode` you own its accessibility: use meaningful text (not color or icon alone) and keep it non-interactive so it can name the group. |
|
|
15
|
+
| value | `ReactNode` | `—` | Headline figure in the value row. A string (or number) is wrapped in **Text** with `type="Display Small"` as `<p>`; pass a `Text` node for a different type. A `Text` keeps its `type` and is rendered as `<p>` so a grid of StatTiles does not become a heading outline. Any other `ReactNode` is allowed; make sure the figure reads as text and is not conveyed through color or an icon alone (WCAG 1.4.1). |
|
|
16
16
|
| qualifier | `boolean` | `true` | When true, allows the qualifier to render when content is present. |
|
|
17
|
-
| qualifierContent | `ReactNode` | `—` | Qualifier beside the value.
|
|
17
|
+
| qualifierContent | `ReactNode` | `—` | Qualifier beside the value. A string (or number) is wrapped in **Text** with `type="Body Medium"`; pass a `Text` node for a different type. Only rendered when `qualifier` is true and content is present. When you pass a `Text` or other `ReactNode`, ensure it reads as text; do not rely on color alone to distinguish comparison periods. |
|
|
18
18
|
| tooltipSlot | `boolean` | `true` | When true, allows the tooltip slot to render when content is present. |
|
|
19
19
|
| tooltipContent | `ReactNode` | `—` | Info control at the end of the label row (typically `Tooltip` wrapping `IconButton`). Only rendered when `tooltipSlot` is true and content is present. Icon-only triggers within this slot must carry an accessible name via `aria-label` on the interactive element (e.g. `<IconButton aria-label="More information" />`). |
|
|
20
20
|
| trailingSlot | `boolean` | `false` | When true, allows the trailing region to render when content is present. The slot accepts any content. |
|
|
@@ -70,7 +70,7 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
|
|
|
70
70
|
// ReactNode override — caller owns accessibility (semantic element, text)
|
|
71
71
|
<StatTile
|
|
72
72
|
label={<Text type="Body Medium" width={false}>Fill rate</Text>}
|
|
73
|
-
value={<Text type="Display
|
|
73
|
+
value={<Text type="Display Small" as="p">94%</Text>}
|
|
74
74
|
/>
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -78,7 +78,7 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
|
|
|
78
78
|
// Polling dashboard — aria-live on the value announces only the change
|
|
79
79
|
<StatTile
|
|
80
80
|
label="Active sessions"
|
|
81
|
-
value={<Text type="Display
|
|
81
|
+
value={<Text type="Display Small" as="p" aria-live="polite">{count}</Text>}
|
|
82
82
|
/>
|
|
83
83
|
```
|
|
84
84
|
|
|
@@ -111,40 +111,11 @@ Card-styled readout for a headline figure with a label, optional qualifier, info
|
|
|
111
111
|
|
|
112
112
|
## Notes
|
|
113
113
|
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
- a `
|
|
117
|
-
-
|
|
118
|
-
- `Text` passed as `value` is rendered as `<p>`.
|
|
119
|
-
- Pass a library `Tooltip` (typically wrapping `IconButton`) through
|
|
120
|
-
- `tooltipContent`. Toggle regions with `qualifier`, `tooltipSlot`, and
|
|
121
|
-
- `trailingSlot`; empty content is not rendered. `trailingSlotContent` is an
|
|
122
|
-
- open slot — any content is allowed. Surface, elevation, radius, and
|
|
123
|
-
- padding come from `Card`. StatTile is a standalone tile: place it next to
|
|
124
|
-
- other StatTiles in a grid. Do not wrap it in `Card`. A figure inside a chart
|
|
125
|
-
- card or table cell is a different surface — compose `Text` there.
|
|
126
|
-
- **Accessibility.** The root is a `role="group"` region. It is named
|
|
127
|
-
- automatically from `label` (via `aria-labelledby`), so the group is announced
|
|
128
|
-
- with context in both orders — including `"valueFirst"`, where the value
|
|
129
|
-
- precedes the label in the DOM. Supply your own `aria-label` or
|
|
130
|
-
- `aria-labelledby` to override the derived name; this is **required** when
|
|
131
|
-
- there is no `label` (e.g. `aria-label="Median first response time, 1 day 12
|
|
132
|
-
- hours"`) so the figure is not announced without context. In development,
|
|
133
|
-
- StatTile warns when content is present but the group has no name. The trailing
|
|
134
|
-
- slot is open, so its accessibility is the caller's responsibility — see
|
|
135
|
-
- `trailingSlotContent`.
|
|
136
|
-
- **Navigation.** Do not put `onClick` on StatTile — the root is a named group,
|
|
137
|
-
- not a control, and a wrapped link would nest interactives when a tooltip is
|
|
138
|
-
- present. Put the destination in `trailingSlotContent` as a `Link` or
|
|
139
|
-
- `IconButton`.
|
|
114
|
+
- `label`, `value`, and `qualifierContent` each accept a string (or number) or a **Text** node. A string is wrapped in that region's default `Text` type — no wrapper needed. Pass a `Text` when you need a different `type` or a live region. A `Text` passed as `value` keeps its `type` and is rendered as `<p>`. The label always truncates to one line so a row of tiles stays scannable; hover or focus a truncated label to read the full name (`Text` Body Small). This overflow tooltip is separate from `tooltipContent` (the info control).
|
|
115
|
+
- Pass a library `Tooltip` (typically wrapping `IconButton`) through `tooltipContent`. Toggle regions with `qualifier`, `tooltipSlot`, and `trailingSlot`; empty content is not rendered. `trailingSlotContent` is an open slot — any content is allowed. Surface, elevation, radius, and padding come from `Card`. StatTile is a standalone tile: place it next to other StatTiles in a grid. Do not wrap it in `Card`. A figure inside a chart card or table cell is a different surface — compose `Text` there.
|
|
116
|
+
- **Accessibility.** The root is a `role="group"` region. It is named automatically from `label` (via `aria-labelledby`), so the group is announced with context in both orders — including `"valueFirst"`, where the value precedes the label in the DOM. Supply your own `aria-label` or `aria-labelledby` to override the derived name; this is **required** when there is no `label` (e.g. `aria-label="Median first response time, 1 day 12 hours"`) so the figure is not announced without context. In development, StatTile warns when content is present but the group has no name. The trailing slot is open, so its accessibility is the caller's responsibility — see `trailingSlotContent`.
|
|
117
|
+
- **Navigation.** Do not put `onClick` on StatTile — the root is a named group, not a control, and a wrapped link would nest interactives when a tooltip is present. Put the destination in `trailingSlotContent` as a `Link` or `IconButton`.
|
|
140
118
|
- Set `order` to `"valueFirst"` to put the value row above the label row.
|
|
141
|
-
- The root fills its parent by default (`fullWidth`). Set `fullWidth={false}`
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
- (full-width rectangle) inside `SkeletonGroup`. Do not pass a skeleton as
|
|
145
|
-
- `value`.
|
|
146
|
-
- For polling dashboards where the value updates in place, prefer putting
|
|
147
|
-
- `aria-live="polite"` on the **value** node so only the changing figure is
|
|
148
|
-
- announced (a root-level `aria-live` also works but re-announces the whole
|
|
149
|
-
- group). When using a `string` `value`, wrap it yourself to place the live
|
|
150
|
-
- region precisely.
|
|
119
|
+
- The root fills its parent by default (`fullWidth`). Set `fullWidth={false}` to hug contents. Long value and qualifier copy wrap inside the tile; the label ellipsizes instead of wrapping.
|
|
120
|
+
- While data is loading, replace the tile with a `Skeleton` sized to the card (full-width rectangle) inside `SkeletonGroup`. Do not pass a skeleton as `value`.
|
|
121
|
+
- For polling dashboards where the value updates in place, prefer putting `aria-live="polite"` on the **value** node so only the changing figure is announced (a root-level `aria-live` also works but re-announces the whole group). When using a `string` `value`, wrap it yourself to place the live region precisely.
|
|
@@ -12,10 +12,11 @@ Peer dependencies: React 18 or 19.
|
|
|
12
12
|
npm install @uxelle/components @uxelle/themes
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
Import the two stylesheets:
|
|
16
|
+
|
|
15
17
|
```tsx
|
|
16
|
-
import "@uxelle/components/
|
|
17
|
-
import "@uxelle/
|
|
18
|
-
import "@uxelle/themes/themes/open-source/open-source.css";
|
|
18
|
+
import "@uxelle/components/base.css";
|
|
19
|
+
import "@uxelle/themes/open-source.css";
|
|
19
20
|
|
|
20
21
|
import { Button } from "@uxelle/components";
|
|
21
22
|
|
|
@@ -24,22 +25,52 @@ function App() {
|
|
|
24
25
|
}
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Then set the theme attributes on `<html>`:
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<html data-open-source="light" data-color-switcher="default"></html>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Both attributes are required. Every `--uxl-*` token is declared under
|
|
35
|
+
`[data-open-source="light"]` or `[data-open-source="dark"]`, so components render
|
|
36
|
+
unstyled without them. See [Themes](#themes) to switch modes at runtime.
|
|
37
|
+
|
|
38
|
+
`base.css` is the single component stylesheet. `@uxelle/components/styles.css`
|
|
39
|
+
(global baseline) and `@uxelle/components/index.css` (component rules)
|
|
40
|
+
stay exported if you need to load them separately.
|
|
28
41
|
|
|
29
42
|
### Next.js / SSR
|
|
30
43
|
|
|
31
|
-
- Import
|
|
44
|
+
- Import both stylesheets in `app/layout.tsx` (or a top-level provider). JS side effects will not style App Router.
|
|
32
45
|
- Published `@uxelle/components` JS is prefixed with `"use client"`. Importing any component places that file in the client graph.
|
|
33
46
|
- Pass `mobile={true}` or `mobile={false}` on `NavigationSide` so server and client markup match. Omit `mobile` only in client-only surfaces.
|
|
34
47
|
|
|
35
48
|
Optional: for an agent-driven UI surface, also install `@uxelle/a2ui`.
|
|
36
49
|
|
|
50
|
+
### Icons
|
|
51
|
+
|
|
52
|
+
`@uxelle/components` ships GPF chrome glyphs (`close`, `chevron_*`, `check`, and other names the library itself uses). For any other Material Symbol Sharp name, install `@uxelle/icons` and register once before the first `Icon` render:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install @uxelle/icons
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import "@uxelle/icons/register";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`@uxelle/a2ui` registers the full catalog when you import it. Unknown names render no glyph (an empty span). Development warns; production is silent, so an icon-only control with `aria-label` can still pass a11y tests while showing nothing. Register `@uxelle/icons` before the first render, or type names with `UxelleIconName` from `@uxelle/icons`.
|
|
63
|
+
|
|
37
64
|
## Themes
|
|
38
65
|
|
|
39
|
-
Theme CSS is published as `@uxelle/themes` and generated from design tokens.
|
|
66
|
+
Theme CSS is published as `@uxelle/themes` and generated from design tokens. Quick Start covers the import and the two required attributes; this section covers changing them.
|
|
67
|
+
|
|
68
|
+
- `data-open-source` — `"light"` or `"dark"`
|
|
69
|
+
- `data-color-switcher` — `"default"`, `"default-subtle"`, `"success"`, `"warning"`, `"danger"`, or `"info"`
|
|
70
|
+
|
|
71
|
+
To drive both from React instead of hardcoding them, wrap the app with `UXelleThemeProvider`, which sets the attributes on `document.documentElement`:
|
|
40
72
|
|
|
41
73
|
```tsx
|
|
42
|
-
import "@uxelle/themes/themes/open-source/open-source.css";
|
|
43
74
|
import { UXelleThemeProvider } from "@uxelle/themes/react";
|
|
44
75
|
|
|
45
76
|
function AppShell({ children }: { children: React.ReactNode }) {
|
|
@@ -77,6 +77,7 @@ palette. The full mapping of principles to uxElle mechanisms lives in
|
|
|
77
77
|
card row -> [recipe-card-grid.md](recipe-card-grid.md);
|
|
78
78
|
metrics -> [recipe-stat-callouts.md](recipe-stat-callouts.md);
|
|
79
79
|
empty/loading/error -> [recipe-states.md](recipe-states.md);
|
|
80
|
+
header navigation -> [recipe-navigation.md](recipe-navigation.md);
|
|
80
81
|
page footer -> [recipe-footer.md](recipe-footer.md);
|
|
81
82
|
hero -> [recipe-hero.md](recipe-hero.md); feature -> [recipe-feature-section.md](recipe-feature-section.md);
|
|
82
83
|
trust bar -> [recipe-logo-wall.md](recipe-logo-wall.md); testimonial -> [recipe-testimonial.md](recipe-testimonial.md);
|
|
@@ -136,7 +137,9 @@ Every generated screen must pass this. It is the definition of done.
|
|
|
136
137
|
a type (an `h1` and `h2` both on `Display Extra Small` flatten the page), and
|
|
137
138
|
headings inside a `Card` step down one stop ([density.md](density.md)).
|
|
138
139
|
- **Responsive**: reflows mobile -> up with no px literals; clusters stack, `Table`
|
|
139
|
-
scrolls,
|
|
140
|
+
scrolls, compact header menu sits in `Navigation` `trailingSlot` and opens a
|
|
141
|
+
`Sheet` from that side (panel interior matches IA depth); touch targets stay
|
|
142
|
+
>= 24 CSS px.
|
|
140
143
|
- **Color discipline**: at most one dominant brand band per view; status palettes
|
|
141
144
|
only for semantic status, never color-alone (paired with text or icon).
|
|
142
145
|
- **Accessibility**: skip link + one `main`; landmarks named; focus visible; every
|
|
@@ -164,7 +167,8 @@ Marketing: [landing-page](recipe-landing-page.md).
|
|
|
164
167
|
[form-section](recipe-form-section.md) · [query-bar](recipe-query-bar.md) ·
|
|
165
168
|
[card-grid](recipe-card-grid.md) · [summary-list](recipe-summary-list.md) ·
|
|
166
169
|
[states](recipe-states.md) · [stat-callouts](recipe-stat-callouts.md) ·
|
|
167
|
-
[logo-wall](recipe-logo-wall.md) · [
|
|
170
|
+
[logo-wall](recipe-logo-wall.md) · [navigation](recipe-navigation.md) ·
|
|
171
|
+
[footer](recipe-footer.md). Marketing: [hero](recipe-hero.md) ·
|
|
168
172
|
[feature-section](recipe-feature-section.md) · [testimonial](recipe-testimonial.md) ·
|
|
169
173
|
[pricing](recipe-pricing.md) · [cta-band](recipe-cta-band.md).
|
|
170
174
|
|
|
@@ -13,6 +13,10 @@ Hosts inject `uxelleCatalogSchema` (and optionally a recipe's messages) into the
|
|
|
13
13
|
agent prompt and render the result. Do not open package source to "discover" props —
|
|
14
14
|
use the catalog schema the host provides.
|
|
15
15
|
|
|
16
|
+
Importing `@uxelle/a2ui` registers the full Material Symbols Sharp catalog. React
|
|
17
|
+
hosts that do not use A2UI must `import "@uxelle/icons/register"` themselves if
|
|
18
|
+
generated UI uses icon names beyond GPF chrome.
|
|
19
|
+
|
|
16
20
|
## Hard constraints
|
|
17
21
|
|
|
18
22
|
- **Catalog adapters only.** No raw HTML, no custom components.
|
|
@@ -70,6 +74,11 @@ chips; a chip wider than its container ellipsizes so dismiss stays visible.
|
|
|
70
74
|
Exclusive `A2uiChoiceChip` selection is one checked value in host state (toggles,
|
|
71
75
|
not radios). Use `A2uiRadioGroup` or `A2uiSegmentedControl` for radio semantics.
|
|
72
76
|
|
|
77
|
+
`A2uiStatTile` labels stay on one line and ellipsize; hover or focus a truncated
|
|
78
|
+
label to read the full name. Pass `label` as a string — do not wrap it in
|
|
79
|
+
`A2uiText` or set truncation. Value and qualifier copy wrap. The overflow tooltip
|
|
80
|
+
is automatic and separate from `tooltipContent`.
|
|
81
|
+
|
|
73
82
|
Known gaps — substitute rather than invent:
|
|
74
83
|
|
|
75
84
|
| React | A2UI |
|
|
@@ -26,7 +26,7 @@ This file covers **composition**: landmarks, heading ranks, names, and live regi
|
|
|
26
26
|
- `aria-describedby` — extra help, not the name (`fieldDescription`, instructions).
|
|
27
27
|
- `aria-hidden="true"` — decorative / redundant graphics (default `Icon`).
|
|
28
28
|
- `aria-live="polite"` — status that appears without moving focus. `assertive` only for urgent interruptions.
|
|
29
|
-
- `aria-current="page"` — current item in nav (`NavLink`, or `activated` on `NavigationSideItem` / `NavigationSideSubItem` destinations — not accordion parents).
|
|
29
|
+
- `aria-current="page"` — current item in nav (`NavLink`, or `activated` on `NavigationSideItem` / `NavigationSideSubItem` destinations — not accordion parents). When the side rail is collapsed, an accordion parent of an activated nested row still shows the selected appearance so the current section remains visible on the icon-only rail.
|
|
30
30
|
- `aria-expanded` — only on a real disclosure control, not on every section.
|
|
31
31
|
- `aria-pressed` — toggle buttons (`IconButton` `activated`); `Switch` uses `checked` instead.
|
|
32
32
|
|
|
@@ -14,6 +14,10 @@ Load component and theme CSS as in
|
|
|
14
14
|
[getting-started.md](../uxelle-components/getting-started.md). Do not set
|
|
15
15
|
`font-family` in app CSS — `Text` uses theme tokens.
|
|
16
16
|
|
|
17
|
+
If generated UI uses Material Symbol names beyond GPF chrome (`home`, `mail`,
|
|
18
|
+
`campaign`, …), add `import "@uxelle/icons/register"` once at the app entry.
|
|
19
|
+
`@uxelle/a2ui` already registers the full catalog.
|
|
20
|
+
|
|
17
21
|
On `<html>`:
|
|
18
22
|
|
|
19
23
|
- Theme mode (host-owned): `data-open-source="light" | "dark"` (other themes use
|
|
@@ -74,7 +78,7 @@ First focusable node in the DOM, targeting `id="main-content"` on `<main>`:
|
|
|
74
78
|
<a className="uxl-skip-link" href="#main-content">
|
|
75
79
|
Skip to main content
|
|
76
80
|
</a>
|
|
77
|
-
{/* Navigation — recipe-
|
|
81
|
+
{/* Navigation — recipe-navigation.md */}
|
|
78
82
|
<main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
|
|
79
83
|
{/* page column — how-to-page-layout.md */}
|
|
80
84
|
{/* Footer — inside this scrollport; recipe-app-chrome.md */}
|
|
@@ -184,8 +184,10 @@ Bands: `tablet` (from `--uxl-theme-layout-tablet-screen-width-min`), `desktop`,
|
|
|
184
184
|
when a component needs an explicit count.
|
|
185
185
|
- **Header / query clusters**: title over actions, search full-width with
|
|
186
186
|
filters/export below, when narrow; one row when wide (see below).
|
|
187
|
-
- **Navigation**:
|
|
188
|
-
|
|
187
|
+
- **Navigation**: below desktop the menu `IconButton` defaults to `Navigation`
|
|
188
|
+
`trailingSlot` and opens a `Sheet` from that same side (`useBreakpointUp("desktop")`,
|
|
189
|
+
not the tablet band). From desktop up it may live in any slot. Destinations
|
|
190
|
+
follow IA depth ([recipe-navigation.md](recipe-navigation.md)).
|
|
189
191
|
- **Tables**: `Table` scrolls horizontally when narrow (no stacked-row API); use
|
|
190
192
|
`PaginationSimple` where full `Pagination` is too wide.
|
|
191
193
|
- **Hero / media**: `Hero` `contentDirection` handles narrow stacking; related-field
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## When
|
|
4
4
|
|
|
5
|
-
React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md)
|
|
5
|
+
React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md)
|
|
6
|
+
(composition in [recipe-navigation.md](recipe-navigation.md)), scrolling `<main>`,
|
|
7
|
+
optional [`Footer`](../uxelle-components/Footer.md). Serves both product and
|
|
8
|
+
marketing shells.
|
|
6
9
|
|
|
7
10
|
## When not
|
|
8
11
|
|
|
@@ -17,7 +20,11 @@ React app frame: skip link, [`Navigation`](../uxelle-components/Navigation.md),
|
|
|
17
20
|
|
|
18
21
|
## Regions
|
|
19
22
|
|
|
20
|
-
skip link → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → *the experience's* content column ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)) → optional `Footer` **inside that same `<main>`**
|
|
23
|
+
skip link → optional [`BannerAnnouncement`](../uxelle-components/BannerAnnouncement.md) → `Navigation` (header) and/or `NavigationSide` (side rail wrapping `NavigationSideGroup` rows) → `<main id="main-content">` → *the experience's* content column ([how-to-page-layout.md](how-to-page-layout.md#who-owns-the-content-column)) → optional `Footer` **inside that same `<main>`**
|
|
24
|
+
|
|
25
|
+
Pick the **chrome pattern** before filling header slots (marketing header, signed-in
|
|
26
|
+
side rail, hybrid portal, or focused lockup-only) —
|
|
27
|
+
[recipe-navigation.md](recipe-navigation.md#chrome-pattern).
|
|
21
28
|
|
|
22
29
|
## Spacing
|
|
23
30
|
|
|
@@ -29,10 +36,20 @@ Do not wrap `Footer` in extra `Layout` padding. Band padding is catalog-owned: t
|
|
|
29
36
|
|
|
30
37
|
## A11y
|
|
31
38
|
|
|
32
|
-
[how-to-accessibility.md](how-to-accessibility.md) and skip-link CSS in [how-to-host.md](how-to-host.md). One `main`. `aria-current="page"` on the current `NavLink` or `NavigationSideItem` / `NavigationSideSubItem` (`activated`). Decorative `Logo`
|
|
39
|
+
[how-to-accessibility.md](how-to-accessibility.md) and skip-link CSS in [how-to-host.md](how-to-host.md). One `main`. `aria-current="page"` on the current `NavLink` or `NavigationSideItem` / `NavigationSideSubItem` (`activated`). Decorative `Logo` beside a visible product name: `aria-hidden`; name the home control on the wrapping `<a>` (`aria-label="Home"`).
|
|
33
40
|
|
|
34
41
|
`NavLink` in `Navigation` is **header** page navigation. `NavigationSide` is the side-rail **chrome** (`<aside>`, or a `<header>` when `mobile`). `NavigationSideGroup` is the **side** navigation landmark for the experience (a named `<nav>` of destination rows) and belongs in `NavigationSide` `centerSlotContent`. Do not wrap `NavigationSide` or `NavigationSideGroup` in another `nav`. Pass `aria-label` when more than one group is on the page. Accordion parents are not destinations — put section landing pages on `NavigationSideSubItem` children. Tabs that only change which rows appear in a table are **data** navigation — put them on the table ([recipe-data-table-page.md](recipe-data-table-page.md)), not in `centerSlot` and not as a second nav under the page title.
|
|
35
42
|
|
|
43
|
+
## Navigation
|
|
44
|
+
|
|
45
|
+
Header slot architecture — lockup (Logo as home, Condensed Alt name / Condensed
|
|
46
|
+
location or descriptor), empty `centerSlot` unless the IA is two or more levels
|
|
47
|
+
deep, compact hamburger in `trailingSlot` below desktop (omit it when
|
|
48
|
+
`NavigationSide` already owns the compact menu), Sheet `open` / `onOpenChange`
|
|
49
|
+
and `direction` matching the menu side, trailing search/session cluster,
|
|
50
|
+
IA-depth interiors — lives in [recipe-navigation.md](recipe-navigation.md). Pass
|
|
51
|
+
every slot explicitly (`null` to drop a region). Do not pass `false`.
|
|
52
|
+
|
|
36
53
|
## Side rail
|
|
37
54
|
|
|
38
55
|
Compose `NavigationSide` with `centerSlotContent` of `NavigationSideGroup` + `NavigationSideItem` (optional `accordion` with `NavigationSideSubItem` children). Set `expanded={false}` on `NavigationSide` for the icon-only rail (groups inherit collapsed layout). SSR hosts must pass `mobile={true}` or `mobile={false}` so server and client markup match. Omit `mobile` only in client-only surfaces to follow the theme mobile range. Do not hard-code viewport widths. Trailing slot content on destination rows must be non-interactive. When the mobile layout is shown, the menu control opens a right `Sheet` with the same `centerSlotContent` and `bottomSlotContent` as the rail (labeled rows). Use `menuOpen` / `onMenuOpenChange` to close it after navigation.
|
|
@@ -47,6 +64,12 @@ Footer content architecture — the brand block, categorized link columns, the l
|
|
|
47
64
|
|
|
48
65
|
Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
|
|
49
66
|
|
|
67
|
+
Signed-in default: identity + utilities, **no header hamburger**. Destinations
|
|
68
|
+
and the compact menu live on `NavigationSide` ([Side rail](#side-rail)). Public
|
|
69
|
+
marketing chrome with a trailing Sheet is in
|
|
70
|
+
[recipe-navigation.md](recipe-navigation.md) and
|
|
71
|
+
[Help and marketing chrome](#help-and-marketing-chrome).
|
|
72
|
+
|
|
50
73
|
```tsx
|
|
51
74
|
<Layout
|
|
52
75
|
display="flex"
|
|
@@ -64,16 +87,25 @@ Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
|
|
|
64
87
|
<Navigation
|
|
65
88
|
bottomSlot={null}
|
|
66
89
|
leadingSlot={
|
|
67
|
-
<Text type="Condensed Alt" truncation width={false}>
|
|
68
|
-
Product name
|
|
69
|
-
</Text>
|
|
70
|
-
}
|
|
71
|
-
centerSlot={
|
|
72
90
|
<>
|
|
73
|
-
<
|
|
74
|
-
|
|
91
|
+
<a href="/" aria-label="Home">
|
|
92
|
+
<Logo name="generic" width={48} interactive aria-hidden />
|
|
93
|
+
</a>
|
|
94
|
+
<Layout
|
|
95
|
+
display="flex"
|
|
96
|
+
flexDirection="column"
|
|
97
|
+
gap={0}
|
|
98
|
+
>
|
|
99
|
+
<Text type="Condensed Alt" truncation width={false}>
|
|
100
|
+
Product name
|
|
101
|
+
</Text>
|
|
102
|
+
<Text type="Condensed" truncation width={false}>
|
|
103
|
+
Short descriptor
|
|
104
|
+
</Text>
|
|
105
|
+
</Layout>
|
|
75
106
|
</>
|
|
76
107
|
}
|
|
108
|
+
centerSlot={null}
|
|
77
109
|
trailingSlot={
|
|
78
110
|
<IconButton emphasis="low" size="small" iconName="search" aria-label="Search" />
|
|
79
111
|
}
|
|
@@ -102,44 +134,89 @@ Import from `@uxelle/components`. Put skip-link CSS in the host stylesheet.
|
|
|
102
134
|
|
|
103
135
|
## Responsive
|
|
104
136
|
|
|
105
|
-
- **
|
|
106
|
-
|
|
137
|
+
- **Header navigation** — [recipe-navigation.md](recipe-navigation.md): below
|
|
138
|
+
desktop the visible menu control defaults to `trailingSlot` and opens a `Sheet`
|
|
139
|
+
from that same side. From desktop up the hamburger may live in any slot; omit
|
|
140
|
+
it when `centerSlot` is visible or when `NavigationSide` owns the compact menu.
|
|
141
|
+
When `centerSlot` holds category disclosures (two- or three-level IA), hide that
|
|
142
|
+
row below desktop (`useBreakpointUp("desktop")`). Trailing utilities stay a **row**
|
|
143
|
+
at every width — [how-to-page-layout.md](how-to-page-layout.md#action-clusters).
|
|
107
144
|
- **Footer** columns wrap intrinsically ([recipe-footer.md](recipe-footer.md)); band padding is catalog-owned. **Content column** reflows through the chrome tokens with nothing to set per breakpoint.
|
|
108
145
|
|
|
109
146
|
## A2UI
|
|
110
147
|
|
|
111
|
-
When-not for this recipe. Host owns chrome (skip link, viewport shell, html `data-*`). Catalog adapters: `A2uiNavigation`, `A2uiFooter`, `A2uiBannerAnnouncement` if the surface needs them — no `getUxelleRecipe("app-chrome")`. For the side rail only, `getUxelleRecipe("navigation-side")` from `@uxelle/a2ui`: trailing badges are `A2uiLabelBadge` with `"emphasis": "high"`; the account row is `A2uiList` + `A2uiListItem`. See [a2ui.md](a2ui.md).
|
|
148
|
+
When-not for this recipe. Host owns chrome (skip link, viewport shell, html `data-*`). Catalog adapters: `A2uiNavigation`, `A2uiFooter`, `A2uiBannerAnnouncement` if the surface needs them — no `getUxelleRecipe("app-chrome")`. Header slot defaults for `A2uiNavigation`: [recipe-navigation.md](recipe-navigation.md). For the side rail only, `getUxelleRecipe("navigation-side")` from `@uxelle/a2ui`: trailing badges are `A2uiLabelBadge` with `"emphasis": "high"`; the account row is `A2uiList` + `A2uiListItem`. See [a2ui.md](a2ui.md).
|
|
112
149
|
|
|
113
150
|
## Help and marketing chrome
|
|
114
151
|
|
|
115
|
-
Use the same `Navigation`
|
|
116
|
-
|
|
117
|
-
|
|
152
|
+
Use the same `Navigation` slot defaults as [recipe-navigation.md](recipe-navigation.md)
|
|
153
|
+
(help center, marketing). Pass `bottomSlot={null}` unless there is a persistent L2
|
|
154
|
+
bar. Unused **primary** slots must not be omitted — an omitted (`undefined`)
|
|
155
|
+
`leadingSlot` / `centerSlot` / `trailingSlot` fills catalog demo content. Pass
|
|
156
|
+
**`null`** to drop a slot. Do not pass `false`.
|
|
118
157
|
|
|
119
158
|
`Breadcrumbs` belong in the **page column**, not in `Navigation`. Related articles are an in-page `Link` list under the article, not nav or crumbs.
|
|
120
159
|
|
|
160
|
+
Control the header Sheet with `open` / `onOpenChange` and close it from each leaf
|
|
161
|
+
`onClick` ([recipe-navigation.md](recipe-navigation.md)).
|
|
162
|
+
|
|
121
163
|
```tsx
|
|
164
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
165
|
+
|
|
122
166
|
<Navigation
|
|
123
167
|
bottomSlot={null}
|
|
124
168
|
leadingSlot={
|
|
125
|
-
|
|
126
|
-
<
|
|
127
|
-
|
|
169
|
+
<>
|
|
170
|
+
<a href="/" aria-label="Home">
|
|
171
|
+
<Logo name="generic" width={48} interactive aria-hidden />
|
|
172
|
+
</a>
|
|
173
|
+
<Layout
|
|
174
|
+
display="flex"
|
|
175
|
+
flexDirection="column"
|
|
176
|
+
gap={0}
|
|
177
|
+
>
|
|
178
|
+
<Text type="Condensed Alt" truncation width={false}>
|
|
179
|
+
Help center
|
|
180
|
+
</Text>
|
|
181
|
+
</Layout>
|
|
182
|
+
</>
|
|
128
183
|
}
|
|
129
184
|
centerSlot={null}
|
|
130
185
|
trailingSlot={
|
|
131
186
|
<>
|
|
132
187
|
<LanguageSelector value="EN" />
|
|
133
188
|
<Button emphasis="low" size="small">
|
|
134
|
-
|
|
135
|
-
Log in
|
|
136
|
-
</Text>
|
|
189
|
+
Log in
|
|
137
190
|
</Button>
|
|
138
191
|
<Button emphasis="high" size="small">
|
|
139
|
-
|
|
140
|
-
Register
|
|
141
|
-
</Text>
|
|
192
|
+
Register
|
|
142
193
|
</Button>
|
|
194
|
+
<Sheet
|
|
195
|
+
open={menuOpen}
|
|
196
|
+
onOpenChange={setMenuOpen}
|
|
197
|
+
direction="Right"
|
|
198
|
+
title=""
|
|
199
|
+
aria-label="Menu"
|
|
200
|
+
trigger={
|
|
201
|
+
<IconButton
|
|
202
|
+
emphasis="low"
|
|
203
|
+
size="small"
|
|
204
|
+
iconName="menu"
|
|
205
|
+
iconVariant="sharpUnfilled"
|
|
206
|
+
aria-label="Menu"
|
|
207
|
+
/>
|
|
208
|
+
}
|
|
209
|
+
>
|
|
210
|
+
<List>
|
|
211
|
+
<ListItem
|
|
212
|
+
interactive
|
|
213
|
+
href="/help"
|
|
214
|
+
centerText="Articles"
|
|
215
|
+
bottomText=""
|
|
216
|
+
onClick={() => setMenuOpen(false)}
|
|
217
|
+
/>
|
|
218
|
+
</List>
|
|
219
|
+
</Sheet>
|
|
143
220
|
</>
|
|
144
221
|
}
|
|
145
222
|
/>
|
|
@@ -32,7 +32,8 @@ sections
|
|
|
32
32
|
|
|
33
33
|
KPI and card grids reflow with `auto-fit` / `minmax` (no JS); the header stacks and
|
|
34
34
|
its actions go `fullWidth` below tablet; the primary `Table` scrolls horizontally
|
|
35
|
-
when narrow.
|
|
35
|
+
when narrow. StatTile labels ellipsize on one line so a KPI row stays scannable.
|
|
36
|
+
See [how-to-page-layout.md](how-to-page-layout.md#responsiveness).
|
|
36
37
|
|
|
37
38
|
## Spacing
|
|
38
39
|
|
|
@@ -49,7 +50,8 @@ KPIs rather than a dense monitoring surface — but pick one mode and hold it.
|
|
|
49
50
|
## A11y
|
|
50
51
|
|
|
51
52
|
One `main`, one `h1`. Name each region (KPIs `aria-label="Key metrics"`, the primary
|
|
52
|
-
surface by its heading). KPI deltas pair color with a sign or word.
|
|
53
|
+
surface by its heading). KPI deltas pair color with a sign or word. Truncated KPI
|
|
54
|
+
labels keep the full name on the group; hover or focus reveals it. Loading and empty
|
|
53
55
|
for the primary surface follow [states](recipe-states.md). See
|
|
54
56
|
[how-to-accessibility.md](how-to-accessibility.md).
|
|
55
57
|
|