import { type ConditionEntry, type ConditionId, getConditionDescription, getConditionsForEdition, type PersistentDamageEntry, type PersistentDamageType, } from "@initiative/domain"; import { Check, Flame, Minus, Plus } from "lucide-react"; import React, { useLayoutEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useRulesEditionContext } from "../contexts/rules-edition-context.js"; import { useClickOutside } from "../hooks/use-click-outside.js"; import { cn } from "../lib/utils"; import { CONDITION_COLOR_CLASSES, CONDITION_ICON_MAP, } from "./condition-styles.js"; import { PersistentDamagePicker } from "./persistent-damage-picker.js"; import { Tooltip } from "./ui/tooltip.js"; interface ConditionPickerProps { anchorRef: React.RefObject; activeConditions: readonly ConditionEntry[] | undefined; activePersistentDamage?: readonly PersistentDamageEntry[]; onToggle: (conditionId: ConditionId) => void; onSetValue: (conditionId: ConditionId, value: number) => void; onAddPersistentDamage?: ( damageType: PersistentDamageType, formula: string, ) => void; onClose: () => void; } export function ConditionPicker({ anchorRef, activeConditions, activePersistentDamage, onToggle, onSetValue, onAddPersistentDamage, onClose, }: Readonly) { const ref = useRef(null); const [pos, setPos] = useState<{ top: number; left: number; maxHeight: number; } | null>(null); const [editing, setEditing] = useState<{ id: ConditionId; value: number; } | null>(null); const [showPersistentDamage, setShowPersistentDamage] = useState(false); useLayoutEffect(() => { const anchor = anchorRef.current; const el = ref.current; if (!anchor || !el) return; const anchorRect = anchor.getBoundingClientRect(); const menuHeight = el.scrollHeight; const pad = 8; const spaceBelow = window.innerHeight - anchorRect.bottom - pad; const spaceAbove = anchorRect.top - pad; const openBelow = spaceBelow >= menuHeight || spaceBelow >= spaceAbove; const top = openBelow ? anchorRect.bottom + 4 : Math.max(pad, anchorRect.top - Math.min(menuHeight, spaceAbove) - 4); const maxHeight = openBelow ? spaceBelow : Math.min(menuHeight, spaceAbove); setPos({ top, left: anchorRect.left, maxHeight }); }, [anchorRef]); useClickOutside(ref, onClose); const { edition } = useRulesEditionContext(); const conditions = getConditionsForEdition(edition); const activeMap = new Map( (activeConditions ?? []).map((e) => [e.id, e.value]), ); const showPersistentDamageEntry = edition === "pf2e" && !!onAddPersistentDamage; const persistentDamageInsertIndex = showPersistentDamageEntry ? conditions.findIndex( (d) => d.label.localeCompare("Persistent Damage") > 0, ) : -1; const persistentDamageEntry = showPersistentDamageEntry ? (
{!!showPersistentDamage && ( setShowPersistentDamage(false)} /> )}
) : null; return createPortal(
{conditions.map((def, index) => { const Icon = CONDITION_ICON_MAP[def.iconName]; if (!Icon) return null; const isActive = activeMap.has(def.id); const activeValue = activeMap.get(def.id); const isEditing = editing?.id === def.id; const colorClass = CONDITION_COLOR_CLASSES[def.color] ?? "text-muted-foreground"; const handleClick = () => { if (def.valued && edition === "pf2e") { const current = activeMap.get(def.id); setEditing({ id: def.id, value: current ?? 1, }); } else { onToggle(def.id); } }; return ( {index === persistentDamageInsertIndex && persistentDamageEntry}
{isActive && def.valued && edition === "pf2e" && !isEditing && ( {activeValue} )} {isEditing && (
{editing.value} {(() => { const atMax = def.maxValue !== undefined && editing.value >= def.maxValue; return ( ); })()}
)}
); })} {persistentDamageInsertIndex === -1 && persistentDamageEntry}
, document.body, ); }