@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.
@@ -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,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 — 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"),
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 — level-1 left indent
144
- 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
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 — Level 0 only */}
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
- marginLeft: L.rowPadH, color: iconColor }}>
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
- fontVariationSettings: "'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20" }}>
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={{ flex: 1, minWidth: 0,
262
- paddingLeft: isChild ? 32 : L.textPad,
263
- paddingRight: (!isChild && hasChildren) ? 0 : L.rowPadH,
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 — groupers only */}
278
- {!isChild && hasChildren && (
279
- <div style={{ width: 40, height: L.iconWrap, display: "flex",
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 — --motion-duration-5 + --motion-easing-accordion */}
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 — building block used in CollapsedPopover headers.
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 — Figma component 40006794-5977 */}
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) — different from nav item icons. SVG assets fetched from Figma directly.
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) — always static, no hover change
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 — 4px stripe column (structural, always present) */}
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 — label (hidden when collapsed) */}
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 — Action Icon (right_panel_open / left_panel_open from Figma) */}
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 — 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)
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) — TopNav hamburger is the sole toggle there.
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) — always static
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 — Stroke/Static/Neutral/Base, py-[2px] in Figma */}
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 — leading icon for SideNavListSection's ListItem.
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 — no icons, no children, no expand/collapse. Used for contextual
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 — animated accordion. grid-template-rows 0fr→1fr at
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 — soft personality, no overshoot.
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 — 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
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 — the ONLY scroll region, wrapped in <Scrollable> so the custom
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 — badly off on the 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 — keeps the thumb flush on any scroll surface.)
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 — when `sections` prop is provided */}
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 — when only `items` is provided (backward compatibility) */}
858
+ {/* Flat render: when only `items` is provided (backward compatibility) */}
813
859
  {!sections && (items || []).map(renderItem)}
814
860
 
815
- {/* SideNavListSection — optional. Hidden in collapsed rail. */}
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 — scrolls with the item list, so it sits INSIDE
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 — fills remaining height */}
876
+ {/* Bottom spacer, fills remaining height */}
831
877
  <div style={{ flex: 1, minHeight: L.menuPadB }} />
832
878
  </Scrollable>
833
879
 
834
- {/* 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
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 — DO NOT RE-SYNC THESE TWO FROM FIGMA.
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 — 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)
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 — 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)
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
- 1152. That number is not the contract, and the table below says why.
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` | 34 | **no** | **no** | Per-component metrics. This repo's components use these. |
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 — 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,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.0.0",
4
- "generated": "2026-10-02",
3
+ "version": "11.2.0",
4
+ "generated": "2026-10-13",
5
5
  "counts": {
6
6
  "contract": 360,
7
- "infrastructure": 582,
8
- "declarationsAcrossAllFiles": 1152
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": 34,
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 — 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
- Declares 34 custom properties.
13
- Generated — do not edit. Run `npm run build-tokens`.
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 — 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
  }