From 0a8ec845efd06263b6df36302da8ee75a6d9fc67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Tue, 28 Jul 2026 15:05:36 +0000 Subject: [PATCH] =?UTF-8?q?web:=20music=20dock=20joins=20the=20layout=20fl?= =?UTF-8?q?ow=20=E2=80=94=20removes=20the=20nav-dock=20overlap=20hacks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The music dock was position:fixed, overlaying the bottom of the content, so the nav dock needed a pile of hacks to dodge it: a hand-measured MUSIC_DOCK_HEIGHT (72) constant, a presence flag, a translateY lift, and a matching hover-threshold lift (via a ref) so it wouldn't slide away under the cursor. Fragile the moment the music dock's height changed. Now the dock is an in-flow bottom bar that reserves its own height: - DashboardLayout is a flex column: the content region (flex-1, min-h-0) shrinks when the music dock takes its space; MusicPlayerHost renders an in-flow bar (shrink-0) instead of a fixed overlay. - The nav Dock is absolute within the content region and measures its reveal/hide boundary from that region's bottom edge (a boundaryRef) — so it always sits just above whatever's at the bottom, music dock or not, with zero knowledge of it. - Deleted MUSIC_DOCK_HEIGHT, musicDockPresent, the lift, and the transform hack. Bonus: content at the very bottom is no longer hidden under the fixed dock. Co-Authored-By: Claude Opus 4.8 --- .../Dashboard/Layout/DashboardLayout.tsx | 18 +++++----- .../Screens/Dashboard/Layout/Dock.tsx | 35 +++++++------------ .../src/MusicPlayer/MusicPlayerHost.tsx | 4 ++- .../src/MusicPlayer/useMusicPlayer.ts | 3 -- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/DashboardLayout.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/DashboardLayout.tsx index 7d59a627..150cda6e 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/DashboardLayout.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/DashboardLayout.tsx @@ -1,3 +1,4 @@ +import { useRef } from 'react'; import { useDock, MusicPlayerHost } from 'officerdev'; import { Background } from './Background'; import { Header } from './Header'; @@ -12,20 +13,19 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { const { items: visibleItems } = useDock(ALL_DOCK_ITEMS, DEFAULT_DOCK_PATHS); const isTouch = useIsTouch(); usePageTitleSync(); + // The content region shrinks when the (in-flow) music dock takes its space; the nav dock measures its + // reveal boundary from this element, so it always sits just above whatever's at the bottom. + const regionRef = useRef(null); return ( -
+
-
+
- {!isTouch && } -
- {children} -
+ {!isTouch && } +
{children}
); -}; - - +} diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx index 8aebb6bc..7205d8e6 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx @@ -1,7 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { Link, useLocation } from 'react-router'; import type { LucideIcon } from 'lucide-react'; -import { useMusicPlayer, MUSIC_DOCK_HEIGHT } from 'officerdev'; export type DockItem = { label: string; @@ -13,6 +12,9 @@ export type DockItem = { type DockProps = { items: DockItem[]; className?: string; + // The content region the dock lives in. Reveal/hide is measured from ITS bottom edge, which the + // in-flow music dock shrinks when present — so the dock clears the music dock with no magic offset. + boundaryRef: React.RefObject; }; const ICON_SIZE = 48; @@ -30,30 +32,22 @@ const getScale = (mouseX: number | null, iconCenterX: number) => { return 1 + (MAX_SCALE - 1) * Math.cos((distance / MAX_DISTANCE) * (Math.PI / 2)); }; -export const Dock = ({ items, className }: DockProps) => { +export const Dock = ({ items, className, boundaryRef }: DockProps) => { const [mouseX, setMouseX] = useState(null); const [visible, setVisible] = useState(false); const dockRef = useRef(null); const location = useLocation(); - const { current } = useMusicPlayer(); - const musicDockPresent = !!current; - // Read the latest value inside the (once-registered) mousemove handler, so it reacts to the music - // dock appearing/disappearing without re-binding the listener. - const musicPresentRef = useRef(musicDockPresent); - musicPresentRef.current = musicDockPresent; const isActive = (to: string) => (to === '/' ? location.pathname === '/' : location.pathname.startsWith(to)); useEffect(() => { const handleMouseMove = (ev: MouseEvent) => { - const distFromBottom = window.innerHeight - ev.clientY; - // When the music dock is present the nav dock rides MUSIC_DOCK_HEIGHT higher, so lift the hide/ - // magnify zone by the same amount — otherwise hovering the raised dock counts as past the hide - // threshold and it slides away under the cursor. The reveal trigger stays at the bottom edge. - const lift = musicPresentRef.current ? MUSIC_DOCK_HEIGHT : 0; - const hideAt = HIDE_THRESHOLD + lift; - setVisible((prev) => (prev ? distFromBottom <= hideAt : distFromBottom <= SHOW_THRESHOLD)); - if (distFromBottom <= hideAt) { + // Distance from the bottom of the content region (which the in-flow music dock shrinks when + // present) — so reveal/hide triggers just above wherever the region ends, above the music dock. + const bottom = boundaryRef.current?.getBoundingClientRect().bottom ?? window.innerHeight; + const distFromBottom = bottom - ev.clientY; + setVisible((prev) => (prev ? distFromBottom <= HIDE_THRESHOLD : distFromBottom <= SHOW_THRESHOLD)); + if (distFromBottom <= HIDE_THRESHOLD) { const rect = dockRef.current?.getBoundingClientRect(); if (rect) setMouseX(ev.clientX - rect.left); } else { @@ -63,20 +57,17 @@ export const Dock = ({ items, className }: DockProps) => { document.addEventListener('mousemove', handleMouseMove); return () => document.removeEventListener('mousemove', handleMouseMove); - }, []); + }, [boundaryRef]); return (
{items.map((item, index) => { diff --git a/src/workspaces/officerdev/src/MusicPlayer/MusicPlayerHost.tsx b/src/workspaces/officerdev/src/MusicPlayer/MusicPlayerHost.tsx index 0dd75db9..04fdab45 100644 --- a/src/workspaces/officerdev/src/MusicPlayer/MusicPlayerHost.tsx +++ b/src/workspaces/officerdev/src/MusicPlayer/MusicPlayerHost.tsx @@ -249,8 +249,10 @@ export const MusicPlayerHost = () => { if (!current) return null; + // In-flow bottom bar (NOT position:fixed) — it reserves its own height so the content above shrinks to + // fit and the nav dock naturally sits above it, no overlap hacks needed. return ( -
+
{/* cover + info — click to open this album in /music */}