feat(panels): peek-open side panels with a docked tab

The shared SidePanel now follows the nav rail's model, so Filters, Plex and
Playlists all inherit it: unpinned it sits as a small tab docked flush against
the rail (replacing the full-height vertical strip) and expands into the card on
hover or keyboard focus without pushing content; pinned it stays expanded
in-flow. The collapse toggle became pin/unpin, and panels now default to
unpinned. The tab carries the active-filter count and an accent ring.

The tab stays mounted under the open card on purpose: swapping it out from under
the pointer drops the hover that opened the card, and unmounting it while
focused strands keyboard focus.

Measured: feed content starts at 123px instead of 336px when unpinned.
This commit is contained in:
2026-07-15 20:57:21 +02:00
parent 11451c1af2
commit 79d9bca290
4 changed files with 99 additions and 78 deletions
+3 -2
View File
@@ -373,12 +373,13 @@ export default function App() {
const [navCollapsed, setNavCollapsedState] = useState<boolean>(() =>
Boolean(readAccount(LS.navCollapsed, true))
);
// Default unpinned (tab + hover-expand overlay); accounts that explicitly pin keep their choice.
const [filterCollapsed, setFilterCollapsedState] = useState<boolean>(() =>
Boolean(readAccount(LS.filterCollapsed, false))
Boolean(readAccount(LS.filterCollapsed, true))
);
// The Playlists rail has its own collapse flag (it's a list/navigator, not filters).
const [playlistsCollapsed, setPlaylistsCollapsedState] = useState<boolean>(() =>
Boolean(readAccount(LS.playlistsCollapsed, false))
Boolean(readAccount(LS.playlistsCollapsed, true))
);
function setNavCollapsed(next: boolean) {
setNavCollapsedState(next);