@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.
@@ -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).
@@ -24,6 +24,8 @@ A small set of headline numbers — revenue, active users, uptime, "10k+ teams."
24
24
  - Do not put `onClick` on StatTile — put a `Link` or `IconButton` in
25
25
  `trailingSlotContent`.
26
26
  - Do not signal a delta with color alone — pair it with a sign or word.
27
+ - Do not wrap StatTile labels yourself. The label stays on one line and
28
+ ellipsizes; hover or focus a truncated label to read the full name.
27
29
 
28
30
  ## Regions
29
31
 
@@ -50,10 +52,10 @@ band on a `spacious` page it adds breathing with the **Band** step (`large-15`)
50
52
  ## A11y
51
53
 
52
54
  Each figure and its label read together. StatTile names the group from `label`
53
- automatically (including `valueFirst`). If there is no label, set `aria-label`
54
- that combines the figure and its meaning. If the trailing slot holds a delta,
55
- keep the meaning in text, not color alone. See
56
- [how-to-accessibility.md](how-to-accessibility.md).
55
+ automatically (including `valueFirst`), including when the visible label is
56
+ ellipsized. If there is no label, set `aria-label` that combines the figure and
57
+ its meaning. If the trailing slot holds a delta, keep the meaning in text, not
58
+ color alone. See [how-to-accessibility.md](how-to-accessibility.md).
57
59
 
58
60
  ## Color
59
61