swarm-ui: fix underline not showing on the initial page load
Root cause (found via a zoomed pixel-level screenshot check, not guessed): the indicator span always exists in the DOM starting from left:0/width:0/transparent, and the very first real position landed via a CSS-transitioned change from that fallback rather than a snap — so the underline visibly grew in from nothing over 140ms instead of being there immediately, reading as entirely absent on a fast/slow first paint alike depending on timing. Every subsequent navigation was unaffected (always transitioning between two already-visible states). Fixed with a one-render-only transition suppression (indicatorSettledOnce, gated one tick behind the indicator's first non-null commit) so the first placement snaps instead of animating in, while every later hop still animates normally. Verified against the exact repro: a fresh page load at the same short virtual-time-budget that previously showed no underline now shows it immediately.
This commit is contained in:
parent
daabb4fe6a
commit
6d9a2fa9f6
2 changed files with 29 additions and 1 deletions
|
|
@ -128,6 +128,16 @@
|
|||
width 140ms ease,
|
||||
background-color 140ms ease;
|
||||
}
|
||||
/* Suppresses the transition for exactly one render — the indicator's
|
||||
very first real position, so it snaps into place instead of visibly
|
||||
growing from the unmounted `left:0/width:0/transparent` fallback (see
|
||||
Shell.tsx's `indicatorSettledOnce`). Two-class selector so it wins
|
||||
over the plain `.shell-nav-indicator` rule above without `!important`,
|
||||
same reasoning `:root[data-motion='reduce']`'s extra attribute
|
||||
selector already relies on. */
|
||||
.shell-nav-indicator.shell-nav-indicator-instant {
|
||||
transition: none;
|
||||
}
|
||||
.shell-body {
|
||||
max-width: 60em;
|
||||
margin: 0 auto;
|
||||
|
|
|
|||
|
|
@ -114,6 +114,14 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
|||
// agree once every intermediate hop has landed. `-1` = not settled
|
||||
// anywhere yet (first mount) or on a route with no nav item (404).
|
||||
const settledIndexRef = useRef(-1);
|
||||
// False until the indicator's very first real position has committed —
|
||||
// gates `.shell-nav-indicator-instant` (see Shell.css) so that first
|
||||
// placement snaps instead of *transitioning in* from the unmounted
|
||||
// `left:0/width:0/transparent` fallback, which read as "no underline at
|
||||
// all" on a fresh page load (reported live by mara, reproduced here with
|
||||
// a zoomed pixel-level screenshot check — real, not a rendering fluke).
|
||||
// Every subsequent change (a real navigation) still animates normally.
|
||||
const [indicatorSettledOnce, setIndicatorSettledOnce] = useState(false);
|
||||
|
||||
// Applied once here, not inside `SettingsMenu` — every route mounts
|
||||
// through this one `<Shell>`, so the override takes effect regardless
|
||||
|
|
@ -185,6 +193,16 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
|||
};
|
||||
}, [location]);
|
||||
|
||||
// Fires the render *after* the indicator's first non-null commit —
|
||||
// deliberately one tick behind, not folded into the effect above,
|
||||
// because the instant/animated class has to still read "instant" on
|
||||
// the very render where `indicator` itself first goes non-null (that's
|
||||
// the snap this exists for); only from the render after that should
|
||||
// transitions be armed.
|
||||
useEffect(() => {
|
||||
if (indicator && !indicatorSettledOnce) setIndicatorSettledOnce(true);
|
||||
}, [indicator, indicatorSettledOnce]);
|
||||
|
||||
// Re-measures the current (already-settled) position on resize — the
|
||||
// nav's own `flex-wrap` means a link's position genuinely changes at
|
||||
// narrow widths, not just its route. Not part of the hop effect above
|
||||
|
|
@ -249,7 +267,7 @@ export function Shell({ children }: { children: ComponentChildren }) {
|
|||
nav item (404), so it doesn't pop in with a stale position
|
||||
the next time a real nav item becomes active. */}
|
||||
<span
|
||||
class="shell-nav-indicator"
|
||||
class={'shell-nav-indicator' + (indicatorSettledOnce ? '' : ' shell-nav-indicator-instant')}
|
||||
style={{
|
||||
left: `${indicator?.left ?? 0}px`,
|
||||
top: `${indicator?.top ?? 0}px`,
|
||||
|
|
|
|||
Loading…
Reference in a new issue