Adds usable, unselectable, disableRightArrow

This commit is contained in:
Kr3mu
2025-08-30 08:47:42 +02:00
parent 7b36d7aca0
commit 8aca701c40
8 changed files with 98 additions and 52 deletions
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ESX Menu Default</title>
<script type="module" crossorigin src="./assets/index-DFrU2HgH.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DfU8TAxG.css">
<script type="module" crossorigin src="./assets/index-Begvl3m4.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DB0yiboF.css">
</head>
<body>
@@ -14,7 +14,9 @@ export interface Element {
min: number;
max: number;
options?: string[];
usable?: boolean;
unselectable?: boolean;
disableRightArrow?: boolean;
}
export interface MenuData {
@@ -63,6 +65,8 @@ const App: React.FC = () => {
};
});
console.log(JSON.stringify(data))
setCurrentMenu(data);
});
@@ -1,27 +1,24 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { ArrowRight } from "lucide-react";
import { Element } from "@/app/App";
interface Props {
label: string;
description?: string;
icon?: IconProp;
element: Element;
isSelected?: boolean;
}
const MenuButton: React.FC<Props> = ({
label,
description,
icon,
isSelected,
}) => {
const MenuButton: React.FC<Props> = ({ element, isSelected }) => {
const { label, description, icon, usable, disableRightArrow, unselectable } =
element;
const base = "rounded-[4px] flex items-center p-4 gap-4 justify-between";
const sel = isSelected
? "bg-[#FB9B041A] border border-[#FB9B04]"
: "bg-[#252525] border border-transparent";
const unSelectable = unselectable ? "opacity-80" : "";
return (
<div className={`${base} ${sel}`}>
<div className={`${base} ${sel} ${unSelectable}`}>
<div className="flex items-center gap-4">
{icon && (
<FontAwesomeIcon
@@ -58,7 +55,11 @@ const MenuButton: React.FC<Props> = ({
</div>
</div>
<ArrowRight color={isSelected ? "#FB9B04" : "white"} size="1.6rem" />
{usable !== false &&
disableRightArrow !== true &&
unselectable !== true && (
<ArrowRight color={isSelected ? "#FB9B04" : "white"} size="1.6rem" />
)}
</div>
);
};
@@ -8,7 +8,7 @@ interface Props {
}
const MenuSlider: React.FC<Props> = ({ element, isSelected }) => {
const { label, description, icon } = element;
const { label, description, icon, unselectable } = element;
const options = (element as Element).options as string[] | undefined;
@@ -24,8 +24,10 @@ const MenuSlider: React.FC<Props> = ({ element, isSelected }) => {
: "bg-[#252525] border border-transparent";
const text = isSelected ? "text-[#FB9B04]" : "text-neutral-100";
const unSelectable = unselectable ? "opacity-50" : ""
return (
<div className={`${base} ${sel}`}>
<div className={`${base} ${sel} ${unSelectable}`}>
<div className="flex items-center gap-4">
{icon && (
<FontAwesomeIcon
@@ -24,13 +24,27 @@ const Menu: React.FC<Props> = ({ data }) => {
elements,
});
const submit = () => fetchNui("menu_submit", payload());
const submit = () => {
const el = elements[position];
if (el?.usable === false) return;
fetchNui("menu_submit", payload());
};
const cancel = () =>
fetchNui("menu_cancel", { _namespace: data.namespace, _name: data.name });
const change = () => fetchNui("menu_change", payload());
const change = () => {
const el = elements[position];
if (el?.usable === false) return;
fetchNui("menu_change", payload());
};
const stepSelectedSlider = (delta: number) => {
const el = elements[position];
if (el?.usable === false) return;
if (el?.type !== "slider") return;
const options = (el as any).options as string[] | undefined;
@@ -53,7 +67,7 @@ const Menu: React.FC<Props> = ({ data }) => {
};
useControls({
length: elements.length,
elements,
position,
setPosition: (updater) => {
setPosition(updater);
@@ -76,6 +90,13 @@ const Menu: React.FC<Props> = ({ data }) => {
}
}, [position]);
useEffect(() => {
if (elements[position]?.unselectable) {
const firstSelectable = elements.findIndex((el) => !el.unselectable);
if (firstSelectable !== -1) setPosition(firstSelectable);
}
}, []);
return (
<div className={`absolute ${GetPosition(data.align)}`}>
<div className="w-[26rem] h-fit max-h-[42rem] bg-[#161616] p-4 rounded-[10px] flex flex-col gap-4">
@@ -105,12 +126,7 @@ const Menu: React.FC<Props> = ({ data }) => {
{element.type === "slider" ? (
<MenuSlider element={element} isSelected={isSelected} />
) : (
<MenuButton
label={element.label}
description={element.description}
icon={element.icon}
isSelected={isSelected}
/>
<MenuButton element={element} isSelected={isSelected} />
)}
</div>
);
@@ -1,12 +1,11 @@
// hooks/useControls.ts
import { useEffect } from "react";
export type Control = "TOP" | "DOWN" | "ENTER" | "BACKSPACE" | "LEFT" | "RIGHT";
interface Props {
length: number;
elements: any[];
position: number;
setPosition: (p: number | ((prev: number) => number)) => void; // you'll wrap this to call change()
setPosition: (p: number | ((prev: number) => number)) => void;
submit: () => void;
cancel: () => void;
changeLeft: () => void;
@@ -14,7 +13,7 @@ interface Props {
}
export const useControls = ({
length,
elements,
position,
setPosition,
submit,
@@ -28,15 +27,31 @@ export const useControls = ({
) => {
if (ev.data.action !== "controlPressed") return;
const skipUp = (p: number) => {
let next = p === 0 ? elements.length - 1 : p - 1;
while (elements[next]?.unselectable) {
next = next === 0 ? elements.length - 1 : next - 1;
}
return next;
};
const skipDown = (p: number) => {
let next = p === elements.length - 1 ? 0 : p + 1;
while (elements[next]?.unselectable) {
next = next === elements.length - 1 ? 0 : next + 1;
}
return next;
};
switch (ev.data.control) {
case "TOP":
setPosition((p) => (p === 0 ? length - 1 : p - 1));
setPosition((p) => skipUp(typeof p === "number" ? p : position));
break;
case "DOWN":
setPosition((p) => (p === length - 1 ? 0 : p + 1));
setPosition((p) => skipDown(typeof p === "number" ? p : position));
break;
case "ENTER":
submit();
if (!elements[position]?.unselectable) submit();
break;
case "BACKSPACE":
cancel();
@@ -52,5 +67,13 @@ export const useControls = ({
window.addEventListener("message", onMessage);
return () => window.removeEventListener("message", onMessage);
}, [length, position, setPosition, submit, cancel, changeLeft, changeRight]);
}, [
elements,
position,
setPosition,
submit,
cancel,
changeLeft,
changeRight,
]);
};