feat: update appearance settings page
This commit is contained in:
parent
b489296b08
commit
04798b5adb
@ -1,14 +1,11 @@
|
|||||||
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { useUserConfig, useUpdateAppearance } from "./hooks";
|
import { useUserConfig, useUpdateAppearance } from "./hooks";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
|
import { themePresets, applyTheme, getThemeById, getSavedThemeId, saveThemeId } from "@/lib/theme";
|
||||||
const themes = [
|
import { Check } from "lucide-react";
|
||||||
{ value: "light", label: "Light" },
|
|
||||||
{ value: "dark", label: "Dark" },
|
|
||||||
{ value: "system", label: "System" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const codeThemes = [
|
const codeThemes = [
|
||||||
{ value: "github-light", label: "GitHub Light" },
|
{ value: "github-light", label: "GitHub Light" },
|
||||||
@ -26,11 +23,62 @@ const densities = [
|
|||||||
{ value: "comfortable", label: "Comfortable" },
|
{ 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() {
|
export default function SettingsAppearancePage() {
|
||||||
const { data: config, isLoading } = useUserConfig();
|
const { data: config, isLoading } = useUserConfig();
|
||||||
const update = useUpdateAppearance();
|
const update = useUpdateAppearance();
|
||||||
const appearance = config?.appearance;
|
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 (
|
return (
|
||||||
<div className="px-8 py-10">
|
<div className="px-8 py-10">
|
||||||
<h1 className="text-xl font-heading font-bold text-foreground">Appearance</h1>
|
<h1 className="text-xl font-heading font-bold text-foreground">Appearance</h1>
|
||||||
@ -44,28 +92,57 @@ export default function SettingsAppearancePage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-8 space-y-6">
|
<div className="mt-8 space-y-6">
|
||||||
|
{/* Color Theme */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Theme</CardTitle>
|
<CardTitle>Color Theme</CardTitle>
|
||||||
<CardDescription>Select your preferred color theme</CardDescription>
|
<CardDescription>Choose a preset color palette for the entire interface</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Select
|
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4">
|
||||||
value={appearance?.theme ?? "system"}
|
{themePresets.map((preset) => {
|
||||||
onValueChange={(v) => update.mutate({ theme: v })}
|
const isActive = activeTheme === preset.id;
|
||||||
>
|
return (
|
||||||
<SelectTrigger className="w-48">
|
<button
|
||||||
<SelectValue />
|
key={preset.id}
|
||||||
</SelectTrigger>
|
onClick={() => handleThemeChange(preset.id)}
|
||||||
<SelectContent>
|
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"
|
||||||
{themes.map((t) => (
|
style={{
|
||||||
<SelectItem key={t.value} value={t.value}>{t.label}</SelectItem>
|
borderColor: isActive ? preset.colors.ring : preset.colors.border,
|
||||||
))}
|
background: isActive ? preset.colors.muted : preset.colors.card,
|
||||||
</SelectContent>
|
}}
|
||||||
</Select>
|
>
|
||||||
|
{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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Code Theme */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Code Theme</CardTitle>
|
<CardTitle>Code Theme</CardTitle>
|
||||||
@ -88,6 +165,7 @@ export default function SettingsAppearancePage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Layout Density */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Layout Density</CardTitle>
|
<CardTitle>Layout Density</CardTitle>
|
||||||
@ -110,6 +188,7 @@ export default function SettingsAppearancePage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Editor */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Editor</CardTitle>
|
<CardTitle>Editor</CardTitle>
|
||||||
@ -138,4 +217,4 @@ export default function SettingsAppearancePage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user