feat: add working theme selector and improve settings UI

Added fully functional theme selector and improved the settings UI with
better components and cleaner copy.

Changes:
- Created Switch component (shadcn/radix-ui) for boolean settings
- Added working theme selector that integrates with existing theme system
- Uses useTheme hook to display available themes (Dark, Light, Plan9)
- Reordered sections: Appearance first, Post second
- Reordered settings: Theme first in Appearance section
- Replaced Checkbox with Switch for better UX on boolean toggles
- Simplified copy: "Add Grimoire tag to published events" instead of listing kinds
- Simplified copy: "Display client identifiers in events" instead of "via Grimoire" mention
- Better layout: Label/description on left, Switch on right

Settings now use proper form components:
- Switch for boolean toggles (include client tag, show client tags)
- Button group for theme selection
- Clean justify-between layout for settings rows

The theme selector works immediately - clicking Dark/Light/Plan9 applies
the theme instantly via the existing ThemeProvider context.
This commit is contained in:
Claude
2026-01-21 21:47:34 +00:00
parent e3d7b5c8cd
commit a828b635a7
4 changed files with 167 additions and 48 deletions

View File

@@ -1,21 +1,73 @@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
import { Checkbox } from "./ui/checkbox";
import { Switch } from "./ui/switch";
import { Button } from "./ui/button";
import { useSettings } from "@/hooks/useSettings";
import { useTheme } from "@/lib/themes";
export function SettingsViewer() {
const { settings, updateSetting } = useSettings();
const { themeId, setTheme, availableThemes } = useTheme();
return (
<div className="h-full flex flex-col">
<Tabs defaultValue="post" className="flex-1 flex flex-col">
<Tabs defaultValue="appearance" className="flex-1 flex flex-col">
<div className="border-b px-6 py-3">
<TabsList>
<TabsTrigger value="post">Post</TabsTrigger>
<TabsTrigger value="appearance">Appearance</TabsTrigger>
<TabsTrigger value="post">Post</TabsTrigger>
</TabsList>
</div>
<div className="flex-1 overflow-y-auto">
<TabsContent value="appearance" className="m-0 p-6 space-y-6">
<div>
<h3 className="text-lg font-semibold mb-1">Appearance</h3>
<p className="text-sm text-muted-foreground">
Customize display preferences
</p>
</div>
<div className="space-y-6">
<div className="space-y-3">
<label className="text-sm">Theme</label>
<div className="flex gap-2">
{availableThemes.map((theme) => (
<Button
key={theme.id}
variant={themeId === theme.id ? "default" : "outline"}
size="sm"
onClick={() => setTheme(theme.id)}
className="capitalize"
>
{theme.name}
</Button>
))}
</div>
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<label
htmlFor="show-client-tags"
className="text-sm font-medium cursor-pointer"
>
Show client tags
</label>
<p className="text-sm text-muted-foreground">
Display client identifiers in events
</p>
</div>
<Switch
id="show-client-tags"
checked={settings?.appearance?.showClientTags ?? true}
onCheckedChange={(checked: boolean) =>
updateSetting("appearance", "showClientTags", checked)
}
/>
</div>
</div>
</TabsContent>
<TabsContent value="post" className="m-0 p-6 space-y-6">
<div>
<h3 className="text-lg font-semibold mb-1">Post Settings</h3>
@@ -25,57 +77,25 @@ export function SettingsViewer() {
</div>
<div className="space-y-4">
<div className="flex items-start space-x-3">
<Checkbox
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<label
htmlFor="include-client-tag"
className="text-sm font-medium cursor-pointer"
>
Include client tag
</label>
<p className="text-sm text-muted-foreground">
Add Grimoire tag to published events
</p>
</div>
<Switch
id="include-client-tag"
checked={settings?.post?.includeClientTag ?? true}
onCheckedChange={(checked: boolean) =>
updateSetting("post", "includeClientTag", checked)
}
/>
<div className="space-y-1">
<label
htmlFor="include-client-tag"
className="text-sm cursor-pointer"
>
Include client tag
</label>
<p className="text-sm text-muted-foreground">
Add Grimoire tag to posts, spells, and deletions
</p>
</div>
</div>
</div>
</TabsContent>
<TabsContent value="appearance" className="m-0 p-6 space-y-6">
<div>
<h3 className="text-lg font-semibold mb-1">Appearance</h3>
<p className="text-sm text-muted-foreground">
Customize display preferences
</p>
</div>
<div className="space-y-4">
<div className="flex items-start space-x-3">
<Checkbox
id="show-client-tags"
checked={settings?.appearance?.showClientTags ?? true}
onCheckedChange={(checked: boolean) =>
updateSetting("appearance", "showClientTags", checked)
}
/>
<div className="space-y-1">
<label
htmlFor="show-client-tags"
className="text-sm cursor-pointer"
>
Show client tags
</label>
<p className="text-sm text-muted-foreground">
Display "via Grimoire" and other client identifiers
</p>
</div>
</div>
</div>
</TabsContent>

View File

@@ -0,0 +1,27 @@
import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/lib/utils";
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className,
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
)}
/>
</SwitchPrimitives.Root>
));
Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch };