feat(slint): sticky JUMP TO bar, search toggle, View less

Three follow-on refinements after the layout restructure.

JUMP TO bar (`ui/primitives/JumpNavBar.slint`):
  - New reusable component — same shape Tauri ships above
    section-rich pages (artist, library, future label / musician
    views): "JUMP TO" label, horizontal list of tabs with an accent
    underline on the active one, search icon on the right.
  - Presentation-only — sticky positioning is the parent's
    responsibility. Tabs and search icon emit callbacks; reuse by
    passing a fresh tab list and routing the callbacks.

Artist page wiring:
  - JumpNavBar mounted as an overlay sibling of the page Flickable
    with the same sticky math as the sidebar: y =
    `max(290px + page-flickable.viewport-y, 0px)`. At rest the bar
    sits below the artist header; once the user scrolls past the
    header, it pins to the top of the page. (This is the reusable
    sticky recipe the user asked for — it's wired here for artist
    and lifts straight into the next section that needs it.)
  - jump-tabs hardcoded with About / Popular Tracks / Discography /
    EPs & Singles / Compilations / Playlists — same labels as
    Tauri. tab-clicked updates the active-tab visual state; actual
    scroll-to-section wiring is a follow-up (needs anchor positions
    that depend on per-artist data).
  - search-visible flag flips on the bar's search-clicked. The
    existing in-page search input is now gated by it, so the search
    is closed by default (matches Tauri's `.page-search.open`
    behaviour) and opens on click of the JUMP TO bar's search icon.

Popular Tracks "View less":
  - The Load more affordance now flips its label to "View less" once
    expanded so users can collapse back to the 5-track preview
    without scrolling. A small improvement over the Tauri flow
    (which only shows Load more).

cc
This commit is contained in:
vicrodh committed 2026-05-20 12:26:01 -06:00
1 parent 50b92031cb
commit ccebf2833c
2 files changed
+204 -9

No files matched your search

@@ -25,6 +25,7 @@ import { TrackContextMenu } from "../primitives/TrackContextMenu.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
import { ReleaseGrid } from "ReleaseGrid.slint";
import { NavButtons } from "../shell/NavButtons.slint";
import { JumpNavBar, JumpNavTab } from "../primitives/JumpNavBar.slint";
component CircleAction inherits Rectangle {
in property <image> icon;
@@ -268,6 +269,24 @@ 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;
// Whether the in-page search input is currently visible.
// Closed by default; the JUMP TO bar's search icon toggles it.
property <bool> search-visible: false;
// Active JUMP TO tab. Currently set by tab-clicked only — a
// future pass will compute it from scroll position so the bar
// self-tracks the visible section.
property <string> active-jump-tab: "popular-tracks";
// Static tab list — same labels Tauri uses. The Slint tabs trigger
// scroll-to-section via tab-clicked; until the section anchors are
// wired, the click just updates the active state.
property <[JumpNavTab]> jump-tabs: [
{ id: "about", label: "About" },
{ id: "popular-tracks", label: "Popular Tracks" },
{ id: "discography", label: "Discography" },
{ id: "eps-singles", label: "EPs & Singles" },
{ id: "compilations", label: "Compilations" },
{ id: "playlists", label: "Playlists" },
];
// 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
@@ -484,10 +503,11 @@ export component ArtistPageView inherits Rectangle {
// --- In-page search ---------------------------------------------
// Single client-side substring filter — applied to Popular
// Tracks (title + artist) and to every release-section
// album title. Same pattern as Tauri's `.page-search` plus
// the AlbumActions.search wiring AlbumPageView uses for
// its track list.
if !ArtistState.loading || ArtistState.top-tracks.length > 0:
// album title. Hidden by default; the JUMP TO bar's search
// icon toggles `search-visible` (mirrors Tauri's search
// toggle inside `.jump-nav`).
if root.search-visible
&& (!ArtistState.loading || ArtistState.top-tracks.length > 0):
Rectangle {
height: 36px;
border-radius: Radius.sm;
@@ -605,9 +625,11 @@ export component ArtistPageView inherits Rectangle {
root.media-action(kind, id, action);
}
}
// Load more — visible when there are more than the preview cap.
if !root.top-tracks-expanded
&& ArtistState.top-tracks.length > root.top-tracks-preview: HorizontalLayout {
// Load more / View less — same affordance, label flips
// with the expanded state. Tauri only shows Load more;
// View less is a Slint-side improvement so users can
// collapse back to the 5-track preview without scrolling.
if ArtistState.top-tracks.length > root.top-tracks-preview: HorizontalLayout {
alignment: start;
Rectangle {
width: load-more-text.preferred-width + 32px;
@@ -619,7 +641,9 @@ export component ArtistPageView inherits Rectangle {
? Theme.surface-elevated
: Theme.surface-card;
load-more-text := Text {
text: @tr("Load more");
text: root.top-tracks-expanded
? @tr("View less")
: @tr("Load more");
color: Theme.text-secondary;
font-size: 13px;
x: (parent.width - self.width) / 2;
@@ -628,7 +652,7 @@ export component ArtistPageView inherits Rectangle {
load-more-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.top-tracks-expanded = true;
root.top-tracks-expanded = !root.top-tracks-expanded;
}
}
}
@@ -660,6 +684,30 @@ export component ArtistPageView inherits Rectangle {
} // close page VerticalLayout
} // close Flickable
// --- Sticky JUMP TO bar -------------------------------------------------
// Overlay anchored to the top of ArtistPageView with the same
// sticky math as the sidebar: tracks the page scroll until the
// user scrolls past the header, then pins to y = 0. The natural
// y is the row just below the artist header in the page layout
// (NavButtons + spacer + artist header + small gap, ~290px).
jump-nav := JumpNavBar {
property <length> natural-top: 290px;
property <length> sticky-top: 0px;
x: 0;
y: max(self.natural-top + page-flickable.viewport-y, self.sticky-top);
width: root.width;
tabs: root.jump-tabs;
active-tab-id: root.active-jump-tab;
search-open: root.search-visible;
tab-clicked(id) => {
root.active-jump-tab = id;
}
search-clicked => {
root.search-visible = !root.search-visible;
}
}
// 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