Skip to content

Commit 34e0d2a

Browse files
committed
Final round of UX polish
- AETHER header button now navigates to the Editor tab instead of opening GitHub. GitHub already has a home in the About tab, and a header logo conventionally goes home, not to a github page - Drop the redundant LightModeToggle from SettingsSidebar; the same toggle now lives as an icon button in the HeaderBar (also delete the now-unused component file) - Extract SearchIcon shared component (3 sites: HeaderBar magnifier, WallhavenBrowser empty-state icon, LocalBrowser empty-state icon) with a configurable strokeWidth prop - Extract KbdInverse shared component for the inline accent-button kbd chip; replaces the duplicated class string in both the ActionBar Apply Theme button and the WallpaperEditor Apply button - AppColorOverrides now uses the existing ExpandableSection shared component for its expand/collapse instead of a custom chevron pattern; matches the rest of the sidebar's expand UX - Add a prefers-reduced-motion guard for the chrome-surface color transitions in app.css - New Modal shared component handles the backdrop, outside-click close, and Escape dismissal in one place; ConfirmDialog, SaveDialog, and ActionBar's Export dialog all migrated to it. Removes ~35 lines of duplicated backdrop boilerplate and the six matching svelte-ignore comments.
1 parent 7925046 commit 34e0d2a

14 files changed

Lines changed: 265 additions & 304 deletions

File tree

frontend/src/app.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ main {
6161
border-color 180ms ease-out;
6262
}
6363

64+
@media (prefers-reduced-motion: reduce) {
65+
body,
66+
header,
67+
footer,
68+
aside,
69+
main {
70+
transition: none;
71+
}
72+
}
73+
6474
/* Transparent mode — applied via .transparent-widget on <html> */
6575
:root.transparent-widget,
6676
:root.transparent-widget body {

frontend/src/lib/components/blueprints/SaveDialog.svelte

Lines changed: 73 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
getAppOverrides,
1010
getAdjustments,
1111
} from '$lib/stores/theme.svelte';
12+
import Modal from '$lib/components/shared/Modal.svelte';
1213
1314
let {onclose, onsave}: {onclose: () => void; onsave: () => void} = $props();
1415
let name = $state('');
@@ -25,6 +26,28 @@
2526
attemptedSubmit && !name.trim() ? 'Theme name is required' : ''
2627
);
2728
29+
// Enter on the form commits; Esc/backdrop dismissal goes through Modal's
30+
// onclose, which we override below to step back from the override-confirm
31+
// sub-panel before fully closing.
32+
$effect(() => {
33+
const onKey = (e: KeyboardEvent) => {
34+
if (e.key === 'Enter' && !showOverrideConfirm) {
35+
e.preventDefault();
36+
handleSave();
37+
}
38+
};
39+
window.addEventListener('keydown', onKey);
40+
return () => window.removeEventListener('keydown', onKey);
41+
});
42+
43+
function handleClose() {
44+
if (showOverrideConfirm) {
45+
showOverrideConfirm = false;
46+
} else {
47+
onclose();
48+
}
49+
}
50+
2851
async function handleSave() {
2952
attemptedSubmit = true;
3053
if (!name.trim()) {
@@ -73,87 +96,57 @@
7396
isSaving = false;
7497
}
7598
}
76-
77-
function handleKeydown(e: KeyboardEvent) {
78-
if (e.key === 'Enter' && !showOverrideConfirm) handleSave();
79-
if (e.key === 'Escape') {
80-
if (showOverrideConfirm) {
81-
showOverrideConfirm = false;
82-
} else {
83-
onclose();
84-
}
85-
}
86-
}
8799
</script>
88100

89-
<!-- svelte-ignore a11y_no_static_element_interactions -->
90-
<!-- svelte-ignore a11y_click_events_have_key_events -->
91-
<div
92-
class="fixed inset-0 z-40 flex items-center justify-center bg-black/40"
93-
onclick={e => {
94-
if (e.target === e.currentTarget) onclose();
95-
}}
96-
role="presentation"
97-
>
98-
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
99-
<div
100-
class="bg-bg-secondary border-border w-80 border p-4 shadow-xl"
101-
onkeydown={handleKeydown}
102-
>
103-
{#if showOverrideConfirm}
104-
<h3 class="text-fg-primary mb-3 text-[12px] font-medium">
105-
Override existing theme?
106-
</h3>
107-
<p class="text-fg-dimmed mb-3 text-[11px]">
108-
A theme named "{name.trim()}" already exists.
101+
<Modal open={true} onclose={handleClose} z="z-40">
102+
{#if showOverrideConfirm}
103+
<h3 class="text-fg-primary mb-3 text-[12px] font-medium">
104+
Override existing theme?
105+
</h3>
106+
<p class="text-fg-dimmed mb-3 text-[11px]">
107+
A theme named "{name.trim()}" already exists.
108+
</p>
109+
<div class="flex justify-end gap-2">
110+
<button
111+
class="text-fg-dimmed hover:text-fg-secondary px-3 py-1.5 text-[11px]"
112+
onclick={() => (showOverrideConfirm = false)}>Cancel</button
113+
>
114+
<button
115+
class="bg-accent hover:bg-accent-hover px-3 py-1.5 text-[11px] font-medium text-[#111116] disabled:opacity-50"
116+
onclick={doSave}
117+
disabled={isSaving}
118+
>{isSaving ? 'Saving...' : 'Override'}</button
119+
>
120+
</div>
121+
{:else}
122+
<h3 class="text-fg-primary mb-3 text-[12px] font-medium">Save Theme</h3>
123+
<input
124+
bind:this={nameInput}
125+
type="text"
126+
class="bg-bg-surface text-fg-primary focus:border-border-focus w-full border px-2 py-1.5 text-[12px] outline-none {nameError
127+
? 'border-destructive'
128+
: 'border-border'}"
129+
placeholder="Theme name..."
130+
bind:value={name}
131+
aria-invalid={!!nameError}
132+
aria-describedby={nameError ? 'save-name-error' : undefined}
133+
/>
134+
{#if nameError}
135+
<p id="save-name-error" class="text-destructive mt-1 text-[10px]">
136+
{nameError}
109137
</p>
110-
<div class="flex justify-end gap-2">
111-
<button
112-
class="text-fg-dimmed hover:text-fg-secondary px-3 py-1.5 text-[11px]"
113-
onclick={() => (showOverrideConfirm = false)}>Cancel</button
114-
>
115-
<button
116-
class="bg-accent hover:bg-accent-hover px-3 py-1.5 text-[11px] font-medium text-[#111116] disabled:opacity-50"
117-
onclick={doSave}
118-
disabled={isSaving}
119-
>{isSaving ? 'Saving...' : 'Override'}</button
120-
>
121-
</div>
122-
{:else}
123-
<h3 class="text-fg-primary mb-3 text-[12px] font-medium">
124-
Save Theme
125-
</h3>
126-
<input
127-
bind:this={nameInput}
128-
type="text"
129-
class="bg-bg-surface text-fg-primary focus:border-border-focus w-full border px-2 py-1.5 text-[12px] outline-none {nameError
130-
? 'border-destructive'
131-
: 'border-border'}"
132-
placeholder="Theme name..."
133-
bind:value={name}
134-
aria-invalid={!!nameError}
135-
aria-describedby={nameError ? 'save-name-error' : undefined}
136-
/>
137-
{#if nameError}
138-
<p
139-
id="save-name-error"
140-
class="text-destructive mt-1 text-[10px]"
141-
>
142-
{nameError}
143-
</p>
144-
{/if}
145-
<div class="mt-3 flex justify-end gap-2">
146-
<button
147-
class="text-fg-dimmed hover:text-fg-secondary px-3 py-1.5 text-[11px]"
148-
onclick={onclose}>Cancel</button
149-
>
150-
<button
151-
class="bg-accent hover:bg-accent-hover px-3 py-1.5 text-[11px] font-medium text-[#111116] disabled:opacity-50"
152-
onclick={handleSave}
153-
disabled={!name.trim() || isSaving}
154-
>{isSaving ? 'Saving...' : 'Save'}</button
155-
>
156-
</div>
157138
{/if}
158-
</div>
159-
</div>
139+
<div class="mt-3 flex justify-end gap-2">
140+
<button
141+
class="text-fg-dimmed hover:text-fg-secondary px-3 py-1.5 text-[11px]"
142+
onclick={onclose}>Cancel</button
143+
>
144+
<button
145+
class="bg-accent hover:bg-accent-hover px-3 py-1.5 text-[11px] font-medium text-[#111116] disabled:opacity-50"
146+
onclick={handleSave}
147+
disabled={!name.trim() || isSaving}
148+
>{isSaving ? 'Saving...' : 'Save'}</button
149+
>
150+
</div>
151+
{/if}
152+
</Modal>

frontend/src/lib/components/editor/AppColorOverrides.svelte

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import {openOverrideColorPicker} from '$lib/stores/ui.svelte';
1111
import {isLightColor, copyColor} from '$lib/utils/color';
1212
import ContextMenu from '$lib/components/shared/ContextMenu.svelte';
13+
import ExpandableSection from '$lib/components/shared/ExpandableSection.svelte';
1314
import {appLabel} from '$lib/constants/apps';
1415
1516
let expanded = $state(false);
@@ -148,32 +149,11 @@
148149
</script>
149150

150151
<div>
151-
<button
152-
class="mb-2 flex w-full items-center justify-between"
153-
onclick={() => (expanded = !expanded)}
152+
<ExpandableSection
153+
title="Template Overrides"
154+
bind:expanded
155+
suffix={totalOverrideCount > 0 ? ` (${totalOverrideCount})` : ''}
154156
>
155-
<h3
156-
class="text-fg-dimmed text-[10px] font-medium uppercase tracking-wider"
157-
>
158-
Template Overrides
159-
{#if totalOverrideCount > 0}
160-
<span class="text-accent ml-1">({totalOverrideCount})</span>
161-
{/if}
162-
</h3>
163-
<svg
164-
class="text-fg-dimmed h-3 w-3 transition-transform duration-150 {expanded
165-
? 'rotate-180'
166-
: ''}"
167-
viewBox="0 0 24 24"
168-
fill="none"
169-
stroke="currentColor"
170-
stroke-width="2"
171-
>
172-
<path d="M6 9l6 6 6-6"></path>
173-
</svg>
174-
</button>
175-
176-
{#if expanded}
177157
<div class="space-y-2.5">
178158
<!-- App chip picker -->
179159
<div class="flex flex-wrap items-center gap-1">
@@ -250,7 +230,7 @@
250230
</p>
251231
{/if}
252232
</div>
253-
{/if}
233+
</ExpandableSection>
254234
</div>
255235

256236
<ContextMenu

0 commit comments

Comments
 (0)