Spaces:
Running
Running
| import { MoreVertical } from "lucide-react"; | |
| import { useEffect, useRef, useState } from "react"; | |
| import { createPortal } from "react-dom"; | |
| import type { Person } from "../../types"; | |
| interface AllocationBarMenuProps { | |
| onEdit: () => void; | |
| onClone?: () => void; | |
| onSelectAllToRight?: () => void; | |
| onToggleMultiSelect?: () => void; | |
| multiSelectActive?: boolean; | |
| onDelete?: () => void; | |
| transferPeople?: Person[]; | |
| onTransferPerson?: (personId: number) => void; | |
| } | |
| export function AllocationBarMenuTrigger({ | |
| onEdit, | |
| onClone, | |
| onSelectAllToRight, | |
| onToggleMultiSelect, | |
| multiSelectActive = false, | |
| onDelete, | |
| transferPeople = [], | |
| onTransferPerson, | |
| }: AllocationBarMenuProps) { | |
| const triggerRef = useRef<HTMLButtonElement>(null); | |
| const menuRef = useRef<HTMLDivElement>(null); | |
| const [open, setOpen] = useState(false); | |
| const [transferOpen, setTransferOpen] = useState(false); | |
| const [anchor, setAnchor] = useState<{ top: number; left: number } | null>(null); | |
| useEffect(() => { | |
| if (!open) return; | |
| const onKey = (event: KeyboardEvent) => { | |
| if (event.key === "Escape") { | |
| setOpen(false); | |
| setTransferOpen(false); | |
| } | |
| }; | |
| const onPointerDown = (event: PointerEvent) => { | |
| const target = event.target as Node; | |
| if (triggerRef.current?.contains(target)) return; | |
| if (menuRef.current?.contains(target)) return; | |
| setOpen(false); | |
| setTransferOpen(false); | |
| }; | |
| window.addEventListener("keydown", onKey); | |
| const timer = window.setTimeout(() => { | |
| window.addEventListener("pointerdown", onPointerDown); | |
| }, 0); | |
| return () => { | |
| window.clearTimeout(timer); | |
| window.removeEventListener("keydown", onKey); | |
| window.removeEventListener("pointerdown", onPointerDown); | |
| }; | |
| }, [open]); | |
| const toggleMenu = (event: React.MouseEvent<HTMLButtonElement>) => { | |
| event.stopPropagation(); | |
| event.preventDefault(); | |
| if (open) { | |
| setOpen(false); | |
| setTransferOpen(false); | |
| return; | |
| } | |
| const rect = triggerRef.current?.getBoundingClientRect(); | |
| if (!rect) return; | |
| setAnchor({ top: rect.bottom + 4, left: rect.right - 200 }); | |
| setOpen(true); | |
| }; | |
| const close = () => { | |
| setOpen(false); | |
| setTransferOpen(false); | |
| }; | |
| const run = (event: React.MouseEvent, action: () => void) => { | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| close(); | |
| window.setTimeout(() => action(), 0); | |
| }; | |
| return ( | |
| <> | |
| <button | |
| aria-expanded={open} | |
| aria-label="Assignment options" | |
| className="allocation-bar-menu-btn" | |
| onClick={toggleMenu} | |
| onPointerDown={(event) => event.stopPropagation()} | |
| onPointerUp={(event) => event.stopPropagation()} | |
| ref={triggerRef} | |
| type="button" | |
| > | |
| <MoreVertical aria-hidden size={16} strokeWidth={2.75} /> | |
| </button> | |
| {open && anchor | |
| ? createPortal( | |
| <div | |
| className="allocation-bar-menu" | |
| onMouseDown={(event) => event.stopPropagation()} | |
| onPointerDown={(event) => event.stopPropagation()} | |
| ref={menuRef} | |
| style={{ position: "fixed", top: anchor.top, left: Math.max(8, anchor.left), zIndex: 3500 }} | |
| > | |
| <button | |
| className="allocation-bar-menu-item" | |
| onClick={(event) => run(event, onEdit)} | |
| onMouseDown={(event) => event.stopPropagation()} | |
| type="button" | |
| > | |
| Edit | |
| </button> | |
| {onTransferPerson && transferPeople.length > 0 ? ( | |
| <> | |
| <button | |
| className="allocation-bar-menu-item" | |
| onClick={(event) => { | |
| event.stopPropagation(); | |
| setTransferOpen((value) => !value); | |
| }} | |
| onMouseDown={(event) => event.stopPropagation()} | |
| type="button" | |
| > | |
| Transfer | |
| </button> | |
| {transferOpen ? ( | |
| <div className="allocation-bar-menu-sub"> | |
| <select | |
| onChange={(event) => { | |
| const id = Number(event.target.value); | |
| if (id) { | |
| close(); | |
| window.setTimeout(() => onTransferPerson(id), 0); | |
| } | |
| }} | |
| onMouseDown={(event) => event.stopPropagation()} | |
| value="" | |
| > | |
| <option value="">Choose person…</option> | |
| {transferPeople.map((person) => ( | |
| <option key={person.id} value={person.id}> | |
| {person.name} | |
| </option> | |
| ))} | |
| </select> | |
| </div> | |
| ) : null} | |
| </> | |
| ) : null} | |
| {onClone ? ( | |
| <button | |
| className="allocation-bar-menu-item" | |
| onClick={(event) => run(event, onClone)} | |
| onMouseDown={(event) => event.stopPropagation()} | |
| type="button" | |
| > | |
| Clone | |
| </button> | |
| ) : null} | |
| {onSelectAllToRight ? ( | |
| <button | |
| className="allocation-bar-menu-item" | |
| onClick={(event) => run(event, onSelectAllToRight)} | |
| onMouseDown={(event) => event.stopPropagation()} | |
| type="button" | |
| > | |
| Select All to Right | |
| </button> | |
| ) : null} | |
| {onToggleMultiSelect ? ( | |
| <button | |
| className="allocation-bar-menu-item" | |
| onClick={(event) => run(event, onToggleMultiSelect)} | |
| onMouseDown={(event) => event.stopPropagation()} | |
| type="button" | |
| > | |
| {multiSelectActive ? "Disable Multi-Select Mode" : "Enable Multi-Select Mode"} | |
| </button> | |
| ) : null} | |
| {onDelete ? ( | |
| <button | |
| className="allocation-bar-menu-item is-danger" | |
| onClick={(event) => run(event, onDelete)} | |
| onMouseDown={(event) => event.stopPropagation()} | |
| type="button" | |
| > | |
| Delete | |
| </button> | |
| ) : null} | |
| </div>, | |
| document.body, | |
| ) | |
| : null} | |
| </> | |
| ); | |
| } | |