Make the library page header (title + search bar) fully sticky
The search bar was already sticky on its own; the title above it still scrolled away. Wraps both in one sticky .libraryHeader block, scoped to views/library only, and stacks it below the mobile shell's own sticky top bar instead of racing it for the same spot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0a9b3fb498
commit
3fda483688
7 changed files with 86 additions and 3 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
24
client/src/views/library/Library.css
Normal file
24
client/src/views/library/Library.css
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<div class="pageHeader"><h1></h1></div>
|
<div class="libraryHeader">
|
||||||
<input id="searchInput" class="searchBar" type="search" placeholder="Rechercher dans cette bibliothèque..." />
|
<div class="pageHeader"><h1></h1></div>
|
||||||
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue