themes get their own control, and the vimazing keys
Fourteen themes was too many to keep under an avatar next to "Sign out" — that is where a preference goes when nobody has decided it matters. It now has its own button in the header, between the controls and the avatar, grouped by family. ── The keys, ported from the owner's vimazing ── ctrl+shift+l / ctrl+shift+h cycle forward / back, from anywhere, no UI ctrl+alt+space open the list j / k move the focus l or space apply the focused theme to the app BEHIND the list h back to where you started enter keep what you are looking at escape restore and close The browse is the point, and it is why the overlay is a portal over a 55% backdrop rather than a modal: you are not reading names, you are watching the application change. The theme you started on is captured when the list opens, so `h` and `escape` undo a whole browse however many previews happened inside it. Typing is still typing: the list ignores j/k/l/h while focus is in an input, textarea, select or anything contenteditable. ── Where it lives ── The key handling is in the theme package — it is behaviour that belongs to themes, and it needs no UI. The list and the button are Officer's, because a dialog would drag a component library into a package that currently has none. Verified by driving the real keyboard in a browser: start duck-dark · ctrl+shift+l -> plain · ctrl+alt+space -> overlay j, l -> tokyo-night (previewed) · escape -> plain, closed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,15 @@
|
||||
import { Link } from 'react-router';
|
||||
import * as Dropdown from '@/components/ui/dropdown-menu';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { User, LogOut, Settings, Puzzle, Bot, UserCog, Palette } from 'lucide-react';
|
||||
import { User, LogOut, Settings, Puzzle, Bot, UserCog } from 'lucide-react';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
import { useTheme } from '@/components/ui/ThemeProvider';
|
||||
|
||||
const { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } = Dropdown;
|
||||
|
||||
export function UserMenu() {
|
||||
const { user, isLoading } = useAuth();
|
||||
const { t } = useTranslation();
|
||||
const { themeId, setTheme, themes } = useTheme();
|
||||
if (isLoading) return null;
|
||||
|
||||
return (
|
||||
@@ -27,21 +25,6 @@ export function UserMenu() {
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="z-10 w-48">
|
||||
{/*
|
||||
Every theme this build ships. This replaced a Light/Dark toggle, which had become a second way to
|
||||
do the same thing — light and dark are two themes now, both already in this list, and the toggle
|
||||
was the last thing writing the legacy `appearance.colorMode`.
|
||||
|
||||
Deliberately the crudest possible control: proving the app can wear another skin, not designing a
|
||||
picker. A real one belongs in an appearance settings screen, which does not exist yet.
|
||||
*/}
|
||||
{themes.map((theme) => (
|
||||
<DropdownMenuItem key={theme.id} onClick={() => setTheme(theme.id)} className="cursor-pointer">
|
||||
<Palette className={`mr-2 h-4 w-4 ${theme.id === themeId ? 'opacity-100' : 'opacity-30'}`} />
|
||||
{theme.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem asChild className="cursor-pointer">
|
||||
<Link to="/settings/profile">
|
||||
<User className="mr-2 h-4 w-4" />
|
||||
|
||||
Reference in New Issue
Block a user