Right-click or long-press the d20 button (per-combatant or Roll All) to open a context menu with Advantage and Disadvantage options. Normal left-click behavior is unchanged. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
89 lines
2.3 KiB
TypeScript
89 lines
2.3 KiB
TypeScript
import type { RollMode } from "@initiative/domain";
|
|
import { ChevronsDown, ChevronsUp } from "lucide-react";
|
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
|
|
interface RollModeMenuProps {
|
|
readonly position: { x: number; y: number };
|
|
readonly onSelect: (mode: RollMode) => void;
|
|
readonly onClose: () => void;
|
|
}
|
|
|
|
export function RollModeMenu({
|
|
position,
|
|
onSelect,
|
|
onClose,
|
|
}: RollModeMenuProps) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const [pos, setPos] = useState<{ top: number; left: number } | null>(null);
|
|
|
|
useLayoutEffect(() => {
|
|
const el = ref.current;
|
|
if (!el) return;
|
|
const rect = el.getBoundingClientRect();
|
|
const vw = document.documentElement.clientWidth;
|
|
const vh = document.documentElement.clientHeight;
|
|
|
|
let left = position.x;
|
|
let top = position.y;
|
|
|
|
if (left + rect.width > vw) left = vw - rect.width - 8;
|
|
if (left < 8) left = 8;
|
|
if (top + rect.height > vh) top = position.y - rect.height;
|
|
if (top < 8) top = 8;
|
|
|
|
setPos({ top, left });
|
|
}, [position.x, position.y]);
|
|
|
|
useEffect(() => {
|
|
function handleMouseDown(e: MouseEvent) {
|
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
onClose();
|
|
}
|
|
}
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
if (e.key === "Escape") onClose();
|
|
}
|
|
document.addEventListener("mousedown", handleMouseDown);
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
return () => {
|
|
document.removeEventListener("mousedown", handleMouseDown);
|
|
document.removeEventListener("keydown", handleKeyDown);
|
|
};
|
|
}, [onClose]);
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className="card-glow fixed z-50 min-w-40 rounded-lg border border-border bg-card py-1"
|
|
style={
|
|
pos
|
|
? { top: pos.top, left: pos.left }
|
|
: { visibility: "hidden" as const }
|
|
}
|
|
>
|
|
<button
|
|
type="button"
|
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-emerald-400 text-sm hover:bg-hover-neutral-bg"
|
|
onClick={() => {
|
|
onSelect("advantage");
|
|
onClose();
|
|
}}
|
|
>
|
|
<ChevronsUp className="h-4 w-4" />
|
|
Advantage
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-red-400 text-sm hover:bg-hover-neutral-bg"
|
|
onClick={() => {
|
|
onSelect("disadvantage");
|
|
onClose();
|
|
}}
|
|
>
|
|
<ChevronsDown className="h-4 w-4" />
|
|
Disadvantage
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|