fix(ux): dock filter tab flush to the nav + reliable full-viewport plex backdrop
Follow-ups on the 7-item batch: - The slim nav slot was still w-20 while its card is w-14, leaving a ~24px dead gap the filter tab/bar couldn't dock against. Slot is now w-14 (exactly the card width), so the filter's flat-left tab sits flush on the rail's right edge (measured gap 0), both collapsed and expanded. - The Plex art backdrop was invisible: a position:fixed layer at z-index -1 is painted BEHIND the body's opaque background here, so nothing showed. Paint the art on the app root (#root) instead — it's exactly the viewport and a real ancestor of the transparent nav/header/content, so the art fills the whole page edge-to-edge and the glass chrome floats over it (verified in-browser).
This commit is contained in:
@@ -201,7 +201,8 @@ export default function NavSidebar({
|
|||||||
// Layout slot: reserves only the slim footprint when unpinned, so the expanded rail (below)
|
// Layout slot: reserves only the slim footprint when unpinned, so the expanded rail (below)
|
||||||
// overlays content instead of pushing it. Pinned reserves the full width (in-flow).
|
// overlays content instead of pushing it. Pinned reserves the full width (in-flow).
|
||||||
// Slim icon rail, flush against the left viewport edge (only top/bottom margin), never grows.
|
// Slim icon rail, flush against the left viewport edge (only top/bottom margin), never grows.
|
||||||
<div className="relative shrink-0 w-20">
|
// The slot is exactly the card's width so the filter tab/bar docks flush against its right edge.
|
||||||
|
<div className="relative shrink-0 w-14">
|
||||||
<nav
|
<nav
|
||||||
className="glass absolute top-0 bottom-0 left-0 z-rail rounded-2xl flex flex-col py-3 my-3 w-14 px-2"
|
className="glass absolute top-0 bottom-0 left-0 z-rail rounded-2xl flex flex-col py-3 my-3 w-14 px-2"
|
||||||
aria-label={t("nav.primary")}
|
aria-label={t("nav.primary")}
|
||||||
|
|||||||
@@ -78,24 +78,37 @@ export function Filterable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Paint the item's art as a faint FULL-VIEWPORT backdrop (HTPC-style) so the glass chrome floats over
|
// Paint the item's art as a faint FULL-VIEWPORT backdrop (HTPC-style) so the glass chrome floats over
|
||||||
// it. A dedicated `position:fixed inset-0` layer at z-index -1 sits BEHIND the (transparent / glassy)
|
// it. Painted on the app root (#root) — which is exactly the viewport and sits BEHIND the transparent
|
||||||
// app content — so the art fills the whole page edge-to-edge, under the nav rail and floating header
|
// nav / header / content — so the art fills the whole page edge-to-edge (under the rail + floating
|
||||||
// too, instead of only the <main> box (which is inset by the rail, the header and the scrollbar
|
// header too), not just the inset <main> box. (A `position:fixed` layer at negative z-index does NOT
|
||||||
// gutter). Removed on unmount / when disabled / when there's no art.
|
// work here: the body's opaque background is painted over it, so it's invisible; #root's background,
|
||||||
|
// being a real ancestor of the content, is the reliable full-viewport layer.) `attachment:fixed`
|
||||||
|
// keeps it viewport-anchored while the content scrolls. Cleared on unmount / disable / no art.
|
||||||
export function useArtBackdrop(art: string | null | undefined, enabled: boolean) {
|
export function useArtBackdrop(art: string | null | undefined, enabled: boolean) {
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!enabled || !art) return;
|
const el = document.getElementById("root");
|
||||||
const el = document.createElement("div");
|
if (!el) return;
|
||||||
el.setAttribute("data-plex-art-backdrop", "");
|
const s = el.style;
|
||||||
el.style.cssText =
|
const clear = () => {
|
||||||
"position:fixed;inset:0;z-index:-1;pointer-events:none;" +
|
s.backgroundImage = "";
|
||||||
"background-size:cover;background-position:center 20%;background-repeat:no-repeat;";
|
s.backgroundSize = "";
|
||||||
// The faint --bg overlay keeps it subtle and text-safe (same tuning the old <main> paint used).
|
s.backgroundPosition = "";
|
||||||
el.style.backgroundImage =
|
s.backgroundRepeat = "";
|
||||||
`linear-gradient(color-mix(in srgb, var(--bg) 60%, transparent), ` +
|
s.backgroundAttachment = "";
|
||||||
`color-mix(in srgb, var(--bg) 64%, transparent)), url("${art}")`;
|
};
|
||||||
document.body.appendChild(el);
|
if (enabled && art) {
|
||||||
return () => el.remove();
|
// The faint --bg overlay keeps it subtle and text-safe (same tuning the old paint used).
|
||||||
|
s.backgroundImage =
|
||||||
|
`linear-gradient(color-mix(in srgb, var(--bg) 60%, transparent), ` +
|
||||||
|
`color-mix(in srgb, var(--bg) 64%, transparent)), url("${art}")`;
|
||||||
|
s.backgroundSize = "cover";
|
||||||
|
s.backgroundPosition = "center 20%";
|
||||||
|
s.backgroundRepeat = "no-repeat";
|
||||||
|
s.backgroundAttachment = "fixed";
|
||||||
|
} else {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
return clear;
|
||||||
}, [art, enabled]);
|
}, [art, enabled]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user