@uxelle/skills 0.2.3 → 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 +17 -12
- package/index.json +17 -12
- package/package.json +1 -1
- package/skills/uxelle-components/NavLink.md +2 -2
- package/skills/uxelle-components/Navigation.md +33 -23
- package/skills/uxelle-design-harness/SKILL.md +6 -2
- package/skills/uxelle-design-harness/how-to-host.md +1 -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-navigation.md +433 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/manifest.generated.ts
|
|
2
2
|
var manifest = {
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"name": "uxelle-components",
|
|
@@ -249,8 +249,8 @@ var manifest = {
|
|
|
249
249
|
},
|
|
250
250
|
{
|
|
251
251
|
"path": "Navigation.md",
|
|
252
|
-
"size":
|
|
253
|
-
"hash": "sha256:
|
|
252
|
+
"size": 3749,
|
|
253
|
+
"hash": "sha256:d51bf6f799deda23afae3b13a60e38d90b8a8069be1eedd05c5c6ec5d6d806ad"
|
|
254
254
|
},
|
|
255
255
|
{
|
|
256
256
|
"path": "NavigationSide.md",
|
|
@@ -274,8 +274,8 @@ var manifest = {
|
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
276
|
"path": "NavLink.md",
|
|
277
|
-
"size":
|
|
278
|
-
"hash": "sha256:
|
|
277
|
+
"size": 2765,
|
|
278
|
+
"hash": "sha256:6d8d696c1066b536cb3a36a145f316968bef40879b265a8d5fac06fbeaf9d69b"
|
|
279
279
|
},
|
|
280
280
|
{
|
|
281
281
|
"path": "NotificationBadge.md",
|
|
@@ -436,12 +436,12 @@ var manifest = {
|
|
|
436
436
|
{
|
|
437
437
|
"path": "how-to-host.md",
|
|
438
438
|
"size": 2856,
|
|
439
|
-
"hash": "sha256:
|
|
439
|
+
"hash": "sha256:dab3dc61a74b069600d56940f4b2b5e9210f2899ab5688613fbbbfdc035c60ba"
|
|
440
440
|
},
|
|
441
441
|
{
|
|
442
442
|
"path": "how-to-page-layout.md",
|
|
443
|
-
"size":
|
|
444
|
-
"hash": "sha256:
|
|
443
|
+
"size": 11126,
|
|
444
|
+
"hash": "sha256:2d06c39afbe035eb13bace7c744f7d19a2130248eb574a8b11f2bf179f83fa67"
|
|
445
445
|
},
|
|
446
446
|
{
|
|
447
447
|
"path": "principles.md",
|
|
@@ -450,8 +450,8 @@ var manifest = {
|
|
|
450
450
|
},
|
|
451
451
|
{
|
|
452
452
|
"path": "recipe-app-chrome.md",
|
|
453
|
-
"size":
|
|
454
|
-
"hash": "sha256:
|
|
453
|
+
"size": 12052,
|
|
454
|
+
"hash": "sha256:f4a1380400b4ea54a70dd8d77bb800d37fdf90240e1aaecd5aafad67730730bb"
|
|
455
455
|
},
|
|
456
456
|
{
|
|
457
457
|
"path": "recipe-card-grid.md",
|
|
@@ -508,6 +508,11 @@ var manifest = {
|
|
|
508
508
|
"size": 3876,
|
|
509
509
|
"hash": "sha256:351069ddf1e5d085d9f1a89caa47c368874b5d15d4cb9a53e9e1691c3df44a14"
|
|
510
510
|
},
|
|
511
|
+
{
|
|
512
|
+
"path": "recipe-navigation.md",
|
|
513
|
+
"size": 17612,
|
|
514
|
+
"hash": "sha256:37dde1f17bb34f12b9e53a930f336a7beafbb4ae9fc237bcff02a2b37018f922"
|
|
515
|
+
},
|
|
511
516
|
{
|
|
512
517
|
"path": "recipe-page-header.md",
|
|
513
518
|
"size": 4161,
|
|
@@ -565,8 +570,8 @@ var manifest = {
|
|
|
565
570
|
},
|
|
566
571
|
{
|
|
567
572
|
"path": "SKILL.md",
|
|
568
|
-
"size":
|
|
569
|
-
"hash": "sha256:
|
|
573
|
+
"size": 11032,
|
|
574
|
+
"hash": "sha256:75b96a393abccffd6c6292a22bbaf88f402fc68edbcd9326f7d099ea0d575d48"
|
|
570
575
|
},
|
|
571
576
|
{
|
|
572
577
|
"path": "spacing-steps.md",
|
package/index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
6
6
|
"name": "uxelle-components",
|
|
@@ -248,8 +248,8 @@
|
|
|
248
248
|
},
|
|
249
249
|
{
|
|
250
250
|
"path": "Navigation.md",
|
|
251
|
-
"size":
|
|
252
|
-
"hash": "sha256:
|
|
251
|
+
"size": 3749,
|
|
252
|
+
"hash": "sha256:d51bf6f799deda23afae3b13a60e38d90b8a8069be1eedd05c5c6ec5d6d806ad"
|
|
253
253
|
},
|
|
254
254
|
{
|
|
255
255
|
"path": "NavigationSide.md",
|
|
@@ -273,8 +273,8 @@
|
|
|
273
273
|
},
|
|
274
274
|
{
|
|
275
275
|
"path": "NavLink.md",
|
|
276
|
-
"size":
|
|
277
|
-
"hash": "sha256:
|
|
276
|
+
"size": 2765,
|
|
277
|
+
"hash": "sha256:6d8d696c1066b536cb3a36a145f316968bef40879b265a8d5fac06fbeaf9d69b"
|
|
278
278
|
},
|
|
279
279
|
{
|
|
280
280
|
"path": "NotificationBadge.md",
|
|
@@ -435,12 +435,12 @@
|
|
|
435
435
|
{
|
|
436
436
|
"path": "how-to-host.md",
|
|
437
437
|
"size": 2856,
|
|
438
|
-
"hash": "sha256:
|
|
438
|
+
"hash": "sha256:dab3dc61a74b069600d56940f4b2b5e9210f2899ab5688613fbbbfdc035c60ba"
|
|
439
439
|
},
|
|
440
440
|
{
|
|
441
441
|
"path": "how-to-page-layout.md",
|
|
442
|
-
"size":
|
|
443
|
-
"hash": "sha256:
|
|
442
|
+
"size": 11126,
|
|
443
|
+
"hash": "sha256:2d06c39afbe035eb13bace7c744f7d19a2130248eb574a8b11f2bf179f83fa67"
|
|
444
444
|
},
|
|
445
445
|
{
|
|
446
446
|
"path": "principles.md",
|
|
@@ -449,8 +449,8 @@
|
|
|
449
449
|
},
|
|
450
450
|
{
|
|
451
451
|
"path": "recipe-app-chrome.md",
|
|
452
|
-
"size":
|
|
453
|
-
"hash": "sha256:
|
|
452
|
+
"size": 12052,
|
|
453
|
+
"hash": "sha256:f4a1380400b4ea54a70dd8d77bb800d37fdf90240e1aaecd5aafad67730730bb"
|
|
454
454
|
},
|
|
455
455
|
{
|
|
456
456
|
"path": "recipe-card-grid.md",
|
|
@@ -507,6 +507,11 @@
|
|
|
507
507
|
"size": 3876,
|
|
508
508
|
"hash": "sha256:351069ddf1e5d085d9f1a89caa47c368874b5d15d4cb9a53e9e1691c3df44a14"
|
|
509
509
|
},
|
|
510
|
+
{
|
|
511
|
+
"path": "recipe-navigation.md",
|
|
512
|
+
"size": 17612,
|
|
513
|
+
"hash": "sha256:37dde1f17bb34f12b9e53a930f336a7beafbb4ae9fc237bcff02a2b37018f922"
|
|
514
|
+
},
|
|
510
515
|
{
|
|
511
516
|
"path": "recipe-page-header.md",
|
|
512
517
|
"size": 4161,
|
|
@@ -564,8 +569,8 @@
|
|
|
564
569
|
},
|
|
565
570
|
{
|
|
566
571
|
"path": "SKILL.md",
|
|
567
|
-
"size":
|
|
568
|
-
"hash": "sha256:
|
|
572
|
+
"size": 11032,
|
|
573
|
+
"hash": "sha256:75b96a393abccffd6c6292a22bbaf88f402fc68edbcd9326f7d099ea0d575d48"
|
|
569
574
|
},
|
|
570
575
|
{
|
|
571
576
|
"path": "spacing-steps.md",
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ Primary or secondary navigation item with Navigation Link typography.
|
|
|
17
17
|
| trailingIconName | `string` | `"keyboard_arrow_down"` | Material Symbol name for the trailing icon. Ignored when `trailingIcon` is false, and overridden by the menu chevron when `withMenu` is true. |
|
|
18
18
|
| iconVariant | `"sharpUnfilled" \| "sharpFilled"` | `"sharpUnfilled"` | Fill style shared by both icons: `sharpUnfilled` for the outline glyph, `sharpFilled` for the solid one. |
|
|
19
19
|
| navigationLevel | `"Primary" \| "Secondary"` | `"Primary"` | Which `Navigation` band the link sits in, so its colors match that surface: `Primary` for the main bar, `Secondary` for the sub-navigation bar. |
|
|
20
|
-
| withMenu | `boolean` | `false` | Wraps the link in a `Menu`
|
|
20
|
+
| withMenu | `boolean` | `false` | Wraps the link in a `Menu` (`pattern="menu"`, Tab dismisses) for overflow labeled More — not destination panels (`Menu` `pattern="disclosure"` with a `NavLink` trigger instead). The trigger stays a button (so `href` is ignored) and always shows a trailing `keyboard_arrow_down` that rotates 180° when open. |
|
|
21
21
|
| href | `string` | `undefined` | Destination URL, which renders the link as an anchor instead of a button. Ignored when `withMenu` is true. |
|
|
22
22
|
| children | `ReactNode` | `undefined` | Dropdown panel content — typically a `List` of `ListItem`s — shown when `withMenu` is true. Ignored otherwise; the visible text comes from `label`. |
|
|
23
23
|
| className | `string` | `—` | |
|
|
@@ -40,5 +40,5 @@ Primary or secondary navigation item with Navigation Link typography.
|
|
|
40
40
|
## Notes
|
|
41
41
|
|
|
42
42
|
- Renders as an anchor when `href` is set; otherwise a button.
|
|
43
|
-
- Set `withMenu`
|
|
43
|
+
- Set `withMenu` for overflow items labeled More. Destination panels use `Menu` `pattern="disclosure"` with a `NavLink` trigger (no `href`) so Tab walks links. `href` is ignored while `withMenu` is true so the trigger stays a button. A trailing `keyboard_arrow_down` icon is shown and rotates 180° when open.
|
|
44
44
|
- Use `navigationLevel="Secondary"` in the secondary nav band so colors follow that surface.
|
|
@@ -10,10 +10,10 @@ Two-tier navigation bar for enterprise applications.
|
|
|
10
10
|
|
|
11
11
|
| Prop | Type | Default | Description |
|
|
12
12
|
|------|------|---------|-------------|
|
|
13
|
-
| leadingSlot | `ReactNode \| null` | `undefined` | Start of the primary bar
|
|
14
|
-
| centerSlot | `ReactNode \| null` | `undefined` | Middle of the primary bar
|
|
15
|
-
| trailingSlot | `ReactNode \| null` | `undefined` | End of the primary bar
|
|
16
|
-
| bottomSlot | `ReactNode \| null` | `undefined` | Contents of the secondary bar
|
|
13
|
+
| leadingSlot | `ReactNode \| null` | `undefined` | Start of the primary bar. Slots are open — any node is valid. Generation default: a **Logo** home control plus a two-line lockup (`Text` Condensed Alt for the site name, optional Condensed for location or a short descriptor). The compact (below-desktop) menu `IconButton` does not belong here unless asked. Pass `null` to drop the region; omit to render the built-in placeholder branding. |
|
|
14
|
+
| centerSlot | `ReactNode \| null` | `undefined` | Middle of the primary bar. Slots are open. Generation default: `null` (destinations live in a trailing **Sheet**). Fill from desktop up with **Menu** `pattern="disclosure"` category triggers when the IA is two or more levels deep (`NavLink withMenu` is overflow More only). Pass `null` to leave the middle empty (blank space between leading and trailing); omit to render placeholder links. |
|
|
15
|
+
| trailingSlot | `ReactNode \| null` | `undefined` | End of the primary bar. Slots are open. Generation default: utilities (search, session) plus, below desktop, the menu `IconButton` that opens a **Sheet** from this side. Pass `null` to drop the region; omit for the built-in utility set. |
|
|
16
|
+
| bottomSlot | `ReactNode \| null` | `undefined` | Contents of the secondary bar. Generation default: `null` unless a persistent L2 module bar is needed (`NavLink`s with `navigationLevel="Secondary"`). Pass `null` to drop the secondary bar entirely; omit to render placeholder links. |
|
|
17
17
|
|
|
18
18
|
<!-- prettier-ignore-end -->
|
|
19
19
|
|
|
@@ -22,40 +22,50 @@ Extends `HTMLAttributes` — supports standard HTML attributes.
|
|
|
22
22
|
## Usage
|
|
23
23
|
|
|
24
24
|
```tsx
|
|
25
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
26
|
+
|
|
25
27
|
<Navigation
|
|
26
28
|
leadingSlot={
|
|
27
29
|
<>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<NavLink label="Menu Link" href="#" />
|
|
36
|
-
<NavLink label="Menu Link" href="#" />
|
|
30
|
+
<a href="/" aria-label="Home">
|
|
31
|
+
<Logo name="generic" width={48} interactive aria-hidden />
|
|
32
|
+
</a>
|
|
33
|
+
<Layout display="flex" flexDirection="column" gap={0}>
|
|
34
|
+
<Text type="Condensed Alt" truncation width={false}>Crop Science</Text>
|
|
35
|
+
<Text type="Condensed" truncation width={false}>United States</Text>
|
|
36
|
+
</Layout>
|
|
37
37
|
</>
|
|
38
38
|
}
|
|
39
|
+
centerSlot={null}
|
|
39
40
|
trailingSlot={
|
|
40
41
|
<>
|
|
41
42
|
<IconButton iconName="search" aria-label="Search" emphasis="low" size="small" />
|
|
42
43
|
<LanguageSelector value="EN" />
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
<Button emphasis="low" size="small">Log In</Button>
|
|
45
|
+
<Sheet
|
|
46
|
+
open={menuOpen}
|
|
47
|
+
onOpenChange={setMenuOpen}
|
|
48
|
+
direction="Right"
|
|
49
|
+
title=""
|
|
50
|
+
aria-label="Menu"
|
|
51
|
+
trigger={
|
|
52
|
+
<IconButton emphasis="low" size="small" iconName="menu" iconVariant="sharpUnfilled" aria-label="Menu" />
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
<List>
|
|
56
|
+
<ListItem interactive href="/about" centerText="About" bottomText="" onClick={() => setMenuOpen(false)} />
|
|
57
|
+
</List>
|
|
58
|
+
</Sheet>
|
|
51
59
|
</>
|
|
52
60
|
}
|
|
61
|
+
bottomSlot={null}
|
|
53
62
|
/>
|
|
54
63
|
```
|
|
55
64
|
|
|
56
65
|
|
|
57
66
|
## Notes
|
|
58
67
|
|
|
59
|
-
- **
|
|
68
|
+
- **Slots are open**: any node may go in `leadingSlot`, `centerSlot`, `trailingSlot`, or `bottomSlot`.
|
|
69
|
+
- **Generation defaults** (overridable): lockup in leading (Logo as home, Condensed Alt name, optional Condensed location or descriptor), `centerSlot` `null` unless the IA is two or more levels deep, compact menu `IconButton` in `trailingSlot` below desktop opening a **Sheet** from that same side (`open` / `onOpenChange` to close after a leaf). Omit a slot for the built-in placeholder. Pass `null` to drop leading, trailing, or the secondary bar; `centerSlot={null}` leaves blank space between leading and trailing.
|
|
70
|
+
- **Primary nav**: Leading, center, and trailing regions.
|
|
60
71
|
- **Secondary nav**: Optional bottom bar for sub-navigation.
|
|
61
|
-
- **Slots**: Pass `leadingSlot`, `centerSlot`, `trailingSlot`, and `bottomSlot` to customize each region. Omit a slot for the built-in placeholder; pass `null` to drop that region.
|
|
@@ -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
|
|
|
@@ -78,7 +78,7 @@ First focusable node in the DOM, targeting `id="main-content"` on `<main>`:
|
|
|
78
78
|
<a className="uxl-skip-link" href="#main-content">
|
|
79
79
|
Skip to main content
|
|
80
80
|
</a>
|
|
81
|
-
{/* Navigation — recipe-
|
|
81
|
+
{/* Navigation — recipe-navigation.md */}
|
|
82
82
|
<main id="main-content" tabIndex={-1} style={{ flexGrow: 1, minHeight: 0, overflow: "auto" }}>
|
|
83
83
|
{/* page column — how-to-page-layout.md */}
|
|
84
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
|
/>
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Recipe: navigation
|
|
2
|
+
|
|
3
|
+
A shared **piece**: the header bar — brand lockup, optional category disclosures,
|
|
4
|
+
utilities, and a compact menu [`Sheet`](../uxelle-components/Sheet.md). Used by
|
|
5
|
+
[app-chrome](recipe-app-chrome.md) (product and marketing).
|
|
6
|
+
|
|
7
|
+
Composes [`Navigation`](../uxelle-components/Navigation.md),
|
|
8
|
+
[`Logo`](../uxelle-components/Logo.md), [`Text`](../uxelle-components/Text.md),
|
|
9
|
+
[`NavLink`](../uxelle-components/NavLink.md),
|
|
10
|
+
[`IconButton`](../uxelle-components/IconButton.md),
|
|
11
|
+
[`Sheet`](../uxelle-components/Sheet.md), [`Menu`](../uxelle-components/Menu.md),
|
|
12
|
+
[`List`](../uxelle-components/List.md), [`ListItem`](../uxelle-components/ListItem.md),
|
|
13
|
+
[`Layout`](../uxelle-components/Layout.md), [`Button`](../uxelle-components/Button.md),
|
|
14
|
+
and optionally [`Textfield`](../uxelle-components/Textfield.md),
|
|
15
|
+
[`LanguageSelector`](../uxelle-components/LanguageSelector.md),
|
|
16
|
+
[`Accordion`](../uxelle-components/Accordion.md),
|
|
17
|
+
[`NotificationBadge`](../uxelle-components/NotificationBadge.md), and
|
|
18
|
+
[`BannerAnnouncement`](../uxelle-components/BannerAnnouncement.md).
|
|
19
|
+
|
|
20
|
+
Slots are **open** — any node may live in any region. The defaults below are
|
|
21
|
+
generation defaults; a prompt may override them.
|
|
22
|
+
|
|
23
|
+
## When
|
|
24
|
+
|
|
25
|
+
The top-of-page wayfinding for an experience that uses header `Navigation`. Place
|
|
26
|
+
it in the app frame above `<main>` ([app-chrome](recipe-app-chrome.md)).
|
|
27
|
+
|
|
28
|
+
## When not
|
|
29
|
+
|
|
30
|
+
- Not an `AppChrome` / `NavigationBar` component to export from the customer app.
|
|
31
|
+
- Signed-in product destinations belong on
|
|
32
|
+
[`NavigationSide`](../uxelle-components/NavigationSide.md) — do not also put the
|
|
33
|
+
full IA in a header mega menu or a second hamburger. The header then holds
|
|
34
|
+
identity + utilities only.
|
|
35
|
+
- Focused flows (login, checkout, wizard): lockup only — no hamburger, no center
|
|
36
|
+
row, no footer.
|
|
37
|
+
- Breadcrumbs, table search, and data tabs stay in the **page column**
|
|
38
|
+
([recipe-data-table-page.md](recipe-data-table-page.md)), not in `Navigation`.
|
|
39
|
+
- A zip / region / “add store” strip above the bar is `BannerAnnouncement`, not a
|
|
40
|
+
fourth Navigation slot.
|
|
41
|
+
- Pass real slot content; omitting a slot (`undefined`) fills catalog demo
|
|
42
|
+
content. Pass **`null`** to drop a region. Do not pass `false` (empty wrapper
|
|
43
|
+
remains).
|
|
44
|
+
|
|
45
|
+
## Chrome pattern
|
|
46
|
+
|
|
47
|
+
Choose a frame **before** filling slots. Stacking every pattern is the usual miss.
|
|
48
|
+
|
|
49
|
+
- **Marketing / public** — header `Navigation` only (lockup + Sheet or mega menu).
|
|
50
|
+
This recipe’s default.
|
|
51
|
+
- **Signed-in product** — `NavigationSide` owns destinations; the header is
|
|
52
|
+
identity + utilities (search, account).
|
|
53
|
+
- **Hybrid portal** — lockup + hamburger Sheet, little or no center row, even on
|
|
54
|
+
desktop.
|
|
55
|
+
- **Focused flow** — lockup only; `centerSlot={null}`, no menu control.
|
|
56
|
+
|
|
57
|
+
## Regions
|
|
58
|
+
|
|
59
|
+
`leadingSlot` (brand lockup) · `centerSlot` (desktop category disclosures when
|
|
60
|
+
the IA is two or more levels deep; otherwise `null`, which leaves blank space
|
|
61
|
+
between leading and trailing) · `trailingSlot` (utilities + compact hamburger) ·
|
|
62
|
+
`bottomSlot` (`null` unless a persistent L2 module bar).
|
|
63
|
+
|
|
64
|
+
Optional `BannerAnnouncement` sits **above** `Navigation`, not in a slot.
|
|
65
|
+
|
|
66
|
+
## Lockup (`leadingSlot`)
|
|
67
|
+
|
|
68
|
+
- [`Logo`](../uxelle-components/Logo.md) when the experience has a mark
|
|
69
|
+
(`width={48}`). **Home is the mark**, not the whole lockup: wrap only `Logo` in
|
|
70
|
+
a plain `<a href="/" aria-label="Home">` and set `interactive`. Keep the mark
|
|
71
|
+
`aria-hidden` when title text is present.
|
|
72
|
+
- Two-line type — **no extra gap** (`gap={0}`). Condensed line boxes already
|
|
73
|
+
separate the lines; do not add Tight / `micro-2` between them
|
|
74
|
+
([recipe-data-table-page.md](recipe-data-table-page.md) identity cells):
|
|
75
|
+
- Top — `Text type="Condensed Alt"` `truncation`: the **site or experience
|
|
76
|
+
name**.
|
|
77
|
+
- Bottom — `Text type="Condensed"`: **location or a short descriptor**. Omit
|
|
78
|
+
the second line when there is nothing to say.
|
|
79
|
+
- Do not use Display or Body types in the lockup.
|
|
80
|
+
- Do **not** wrap the lockup in [`Link`](../uxelle-components/Link.md) or nest the
|
|
81
|
+
title in `<a>` / `uxl-link`. `Link` underlines at rest — that underline on
|
|
82
|
+
Condensed lockup text looks wrong.
|
|
83
|
+
- No mark → do not turn the title into a link just to get a home control.
|
|
84
|
+
- Do not also add a “Home” `NavLink` or Sheet row when the logo already goes home.
|
|
85
|
+
- The lockup is not the page `h1`.
|
|
86
|
+
|
|
87
|
+
## Center and overflow
|
|
88
|
+
|
|
89
|
+
- **One-level IA:** `centerSlot={null}`. Destinations live in the Sheet.
|
|
90
|
+
- **Two or more levels, from desktop up:** fill `centerSlot` with top-level
|
|
91
|
+
category controls that **click** open a disclosure panel (see
|
|
92
|
+
[IA depth](#ia-depth-what-is-inside-the-menu)). Hide that row below desktop.
|
|
93
|
+
Never show `centerSlot` and the compact hamburger together.
|
|
94
|
+
- About five to seven L1 items, then a `NavLink withMenu` labeled More
|
|
95
|
+
(overflow only). Do not wrap an unbounded row of categories.
|
|
96
|
+
|
|
97
|
+
## Menu control and Sheet
|
|
98
|
+
|
|
99
|
+
[`IconButton`](../uxelle-components/IconButton.md) `iconName="menu"`
|
|
100
|
+
`iconVariant="sharpUnfilled"` `emphasis="low"` `aria-label="Menu"` opens a
|
|
101
|
+
`Sheet` whose body is the destination tree.
|
|
102
|
+
|
|
103
|
+
Control the Sheet with `open` / `onOpenChange` (same idea as `NavigationSide`
|
|
104
|
+
`menuOpen` / `onMenuOpenChange`). Close it after a leaf: `onClick={() =>
|
|
105
|
+
setMenuOpen(false)}` on each destination `ListItem`. An uncontrolled Sheet
|
|
106
|
+
stays open after in-app navigation.
|
|
107
|
+
|
|
108
|
+
- **From desktop up:** the menu control may live in **any slot**. For two- and
|
|
109
|
+
three-level IA, omit it while `centerSlot` is visible — the compact control
|
|
110
|
+
is for below desktop.
|
|
111
|
+
- **Below desktop:** the **visible** menu control defaults to **`trailingSlot`**
|
|
112
|
+
(right edge). If a desktop menu is in `leadingSlot`, hide that instance below
|
|
113
|
+
desktop and show a trailing one — one visible hamburger per breakpoint, not
|
|
114
|
+
two. Switch with `useBreakpointUp("desktop")` (the band catalog chrome uses).
|
|
115
|
+
Do not use the tablet band for this — tablet would keep L1 links and the
|
|
116
|
+
hamburger on screen together.
|
|
117
|
+
- Do not leave only a leading hamburger glued to the title at compact widths.
|
|
118
|
+
- Do not add a header hamburger when the frame already uses `NavigationSide`
|
|
119
|
+
(it already owns the compact menu + right Sheet).
|
|
120
|
+
|
|
121
|
+
**Sheet `direction` follows the menu.** Open the panel from the same side as the
|
|
122
|
+
visible control:
|
|
123
|
+
|
|
124
|
+
- Menu on the **right** (`trailingSlot`) → `direction="Right"`.
|
|
125
|
+
- Menu on the **left** (`leadingSlot`) → `direction="Left"`.
|
|
126
|
+
- Ambiguous (e.g. center) → `Right`.
|
|
127
|
+
- Desktop leading + below-desktop trailing: switch `direction` with the same
|
|
128
|
+
breakpoint as the visible trigger (`useBreakpointUp("desktop")`), or use one
|
|
129
|
+
Sheet per visible trigger.
|
|
130
|
+
|
|
131
|
+
Leave the Sheet header empty except the close control: `title=""` and
|
|
132
|
+
`aria-label="Menu"` (same as `NavigationSide`). Do not put a visible “Menu”
|
|
133
|
+
heading before the close button. Use the `trigger` slot for the `IconButton`.
|
|
134
|
+
|
|
135
|
+
## Trailing utilities
|
|
136
|
+
|
|
137
|
+
Stay a **row** at every width
|
|
138
|
+
([how-to-page-layout.md](how-to-page-layout.md#action-clusters)). Typical
|
|
139
|
+
cluster, left to right: search → language (if needed) → session → compact
|
|
140
|
+
hamburger.
|
|
141
|
+
|
|
142
|
+
- **Search** — marketing/catalog: visible [`Textfield`](../uxelle-components/Textfield.md)
|
|
143
|
+
(`label={false}`, `aria-label="Search"`) in a [`Layout`](../uxelle-components/Layout.md)
|
|
144
|
+
`width="16rem"` `flexShrink={0}`. Dense product: search `IconButton`. Table/query
|
|
145
|
+
search stays on the page, not here.
|
|
146
|
+
- **Session** — logged out: Log in + optional Register (one high-emphasis CTA).
|
|
147
|
+
Logged in: account `IconButton` + `Menu` (profile, settings, log out), optional
|
|
148
|
+
`NotificationBadge`. Do not keep Register when the user is signed in.
|
|
149
|
+
- Do not bury Log in / search in the Sheet.
|
|
150
|
+
|
|
151
|
+
## Secondary bar (`bottomSlot`)
|
|
152
|
+
|
|
153
|
+
`null` unless there is a persistent **L2 module bar** under the primary bar
|
|
154
|
+
(app section switcher). `NavLink`s there use `navigationLevel="Secondary"`. Not
|
|
155
|
+
breadcrumbs, not in-page tabs, not a dump for extra links.
|
|
156
|
+
|
|
157
|
+
## IA depth: what is inside the menu
|
|
158
|
+
|
|
159
|
+
Treat depth of the information architecture as the switch. Compose existing
|
|
160
|
+
pieces; do not invent a mega-menu component.
|
|
161
|
+
|
|
162
|
+
**Open on click, not hover.** Destination panels are `Menu` `pattern="disclosure"`
|
|
163
|
+
with a `NavLink` trigger (no `href`, `trailingIcon`) so Tab walks links.
|
|
164
|
+
`NavLink withMenu` is overflow labeled More only (`pattern="menu"`, Tab
|
|
165
|
+
dismisses). `keyboard_arrow_down` on a category trigger signals a disclosure.
|
|
166
|
+
`chevron_right` on a row means **another column / level**, not outbound.
|
|
167
|
+
|
|
168
|
+
### One level (flat site)
|
|
169
|
+
|
|
170
|
+
Destinations are peers. `centerSlot={null}`. The Sheet holds a `List` of
|
|
171
|
+
interactive `ListItem`s with `href` — **no chevrons**. Example: About, Contact.
|
|
172
|
+
Pass `bottomText=""` so the catalog demo line does not show.
|
|
173
|
+
|
|
174
|
+
### Two levels
|
|
175
|
+
|
|
176
|
+
Top-level groups, each with a flat list of leaves.
|
|
177
|
+
|
|
178
|
+
- **From desktop up:** `centerSlot` is `Menu` `pattern="disclosure"` per group
|
|
179
|
+
(`NavLink` trigger, no `href`, `trailingIcon`). Panel is one column of leaf
|
|
180
|
+
`ListItem`s (`href`, no chevron). Optional short group intro above the list.
|
|
181
|
+
Overflow labeled More uses `NavLink withMenu`.
|
|
182
|
+
- **Below desktop:** hide `centerSlot`. Sheet lists the groups as
|
|
183
|
+
[`Accordion`](../uxelle-components/Accordion.md) (omit `headingLevel` — these
|
|
184
|
+
are not page headings). Each panel is the leaf list. Leaves have no chevron.
|
|
185
|
+
Close the Sheet from each leaf `onClick`.
|
|
186
|
+
|
|
187
|
+
### Three levels
|
|
188
|
+
|
|
189
|
+
Top-level areas → categories → leaves. Chevron means another column / level.
|
|
190
|
+
|
|
191
|
+
- **From desktop up:** `centerSlot` uses `Menu` `pattern="disclosure"` with a
|
|
192
|
+
`NavLink` trigger (no `href`, `trailingIcon`). The panel is a **wide**
|
|
193
|
+
`Layout` row, not a skinny dropdown:
|
|
194
|
+
1. **Intro column** — area title (`Text`), one-line descriptor, `Link` to the
|
|
195
|
+
area landing. Quieter surface; not a list.
|
|
196
|
+
2. **Category column** — interactive `ListItem`s. Rows with children get
|
|
197
|
+
`trailingSlot` + `trailingIconName="chevron_right"`. Hold the selected
|
|
198
|
+
category in state and set `activated` from that state (it drives the next
|
|
199
|
+
column). A row with no children is a leaf (no chevron).
|
|
200
|
+
3. **Leaf column** — map the selected category to `ListItem` `href` rows with
|
|
201
|
+
**no** chevron. Optional “View all {category}” at the bottom of this
|
|
202
|
+
column only.
|
|
203
|
+
- **Below desktop:** do **not** lay out three columns in the Sheet. Same tree as
|
|
204
|
+
a **drill-down**: Accordion (or nested Accordion) so one extra level expands in
|
|
205
|
+
place; leaves are `href` rows. One visible extra level at a time.
|
|
206
|
+
|
|
207
|
+
Selecting a category in the desktop panel **updates the next column in place**
|
|
208
|
+
(does not navigate away). Only leaf rows and “Explore …” / “View all …”
|
|
209
|
+
navigate. Close the panel or Sheet after a leaf navigation (`onOpenChange` /
|
|
210
|
+
`setMenuOpen(false)`).
|
|
211
|
+
|
|
212
|
+
## Responsive
|
|
213
|
+
|
|
214
|
+
- One-level: hamburger may sit in `trailingSlot` at every width (valid on
|
|
215
|
+
desktop and below desktop).
|
|
216
|
+
- Two- and three-level: show `centerSlot` from desktop up; below desktop set
|
|
217
|
+
`centerSlot={null}` and put the menu `IconButton` + Sheet in `trailingSlot`.
|
|
218
|
+
Switch with `useBreakpointUp("desktop")`
|
|
219
|
+
([how-to-page-layout.md](how-to-page-layout.md#responsiveness)). Never
|
|
220
|
+
hard-code breakpoint px. Do not use the tablet band for this chrome switch.
|
|
221
|
+
- Trailing utilities stay a row at every width.
|
|
222
|
+
|
|
223
|
+
## Spacing
|
|
224
|
+
|
|
225
|
+
`Navigation` is **density-invariant** chrome (its own tokens). Do not wrap it in
|
|
226
|
+
extra `p` / `gap`. Lockup lines use `gap={0}` — not the Tight step. Slot content
|
|
227
|
+
inherits the host experience’s density mode ([density.md](density.md)).
|
|
228
|
+
|
|
229
|
+
## A11y
|
|
230
|
+
|
|
231
|
+
[how-to-accessibility.md](how-to-accessibility.md). `Navigation` already exposes
|
|
232
|
+
named `nav` landmarks. Name the home control on the Logo’s wrapping `<a>`
|
|
233
|
+
(`aria-label="Home"`); keep the mark decorative. Name the menu `IconButton`.
|
|
234
|
+
`aria-current="page"` on the current leaf `NavLink` / `ListItem` `href`.
|
|
235
|
+
`activated` on the selected mega-menu category (not a leaf). Name the Sheet with
|
|
236
|
+
`aria-label="Menu"` and `title=""` so the header is only the close control.
|
|
237
|
+
Every `IconButton` has an `aria-label`.
|
|
238
|
+
|
|
239
|
+
## Color
|
|
240
|
+
|
|
241
|
+
`Navigation` owns each band’s surface — style slot content with role tokens; do
|
|
242
|
+
not hardcode color ([how-to-color.md](how-to-color.md)). Secondary-bar
|
|
243
|
+
`NavLink`s use `navigationLevel="Secondary"` so they follow that band.
|
|
244
|
+
|
|
245
|
+
## React
|
|
246
|
+
|
|
247
|
+
Import from `@uxelle/components`. One-level marketing/public default (lockup +
|
|
248
|
+
trailing hamburger Sheet). Control the Sheet with `open` / `onOpenChange` and
|
|
249
|
+
close it from each leaf `onClick`. `desktopUp` is unused here because the menu
|
|
250
|
+
stays in `trailingSlot` at every width.
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
254
|
+
|
|
255
|
+
<Navigation
|
|
256
|
+
bottomSlot={null}
|
|
257
|
+
leadingSlot={
|
|
258
|
+
<>
|
|
259
|
+
<a href="/" aria-label="Home">
|
|
260
|
+
<Logo name="generic" width={48} interactive aria-hidden />
|
|
261
|
+
</a>
|
|
262
|
+
<Layout
|
|
263
|
+
display="flex"
|
|
264
|
+
flexDirection="column"
|
|
265
|
+
gap={0}
|
|
266
|
+
>
|
|
267
|
+
<Text type="Condensed Alt" truncation width={false}>
|
|
268
|
+
Crop Science
|
|
269
|
+
</Text>
|
|
270
|
+
<Text type="Condensed" truncation width={false}>
|
|
271
|
+
United States
|
|
272
|
+
</Text>
|
|
273
|
+
</Layout>
|
|
274
|
+
</>
|
|
275
|
+
}
|
|
276
|
+
centerSlot={null}
|
|
277
|
+
trailingSlot={
|
|
278
|
+
<>
|
|
279
|
+
<LanguageSelector value="EN" />
|
|
280
|
+
<Button emphasis="low" size="small">
|
|
281
|
+
Log in
|
|
282
|
+
</Button>
|
|
283
|
+
<Button emphasis="high" size="small">
|
|
284
|
+
Register
|
|
285
|
+
</Button>
|
|
286
|
+
<Sheet
|
|
287
|
+
open={menuOpen}
|
|
288
|
+
onOpenChange={setMenuOpen}
|
|
289
|
+
direction="Right"
|
|
290
|
+
title=""
|
|
291
|
+
aria-label="Menu"
|
|
292
|
+
trigger={
|
|
293
|
+
<IconButton
|
|
294
|
+
emphasis="low"
|
|
295
|
+
size="small"
|
|
296
|
+
iconName="menu"
|
|
297
|
+
iconVariant="sharpUnfilled"
|
|
298
|
+
aria-label="Menu"
|
|
299
|
+
/>
|
|
300
|
+
}
|
|
301
|
+
>
|
|
302
|
+
<List>
|
|
303
|
+
<ListItem
|
|
304
|
+
interactive
|
|
305
|
+
href="/about"
|
|
306
|
+
centerText="About"
|
|
307
|
+
bottomText=""
|
|
308
|
+
onClick={() => setMenuOpen(false)}
|
|
309
|
+
/>
|
|
310
|
+
<ListItem
|
|
311
|
+
interactive
|
|
312
|
+
href="/contact"
|
|
313
|
+
centerText="Contact"
|
|
314
|
+
bottomText=""
|
|
315
|
+
onClick={() => setMenuOpen(false)}
|
|
316
|
+
/>
|
|
317
|
+
</List>
|
|
318
|
+
</Sheet>
|
|
319
|
+
</>
|
|
320
|
+
}
|
|
321
|
+
/>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Two-level desktop `centerSlot` (hide below desktop; overflow More uses
|
|
325
|
+
`withMenu`; Sheet in trailing carries the same tree):
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
<Menu
|
|
329
|
+
pattern="disclosure"
|
|
330
|
+
direction="Bottom Left"
|
|
331
|
+
trigger={<NavLink label="Crop Protection" trailingIcon />}
|
|
332
|
+
>
|
|
333
|
+
<List>
|
|
334
|
+
<ListItem interactive href="/crop-protection/fungicides" centerText="Fungicides" bottomText="" />
|
|
335
|
+
<ListItem interactive href="/crop-protection/herbicides" centerText="Herbicides" bottomText="" />
|
|
336
|
+
<ListItem interactive href="/crop-protection/trial-data" centerText="Trial Data" bottomText="" />
|
|
337
|
+
</List>
|
|
338
|
+
</Menu>
|
|
339
|
+
<NavLink label="More" withMenu trailingIcon>
|
|
340
|
+
<List>
|
|
341
|
+
<ListItem interactive href="/seeds" centerText="Seeds" bottomText="" />
|
|
342
|
+
</List>
|
|
343
|
+
</NavLink>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Three-level desktop `centerSlot` — selected category in state fills the next
|
|
347
|
+
column; close the panel from each leaf:
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
351
|
+
const [category, setCategory] = useState<"fungicides" | "herbicides">("fungicides");
|
|
352
|
+
|
|
353
|
+
const leaves = {
|
|
354
|
+
fungicides: [
|
|
355
|
+
{ href: "/products/delaro", label: "Delaro" },
|
|
356
|
+
{ href: "/products/luna", label: "Luna Family" },
|
|
357
|
+
{ href: "/crop-protection/fungicides", label: "View all Fungicides" },
|
|
358
|
+
],
|
|
359
|
+
herbicides: [
|
|
360
|
+
{ href: "/products/roundup", label: "Roundup" },
|
|
361
|
+
{ href: "/crop-protection/herbicides", label: "View all Herbicides" },
|
|
362
|
+
],
|
|
363
|
+
} as const;
|
|
364
|
+
|
|
365
|
+
<Menu
|
|
366
|
+
pattern="disclosure"
|
|
367
|
+
direction="Bottom Left"
|
|
368
|
+
open={panelOpen}
|
|
369
|
+
onOpenChange={setPanelOpen}
|
|
370
|
+
trigger={<NavLink label="Crop Protection" trailingIcon />}
|
|
371
|
+
>
|
|
372
|
+
<Layout display="flex" gap="var(--uxl-theme-layout-spacing-small-4)">
|
|
373
|
+
<Layout display="flex" flexDirection="column" gap="var(--uxl-theme-layout-spacing-micro-2)">
|
|
374
|
+
<Text type="Condensed Alt" width={false}>
|
|
375
|
+
Crop Protection
|
|
376
|
+
</Text>
|
|
377
|
+
<Text type="Condensed" width={false}>
|
|
378
|
+
Innovation to help farmers protect their harvests.
|
|
379
|
+
</Text>
|
|
380
|
+
<Link href="/crop-protection">Explore Crop Protection</Link>
|
|
381
|
+
</Layout>
|
|
382
|
+
<List>
|
|
383
|
+
<ListItem
|
|
384
|
+
interactive
|
|
385
|
+
activated={category === "fungicides"}
|
|
386
|
+
trailingSlot
|
|
387
|
+
trailingIconName="chevron_right"
|
|
388
|
+
centerText="Fungicides"
|
|
389
|
+
bottomText=""
|
|
390
|
+
onClick={() => setCategory("fungicides")}
|
|
391
|
+
/>
|
|
392
|
+
<ListItem
|
|
393
|
+
interactive
|
|
394
|
+
activated={category === "herbicides"}
|
|
395
|
+
trailingSlot
|
|
396
|
+
trailingIconName="chevron_right"
|
|
397
|
+
centerText="Herbicides"
|
|
398
|
+
bottomText=""
|
|
399
|
+
onClick={() => setCategory("herbicides")}
|
|
400
|
+
/>
|
|
401
|
+
<ListItem
|
|
402
|
+
interactive
|
|
403
|
+
href="/crop-protection/trial-data"
|
|
404
|
+
centerText="Trial Data"
|
|
405
|
+
bottomText=""
|
|
406
|
+
onClick={() => setPanelOpen(false)}
|
|
407
|
+
/>
|
|
408
|
+
</List>
|
|
409
|
+
<List>
|
|
410
|
+
{leaves[category].map((item) => (
|
|
411
|
+
<ListItem
|
|
412
|
+
key={item.href}
|
|
413
|
+
interactive
|
|
414
|
+
href={item.href}
|
|
415
|
+
centerText={item.label}
|
|
416
|
+
bottomText=""
|
|
417
|
+
onClick={() => setPanelOpen(false)}
|
|
418
|
+
/>
|
|
419
|
+
))}
|
|
420
|
+
</List>
|
|
421
|
+
</Layout>
|
|
422
|
+
</Menu>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## A2UI
|
|
426
|
+
|
|
427
|
+
No `getUxelleRecipe("navigation")`. Hosts often own chrome; when the surface
|
|
428
|
+
already has `A2uiNavigation`, use the same slot assignment. Runtime chat has no
|
|
429
|
+
`matchMedia` — default to one-level (lockup + trailing `A2uiSheet` `direction`
|
|
430
|
+
`"Right"`). Empty or omitted child lists become catalog placeholders (the adapter
|
|
431
|
+
maps them to `undefined`); prefer host-owned chrome when a region must be absent.
|
|
432
|
+
Compose `A2uiLogo`, `A2uiText`, `A2uiNavLink`, `A2uiList` / `A2uiListItem`,
|
|
433
|
+
`A2uiIconButton`, `A2uiSheet`. See [a2ui.md](a2ui.md).
|