@leta-io/design-tokens 0.1.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 +30 -0
- package/dist/dark-elevation.css +90 -0
- package/dist/fonts.d.ts +2 -0
- package/dist/fonts.js +2 -0
- package/dist/index.d.ts +2176 -0
- package/dist/index.js +2162 -0
- package/dist/map-elevation.css +148 -0
- package/dist/scroll-utilities.css +258 -0
- package/dist/tailwind.preset.cjs +795 -0
- package/dist/text-styles.css +349 -0
- package/dist/tokens.css +1946 -0
- package/package.json +52 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map Elevation — LETA Design System
|
|
3
|
+
* @leta-io/design-tokens/map-elevation.css
|
|
4
|
+
*
|
|
5
|
+
* ─── Why this exists ───────────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* A shadow does not communicate elevation on its own. It communicates elevation
|
|
8
|
+
* by CONTRASTING with whatever sits behind it. Every shadow in the Figma library
|
|
9
|
+
* — Neutral Drop Shadow 1/2/3 — was tuned against the app's own near-white page
|
|
10
|
+
* surfaces, where a soft 10% black reads clearly.
|
|
11
|
+
*
|
|
12
|
+
* A map is a different substrate entirely: mid-tone, saturated, and visually
|
|
13
|
+
* busy (roads, parks, water, labels). Against it those shadows disappear, and
|
|
14
|
+
* white overlays read as flat shapes pasted onto the tiles rather than floating
|
|
15
|
+
* above them. Verified directly in the app — with Neutral Drop Shadow 2 applied
|
|
16
|
+
* exactly as Figma specifies, the depot/recipient cards, zoom control and
|
|
17
|
+
* collapse button all still read flat.
|
|
18
|
+
*
|
|
19
|
+
* Compounding it, two of Neutral Drop Shadow 2's three layers use `spread: -8`
|
|
20
|
+
* against `blur: 8`, which places them inside the element's own edge — invisible
|
|
21
|
+
* behind an opaque surface. So it effectively renders as one soft 10% layer.
|
|
22
|
+
* Neutral Drop Shadow 3 is stronger but its `spread: -4/-6` eats most of it too.
|
|
23
|
+
*
|
|
24
|
+
* So map overlays get their own elevation, built the way map products (Google,
|
|
25
|
+
* Mapbox, Apple) build theirs:
|
|
26
|
+
*
|
|
27
|
+
* 1. a **hairline ring** — guarantees a crisp edge no matter what colour the
|
|
28
|
+
* tile underneath happens to be; a shadow alone can never promise contrast
|
|
29
|
+
* against arbitrary imagery
|
|
30
|
+
* 2. a **contact shadow** — tight and relatively opaque, anchoring the element
|
|
31
|
+
* 3. an **ambient shadow** — wider and softer, giving the actual sense of lift
|
|
32
|
+
*
|
|
33
|
+
* ─── This is an addition to the system, not an override of it ──────────────
|
|
34
|
+
*
|
|
35
|
+
* Neutral Drop Shadow 1/2/3 remain correct and unchanged for everything on a
|
|
36
|
+
* page surface. This is a fourth, substrate-specific level. It should be added
|
|
37
|
+
* to the Figma library as an effect style ("Map Overlay Shadow") so design and
|
|
38
|
+
* code stay in sync — until then, Figma will show map overlays a step flatter
|
|
39
|
+
* than the build.
|
|
40
|
+
*
|
|
41
|
+
* ─── Usage ─────────────────────────────────────────────────────────────────
|
|
42
|
+
*
|
|
43
|
+
* import '@leta-io/design-tokens/map-elevation.css';
|
|
44
|
+
*
|
|
45
|
+
* // Any element floating on a map: cards, banners, legend, zoom control,
|
|
46
|
+
* // expand/collapse buttons, popovers, notification banners.
|
|
47
|
+
* style={{ boxShadow: 'var(--shadow-map-overlay)' }}
|
|
48
|
+
*
|
|
49
|
+
* Pins are the exception: a pin is a teardrop, not a rectangle, and `box-shadow`
|
|
50
|
+
* traces the element's BOX — it would draw a rectangular shadow around a pointed
|
|
51
|
+
* shape. Pins use `filter: drop-shadow()`, which follows the shape's alpha.
|
|
52
|
+
* `MapIcon` carries that equivalent internally (`filter` takes no custom
|
|
53
|
+
* properties reliably across engines, and it has no `spread` parameter, so the
|
|
54
|
+
* values are folded rather than copied).
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
:root {
|
|
58
|
+
/**
|
|
59
|
+
* Elevation for anything floating on a map surface.
|
|
60
|
+
* Ring + contact + ambient — see the note above for why all three.
|
|
61
|
+
*/
|
|
62
|
+
--shadow-map-overlay:
|
|
63
|
+
0 0 0 1px rgb(16 16 16 / 0.06),
|
|
64
|
+
0 1px 3px rgb(16 16 16 / 0.22),
|
|
65
|
+
0 6px 16px -4px rgb(16 16 16 / 0.28);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* ─── Map pins do not follow the app theme ──────────────────────────────
|
|
69
|
+
*
|
|
70
|
+
* A pin sits on **map imagery**, not on the app's page surface. That is the same
|
|
71
|
+
* argument this file already makes for shadows, and it applies just as directly
|
|
72
|
+
* to the pin's own colours: the point of the white chassis is to guarantee the
|
|
73
|
+
* marker reads against arbitrary cartography, and the point of the coloured inner
|
|
74
|
+
* is to carry a status colour. Neither job changes because the app went dark.
|
|
75
|
+
*
|
|
76
|
+
* They were bound to app-surface semantic tokens, so in dark mode every pin turned
|
|
77
|
+
* inside out — measured 2026-08-26: chassis `#fefefe` → `#0d0d0d`, inner navy
|
|
78
|
+
* `#192037` → `#f9f9fa`, content `#fafafa` → `#101010`. One root cause behind three
|
|
79
|
+
* separate reports:
|
|
80
|
+
*
|
|
81
|
+
* • "the pin colours look desaturated" — they had inverted, not desaturated.
|
|
82
|
+
* `--surface-primary-bg` goes `#ff3941` → `#ffd7d9`: vivid red to pale pink.
|
|
83
|
+
* • "the pins have no drop shadow and don't stand out" — a near-black pin with a
|
|
84
|
+
* black shadow on a dark map.
|
|
85
|
+
* • "why do these pins have a dark blur behind them" — the same black shadow,
|
|
86
|
+
* now visible as a smudge wherever the map underneath was lighter.
|
|
87
|
+
*
|
|
88
|
+
* These are drawn from the **primitive** `--colors-*` layer, which is stable across
|
|
89
|
+
* themes (verified), so there is deliberately no `[data-theme="dark"]` block for
|
|
90
|
+
* them. A dark shadow under a light chassis then means what it is supposed to mean.
|
|
91
|
+
*/
|
|
92
|
+
--map-pin-body: var(--colors-neutral-white);
|
|
93
|
+
--map-pin-edge: var(--colors-neutral-200);
|
|
94
|
+
--map-pin-content: var(--colors-neutral-50);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Status colours for a pin's inner shape. Callers pass one of these rather than an
|
|
98
|
+
* app-surface token, for the reason above — a pin's red has to stay red on a dark
|
|
99
|
+
* map, and `--surface-primary-bg` does not.
|
|
100
|
+
*/
|
|
101
|
+
--map-pin-primary: var(--colors-primary-default);
|
|
102
|
+
--map-pin-secondary: var(--colors-secondary-default);
|
|
103
|
+
--map-pin-error: var(--colors-error-default);
|
|
104
|
+
--map-pin-success: var(--colors-success-default);
|
|
105
|
+
--map-pin-warning: var(--colors-warning-default);
|
|
106
|
+
--map-pin-neutral: var(--colors-neutral-default);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Pin / glyph filter — the `filter: drop-shadow()` equivalent of the above, for
|
|
110
|
+
* shapes rather than boxes (see the note on pins). Consumed by `MapIcon`.
|
|
111
|
+
*
|
|
112
|
+
* A chain rather than a single shadow, because dark mode adds a colour step to it
|
|
113
|
+
* (below). Light mode is shadow only: on light tiles a dark shadow both grounds the
|
|
114
|
+
* pin and gives its edge contrast, and the pin's own colour is already vivid
|
|
115
|
+
* against pale cartography.
|
|
116
|
+
*/
|
|
117
|
+
--map-glyph-filter:
|
|
118
|
+
drop-shadow(0 1px 2px rgb(16 16 16 / 0.30))
|
|
119
|
+
drop-shadow(0 4px 8px rgb(16 16 16 / 0.22));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* ─── Dark basemap: the shadow gets deeper, the colours do not move ─────────
|
|
124
|
+
*
|
|
125
|
+
* With the chassis staying light (above), a dark shadow means what it is supposed
|
|
126
|
+
* to mean again — a light object casting onto a dark ground — so this is a tuning
|
|
127
|
+
* step, not a different device. Pure black, because the light-mode value is mixed
|
|
128
|
+
* toward the page ink and reads as nothing on a dark map.
|
|
129
|
+
*
|
|
130
|
+
* **Tighter than light mode, not heavier.** The instinct is to strengthen a shadow
|
|
131
|
+
* that is failing to show; on a dark ground that produces a soft black cloud around
|
|
132
|
+
* an already-dark pin, which reads as a smudge rather than as lift — reported as
|
|
133
|
+
* "why do these pins have a dark blur behind them". So the contact layer stays close
|
|
134
|
+
* (1px blur) and the ambient one is small and faint (4px at 32%). Elevation on a
|
|
135
|
+
* dark map comes from the pin's light chassis; the shadow only has to seat it.
|
|
136
|
+
*
|
|
137
|
+
* Note what is deliberately NOT here: no halo, and no `saturate()`. Both were tried
|
|
138
|
+
* and removed (2026-08-26). The halo ringed the glyph in light and changed what the
|
|
139
|
+
* marker was; the saturation was compensating for a colour that had *inverted*
|
|
140
|
+
* rather than faded, so it could not have worked — a near-white inner does not get
|
|
141
|
+
* more colourful when you saturate it. Fixing the inversion at the source removed
|
|
142
|
+
* the need for either.
|
|
143
|
+
*/
|
|
144
|
+
:root[data-theme="dark"] {
|
|
145
|
+
--map-glyph-filter:
|
|
146
|
+
drop-shadow(0 1px 1px rgb(0 0 0 / 0.55))
|
|
147
|
+
drop-shadow(0 2px 4px rgb(0 0 0 / 0.32));
|
|
148
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scroll Utilities — LETA Design System
|
|
3
|
+
* @leta-io/design-tokens/scroll-utilities.css
|
|
4
|
+
*
|
|
5
|
+
* Scroll behaviour + scrollbar appearance utilities.
|
|
6
|
+
* Import alongside tokens.css and text-styles.css wherever the design system
|
|
7
|
+
* is consumed (Storybook, Playground, host applications).
|
|
8
|
+
*
|
|
9
|
+
* Three independent concerns live here:
|
|
10
|
+
* 1. Scroll CHAINING — .scroll-isolated / .scroll-rigid (overscroll-behavior)
|
|
11
|
+
* 2. Scrollbar LOOK — .scroll-slim / .scroll-hidden (see the note below)
|
|
12
|
+
* 3. SCROLL EDGES — --scroll-edge-* tokens (see the note below)
|
|
13
|
+
*
|
|
14
|
+
* ─── Rule reference ────────────────────────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* .scroll-isolated (overscroll-behavior: contain)
|
|
17
|
+
* Use on all NESTED / INTERNAL scroll containers: dropdown option lists,
|
|
18
|
+
* modal body panels, sidebar navigation, search-results panels, date-picker
|
|
19
|
+
* lists, mobile dropdown panels, and configuration form columns.
|
|
20
|
+
* Effect: the browser stops at the element boundary — no scroll chain to
|
|
21
|
+
* the parent and no pull-to-refresh trigger when the user reaches the top
|
|
22
|
+
* or bottom of the inner list.
|
|
23
|
+
*
|
|
24
|
+
* .scroll-rigid (overscroll-behavior: none)
|
|
25
|
+
* Use on DATA TABLE bodies and precision SNAP-SCROLL surfaces (time wheels).
|
|
26
|
+
* Effect: zero overscroll bounce / rubber-band on any axis. Prevents
|
|
27
|
+
* distracting UI shifts when aligning tabular data or advancing a snap list.
|
|
28
|
+
*
|
|
29
|
+
* Root page / document scroll: leave untouched (native elastic bounce on iOS,
|
|
30
|
+
* standard desktop behaviour). Do NOT apply either class to <html> or <body>.
|
|
31
|
+
*
|
|
32
|
+
* ─── Usage in component code (inline-style equivalent) ────────────────────
|
|
33
|
+
*
|
|
34
|
+
* Nested list / panel → style={{ overscrollBehavior: 'contain' }}
|
|
35
|
+
* Data table / wheel → style={{ overscrollBehavior: 'none' }}
|
|
36
|
+
*
|
|
37
|
+
* ─── Usage in playground / host app (class-based) ─────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* import '@leta-io/design-tokens/scroll-utilities.css';
|
|
40
|
+
*
|
|
41
|
+
* // Internal panel
|
|
42
|
+
* <div className="scroll-isolated" style={{ overflowY: 'auto', maxHeight: 300 }}>
|
|
43
|
+
* {items}
|
|
44
|
+
* </div>
|
|
45
|
+
*
|
|
46
|
+
* // Data table viewport
|
|
47
|
+
* <div className="scroll-rigid" style={{ overflow: 'auto', maxHeight: 600 }}>
|
|
48
|
+
* <table>…</table>
|
|
49
|
+
* </div>
|
|
50
|
+
*
|
|
51
|
+
* ─── Already applied in design-system components ──────────────────────────
|
|
52
|
+
*
|
|
53
|
+
* contain: ModalShell (body) · SideBar (nav content) · DesktopDropdowns (all
|
|
54
|
+
* scroll lists + filter-group panes) · MapSearchInput (results panel) ·
|
|
55
|
+
* MobileDropdown (panel) · DateTimePicker (desktop time-list, mobile
|
|
56
|
+
* content) · Page (config column)
|
|
57
|
+
*
|
|
58
|
+
* none: Table (body — both fillHeight and maxBodyHeight modes, both-axes
|
|
59
|
+
* scrollX mode) · DateTimePicker WheelColumn (snap-scroll time wheel)
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/* ─── Scroll Edges ───────────────────────────────────────────────────────────
|
|
63
|
+
*
|
|
64
|
+
* **"Scroll Edge"** is the LETA name for the affordance that tells a user
|
|
65
|
+
* *there is more content past this edge*. One concept, two treatments — chosen by
|
|
66
|
+
* what sits at the edge, not by axis:
|
|
67
|
+
*
|
|
68
|
+
* · **Edge fade** — a soft gradient painted OVER the scrolling content itself,
|
|
69
|
+
* for when content slides under an opaque neighbour with no chrome between
|
|
70
|
+
* them. Used by the Table's pinned anchor columns (Order ID left / Actions
|
|
71
|
+
* right) on horizontal scroll.
|
|
72
|
+
*
|
|
73
|
+
* · **Edge shadow** — a drop shadow cast BY a piece of chrome onto the content
|
|
74
|
+
* scrolling beneath it, for when a fixed header or footer needs to read as
|
|
75
|
+
* separate from (and above) the scroll region. Used by ModalShell's header and
|
|
76
|
+
* footer, and by FooterFrame's `scrollShadow`.
|
|
77
|
+
*
|
|
78
|
+
* Both are driven by the same detection primitive — `useScrollEdges(ref)` in
|
|
79
|
+
* `@leta-io/components` — so "is there more past this edge?" is answered one way
|
|
80
|
+
* everywhere. Never hand-roll that check; the hook also catches the cases a naive
|
|
81
|
+
* implementation misses (content growing without the container resizing, and the
|
|
82
|
+
* scroll target being swapped out under a stable ref).
|
|
83
|
+
*
|
|
84
|
+
* ─── Why these are tokens and not literals ─────────────────────────────────
|
|
85
|
+
*
|
|
86
|
+
* A scroll edge is drawn in INK over a surface, so it must invert with the theme.
|
|
87
|
+
* The light palette's table/modal surface is `#fefefe` and the dark palette's is
|
|
88
|
+
* `#0d0d0d` — very nearly black. A dark-ink fade (the light-mode treatment) over
|
|
89
|
+
* `#0d0d0d` is mathematically invisible: there is almost no headroom left to
|
|
90
|
+
* darken. So dark mode flips to LIGHT ink, the same logic as the hairline in
|
|
91
|
+
* `dark-elevation.css` — on a dark surface, "lifted" reads as light, not darker.
|
|
92
|
+
*
|
|
93
|
+
* Deliberately NOT reusing `--shadow-neutral-*`: those are ELEVATION (an object
|
|
94
|
+
* floating above a page) and in dark mode they carry a full 1px hairline ring on
|
|
95
|
+
* all four sides. A scroll edge is a single-edge affordance — a ring around a
|
|
96
|
+
* modal header is wrong, and that is exactly what reusing the elevation token
|
|
97
|
+
* produced before this was split out.
|
|
98
|
+
*
|
|
99
|
+
* Consume as:
|
|
100
|
+
* background: linear-gradient(to right,
|
|
101
|
+
* rgb(var(--scroll-edge-ink) / var(--scroll-edge-fade-1)), …);
|
|
102
|
+
* box-shadow: var(--scroll-edge-shadow-down);
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
:root {
|
|
106
|
+
/** Ink the edge is drawn in — dark over light surfaces. Space-separated RGB
|
|
107
|
+
* channels so callers can apply their own alpha via `rgb(… / …)`. */
|
|
108
|
+
--scroll-edge-ink: 16 16 16;
|
|
109
|
+
/** Eased fade falloff (4 stops — a plain 2-stop gradient reads as a hard band). */
|
|
110
|
+
--scroll-edge-fade-1: 0.045;
|
|
111
|
+
--scroll-edge-fade-2: 0.028;
|
|
112
|
+
--scroll-edge-fade-3: 0.012;
|
|
113
|
+
/** Chrome edge shadows — `down` for a header above content, `up` for a footer below. */
|
|
114
|
+
--scroll-edge-shadow-down: 0 4px 10px -4px rgb(var(--scroll-edge-ink) / 0.1);
|
|
115
|
+
--scroll-edge-shadow-up: 0 -4px 10px -4px rgb(var(--scroll-edge-ink) / 0.1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Dark mode: light ink, and LOWER alphas than light mode.
|
|
119
|
+
*
|
|
120
|
+
* The ink flips because a near-black surface (`#0d0d0d`) gives a dark fade nowhere
|
|
121
|
+
* to go — the edge has to be drawn as a subtle lift rather than a shadow.
|
|
122
|
+
*
|
|
123
|
+
* The alphas come DOWN rather than up, which is the counter-intuitive part: light
|
|
124
|
+
* ink on near-black is far more perceptible than dark ink on near-white, because
|
|
125
|
+
* perception near black is logarithmic (a small absolute lift is a large relative
|
|
126
|
+
* change). Matching light mode's alphas here reads as a hard white band — an
|
|
127
|
+
* "intense glow", reported 2026-08-24 — so these sit at roughly half. Tune by eye
|
|
128
|
+
* against the Table's pinned columns and a scrolling modal footer, not by matching
|
|
129
|
+
* the light-mode numbers. */
|
|
130
|
+
[data-theme='dark'] {
|
|
131
|
+
--scroll-edge-ink: 255 255 255;
|
|
132
|
+
--scroll-edge-fade-1: 0.05;
|
|
133
|
+
--scroll-edge-fade-2: 0.028;
|
|
134
|
+
--scroll-edge-fade-3: 0.01;
|
|
135
|
+
--scroll-edge-shadow-down: 0 4px 10px -4px rgb(var(--scroll-edge-ink) / 0.06);
|
|
136
|
+
--scroll-edge-shadow-up: 0 -4px 10px -4px rgb(var(--scroll-edge-ink) / 0.06);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Isolate scroll chain — for nested lists, panels, and navigation drawers. */
|
|
140
|
+
.scroll-isolated {
|
|
141
|
+
overscroll-behavior: contain;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Rigid scroll — for data tables and snap-scroll precision surfaces. */
|
|
145
|
+
.scroll-rigid {
|
|
146
|
+
overscroll-behavior: none;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* ─── Scrollbar appearance ───────────────────────────────────────────────────
|
|
150
|
+
*
|
|
151
|
+
* WHAT IS AND ISN'T POSSIBLE, per platform:
|
|
152
|
+
*
|
|
153
|
+
* · macOS — the OS default is already an *overlay* scrollbar: invisible at
|
|
154
|
+
* rest, fading in while scrolling, taking zero layout width. Nothing
|
|
155
|
+
* needs fixing there, which is why this problem is usually invisible
|
|
156
|
+
* to a designer on a Mac and glaring to a user on Windows.
|
|
157
|
+
* (A Mac set to "Always show scroll bars" behaves like Windows, and
|
|
158
|
+
* these rules cover that too.)
|
|
159
|
+
* · Windows / Linux — classic scrollbars: always visible and, critically, they
|
|
160
|
+
* OCCUPY LAYOUT WIDTH (~15px), so a panel silently gets narrower and
|
|
161
|
+
* a chunky grey gutter sits against the design.
|
|
162
|
+
*
|
|
163
|
+
* Both engine families can be styled, so a consistent result IS achievable:
|
|
164
|
+
* · `scrollbar-width` / `scrollbar-color` — the standard properties. Firefox
|
|
165
|
+
* has had them for years; Chrome/Edge support them from 121. `thin` still
|
|
166
|
+
* reserves a (narrow) gutter, so layout stays stable and nothing overlaps.
|
|
167
|
+
* · `::-webkit-scrollbar*` — the older Blink/WebKit pseudo-elements, giving
|
|
168
|
+
* exact width, colour and radius. Kept alongside the standard properties
|
|
169
|
+
* because they win where both are supported and give a nicer thumb.
|
|
170
|
+
*
|
|
171
|
+
* Deliberately NOT doing `scrollbar-width: none` / `display: none` as a general
|
|
172
|
+
* rule: that removes the only cue that a region scrolls at all. Hiding is right
|
|
173
|
+
* only where another element already carries that signal — the Table hides its
|
|
174
|
+
* vertical bar because the pagination footer is the boundary, and the horizontal
|
|
175
|
+
* bar stays visible precisely because nothing else says "there is more to the
|
|
176
|
+
* right". Use `.scroll-hidden` for that narrow case, `.scroll-slim` by default.
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Slim, low-contrast scrollbar — the default for internal scroll containers
|
|
181
|
+
* (modal panels, dropdown lists, drawers, side panels).
|
|
182
|
+
*
|
|
183
|
+
* The thumb is a neutral token at low opacity that darkens on hover, so it reads
|
|
184
|
+
* as part of the surface rather than as OS chrome, and the track is transparent
|
|
185
|
+
* so it disappears against whatever it is sitting on.
|
|
186
|
+
*/
|
|
187
|
+
.scroll-slim {
|
|
188
|
+
scrollbar-width: thin;
|
|
189
|
+
scrollbar-color: var(--border-neutral-default) transparent;
|
|
190
|
+
}
|
|
191
|
+
.scroll-slim::-webkit-scrollbar {
|
|
192
|
+
width: 8px;
|
|
193
|
+
height: 8px;
|
|
194
|
+
}
|
|
195
|
+
.scroll-slim::-webkit-scrollbar-track {
|
|
196
|
+
background: transparent;
|
|
197
|
+
}
|
|
198
|
+
.scroll-slim::-webkit-scrollbar-thumb {
|
|
199
|
+
background-color: var(--border-neutral-default);
|
|
200
|
+
border-radius: var(--rounding-round);
|
|
201
|
+
/* Inset the thumb inside its 8px lane so it reads as a 4px pill with padding,
|
|
202
|
+
rather than a bar wedged against the content edge. */
|
|
203
|
+
border: 2px solid transparent;
|
|
204
|
+
background-clip: content-box;
|
|
205
|
+
}
|
|
206
|
+
.scroll-slim::-webkit-scrollbar-thumb:hover {
|
|
207
|
+
background-color: var(--icons-neutral-idle);
|
|
208
|
+
}
|
|
209
|
+
/* Stops the little square where the two bars meet from rendering as grey chrome. */
|
|
210
|
+
.scroll-slim::-webkit-scrollbar-corner {
|
|
211
|
+
background: transparent;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Fully hidden scrollbar — only where a *different* element already tells the
|
|
216
|
+
* user the region scrolls (see the note above). Scrolling itself is unaffected.
|
|
217
|
+
*/
|
|
218
|
+
.scroll-hidden {
|
|
219
|
+
scrollbar-width: none;
|
|
220
|
+
}
|
|
221
|
+
.scroll-hidden::-webkit-scrollbar {
|
|
222
|
+
width: 0;
|
|
223
|
+
height: 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* ─── Global slim scrollbars, gated on platform ──────────────────────────────
|
|
227
|
+
*
|
|
228
|
+
* Applied only when `applyScrollbarMode()` has detected CLASSIC scrollbars —
|
|
229
|
+
* i.e. Windows/Linux, where bars are always visible and eat layout width.
|
|
230
|
+
* macOS's overlay scrollbars are already invisible-at-rest and zero-width, and
|
|
231
|
+
* styling them would REGRESS that into a permanent gutter, so they are left
|
|
232
|
+
* alone. See `scrollbars.ts` for the full reasoning.
|
|
233
|
+
*
|
|
234
|
+
* Call once from the app entry: applyScrollbarMode()
|
|
235
|
+
*/
|
|
236
|
+
[data-scrollbars='classic'] * {
|
|
237
|
+
scrollbar-width: thin;
|
|
238
|
+
scrollbar-color: var(--border-neutral-default) transparent;
|
|
239
|
+
}
|
|
240
|
+
[data-scrollbars='classic'] *::-webkit-scrollbar {
|
|
241
|
+
width: 8px;
|
|
242
|
+
height: 8px;
|
|
243
|
+
}
|
|
244
|
+
[data-scrollbars='classic'] *::-webkit-scrollbar-track {
|
|
245
|
+
background: transparent;
|
|
246
|
+
}
|
|
247
|
+
[data-scrollbars='classic'] *::-webkit-scrollbar-thumb {
|
|
248
|
+
background-color: var(--border-neutral-default);
|
|
249
|
+
border-radius: var(--rounding-round);
|
|
250
|
+
border: 2px solid transparent;
|
|
251
|
+
background-clip: content-box;
|
|
252
|
+
}
|
|
253
|
+
[data-scrollbars='classic'] *::-webkit-scrollbar-thumb:hover {
|
|
254
|
+
background-color: var(--icons-neutral-idle);
|
|
255
|
+
}
|
|
256
|
+
[data-scrollbars='classic'] *::-webkit-scrollbar-corner {
|
|
257
|
+
background: transparent;
|
|
258
|
+
}
|