release / go (push) Successful in 1m53s
release / govulncheck (push) Successful in 49s
release / web (push) Successful in 1m50s
release / android (push) Successful in 6m44s
release / Build signed APK (releases and dev) (push) Successful in 6m30s
release / Attach APK to the Release (tag releases only) (push) Canceled after 0s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
release / integration (push) Canceled after 21m41s
Preference 172: no "Load more" buttons. Eight remained, on Liked (three sections), the three Search overflow pages, Genres and Years. - ListContinuation: the one bottom-of-list block. It loads the next page ahead of the reader, announces "Loading more…" through aria-live, shows an optional end line, and on a failed page shows the error with Try again, dropping the sentinel so a dead endpoint isn't re-hit on every scroll. Used on all ten paged lists, including the four that already autoloaded. - A failed next page no longer replaces the list. TanStack sets isError for it, so the page-level error branches now apply only to a failed first load. Genres and Years did the same with their own loader; a later page's failure now keeps the grid. - Liked: Artists | Albums | Tracks tabs (operator's choice). Stacked, a long Artists list loading as you scroll would bury the other two. It opens on the first tab that has likes. - TabStrip: the in-page tab strip, now shared by Liked, Playback errors and Requests. - test-utils/intersectionObserver: a stand-in so tests can scroll to the end. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
1.4 KiB
Svelte
48 lines
1.4 KiB
Svelte
<script lang="ts" generics="Id extends string">
|
|
// In-page tabs: one strip of buttons switching what a single page shows
|
|
// (#5416). The same look as AdminTabs and DiscoverTabs, which are route
|
|
// navigation and so stay links; this is for state inside one page, such as
|
|
// Liked's Artists | Albums | Tracks or a queue's status filter.
|
|
type Item = {
|
|
id: Id;
|
|
label: string;
|
|
/** Shown as a pill after the label. Omit for no pill. */
|
|
count?: number;
|
|
};
|
|
|
|
let {
|
|
items,
|
|
value = $bindable(),
|
|
ariaLabel
|
|
}: {
|
|
items: Item[];
|
|
value: Id;
|
|
ariaLabel: string;
|
|
} = $props();
|
|
</script>
|
|
|
|
<div role="tablist" aria-label={ariaLabel} class="flex gap-2 border-b border-border">
|
|
{#each items as item (item.id)}
|
|
{@const active = value === item.id}
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={active}
|
|
class="border-b-2 px-3 py-2 text-sm transition-colors {active
|
|
? 'border-accent text-text-primary'
|
|
: 'border-transparent text-text-secondary hover:text-text-primary'}"
|
|
onclick={() => (value = item.id)}
|
|
>
|
|
{item.label}
|
|
{#if item.count !== undefined}
|
|
<span
|
|
class="ml-1.5 inline-flex items-center rounded-full bg-accent-tint px-2 py-0.5 text-xs text-accent-fg"
|
|
data-testid="tab-count"
|
|
>
|
|
{item.count}
|
|
</span>
|
|
{/if}
|
|
</button>
|
|
{/each}
|
|
</div>
|