This commit is contained in:
7
LOG.md
7
LOG.md
@@ -1,5 +1,12 @@
|
|||||||
# Work Log
|
# Work Log
|
||||||
|
|
||||||
|
## 2026-06-06
|
||||||
|
|
||||||
|
- Fixed the desktop Dock so route changes from Dock menu links no longer leave focus-held open state behind when the Dock is not pinned.
|
||||||
|
- Kept the saved `dock-pinned-v2` preference unchanged during navigation and only released transient Dock focus/hover state after menu clicks and pathname changes.
|
||||||
|
- Validation: `npm run lint` passed and `npm run build` passed. In-app browser verification on `http://localhost:3111/` confirmed that after unpinning the Dock, clicking the Dock archive menu navigates to `/archive`, the pin button remains `aria-pressed=false`, and the Dock collapses after the pointer leaves.
|
||||||
|
- Recommended next task: do one visual pass on the Dock at narrow desktop widths to confirm the collapse animation still feels smooth around the sidebar offset.
|
||||||
|
|
||||||
## 2026-06-02
|
## 2026-06-02
|
||||||
|
|
||||||
- Refined the public layout after desktop/mobile review: the shell now guards horizontal overflow, uses route-aware bottom padding, hides the top menubar on mobile, and opens the mobile sidebar/search panel from the Dock.
|
- Refined the public layout after desktop/mobile review: the shell now guards horizontal overflow, uses route-aware bottom padding, hides the top menubar on mobile, and opens the mobile sidebar/search panel from the Dock.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { type CSSProperties, type FocusEvent, useEffect, useRef, useState } from 'react';
|
import { type CSSProperties, type FocusEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { clsx } from 'clsx';
|
import { clsx } from 'clsx';
|
||||||
@@ -124,6 +124,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
const [isDockHovered, setIsDockHovered] = useState(false);
|
const [isDockHovered, setIsDockHovered] = useState(false);
|
||||||
const [isDockFocused, setIsDockFocused] = useState(false);
|
const [isDockFocused, setIsDockFocused] = useState(false);
|
||||||
const [isDockClosing, setIsDockClosing] = useState(false);
|
const [isDockClosing, setIsDockClosing] = useState(false);
|
||||||
|
const isPinnedRef = useRef(isPinned);
|
||||||
const dockZoneRef = useRef<HTMLDivElement>(null);
|
const dockZoneRef = useRef<HTMLDivElement>(null);
|
||||||
const dockLeaveTimerRef = useRef<number | null>(null);
|
const dockLeaveTimerRef = useRef<number | null>(null);
|
||||||
const dockCollapseTimerRef = useRef<number | null>(null);
|
const dockCollapseTimerRef = useRef<number | null>(null);
|
||||||
@@ -132,7 +133,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
const isDockOpen = isPinned || isDockHovered || isDockFocused;
|
const isDockOpen = isPinned || isDockHovered || isDockFocused;
|
||||||
const isDockRangeExpanded = isDockOpen || isDockClosing;
|
const isDockRangeExpanded = isDockOpen || isDockClosing;
|
||||||
|
|
||||||
const clearDockTimers = () => {
|
const clearDockTimers = useCallback(() => {
|
||||||
if (dockLeaveTimerRef.current !== null) {
|
if (dockLeaveTimerRef.current !== null) {
|
||||||
window.clearTimeout(dockLeaveTimerRef.current);
|
window.clearTimeout(dockLeaveTimerRef.current);
|
||||||
dockLeaveTimerRef.current = null;
|
dockLeaveTimerRef.current = null;
|
||||||
@@ -142,7 +143,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
window.clearTimeout(dockCollapseTimerRef.current);
|
window.clearTimeout(dockCollapseTimerRef.current);
|
||||||
dockCollapseTimerRef.current = null;
|
dockCollapseTimerRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const expandDock = () => {
|
const expandDock = () => {
|
||||||
clearDockTimers();
|
clearDockTimers();
|
||||||
@@ -150,7 +151,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
setIsDockHovered(true);
|
setIsDockHovered(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const startDockCollapse = () => {
|
const startDockCollapse = useCallback(() => {
|
||||||
clearDockTimers();
|
clearDockTimers();
|
||||||
setIsDockHovered(false);
|
setIsDockHovered(false);
|
||||||
setIsDockClosing(true);
|
setIsDockClosing(true);
|
||||||
@@ -158,7 +159,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
setIsDockClosing(false);
|
setIsDockClosing(false);
|
||||||
dockCollapseTimerRef.current = null;
|
dockCollapseTimerRef.current = null;
|
||||||
}, DOCK_COLLAPSE_MS);
|
}, DOCK_COLLAPSE_MS);
|
||||||
};
|
}, [clearDockTimers]);
|
||||||
|
|
||||||
const scheduleDockCollapse = () => {
|
const scheduleDockCollapse = () => {
|
||||||
if (isPinned || isDockFocused) return;
|
if (isPinned || isDockFocused) return;
|
||||||
@@ -169,10 +170,44 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
}, DOCK_LEAVE_DELAY_MS);
|
}, DOCK_LEAVE_DELAY_MS);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const blurFocusedDockItem = useCallback(() => {
|
||||||
|
if (
|
||||||
|
document.activeElement instanceof HTMLElement &&
|
||||||
|
dockZoneRef.current?.contains(document.activeElement)
|
||||||
|
) {
|
||||||
|
document.activeElement.blur();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const releaseUnpinnedDockAfterNavigation = useCallback(() => {
|
||||||
|
if (isPinnedRef.current) return;
|
||||||
|
|
||||||
|
const isPointerInsideDock = dockZoneRef.current?.matches(':hover') ?? false;
|
||||||
|
|
||||||
|
blurFocusedDockItem();
|
||||||
|
setIsDockFocused(false);
|
||||||
|
|
||||||
|
if (isPointerInsideDock) {
|
||||||
|
clearDockTimers();
|
||||||
|
setIsDockClosing(false);
|
||||||
|
setIsDockHovered(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startDockCollapse();
|
||||||
|
}, [blurFocusedDockItem, clearDockTimers, startDockCollapse]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isPinnedRef.current = isPinned;
|
||||||
|
}, [isPinned]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const frameId = window.requestAnimationFrame(() => {
|
const frameId = window.requestAnimationFrame(() => {
|
||||||
const savedValue = window.localStorage.getItem(DOCK_PINNED_STORAGE_KEY);
|
const savedValue = window.localStorage.getItem(DOCK_PINNED_STORAGE_KEY);
|
||||||
setIsPinned(savedValue === null ? true : savedValue === 'true');
|
const nextValue = savedValue === null ? true : savedValue === 'true';
|
||||||
|
|
||||||
|
isPinnedRef.current = nextValue;
|
||||||
|
setIsPinned(nextValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => window.cancelAnimationFrame(frameId);
|
return () => window.cancelAnimationFrame(frameId);
|
||||||
@@ -181,14 +216,15 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const frameId = window.requestAnimationFrame(() => {
|
const frameId = window.requestAnimationFrame(() => {
|
||||||
setIsMoreOpen(false);
|
setIsMoreOpen(false);
|
||||||
|
releaseUnpinnedDockAfterNavigation();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => window.cancelAnimationFrame(frameId);
|
return () => window.cancelAnimationFrame(frameId);
|
||||||
}, [pathname]);
|
}, [pathname, releaseUnpinnedDockAfterNavigation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => clearDockTimers();
|
return () => clearDockTimers();
|
||||||
}, []);
|
}, [clearDockTimers]);
|
||||||
|
|
||||||
const handlePinnedChange = () => {
|
const handlePinnedChange = () => {
|
||||||
const nextValue = !isPinned;
|
const nextValue = !isPinned;
|
||||||
@@ -198,6 +234,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
pinToggleStartedByPointerRef.current = false;
|
pinToggleStartedByPointerRef.current = false;
|
||||||
window.localStorage.setItem(DOCK_PINNED_STORAGE_KEY, String(nextValue));
|
window.localStorage.setItem(DOCK_PINNED_STORAGE_KEY, String(nextValue));
|
||||||
clearDockTimers();
|
clearDockTimers();
|
||||||
|
isPinnedRef.current = nextValue;
|
||||||
setIsPinned(nextValue);
|
setIsPinned(nextValue);
|
||||||
|
|
||||||
if (nextValue) {
|
if (nextValue) {
|
||||||
@@ -208,13 +245,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
|
|
||||||
if (startedByPointer) {
|
if (startedByPointer) {
|
||||||
setIsDockFocused(false);
|
setIsDockFocused(false);
|
||||||
|
blurFocusedDockItem();
|
||||||
if (
|
|
||||||
document.activeElement instanceof HTMLElement &&
|
|
||||||
dockZoneRef.current?.contains(document.activeElement)
|
|
||||||
) {
|
|
||||||
document.activeElement.blur();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPointerInsideDock) {
|
if (isPointerInsideDock) {
|
||||||
@@ -387,6 +418,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
|||||||
aria-current={active ? 'page' : undefined}
|
aria-current={active ? 'page' : undefined}
|
||||||
className={itemClass}
|
className={itemClass}
|
||||||
style={toneStyle}
|
style={toneStyle}
|
||||||
|
onClick={releaseUnpinnedDockAfterNavigation}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
Reference in New Issue
Block a user