mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 08:27:27 +02:00
fix: Improve theme contrast and persistence
- Fix theme persistence: properly check localStorage before using default - Plan9: make blue subtler (reduce saturation), darken gradient colors for better contrast on pale yellow background - Light theme: improve contrast with darker muted foreground and borders - Change theme selector from flat list to dropdown submenu
This commit is contained in:
@@ -13,6 +13,9 @@ import {
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import Nip05 from "./nip05";
|
||||
@@ -155,33 +158,30 @@ export default function UserMenu() {
|
||||
)}
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal flex items-center gap-1.5">
|
||||
<Palette className="size-3.5" />
|
||||
<span>Theme</span>
|
||||
</DropdownMenuLabel>
|
||||
{availableThemes.map((theme) => (
|
||||
<DropdownMenuItem
|
||||
key={theme.id}
|
||||
className="cursor-crosshair"
|
||||
onClick={() => setTheme(theme.id)}
|
||||
>
|
||||
<span
|
||||
className={`size-3 rounded-full mr-2 ${
|
||||
themeId === theme.id
|
||||
? "bg-primary"
|
||||
: "bg-muted-foreground/30"
|
||||
}`}
|
||||
/>
|
||||
<span className="text-sm">{theme.name}</span>
|
||||
{themeId === theme.id && (
|
||||
<span className="ml-auto text-xs text-muted-foreground">
|
||||
active
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="cursor-crosshair">
|
||||
<Palette className="size-4 mr-2" />
|
||||
Theme
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
{availableThemes.map((theme) => (
|
||||
<DropdownMenuItem
|
||||
key={theme.id}
|
||||
className="cursor-crosshair"
|
||||
onClick={() => setTheme(theme.id)}
|
||||
>
|
||||
<span
|
||||
className={`size-2 rounded-full mr-2 ${
|
||||
themeId === theme.id
|
||||
? "bg-primary"
|
||||
: "bg-muted-foreground/30"
|
||||
}`}
|
||||
/>
|
||||
{theme.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-crosshair">
|
||||
Log out
|
||||
@@ -189,33 +189,30 @@ export default function UserMenu() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel className="text-xs text-muted-foreground font-normal flex items-center gap-1.5">
|
||||
<Palette className="size-3.5" />
|
||||
<span>Theme</span>
|
||||
</DropdownMenuLabel>
|
||||
{availableThemes.map((theme) => (
|
||||
<DropdownMenuItem
|
||||
key={theme.id}
|
||||
className="cursor-crosshair"
|
||||
onClick={() => setTheme(theme.id)}
|
||||
>
|
||||
<span
|
||||
className={`size-3 rounded-full mr-2 ${
|
||||
themeId === theme.id
|
||||
? "bg-primary"
|
||||
: "bg-muted-foreground/30"
|
||||
}`}
|
||||
/>
|
||||
<span className="text-sm">{theme.name}</span>
|
||||
{themeId === theme.id && (
|
||||
<span className="ml-auto text-xs text-muted-foreground">
|
||||
active
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="cursor-crosshair">
|
||||
<Palette className="size-4 mr-2" />
|
||||
Theme
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
{availableThemes.map((theme) => (
|
||||
<DropdownMenuItem
|
||||
key={theme.id}
|
||||
className="cursor-crosshair"
|
||||
onClick={() => setTheme(theme.id)}
|
||||
>
|
||||
<span
|
||||
className={`size-2 rounded-full mr-2 ${
|
||||
themeId === theme.id
|
||||
? "bg-primary"
|
||||
: "bg-muted-foreground/30"
|
||||
}`}
|
||||
/>
|
||||
{theme.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => setShowLogin(true)}>
|
||||
Log in
|
||||
|
||||
@@ -25,23 +25,23 @@ export const lightTheme: Theme = {
|
||||
secondary: "210 40% 96.1%",
|
||||
secondaryForeground: "222.2 47.4% 11.2%",
|
||||
|
||||
accent: "270 80% 60%",
|
||||
accent: "270 70% 55%",
|
||||
accentForeground: "0 0% 100%",
|
||||
|
||||
muted: "210 40% 96.1%",
|
||||
mutedForeground: "215.4 16.3% 46.9%",
|
||||
mutedForeground: "215.4 16.3% 40%",
|
||||
|
||||
destructive: "0 84.2% 60.2%",
|
||||
destructiveForeground: "210 40% 98%",
|
||||
destructive: "0 72% 51%",
|
||||
destructiveForeground: "0 0% 100%",
|
||||
|
||||
border: "214.3 31.8% 91.4%",
|
||||
border: "214.3 31.8% 85%",
|
||||
input: "214.3 31.8% 91.4%",
|
||||
ring: "222.2 84% 4.9%",
|
||||
|
||||
// Status colors
|
||||
success: "142 76% 36%",
|
||||
warning: "45 93% 47%",
|
||||
info: "199 89% 48%",
|
||||
// Status colors (darker for better contrast)
|
||||
success: "142 70% 30%",
|
||||
warning: "38 92% 40%",
|
||||
info: "199 80% 40%",
|
||||
},
|
||||
|
||||
syntax: {
|
||||
|
||||
@@ -27,9 +27,9 @@ export const plan9Theme: Theme = {
|
||||
popover: "60 100% 97%",
|
||||
popoverForeground: "0 0% 0%",
|
||||
|
||||
// Dark blue for interactive elements
|
||||
primary: "220 100% 25%",
|
||||
primaryForeground: "60 100% 94%",
|
||||
// Muted blue for interactive elements (subtler than before)
|
||||
primary: "220 50% 35%",
|
||||
primaryForeground: "60 100% 96%",
|
||||
|
||||
// Muted green secondary
|
||||
secondary: "120 30% 88%",
|
||||
@@ -41,54 +41,54 @@ export const plan9Theme: Theme = {
|
||||
|
||||
// Muted yellow for subdued elements
|
||||
muted: "60 30% 88%",
|
||||
mutedForeground: "0 0% 35%",
|
||||
mutedForeground: "0 0% 25%",
|
||||
|
||||
// Red for destructive
|
||||
destructive: "0 70% 45%",
|
||||
destructive: "0 70% 40%",
|
||||
destructiveForeground: "0 0% 100%",
|
||||
|
||||
// Dark blue borders
|
||||
border: "220 40% 50%",
|
||||
// Subtle borders
|
||||
border: "60 20% 75%",
|
||||
input: "60 30% 92%",
|
||||
ring: "220 100% 25%",
|
||||
ring: "220 50% 35%",
|
||||
|
||||
// Status colors
|
||||
success: "120 60% 30%",
|
||||
warning: "45 90% 45%",
|
||||
info: "200 80% 40%",
|
||||
// Status colors (darker for contrast)
|
||||
success: "120 60% 25%",
|
||||
warning: "35 90% 35%",
|
||||
info: "200 70% 35%",
|
||||
},
|
||||
|
||||
syntax: {
|
||||
// Acme-inspired syntax colors
|
||||
comment: "0 0% 45%", // Gray
|
||||
punctuation: "0 0% 25%", // Dark gray
|
||||
property: "220 100% 25%", // Dark blue
|
||||
property: "220 50% 35%", // Muted blue
|
||||
string: "120 60% 28%", // Forest green
|
||||
keyword: "280 60% 35%", // Purple
|
||||
function: "220 100% 25%", // Dark blue
|
||||
keyword: "280 50% 35%", // Muted purple
|
||||
function: "220 50% 35%", // Muted blue
|
||||
variable: "0 0% 0%", // Black
|
||||
operator: "0 0% 15%", // Near black
|
||||
|
||||
// Diff colors - subtle on yellow background
|
||||
diffInserted: "120 60% 25%",
|
||||
diffInsertedBg: "120 50% 85%",
|
||||
diffDeleted: "0 65% 40%",
|
||||
diffDeletedBg: "0 50% 90%",
|
||||
diffMeta: "200 70% 35%",
|
||||
diffMetaBg: "200 50% 88%",
|
||||
diffInsertedBg: "120 40% 85%",
|
||||
diffDeleted: "0 60% 40%",
|
||||
diffDeletedBg: "0 40% 90%",
|
||||
diffMeta: "200 50% 35%",
|
||||
diffMetaBg: "200 40% 88%",
|
||||
},
|
||||
|
||||
scrollbar: {
|
||||
thumb: "220 30% 55%",
|
||||
thumbHover: "220 40% 45%",
|
||||
track: "60 30% 90%",
|
||||
thumb: "60 20% 70%",
|
||||
thumbHover: "60 25% 60%",
|
||||
track: "60 30% 92%",
|
||||
},
|
||||
|
||||
gradient: {
|
||||
// Muted gradient for Plan9 aesthetic
|
||||
color1: "180 140 20", // Olive/mustard
|
||||
color2: "200 120 50", // Burnt orange
|
||||
color3: "100 60 180", // Muted purple
|
||||
color4: "40 160 180", // Teal
|
||||
// Darker gradient for contrast on pale yellow background
|
||||
color1: "140 110 20", // Dark olive/mustard
|
||||
color2: "180 90 40", // Dark burnt orange
|
||||
color3: "80 50 140", // Dark muted purple
|
||||
color4: "30 120 130", // Dark teal
|
||||
},
|
||||
};
|
||||
|
||||
@@ -108,9 +108,16 @@ export function ThemeProvider({
|
||||
children,
|
||||
defaultTheme,
|
||||
}: ThemeProviderProps): React.ReactElement {
|
||||
// Initialize from localStorage or default
|
||||
// Initialize from localStorage, falling back to defaultTheme prop or DEFAULT_THEME_ID
|
||||
const [themeId, setThemeIdState] = React.useState<string>(() => {
|
||||
return defaultTheme || getSavedThemeId();
|
||||
const saved = getSavedThemeId();
|
||||
// Only use defaultTheme if nothing is saved (saved returns DEFAULT_THEME_ID when empty)
|
||||
// Check localStorage directly to see if user has explicitly chosen a theme
|
||||
const hasExplicitSave = localStorage.getItem(STORAGE_KEY) !== null;
|
||||
if (hasExplicitSave) {
|
||||
return saved;
|
||||
}
|
||||
return defaultTheme || DEFAULT_THEME_ID;
|
||||
});
|
||||
|
||||
const [customThemes, setCustomThemes] = React.useState<Theme[]>(() => {
|
||||
|
||||
Reference in New Issue
Block a user