Merge pull request 'Make the library page header (title + search bar) fully sticky' (#14) from feature/library-sticky-header into master
Some checks failed
Publier l'image Docker / publish (push) Blocked by required conditions
Publier l'image Docker / test (push) Has been cancelled

This commit is contained in:
kyuno 2026-09-16 21:24:14 +02:00
commit 3ba0de341e
7 changed files with 86 additions and 3 deletions

View file

@ -367,6 +367,8 @@ Then put it behind your existing reverse proxy like everything else
- [x] Library/artist grid tiles shrink fluidly on a narrow phone instead - [x] Library/artist grid tiles shrink fluidly on a narrow phone instead
of a fixed 160px floor collapsing to one column per row — see of a fixed 160px floor collapsing to one column per row — see
docs/architecture.md docs/architecture.md
- [x] Library page title now stays pinned alongside the search bar while
scrolling, instead of just the search bar — see docs/architecture.md
- [ ] Try a full party end-to-end with two real accounts (still the main gap) - [ ] Try a full party end-to-end with two real accounts (still the main gap)
- [ ] Try casting to a real Chromecast (only the SDK's own bootstrap is - [ ] Try casting to a real Chromecast (only the SDK's own bootstrap is
confirmed here — no real device/session) confirmed here — no real device/session)

View file

@ -79,7 +79,8 @@
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 3; z-index: 3;
padding: 14px 16px; height: var(--mobile-topbar-h);
padding: 0 16px;
background: var(--bg-header); background: var(--bg-header);
} }

View file

@ -14,6 +14,7 @@
--accent: #52b54b; /* Emby's theme-primary-color (hsl(116, 41.7%, 50.2%)) */ --accent: #52b54b; /* Emby's theme-primary-color (hsl(116, 41.7%, 50.2%)) */
--accent-bright: #6ccf65; /* Emby's --theme-accent-text-color-darkbg */ --accent-bright: #6ccf65; /* Emby's --theme-accent-text-color-darkbg */
--danger: #ff6b6b; --danger: #ff6b6b;
--mobile-topbar-h: 49px; /* shell/Shell.css's .mobileTopBar - a fixed height so any other sticky element (e.g. views/library's .libraryHeader) can stack directly below it instead of guessing its rendered height */
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }

View file

@ -0,0 +1,24 @@
/* ---- Library page header (title + search bar) - see Library.ts/html. ----
Wraps .pageHeader and tiles.css's .searchBar (already sticky on its own)
in one sticky block so the title stays put too, instead of scrolling
away while the search bar stays behind - reported directly. Needs its
own opaque background since .pageHeader's <h1> has none - without it,
grid tiles scrolling underneath would show through the title text once
stuck. */
.libraryHeader {
position: sticky;
top: 0;
z-index: 2;
background: var(--bg);
padding-top: 4px;
}
/* Below 720px, shell/Shell.css's own sticky .mobileTopBar already occupies
top:0 - stacking this header at top:0 too would have both fighting over
the same spot, with this one (later in paint order) covering the app's
brand bar. Stick it right below instead. */
@media (max-width: 720px) {
.libraryHeader {
top: var(--mobile-topbar-h);
}
}

View file

@ -1,4 +1,6 @@
<div class="libraryHeader">
<div class="pageHeader"><h1></h1></div> <div class="pageHeader"><h1></h1></div>
<input id="searchInput" class="searchBar" type="search" placeholder="Rechercher dans cette bibliothèque..." /> <input id="searchInput" class="searchBar" type="search" placeholder="Rechercher dans cette bibliothèque..." />
</div>
<p class="hint searchEmpty hide">Aucun résultat.</p> <p class="hint searchEmpty hide">Aucun résultat.</p>
<div id="grid" class="grid"><p class="hint"><span class="spinner"></span>Chargement...</p></div> <div id="grid" class="grid"><p class="hint"><span class="spinner"></span>Chargement...</p></div>

View file

@ -1,3 +1,4 @@
import './Library.css';
import html from './Library.html'; import html from './Library.html';
import { getArtists, getLibraries, getLibraryItems } from '../../api/embyApi'; import { getArtists, getLibraries, getLibraryItems } from '../../api/embyApi';
import { createParty } from '../../api/party'; import { createParty } from '../../api/party';

View file

@ -3911,6 +3911,58 @@ from 320 to 393px now gets 2 columns (previously 1), 430px gets 3, and
768/1200px match what the old fixed `160px` floor already gave - 768/1200px match what the old fixed `160px` floor already gave -
confirmed empirically, not assumed from the formula alone. confirmed empirically, not assumed from the formula alone.
## The library header wasn't fully sticky: the title still scrolled away
"A sticky search bar" (above) only ever made the `<input>` itself
sticky - asked for directly again, this time to also keep the page
title (`.pageHeader`'s `<h1>`) pinned alongside it on `views/library`,
so both stay visible together while a long grid scrolls underneath
instead of just the search box.
New `.libraryHeader` wrapper (`views/library/Library.css` - the one
view of this shape that had no dedicated stylesheet yet, everything
borrowed from `tiles.css`/`base.css`) around the existing `.pageHeader`
+ `.searchBar` pair, `position: sticky; top: 0` on the wrapper itself
with its own opaque `var(--bg)` background - the `<h1>` has none of its
own, so without this, tiles scrolling underneath would show through the
title text once stuck. `.searchBar` keeps its own pre-existing
`position: sticky; top: 0` from `tiles.css` unchanged (still needed as-is
on `views/artist`/`views/album`, which don't get this wrapper); nested
inside an already-stuck ancestor that isn't itself scrolling, it's
inert rather than conflicting.
Scoped to `views/library` only, not `tiles.css`'s shared `.pageHeader` -
`views/home` uses that same class for a plain, non-sticky title, and
`views/artist`/`views/album` have since moved to their own hero headers
(see "Music UI rework" above), not `.pageHeader` at all.
One real collision worth catching: below 720px, `shell/Shell.css`'s own
`.mobileTopBar` is *also* `position: sticky; top: 0` (the app's brand
bar, sitting above `.mainContent` as a sibling, not an ancestor) - the
pre-existing sticky `.searchBar` already raced it for the same spot,
just narrowly enough (one slim input) not to get reported. Stacking a
whole title+search block at `top: 0` too would have properly covered
it, later in paint order. `.mobileTopBar` got an explicit
`height: var(--mobile-topbar-h)` (new `base.css` token, `49px` -
replacing its previous padding-derived auto height with a fixed one
anything else can reliably stack against), and `.libraryHeader` sticks
to `top: var(--mobile-topbar-h)` instead of `0` under that same 720px
breakpoint - directly below the brand bar instead of underneath it.
Verified: full `typecheck`/`build` (client only). No Emby session is
needed to see either state, so checked directly: real compiled
`app.css` loaded (same-origin, from the running dev server) into two
`<iframe>`s - 1000px and 375px wide, each its own independent viewport
for the 720px media query, the same workaround this sandbox's own
broken `resize_window` emulation has needed all session - reproducing
`Shell.html`'s `.mobileTopBar`/`.mainContent` plus `Library.html`'s new
markup and a couple dozen dummy tiles, then scrolled deep into the grid
in both and read `getBoundingClientRect()` back: on the 1000px frame
the title and search bar both stayed at the viewport top, tiles
scrolling underneath, opaque background confirmed; on the 375px frame
`.libraryHeader`'s top edge landed exactly at `.mobileTopBar`'s bottom
edge (49px) with zero overlap, both bars' text still visible together.
## Known limitations ## Known limitations
- Playback always goes through `hls.js` against whatever Emby's - Playback always goes through `hls.js` against whatever Emby's