feat: update appearance settings page

This commit is contained in:
zhenyi 2026-05-30 15:07:56 +08:00
parent b489296b08
commit 04798b5adb

View File

@ -1,14 +1,11 @@
import { useState, useEffect, useCallback } from "react";
import { useUserConfig, useUpdateAppearance } from "./hooks";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
const themes = [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
{ value: "system", label: "System" },
];
import { themePresets, applyTheme, getThemeById, getSavedThemeId, saveThemeId } from "@/lib/theme";
import { Check } from "lucide-react";
const codeThemes = [
{ value: "github-light", label: "GitHub Light" },
@ -26,11 +23,62 @@ const densities = [
{ value: "comfortable", label: "Comfortable" },
];
function ThemeSwatch({ preset }: { preset: (typeof themePresets)[0] }) {
const c = preset.colors;
return (
<div
className="relative h-20 w-full overflow-hidden rounded-xl border"
style={{
background: c.background,
borderColor: c.border,
}}
>
<div className="absolute inset-0 flex flex-col p-2.5 gap-1.5">
<div className="flex items-center gap-1.5">
<div
className="h-3.5 w-3.5 rounded-md"
style={{ background: c.primary }}
/>
<div
className="h-1.5 w-12 rounded-full"
style={{ background: c.mutedForeground, opacity: 0.5 }}
/>
</div>
<div className="flex gap-1">
<div
className="h-5 flex-1 rounded-md"
style={{ background: c.card, border: `1px solid ${c.border}` }}
/>
<div
className="h-5 flex-1 rounded-md"
style={{ background: c.muted }}
/>
</div>
</div>
</div>
);
}
export default function SettingsAppearancePage() {
const { data: config, isLoading } = useUserConfig();
const update = useUpdateAppearance();
const appearance = config?.appearance;
const [activeTheme, setActiveTheme] = useState(getSavedThemeId());
useEffect(() => {
setActiveTheme(getSavedThemeId());
}, []);
const handleThemeChange = useCallback((id: string) => {
setActiveTheme(id);
const preset = getThemeById(id);
if (preset) {
applyTheme(preset);
saveThemeId(id);
}
}, []);
return (
<div className="px-8 py-10">
<h1 className="text-xl font-heading font-bold text-foreground">Appearance</h1>
@ -44,28 +92,57 @@ export default function SettingsAppearancePage() {
</div>
) : (
<div className="mt-8 space-y-6">
{/* Color Theme */}
<Card>
<CardHeader>
<CardTitle>Theme</CardTitle>
<CardDescription>Select your preferred color theme</CardDescription>
<CardTitle>Color Theme</CardTitle>
<CardDescription>Choose a preset color palette for the entire interface</CardDescription>
</CardHeader>
<CardContent>
<Select
value={appearance?.theme ?? "system"}
onValueChange={(v) => update.mutate({ theme: v })}
>
<SelectTrigger className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
{themes.map((t) => (
<SelectItem key={t.value} value={t.value}>{t.label}</SelectItem>
))}
</SelectContent>
</Select>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
{themePresets.map((preset) => {
const isActive = activeTheme === preset.id;
return (
<button
key={preset.id}
onClick={() => handleThemeChange(preset.id)}
className="group relative flex flex-col gap-2 rounded-2xl border p-3 text-left transition-all hover:border-ring/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30"
style={{
borderColor: isActive ? preset.colors.ring : preset.colors.border,
background: isActive ? preset.colors.muted : preset.colors.card,
}}
>
{isActive && (
<div
className="absolute top-2.5 right-2.5 grid size-5 place-items-center rounded-full"
style={{ background: preset.colors.primary }}
>
<Check className="size-3" style={{ color: preset.colors.primaryForeground }} />
</div>
)}
<ThemeSwatch preset={preset} />
<div className="flex flex-col gap-0.5">
<span
className="text-[13px] font-semibold"
style={{ color: preset.colors.foreground }}
>
{preset.name}
</span>
<span
className="text-[11px] leading-tight"
style={{ color: preset.colors.mutedForeground }}
>
{preset.description}
</span>
</div>
</button>
);
})}
</div>
</CardContent>
</Card>
{/* Code Theme */}
<Card>
<CardHeader>
<CardTitle>Code Theme</CardTitle>
@ -88,6 +165,7 @@ export default function SettingsAppearancePage() {
</CardContent>
</Card>
{/* Layout Density */}
<Card>
<CardHeader>
<CardTitle>Layout Density</CardTitle>
@ -110,6 +188,7 @@ export default function SettingsAppearancePage() {
</CardContent>
</Card>
{/* Editor */}
<Card>
<CardHeader>
<CardTitle>Editor</CardTitle>
@ -138,4 +217,4 @@ export default function SettingsAppearancePage() {
)}
</div>
);
}
}