feat(ui): add ThemeToggle — segmented Monitor/Sun/Moon control
Replaces the plain <select> theme dropdown with three icon buttons. Defaults to "system" (Monitor), matching the existing behavior of following the OS preference until the user picks an explicit override.
This commit is contained in:
1 parent
4c40ebc939
commit
7ae146f99f
1 file changed
+72
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
// Segmented System/Light/Dark control (T7.2, FR-UX-2), replacing the old
|
||||
// plain <select> — three icon buttons instead of a text dropdown, defaults
|
||||
// to "system" so the app follows the OS until the user picks an override.
|
||||
import { Monitor, Sun, Moon } from "@lucide/svelte";
|
||||
|
||||
interface Props {
|
||||
value: "system" | "light" | "dark";
|
||||
onchange: (value: "system" | "light" | "dark") => void;
|
||||
}
|
||||
let { value, onchange }: Props = $props();
|
||||
|
||||
const options = [
|
||||
{ id: "system", label: "Match system", Icon: Monitor },
|
||||
{ id: "light", label: "Light", Icon: Sun },
|
||||
{ id: "dark", label: "Dark", Icon: Moon },
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<div class="toggle" role="radiogroup" aria-label="Theme">
|
||||
{#each options as opt (opt.id)}
|
||||
<button
|
||||
role="radio"
|
||||
aria-checked={value === opt.id}
|
||||
class:active={value === opt.id}
|
||||
title={opt.label}
|
||||
aria-label={opt.label}
|
||||
onclick={() => onchange(opt.id)}
|
||||
>
|
||||
<opt.Icon size={15} strokeWidth={2} aria-hidden="true" />
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 2px;
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
button {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-full);
|
||||
color: var(--fg-muted);
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 150ms ease-out,
|
||||
color 150ms ease-out;
|
||||
}
|
||||
button:hover {
|
||||
color: var(--fg);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
button.active {
|
||||
background: var(--accent);
|
||||
color: var(--accent-fg);
|
||||
}
|
||||
button:focus-visible {
|
||||
outline: 2px solid var(--focus-ring);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user