@helloimjolopez-pathway/pathway-tokens 5.4.0 → 6.0.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 +12 -5
- package/components/scrollbar/scrollbar.jsx +184 -0
- package/components/sidenav/sidenav.jsx +795 -0
- package/dist/breakpoints.css +11 -0
- package/dist/layout-contextual.css +52 -0
- package/dist/layout.css +51 -0
- package/dist/motion.css +23 -0
- package/dist/primitives.css +356 -0
- package/dist/themes/light.css +332 -0
- package/dist/themes/midnight.css +332 -0
- package/dist/tokens.js +14378 -69506
- package/dist/tokens.json +4765 -10337
- package/dist/type.css +49 -0
- package/package.json +43 -7
- package/dist/tokens.css +0 -2648
package/README.md
CHANGED
|
@@ -24,7 +24,15 @@ import tokenJson from "@helloimjolopez-pathway/pathway-tokens/json";
|
|
|
24
24
|
Without a bundler — link the CSS directly:
|
|
25
25
|
|
|
26
26
|
```html
|
|
27
|
-
|
|
27
|
+
<!-- primitives first: the themes reference these via var(), so this must load -->
|
|
28
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/primitives.css" />
|
|
29
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/themes/light.css" />
|
|
30
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/themes/midnight.css" />
|
|
31
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/type.css" />
|
|
32
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/layout.css" />
|
|
33
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/layout-contextual.css" />
|
|
34
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/motion.css" />
|
|
35
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/helloimjolopez-collab/pathway-ds@main/src/tokens/breakpoints.css" />
|
|
28
36
|
```
|
|
29
37
|
|
|
30
38
|
Once loaded, use the custom properties anywhere:
|
|
@@ -57,7 +65,7 @@ pathway-ds/
|
|
|
57
65
|
│ ├── figma-export/ Raw Figma export (sync target)
|
|
58
66
|
│ └── pathway-design-tokens.json W3C DTCG format
|
|
59
67
|
├── src/
|
|
60
|
-
│ ├── tokens/ Built output (
|
|
68
|
+
│ ├── tokens/ Built output (the 8-file contract + tokens.js)
|
|
61
69
|
│ └── stories/ Storybook sources
|
|
62
70
|
│ ├── Primitives/ Token primitives (Color, Typography, Units)
|
|
63
71
|
│ ├── Semantics/ Semantic tokens (light/dark, layout, typography)
|
|
@@ -65,7 +73,6 @@ pathway-ds/
|
|
|
65
73
|
├── .storybook/
|
|
66
74
|
├── .github/workflows/ Token sync · component sync · Storybook deploy
|
|
67
75
|
├── scripts/ Token sync/audit/fix + component build scripts
|
|
68
|
-
└── storybook/ Built Storybook output (deployed by CI)
|
|
69
76
|
```
|
|
70
77
|
|
|
71
78
|
## Governance — where things live
|
|
@@ -74,7 +81,7 @@ pathway-ds/
|
|
|
74
81
|
|---|---|
|
|
75
82
|
| Published token package | [`@helloimjolopez-pathway/pathway-tokens`](https://www.npmjs.com/package/@helloimjolopez-pathway/pathway-tokens) |
|
|
76
83
|
| Token values | [`tokens/pathway-design-tokens.json`](tokens/pathway-design-tokens.json) (source-of-truth, synced from Figma) |
|
|
77
|
-
| Built CSS consumed by Storybook | [`src/tokens
|
|
84
|
+
| Built CSS consumed by Storybook | the eight files in [`src/tokens/`](src/tokens/) (auto-regenerated by Style Dictionary) |
|
|
78
85
|
| Component geometry, tokens, accessibility rules | [`components/<name>/<name>-spec.md`](components/) |
|
|
79
86
|
| Component reference implementation | [`components/<name>/<name>.html`](components/) |
|
|
80
87
|
| How a component appears in Storybook | [`src/stories/Library/<Name>/`](src/stories/Library/) |
|
|
@@ -87,7 +94,7 @@ Three workflows in `.github/workflows/`:
|
|
|
87
94
|
|
|
88
95
|
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`.
|
|
89
96
|
2. **`sync-component.yml`** — fires when `components/sidenav/sidenav.html` changes. Regenerates the `sidenav-figmamake.html` variant and commits it.
|
|
90
|
-
3. **`deploy-
|
|
97
|
+
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.)
|
|
91
98
|
|
|
92
99
|
Source of truth is always the repo on `main`; Storybook is a downstream artifact.
|
|
93
100
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrollable — Pathway Design System
|
|
3
|
+
*
|
|
4
|
+
* A reusable overlay-scrollbar wrapper. Hides the native OS scrollbar entirely and renders
|
|
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).
|
|
7
|
+
*
|
|
8
|
+
* Behaviour (see components/scrollbar/scrollbar-spec.md):
|
|
9
|
+
* - Thumb height is PROPORTIONAL to content: (viewport / total) × track, floored at 28px.
|
|
10
|
+
* - Reveal: appears while scrolling, fades ~900ms after; also appears when the MOUSE is near
|
|
11
|
+
* the right-edge bar (so it can be grabbed). It does NOT appear from hovering the panel.
|
|
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
|
|
14
|
+
* strip is disabled on touch devices via a `(hover: none)` rule.
|
|
15
|
+
* - Keyboard / a11y: the content scrolls natively (arrows, PageUp/Down, Home/End, Space); the
|
|
16
|
+
* thumb is decorative (`aria-hidden`) and never the only scroll mechanism.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* <Scrollable style={{ flex: 1, minHeight: 0 }}>…tall content…</Scrollable>
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import React, { useRef, useState, useEffect, useCallback } from "react";
|
|
23
|
+
|
|
24
|
+
// ─── TOKENS ──────────────────────────────────────────────────────────────────
|
|
25
|
+
// Every colour + unit resolves through a SEMANTIC Pathway token — no primitives,
|
|
26
|
+
// no hardcoded hex/px. Motion uses the --motion-* tokens (durations/easings from design-system-spec §2).
|
|
27
|
+
export const SCROLL = {
|
|
28
|
+
thumbWidth: "var(--semantic-layout-units-padding-xtight)", // 6px — visible thumb thickness
|
|
29
|
+
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)
|
|
33
|
+
thumbRest: "var(--semantic-color-scrim-faint)", // black 16% — faint overlay (rest)
|
|
34
|
+
thumbHover: "var(--semantic-color-scrim-light)", // black 30% — hover/drag
|
|
35
|
+
thumbBlur: "blur(8px) saturate(180%)",
|
|
36
|
+
// Hairline glass edge: semantic white at 35% via color-mix.
|
|
37
|
+
thumbEdge: "inset 0 0 0 0.5px color-mix(in srgb, var(--semantic-color-fill-static-neutral-faint) 35%, transparent)",
|
|
38
|
+
// Asymmetric fade, motion-token driven: snappy appear (200ms decelerate glide-in), graceful fade-out (380ms).
|
|
39
|
+
fadeIn: "opacity var(--motion-duration-3) var(--motion-easing-decelerate), background var(--motion-duration-3) var(--motion-easing-standard)",
|
|
40
|
+
fadeOut: "opacity var(--motion-duration-5) var(--motion-easing-standard)",
|
|
41
|
+
idleHideMs: 500, // hide the thumb this long after scrolling stops / mouse leaves the bar
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Injected once: hide the native bar on the opt-in class, and disable the mouse grab strip on
|
|
45
|
+
// touch devices so finger-swipes scroll the content underneath (the thumb stays a passive indicator).
|
|
46
|
+
if (typeof document !== "undefined" && !document.getElementById("pds-scrollable-base")) {
|
|
47
|
+
const s = document.createElement("style");
|
|
48
|
+
s.id = "pds-scrollable-base";
|
|
49
|
+
s.textContent =
|
|
50
|
+
".pds-scrollable__view{scrollbar-width:none;-ms-overflow-style:none}" +
|
|
51
|
+
".pds-scrollable__view::-webkit-scrollbar{width:0;height:0;display:none}" +
|
|
52
|
+
"@media (hover: none){.pds-scrollable__grab{pointer-events:none!important}}";
|
|
53
|
+
document.head.appendChild(s);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function Scrollable({ children, className = "", style = {}, viewClassName = "", viewStyle = {} }) {
|
|
57
|
+
const viewRef = useRef(null);
|
|
58
|
+
const dragRef = useRef(null);
|
|
59
|
+
const idleRef = useRef(null);
|
|
60
|
+
const nearRef = useRef(false); // mouse currently near the right-edge bar
|
|
61
|
+
const [thumb, setThumb] = useState({ h: 0, top: 0, show: false });
|
|
62
|
+
const [active, setActive] = useState(false); // thumb visible (scrolling, near-bar, or dragging)
|
|
63
|
+
const [hot, setHot] = useState(false); // mouse over / dragging the grab strip
|
|
64
|
+
|
|
65
|
+
// Thumb height is PROPORTIONAL to content; floored at thumbMin. Position maps the floored
|
|
66
|
+
// thumb across the full track so it still reaches top and bottom exactly.
|
|
67
|
+
const recompute = useCallback(() => {
|
|
68
|
+
const el = viewRef.current;
|
|
69
|
+
if (!el) return;
|
|
70
|
+
const { scrollHeight, clientHeight, scrollTop } = el;
|
|
71
|
+
if (scrollHeight <= clientHeight + 1) { setThumb(t => (t.show ? { ...t, show: false } : t)); return; }
|
|
72
|
+
const h = Math.max(SCROLL.thumbMin, (clientHeight / scrollHeight) * clientHeight);
|
|
73
|
+
const top = (scrollTop / (scrollHeight - clientHeight)) * (clientHeight - h);
|
|
74
|
+
setThumb({ h, top, show: true });
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
recompute();
|
|
79
|
+
const el = viewRef.current;
|
|
80
|
+
if (!el || typeof ResizeObserver === "undefined") return;
|
|
81
|
+
const ro = new ResizeObserver(recompute);
|
|
82
|
+
ro.observe(el);
|
|
83
|
+
Array.from(el.children).forEach(c => ro.observe(c));
|
|
84
|
+
return () => ro.disconnect();
|
|
85
|
+
}, [children, recompute]);
|
|
86
|
+
|
|
87
|
+
const scheduleHide = () => {
|
|
88
|
+
clearTimeout(idleRef.current);
|
|
89
|
+
idleRef.current = setTimeout(() => {
|
|
90
|
+
if (!dragRef.current && !nearRef.current) setActive(false);
|
|
91
|
+
}, SCROLL.idleHideMs);
|
|
92
|
+
};
|
|
93
|
+
const wake = () => { setActive(true); scheduleHide(); }; // reveal while scrolling
|
|
94
|
+
const onScroll = () => { recompute(); wake(); };
|
|
95
|
+
|
|
96
|
+
// Edge-proximity reveal (mouse): show the bar when the cursor is within grabZone of the right
|
|
97
|
+
// edge, so it can be grabbed — but NOT from hovering the rest of the panel.
|
|
98
|
+
const onMouseMove = (e) => {
|
|
99
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
100
|
+
if (rect.right - e.clientX <= SCROLL.grabZone) {
|
|
101
|
+
nearRef.current = true; setActive(true); clearTimeout(idleRef.current);
|
|
102
|
+
} else if (nearRef.current) {
|
|
103
|
+
nearRef.current = false; scheduleHide();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const onMouseLeave = () => { nearRef.current = false; if (!dragRef.current) scheduleHide(); };
|
|
107
|
+
|
|
108
|
+
// Drag (mouse only). The grab strip carries this; the visual thumb is decorative.
|
|
109
|
+
const onGrabDown = (e) => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
const el = viewRef.current;
|
|
112
|
+
if (!el) return;
|
|
113
|
+
dragRef.current = { startY: e.clientY, startTop: el.scrollTop };
|
|
114
|
+
setHot(true); setActive(true); clearTimeout(idleRef.current);
|
|
115
|
+
const onMove = (ev) => {
|
|
116
|
+
const { scrollHeight, clientHeight } = el;
|
|
117
|
+
const h = Math.max(SCROLL.thumbMin, (clientHeight / scrollHeight) * clientHeight);
|
|
118
|
+
const ratio = (scrollHeight - clientHeight) / (clientHeight - h);
|
|
119
|
+
el.scrollTop = dragRef.current.startTop + (ev.clientY - dragRef.current.startY) * ratio;
|
|
120
|
+
};
|
|
121
|
+
const onUp = () => {
|
|
122
|
+
dragRef.current = null; setHot(false);
|
|
123
|
+
document.removeEventListener("mousemove", onMove);
|
|
124
|
+
document.removeEventListener("mouseup", onUp);
|
|
125
|
+
wake();
|
|
126
|
+
};
|
|
127
|
+
document.addEventListener("mousemove", onMove);
|
|
128
|
+
document.addEventListener("mouseup", onUp);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const barTop = thumb.top, barH = thumb.h;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
className={`pds-scrollable ${className}`}
|
|
136
|
+
style={{ position: "relative", minHeight: 0, ...style }}
|
|
137
|
+
onMouseMove={onMouseMove}
|
|
138
|
+
onMouseLeave={onMouseLeave}
|
|
139
|
+
>
|
|
140
|
+
<div
|
|
141
|
+
ref={viewRef}
|
|
142
|
+
className={`pds-scrollable__view ${viewClassName}`}
|
|
143
|
+
onScroll={onScroll}
|
|
144
|
+
style={{ height: "100%", overflowY: "auto", overflowX: "hidden", ...viewStyle }}
|
|
145
|
+
>
|
|
146
|
+
{children}
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{thumb.show && (
|
|
150
|
+
<>
|
|
151
|
+
{/* Visual thumb — decorative, never intercepts pointer events. */}
|
|
152
|
+
<div
|
|
153
|
+
aria-hidden="true"
|
|
154
|
+
style={{
|
|
155
|
+
position: "absolute", top: barTop, right: SCROLL.gutter,
|
|
156
|
+
width: SCROLL.thumbWidth, height: barH, borderRadius: SCROLL.thumbRadius,
|
|
157
|
+
background: hot ? SCROLL.thumbHover : SCROLL.thumbRest,
|
|
158
|
+
backdropFilter: SCROLL.thumbBlur, WebkitBackdropFilter: SCROLL.thumbBlur,
|
|
159
|
+
boxShadow: SCROLL.thumbEdge,
|
|
160
|
+
opacity: active ? 1 : 0, transition: active ? SCROLL.fadeIn : SCROLL.fadeOut,
|
|
161
|
+
pointerEvents: "none", zIndex: 5,
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
{/* Invisible mouse grab strip — wider than the thumb so it's catchable; disabled on
|
|
165
|
+
touch via the (hover:none) rule so finger-swipes scroll the content beneath. */}
|
|
166
|
+
<div
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
className="pds-scrollable__grab"
|
|
169
|
+
onMouseDown={onGrabDown}
|
|
170
|
+
onMouseEnter={() => setHot(true)}
|
|
171
|
+
onMouseLeave={() => setHot(false)}
|
|
172
|
+
style={{
|
|
173
|
+
position: "absolute", top: barTop, right: 0,
|
|
174
|
+
width: SCROLL.grabZone, height: barH,
|
|
175
|
+
pointerEvents: active ? "auto" : "none", zIndex: 6, background: "transparent",
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
</>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export default Scrollable;
|