import { type ConditionId, getConditionDescription, getConditionsForEdition, } from "@initiative/domain"; import type { LucideIcon } from "lucide-react"; import { ArrowDown, Ban, BatteryLow, Droplet, EarOff, EyeOff, Gem, Ghost, Hand, Heart, Link, Moon, ShieldMinus, Siren, Snail, Sparkles, ZapOff, } from "lucide-react"; import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useRulesEditionContext } from "../contexts/rules-edition-context.js"; import { cn } from "../lib/utils"; import { Tooltip } from "./ui/tooltip.js"; const ICON_MAP: Record = { EyeOff, Heart, EarOff, BatteryLow, Siren, Hand, Ban, Ghost, ZapOff, Gem, Droplet, ArrowDown, Link, ShieldMinus, Snail, Sparkles, Moon, }; const COLOR_CLASSES: Record = { neutral: "text-muted-foreground", pink: "text-pink-400", amber: "text-amber-400", orange: "text-orange-400", gray: "text-gray-400", violet: "text-violet-400", yellow: "text-yellow-400", slate: "text-slate-400", green: "text-green-400", indigo: "text-indigo-400", sky: "text-sky-400", }; interface ConditionPickerProps { anchorRef: React.RefObject; activeConditions: readonly ConditionId[] | undefined; onToggle: (conditionId: ConditionId) => void; onClose: () => void; } export function ConditionPicker({ anchorRef, activeConditions, onToggle, onClose, }: Readonly) { const ref = useRef(null); const [pos, setPos] = useState<{ top: number; left: number; maxHeight: number; } | null>(null); 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]); useEffect(() => { function handleClickOutside(e: MouseEvent) { if (ref.current && !ref.current.contains(e.target as Node)) { onClose(); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [onClose]); const { edition } = useRulesEditionContext(); const conditions = getConditionsForEdition(edition); const active = new Set(activeConditions ?? []); return createPortal(
{conditions.map((def) => { const Icon = ICON_MAP[def.iconName]; if (!Icon) return null; const isActive = active.has(def.id); const colorClass = COLOR_CLASSES[def.color] ?? "text-muted-foreground"; return ( ); })}
, document.body, ); }