@helloimjolopez-pathway/pathway-tokens 11.0.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 +178 -12
- package/components/scrollbar/scrollbar.jsx +39 -16
- package/components/sidenav/sidenav.jsx +90 -44
- package/dist/README.md +2 -2
- package/dist/brands/newco/index.css +2 -2
- package/dist/brands/newco/layout-contextual.css +4 -1
- package/dist/brands/newco/tokens.js +34 -1
- package/dist/breakpoints.css +2 -2
- package/dist/contract.json +5 -5
- package/dist/index.css +2 -2
- package/dist/layout-contextual.css +7 -4
- package/dist/layout-responsive.css +2 -2
- package/dist/layout.css +3 -3
- package/dist/motion.css +10 -10
- package/dist/primitives.css +3 -3
- package/dist/themes/light.css +3 -3
- package/dist/themes/midnight.css +2 -2
- package/dist/tokens.js +33 -0
- package/dist/tokens.json +40 -8
- package/dist/type.css +2 -2
- package/package.json +28 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SideNav
|
|
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=
|
|
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
|
|
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,12 +138,16 @@ 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
|
|
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"),
|
|
144
|
+
// Collapsed rail: container.rowStart's padding-right in Figma. It is what
|
|
145
|
+
// keeps the centred icon a hair left of the square's true centre, the same as
|
|
146
|
+
// the design.
|
|
147
|
+
railIconInset: u("padding-xxtight"),
|
|
142
148
|
textPad: u("padding-xtight"),
|
|
143
|
-
childIndent: 24, // no token in Figma
|
|
144
|
-
stripeW: 4, // no token in Figma
|
|
149
|
+
childIndent: 24, // no token in Figma, level-1 left indent
|
|
150
|
+
stripeW: 4, // no token in Figma, indicator stripe width
|
|
145
151
|
colPadL: u("padding-medium"),
|
|
146
152
|
colPadR: u("padding-tight"),
|
|
147
153
|
collapseGap: u("padding-xxtight"),
|
|
@@ -234,23 +240,46 @@ export function SideNavItem({
|
|
|
234
240
|
width: isSidebarCollapsed ? L.itemH : "100%",
|
|
235
241
|
marginLeft: isSidebarCollapsed ? "auto" : undefined,
|
|
236
242
|
marginRight: isSidebarCollapsed ? "auto" : undefined,
|
|
243
|
+
// Figma's container.rowStart is 40 wide inside the 44 square with
|
|
244
|
+
// padding-right 6 and primary=CENTER, so the icon centres in the 34
|
|
245
|
+
// that is left after the 4px indicator column. See the icon wrap below.
|
|
246
|
+
paddingRight: isSidebarCollapsed ? L.railIconInset : undefined,
|
|
247
|
+
// border-box or the 6px padding is ADDED to the 44, which made the rail
|
|
248
|
+
// item 50 wide and no longer a square.
|
|
249
|
+
boxSizing: "border-box",
|
|
237
250
|
borderRadius: T.radius, backgroundColor: fillBg,
|
|
238
251
|
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
239
252
|
overflow: "hidden", transition: "background-color var(--motion-duration-3) var(--motion-easing-standard)", userSelect: "none" }}>
|
|
240
253
|
|
|
241
254
|
<IndicatorStripe visible={showStripe} />
|
|
242
255
|
|
|
243
|
-
{/* Leading icon
|
|
256
|
+
{/* Leading icon, Level 0 only */}
|
|
244
257
|
{!isChild && item.icon && (
|
|
245
258
|
<div style={{ width: L.iconWrap, height: L.iconWrap, display: "flex",
|
|
246
259
|
alignItems: "center", justifyContent: "center", flexShrink: 0,
|
|
247
|
-
|
|
260
|
+
// COLLAPSED CENTRES, EXPANDED INDENTS. The fixed 12px left margin
|
|
261
|
+
// applied in both states, so on the rail the 24px icon sat at
|
|
262
|
+
// x 16..40 of the 44 square: 6px right of centre, in every state.
|
|
263
|
+
// Reported 2026-10-02. Auto margins hand the free space back to
|
|
264
|
+
// both sides, which with the 6px right padding above reproduces
|
|
265
|
+
// Figma's x=9 exactly.
|
|
266
|
+
marginLeft: isSidebarCollapsed ? "auto" : L.rowPadH,
|
|
267
|
+
marginRight: isSidebarCollapsed ? "auto" : undefined,
|
|
268
|
+
color: iconColor }}>
|
|
248
269
|
{typeof item.icon === "function"
|
|
249
270
|
? item.icon({ size: L.iconInner, color: iconColor })
|
|
250
271
|
: typeof item.icon === "string"
|
|
251
272
|
? <span className="material-symbols-rounded" aria-hidden="true"
|
|
252
273
|
style={{ fontSize: L.iconInner, lineHeight: 1, color: iconColor,
|
|
253
|
-
|
|
274
|
+
// display:block so the glyph is laid out as a box rather than
|
|
275
|
+
// on a text baseline: an inline glyph in a flex-centred
|
|
276
|
+
// parent is centred by its line box, not by its own art, so
|
|
277
|
+
// it rode high inside the 24px wrap.
|
|
278
|
+
display: "block",
|
|
279
|
+
// opsz MUST match fontSize. It said 20 against a 16px size,
|
|
280
|
+
// so the glyph was drawn on a 20px grid and scaled down into
|
|
281
|
+
// a 16px box, which shifts the art off centre.
|
|
282
|
+
fontVariationSettings: `'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' ${L.iconInner}` }}>
|
|
254
283
|
{item.icon}
|
|
255
284
|
</span>
|
|
256
285
|
: React.cloneElement(item.icon, { style: { color: iconColor } })}
|
|
@@ -258,9 +287,19 @@ export function SideNavItem({
|
|
|
258
287
|
)}
|
|
259
288
|
|
|
260
289
|
{/* Label */}
|
|
261
|
-
<div style={{
|
|
262
|
-
|
|
263
|
-
|
|
290
|
+
<div style={{
|
|
291
|
+
// Collapsed, the label must take NO part in the flex distribution.
|
|
292
|
+
// With flex:1 it still claimed the free space even though max-width
|
|
293
|
+
// clamped it to zero, so the icon's auto margins had nothing left to
|
|
294
|
+
// centre with and the icon stayed hard left.
|
|
295
|
+
flex: isSidebarCollapsed ? "0 0 0px" : 1,
|
|
296
|
+
minWidth: 0,
|
|
297
|
+
// Collapsed, the padding has to go too. max-width clamps the CONTENT
|
|
298
|
+
// box, so the label still occupied 8px of padding on the rail and ate
|
|
299
|
+
// most of the free space the icon's auto margins needed: the icon
|
|
300
|
+
// landed at x=5 instead of Figma's x=9.
|
|
301
|
+
paddingLeft: isSidebarCollapsed ? 0 : (isChild ? 32 : L.textPad),
|
|
302
|
+
paddingRight: isSidebarCollapsed ? 0 : ((!isChild && hasChildren) ? 0 : L.rowPadH),
|
|
264
303
|
maxWidth: isSidebarCollapsed ? 0 : 200,
|
|
265
304
|
opacity: isSidebarCollapsed ? 0 : 1,
|
|
266
305
|
overflow: "hidden",
|
|
@@ -274,15 +313,22 @@ export function SideNavItem({
|
|
|
274
313
|
</p>
|
|
275
314
|
</div>
|
|
276
315
|
|
|
277
|
-
{/* Chevron
|
|
278
|
-
|
|
279
|
-
|
|
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 && (
|
|
325
|
+
<div style={{ width: isSidebarCollapsed ? 0 : 40, height: L.iconWrap, display: "flex",
|
|
280
326
|
alignItems: "center", justifyContent: "center", flexShrink: 0,
|
|
281
327
|
maxWidth: isSidebarCollapsed ? 0 : 40,
|
|
282
328
|
opacity: isSidebarCollapsed ? 0 : 1,
|
|
283
329
|
overflow: "hidden",
|
|
284
330
|
transition: "max-width var(--motion-duration-6) var(--motion-easing-emphasized), opacity var(--motion-duration-3) var(--motion-easing-standard)" }}>
|
|
285
|
-
{/* Chevron rotation matches accordion timing
|
|
331
|
+
{/* Chevron rotation matches accordion timing, --motion-duration-5 + --motion-easing-accordion */}
|
|
286
332
|
<div style={{ transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
|
|
287
333
|
transition: "transform var(--motion-duration-5) var(--motion-easing-accordion)" }}>
|
|
288
334
|
{chevronDown ? chevronDown({ size: 10, color: iconColor }) : <ChevDown size={10} color={iconColor} />}
|
|
@@ -348,7 +394,7 @@ export function SideNavTooltip({ label, anchorRect, onMouseEnter, onMouseLeave }
|
|
|
348
394
|
}
|
|
349
395
|
|
|
350
396
|
// ─── SectionLabel ─────────────────────────────────────────────────────────────
|
|
351
|
-
// Figma node 40006794-5977
|
|
397
|
+
// Figma node 40006794-5977, building block used in CollapsedPopover headers.
|
|
352
398
|
// Tokens: Foreground/Static/Neutral/Base (#313131), Label/Section/Small/Semibold
|
|
353
399
|
// (11px / 600 / 16px / 0.6px letter-spacing, uppercase)
|
|
354
400
|
// Padding: Padding/Tight (12px left), Padding/XTight (8px vertical)
|
|
@@ -377,7 +423,7 @@ export function CollapsedPopover({ item, onClick, anchorRect, onMouseEnter, onMo
|
|
|
377
423
|
boxShadow: c2("--elevation-widget"),
|
|
378
424
|
padding: "var(--semantic-layout-units-padding-xxtight)", minWidth: 200, pointerEvents: "auto",
|
|
379
425
|
animation: "popoverIn var(--motion-duration-3) var(--motion-easing-spring) forwards" }}>
|
|
380
|
-
{/* Section label
|
|
426
|
+
{/* Section label, Figma component 40006794-5977 */}
|
|
381
427
|
<div style={{ borderBottom: `0.5px solid ${c("stroke-static-neutral-faint")}` }}>
|
|
382
428
|
<SectionLabel label={item.label} />
|
|
383
429
|
</div>
|
|
@@ -416,11 +462,11 @@ export function PopoverRow({ item, onClick, activeId }) {
|
|
|
416
462
|
// ─── CollapseButton ───────────────────────────────────────────────────────────
|
|
417
463
|
// Redesigned in Figma (2026-05-12): now uses Slot.RowStart (Module.Mark icon)
|
|
418
464
|
// + Slot.RowEnd (Action Icon). The action icon uses Foreground/Action/Secondary/Rest
|
|
419
|
-
// (#6b6b6b)
|
|
465
|
+
// (#6b6b6b), different from nav item icons. SVG assets fetched from Figma directly.
|
|
420
466
|
export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, expandIcon }) {
|
|
421
467
|
const [h, setH] = useState(false);
|
|
422
468
|
const labelColor = h ? T.text.navHover : T.text.navBase;
|
|
423
|
-
const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover)
|
|
469
|
+
const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover), always static, no hover change
|
|
424
470
|
return (
|
|
425
471
|
<div style={{ width: "100%" }}>
|
|
426
472
|
<div style={{ height: 1, backgroundColor: T.fill.infoSubtle, marginBottom: L.collapseGap }} />
|
|
@@ -432,10 +478,10 @@ export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, exp
|
|
|
432
478
|
borderRadius: T.radius, backgroundColor: h ? T.fill.navHover : T.fill.navBase,
|
|
433
479
|
cursor: "pointer", transition: "background-color var(--motion-duration-3) var(--motion-easing-standard)", overflow: "hidden" }}>
|
|
434
480
|
|
|
435
|
-
{/* Slot.RowStart
|
|
481
|
+
{/* Slot.RowStart, 4px stripe column (structural, always present) */}
|
|
436
482
|
<div style={{ width: L.stripeW, alignSelf: "stretch", flexShrink: 0 }} />
|
|
437
483
|
|
|
438
|
-
{/* Container.Main
|
|
484
|
+
{/* Container.Main, label (hidden when collapsed) */}
|
|
439
485
|
<div style={{ flex: 1, paddingLeft: L.rowPadH,
|
|
440
486
|
maxWidth: isSidebarCollapsed ? 0 : 200,
|
|
441
487
|
opacity: isSidebarCollapsed ? 0 : 1,
|
|
@@ -449,7 +495,7 @@ export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, exp
|
|
|
449
495
|
</span>
|
|
450
496
|
</div>
|
|
451
497
|
|
|
452
|
-
{/* Slot.RowEnd
|
|
498
|
+
{/* Slot.RowEnd, Action Icon (right_panel_open / left_panel_open from Figma) */}
|
|
453
499
|
<div style={{ width: 40, height: L.iconWrap, display: "flex",
|
|
454
500
|
alignItems: "center", justifyContent: "center", flexShrink: 0,
|
|
455
501
|
marginLeft: isSidebarCollapsed ? "auto" : 0,
|
|
@@ -472,15 +518,15 @@ export function CollapseButton({ isSidebarCollapsed, onToggle, collapseIcon, exp
|
|
|
472
518
|
// Optional in-nav section heading. Figma: SideNav.SectionLabel (node 40006794:5975).
|
|
473
519
|
// Tokens: Label/Section/Small/Semibold (11px/600/16px/0.6px), Foreground/Static/Neutral/Base (#606060)
|
|
474
520
|
// Container: h-[40px], pl-[4px], pr-[4px], py-[8px]
|
|
475
|
-
// In collapsed rail: hidden
|
|
521
|
+
// In collapsed rail: hidden, replaced by a Divider (rendered by SideNav itself, see §2.3)
|
|
476
522
|
// ── NavHeader ────────────────────────────────────────────────────────────────
|
|
477
523
|
// Sits at the TOP of the SideNav. 48px row with the expand/collapse action icon
|
|
478
524
|
// in Slot.RowEnd (expanded) or centered (collapsed). Divider below.
|
|
479
525
|
// Matches Figma "Slot.NavHeader" (node 40007331:7794).
|
|
480
|
-
// Hidden on mobile (<768px)
|
|
526
|
+
// Hidden on mobile (<768px), TopNav hamburger is the sole toggle there.
|
|
481
527
|
export function NavHeader({ isSidebarCollapsed, onToggle, collapseIcon, expandIcon }) {
|
|
482
528
|
const [h, setH] = useState(false);
|
|
483
|
-
const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover)
|
|
529
|
+
const actionIconColor = T.icon.actionSecondary; // var(--semantic-color-stroke-action-field-hover), always static
|
|
484
530
|
return (
|
|
485
531
|
<div style={{ width: "100%", flexShrink: 0 }}>
|
|
486
532
|
<div onClick={onToggle} onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
|
|
@@ -510,7 +556,7 @@ export function NavHeader({ isSidebarCollapsed, onToggle, collapseIcon, expandIc
|
|
|
510
556
|
</div>
|
|
511
557
|
)}
|
|
512
558
|
</div>
|
|
513
|
-
{/* Divider
|
|
559
|
+
{/* Divider, Stroke/Static/Neutral/Base, py-[2px] in Figma */}
|
|
514
560
|
<div style={{ padding: "2px 0" }}>
|
|
515
561
|
<div style={{ height: 1, backgroundColor: T.fill.infoSubtle }} />
|
|
516
562
|
</div>
|
|
@@ -534,7 +580,7 @@ export function NavSectionLabel({ label }) {
|
|
|
534
580
|
}
|
|
535
581
|
|
|
536
582
|
// ── BulletDot ────────────────────────────────────────────────────────────────
|
|
537
|
-
// 6px filled circle
|
|
583
|
+
// 6px filled circle, leading icon for SideNavListSection's ListItem.
|
|
538
584
|
// Color cycles Base / Hover / Active just like SideNavItem icons.
|
|
539
585
|
export function BulletDot({ color }) {
|
|
540
586
|
return (
|
|
@@ -581,7 +627,7 @@ export function ListItem({ item, isActive, onClick }) {
|
|
|
581
627
|
|
|
582
628
|
// ── SideNavListSection ───────────────────────────────────────────────────────
|
|
583
629
|
// Figma: SideNav.ListSection (node 40007332:8034). A labelled grouping of flat
|
|
584
|
-
// nav items
|
|
630
|
+
// nav items: no icons, no children, no expand/collapse. Used for contextual
|
|
585
631
|
// link lists (Recent Content, Pinned, Bookmarks). Only shown in expanded sidebar.
|
|
586
632
|
export function SideNavListSection({ label, items, activeId, onNavigate }) {
|
|
587
633
|
return (
|
|
@@ -690,7 +736,7 @@ export function SideNav({
|
|
|
690
736
|
anchorRect={popoverId === item.id ? popoverRect : null}
|
|
691
737
|
onPopoverEnter={onPopoverEnter} onPopoverLeave={onPopoverLeave}
|
|
692
738
|
activeId={activeId} />
|
|
693
|
-
{/* Level 1 children
|
|
739
|
+
{/* Level 1 children, animated accordion. grid-template-rows 0fr→1fr at
|
|
694
740
|
--motion-duration-5 + --motion-easing-accordion, with inner wrapper opacity
|
|
695
741
|
fade --motion-duration-4 (70ms delay on expand, no delay on collapse). */}
|
|
696
742
|
{hasChildren && !collapsed && (
|
|
@@ -730,12 +776,12 @@ export function SideNav({
|
|
|
730
776
|
paddingLeft: collapsed ? L.navColPadH : L.navPadH,
|
|
731
777
|
paddingRight: collapsed ? L.navColPadH : L.navPadH,
|
|
732
778
|
boxSizing: "border-box", flexShrink: 0,
|
|
733
|
-
// Smooth-spring curve
|
|
779
|
+
// Smooth-spring curve, soft personality, no overshoot.
|
|
734
780
|
transition: "width var(--motion-duration-6) var(--motion-easing-emphasized)",
|
|
735
781
|
borderRight: `0.5px solid ${T.fill.infoSubtle}`,
|
|
736
782
|
overflow: "hidden", // the inner menu scrolls; the nav itself does not
|
|
737
783
|
}}>
|
|
738
|
-
{/* NavHeader
|
|
784
|
+
{/* NavHeader, TOP of nav, PINNED (flexShrink:0) so it stays visible while the
|
|
739
785
|
item list scrolls beneath it. Hidden on mobile (<768px). */}
|
|
740
786
|
{(!hideCollapseButton || headerEnd) && (
|
|
741
787
|
<div style={{ flexShrink: 0 }}>
|
|
@@ -765,7 +811,7 @@ export function SideNav({
|
|
|
765
811
|
</div>
|
|
766
812
|
)}
|
|
767
813
|
|
|
768
|
-
{/* SideNavMenu
|
|
814
|
+
{/* SideNavMenu: the ONLY scroll region, wrapped in <Scrollable> so the custom
|
|
769
815
|
overlay scrollbar (identical on every OS, never affects layout/padding) handles
|
|
770
816
|
overflow. flex:1 + minHeight:0 lets it shrink below content height; the pinned
|
|
771
817
|
header above never moves. Applies in BOTH expanded (240px) and collapsed (72px) states. */}
|
|
@@ -773,7 +819,7 @@ export function SideNav({
|
|
|
773
819
|
style={{ flex: 1, minHeight: 0,
|
|
774
820
|
// Bleed the scroll region out to the nav's TRUE right edge so the overlay thumb
|
|
775
821
|
// hugs the edge in BOTH states (the nav's border-box paddingRight would otherwise
|
|
776
|
-
// inset the thumb by 16px expanded / 12px on the 72px rail
|
|
822
|
+
// inset the thumb by 16px expanded / 12px on the 72px rail, badly off on the rail).
|
|
777
823
|
// negated through calc() because these are var() references, and negating
|
|
778
824
|
// a string would produce NaN
|
|
779
825
|
marginRight: neg(collapsed ? L.navColPadH : L.navPadH) }}
|
|
@@ -781,11 +827,11 @@ export function SideNav({
|
|
|
781
827
|
paddingTop: L.menuPadT,
|
|
782
828
|
// Re-add the content's right inset INSIDE the scroll view so items keep their
|
|
783
829
|
// position; only the thumb moves to the edge. (Spec rule: padding on viewStyle,
|
|
784
|
-
// never on the wrapper
|
|
830
|
+
// never on the wrapper, keeps the thumb flush on any scroll surface.)
|
|
785
831
|
paddingRight: collapsed ? L.navColPadH : L.navPadH }}
|
|
786
832
|
>
|
|
787
833
|
|
|
788
|
-
{/* Sectioned render
|
|
834
|
+
{/* Sectioned render: when `sections` prop is provided */}
|
|
789
835
|
{sections && sections.map(({ section, items: secItems }, sIdx) => (
|
|
790
836
|
<div key={section} style={{ display: "flex", flexDirection: "column",
|
|
791
837
|
gap: L.menuGap, flexShrink: 0 }}>
|
|
@@ -809,10 +855,10 @@ export function SideNav({
|
|
|
809
855
|
</div>
|
|
810
856
|
))}
|
|
811
857
|
|
|
812
|
-
{/* Flat render
|
|
858
|
+
{/* Flat render: when only `items` is provided (backward compatibility) */}
|
|
813
859
|
{!sections && (items || []).map(renderItem)}
|
|
814
860
|
|
|
815
|
-
{/* SideNavListSection
|
|
861
|
+
{/* SideNavListSection, optional. Hidden in collapsed rail. */}
|
|
816
862
|
{listSection && (
|
|
817
863
|
<div style={{ opacity: collapsed ? 0 : 1,
|
|
818
864
|
maxHeight: collapsed ? 0 : 400, overflow: "hidden",
|
|
@@ -822,16 +868,16 @@ export function SideNav({
|
|
|
822
868
|
</div>
|
|
823
869
|
)}
|
|
824
870
|
|
|
825
|
-
{/* Body slot
|
|
871
|
+
{/* Body slot, scrolls with the item list, so it sits INSIDE
|
|
826
872
|
Scrollable and above the spacer. Use this for content that belongs
|
|
827
873
|
with the items; use `footer` for anything that must stay visible. */}
|
|
828
874
|
{children && <div style={{ flexShrink: 0 }}>{children}</div>}
|
|
829
875
|
|
|
830
|
-
{/* Bottom spacer
|
|
876
|
+
{/* Bottom spacer, fills remaining height */}
|
|
831
877
|
<div style={{ flex: 1, minHeight: L.menuPadB }} />
|
|
832
878
|
</Scrollable>
|
|
833
879
|
|
|
834
|
-
{/* Footer slot
|
|
880
|
+
{/* Footer slot, OUTSIDE Scrollable and flexShrink:0, so it is pinned to
|
|
835
881
|
the bottom of the nav and stays put while the list scrolls. The
|
|
836
882
|
divider only appears when the slot has content, so a nav without a
|
|
837
883
|
footer is unchanged. Padding drops to the rail's narrower inset when
|
|
@@ -869,7 +915,7 @@ function ChevUp({ size = 10, color }) {
|
|
|
869
915
|
|
|
870
916
|
// ─── Collapse / Expand action icons (fetched from Figma 2026-05-12) ──────────
|
|
871
917
|
//
|
|
872
|
-
// PINNED 2026-09-07
|
|
918
|
+
// PINNED 2026-09-07: DO NOT RE-SYNC THESE TWO FROM FIGMA.
|
|
873
919
|
//
|
|
874
920
|
// The Figma SideBar Expand/Collapse component was changed after this date to a
|
|
875
921
|
// different icon. Jo has explicitly rejected that change for the repo, for
|
|
@@ -882,7 +928,7 @@ function ChevUp({ size = 10, color }) {
|
|
|
882
928
|
// by pulling Figma, and do not silently re-fetch node 40006793:3783. Ask first
|
|
883
929
|
// if you think it should change.
|
|
884
930
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
885
|
-
// right_panel_open
|
|
931
|
+
// right_panel_open, used on the collapse button when sidebar is EXPANDED (click to collapse)
|
|
886
932
|
// Token: Foreground/Action/Secondary/Rest (#6b6b6b)
|
|
887
933
|
// Figma component: SideBar Expand/Collapse, Type=Collapse, node 40006793:3783
|
|
888
934
|
function RightPanelOpenIcon({ size = 12, color = T.icon.actionSecondary }) {
|
|
@@ -893,7 +939,7 @@ function RightPanelOpenIcon({ size = 12, color = T.icon.actionSecondary }) {
|
|
|
893
939
|
);
|
|
894
940
|
}
|
|
895
941
|
|
|
896
|
-
// left_panel_open
|
|
942
|
+
// left_panel_open, used on the collapse button when sidebar is COLLAPSED (click to expand)
|
|
897
943
|
// Token: Foreground/Action/Secondary/Rest (#6b6b6b)
|
|
898
944
|
// Figma component: SideBar Expand/Collapse, Type=Expand, node 40006793:3783
|
|
899
945
|
function LeftPanelOpenIcon({ size = 12, color = T.icon.actionSecondary }) {
|
package/dist/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**The colour contract is 210 names.** Your full working vocabulary is
|
|
4
4
|
**360**. If you add up every declaration in this folder you get
|
|
5
|
-
|
|
5
|
+
1155. That number is not the contract, and the table below says why.
|
|
6
6
|
|
|
7
7
|
## Link these, in this order
|
|
8
8
|
|
|
@@ -30,7 +30,7 @@ error**.
|
|
|
30
30
|
| `themes/midnight.css` | 210 | yes | yes | Colour contract, Midnight Mode. Same names as light. |
|
|
31
31
|
| `type.css` | 39 | yes | yes | Type scale. Compose from these. |
|
|
32
32
|
| `layout.css` | 76 | yes | yes | Spacing, radii, border widths. |
|
|
33
|
-
| `layout-contextual.css` |
|
|
33
|
+
| `layout-contextual.css` | 37 | **no** | **no** | Per-component metrics. This repo's components use these. |
|
|
34
34
|
| `motion.css` | 21 | yes | yes | Durations and easings. |
|
|
35
35
|
| `layout-responsive.css` | 9 | yes | yes | Sheet and TopNav padding. The only layout tokens with media queries. |
|
|
36
36
|
| `breakpoints.css` | 5 | yes | yes | Breakpoint values. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
NewCo brand layer
|
|
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
|
|
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
|
|
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": "#
|
|
4250
|
+
"value": "#8598d7b3",
|
|
4218
4251
|
"type": "color",
|
|
4219
4252
|
"path": [
|
|
4220
4253
|
"primitive-color",
|
package/dist/breakpoints.css
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
2
|
==========================================================================
|
|
3
|
-
PATHWAY DESIGN TOKENS
|
|
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
|
|
9
|
+
Generated: do not edit. Run `npm run build-tokens`.
|
|
10
10
|
==========================================================================
|
|
11
11
|
*/
|
|
12
12
|
:root {
|
package/dist/contract.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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.
|
|
4
|
-
"generated": "2026-10-
|
|
3
|
+
"version": "11.2.0",
|
|
4
|
+
"generated": "2026-10-13",
|
|
5
5
|
"counts": {
|
|
6
6
|
"contract": 360,
|
|
7
|
-
"infrastructure":
|
|
8
|
-
"declarationsAcrossAllFiles":
|
|
7
|
+
"infrastructure": 585,
|
|
8
|
+
"declarationsAcrossAllFiles": 1155
|
|
9
9
|
},
|
|
10
10
|
"loadOrder": [
|
|
11
11
|
"primitives.css",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"layout-contextual.css": {
|
|
52
52
|
"contract": false,
|
|
53
53
|
"mustLoad": false,
|
|
54
|
-
"count":
|
|
54
|
+
"count": 37,
|
|
55
55
|
"role": "Per-component metrics. This repo's components use these."
|
|
56
56
|
},
|
|
57
57
|
"motion.css": {
|
package/dist/index.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Pathway design tokens
|
|
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
|
|
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
|
|
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
|
-
Declares
|
|
13
|
-
Generated
|
|
12
|
+
Declares 37 custom properties.
|
|
13
|
+
Generated: do not edit. Run `npm run build-tokens`.
|
|
14
14
|
==========================================================================
|
|
15
15
|
*/
|
|
16
16
|
:root {
|
|
@@ -48,4 +48,7 @@
|
|
|
48
48
|
--contextual-layout-units-sidenav-width-collapsed: 84px;
|
|
49
49
|
--contextual-layout-units-sidenav-width-expanded: 256px;
|
|
50
50
|
--contextual-layout-units-topnav-height: 60px;
|
|
51
|
+
--contextual-layout-units-widget-minwidth-detail: 515px;
|
|
52
|
+
--contextual-layout-units-widget-minwidth-explore: 1050px;
|
|
53
|
+
--contextual-layout-units-widget-minwidth-glance: 275px;
|
|
51
54
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
==========================================================================
|
|
3
|
-
PATHWAY DESIGN TOKENS
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
38
|
-
--motion-easing-accelerate: cubic-bezier(0.4, 0, 1, 1); /** Exits where the element leaves cleanly
|
|
39
|
-
--motion-easing-accordion: cubic-bezier(0.33, 1.03, 0.68, 1); /** Accordion settle
|
|
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
|
}
|