@helloimjolopez-pathway/pathway-tokens 11.1.0 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Design system for Ministry Brands Amplify. Tokens + components + specifications, all versioned in one place, with Storybook deployed from `main`.
4
4
 
5
- > **Looking for the tokens?** → **[TOKENS.md](TOKENS.md)** — where the CSS lives, which
5
+ > **Looking for the tokens?** → **[TOKENS.md](TOKENS.md)**, where the CSS lives, which
6
6
  > of the 284 names you may use, and what to link in what order.
7
7
 
8
8
  ## Using the tokens
@@ -16,14 +16,14 @@ one name set rather than each having their own.
16
16
 
17
17
  | | Count | Name these? |
18
18
  |---|---|---|
19
- | Colour (`themes/light.css`, `themes/midnight.css` — same names) | **186** | Yes |
19
+ | Colour (`themes/light.css`, `themes/midnight.css`, same names) | **186** | Yes |
20
20
  | Type scale (`type.css`) | 41 | Yes |
21
21
  | Spacing, radii, borders (`layout.css`) | 40 | Yes |
22
22
  | Motion (`motion.css`) | 17 | Yes |
23
23
  | Sheet and TopNav padding (`layout-responsive.css`) | 6 | Yes |
24
24
  | Breakpoints (`breakpoints.css`) | 5 | Yes |
25
25
  | **Working vocabulary** | **284** | |
26
- | Raw ramps (`primitives.css`) | 350 | **No** — but you must load it |
26
+ | Raw ramps (`primitives.css`) | 350 | **No**, but you must load it |
27
27
  | Component metrics (`layout-contextual.css`) | 26 | This repo's components |
28
28
 
29
29
  **The CSS lives in [`src/tokens/`](src/tokens/), and that folder has its own README**
@@ -40,8 +40,7 @@ npm install @helloimjolopez-pathway/pathway-tokens
40
40
  NuGet: `Pathway.DesignTokens`.
41
41
 
42
42
  ```js
43
- // Every stylesheet is a named subpath export. primitives.css MUST come first —
44
- // the themes resolve through it, so without it all colour resolves to nothing
43
+ // Every stylesheet is a named subpath export. primitives.css MUST come first, // the themes resolve through it, so without it all colour resolves to nothing
45
44
  // and the page renders unstyled with no console error.
46
45
  import "@helloimjolopez-pathway/pathway-tokens/primitives.css";
47
46
  import "@helloimjolopez-pathway/pathway-tokens/themes/light.css";
@@ -92,8 +91,7 @@ as too granular to adopt. Retired 2026-09-03 and not coming back.
92
91
  ```
93
92
 
94
93
  One name carries both modes. Set `data-theme="midnight"` on `<html>` to flip the page,
95
- or on any element to flip just that subtree. Never write a mode into a property name —
96
- `--semantic-color-light-mode-*` resolves to nothing.
94
+ or on any element to flip just that subtree. Never write a mode into a property name, `--semantic-color-light-mode-*` resolves to nothing.
97
95
 
98
96
  ### Can I use a primitive?
99
97
 
@@ -129,7 +127,7 @@ pathway-ds/
129
127
  ├── scripts/ Token sync/audit/fix + component build scripts
130
128
  ```
131
129
 
132
- ## Governance — where things live
130
+ ## Governance: where things live
133
131
 
134
132
  | To change… | Where |
135
133
  |---|---|
@@ -146,15 +144,15 @@ pathway-ds/
146
144
 
147
145
  Three workflows in `.github/workflows/`:
148
146
 
149
- 1. **`sync-tokens.yml`** — fires when `tokens/figma-export/**` changes. Normalises the Figma export into the W3C DTCG JSON and commits `tokens/pathway-design-tokens.json`.
150
- 2. **`sync-component.yml`** — fires when `components/sidenav/sidenav.html` changes. Regenerates the `sidenav-figmamake.html` variant and commits it.
151
- 3. **`deploy-pages.yml`** — fires on every push to `main`. Builds Style Dictionary and Storybook fresh and deploys a clean `_site` artifact through the official Pages actions. Nothing is committed back to `main`. (`deploy-storybook.yml` is the retired predecessor, kept manual-only; it committed 14MB of build output per push.)
147
+ 1. **`sync-tokens.yml`**, fires when `tokens/figma-export/**` changes. Normalises the Figma export into the W3C DTCG JSON and commits `tokens/pathway-design-tokens.json`.
148
+ 2. **`sync-component.yml`**, fires when `components/sidenav/sidenav.html` changes. Regenerates the `sidenav-figmamake.html` variant and commits it.
149
+ 3. **`deploy-pages.yml`**, fires on every push to `main`. Builds Style Dictionary and Storybook fresh and deploys a clean `_site` artifact through the official Pages actions. Nothing is committed back to `main`. (`deploy-storybook.yml` is the retired predecessor, kept manual-only; it committed 14MB of build output per push.)
152
150
 
153
151
  Source of truth is always the repo on `main`; Storybook is a downstream artifact.
154
152
 
155
153
  ## Typography (brand fonts)
156
154
 
157
- Pathway uses **Red Hat Text** (all UI text) and **Red Hat Display** (headings H1–H3 only). Both are open-source Google Fonts.
155
+ Pathway uses **Red Hat Text** (all UI text) and **Red Hat Display** (headings H1H3 only). Both are open-source Google Fonts.
158
156
 
159
157
  **Load via CDN (web/prototypes):**
160
158
 
@@ -170,6 +168,174 @@ Pathway uses **Red Hat Text** (all UI text) and **Red Hat Display** (headings H1
170
168
 
171
169
  Licensed under the [SIL Open Font License](https://scripts.sil.org/OFL). Free for any use.
172
170
 
171
+ ## The components
172
+
173
+ Everything in the library, with the four things you need for each: what it is
174
+ specified as, what implements it, where to see it running, and which Figma node
175
+ it was measured from. This table is generated from
176
+ [`components/manifest.json`](components/manifest.json) by
177
+ `npm run build-component-index`, so it cannot drift from the registry.
178
+
179
+ <!-- BEGIN component-index (generated by scripts/build-component-index.js) -->
180
+
181
+ **17 components with their own page, 2 building blocks that only exist nested.**
182
+
183
+ | Component | Spec | Module | Storybook | Figma node | Code Connect files |
184
+ |---|---|---|---|---|---|
185
+ | **SideNav** | [spec](components/sidenav/sidenav-spec.md) | [`sidenav.jsx`](components/sidenav/sidenav.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-sidenav--docs) | `40004059:1375` | 9 |
186
+ | **Scrollable** | [spec](components/scrollbar/scrollbar-spec.md) | [`scrollbar.jsx`](components/scrollbar/scrollbar.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-scrollbar--docs) | repo only, not in Figma | 0 |
187
+ | **Checkbox** | [spec](components/checkbox/checkbox-spec.md) | [`checkbox.jsx`](components/checkbox/checkbox.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-checkbox--docs) | `40002324:54532` | 1 |
188
+ | **OrgSwitcher** | [spec](components/org-switcher/org-switcher-spec.md) | [`org-switcher.jsx`](components/org-switcher/org-switcher.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-orgswitcher--docs) | `40006819:14583` | 2 |
189
+ | **TopNav.Global** | [spec](components/top-nav/top-nav-spec.md) | [`top-nav.jsx`](components/top-nav/top-nav.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-topnav--docs) | `40007067:6508` | 4 |
190
+ | **Spinner** | [spec](components/spinner/spinner-spec.md) | [`spinner.jsx`](components/spinner/spinner.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-spinner--docs) | `40006622:50003` | 0 |
191
+ | **Button** | [spec](components/button/button-spec.md) | [`button.jsx`](components/button/button.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-button--docs) | `40003293:93741` | 3 |
192
+ | **Search** | [spec](components/search/search-spec.md) | [`search.jsx`](components/search/search.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-global-search-top-nav--docs) | `40007095:4048` | 1 |
193
+ | **NavShell** | [spec](components/nav-shell/nav-shell-spec.md) | composed, see its stories | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-navshell--docs) | 3 sets on page `Navigation: Top Nav, Navigation: Side Nav, Page Template` | 0 |
194
+ | **Widget** | [spec](components/widget/widget-spec.md) | [`widget.jsx`](components/widget/widget.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-widget--docs) | `40009622:39702` | 2 |
195
+ | **KPI Tile** | [spec](components/kpi-tile/kpi-tile-spec.md) | [`kpi-tile.jsx`](components/kpi-tile/kpi-tile.jsx)<br>[`kpi-number-trend.jsx`](components/kpi-tile/kpi-number-trend.jsx)<br>[`mini-chart.jsx`](components/kpi-tile/mini-chart.jsx)<br>[`bar-chart.jsx`](components/kpi-tile/bar-chart.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-kpi-tile--docs) | 7 sets on page `40009862:10309` | 9 |
196
+ | **Dashboard** | [spec](components/dashboard/dashboard-spec.md) | [`dashboard.jsx`](components/dashboard/dashboard.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-dashboard--docs) | no Figma component | 0 |
197
+ | **Icon** | [spec](components/icon/icon-spec.md) | [`icon.jsx`](components/icon/icon.jsx)<br>[`action-icon.jsx`](components/icon/action-icon.jsx)<br>[`display-icon.jsx`](components/icon/display-icon.jsx)<br>[`featured-icon.jsx`](components/icon/featured-icon.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-action-icon--docs) | `48:1153` | 4 |
198
+ | **Dropdown** | [spec](components/dropdown/dropdown-spec.md) | [`dropdown.jsx`](components/dropdown/dropdown.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-dropdown--docs) | `40005568:4207` | 3 |
199
+ | **Mini Chart** | [spec](components/kpi-tile/mini-chart-spec.md) | [`mini-chart.jsx`](components/kpi-tile/mini-chart.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-mini-chart--docs) | `40009415:27919` | 1 |
200
+ | **Bar Chart** | [spec](components/kpi-tile/bar-chart-spec.md) | [`bar-chart.jsx`](components/kpi-tile/bar-chart.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-bar-chart--docs) | `40009417:13029` | 3 |
201
+ | **Page Template** | [spec](components/page-template/page-template-spec.md) | [`page-template.jsx`](components/page-template/page-template.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-page-template--docs) | `40009709:26016` | 0 |
202
+
203
+ Building blocks. Each is mapped for Code Connect and specced, but has no page
204
+ of its own because it only ever appears as a nested instance inside the
205
+ component above it.
206
+
207
+ | Component | Spec | Module | Storybook | Figma node | Code Connect files |
208
+ |---|---|---|---|---|---|
209
+ | **ModuleIcon** | [spec](components/module-icon/module-icon-spec.md) | [`module-icon.jsx`](components/module-icon/module-icon.jsx) | nested only | `40006876:42134` | 1 |
210
+ | **TopNav.Actions** | [spec](components/top-nav-actions/top-nav-actions-spec.md) | [`top-nav.jsx`](components/top-nav/top-nav.jsx) | [open](https://helloimjolopez-collab.github.io/pathway-ds/storybook/?path=/docs/library-topnav--docs) | `40007082:7313` | 1 |
211
+
212
+ <!-- END component-index -->
213
+
214
+ ### What is checked, and what is not
215
+
216
+ Two suites. `npm run build-dist` runs the token-integrity checks, and
217
+ `npm run check-tokens` runs the documentation and story checks, so both fail a
218
+ build rather than relying on someone noticing.
219
+
220
+ In `build-dist`:
221
+
222
+ | Check | What it catches |
223
+ |---|---|
224
+ | `check-demo-tokens` | a document naming a token that does not exist |
225
+ | `check-token-refs` | a reference to the retired `--semantic-color-light-mode-*` form |
226
+ | `check-token-names` | a name that is not kebab-case lowercase |
227
+ | `check-token-object-refs` | a `T`/`L` object key referenced but never defined |
228
+ | `check-ladder-order` | a ladder whose names disagree with its resolved luminance |
229
+ | `check-state-distinctness` | an interaction state indistinguishable from another state in its group |
230
+ | `check-variant-distinctness` | a component prop that has stopped changing anything |
231
+ | `check-fill-foreground-pairs` | a fill with no paired `On Subtle` / `On Strong` foreground, or the reverse |
232
+ | `check-motion-coverage` | a state change with no transition, or a partial one |
233
+ | `fix-stale-doc-hex --check` | a hand-copied hex in a doc that no longer matches its token |
234
+ | `check-story-yield` | a token docs page rendering less content than its floor |
235
+
236
+ In `check-tokens`, additionally:
237
+
238
+ | Check | What it catches |
239
+ |---|---|
240
+ | `build-component-index --check` | the component table above out of step with the manifest, and a Storybook link in it that is not in the build |
241
+ | `check-spec-stories` | a spec whose Storybook section names a story that does not exist, or omits one that does |
242
+ | `check-demo-links` | a standalone demo linking a stylesheet or script that is not served where the page is served. All twelve demos linked `../../src/tokens/*.css`, which resolves in the repo and NOWHERE ELSE: the built Storybook mounts `components/` only, and Pages copies `components/` while putting the token contract at `/tokens/`. Verified against the live site on 2026-10-08, `/src/tokens/primitives.css` was 404. Every `StandaloneDemo` story and every published demo URL had been loading nine 404s and rendering on fallback colours rather than Pathway's. `pathway-sidenav.html` was worse: it loaded `../../dist/pathway-sidenav.js`, which is gitignored and never published, so that page had no script at all and rendered nothing. |
243
+ | `check-component-tables` | a component that keeps its measurements as data naming a key its lookup object does not define. `KPI_TILE_SPECS` named `gapChart03`, `L` had no such key, so the value was undefined, React dropped the style and CSS fell back to no gap. Nothing threw and no story was blank. |
244
+
245
+ And the ones that need a built Storybook, so they are their own command,
246
+ `npm run verify-storybook`:
247
+
248
+ | Check | What it catches |
249
+ |---|---|
250
+ | `check-syntax` | a component or story file that does not parse. Redundant with the build and worth it anyway: it parses all 54 files in about a second, where the build takes three minutes and reports the same error as a WARN line far above the failure |
251
+ | `check-stories-render` | a story that renders NOTHING while the build stays green |
252
+ | `check-mdx-refs` | a docs page referencing a story that does not exist, which renders an empty block, and a `<Controls>` pointed at a story with no argTypes, which renders an empty table under a heading promising controls |
253
+ | `check-accessible-names` | a button with no accessible name, resolved the way a browser does: `aria-label`, `aria-labelledby`, text content with `aria-hidden` subtrees removed, then `title` |
254
+
255
+ **That last one matters more than it sounds.** A module-level error in a CSF
256
+ file blanks every story in that file and webpack compiles it happily. It has
257
+ happened three times here: a leftover `storyName` assignment after a rename
258
+ blanked all nine Global Search stories, a dangling `BRAND_AXIS` reference
259
+ blanked more, and `useState` used without being imported blanked the Widget
260
+ Glance configurations story. The check loads all 107 stories in headless Chrome
261
+ and fails on an empty root. It was verified by breaking a story on purpose: the
262
+ build reported no errors and the check reported the blank page.
263
+
264
+ **`check-accessible-names` found 101 on its first run,** all of them Buttons.
265
+ Every labelled Pathway Button announced as just "button": `aria-label` was set
266
+ only when `showText` was false, on the rule that a button showing its text does
267
+ not need a label. True of an ordinary button, false here, because the text sat
268
+ inside `pw-button__container`, which carried `aria-hidden="true"`, so the name
269
+ computation skipped it. The only named case was the icon-only one, which is
270
+ backwards. Fixed by removing that `aria-hidden` rather than adding a label, so
271
+ the name comes from the visible text and cannot drift from it.
272
+
273
+ **Standalone demos are generated now, for the three newest components.**
274
+ `scripts/build-demos.js` bundles the real module with esbuild and writes the
275
+ page around it, so Widget, KPI Tile and Dashboard have a hands-on resize demo
276
+ that cannot fall behind the component. The twelve older demos are hand-written
277
+ and each says, at the top of its script block, "Mirrors `<name>.jsx` exactly.
278
+ Keep the two in step": a second implementation of a shipped component, kept
279
+ correct by memory. Converting them is a separate job. Their token links were
280
+ repointed at `components/_demo/tokens/` so they at least render.
281
+
282
+ **What is NOT checked, and why it matters.**
283
+
284
+ - **Code Connect verifies nothing.** It publishes a code snippet to a Figma
285
+ component. It never compares a token value, a measurement or a layout, and it
286
+ flows repo to Figma only. A component can be fully mapped and completely
287
+ wrong.
288
+ - **The token checks confirm that a name RESOLVES, not that it is the right
289
+ name.** `check-fill-foreground-pairs` proves every fill HAS a paired
290
+ foreground; nothing measures the pair a component actually uses against a
291
+ contrast ratio per mode. Button's Primary and Negative labels sat on the mono
292
+ anchor at 3.04:1 and 2.85:1 in Midnight while every check passed.
293
+ - **Nothing can verify a Figma node id from here.** Only Figma knows whether an
294
+ id resolves and what kind of thing it is, and the MCP is interactively
295
+ authenticated, so this needs an agent session.
296
+ `npm run check-figma-nodes` does the half that can be automated: it lists
297
+ every id the repo cites, where each is cited, and which are cited as a set.
298
+
299
+ So drift between Figma and this repo is the normal state, not the exception,
300
+ and only measurement finds it. A sweep on 2026-10-07 found four defects of that
301
+ class that had each survived months: two cited ids that do not exist in the
302
+ file at all, Search's node pointing at a 2594x1680 layout frame rather than a
303
+ component, and NavShell's pointing at a variant rather than its set.
304
+
305
+ A second sweep on 2026-10-12 found three more. NavShell cited `ScreenTemplate`
306
+ as though it owned it: there is no NavShell set in the file, so the manifest now
307
+ names the three sets it composes and says so. `icon-spec.md` held the whole
308
+ 14-section spec twice, 645 lines, the second copy still describing the retired
309
+ single `Icon` Storybook page. The Page Template page's second
310
+ `Page Template` frame is the Midnight Mode showcase: instances of the one
311
+ `ScreenTemplate` set under a `Semantic: Color` override to `Amplify Dark`, one
312
+ of them also overridden to `Tablet 798pt`.
313
+
314
+ That last one is in this list as a defect of MINE, not of the file. On
315
+ 2026-10-12 the manifest and a commit message both called those four nodes
316
+ duplicate component sets. They are one set and two instances of it. The error
317
+ came from comparing bound fill variables, which are identical across modes
318
+ because a variable is the same variable in every mode, and never reading
319
+ `explicitVariableModes`, which is the only thing that marks a mode showcase.
320
+ When two nodes look identical in Figma, read the mode override before
321
+ concluding anything.
322
+
323
+ ### Walking through one
324
+
325
+ Take Widget as the shape of it. Open [`components/widget/widget-spec.md`](components/widget/widget-spec.md)
326
+ for the anatomy, the token table, the size ladder and the grid; open the
327
+ Storybook page for the same thing running, with a Playground, a size ladder at
328
+ the exact Figma boxes, and two token listings that resolve against the live
329
+ document; open [`components/widget/widget.jsx`](components/widget/widget.jsx)
330
+ for the implementation, where every measurement carries the Figma node it came
331
+ from. The Code Connect file next to it is what makes the Figma component offer
332
+ this code, and it publishes repo to Figma only.
333
+
334
+ Widget, KPI Tile and Dashboard are three components, not three views of one.
335
+ A Widget is a container that does not know what it holds. A KPI Tile is a card
336
+ in its own right. Both nest the same building block, `KpiNumberAndTrend`, which
337
+ has no card of its own and gets one from whatever nests it.
338
+
173
339
  ## Component-to-token map
174
340
 
175
341
  `tokens/component-token-map.json` maps every component element to its CSS variable. Use this file to answer "which token does X use?" without reading specs.
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Scrollable — Pathway Design System
2
+ * Scrollable, Pathway Design System
3
3
  *
4
4
  * A reusable overlay-scrollbar wrapper. Hides the native OS scrollbar entirely and renders
5
5
  * one custom liquid-glass thumb so scrolling looks and behaves IDENTICALLY on macOS, Windows,
6
- * iOS, and Android (native scrollbars can't be made consistent — this sidesteps them).
6
+ * iOS, and Android (native scrollbars can't be made consistent: this sidesteps them).
7
7
  *
8
8
  * Behaviour (see components/scrollbar/scrollbar-spec.md):
9
9
  * - Thumb height is PROPORTIONAL to content: (viewport / total) × track, floored at 28px.
10
10
  * - Reveal: appears while scrolling, fades ~900ms after; also appears when the MOUSE is near
11
11
  * the right-edge bar (so it can be grabbed). It does NOT appear from hovering the panel.
12
12
  * - Drag: MOUSE only, via a wider invisible grab strip (the 6px thumb alone is hard to hit).
13
- * - Touch: passive — the bar just shows position; you scroll by swiping content. The grab
13
+ * - Touch: passive: the bar just shows position; you scroll by swiping content. The grab
14
14
  * strip is disabled on touch devices via a `(hover: none)` rule.
15
15
  * - Keyboard / a11y: the content scrolls natively (arrows, PageUp/Down, Home/End, Space); the
16
16
  * thumb is decorative (`aria-hidden`) and never the only scroll mechanism.
@@ -20,16 +20,17 @@
20
20
  */
21
21
 
22
22
  import React, { useRef, useState, useEffect, useCallback } from "react";
23
+ import { useReducedMotion } from "../_shared/use-reduced-motion.jsx";
23
24
 
24
25
  // ─── TOKENS ──────────────────────────────────────────────────────────────────
25
- // Every colour + unit resolves through a SEMANTIC Pathway token — no primitives,
26
+ // Every colour + unit resolves through a SEMANTIC Pathway token: no primitives,
26
27
  // no hardcoded hex/px. Motion uses the --motion-* tokens (durations/easings from design-system-spec §2).
27
28
  export const SCROLL = {
28
- thumbWidth: "var(--semantic-layout-units-padding-xtight)", // 6px — visible thumb thickness
29
+ thumbWidth: "var(--semantic-layout-units-padding-xtight)", // 6px: visible thumb thickness
29
30
  thumbRadius: "var(--semantic-layout-units-cornerradius-full)", // fully-rounded pill
30
- thumbMin: 28, // px — min thumb length (grab-target floor); JS layout math
31
- gutter: "var(--semantic-layout-units-padding-xxxtight)", // 2px — inset from the right edge
32
- grabZone: 16, // px — invisible mouse grab strip (wider than the 6px thumb so it's catchable)
31
+ thumbMin: 28, // px, min thumb length (grab-target floor); JS layout math
32
+ gutter: "var(--semantic-layout-units-padding-xxxtight)", // 2px, inset from the right edge
33
+ grabZone: 16, // px, invisible mouse grab strip (wider than the 6px thumb so it's catchable)
33
34
  // THE LIGHT RUNGS ARE A DELIBERATE CHOICE BY THE DESIGN OWNER. Do not raise
34
35
  // them again without asking Jo first.
35
36
  //
@@ -46,7 +47,7 @@ export const SCROLL = {
46
47
  // Scrim/Faint and Scrim/Light resolve to Cool Neutral 800, so the thumb was
47
48
  // drawing a COOL grey over warm surfaces and reading as a cold bar against
48
49
  // them. Reported 2026-10-02. The comment below has always said "light warm
49
- // overlay" — the value simply did not match it.
50
+ // overlay": the value simply did not match it.
50
51
  //
51
52
  // Scrim is also the wrong family: it is the modal backdrop, where a cool
52
53
  // near-black is right. The thumb is an overlay on a surface, and under the
@@ -61,7 +62,7 @@ export const SCROLL = {
61
62
  thumbEdge: "inset 0 0 0 0.5px color-mix(in srgb, var(--semantic-color-fill-static-neutral-faint) 35%, transparent)",
62
63
  // Fade OUT gracefully, but appear INSTANTLY. Animating opacity on the way in
63
64
  // multiplied the thumb's own alpha by the in-flight opacity, so during the
64
- // 200ms glide-in it rendered as low as 1.18:1 — below the floor at the exact
65
+ // 200ms glide-in it rendered as low as 1.18:1, below the floor at the exact
65
66
  // moment the user is looking for it. Only the fade-out is animated now.
66
67
  // Asymmetric fade: snappy decelerate glide-IN, graceful fade-OUT. Each entry
67
68
  // names every property it is responsible for, because the element swaps
@@ -94,12 +95,13 @@ if (typeof document !== "undefined" && !document.getElementById("pds-scrollable-
94
95
  "--pw-scrollbar-thumb-rest:var(--primitive-color-warm-neutral-100-16);" +
95
96
  "--pw-scrollbar-thumb-hover:var(--primitive-color-warm-neutral-100-24)}" +
96
97
  ".pds-scrollable__view{scrollbar-width:none;-ms-overflow-style:none}" +
97
- ".pds-scrollable__view::-webkit-scrollbar{width:0;height:0;display:none}" +
98
+ ".pds-scrollable__view:-webkit-scrollbar{width:0;height:0;display:none}" +
98
99
  "@media (hover: none){.pds-scrollable__grab{pointer-events:none!important}}";
99
100
  document.head.appendChild(s);
100
101
  }
101
102
 
102
103
  export function Scrollable({ children, className = "", style = {}, viewClassName = "", viewStyle = {} }) {
104
+ const noMotion = useReducedMotion();
103
105
  const viewRef = useRef(null);
104
106
  const dragRef = useRef(null);
105
107
  const idleRef = useRef(null);
@@ -140,7 +142,7 @@ export function Scrollable({ children, className = "", style = {}, viewClassName
140
142
  const onScroll = () => { recompute(); wake(); };
141
143
 
142
144
  // Edge-proximity reveal (mouse): show the bar when the cursor is within grabZone of the right
143
- // edge, so it can be grabbed — but NOT from hovering the rest of the panel.
145
+ // edge, so it can be grabbed: but NOT from hovering the rest of the panel.
144
146
  const onMouseMove = (e) => {
145
147
  const rect = e.currentTarget.getBoundingClientRect();
146
148
  if (rect.right - e.clientX <= SCROLL.grabZone) {
@@ -194,7 +196,7 @@ export function Scrollable({ children, className = "", style = {}, viewClassName
194
196
 
195
197
  {thumb.show && (
196
198
  <>
197
- {/* Visual thumb — decorative, never intercepts pointer events. */}
199
+ {/* Visual thumb, decorative, never intercepts pointer events. */}
198
200
  <div
199
201
  aria-hidden="true"
200
202
  style={{
@@ -203,11 +205,11 @@ export function Scrollable({ children, className = "", style = {}, viewClassName
203
205
  background: hot ? SCROLL.thumbHover : SCROLL.thumbRest,
204
206
  backdropFilter: SCROLL.thumbBlur, WebkitBackdropFilter: SCROLL.thumbBlur,
205
207
  boxShadow: SCROLL.thumbEdge,
206
- opacity: active ? 1 : 0, transition: active ? SCROLL.fadeIn : SCROLL.fadeOut,
208
+ opacity: active ? 1 : 0, transition: noMotion ? "none" : (active ? SCROLL.fadeIn : SCROLL.fadeOut),
207
209
  pointerEvents: "none", zIndex: 5,
208
210
  }}
209
211
  />
210
- {/* Invisible mouse grab strip — wider than the thumb so it's catchable; disabled on
212
+ {/* Invisible mouse grab strip, wider than the thumb so it's catchable; disabled on
211
213
  touch via the (hover:none) rule so finger-swipes scroll the content beneath. */}
212
214
  <div
213
215
  aria-hidden="true"
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SideNav — Pathway Design System
2
+ * SideNav, Pathway Design System
3
3
  *
4
4
  * Importable React component module. Source of truth for the SideNav
5
5
  * implementation; the standalone demo (sidenav.html) and the Storybook
@@ -13,7 +13,9 @@
13
13
  * drift, and wait. Do not resolve it by fetching Figma. See CLAUDE.md 1.1.
14
14
  *
15
15
  * Spec: components/sidenav/sidenav-spec.md
16
- * Figma: https://www.figma.com/design/3sw45aVcngFAmpbP6cfrXP/?node-id=40003951-2927
16
+ * Figma: https://www.figma.com/design/3sw45aVcngFAmpbP6cfrXP/?node-id=40004059-1375
17
+ * SideNav.Container, a COMPONENT_SET. The id cited here until
18
+ * 2026-10-07, 40003951-2927, does not exist in the file.
17
19
  */
18
20
 
19
21
  import React, { useState, useRef } from "react";
@@ -32,7 +34,7 @@ import ReactDOM from "react-dom";
32
34
  * one theme into the bundle, so Midnight Mode could not work at all in the coded
33
35
  * component no matter what the consumer did. `t()` also returns the token id
34
36
  * unchanged when it cannot resolve a name, which emits invalid CSS that the
35
- * browser silently drops — so a renamed token failed as a colour that quietly
37
+ * browser silently drops: so a renamed token failed as a colour that quietly
36
38
  * reverted to inherited, with no error anywhere.
37
39
  *
38
40
  * Referencing the custom property instead means the cascade resolves the value,
@@ -136,7 +138,7 @@ export const L = {
136
138
  menuPadT: u("padding-tight"),
137
139
  menuPadB: u("padding-xxwide"),
138
140
  itemH: u("accessibility-touch-target-aa-height"),
139
- iconWrap: 24, // no token in Figma — leading icon wrapper
141
+ iconWrap: 24, // no token in Figma, leading icon wrapper
140
142
  iconInner: 16, // Material Symbols Rounded frame size, per design-system-spec §7.2
141
143
  rowPadH: u("padding-tight"),
142
144
  // Collapsed rail: container.rowStart's padding-right in Figma. It is what
@@ -144,8 +146,8 @@ export const L = {
144
146
  // the design.
145
147
  railIconInset: u("padding-xxtight"),
146
148
  textPad: u("padding-xtight"),
147
- childIndent: 24, // no token in Figma — level-1 left indent
148
- stripeW: 4, // no token in Figma — indicator stripe width
149
+ childIndent: 24, // no token in Figma, level-1 left indent
150
+ stripeW: 4, // no token in Figma, indicator stripe width
149
151
  colPadL: u("padding-medium"),
150
152
  colPadR: u("padding-tight"),
151
153
  collapseGap: u("padding-xxtight"),
@@ -251,7 +253,7 @@ export function SideNavItem({
251
253
 
252
254
  <IndicatorStripe visible={showStripe} />
253
255
 
254
- {/* Leading icon — Level 0 only */}
256
+ {/* Leading icon, Level 0 only */}
255
257
  {!isChild && item.icon && (
256
258
  <div style={{ width: L.iconWrap, height: L.iconWrap, display: "flex",
257
259
  alignItems: "center", justifyContent: "center", flexShrink: 0,
@@ -270,7 +272,7 @@ export function SideNavItem({
270
272
  ? <span className="material-symbols-rounded" aria-hidden="true"
271
273
  style={{ fontSize: L.iconInner, lineHeight: 1, color: iconColor,
272
274
  // display:block so the glyph is laid out as a box rather than
273
- // on a text baseline — an inline glyph in a flex-centred
275
+ // on a text baseline: an inline glyph in a flex-centred
274
276
  // parent is centred by its line box, not by its own art, so
275
277
  // it rode high inside the 24px wrap.
276
278
  display: "block",
@@ -311,15 +313,22 @@ export function SideNavItem({
311
313
  </p>
312
314
  </div>
313
315
 
314
- {/* Chevron — groupers only */}
315
- {!isChild && hasChildren && (
316
+ {/* Chevron, groupers only, and EXPANDED ONLY.
317
+ Figma's SideNavItem.Collapsed is indicator + Container.LeadingIcon
318
+ and nothing else: there is no chevron in the rail. Giving it
319
+ width 0 and max-width 0 was not enough, because the glyph still
320
+ painted outside its collapsed box, so the rail showed a second
321
+ column of 10px chevrons 17px to the right of the icons. Measured
322
+ 2026-10-02: two distinct glyph centres, 28.8 and 45.8, where there
323
+ should be one. It is now not rendered at all when collapsed. */}
324
+ {!isChild && hasChildren && !isSidebarCollapsed && (
316
325
  <div style={{ width: isSidebarCollapsed ? 0 : 40, height: L.iconWrap, display: "flex",
317
326
  alignItems: "center", justifyContent: "center", flexShrink: 0,
318
327
  maxWidth: isSidebarCollapsed ? 0 : 40,
319
328
  opacity: isSidebarCollapsed ? 0 : 1,
320
329
  overflow: "hidden",
321
330
  transition: "max-width var(--motion-duration-6) var(--motion-easing-emphasized), opacity var(--motion-duration-3) var(--motion-easing-standard)" }}>
322
- {/* Chevron rotation matches accordion timing — --motion-duration-5 + --motion-easing-accordion */}
331
+ {/* Chevron rotation matches accordion timing, --motion-duration-5 + --motion-easing-accordion */}
323
332
  <div style={{ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
324
333
  transition: "transform var(--motion-duration-5) var(--motion-easing-accordion)" }}>
325
334
  {chevronDown ? chevronDown({ size: 10, color: iconColor }) : <ChevDown size={10} color={iconColor} />}
@@ -385,7 +394,7 @@ export function SideNavTooltip({ label, anchorRect, onMouseEnter, onMouseLeave }
385
394
  }
386
395
 
387
396
  // ─── SectionLabel ─────────────────────────────────────────────────────────────
388
- // Figma node 40006794-5977 — building block used in CollapsedPopover headers.
397
+ // Figma node 40006794-5977, building block used in CollapsedPopover headers.
389
398
  // Tokens: Foreground/Static/Neutral/Base (#313131), Label/Section/Small/Semibold
390
399
  // (11px / 600 / 16px / 0.6px letter-spacing, uppercase)
391
400
  // Padding: Padding/Tight (12px left), Padding/XTight (8px vertical)
@@ -414,7 +423,7 @@ export function CollapsedPopover({ item, onClick, anchorRect, onMouseEnter, onMo
414
423
  boxShadow: c2("--elevation-widget"),
415
424
  padding: "var(--semantic-layout-units-padding-xxtight)", minWidth: 200, pointerEvents: "auto",
416
425
  animation: "popoverIn var(--motion-duration-3) var(--motion-easing-spring) forwards" }}>
417
- {/* Section label — Figma component 40006794-5977 */}
426
+ {/* Section label, Figma component 40006794-5977 */}
418
427
  <div style={{ borderBottom: `0.5px solid ${c("stroke-static-neutral-faint")}` }}>
419
428
  <SectionLabel label={item.label} />
420
429
  </div>
@@ -453,11 +462,11 @@ export function PopoverRow({ item, onClick, activeId }) {
453
462
  // ─── CollapseButton ───────────────────────────────────────────────────────────
454
463
  // Redesigned in Figma (2026-05-12): now uses Slot.RowStart (Module.Mark icon)
455
464
  // + Slot.RowEnd (Action Icon). The action icon uses Foreground/Action/Secondary/Rest
456
- // (#6b6b6b) — different from nav item icons. SVG assets fetched from Figma directly.
465
+ // (#6b6b6b), different from nav item icons. SVG assets fetched from Figma directly.
457
466
  export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, expandIcon }) {
458
467
  const [h, setH] = useState(false);
459
468
  const labelColor = h ? T.text.navHover : T.text.navBase;
460
- const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover) — always static, no hover change
469
+ const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover), always static, no hover change
461
470
  return (
462
471
  <div style={{ width: "100%" }}>
463
472
  <div style={{ height: 1, backgroundColor: T.fill.infoSubtle, marginBottom: L.collapseGap }} />
@@ -469,10 +478,10 @@ export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, exp
469
478
  borderRadius: T.radius, backgroundColor: h ? T.fill.navHover : T.fill.navBase,
470
479
  cursor: "pointer", transition: "background-color var(--motion-duration-3) var(--motion-easing-standard)", overflow: "hidden" }}>
471
480
 
472
- {/* Slot.RowStart — 4px stripe column (structural, always present) */}
481
+ {/* Slot.RowStart, 4px stripe column (structural, always present) */}
473
482
  <div style={{ width: L.stripeW, alignSelf: "stretch", flexShrink: 0 }} />
474
483
 
475
- {/* Container.Main — label (hidden when collapsed) */}
484
+ {/* Container.Main, label (hidden when collapsed) */}
476
485
  <div style={{ flex: 1, paddingLeft: L.rowPadH,
477
486
  maxWidth: isSidebarCollapsed ? 0 : 200,
478
487
  opacity: isSidebarCollapsed ? 0 : 1,
@@ -486,7 +495,7 @@ export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, exp
486
495
  </span>
487
496
  </div>
488
497
 
489
- {/* Slot.RowEnd — Action Icon (right_panel_open / left_panel_open from Figma) */}
498
+ {/* Slot.RowEnd, Action Icon (right_panel_open / left_panel_open from Figma) */}
490
499
  <div style={{ width: 40, height: L.iconWrap, display: "flex",
491
500
  alignItems: "center", justifyContent: "center", flexShrink: 0,
492
501
  marginLeft: isSidebarCollapsed ? "auto" : 0,
@@ -509,15 +518,15 @@ export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, exp
509
518
  // Optional in-nav section heading. Figma: SideNav.SectionLabel (node 40006794:5975).
510
519
  // Tokens: Label/Section/Small/Semibold (11px/600/16px/0.6px), Foreground/Static/Neutral/Base (#606060)
511
520
  // Container: h-[40px], pl-[4px], pr-[4px], py-[8px]
512
- // In collapsed rail: hidden — replaced by a Divider (rendered by SideNav itself, see §2.3)
521
+ // In collapsed rail: hidden, replaced by a Divider (rendered by SideNav itself, see §2.3)
513
522
  // ── NavHeader ────────────────────────────────────────────────────────────────
514
523
  // Sits at the TOP of the SideNav. 48px row with the expand/collapse action icon
515
524
  // in Slot.RowEnd (expanded) or centered (collapsed). Divider below.
516
525
  // Matches Figma "Slot.NavHeader" (node 40007331:7794).
517
- // Hidden on mobile (<768px) — TopNav hamburger is the sole toggle there.
526
+ // Hidden on mobile (<768px), TopNav hamburger is the sole toggle there.
518
527
  export function NavHeader({ isSidebarCollapsed, onToggle, collapseIcon, expandIcon }) {
519
528
  const [h, setH] = useState(false);
520
- const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover) — always static
529
+ const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover), always static
521
530
  return (
522
531
  <div style={{ width: "100%", flexShrink: 0 }}>
523
532
  <div onClick={onToggle} onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
@@ -547,7 +556,7 @@ export function NavHeader({ isSidebarCollapsed, onToggle, collapseIcon, expandIc
547
556
  </div>
548
557
  )}
549
558
  </div>
550
- {/* Divider — Stroke/Static/Neutral/Base, py-[2px] in Figma */}
559
+ {/* Divider, Stroke/Static/Neutral/Base, py-[2px] in Figma */}
551
560
  <div style={{ padding: "2px 0" }}>
552
561
  <div style={{ height: 1, backgroundColor: T.fill.infoSubtle }} />
553
562
  </div>
@@ -571,7 +580,7 @@ export function NavSectionLabel({ label }) {
571
580
  }
572
581
 
573
582
  // ── BulletDot ────────────────────────────────────────────────────────────────
574
- // 6px filled circle — leading icon for SideNavListSection's ListItem.
583
+ // 6px filled circle, leading icon for SideNavListSection's ListItem.
575
584
  // Color cycles Base / Hover / Active just like SideNavItem icons.
576
585
  export function BulletDot({ color }) {
577
586
  return (
@@ -618,7 +627,7 @@ export function ListItem({ item, isActive, onClick }) {
618
627
 
619
628
  // ── SideNavListSection ───────────────────────────────────────────────────────
620
629
  // Figma: SideNav.ListSection (node 40007332:8034). A labelled grouping of flat
621
- // nav items — no icons, no children, no expand/collapse. Used for contextual
630
+ // nav items: no icons, no children, no expand/collapse. Used for contextual
622
631
  // link lists (Recent Content, Pinned, Bookmarks). Only shown in expanded sidebar.
623
632
  export function SideNavListSection({ label, items, activeId, onNavigate }) {
624
633
  return (
@@ -727,7 +736,7 @@ export function SideNav({
727
736
  anchorRect={popoverId === item.id ? popoverRect : null}
728
737
  onPopoverEnter={onPopoverEnter} onPopoverLeave={onPopoverLeave}
729
738
  activeId={activeId} />
730
- {/* Level 1 children — animated accordion. grid-template-rows 0fr→1fr at
739
+ {/* Level 1 children, animated accordion. grid-template-rows 0fr→1fr at
731
740
  --motion-duration-5 + --motion-easing-accordion, with inner wrapper opacity
732
741
  fade --motion-duration-4 (70ms delay on expand, no delay on collapse). */}
733
742
  {hasChildren && !collapsed && (
@@ -767,12 +776,12 @@ export function SideNav({
767
776
  paddingLeft: collapsed ? L.navColPadH : L.navPadH,
768
777
  paddingRight: collapsed ? L.navColPadH : L.navPadH,
769
778
  boxSizing: "border-box", flexShrink: 0,
770
- // Smooth-spring curve — soft personality, no overshoot.
779
+ // Smooth-spring curve, soft personality, no overshoot.
771
780
  transition: "width var(--motion-duration-6) var(--motion-easing-emphasized)",
772
781
  borderRight: `0.5px solid ${T.fill.infoSubtle}`,
773
782
  overflow: "hidden", // the inner menu scrolls; the nav itself does not
774
783
  }}>
775
- {/* NavHeader — TOP of nav, PINNED (flexShrink:0) so it stays visible while the
784
+ {/* NavHeader, TOP of nav, PINNED (flexShrink:0) so it stays visible while the
776
785
  item list scrolls beneath it. Hidden on mobile (<768px). */}
777
786
  {(!hideCollapseButton || headerEnd) && (
778
787
  <div style={{ flexShrink: 0 }}>
@@ -802,7 +811,7 @@ export function SideNav({
802
811
  </div>
803
812
  )}
804
813
 
805
- {/* SideNavMenu — the ONLY scroll region, wrapped in <Scrollable> so the custom
814
+ {/* SideNavMenu: the ONLY scroll region, wrapped in <Scrollable> so the custom
806
815
  overlay scrollbar (identical on every OS, never affects layout/padding) handles
807
816
  overflow. flex:1 + minHeight:0 lets it shrink below content height; the pinned
808
817
  header above never moves. Applies in BOTH expanded (240px) and collapsed (72px) states. */}
@@ -810,7 +819,7 @@ export function SideNav({
810
819
  style={{ flex: 1, minHeight: 0,
811
820
  // Bleed the scroll region out to the nav's TRUE right edge so the overlay thumb
812
821
  // hugs the edge in BOTH states (the nav's border-box paddingRight would otherwise
813
- // inset the thumb by 16px expanded / 12px on the 72px rail — badly off on the rail).
822
+ // inset the thumb by 16px expanded / 12px on the 72px rail, badly off on the rail).
814
823
  // negated through calc() because these are var() references, and negating
815
824
  // a string would produce NaN
816
825
  marginRight: neg(collapsed ? L.navColPadH : L.navPadH) }}
@@ -818,11 +827,11 @@ export function SideNav({
818
827
  paddingTop: L.menuPadT,
819
828
  // Re-add the content's right inset INSIDE the scroll view so items keep their
820
829
  // position; only the thumb moves to the edge. (Spec rule: padding on viewStyle,
821
- // never on the wrapper — keeps the thumb flush on any scroll surface.)
830
+ // never on the wrapper, keeps the thumb flush on any scroll surface.)
822
831
  paddingRight: collapsed ? L.navColPadH : L.navPadH }}
823
832
  >
824
833
 
825
- {/* Sectioned render — when `sections` prop is provided */}
834
+ {/* Sectioned render: when `sections` prop is provided */}
826
835
  {sections && sections.map(({ section, items: secItems }, sIdx) => (
827
836
  <div key={section} style={{ display: "flex", flexDirection: "column",
828
837
  gap: L.menuGap, flexShrink: 0 }}>
@@ -846,10 +855,10 @@ export function SideNav({
846
855
  </div>
847
856
  ))}
848
857
 
849
- {/* Flat render — when only `items` is provided (backward compatibility) */}
858
+ {/* Flat render: when only `items` is provided (backward compatibility) */}
850
859
  {!sections && (items || []).map(renderItem)}
851
860
 
852
- {/* SideNavListSection — optional. Hidden in collapsed rail. */}
861
+ {/* SideNavListSection, optional. Hidden in collapsed rail. */}
853
862
  {listSection && (
854
863
  <div style={{ opacity: collapsed ? 0 : 1,
855
864
  maxHeight: collapsed ? 0 : 400, overflow: "hidden",
@@ -859,16 +868,16 @@ export function SideNav({
859
868
  </div>
860
869
  )}
861
870
 
862
- {/* Body slot — scrolls with the item list, so it sits INSIDE
871
+ {/* Body slot, scrolls with the item list, so it sits INSIDE
863
872
  Scrollable and above the spacer. Use this for content that belongs
864
873
  with the items; use `footer` for anything that must stay visible. */}
865
874
  {children && <div style={{ flexShrink: 0 }}>{children}</div>}
866
875
 
867
- {/* Bottom spacer — fills remaining height */}
876
+ {/* Bottom spacer, fills remaining height */}
868
877
  <div style={{ flex: 1, minHeight: L.menuPadB }} />
869
878
  </Scrollable>
870
879
 
871
- {/* Footer slot — OUTSIDE Scrollable and flexShrink:0, so it is pinned to
880
+ {/* Footer slot, OUTSIDE Scrollable and flexShrink:0, so it is pinned to
872
881
  the bottom of the nav and stays put while the list scrolls. The
873
882
  divider only appears when the slot has content, so a nav without a
874
883
  footer is unchanged. Padding drops to the rail's narrower inset when
@@ -906,7 +915,7 @@ function ChevUp({ size = 10, color }) {
906
915
 
907
916
  // ─── Collapse / Expand action icons (fetched from Figma 2026-05-12) ──────────
908
917
  //
909
- // PINNED 2026-09-07 — DO NOT RE-SYNC THESE TWO FROM FIGMA.
918
+ // PINNED 2026-09-07: DO NOT RE-SYNC THESE TWO FROM FIGMA.
910
919
  //
911
920
  // The Figma SideBar Expand/Collapse component was changed after this date to a
912
921
  // different icon. Jo has explicitly rejected that change for the repo, for
@@ -919,7 +928,7 @@ function ChevUp({ size = 10, color }) {
919
928
  // by pulling Figma, and do not silently re-fetch node 40006793:3783. Ask first
920
929
  // if you think it should change.
921
930
  // ─────────────────────────────────────────────────────────────────────────────
922
- // right_panel_open — used on the collapse button when sidebar is EXPANDED (click to collapse)
931
+ // right_panel_open, used on the collapse button when sidebar is EXPANDED (click to collapse)
923
932
  // Token: Foreground/Action/Secondary/Rest (#6b6b6b)
924
933
  // Figma component: SideBar Expand/Collapse, Type=Collapse, node 40006793:3783
925
934
  function RightPanelOpenIcon({ size = 12, color = T.icon.actionSecondary }) {
@@ -930,7 +939,7 @@ function RightPanelOpenIcon({ size = 12, color = T.icon.actionSecondary }) {
930
939
  );
931
940
  }
932
941
 
933
- // left_panel_open — used on the collapse button when sidebar is COLLAPSED (click to expand)
942
+ // left_panel_open, used on the collapse button when sidebar is COLLAPSED (click to expand)
934
943
  // Token: Foreground/Action/Secondary/Rest (#6b6b6b)
935
944
  // Figma component: SideBar Expand/Collapse, Type=Expand, node 40006793:3783
936
945
  function LeftPanelOpenIcon({ size = 12, color = T.icon.actionSecondary }) {
@@ -1,7 +1,7 @@
1
1
  /*
2
- NewCo brand layer — single entry point. Load AFTER the Pathway index and set data-brand="newco".
2
+ NewCo brand layer, single entry point. Load AFTER the Pathway index and set data-brand="newco".
3
3
  @imports every token file in load order (primitives first, so the themes that
4
- reference it resolve). Generated — do not edit. Run `npm run build-dist`.
4
+ reference it resolve). Generated: do not edit. Run `npm run build-dist`.
5
5
  */
6
6
  @import "./primitives.css";
7
7
  @import "./themes/light.css";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * contextual-layout-units — one name per token, breakpoint resolved by media query.
2
+ * contextual-layout-units: one name per token, breakpoint resolved by media query.
3
3
  * Generated by style-dictionary.config.js. Do not edit.
4
4
  *
5
5
  * [data-brand="newco"] is the desktop value. Narrower breakpoints override only what differs.
@@ -39,4 +39,7 @@
39
39
  --contextual-layout-units-sidenav-width-collapsed: 84px;
40
40
  --contextual-layout-units-sidenav-width-expanded: 256px;
41
41
  --contextual-layout-units-topnav-height: 60px;
42
+ --contextual-layout-units-widget-minwidth-detail: 0px;
43
+ --contextual-layout-units-widget-minwidth-explore: 0px;
44
+ --contextual-layout-units-widget-minwidth-glance: 0px;
42
45
  }
@@ -373,6 +373,39 @@ const tokens = {
373
373
  "height"
374
374
  ]
375
375
  },
376
+ "contextual-layout-units-widget-minwidth-detail": {
377
+ "value": 0,
378
+ "type": "number",
379
+ "path": [
380
+ "contextual-layout-units",
381
+ "newco",
382
+ "widget",
383
+ "minwidth",
384
+ "detail"
385
+ ]
386
+ },
387
+ "contextual-layout-units-widget-minwidth-explore": {
388
+ "value": 0,
389
+ "type": "number",
390
+ "path": [
391
+ "contextual-layout-units",
392
+ "newco",
393
+ "widget",
394
+ "minwidth",
395
+ "explore"
396
+ ]
397
+ },
398
+ "contextual-layout-units-widget-minwidth-glance": {
399
+ "value": 0,
400
+ "type": "number",
401
+ "path": [
402
+ "contextual-layout-units",
403
+ "newco",
404
+ "widget",
405
+ "minwidth",
406
+ "glance"
407
+ ]
408
+ },
376
409
  "primitive-color-amethyst-0": {
377
410
  "value": "#faf9fc",
378
411
  "type": "color",
@@ -4214,7 +4247,7 @@ const tokens = {
4214
4247
  ]
4215
4248
  },
4216
4249
  "primitive-color-seabreeze-300-70": {
4217
- "value": "#8598d7b2",
4250
+ "value": "#8598d7b3",
4218
4251
  "type": "color",
4219
4252
  "path": [
4220
4253
  "primitive-color",
@@ -1,12 +1,12 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — CONTRACT
3
+ PATHWAY DESIGN TOKENS, CONTRACT
4
4
  ==========================================================================
5
5
 
6
6
  The breakpoint values. Name these freely.
7
7
 
8
8
  Declares 5 custom properties.
9
- Generated — do not edit. Run `npm run build-tokens`.
9
+ Generated: do not edit. Run `npm run build-tokens`.
10
10
  ==========================================================================
11
11
  */
12
12
  :root {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$comment": "The names a consumer may use. Generated from the emitted CSS by scripts/build-contract.js, do not hand-edit. Lint against `contract` if you want the boundary enforced; nothing here blocks a primitive.",
3
- "version": "11.1.0",
4
- "generated": "2026-10-02",
3
+ "version": "11.2.0",
4
+ "generated": "2026-10-13",
5
5
  "counts": {
6
6
  "contract": 360,
7
7
  "infrastructure": 585,
package/dist/index.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
- Pathway design tokens — single entry point.
2
+ Pathway design tokens, single entry point.
3
3
  @imports every token file in load order (primitives first, so the themes that
4
- reference it resolve). Generated — do not edit. Run `npm run build-dist`.
4
+ reference it resolve). Generated: do not edit. Run `npm run build-dist`.
5
5
  */
6
6
  @import "./primitives.css";
7
7
  @import "./themes/light.css";
@@ -1,16 +1,16 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — COMPONENT INTERNALS
3
+ PATHWAY DESIGN TOKENS, COMPONENT INTERNALS
4
4
  ==========================================================================
5
5
 
6
6
  Per-component metrics (Button, Card, NavItem, Page, focus ring).
7
7
 
8
- This repo's own components use these. Product code generally should not —
8
+ This repo's own components use these. Product code generally should not
9
9
  prefer layout.css, which is the general spacing contract. These exist so a
10
10
  component's geometry can be tuned without moving the whole scale.
11
11
 
12
12
  Declares 37 custom properties.
13
- Generated — do not edit. Run `npm run build-tokens`.
13
+ Generated: do not edit. Run `npm run build-tokens`.
14
14
  ==========================================================================
15
15
  */
16
16
  :root {
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — CONTRACT
3
+ PATHWAY DESIGN TOKENS, CONTRACT
4
4
  ==========================================================================
5
5
 
6
6
  The only layout tokens that change by breakpoint. Name these freely.
@@ -15,7 +15,7 @@
15
15
  columns; it says nothing about the chrome's padding.
16
16
 
17
17
  Declares 23 custom properties.
18
- Generated — do not edit. Run `npm run build-tokens`.
18
+ Generated: do not edit. Run `npm run build-tokens`.
19
19
  ==========================================================================
20
20
  */
21
21
  :root {
package/dist/layout.css CHANGED
@@ -1,11 +1,11 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — CONTRACT
3
+ PATHWAY DESIGN TOKENS, CONTRACT
4
4
  ==========================================================================
5
5
 
6
6
  Spacing, radii and border widths. Name these freely.
7
7
 
8
- Values already carry their unit, so use them directly — do NOT wrap in
8
+ Values already carry their unit, so use them directly: do NOT wrap in
9
9
  calc(... * 1px). That produces calc(4px * 1px), which is not a length, so
10
10
  the browser silently drops the declaration.
11
11
 
@@ -13,7 +13,7 @@
13
13
  putting the breakpoint in the property name, so one name works responsively.
14
14
 
15
15
  Declares 76 custom properties.
16
- Generated — do not edit. Run `npm run build-tokens`.
16
+ Generated: do not edit. Run `npm run build-tokens`.
17
17
  ==========================================================================
18
18
  */
19
19
  :root {
package/dist/motion.css CHANGED
@@ -1,11 +1,11 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — CONTRACT
3
+ PATHWAY DESIGN TOKENS, CONTRACT
4
4
  ==========================================================================
5
5
 
6
6
  Durations and easings. Name these freely.
7
7
 
8
- These are NOT in the Figma Variables panel — the panel has zero motion
8
+ These are NOT in the Figma Variables panel: the panel has zero motion
9
9
  variables. Their source of truth is docs/design-system-spec.md §2, and
10
10
  scripts/sync-motion-tokens.js generates this file from it.
11
11
 
@@ -13,12 +13,12 @@
13
13
  alone gets no motion at all, and no warning. Consume the package.
14
14
 
15
15
  Declares 21 custom properties.
16
- Generated — do not edit. Run `npm run build-tokens`.
16
+ Generated: do not edit. Run `npm run build-tokens`.
17
17
  ==========================================================================
18
18
  */
19
19
  :root {
20
20
  --motion-duration-1: 100ms; /** Instant tap feedback, tiny opacity flickers */
21
- --motion-duration-2: 150ms; /** Hover fills, small opacity fades, colour transitions — the most common micro-interaction timing */
21
+ --motion-duration-2: 150ms; /** Hover fills, small opacity fades, colour transitions: the most common micro-interaction timing */
22
22
  --motion-duration-3: 200ms; /** Micro-interactions needing slightly more presence; small reveals, focus rings */
23
23
  --motion-duration-4: 300ms; /** Chevron / disclosure rotation, dropdown & popover content, in-component state */
24
24
  --motion-duration-5: 380ms; /** Panel state changes: accordions, expand/collapse, item enter/exit */
@@ -26,16 +26,16 @@
26
26
  --motion-duration-7: 680ms; /** Cross-component transitions: page content sliding, large layout shifts */
27
27
  --motion-duration-8: 1100ms; /** Slowest transition step: very large layout shifts, deliberate full-screen moves */
28
28
  --motion-duration-loop: 1000ms; /** Standard continuous-loop period (standalone Spinner, skeleton shimmer) */
29
- --motion-duration-loop-fast: 750ms; /** Tighter loop for embedded/inline contexts (in-button spinner — faster reads as more responsive feedback) */
29
+ --motion-duration-loop-fast: 750ms; /** Tighter loop for embedded/inline contexts (in-button spinner, faster reads as more responsive feedback) */
30
30
  --motion-easing-breath: cubic-bezier(0.37, 0, 0.63, 1);
31
31
  --motion-easing-burst: cubic-bezier(0.30, 0, 0.5, 1.18);
32
- --motion-easing-decelerate: cubic-bezier(0, 0, 0.2, 1); /** Enters where the element should glide in smoothly — overlay panels, popovers, anything appearing from outside. Pure ease-out, no bounce. */
32
+ --motion-easing-decelerate: cubic-bezier(0, 0, 0.2, 1); /** Enters where the element should glide in smoothly: overlay panels, popovers, anything appearing from outside. Pure ease-out, no bounce. */
33
33
  --motion-easing-emphasized: cubic-bezier(0.32, 0.72, 0, 1); /** Large physical-scale transitions: full-panel width changes, SideNav width. Smooth glide, no overshoot. */
34
34
  --motion-easing-inout: cubic-bezier(0.62, 0.02, 0.23, 1);
35
- --motion-easing-spring: cubic-bezier(0.34, 1.04, 0.64, 1); /** Signature Pathway curve. Interactive element entrances: dropdown/popover panel opens. A whisper of overshoot (y2=1.04) — easeful, only *ever so slightly* alive. Softened 2026-06-08 from 1.08, which read as too bouncy. Not for chevrons — see chevron rule below. */
35
+ --motion-easing-spring: cubic-bezier(0.34, 1.04, 0.64, 1); /** Signature Pathway curve. Interactive element entrances: dropdown/popover panel opens. A whisper of overshoot (y2=1.04), easeful, only *ever so slightly* alive. Softened 2026-06-08 from 1.08, which read as too bouncy. Not for chevrons, see chevron rule below. */
36
36
  --motion-easing-spring-soft: cubic-bezier(0.30, 1.05, 0.40, 1);
37
- --motion-easing-standard: cubic-bezier(0.4, 0, 0.2, 1); /** Most transitions — enters AND exits. Smooth, no overshoot. Default for colour, fill, opacity, focus rings. */
38
- --motion-easing-accelerate: cubic-bezier(0.4, 0, 1, 1); /** Exits where the element leaves cleanly — dismissing overlays, scaling down. */
39
- --motion-easing-accordion: cubic-bezier(0.33, 1.03, 0.68, 1); /** Accordion settle — content reveal/hide, chevron rotation. The gentlest overshoot in the system (y2=1.03). Softened 2026-06-08 from 0.22,1.12,0.36,1, which was visibly bouncy. */
37
+ --motion-easing-standard: cubic-bezier(0.4, 0, 0.2, 1); /** Most transitions, enters AND exits. Smooth, no overshoot. Default for colour, fill, opacity, focus rings. */
38
+ --motion-easing-accelerate: cubic-bezier(0.4, 0, 1, 1); /** Exits where the element leaves cleanly, dismissing overlays, scaling down. */
39
+ --motion-easing-accordion: cubic-bezier(0.33, 1.03, 0.68, 1); /** Accordion settle, content reveal/hide, chevron rotation. The gentlest overshoot in the system (y2=1.03). Softened 2026-06-08 from 0.22,1.12,0.36,1, which was visibly bouncy. */
40
40
  --motion-easing-linear: cubic-bezier(0, 0, 1, 1); /** Continuous loops only (spinners, shimmer). Never for bounded transitions. */
41
41
  }
@@ -1,11 +1,11 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — INFRASTRUCTURE
3
+ PATHWAY DESIGN TOKENS, INFRASTRUCTURE
4
4
  ==========================================================================
5
5
 
6
6
  The raw colour ramps. NOT part of the token contract.
7
7
 
8
- You must LOAD this file — every semantic token resolves through it, so
8
+ You must LOAD this file: every semantic token resolves through it, so
9
9
  without it all colour resolves to nothing and the page renders unstyled
10
10
  with no console error. But product code should not NAME these.
11
11
 
@@ -19,7 +19,7 @@
19
19
  Nothing blocks you. It is an escape hatch with a stated cost.
20
20
 
21
21
  Declares 548 custom properties.
22
- Generated — do not edit. Run `npm run build-tokens`.
22
+ Generated: do not edit. Run `npm run build-tokens`.
23
23
  ==========================================================================
24
24
  */
25
25
  :root {
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — CONTRACT
3
+ PATHWAY DESIGN TOKENS, CONTRACT
4
4
  ==========================================================================
5
5
 
6
6
  The colour contract, Light Mode. Name these freely.
@@ -9,11 +9,11 @@
9
9
 
10
10
  themes/midnight.css declares the SAME names with inverted values, so a
11
11
  component names a colour once and both modes resolve by selector. Never
12
- put a mode in a property name — that form came from tokens.css, retired
12
+ put a mode in a property name: that form came from tokens.css, retired
13
13
  2026-09-03, and resolves to nothing.
14
14
 
15
15
  Declares 210 custom properties.
16
- Generated — do not edit. Run `npm run build-tokens`.
16
+ Generated: do not edit. Run `npm run build-tokens`.
17
17
  ==========================================================================
18
18
  */
19
19
  :root, [data-theme="light"] {
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — CONTRACT
3
+ PATHWAY DESIGN TOKENS, CONTRACT
4
4
  ==========================================================================
5
5
 
6
6
  The colour contract, Midnight Mode. Identical names to themes/light.css.
@@ -15,7 +15,7 @@
15
15
  White, XLight and Black are inversion anchors and keep their value.
16
16
 
17
17
  Declares 210 custom properties.
18
- Generated — do not edit. Run `npm run build-tokens`.
18
+ Generated: do not edit. Run `npm run build-tokens`.
19
19
  ==========================================================================
20
20
  */
21
21
  [data-theme="dark"], [data-theme="midnight"] {
package/dist/tokens.json CHANGED
@@ -400,6 +400,22 @@
400
400
  "$type": "number",
401
401
  "$value": "{primitive-unit.unit.60}"
402
402
  }
403
+ },
404
+ "widget": {
405
+ "minwidth": {
406
+ "detail": {
407
+ "$type": "number",
408
+ "$value": 0
409
+ },
410
+ "explore": {
411
+ "$type": "number",
412
+ "$value": 0
413
+ },
414
+ "glance": {
415
+ "$type": "number",
416
+ "$value": 0
417
+ }
418
+ }
403
419
  }
404
420
  }
405
421
  },
@@ -451,7 +467,7 @@
451
467
  "2": {
452
468
  "$type": "duration",
453
469
  "$value": "150ms",
454
- "$description": "Hover fills, small opacity fades, colour transitions — the most common micro-interaction timing"
470
+ "$description": "Hover fills, small opacity fades, colour transitions: the most common micro-interaction timing"
455
471
  },
456
472
  "3": {
457
473
  "$type": "duration",
@@ -491,7 +507,7 @@
491
507
  "loop-fast": {
492
508
  "$type": "duration",
493
509
  "$value": "750ms",
494
- "$description": "Tighter loop for embedded/inline contexts (in-button spinner — faster reads as more responsive feedback)"
510
+ "$description": "Tighter loop for embedded/inline contexts (in-button spinner, faster reads as more responsive feedback)"
495
511
  }
496
512
  },
497
513
  "easing": {
@@ -503,7 +519,7 @@
503
519
  0.2,
504
520
  1
505
521
  ],
506
- "$description": "Most transitions — enters AND exits. Smooth, no overshoot. Default for colour, fill, opacity, focus rings."
522
+ "$description": "Most transitions, enters AND exits. Smooth, no overshoot. Default for colour, fill, opacity, focus rings."
507
523
  },
508
524
  "spring": {
509
525
  "$type": "cubicBezier",
@@ -513,7 +529,7 @@
513
529
  0.64,
514
530
  1
515
531
  ],
516
- "$description": "Signature Pathway curve. Interactive element entrances: dropdown/popover panel opens. A whisper of overshoot (y2=1.04) — easeful, only *ever so slightly* alive. Softened 2026-06-08 from 1.08, which read as too bouncy. Not for chevrons — see chevron rule below."
532
+ "$description": "Signature Pathway curve. Interactive element entrances: dropdown/popover panel opens. A whisper of overshoot (y2=1.04), easeful, only *ever so slightly* alive. Softened 2026-06-08 from 1.08, which read as too bouncy. Not for chevrons, see chevron rule below."
517
533
  },
518
534
  "decelerate": {
519
535
  "$type": "cubicBezier",
@@ -523,7 +539,7 @@
523
539
  0.2,
524
540
  1
525
541
  ],
526
- "$description": "Enters where the element should glide in smoothly — overlay panels, popovers, anything appearing from outside. Pure ease-out, no bounce."
542
+ "$description": "Enters where the element should glide in smoothly: overlay panels, popovers, anything appearing from outside. Pure ease-out, no bounce."
527
543
  },
528
544
  "accelerate": {
529
545
  "$type": "cubicBezier",
@@ -533,7 +549,7 @@
533
549
  1,
534
550
  1
535
551
  ],
536
- "$description": "Exits where the element leaves cleanly — dismissing overlays, scaling down."
552
+ "$description": "Exits where the element leaves cleanly, dismissing overlays, scaling down."
537
553
  },
538
554
  "emphasized": {
539
555
  "$type": "cubicBezier",
@@ -553,7 +569,7 @@
553
569
  0.68,
554
570
  1
555
571
  ],
556
- "$description": "Accordion settle — content reveal/hide, chevron rotation. The gentlest overshoot in the system (y2=1.03). Softened 2026-06-08 from 0.22,1.12,0.36,1, which was visibly bouncy."
572
+ "$description": "Accordion settle, content reveal/hide, chevron rotation. The gentlest overshoot in the system (y2=1.03). Softened 2026-06-08 from 0.22,1.12,0.36,1, which was visibly bouncy."
557
573
  },
558
574
  "linear": {
559
575
  "$type": "cubicBezier",
@@ -3882,7 +3898,7 @@
3882
3898
  },
3883
3899
  "300-70": {
3884
3900
  "$type": "color",
3885
- "$value": "#8598d7b2"
3901
+ "$value": "#8598d7b3"
3886
3902
  },
3887
3903
  "300-8": {
3888
3904
  "$type": "color",
package/dist/type.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  ==========================================================================
3
- PATHWAY DESIGN TOKENS — CONTRACT
3
+ PATHWAY DESIGN TOKENS, CONTRACT
4
4
  ==========================================================================
5
5
 
6
6
  The type SCALE. Name these freely.
@@ -18,7 +18,7 @@
18
18
  designer applies them.
19
19
 
20
20
  Declares 39 custom properties.
21
- Generated — do not edit. Run `npm run build-tokens`.
21
+ Generated: do not edit. Run `npm run build-tokens`.
22
22
  ==========================================================================
23
23
  */
24
24
  :root {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@helloimjolopez-pathway/pathway-tokens",
3
- "version": "11.1.0",
4
- "description": "Pathway Design System tokens — CSS custom properties, JS export, and DTCG JSON",
3
+ "version": "11.2.0",
4
+ "description": "Pathway Design System tokens, CSS custom properties, JS export, and DTCG JSON",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "engines": {
@@ -62,7 +62,7 @@
62
62
  "fix-tokens": "node scripts/fix-tokens.js",
63
63
  "fix-tokens-preview": "node scripts/fix-tokens.js --dry-run",
64
64
  "build-tokens": "node scripts/sync-motion-tokens.js && node style-dictionary.config.js && node scripts/stamp-headers.js",
65
- "build-dist": "node scripts/sync-motion-tokens.js && node style-dictionary.config.js && node scripts/stamp-headers.js && node scripts/check-demo-tokens.js && node scripts/check-token-refs.js && node scripts/check-token-names.js && node scripts/check-token-object-refs.js && node scripts/fix-stale-doc-hex.js --check && node scripts/check-state-distinctness.js && node scripts/check-ladder-order.js && node scripts/check-variant-distinctness.js && node scripts/check-fill-foreground-pairs.js && node scripts/check-story-yield.js && node scripts/check-motion-coverage.js && node scripts/build-dist.js && node scripts/build-index.js && node scripts/build-contract.js && npm run build-elements && npm run sync-demo-module-icon",
65
+ "build-dist": "node scripts/sync-motion-tokens.js && node style-dictionary.config.js && node scripts/stamp-headers.js && node scripts/check-demo-tokens.js && node scripts/check-token-refs.js && node scripts/check-token-names.js && node scripts/check-token-object-refs.js && node scripts/fix-stale-doc-hex.js --check && node scripts/check-state-distinctness.js && node scripts/check-ladder-order.js && node scripts/check-variant-distinctness.js && node scripts/check-fill-foreground-pairs.js && node scripts/check-story-yield.js && node scripts/check-motion-coverage.js && node scripts/check-spec-stories.js && node scripts/check-code-connect.js && node scripts/build-component-index.js --check && node scripts/build-dist.js && node scripts/build-index.js && node scripts/build-contract.js && npm run build-elements && npm run sync-demo-module-icon && node scripts/check-component-tables.js && node scripts/build-demos.js && node scripts/check-demo-links.js && node scripts/check-alpha-names.js && node scripts/check-token-descriptions.js",
66
66
  "storybook": "node style-dictionary.config.js && PATHWAY_BRAND_AXIS=1 storybook dev -p 6006",
67
67
  "build-storybook": "storybook build",
68
68
  "check-demo-tokens": "node scripts/check-demo-tokens.js",
@@ -74,7 +74,7 @@
74
74
  "check-secrets": "node scripts/check-secrets.js",
75
75
  "check-token-refs": "node scripts/check-token-refs.js",
76
76
  "check-token-names": "node scripts/check-token-names.js",
77
- "check-tokens": "node scripts/check-demo-tokens.js && node scripts/check-token-refs.js && node scripts/check-token-names.js && node scripts/check-token-object-refs.js && node scripts/check-story-yield.js && node scripts/fix-stale-doc-hex.js --check && node scripts/check-motion-coverage.js",
77
+ "check-tokens": "node scripts/check-demo-tokens.js && node scripts/check-token-refs.js && node scripts/check-token-names.js && node scripts/check-token-object-refs.js && node scripts/check-story-yield.js && node scripts/fix-stale-doc-hex.js --check && node scripts/check-motion-coverage.js && node scripts/build-component-index.js --check && node scripts/check-spec-stories.js && node scripts/check-code-connect.js && node scripts/check-component-tables.js && node scripts/check-alpha-names.js && node scripts/check-token-descriptions.js",
78
78
  "build-elements": "esbuild components/sidenav/pathway-sidenav.js --bundle --format=esm --minify --loader:.jsx=jsx --jsx=automatic --define:process.env.NODE_ENV='\"production\"' --outfile=dist/pathway-sidenav.js",
79
79
  "check-story-yield": "node scripts/check-story-yield.js",
80
80
  "stamp-headers": "node scripts/stamp-headers.js",
@@ -85,7 +85,28 @@
85
85
  "check-token-object-refs": "node scripts/check-token-object-refs.js",
86
86
  "check-motion-coverage": "node scripts/check-motion-coverage.js",
87
87
  "sync-demo-module-icon": "node scripts/sync-demo-module-icon.js",
88
- "build-storybook:brands": "PATHWAY_BRAND_AXIS=1 storybook build"
88
+ "build-storybook:brands": "PATHWAY_BRAND_AXIS=1 storybook build",
89
+ "build-component-index": "node scripts/build-component-index.js",
90
+ "check-component-index": "node scripts/build-component-index.js --check",
91
+ "check-spec-stories": "node scripts/check-spec-stories.js",
92
+ "check-figma-nodes": "node scripts/check-figma-nodes.js",
93
+ "check-stories-render": "node scripts/check-stories-render.js",
94
+ "verify-storybook": "node scripts/check-syntax.js && storybook build --quiet && node scripts/check-stories-render.js && node scripts/check-sidebar-shape.js && node scripts/check-reduced-motion.js && node scripts/check-target-size.js && node scripts/check-contrast-focus.js && node scripts/check-mdx-refs.js && node scripts/check-accessible-names.js && node scripts/check-demos-render.js",
95
+ "check-code-connect": "node scripts/check-code-connect.js",
96
+ "check-mdx-refs": "node scripts/check-mdx-refs.js",
97
+ "check-component-tables": "node scripts/check-component-tables.js",
98
+ "check-accessible-names": "node scripts/check-accessible-names.js",
99
+ "check-syntax": "node scripts/check-syntax.js",
100
+ "build-demos": "node scripts/build-demos.js",
101
+ "check-demo-links": "node scripts/check-demo-links.js",
102
+ "check-alpha-names": "node scripts/check-alpha-names.js",
103
+ "check-demos-render": "node scripts/check-demos-render.js",
104
+ "check-token-descriptions": "node scripts/check-token-descriptions.js",
105
+ "check-sidebar-shape": "node scripts/check-sidebar-shape.js",
106
+ "check-reduced-motion": "node scripts/check-reduced-motion.js",
107
+ "check-target-size": "node scripts/check-target-size.js",
108
+ "check-contrast-focus": "node scripts/check-contrast-focus.js",
109
+ "check-spec-structure": "node scripts/check-spec-structure.js"
89
110
  },
90
111
  "devDependencies": {
91
112
  "@babel/preset-env": "^7.29.2",