Files
minstrel/web/src/lib/components/settings/Segmented.svelte
T
bvandeusenandClaude Opus 5.5 c94c09aeae
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
feat: Settings is one page per section down a left rail, Account first, every setting a row; the profile form no longer saves blanks over your name and email (M503 #5595 #5596 #5597)
- /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>
2026-10-10 23:34:51 -04:00

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>