Skip to content

Commit 0d62bdb

Browse files
committed
feat(webapp): fade the side menu scrollbar in on hover
The scroll body's thumb is transparent until the pointer is inside the scroll area, then fades in (200ms) and back out on leave. It is styled via ::-webkit-scrollbar (not scrollbar-width) so Chrome keeps a classic, always-present bar to reveal instead of the auto-hiding overlay, and the thumb color is a registered @Property so it can transition. The thumb is a thin rounded pill (transparent border + padding-box clip) inset within an invisible reserved channel, so there is no layout shift.
1 parent bf08228 commit 0d62bdb

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,10 @@ const DRAG_CLICK_THRESHOLD = 4;
197197
const SIDE_MENU_PAD_X = `calc(0.625rem - 0.375rem * var(--sm-collapse, 0))`;
198198
/**
199199
* Scroll-body right padding DURING a transition (settled-open uses the reserved gutter instead).
200-
* Interpolates from the measured gutter width (seamless handoff) to 4px collapsed; the 12px fallback
200+
* Interpolates from the measured gutter width (seamless handoff) to 4px collapsed; the 8px fallback
201201
* is only for the first paint before `--sm-sb-gutter` is measured.
202202
*/
203-
const SIDE_MENU_SCROLL_PAD_RIGHT = `calc(var(--sm-sb-gutter, 12px) - (var(--sm-sb-gutter, 12px) - 0.25rem) * var(--sm-collapse, 0))`;
203+
const SIDE_MENU_SCROLL_PAD_RIGHT = `calc(var(--sm-sb-gutter, 8px) - (var(--sm-sb-gutter, 8px) - 0.25rem) * var(--sm-collapse, 0))`;
204204
/**
205205
* Hover chevron: its 16px width follows --sm-label-opacity so an invisible chevron never holds width
206206
* mid-drag and pushes the row's clip edge into the icon. Opacity stays class-driven (hover-only).
@@ -431,7 +431,7 @@ export function SideMenu({
431431
const el = rootRef.current;
432432
if (!el) return;
433433
const probe = document.createElement("div");
434-
probe.className = "scrollbar-gutter-stable scrollbar-thin";
434+
probe.className = "scrollbar-gutter-stable scrollbar-thumb-on-hover";
435435
probe.style.cssText =
436436
"position:absolute;top:-9999px;left:-9999px;width:100px;height:100px;overflow-y:auto;visibility:hidden;";
437437
document.body.appendChild(probe);
@@ -736,7 +736,7 @@ export function SideMenu({
736736
// Reserve the gutter only when settled open; during transitions it's dropped so the
737737
// right padding below can animate the spacing seamlessly (see SIDE_MENU_SCROLL_PAD_RIGHT).
738738
showReservedGutter
739-
? "scrollbar-gutter-stable scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control"
739+
? "scrollbar-gutter-stable scrollbar-thumb-on-hover"
740740
: "scrollbar-none"
741741
)}
742742
>

apps/webapp/app/tailwind.css

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,44 @@
353353
scrollbar-gutter: stable;
354354
}
355355

356+
/* Fade the thumb in only while the pointer is in the scroll area. Uses
357+
::-webkit-scrollbar (no scrollbar-width) so Chrome keeps a classic always-present
358+
bar to reveal, not the auto-hiding overlay; Firefox falls back to standard props.
359+
Thumb color is a registered @property so it can transition. */
360+
@property --sm-sb-thumb {
361+
syntax: "<color>";
362+
initial-value: transparent;
363+
inherits: true;
364+
}
365+
366+
@utility scrollbar-thumb-on-hover {
367+
transition: --sm-sb-thumb 200ms ease;
368+
369+
&::-webkit-scrollbar {
370+
width: 8px;
371+
height: 8px;
372+
}
373+
&::-webkit-scrollbar-track {
374+
background: transparent;
375+
}
376+
&::-webkit-scrollbar-thumb {
377+
background-color: var(--sm-sb-thumb);
378+
/* transparent border + padding-box clip = thinner thumb, inset from the channel edges */
379+
border: 2px solid transparent;
380+
border-radius: 9999px;
381+
background-clip: padding-box;
382+
}
383+
384+
@supports (-moz-appearance: none) {
385+
scrollbar-width: thin;
386+
scrollbar-color: var(--sm-sb-thumb) transparent;
387+
}
388+
389+
&:hover {
390+
--sm-sb-thumb: var(--color-surface-control);
391+
}
392+
}
393+
356394
/* Shared stop list for the animated glow utilities below. The gradient itself
357395
is assembled per-element so the animated --gradient-angle resolves there. */
358396
:root {

0 commit comments

Comments
 (0)