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 { open } from "@tauri-apps/plugin-dialog";
|
||||||
import type { BackendId, SyncKind, SyncTargetConfig } from "../api";
|
import type { BackendId, SyncKind, SyncTargetConfig } from "../api";
|
||||||
import { trapFocus } from "../actions/trapFocus";
|
import { trapFocus } from "../actions/trapFocus";
|
||||||
|
import {
|
||||||
|
X,
|
||||||
|
Check,
|
||||||
|
AlertTriangle,
|
||||||
|
Mic,
|
||||||
|
Cpu,
|
||||||
|
HardDrive,
|
||||||
|
CalendarDays,
|
||||||
|
UploadCloud,
|
||||||
|
ShieldCheck,
|
||||||
|
} from "@lucide/svelte";
|
||||||
|
|
||||||
let { onClose }: { onClose: () => void } = $props();
|
let { onClose }: { onClose: () => void } = $props();
|
||||||
|
|
||||||
@@ -49,7 +60,7 @@
|
|||||||
return Math.round((p.received / p.total) * 100);
|
return Math.round((p.received / p.total) * 100);
|
||||||
}
|
}
|
||||||
let showConsent = $state(false);
|
let showConsent = $state(false);
|
||||||
let testResult = $state<string | null>(null);
|
let testResult = $state<{ ok: boolean; message: string } | null>(null);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
settings.load();
|
settings.load();
|
||||||
@@ -113,7 +124,7 @@
|
|||||||
}
|
}
|
||||||
async function testConnection() {
|
async function testConnection() {
|
||||||
const r = await settings.test(form);
|
const r = await settings.test(form);
|
||||||
testResult = (r.ok ? "✓ " : "✗ ") + r.message;
|
testResult = { ok: r.ok, message: r.message };
|
||||||
}
|
}
|
||||||
async function addTarget() {
|
async function addTarget() {
|
||||||
await settings.addTarget(form);
|
await settings.addTarget(form);
|
||||||
@@ -138,24 +149,28 @@
|
|||||||
<header>
|
<header>
|
||||||
<strong>Settings</strong>
|
<strong>Settings</strong>
|
||||||
<nav>
|
<nav>
|
||||||
<button class:active={section === "recording"} onclick={() => (section = "recording")}
|
<button class:active={section === "recording"} onclick={() => (section = "recording")}>
|
||||||
>Recording</button
|
<Mic size={14} aria-hidden="true" /> Recording
|
||||||
>
|
</button>
|
||||||
<button class:active={section === "hardware"} onclick={() => (section = "hardware")}
|
<button class:active={section === "hardware"} onclick={() => (section = "hardware")}>
|
||||||
>Hardware</button
|
<Cpu size={14} aria-hidden="true" /> Hardware
|
||||||
>
|
</button>
|
||||||
<button class:active={section === "storage"} onclick={() => (section = "storage")}
|
<button class:active={section === "storage"} onclick={() => (section = "storage")}>
|
||||||
>Storage</button
|
<HardDrive size={14} aria-hidden="true" /> Storage
|
||||||
>
|
</button>
|
||||||
<button class:active={section === "calendar"} onclick={() => (section = "calendar")}
|
<button class:active={section === "calendar"} onclick={() => (section = "calendar")}>
|
||||||
>Calendar</button
|
<CalendarDays size={14} aria-hidden="true" /> Calendar
|
||||||
>
|
</button>
|
||||||
<button class:active={section === "sync"} onclick={() => (section = "sync")}>Sync</button>
|
<button class:active={section === "sync"} onclick={() => (section = "sync")}>
|
||||||
<button class:active={section === "privacy"} onclick={() => (section = "privacy")}
|
<UploadCloud size={14} aria-hidden="true" /> Sync
|
||||||
>Privacy</button
|
</button>
|
||||||
>
|
<button class:active={section === "privacy"} onclick={() => (section = "privacy")}>
|
||||||
|
<ShieldCheck size={14} aria-hidden="true" /> Privacy
|
||||||
|
</button>
|
||||||
</nav>
|
</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>
|
</header>
|
||||||
|
|
||||||
{#if settings.backendStub && section === "sync"}
|
{#if settings.backendStub && section === "sync"}
|
||||||
@@ -449,11 +464,20 @@
|
|||||||
<button class="primary" onclick={addTarget} disabled={!form.name || !form.base_url}
|
<button class="primary" onclick={addTarget} disabled={!form.name || !form.base_url}
|
||||||
>Add target</button
|
>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>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="banner">
|
<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>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<label class="wide">Name<input bind:value={form.name} placeholder={form.kind} /></label>
|
<label class="wide">Name<input bind:value={form.name} placeholder={form.kind} /></label>
|
||||||
@@ -494,7 +518,10 @@
|
|||||||
|
|
||||||
<h4>Egress allowlist</h4>
|
<h4>Egress allowlist</h4>
|
||||||
{#if settings.privacy.allowlistedHosts.length === 0}
|
{#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}
|
{:else}
|
||||||
<ul class="hosts">
|
<ul class="hosts">
|
||||||
{#each settings.privacy.allowlistedHosts as host (host)}
|
{#each settings.privacy.allowlistedHosts as host (host)}
|
||||||
@@ -532,25 +559,26 @@
|
|||||||
.overlay {
|
.overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(0, 0, 0, 0.45);
|
background: rgba(10, 12, 16, 0.5);
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
z-index: 50;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
.panel {
|
.panel {
|
||||||
background: var(--bg);
|
background: var(--bg-elevated);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
width: min(720px, 92vw);
|
width: min(720px, 92vw);
|
||||||
max-height: 88vh;
|
max-height: 88vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 10px;
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
padding: 0 1.25rem 1.25rem;
|
padding: 0 1.25rem 1.25rem;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
background: var(--bg);
|
background: var(--bg-elevated);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
@@ -563,25 +591,44 @@
|
|||||||
}
|
}
|
||||||
nav button,
|
nav button,
|
||||||
.kinds button {
|
.kinds button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
padding: 0.3rem 0.7rem;
|
padding: 0.35rem 0.7rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 0.85rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background-color 120ms ease-out;
|
||||||
|
}
|
||||||
|
nav button:hover,
|
||||||
|
.kinds button:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
}
|
}
|
||||||
nav button.active,
|
nav button.active,
|
||||||
.kinds button.active {
|
.kinds button.active {
|
||||||
background: var(--fg);
|
background: var(--accent);
|
||||||
color: var(--bg);
|
color: var(--accent-fg);
|
||||||
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
.close {
|
.close {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
transition: background-color 120ms ease-out;
|
||||||
|
}
|
||||||
|
.close:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
margin: 1rem 0 0.5rem;
|
margin: 1rem 0 0.5rem;
|
||||||
@@ -687,13 +734,21 @@
|
|||||||
gap: 0.3rem;
|
gap: 0.3rem;
|
||||||
}
|
}
|
||||||
.banner {
|
.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);
|
border: 1px solid var(--border);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
border-radius: 6px;
|
border-radius: var(--radius-sm);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
.banner :global(svg) {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
ul.targets,
|
ul.targets,
|
||||||
ul.models,
|
ul.models,
|
||||||
ul.events {
|
ul.events {
|
||||||
@@ -764,10 +819,22 @@
|
|||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
.test {
|
.test {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
.test.ok {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
.test.fail {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
.confirm {
|
.confirm {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
}
|
}
|
||||||
ul.hosts {
|
ul.hosts {
|
||||||
|
|||||||
Reference in new issue
Block a user