mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 03:38:32 +02:00
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>
This commit is contained in:
@@ -114,17 +114,18 @@
|
||||
animation: chat-text-shimmer 2.5s linear infinite;
|
||||
}
|
||||
|
||||
/* Navigation progress bar: 1px brand-tinted indeterminate sweep that shows
|
||||
* across the top of the dashboard while a transition-wrapped push/replace is
|
||||
* committing. Driven by useIsNavigating(); independent of the actual network,
|
||||
* so it disappears the moment React commits the new route. */
|
||||
/* Navigation progress bar: 2px brand-colored indeterminate sweep with a
|
||||
* right-edge glow that shows across the top of the dashboard while a
|
||||
* transition-wrapped push/replace is committing. Driven by useIsNavigating();
|
||||
* independent of the actual network, so it disappears the moment React commits
|
||||
* the new route. */
|
||||
@keyframes nav-progress-sweep {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
.animate-nav-progress-sweep {
|
||||
animation: nav-progress-sweep 1.1s ease-in-out infinite;
|
||||
animation: nav-progress-sweep 1.4s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
/* Border beam: a brand-tinted highlight sweeps continuously around the
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useIsNavigating } from "../navigation";
|
||||
|
||||
// 1px top-of-content progress bar shown while a transition-wrapped
|
||||
// 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();
|
||||
if (!isNavigating) return null;
|
||||
const [renderSweep, setRenderSweep] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNavigating) setRenderSweep(true);
|
||||
}, [isNavigating]);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 top-0 z-50 h-px overflow-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"
|
||||
>
|
||||
<div className="h-full w-1/3 animate-nav-progress-sweep bg-primary" />
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user