import type { ActivityCost, SpellReference } from "@initiative/domain"; import { DetailPopover } from "./detail-popover.js"; import { RichDescription } from "./rich-description.js"; import { ActivityIcon } from "./stat-block-parts.js"; interface SpellDetailPopoverProps { readonly spell: SpellReference; readonly anchorRect: DOMRect; readonly onClose: () => void; } const RANK_LABELS = [ "Cantrip", "1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th", ]; function formatRank(rank: number | undefined): string { if (rank === undefined) return ""; return RANK_LABELS[rank] ?? `Rank ${rank}`; } function parseActionCost(cost: string): ActivityCost | null { if (cost === "free") return { number: 1, unit: "free" }; if (cost === "reaction") return { number: 1, unit: "reaction" }; const n = Number(cost); if (n >= 1 && n <= 3) return { number: n, unit: "action" }; return null; } function SpellActionCost({ cost }: Readonly<{ cost: string | undefined }>) { if (!cost) return null; const activity = parseActionCost(cost); if (activity) { return ( ); } return {cost}; } function SpellHeader({ spell }: Readonly<{ spell: SpellReference }>) { return (

{spell.name}

); } function SpellTraits({ traits }: Readonly<{ traits: readonly string[] }>) { if (traits.length === 0) return null; return (
{traits.map((t) => ( {t} ))}
); } function LabeledValue({ label, value, }: Readonly<{ label: string; value: string }>) { return ( <> {label} {value} ); } function SpellRangeLine({ spell }: Readonly<{ spell: SpellReference }>) { const items: { label: string; value: string }[] = []; if (spell.range) items.push({ label: "Range", value: spell.range }); if (spell.target) items.push({ label: "Target", value: spell.target }); if (spell.area) items.push({ label: "Area", value: spell.area }); if (items.length === 0) return null; return (
{items.map((item, i) => ( {i > 0 ? "; " : ""} ))}
); } function SpellMeta({ spell }: Readonly<{ spell: SpellReference }>) { const hasTraditions = spell.traditions !== undefined && spell.traditions.length > 0; return (
{spell.rank === undefined ? null : (
{formatRank(spell.rank)} {hasTraditions ? ( {" "} ({spell.traditions?.join(", ")}) ) : null}
)} {spell.duration ? (
) : null} {spell.defense ? (
) : null}
); } function SpellDetailContent({ spell }: Readonly<{ spell: SpellReference }>) { return (
{spell.description ? ( ) : (

No description available.

)} {spell.heightening ? ( ) : null}
); } export function SpellDetailPopover({ spell, anchorRect, onClose, }: Readonly) { return ( ); }