Files
initiative/apps/web/src/components/settings-modal.tsx
T
Lukas e62c49434c
CI / check (push) Successful in 2m21s
CI / build-image (push) Successful in 24s
Add Pathfinder 2e game system mode
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>
2026-04-07 01:26:22 +02:00

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>
);
}