e62c49434c
Implements PF2e as an alternative game system alongside D&D 5e/5.5e. Settings modal "Game System" selector switches conditions, bestiary, stat block layout, and initiative calculation between systems. - Valued conditions with increment/decrement UX (Clumsy 2, Frightened 3) - 2,502 PF2e creatures from bundled search index (77 sources) - PF2e stat block: level, traits, Perception, Fort/Ref/Will, ability mods - Perception-based initiative rolling - System-scoped source cache (D&D and PF2e sources don't collide) - Backwards-compatible condition rehydration (ConditionId[] → ConditionEntry[]) - Difficulty indicator hidden in PF2e mode (excluded from MVP) Closes dostulata/initiative#19 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
import type { RulesEdition } from "@initiative/domain";
|
|
import { Monitor, Moon, Sun } from "lucide-react";
|
|
import { useRulesEditionContext } from "../contexts/rules-edition-context.js";
|
|
import { useThemeContext } from "../contexts/theme-context.js";
|
|
import { cn } from "../lib/utils.js";
|
|
import { Dialog, DialogHeader } from "./ui/dialog.js";
|
|
|
|
interface SettingsModalProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const EDITION_OPTIONS: { value: RulesEdition; label: string }[] = [
|
|
{ value: "5e", label: "5e (2014)" },
|
|
{ value: "5.5e", label: "5.5e (2024)" },
|
|
{ value: "pf2e", label: "Pathfinder 2e" },
|
|
];
|
|
|
|
const THEME_OPTIONS: {
|
|
value: "system" | "light" | "dark";
|
|
label: string;
|
|
icon: typeof Sun;
|
|
}[] = [
|
|
{ value: "system", label: "System", icon: Monitor },
|
|
{ value: "light", label: "Light", icon: Sun },
|
|
{ value: "dark", label: "Dark", icon: Moon },
|
|
];
|
|
|
|
export function SettingsModal({ open, onClose }: Readonly<SettingsModalProps>) {
|
|
const { edition, setEdition } = useRulesEditionContext();
|
|
const { preference, setPreference } = useThemeContext();
|
|
|
|
return (
|
|
<Dialog open={open} onClose={onClose} className="card-glow w-full max-w-sm">
|
|
<DialogHeader title="Settings" onClose={onClose} />
|
|
|
|
<div className="flex flex-col gap-5">
|
|
<div>
|
|
<span className="mb-2 block font-medium text-muted-foreground text-sm">
|
|
Game System
|
|
</span>
|
|
<div className="flex gap-1">
|
|
{EDITION_OPTIONS.map((opt) => (
|
|
<button
|
|
key={opt.value}
|
|
type="button"
|
|
className={cn(
|
|
"flex-1 rounded-md px-3 py-1.5 text-sm transition-colors",
|
|
edition === opt.value
|
|
? "bg-accent text-primary-foreground"
|
|
: "bg-card text-muted-foreground hover:bg-hover-neutral-bg hover:text-foreground",
|
|
)}
|
|
onClick={() => setEdition(opt.value)}
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<span className="mb-2 block font-medium text-muted-foreground text-sm">
|
|
Theme
|
|
</span>
|
|
<div className="flex gap-1">
|
|
{THEME_OPTIONS.map((opt) => {
|
|
const Icon = opt.icon;
|
|
return (
|
|
<button
|
|
key={opt.value}
|
|
type="button"
|
|
className={cn(
|
|
"flex flex-1 items-center justify-center gap-1.5 rounded-md px-3 py-1.5 text-sm transition-colors",
|
|
preference === opt.value
|
|
? "bg-accent text-primary-foreground"
|
|
: "bg-card text-muted-foreground hover:bg-hover-neutral-bg hover:text-foreground",
|
|
)}
|
|
onClick={() => setPreference(opt.value)}
|
|
>
|
|
<Icon size={14} />
|
|
{opt.label}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
);
|
|
}
|