mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-18 23:46:03 +01:00
feat(web/menu): menu list focus trapping
This commit is contained in:
14
node_modules/.pnpm-debug.log
generated
vendored
Normal file
14
node_modules/.pnpm-debug.log
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"0 debug pnpm:scope": {
|
||||||
|
"selected": 1
|
||||||
|
},
|
||||||
|
"1 error pnpm": {
|
||||||
|
"code": "ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND",
|
||||||
|
"err": {
|
||||||
|
"name": "pnpm",
|
||||||
|
"message": "No package.json (or package.yaml, or package.json5) was found in \"C:\\GTA\\esx-server\\txData\\CFXDefault_6E51DF.base\\resources\\ox_lib\".",
|
||||||
|
"code": "ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND",
|
||||||
|
"stack": "pnpm: No package.json (or package.yaml, or package.json5) was found in \"C:\\GTA\\esx-server\\txData\\CFXDefault_6E51DF.base\\resources\\ox_lib\".\n at readProjectManifest (C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:20853:13)\n at async Object.readProjectManifestOnly (C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:20857:28)\n at async readProjectManifestOnly (C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:44022:24)\n at async Object.handler (C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:184457:24)\n at async C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:192593:21\n at async run (C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:192567:34)\n at async runPnpm (C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:192785:5)\n at async C:\\Users\\Luka-PC\\AppData\\Local\\pnpm\\global\\5\\.pnpm\\pnpm@7.5.0\\node_modules\\pnpm\\dist\\pnpm.cjs:192777:7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"@types/react": "^17.0.43",
|
"@types/react": "^17.0.43",
|
||||||
"@types/react-dom": "^17.0.14",
|
"@types/react-dom": "^17.0.14",
|
||||||
"@vitejs/plugin-react": "^1.3.2",
|
"@vitejs/plugin-react": "^1.3.2",
|
||||||
|
"focus-trap-react": "^9.0.2",
|
||||||
"framer-motion": "^6.2.8",
|
"framer-motion": "^6.2.8",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
"autoprefixer": "^10.0.2",
|
"autoprefixer": "^10.0.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"postcss": "^8.1.0",
|
"postcss": "^8.1.0",
|
||||||
|
"prop-types": "^15.8.1",
|
||||||
"rimraf": "^3.0.2"
|
"rimraf": "^3.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
web/pnpm-lock.yaml
generated
34
web/pnpm-lock.yaml
generated
@@ -23,8 +23,10 @@ specifiers:
|
|||||||
'@vitejs/plugin-react': ^1.3.2
|
'@vitejs/plugin-react': ^1.3.2
|
||||||
autoprefixer: ^10.0.2
|
autoprefixer: ^10.0.2
|
||||||
cross-env: ^7.0.3
|
cross-env: ^7.0.3
|
||||||
|
focus-trap-react: ^9.0.2
|
||||||
framer-motion: ^6.2.8
|
framer-motion: ^6.2.8
|
||||||
postcss: ^8.1.0
|
postcss: ^8.1.0
|
||||||
|
prop-types: ^15.8.1
|
||||||
react: ^17.0.2
|
react: ^17.0.2
|
||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
react-markdown: ^8.0.1
|
react-markdown: ^8.0.1
|
||||||
@@ -50,6 +52,7 @@ dependencies:
|
|||||||
'@types/react': 17.0.43
|
'@types/react': 17.0.43
|
||||||
'@types/react-dom': 17.0.14
|
'@types/react-dom': 17.0.14
|
||||||
'@vitejs/plugin-react': 1.3.2
|
'@vitejs/plugin-react': 1.3.2
|
||||||
|
focus-trap-react: 9.0.2_oxfzelaz5ynxsop2v2nu2h2m64
|
||||||
framer-motion: 6.2.8_sfoxds7t5ydpegc3knd667wn6m
|
framer-motion: 6.2.8_sfoxds7t5ydpegc3knd667wn6m
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
@@ -66,6 +69,7 @@ devDependencies:
|
|||||||
autoprefixer: 10.4.4_postcss@8.4.12
|
autoprefixer: 10.4.4_postcss@8.4.12
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
postcss: 8.4.12
|
postcss: 8.4.12
|
||||||
|
prop-types: 15.8.1
|
||||||
rimraf: 3.0.2
|
rimraf: 3.0.2
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
@@ -2333,6 +2337,26 @@ packages:
|
|||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/focus-trap-react/9.0.2_oxfzelaz5ynxsop2v2nu2h2m64:
|
||||||
|
resolution: {integrity: sha512-ZwhO5by6KG5r3dy48Lk00A1/0zNYw1Z3RZTN6O6kgAPsWFcwTFszOcQ1dLSfM8pIxpS/ttc7wTttJowjVT3jpg==}
|
||||||
|
peerDependencies:
|
||||||
|
prop-types: ^15.8.1
|
||||||
|
react: '>=16.3.0'
|
||||||
|
react-dom: '>=16.3.0'
|
||||||
|
dependencies:
|
||||||
|
focus-trap: 6.9.4
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 17.0.2
|
||||||
|
react-dom: 17.0.2_react@17.0.2
|
||||||
|
tabbable: 5.3.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/focus-trap/6.9.4:
|
||||||
|
resolution: {integrity: sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw==}
|
||||||
|
dependencies:
|
||||||
|
tabbable: 5.3.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fraction.js/4.2.0:
|
/fraction.js/4.2.0:
|
||||||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -2546,7 +2570,6 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
dev: false
|
|
||||||
|
|
||||||
/lz-string/1.4.4:
|
/lz-string/1.4.4:
|
||||||
resolution: {integrity: sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=}
|
resolution: {integrity: sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=}
|
||||||
@@ -2818,9 +2841,8 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/object-assign/4.1.1:
|
/object-assign/4.1.1:
|
||||||
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/once/1.4.0:
|
/once/1.4.0:
|
||||||
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
|
||||||
@@ -2927,7 +2949,6 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
dev: false
|
|
||||||
|
|
||||||
/property-information/6.1.1:
|
/property-information/6.1.1:
|
||||||
resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==}
|
resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==}
|
||||||
@@ -2975,7 +2996,6 @@ packages:
|
|||||||
|
|
||||||
/react-is/16.13.1:
|
/react-is/16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-is/17.0.2:
|
/react-is/17.0.2:
|
||||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||||
@@ -3230,6 +3250,10 @@ packages:
|
|||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/tabbable/5.3.3:
|
||||||
|
resolution: {integrity: sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tiny-invariant/1.2.0:
|
/tiny-invariant/1.2.0:
|
||||||
resolution: {integrity: sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==}
|
resolution: {integrity: sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|||||||
@@ -22,17 +22,17 @@ interface DialogProps {
|
|||||||
cancel?: boolean;
|
cancel?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugData<DialogProps>([
|
// debugData<DialogProps>([
|
||||||
{
|
// {
|
||||||
action: "sendAlert",
|
// action: "sendAlert",
|
||||||
data: {
|
// data: {
|
||||||
header: "Hello there",
|
// header: "Hello there",
|
||||||
content: "General kenobi \n Markdown works",
|
// content: "General kenobi \n Markdown works",
|
||||||
centered: true,
|
// centered: true,
|
||||||
cancel: true,
|
// cancel: true,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
const AlertDialog: React.FC = () => {
|
const AlertDialog: React.FC = () => {
|
||||||
const { locale } = useLocales();
|
const { locale } = useLocales();
|
||||||
@@ -77,10 +77,7 @@ const AlertDialog: React.FC = () => {
|
|||||||
{locale.ui.cancel}
|
{locale.ui.cancel}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button colorScheme={dialogData.cancel ? "blue" : undefined} onClick={() => closeAlert("confirm")}>
|
||||||
colorScheme={dialogData.cancel ? "blue" : undefined}
|
|
||||||
onClick={() => closeAlert("confirm")}
|
|
||||||
>
|
|
||||||
{locale.ui.confirm}
|
{locale.ui.confirm}
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const ListItem = forwardRef<Array<HTMLDivElement | null>, Props>(({ item, index
|
|||||||
<Text color="#909296" textTransform="uppercase" fontSize={12} verticalAlign="middle">
|
<Text color="#909296" textTransform="uppercase" fontSize={12} verticalAlign="middle">
|
||||||
{item.label}
|
{item.label}
|
||||||
</Text>
|
</Text>
|
||||||
<Text>Nice cool</Text>
|
<Text>hello</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<FontAwesomeIcon icon="arrows-left-right" fontSize={20} color="#909296" />
|
<FontAwesomeIcon icon="arrows-left-right" fontSize={20} color="#909296" />
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useNuiEvent } from "../../../hooks/useNuiEvent";
|
|||||||
import { debugData } from "../../../utils/debugData";
|
import { debugData } from "../../../utils/debugData";
|
||||||
import ListItem from "./ListItem";
|
import ListItem from "./ListItem";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
|
import FocusTrap from "focus-trap-react";
|
||||||
|
|
||||||
interface MenuSettings {
|
interface MenuSettings {
|
||||||
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
||||||
@@ -42,7 +43,7 @@ const ListMenu: React.FC = () => {
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const listRefs = useRef<Array<HTMLDivElement | null>>([]);
|
const listRefs = useRef<Array<HTMLDivElement | null>>([]);
|
||||||
|
|
||||||
const moveMenu = (e: KeyboardEvent) => {
|
const moveMenu = (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
case "ArrowDown":
|
case "ArrowDown":
|
||||||
setSelected((selected) => {
|
setSelected((selected) => {
|
||||||
@@ -63,12 +64,6 @@ const ListMenu: React.FC = () => {
|
|||||||
listRefs.current[selected]?.focus();
|
listRefs.current[selected]?.focus();
|
||||||
}, [selected]);
|
}, [selected]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!visible) return document.removeEventListener("keyup", moveMenu);
|
|
||||||
|
|
||||||
document.addEventListener("keyup", moveMenu);
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
useNuiEvent("setMenu", (data: MenuSettings) => {
|
useNuiEvent("setMenu", (data: MenuSettings) => {
|
||||||
if (!data.position) data.position = "top-left";
|
if (!data.position) data.position = "top-left";
|
||||||
setMenu(data);
|
setMenu(data);
|
||||||
@@ -86,6 +81,7 @@ const ListMenu: React.FC = () => {
|
|||||||
bg="#141517"
|
bg="#141517"
|
||||||
position="absolute"
|
position="absolute"
|
||||||
fontFamily="Nunito"
|
fontFamily="Nunito"
|
||||||
|
onKeyDown={(e) => moveMenu(e)}
|
||||||
mt={menu.position === "top-left" || menu.position === "top-right" ? 5 : 0}
|
mt={menu.position === "top-left" || menu.position === "top-right" ? 5 : 0}
|
||||||
ml={menu.position === "top-left" || menu.position === "bottom-left" ? 5 : 0}
|
ml={menu.position === "top-left" || menu.position === "bottom-left" ? 5 : 0}
|
||||||
mr={menu.position === "top-right" || menu.position === "bottom-right" ? 5 : 0}
|
mr={menu.position === "top-right" || menu.position === "bottom-right" ? 5 : 0}
|
||||||
@@ -95,11 +91,13 @@ const ListMenu: React.FC = () => {
|
|||||||
bottom={menu.position === "bottom-left" || menu.position === "bottom-right" ? 1 : undefined}
|
bottom={menu.position === "bottom-left" || menu.position === "bottom-right" ? 1 : undefined}
|
||||||
>
|
>
|
||||||
<Header title={menu.title} />
|
<Header title={menu.title} />
|
||||||
<Stack direction="column" p={2} overflowY="scroll">
|
<FocusTrap active={visible}>
|
||||||
{menu.items.map((item, index) => (
|
<Stack direction="column" p={2} overflowY="scroll">
|
||||||
<ListItem index={index} item={item} ref={listRefs} key={`menu-item-${index}`} />
|
{menu.items.map((item, index) => (
|
||||||
))}
|
<ListItem index={index} item={item} ref={listRefs} key={`menu-item-${index}`} />
|
||||||
</Stack>
|
))}
|
||||||
|
</Stack>
|
||||||
|
</FocusTrap>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user