Add dark and light theme with OS preference support
Follow OS color scheme by default, with a three-way toggle (System / Light / Dark) in the kebab menu. Light theme uses warm, neutral tones with soft card-to-background contrast. Semantic colors (damage, healing, conditions) keep their hue across themes. Closes #10 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,10 @@ import {
|
||||
Import,
|
||||
Library,
|
||||
Minus,
|
||||
Monitor,
|
||||
Moon,
|
||||
Plus,
|
||||
Sun,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import React, { type RefObject, useDeferredValue, useState } from "react";
|
||||
@@ -43,6 +46,8 @@ interface ActionBarProps {
|
||||
rollAllInitiativeDisabled?: boolean;
|
||||
onOpenSourceManager?: () => void;
|
||||
autoFocus?: boolean;
|
||||
themePreference?: "system" | "light" | "dark";
|
||||
onCycleTheme?: () => void;
|
||||
}
|
||||
|
||||
function creatureKey(r: SearchResult): string {
|
||||
@@ -171,7 +176,7 @@ function AddModeSuggestions({
|
||||
>
|
||||
<Minus className="h-3 w-3" />
|
||||
</button>
|
||||
<span className="min-w-5 rounded-full bg-accent px-1.5 py-0.5 text-center font-medium text-foreground">
|
||||
<span className="min-w-5 rounded-full bg-accent px-1.5 py-0.5 text-center font-medium text-primary-foreground">
|
||||
{queued.count}
|
||||
</span>
|
||||
<button
|
||||
@@ -215,12 +220,26 @@ function AddModeSuggestions({
|
||||
);
|
||||
}
|
||||
|
||||
const THEME_ICONS = {
|
||||
system: Monitor,
|
||||
light: Sun,
|
||||
dark: Moon,
|
||||
} as const;
|
||||
|
||||
const THEME_LABELS = {
|
||||
system: "Theme: System",
|
||||
light: "Theme: Light",
|
||||
dark: "Theme: Dark",
|
||||
} as const;
|
||||
|
||||
function buildOverflowItems(opts: {
|
||||
onManagePlayers?: () => void;
|
||||
onOpenSourceManager?: () => void;
|
||||
bestiaryLoaded: boolean;
|
||||
onBulkImport?: () => void;
|
||||
bulkImportDisabled?: boolean;
|
||||
themePreference?: "system" | "light" | "dark";
|
||||
onCycleTheme?: () => void;
|
||||
}): OverflowMenuItem[] {
|
||||
const items: OverflowMenuItem[] = [];
|
||||
if (opts.onManagePlayers) {
|
||||
@@ -245,6 +264,16 @@ function buildOverflowItems(opts: {
|
||||
disabled: opts.bulkImportDisabled,
|
||||
});
|
||||
}
|
||||
if (opts.onCycleTheme) {
|
||||
const pref = opts.themePreference ?? "system";
|
||||
const ThemeIcon = THEME_ICONS[pref];
|
||||
items.push({
|
||||
icon: <ThemeIcon className="h-4 w-4" />,
|
||||
label: THEME_LABELS[pref],
|
||||
onClick: opts.onCycleTheme,
|
||||
keepOpen: true,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -265,6 +294,8 @@ export function ActionBar({
|
||||
rollAllInitiativeDisabled,
|
||||
onOpenSourceManager,
|
||||
autoFocus,
|
||||
themePreference,
|
||||
onCycleTheme,
|
||||
}: Readonly<ActionBarProps>) {
|
||||
const [nameInput, setNameInput] = useState("");
|
||||
const [suggestions, setSuggestions] = useState<SearchResult[]>([]);
|
||||
@@ -454,6 +485,8 @@ export function ActionBar({
|
||||
bestiaryLoaded,
|
||||
onBulkImport,
|
||||
bulkImportDisabled,
|
||||
themePreference,
|
||||
onCycleTheme,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user