Skip to content

Commit ebeb6aa

Browse files
committed
refactor: redesign macOS preferences UI with accordion groups
Replace the long vertical list + separate catalog with a compact accordion layout. Each category (Dock, Finder, Keyboard...) is a collapsible section showing selected/total count. Items have inline value controls. Detail view uses compact key-value rows with 2-column grid on desktop. Also fix bool value display: CLI snapshots send "1"/"0" but UI only checked for "true"/"false", causing all bools to show as OFF.
1 parent 18319b5 commit ebeb6aa

File tree

2 files changed

+329
-311
lines changed

2 files changed

+329
-311
lines changed

src/lib/components/ConfigDetail.svelte

Lines changed: 74 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { goto } from '$app/navigation';
3-
import { getCatalogItem } from '$lib/macos-prefs-catalog';
3+
import { getCatalogItem, CATALOG_CATEGORIES } from '$lib/macos-prefs-catalog';
44
55
let {
66
configUser,
@@ -144,24 +144,19 @@
144144
const snapshot = $derived(config.snapshot || {});
145145
const snapshotPkgs = $derived(snapshot.packages || {});
146146
const macosPrefs = $derived(snapshot.macos_prefs || []);
147-
interface PrefGroup {
148-
category: string;
149-
items: { pref: any; catalogItem: ReturnType<typeof getCatalogItem> }[];
150-
}
151-
const groupedMacosPrefs = $derived.by((): PrefGroup[] => {
152-
const result: PrefGroup[] = [];
147+
148+
const prefsByCategory = $derived.by(() => {
149+
const map: Record<string, { pref: any; catalogItem: ReturnType<typeof getCatalogItem> }[]> = {};
153150
for (const pref of macosPrefs) {
154151
const catalogItem = getCatalogItem(pref.domain, pref.key);
155152
const category = catalogItem?.category ?? 'Custom';
156-
const existing = result.find((g: PrefGroup) => g.category === category);
157-
if (existing) {
158-
existing.items = [...existing.items, { pref, catalogItem }];
159-
} else {
160-
result.push({ category, items: [{ pref, catalogItem }] });
161-
}
153+
if (!map[category]) map[category] = [];
154+
map[category] = [...map[category], { pref, catalogItem }];
162155
}
163-
return result;
156+
return map;
164157
});
158+
159+
const prefCategoryNames = $derived(Object.keys(prefsByCategory));
165160
const shell = $derived(snapshot.shell || {});
166161
const git = $derived(snapshot.git || {});
167162
const devToolsRaw = $derived(snapshot.dev_tools || []);
@@ -436,35 +431,31 @@
436431
{#if macosPrefs.length > 0}
437432
<section class="section">
438433
<h2 class="section-title">🍎 macOS Preferences</h2>
439-
<div class="prefs-groups">
440-
{#each groupedMacosPrefs as group}
441-
<div class="prefs-category-block">
442-
<div class="prefs-category-name">{group.category}</div>
443-
<div class="prefs-category-items">
444-
{#each group.items as { pref, catalogItem }}
445-
<div class="pref-card">
446-
<div class="pref-card-body">
447-
<div class="pref-card-label">{catalogItem?.label ?? pref.key}</div>
448-
{#if catalogItem?.description}
449-
<div class="pref-card-desc">{catalogItem.description}</div>
450-
{:else if pref.desc}
451-
<div class="pref-card-desc">{pref.desc}</div>
452-
{:else}
453-
<div class="pref-card-desc pref-card-raw">{pref.domain} · {pref.key}</div>
454-
{/if}
455-
</div>
456-
<div class="pref-card-value">
434+
<div class="prefs-accordion">
435+
{#each prefCategoryNames as cat}
436+
{@const items = prefsByCategory[cat]}
437+
<div class="prefs-acc-group">
438+
<div class="prefs-acc-header">
439+
<span class="prefs-acc-name">{cat}</span>
440+
<span class="prefs-acc-count">{items.length}</span>
441+
</div>
442+
<div class="prefs-acc-body">
443+
{#each items as { pref, catalogItem }}
444+
<div class="prefs-kv-row">
445+
<span class="prefs-kv-label">{catalogItem?.label ?? pref.key}</span>
446+
<span class="prefs-kv-value">
457447
{#if (catalogItem?.type ?? pref.type) === 'bool'}
458-
<span class="pref-bool {pref.value === 'true' ? 'pref-bool-on' : 'pref-bool-off'}">
459-
{pref.value === 'true' ? 'ON' : 'OFF'}
448+
{@const boolOn = pref.value === 'true' || pref.value === '1'}
449+
<span class="pref-bool {boolOn ? 'pref-bool-on' : 'pref-bool-off'}">
450+
{boolOn ? 'ON' : 'OFF'}
460451
</span>
461452
{:else if catalogItem?.options}
462453
{@const opt = catalogItem.options.find((o: { value: string; label: string }) => o.value === pref.value)}
463454
<span class="pref-option-val">{opt?.label ?? pref.value}</span>
464455
{:else}
465456
<span class="pref-raw-val">{pref.value}</span>
466457
{/if}
467-
</div>
458+
</span>
468459
</div>
469460
{/each}
470461
</div>
@@ -1126,77 +1117,77 @@
11261117
color: var(--text-primary);
11271118
}
11281119
1129-
.prefs-groups {
1130-
display: flex;
1131-
flex-direction: column;
1132-
gap: 28px;
1133-
}
1134-
1135-
.prefs-category-block {
1136-
display: flex;
1137-
flex-direction: column;
1138-
gap: 10px;
1120+
.prefs-accordion {
1121+
border: 1px solid var(--border);
1122+
border-radius: 12px;
1123+
overflow: hidden;
11391124
}
11401125
1141-
.prefs-category-name {
1142-
font-size: 0.75rem;
1143-
font-weight: 700;
1144-
text-transform: uppercase;
1145-
letter-spacing: 0.1em;
1146-
color: var(--text-muted);
1147-
padding-bottom: 8px;
1126+
.prefs-acc-group {
11481127
border-bottom: 1px solid var(--border);
11491128
}
11501129
1151-
.prefs-category-items {
1152-
display: flex;
1153-
flex-direction: column;
1154-
gap: 8px;
1130+
.prefs-acc-group:last-child {
1131+
border-bottom: none;
11551132
}
11561133
1157-
.pref-card {
1134+
.prefs-acc-header {
11581135
display: flex;
11591136
align-items: center;
1160-
justify-content: space-between;
1161-
gap: 16px;
1162-
padding: 14px 18px;
1137+
gap: 8px;
1138+
padding: 10px 16px;
11631139
background: var(--bg-tertiary);
1164-
border: 1px solid var(--border);
1165-
border-radius: 10px;
11661140
}
11671141
1168-
.pref-card-body {
1142+
.prefs-acc-name {
1143+
font-size: 0.75rem;
1144+
font-weight: 700;
1145+
text-transform: uppercase;
1146+
letter-spacing: 0.08em;
1147+
color: var(--text-muted);
11691148
flex: 1;
1170-
min-width: 0;
11711149
}
11721150
1173-
.pref-card-label {
1174-
font-size: 0.95rem;
1175-
font-weight: 600;
1176-
color: var(--text-primary);
1177-
margin-bottom: 3px;
1151+
.prefs-acc-count {
1152+
font-size: 0.7rem;
1153+
color: var(--text-muted);
11781154
}
11791155
1180-
.pref-card-desc {
1181-
font-size: 0.82rem;
1182-
color: var(--text-muted);
1183-
line-height: 1.4;
1156+
.prefs-acc-body {
1157+
display: grid;
1158+
grid-template-columns: 1fr;
11841159
}
11851160
1186-
.pref-card-raw {
1187-
font-family: 'JetBrains Mono', monospace;
1188-
font-size: 0.78rem;
1161+
@media (min-width: 640px) {
1162+
.prefs-acc-body {
1163+
grid-template-columns: 1fr 1fr;
1164+
}
11891165
}
11901166
1191-
.pref-card-value {
1167+
.prefs-kv-row {
1168+
display: flex;
1169+
align-items: center;
1170+
justify-content: space-between;
1171+
gap: 12px;
1172+
padding: 8px 16px;
1173+
border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
1174+
}
1175+
1176+
.prefs-kv-label {
1177+
font-size: 0.88rem;
1178+
color: var(--text-primary);
1179+
font-weight: 500;
1180+
}
1181+
1182+
.prefs-kv-value {
11921183
flex-shrink: 0;
11931184
}
11941185
11951186
.pref-bool {
11961187
display: inline-block;
1197-
padding: 4px 10px;
1198-
border-radius: 6px;
1199-
font-size: 0.78rem;
1188+
padding: 3px 8px;
1189+
border-radius: 5px;
1190+
font-size: 0.72rem;
12001191
font-weight: 700;
12011192
letter-spacing: 0.05em;
12021193
}
@@ -1216,11 +1207,11 @@
12161207
.pref-option-val,
12171208
.pref-raw-val {
12181209
font-family: 'JetBrains Mono', monospace;
1219-
font-size: 0.85rem;
1210+
font-size: 0.8rem;
12201211
color: var(--accent);
12211212
background: var(--bg-secondary);
1222-
padding: 4px 10px;
1223-
border-radius: 6px;
1213+
padding: 3px 8px;
1214+
border-radius: 5px;
12241215
border: 1px solid var(--border);
12251216
}
12261217

0 commit comments

Comments
 (0)