watchparty/client/src/shell/Shell.css
Nicolas 40a897cf1b
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
Replace the mobile sidebar with a bottom sheet, grouped by data shape
The old mobile treatment just reshaped the sidebar into a horizontally
scrolling top bar - easy to miss that there's more to scroll to, and
"Se deconnecter" ended up buried at the far end of it. Explored as
mockups first (compared against a bottom tab bar, a hamburger drawer,
and a polished version of the existing bar) before writing this.

The sheet mirrors the real data shape instead of a flat list: Accueil
and Listes de lecture (when a playlists-type library exists) are fixed
rows bookending a Bibliotheques group, which is the part that actually
varies with whatever the connected Emby server returns - zero new
server work, since getLibraries() already sends collectionType per
library, just unused on the client until now.

The music bar's stacking under the new nav trigger is now structural
(a shared position:fixed flex column) instead of a hardcoded offset,
since the bar's own height isn't constant - its row can wrap on a
narrow screen with a long track/artist name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 14:56:46 +02:00

223 lines
4.8 KiB
CSS

/* ---- App shell: sidebar + routed main content - see Shell.ts. ---- */
.appShell {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 220px;
flex-shrink: 0;
background: var(--bg-header);
padding: 20px 12px;
display: flex;
flex-direction: column;
position: sticky;
top: 0;
height: 100vh;
overflow-y: auto;
}
.sidebarBrand {
font-size: 18px;
font-weight: 700;
padding: 0 12px 20px;
}
.sidebarNav {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
.navSectionLabel {
color: var(--muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: .05em;
padding: 16px 12px 6px;
}
.navItem {
display: block;
padding: 10px 12px;
border-radius: 8px;
color: var(--text);
text-decoration: none;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navItem:hover { background: rgba(255, 255, 255, .06); }
.navItem.active { background: var(--accent); color: white; font-weight: 600; }
.sidebarLogout { margin-top: 12px; padding: 8px 12px; }
.mainContent {
flex: 1;
min-width: 0;
padding: 24px clamp(16px, 3vw, 48px) 80px;
}
/* ==== Mobile navigation (<=720px) ====
Replaces the sidebar outright - see docs/architecture.md's "Mobile
navigation" section for why a bottom sheet was chosen over reshaping
the sidebar into a horizontal bar. Every rule below the base
`display: none` only ever applies once the 720px media query turns
it back on, so none of this affects desktop. */
.mobileTopBar,
.mobileNavTrigger,
.mobileNavScrim,
.mobileNavSheet {
display: none;
}
.mobileTopBar {
position: sticky;
top: 0;
z-index: 3;
padding: 14px 16px;
background: var(--bg-header);
}
.mobileBrand { font-size: 16px; font-weight: 700; }
/* position:fixed, not sticky - #musicBar below already relies on a
fixed wrapper to stay docked regardless of how far #mainContent's
own (whole-page, not internally-scrolled) content runs. */
.bottomStack {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 15;
display: flex;
flex-direction: column;
}
.mobileNavTrigger {
align-items: center;
justify-content: center;
gap: 8px;
background: var(--bg-header);
border: none;
border-top: 1px solid rgba(255, 255, 255, .08);
color: var(--text);
font-family: inherit;
font-size: 13px;
font-weight: 600;
padding: 13px;
padding-bottom: calc(13px + env(safe-area-inset-bottom, 0px));
}
.mobileNavScrim {
position: fixed;
inset: 0;
z-index: 20;
background: rgba(0, 0, 0, .5);
opacity: 0;
pointer-events: none;
transition: opacity .2s;
}
.mobileNavScrim.open { opacity: 1; pointer-events: auto; }
.mobileNavSheet {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 21;
max-height: 75vh;
overflow-y: auto;
background: var(--bg-header);
border-radius: 16px 16px 0 0;
padding: 8px 16px calc(20px + env(safe-area-inset-bottom, 0px));
transform: translateY(100%);
transition: transform .22s ease;
box-shadow: 0 -8px 24px rgba(0, 0, 0, .3);
}
.mobileNavSheet.open { transform: translateY(0); }
.sheetGrabber {
width: 36px;
height: 4px;
border-radius: 99px;
background: var(--muted);
margin: 10px auto 6px;
}
/* Accueil and the (conditional) Listes de lecture row both use this -
same visual weight, bookending the "Bibliothèques" group between
them, so the one part whose size actually varies (however many
libraries Emby returns) reads as visually distinct from the two
fixed rows around it. */
.sheetPrimary {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
background: var(--panel);
border: none;
border-radius: 10px;
color: var(--text);
font-family: inherit;
font-size: 14px;
font-weight: 600;
padding: 12px;
margin: 4px 0;
text-decoration: none;
}
.sheetPrimary.active { background: var(--accent); color: #fff; }
.sheetGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin: 4px 0 10px;
}
.sheetItem {
display: flex;
align-items: center;
gap: 9px;
background: var(--panel);
border-radius: 10px;
color: var(--text);
font-size: 13px;
padding: 12px 10px;
text-decoration: none;
}
.sheetItem.active { background: var(--accent); color: #fff; font-weight: 600; }
.navIcon { width: 20px; height: 20px; flex-shrink: 0; display: block; }
.navIcon svg { width: 100%; height: 100%; }
.sheetLogout {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
margin-top: 8px;
padding: 12px;
border: 1px solid rgba(255, 255, 255, .15);
border-radius: 10px;
color: var(--muted);
font-size: 13px;
}
@media (max-width: 720px) {
.appShell { flex-direction: column; }
.sidebar { display: none; }
.mobileTopBar { display: flex; align-items: center; }
.mobileNavTrigger { display: flex; }
.mobileNavScrim { display: block; }
.mobileNavSheet { display: block; }
}