fix(slint): header spans full width, sticky-style sidebar overlay

Restructures the artist page so the gradient header truly covers the
full window width and the network sidebar behaves like Tauri's
sticky aside: anchored to natural-y below the header while the page
is at the top, then pinned to sticky-top once the user scrolls past
the header. The bottom edge stays flush with the now-playing bar.

Layout:
  - The outer HorizontalLayout wrapping (Flickable + sidebar column)
    is gone. The Flickable is now a direct child of ArtistPageView at
    full root.width, so the gradient Rectangle inside it also runs
    edge-to-edge. (Tables analogy: header row is colspan=2.)
  - Inside the page VerticalLayout, body content (search, popular
    tracks, release grids) is wrapped in a HorizontalLayout with a
    horizontal-stretch main column on the left and a 300px empty
    placeholder on the right when the sidebar is open. The main
    column never extends under the sidebar.

Sidebar:
  - The sidebar block is no longer a sibling of the Flickable inside
    a HorizontalLayout — it's an absolutely-positioned Rectangle
    overlay anchored at x = root.width - 300, y bound to
    `max(natural-top + page-flickable.viewport-y, sticky-top)`. At
    scroll = 0 the sidebar sits at natural-top = 320 (just below the
    header). As the user scrolls, the sidebar tracks the page upward
    until it pins at sticky-top = 44 (reserving room for the future
    sticky JUMP TO bar — harmless until that lands).
  - height = root.height - self.y, so the sidebar always extends to
    the bottom of the page area (top of the now-playing bar). The
    32px bottom inset is gone — no more gap under the sidebar.

cc
This commit is contained in:
vicrodh committed 2026-05-20 12:19:45 -06:00
1 parent 381ab04ef0
commit 50b92031cb
1 file changed
+58 -46
+58 -46
View File
@@ -268,11 +268,11 @@ export component ArtistPageView inherits Rectangle {
// Whether the full-biography modal is open. Mirrors the album
// description Read-more pattern in AlbumPageView.
property <bool> show-bio: false;
// Width of the main content column — shrinks when the network
// sidebar opens, so the bio text wraps inside the visible band
// instead of being clipped by the sidebar.
property <length> content-width:
NetworkSidebarState.open ? root.width - 300px : root.width;
// Width handed down to the bio paragraph. The artist header sits
// above the body row where the sidebar lives, so the bio never
// needs to shrink for the sidebar — it always uses the full
// window width minus the page padding + portrait + header gap.
property <length> content-width: root.width;
// Popular Tracks shows the first 5; Load more expands to all.
property <bool> top-tracks-expanded: false;
property <int> top-tracks-preview: 5;
@@ -283,14 +283,15 @@ export component ArtistPageView inherits Rectangle {
background: Theme.surface-main;
// Main content + optional network sidebar — same flow as Tauri's
// .artist-body. The Flickable shrinks when the sidebar opens; the
// sidebar itself is a fixed 300px column on the right.
HorizontalLayout {
spacing: 0px;
Flickable {
horizontal-stretch: 1;
// Single page-wide Flickable — the gradient header + body row
// (main content + sidebar-slot placeholder) all live inside it so
// the artist header spans the full window width (gradient covers
// edge-to-edge, mirroring Tauri's `.artist-body` row analogy of a
// colspan=2 header followed by a 2-column body). The actual
// sidebar is rendered AFTER the Flickable as an overlay sibling so
// it can stay anchored on the right and fill the available height
// independently of the page scroll.
page-flickable := Flickable {
viewport-width: self.width;
viewport-height: page.preferred-height;
@@ -468,6 +469,16 @@ export component ArtistPageView inherits Rectangle {
}
}
// Body row — main content column (search, popular tracks,
// release grids) flanked by an empty 300px reservation when
// the network sidebar is open, so the actual sidebar
// overlay (rendered below) never covers the page content.
HorizontalLayout {
spacing: 0px;
main-col := VerticalLayout {
horizontal-stretch: 1;
spacing: 0px;
Rectangle { height: 24px; }
// --- In-page search ---------------------------------------------
@@ -637,34 +648,42 @@ export component ArtistPageView inherits Rectangle {
}
}
}
}
}
} // close main-col VerticalLayout
// Network sidebar — a 300px column on the right, vertically
// inset so it does not occupy the full body height (matches
// Tauri's `height: calc(100vh - 180px)`, which leaves room for
// the jump-nav at the top and the now-playing bar at the
// bottom). Sections (Origin / Labels / Similar / Relationships /
// Discovery) are populated in the following phases; the shell
// only renders the header and an empty body.
if NetworkSidebarState.open: VerticalLayout {
// Sidebar slot — empty placeholder that reserves the
// right 300px so the main column does not stretch
// under the overlay sidebar.
if NetworkSidebarState.open: Rectangle {
width: 300px;
}
} // close body HorizontalLayout
} // close page VerticalLayout
} // close Flickable
// Network sidebar — overlay anchored to the right edge of the
// ArtistPageView, with sticky-style y positioning so it tracks
// the page scroll while the header is visible and then pins
// to `sticky-top` once the user scrolls past the header. The
// bottom edge is always flush with the now-playing bar
// (height = root.height - y), so no gap appears when sticky.
if NetworkSidebarState.open: sidebar-panel := Rectangle {
// 320px = NavButtons + artist header + 36px spacer (the
// natural y of the body row). At scroll = 0 the sidebar
// sits below the header; as the user scrolls down, y
// tracks page-flickable.viewport-y until it hits
// sticky-top.
property <length> natural-top: 320px;
// 44px reserves room for a future sticky JUMP TO bar
// (parked at viewport top once the header scrolls away).
// Until that lands, the value is harmless — the sidebar
// simply sticks at y = 44 instead of y = 0.
property <length> sticky-top: 44px;
x: root.width - self.width;
y: max(self.natural-top + page-flickable.viewport-y, self.sticky-top);
width: 300px;
spacing: 0px;
// Top inset clears the entire artist header — NavButtons
// (11 padding + ~32 row + 22 spacer = 65) + the artist
// header itself (~214px: ARTIST label + name + truncated
// bio + Read more + action buttons) + the 36px spacer
// before Popular Tracks. Total ~315px so the sidebar's
// top edge sits inline with where Popular Tracks starts,
// matching Tauri's `.network-sidebar-column` natural flow
// position (inside the body, alongside the sections —
// never overlapping the artist header).
Rectangle { height: 320px; }
sidebar-panel := Rectangle {
vertical-stretch: 1;
background: Theme.surface-card;
height: root.height - self.y;
background: Theme.surface-card;
// Left border line — 1px Theme.border-subtle, matching the
// Tauri `.network-sidebar-column { border-left: 1px solid
@@ -1129,13 +1148,6 @@ export component ArtistPageView inherits Rectangle {
}
}
// 32px gap above the now-playing bar — mirrors Tauri's
// height = calc(100vh - titlebar - jump-nav - nowplaying)
// which leaves a small margin above the bar.
Rectangle { height: 32px; }
}
}
// --- Full-biography modal ----------------------------------------------
// Same pattern as AlbumPageView's description modal: scrim that
// dismisses on click, centered panel with a Flickable so a long