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:
1 parent
ecf38d5313
commit
df82b0636d
1 file changed
+101
-34
+101
-34
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user