import { Box, Stack, Tooltip } from "@chakra-ui/react"; import { useEffect, useRef, useState } from "react"; import { useNuiEvent } from "../../../hooks/useNuiEvent"; import { debugData } from "../../../utils/debugData"; import ListItem from "./ListItem"; import Header from "./Header"; import FocusTrap from "focus-trap-react"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { fetchNui } from "../../../utils/fetchNui"; export interface MenuItem { label: string; values?: string[]; description?: string; icon?: IconProp; } interface MenuSettings { position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"; title: string; items: Array; } debugData([ { action: "setMenu", data: { // position: "bottom-left", title: "Vehicle garage", items: [ { label: "Option 1", icon: "heart" }, { label: "Option 2", icon: "basket-shopping", description: "Tooltip description 1", }, { label: "Vehicle class", values: ["Nice", "Super nice", "Extra nice"], icon: "tag", description: "Tooltip description 2", }, { label: "Option 1" }, { label: "Option 2" }, { label: "Vehicle class", values: ["Nice", "Super nice", "Extra nice"] }, { label: "Option 1" }, { label: "Option 2" }, { label: "Vehicle class", values: ["Nice", "Super nice", "Extra nice"] }, ], }, }, ]); const ListMenu: React.FC = () => { const [menu, setMenu] = useState({ position: "top-left", title: "", items: [], }); const [selected, setSelected] = useState(0); const [visible, setVisible] = useState(false); const [indexStates, setIndexStates] = useState>({}); const listRefs = useRef>([]); const closeMenu = () => { setVisible(false); fetchNui("closeMenu"); }; const moveMenu = (e: React.KeyboardEvent) => { switch (e.code) { case "ArrowDown": setSelected((selected) => { if (selected >= menu.items.length - 1) return selected; return selected + 1; }); break; case "ArrowUp": setSelected((selected) => { if (selected <= 0) return selected; return selected - 1; }); break; case "ArrowRight": if (!Array.isArray(menu.items[selected].values)) return; setIndexStates({ ...indexStates, [selected]: indexStates[selected] + 1 <= menu.items[selected].values?.length! - 1 ? indexStates[selected] + 1 : indexStates[selected], }); break; case "ArrowLeft": if (!Array.isArray(menu.items[selected].values)) return; setIndexStates({ ...indexStates, [selected]: indexStates[selected] - 1 >= 0 ? indexStates[selected] - 1 : indexStates[selected], }); break; case "Enter": if (!menu.items[selected]) return; fetchNui( "confirmSelected", Array.isArray(menu.items[selected].values) ? [selected, indexStates[selected]] : selected ); setVisible(false); break; } }; useEffect(() => { if (!menu.items[selected]) return; listRefs.current[selected]?.scrollIntoView({ block: "nearest", inline: "start", }); listRefs.current[selected]?.focus({ preventScroll: true }); // debounces the callback to avoid spam const timer = setTimeout(() => { fetchNui( "changeSelected", Array.isArray(menu.items[selected].values) ? [selected, indexStates[selected]] : selected ); }, 500); return () => clearTimeout(timer); }, [selected, menu, indexStates]); useEffect(() => { if (!visible) return; const keyHandler = (e: KeyboardEvent) => { if (["Escape", "Backspace"].includes(e.code)) closeMenu(); }; window.addEventListener("keydown", keyHandler); return () => window.removeEventListener("keydown", keyHandler); }, [visible]); useNuiEvent("setMenu", (data: MenuSettings) => { setSelected(0); if (!data.position) data.position = "top-left"; listRefs.current = []; setMenu(data); setVisible(true); let arrayIndexes: { [key: number]: number } = {}; for (let i = 0; i < data.items.length; i++) { if (Array.isArray(data.items[i].values)) arrayIndexes[i] = 0; } setIndexStates(arrayIndexes); listRefs.current[0]?.focus(); }); return ( <> {visible && (
moveMenu(e)} > {menu.items.map((item, index) => ( <> {item.label && ( )} ))} )} ); }; export default ListMenu;