mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-17 06:56:03 +01:00
147 lines
4.4 KiB
TypeScript
147 lines
4.4 KiB
TypeScript
import { Box, createStyles } from '@mantine/core';
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
|
import { useState } from 'react';
|
|
import { useNuiEvent } from '../../../hooks/useNuiEvent';
|
|
import { fetchNui } from '../../../utils/fetchNui';
|
|
import ScaleFade from '../../../transitions/ScaleFade';
|
|
|
|
const radius = 60;
|
|
|
|
export interface MenuItem {
|
|
icon: IconProp;
|
|
label: string;
|
|
}
|
|
|
|
const useStyles = createStyles((theme) => ({
|
|
wrapper: {
|
|
position: 'absolute',
|
|
top: '50%',
|
|
left: '50%',
|
|
transform: 'translate(-50%, -50%)',
|
|
},
|
|
sector: {
|
|
fill: theme.colors.dark[6],
|
|
color: theme.colors.dark[0],
|
|
|
|
'&:hover': {
|
|
fill: theme.fn.primaryColor(),
|
|
cursor: 'pointer',
|
|
'> g > text, > g > svg > path': {
|
|
fill: '#fff',
|
|
},
|
|
},
|
|
'> g > text': {
|
|
fill: theme.colors.dark[0],
|
|
},
|
|
},
|
|
backgroundCircle: {
|
|
fill: theme.colors.dark[6],
|
|
},
|
|
centerCircle: {
|
|
fill: theme.fn.primaryColor(),
|
|
color: '#fff',
|
|
'&:hover': {
|
|
fill: theme.colors[theme.primaryColor][theme.fn.primaryShade() - 1],
|
|
cursor: 'pointer',
|
|
},
|
|
},
|
|
centerIcon: {
|
|
color: '#fff',
|
|
pointerEvents: 'none',
|
|
},
|
|
}));
|
|
|
|
const degToRad = (deg: number) => deg * (Math.PI / 180);
|
|
|
|
const RadialMenu: React.FC = () => {
|
|
const { classes } = useStyles();
|
|
const [visible, setVisible] = useState(false);
|
|
const [menu, setMenu] = useState<{ items: MenuItem[]; sub?: boolean }>({
|
|
items: [],
|
|
sub: false,
|
|
});
|
|
|
|
useNuiEvent('openRadialMenu', (data: { items: MenuItem[]; sub?: boolean } | false) => {
|
|
if (!data) return setVisible(false);
|
|
setMenu(data);
|
|
setVisible(true);
|
|
});
|
|
|
|
useNuiEvent('refreshItems', (data: MenuItem[]) => {
|
|
setMenu({ ...menu, items: data });
|
|
});
|
|
|
|
const handleClick = (index: number) => {
|
|
fetchNui('radialClick', index);
|
|
// TODO: shouldClose
|
|
setVisible(false);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Box className={classes.wrapper}>
|
|
<ScaleFade visible={visible}>
|
|
<svg width="350px" height="350px" transform="rotate(90)">
|
|
{/*Fixed issues with background circle extending the circle when there's less than 3 items*/}
|
|
<g transform="translate(175, 175)">
|
|
<circle r={175} className={classes.backgroundCircle} />
|
|
</g>
|
|
{menu.items.map((item, index) => {
|
|
// Always draw full circle to avoid elipse circles with 2 or less items
|
|
const pieAngle = 360 / (menu.items.length < 3 ? 3 : menu.items.length);
|
|
const angle = degToRad(pieAngle / 2 + 90);
|
|
const radius = 175 * 0.65;
|
|
const iconX = 175 + Math.sin(angle) * radius;
|
|
const iconY = 175 + Math.cos(angle) * radius;
|
|
|
|
return (
|
|
<>
|
|
<g
|
|
transform={`rotate(-${index * pieAngle} 175 175)`}
|
|
className={classes.sector}
|
|
onClick={() => handleClick(index)}
|
|
>
|
|
<path
|
|
d={`M175.01,175.01 l175,0 A175.01,175.01 0 0,0 ${175 + 175 * Math.cos(-degToRad(pieAngle))}, ${
|
|
175 + 175 * Math.sin(-degToRad(pieAngle))
|
|
} z`}
|
|
/>
|
|
<g transform={`rotate(${index * pieAngle - 90} ${iconX} ${iconY})`} pointerEvents="none">
|
|
<FontAwesomeIcon
|
|
x={iconX - 12.5}
|
|
y={iconY - 17.5}
|
|
icon={item.icon}
|
|
width={25}
|
|
height={25}
|
|
fixedWidth
|
|
/>
|
|
<text x={iconX} y={iconY + 25} fill="#fff" textAnchor="middle" pointerEvents="none">
|
|
{item.label}
|
|
</text>
|
|
</g>
|
|
</g>
|
|
</>
|
|
);
|
|
})}
|
|
<g transform="translate(175, 175)" onClick={() => setVisible(false)}>
|
|
<circle r={30} className={classes.centerCircle} />
|
|
</g>
|
|
<FontAwesomeIcon
|
|
icon="xmark"
|
|
className={classes.centerIcon}
|
|
color="#fff"
|
|
width={28}
|
|
height={28}
|
|
x={175 - 28 / 2}
|
|
y={175 - 28 / 2}
|
|
/>
|
|
</svg>
|
|
</ScaleFade>
|
|
</Box>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default RadialMenu;
|