release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m45s
release / go (push) Successful in 1m58s
release / integration (push) Successful in 5m39s
release / android (push) Successful in 5m43s
release / Build signed APK (releases and dev) (push) Successful in 5m58s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 26s
release / Verify release artifacts (tag releases only) (push) Skipped
- /settings lands on Account: the Android app, profile, password, signed-in devices. Then Playback, Appearance, Notifications, Connections, About. - Each setting is a row (name, one short line, control at the end) in a capped-width column; one-of-N choices are a shared Segmented control. - Collection and Settings share one SectionRail. - Connections leads ListenBrainz with a status line: not connected, connected and not sending, or sending with the last scrobble's age. - Notifications prints its column labels once; the digest email's settings link points at /settings/notifications. - #5596: the profile form now loads what is stored and sends only the fields that changed. It used to start blank, and the server reads a blank as "clear", so one Save wiped the display name and email. - Recommendation metrics moves to Library › Your listening, and Hidden tracks gets a Library tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
40 lines
1.2 KiB
Svelte
40 lines
1.2 KiB
Svelte
<script lang="ts" generics="V extends string | number">
|
|
// A one-of-N choice as a row of joined buttons (#5595): Theme, Volume
|
|
// leveling, Target loudness. Each option is a real radio, so it is
|
|
// reachable by keyboard and named by its own text.
|
|
|
|
let {
|
|
name,
|
|
label,
|
|
options,
|
|
value,
|
|
onchange
|
|
}: {
|
|
name: string;
|
|
label: string;
|
|
options: { value: V; label: string }[];
|
|
value: V;
|
|
onchange: (v: V) => void;
|
|
} = $props();
|
|
</script>
|
|
|
|
<div role="radiogroup" aria-label={label} class="inline-flex overflow-hidden rounded-md border border-border">
|
|
{#each options as o, i (o.value)}
|
|
<label
|
|
class="flex cursor-pointer items-center px-3 py-1.5 text-sm transition-colors has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-accent has-[:focus-visible]:ring-inset
|
|
{i > 0 ? 'border-l border-border' : ''}
|
|
{value === o.value ? 'bg-action-secondary text-action-fg' : 'text-text-primary hover:bg-surface-hover'}"
|
|
>
|
|
<input
|
|
type="radio"
|
|
{name}
|
|
value={o.value}
|
|
checked={value === o.value}
|
|
onchange={() => onchange(o.value)}
|
|
class="sr-only"
|
|
/>
|
|
<span class="whitespace-nowrap tabular-nums">{o.label}</span>
|
|
</label>
|
|
{/each}
|
|
</div>
|