Add dark and light theme with OS preference support
All checks were successful
CI / check (push) Successful in 1m22s
CI / build-image (push) Successful in 36s

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:
Lukas
2026-03-17 13:20:22 +01:00
parent 43780772f6
commit 2971898f0c
9 changed files with 202 additions and 13 deletions

View File

@@ -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 (