let the router decide which nav item is active
Dock, Header and the mobile sheet were computing active state from useLocation with two copies of the same startsWith helper. react-router's NavLink already knows. end is set for Home only: without it NavLink treats '/' as an ancestor of every route, and with it on the others a detail route would lose its highlight. Segment matching is stricter than the string prefix it replaces, which is what was meant all along. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
import { NavLink } from 'react-router';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
|
||||
export type DockItem = {
|
||||
@@ -38,9 +38,6 @@ export const Dock = ({ items, className, boundaryRef }: DockProps) => {
|
||||
const [mouseX, setMouseX] = useState<number | null>(null);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const dockRef = useRef<HTMLDivElement | null>(null);
|
||||
const location = useLocation();
|
||||
|
||||
const isActive = (to: string) => (to === '/' ? location.pathname === '/' : location.pathname.startsWith(to));
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (ev: MouseEvent) => {
|
||||
@@ -75,12 +72,15 @@ export const Dock = ({ items, className, boundaryRef }: DockProps) => {
|
||||
{items.map((item, index) => {
|
||||
const iconCenter = DOCK_PADDING + index * (ICON_SIZE + ICON_GAP) + ICON_SIZE / 2;
|
||||
const scale = getScale(mouseX, iconCenter);
|
||||
const active = isActive(item.to);
|
||||
|
||||
return (
|
||||
<Link
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
// Home would otherwise light up on every route: without `end`, NavLink treats "/" as an
|
||||
// ancestor of everything. Every other item wants the ancestor match, so that a detail route
|
||||
// (/plans/x, /system-monitor/btop) keeps its dock tile lit.
|
||||
end={item.to === '/'}
|
||||
className="group relative flex flex-col items-center"
|
||||
style={{
|
||||
transform: `scale(${scale})`,
|
||||
@@ -88,29 +88,33 @@ export const Dock = ({ items, className, boundaryRef }: DockProps) => {
|
||||
transition: 'transform 150ms ease-out',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="absolute -top-9 px-2 py-1 rounded-md text-white text-xs whitespace-nowrap hidden md:block opacity-0 group-hover:opacity-100 transition-opacity duration-150 pointer-events-none"
|
||||
style={{ backgroundColor: 'var(--dock-tooltip-bg)' }}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
<div
|
||||
className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center transition-all"
|
||||
style={{
|
||||
background: item.color,
|
||||
boxShadow: active ? `0 0 12px ${item.color}40` : 'none',
|
||||
}}
|
||||
>
|
||||
{item.image ? (
|
||||
<img src={item.image} alt="" className="h-7 w-7 md:h-8 md:w-8 object-contain" />
|
||||
) : (
|
||||
item.icon && <item.icon className="h-5 w-5 md:h-6 md:w-6 text-white" />
|
||||
)}
|
||||
</div>
|
||||
{active && (
|
||||
<div className="absolute -bottom-1.5 w-1.5 h-1.5 rounded-full" style={{ background: item.color }} />
|
||||
{({ isActive }) => (
|
||||
<>
|
||||
<span
|
||||
className="absolute -top-9 px-2 py-1 rounded-md text-white text-xs whitespace-nowrap hidden md:block opacity-0 group-hover:opacity-100 transition-opacity duration-150 pointer-events-none"
|
||||
style={{ backgroundColor: 'var(--dock-tooltip-bg)' }}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
<div
|
||||
className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center transition-all"
|
||||
style={{
|
||||
background: item.color,
|
||||
boxShadow: isActive ? `0 0 12px ${item.color}40` : 'none',
|
||||
}}
|
||||
>
|
||||
{item.image ? (
|
||||
<img src={item.image} alt="" className="h-7 w-7 md:h-8 md:w-8 object-contain" />
|
||||
) : (
|
||||
item.icon && <item.icon className="h-5 w-5 md:h-6 md:w-6 text-white" />
|
||||
)}
|
||||
</div>
|
||||
{isActive && (
|
||||
<div className="absolute -bottom-1.5 w-1.5 h-1.5 rounded-full" style={{ background: item.color }} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user