Files
multica/packages/views/layout/navigation-progress.tsx
Naiyuan Qing 1cb926d52d feat(views): refine navigation progress bar with brand color and glow (MUL-2269) (#2681)
* feat(views): refine navigation progress bar with brand color and glow (MUL-2269)

The previous 1px bg-primary bar read as near-black on light theme and
snapped on/off in a single frame, which felt abrupt despite being a small
visual element. Switch to a 2px brand-colored sweep with right-edge glow,
slower 1.4s cubic-bezier easing, and a 200ms fade-out so completion
doesn't pop.

- Container: h-px → h-0.5 (2px); always mounted with opacity-driven fade
- Bar: bg-primary → bg-brand + two-layer box-shadow glow via color-mix
- Keyframe: 1.1s ease-in-out → 1.4s cubic-bezier(0.4, 0, 0.2, 1)

Zero new design tokens (reuses existing --brand) and zero tailwind config
changes. Desktop unaffected — same component, same prefetch=no-op path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>

* fix(views): unmount nav progress sweep when hidden (MUL-2269)

Hiding the bar with opacity-0 left the inner element's `infinite` keyframe
animation running on every dashboard page, defeating the perceived-perf goal.
Mount the sweep only while navigating, plus the 200ms fade tail (unmount on
opacity transitionend), so nothing animates while hidden.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-15 17:39:28 +08:00

46 lines
1.5 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useIsNavigating } from "../navigation";
// 2px top-of-content progress bar shown while a transition-wrapped
// push/replace is mid-flight. Indeterminate by design — we don't know
// when the next route will commit, just that it's coming.
//
// The container stays mounted so it can fade out over 200ms instead of
// vanishing in one frame. The inner sweep is mounted only while navigating
// (plus the fade-out tail); leaving its `infinite` keyframe animation
// running while hidden would burn paints on every dashboard view.
export function NavigationProgress() {
const isNavigating = useIsNavigating();
const [renderSweep, setRenderSweep] = useState(false);
useEffect(() => {
if (isNavigating) setRenderSweep(true);
}, [isNavigating]);
return (
<div
aria-hidden
data-visible={isNavigating ? "true" : "false"}
onTransitionEnd={(event) => {
if (event.propertyName === "opacity" && !isNavigating) {
setRenderSweep(false);
}
}}
className="pointer-events-none absolute inset-x-0 top-0 z-50 h-0.5 overflow-hidden opacity-0 transition-opacity duration-200 data-[visible=true]:opacity-100"
>
{renderSweep && (
<div
className="h-full w-1/3 animate-nav-progress-sweep bg-brand"
style={{
boxShadow:
"0 0 8px color-mix(in oklab, var(--brand) 60%, transparent), 0 0 2px color-mix(in oklab, var(--brand) 80%, transparent)",
}}
/>
)}
</div>
);
}