feat(ui): icon nav tabs + elevation for the Settings modal

Recording/Hardware/Storage/Calendar/Sync/Privacy nav tabs get matching
icons; the active tab now uses the accent color instead of an inverted
fg/bg swap. Check/X/AlertTriangle icons replace ✓/✗/✕/⚠ throughout
(connection test result, close button, third-party-cloud warning
banner, no-egress confirmation). The modal itself gets proper
elevation (shadow + larger radius) instead of a flat bordered box.
This commit is contained in:
iamdoubz committed 2026-07-02 12:57:36 -05:00
1 parent ecf38d5313
commit df82b0636d
1 file changed
+101 -34
+101 -34
View File
@@ -9,6 +9,17 @@
import { open } from "@tauri-apps/plugin-dialog";
import type { BackendId, SyncKind, SyncTargetConfig } from "../api";
import { trapFocus } from "../actions/trapFocus";
import {
X,
Check,
AlertTriangle,
Mic,
Cpu,
HardDrive,
CalendarDays,
UploadCloud,
ShieldCheck,
} from "@lucide/svelte";
let { onClose }: { onClose: () => void } = $props();
@@ -49,7 +60,7 @@
return Math.round((p.received / p.total) * 100);
}
let showConsent = $state(false);
let testResult = $state<string | null>(null);
let testResult = $state<{ ok: boolean; message: string } | null>(null);
onMount(() => {
settings.load();
@@ -113,7 +124,7 @@
}
async function testConnection() {
const r = await settings.test(form);
testResult = (r.ok ? "✓ " : "✗ ") + r.message;
testResult = { ok: r.ok, message: r.message };
}
async function addTarget() {
await settings.addTarget(form);
@@ -138,24 +149,28 @@
<header>
<strong>Settings</strong>
<nav>
<button class:active={section === "recording"} onclick={() => (section = "recording")}
>Recording</button
>
<button class:active={section === "hardware"} onclick={() => (section = "hardware")}
>Hardware</button
>
<button class:active={section === "storage"} onclick={() => (section = "storage")}
>Storage</button
>
<button class:active={section === "calendar"} onclick={() => (section = "calendar")}
>Calendar</button
>
<button class:active={section === "sync"} onclick={() => (section = "sync")}>Sync</button>
<button class:active={section === "privacy"} onclick={() => (section = "privacy")}
>Privacy</button
>
<button class:active={section === "recording"} onclick={() => (section = "recording")}>
<Mic size={14} aria-hidden="true" /> Recording
</button>
<button class:active={section === "hardware"} onclick={() => (section = "hardware")}>
<Cpu size={14} aria-hidden="true" /> Hardware
</button>
<button class:active={section === "storage"} onclick={() => (section = "storage")}>
<HardDrive size={14} aria-hidden="true" /> Storage
</button>
<button class:active={section === "calendar"} onclick={() => (section = "calendar")}>
<CalendarDays size={14} aria-hidden="true" /> Calendar
</button>
<button class:active={section === "sync"} onclick={() => (section = "sync")}>
<UploadCloud size={14} aria-hidden="true" /> Sync
</button>
<button class:active={section === "privacy"} onclick={() => (section = "privacy")}>
<ShieldCheck size={14} aria-hidden="true" /> Privacy
</button>
</nav>
<button class="close" onclick={onClose} aria-label="Close">✕</button>
<button class="close" onclick={onClose} aria-label="Close" title="Close (Esc)">
<X size={18} aria-hidden="true" />
</button>
</header>
{#if settings.backendStub && section === "sync"}
@@ -449,11 +464,20 @@
<button class="primary" onclick={addTarget} disabled={!form.name || !form.base_url}
>Add target</button
>
{#if testResult}<span class="test">{testResult}</span>{/if}
{#if testResult}
<span class="test" class:ok={testResult.ok} class:fail={!testResult.ok}>
{#if testResult.ok}<Check size={14} aria-hidden="true" />{:else}<X
size={14}
aria-hidden="true"
/>{/if}
{testResult.message}
</span>
{/if}
</div>
{:else}
<div class="banner">
⚠ {form.kind} is a third-party cloud — uploading sends your data off your device to {form.kind}.
<AlertTriangle size={14} aria-hidden="true" />
{form.kind} is a third-party cloud — uploading sends your data off your device to {form.kind}.
</div>
<div class="grid">
<label class="wide">Name<input bind:value={form.name} placeholder={form.kind} /></label>
@@ -494,7 +518,10 @@
<h4>Egress allowlist</h4>
{#if settings.privacy.allowlistedHosts.length === 0}
<p class="muted confirm">✓ No hosts are allowlisted — WA makes no content egress.</p>
<p class="muted confirm">
<Check size={14} aria-hidden="true" />
No hosts are allowlisted — WA makes no content egress.
</p>
{:else}
<ul class="hosts">
{#each settings.privacy.allowlistedHosts as host (host)}
@@ -532,25 +559,26 @@
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.45);
background: rgba(10, 12, 16, 0.5);
display: grid;
place-items: center;
z-index: 50;
}
.panel {
background: var(--bg);
background: var(--bg-elevated);
color: var(--fg);
width: min(720px, 92vw);
max-height: 88vh;
overflow: auto;
border: 1px solid var(--border);
border-radius: 10px;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 0 1.25rem 1.25rem;
}
header {
position: sticky;
top: 0;
background: var(--bg);
background: var(--bg-elevated);
display: flex;
align-items: center;
gap: 1rem;
@@ -563,25 +591,44 @@
}
nav button,
.kinds button {
display: inline-flex;
align-items: center;
gap: 0.35rem;
background: transparent;
border: 1px solid var(--border);
color: var(--fg);
padding: 0.3rem 0.7rem;
border-radius: 6px;
padding: 0.35rem 0.7rem;
border-radius: var(--radius-sm);
font-size: 0.85rem;
cursor: pointer;
transition: background-color 120ms ease-out;
}
nav button:hover,
.kinds button:hover {
background: var(--bg-hover);
}
nav button.active,
.kinds button.active {
background: var(--fg);
color: var(--bg);
background: var(--accent);
color: var(--accent-fg);
border-color: transparent;
}
.close {
display: grid;
place-items: center;
margin-left: auto;
width: 30px;
height: 30px;
background: transparent;
border: none;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--muted);
cursor: pointer;
font-size: 1rem;
transition: background-color 120ms ease-out;
}
.close:hover {
background: var(--bg-hover);
color: var(--fg);
}
h3 {
margin: 1rem 0 0.5rem;
@@ -687,13 +734,21 @@
gap: 0.3rem;
}
.banner {
background: color-mix(in srgb, var(--danger) 14%, var(--bg));
display: flex;
align-items: flex-start;
gap: 0.4rem;
background: color-mix(in srgb, var(--warning) 14%, var(--bg));
border: 1px solid var(--border);
padding: 0.5rem 0.7rem;
border-radius: 6px;
border-radius: var(--radius-sm);
font-size: 0.85rem;
margin: 0.5rem 0;
}
.banner :global(svg) {
flex-shrink: 0;
margin-top: 0.15rem;
color: var(--warning);
}
ul.targets,
ul.models,
ul.events {
@@ -764,10 +819,22 @@
color: var(--danger);
}
.test {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.82rem;
color: var(--muted);
}
.test.ok {
color: var(--success);
}
.test.fail {
color: var(--danger);
}
.confirm {
display: flex;
align-items: center;
gap: 0.35rem;
color: var(--success);
}
ul.hosts {