refactor(web/context): context menu mantine rewrite

This commit is contained in:
Luke
2022-12-17 14:27:55 +01:00
parent c419474969
commit e7d86af637
4 changed files with 173 additions and 195 deletions

View File

@@ -1,11 +1,11 @@
import { useNuiEvent } from '../../../hooks/useNuiEvent'; import { useNuiEvent } from '../../../hooks/useNuiEvent';
import { Box, Text, Flex, ScaleFade } from '@chakra-ui/react'; import { Box, Stack, Text, Flex, Transition } from '@mantine/core';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ContextMenuProps } from '../../../interfaces/context'; import { ContextMenuProps } from '../../../interfaces/context';
import Item from './Item'; import ContextButton from './components/ContextButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { fetchNui } from '../../../utils/fetchNui'; import { fetchNui } from '../../../utils/fetchNui';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import HeaderButton from './components/HeaderButton';
const openMenu = (id: string | undefined) => { const openMenu = (id: string | undefined) => {
fetchNui<ContextMenuProps>('openContext', { id: id, back: true }); fetchNui<ContextMenuProps>('openContext', { id: id, back: true });
@@ -49,64 +49,30 @@ const ContextMenu: React.FC = () => {
}); });
return ( return (
<Flex position="absolute" w="75%" h="80%" justifyContent="flex-end" alignItems="center"> <Transition mounted={visible} transition="pop" duration={100} exitDuration={100}>
<ScaleFade in={visible} unmountOnExit> {(styles) => (
<Box w="xs" h={580}> <Box style={styles} sx={{ position: 'absolute', top: '15%', right: '25%' }} w={320} h={580}>
<Flex justifyContent="center" alignItems="center" mb={3}> <Flex justify="center" align="center" mb={10} gap={6}>
{contextMenu.menu && ( {contextMenu.menu && (
<Flex <HeaderButton icon="chevron-left" iconSize={16} handleClick={() => openMenu(contextMenu.menu)} />
borderRadius="md"
bg="gray.800"
flex="1 15%"
alignSelf="stretch"
textAlign="center"
justifyContent="center"
alignItems="center"
marginRight={2}
p={2}
_hover={{ bg: 'gray.700' }}
transition="300ms"
onClick={() => openMenu(contextMenu.menu)}
>
<FontAwesomeIcon icon="chevron-left" />
</Flex>
)} )}
<Box borderRadius="md" bg="gray.800" flex="1 85%"> <Box sx={{ borderRadius: 4, flex: '1 85%' }} bg="dark.6">
<Text fontFamily="Poppins" fontSize="md" p={2} textAlign="center" fontWeight="light"> <Text color="dark.0" p={6} align="center">
<ReactMarkdown>{contextMenu.title}</ReactMarkdown> <ReactMarkdown>{contextMenu.title}</ReactMarkdown>
</Text> </Text>
</Box> </Box>
<Flex <HeaderButton icon="xmark" canClose={contextMenu.canClose} iconSize={18} handleClick={closeContext} />
borderRadius="md"
as="button"
bg={contextMenu.canClose === false ? 'gray.600' : 'gray.800'}
flex="1 15%"
alignSelf="stretch"
textAlign="center"
justifyContent="center"
alignItems="center"
marginLeft={2}
p={2}
cursor={contextMenu.canClose === false ? 'not-allowed' : undefined}
_hover={{ bg: contextMenu.canClose === false ? undefined : 'gray.700' }}
transition="300ms"
onClick={() => closeContext()}
>
<FontAwesomeIcon
icon="xmark"
fontSize={20}
color={contextMenu.canClose === false ? '#718096' : undefined}
/>
</Flex>
</Flex> </Flex>
<Box maxH={560} overflowY="scroll"> <Box sx={{ height: 560, overflowY: 'scroll' }}>
{Object.entries(contextMenu.options).map((option, index) => ( <Stack spacing={3}>
<Item option={option} key={`context-item-${index}`} /> {Object.entries(contextMenu.options).map((option, index) => (
))} <ContextButton option={option} key={`context-item-${index}`} />
))}
</Stack>
</Box> </Box>
</Box> </Box>
</ScaleFade> )}
</Flex> </Transition>
); );
}; };

View File

@@ -1,142 +0,0 @@
import {
Portal,
Popover,
PopoverTrigger,
PopoverBody,
PopoverContent,
Box,
Text,
Flex,
Spacer,
Image,
HStack,
Progress,
} from '@chakra-ui/react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import ReactMarkdown from 'react-markdown';
import { Option, ContextMenuProps } from '../../../interfaces/context';
import { fetchNui } from '../../../utils/fetchNui';
const openMenu = (id: string | undefined) => {
fetchNui<ContextMenuProps>('openContext', { id: id, back: false });
};
const clickContext = (id: string) => {
fetchNui('clickContext', id);
};
const Item: React.FC<{
option: [string, Option];
}> = ({ option }) => {
const button = option[1];
const buttonKey = option[0];
return (
<>
<Popover placement="right-start" trigger="hover" eventListeners={{ scroll: true }} isLazy>
<PopoverTrigger>
<Box
bg={button.disabled ? 'gray.600' : 'gray.800'}
borderRadius="md"
h="fit-content"
w="100%"
p={2}
mb={1}
fontFamily="Poppins"
fontSize="md"
transition="300ms"
_hover={{ bg: !button.disabled && 'gray.700' }}
cursor={(button.disabled && 'not-allowed') || undefined}
>
<Flex
w="100%"
alignItems="center"
color={button.disabled ? '#718096' : undefined}
onClick={() =>
!button.disabled ? (button.menu ? openMenu(button.menu) : clickContext(buttonKey)) : null
}
>
{button?.icon && (
<FontAwesomeIcon
fixedWidth
icon={button.icon}
fontSize={20}
style={{
marginRight: 10,
justifySelf: 'center',
color: button.iconColor,
}}
/>
)}
<Box w="100%">
<Box>
<Text w="100%" fontWeight="medium" color={button.disabled ? '#718096' : undefined}>
<ReactMarkdown>{button.title ? button.title : buttonKey}</ReactMarkdown>
</Text>
</Box>
{button.description && (
<Box paddingBottom={1} color={button.disabled ? '#718096' : undefined}>
<Text><ReactMarkdown>{button.description}</ReactMarkdown></Text>
</Box>
)}
{button?.progress && (
<Progress
value={button.progress}
size="sm"
colorScheme={button.colorScheme || 'gray'}
borderRadius="md"
marginRight="5px"
/>
)}
</Box>
{(button.menu || button.arrow) && button.arrow !== false && (
<>
<Spacer />
<Box alignSelf="center" justifySelf="center" mr={4} fontSize="xl">
<FontAwesomeIcon icon="chevron-right" />
</Box>
</>
)}
</Flex>
<Portal>
{!button.disabled && (button.metadata || button.image) && (
<PopoverContent
fontFamily="Poppins"
bg="gray.800"
outline="none"
border="none"
w="fit-content"
maxW="2xs"
>
<PopoverBody>
<>
{button.image && <Image src={button.image} />}
{Array.isArray(button.metadata) ? (
button.metadata.map((metadata: string | { label: string; value: any }, index: number) => (
<Text key={`context-metadata-${index}`}>
{typeof metadata === 'string' ? `${metadata}` : `${metadata.label}: ${metadata.value}`}
</Text>
))
) : (
<>
{typeof button.metadata === 'object' &&
Object.entries(button.metadata).map((metadata: { [key: string]: any }, index) => (
<Text key={`context-metadata-${index}`}>
{metadata[0]}: {metadata[1]}
</Text>
))}
</>
)}
</>
</PopoverBody>
</PopoverContent>
)}
</Portal>
</Box>
</PopoverTrigger>
</Popover>
</>
);
};
export default Item;

View File

@@ -0,0 +1,110 @@
import { Button, Box, Group, Stack, Text, Progress, HoverCard, Image, createStyles } from '@mantine/core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import ReactMarkdown from 'react-markdown';
import { Option, ContextMenuProps } from '../../../../interfaces/context';
import { fetchNui } from '../../../../utils/fetchNui';
const openMenu = (id: string | undefined) => {
fetchNui<ContextMenuProps>('openContext', { id: id, back: false });
};
const clickContext = (id: string) => {
fetchNui('clickContext', id);
};
const useStyles = createStyles((theme, params: { disabled?: boolean }) => ({
inner: {
justifyContent: 'flex-start',
},
label: {
width: '100%',
color: params.disabled ? theme.colors.dark[3] : theme.colors.dark[0],
whiteSpace: 'pre-wrap',
},
dropdown: {
padding: 10,
color: theme.colors.dark[0],
fontSize: 14,
maxWidth: 256,
width: 'fit-content',
},
}));
const ContextButton: React.FC<{
option: [string, Option];
}> = ({ option }) => {
const button = option[1];
const buttonKey = option[0];
const { classes } = useStyles({ disabled: button.disabled });
return (
<>
<HoverCard
position="right-start"
disabled={button.disabled || !(button.metadata || button.image)}
openDelay={200}
>
<HoverCard.Target>
<Button
classNames={{ inner: classes.inner, label: classes.label }}
onClick={() => (!button.disabled ? (button.menu ? openMenu(button.menu) : clickContext(buttonKey)) : null)}
variant="default"
h="fit-content"
p={10}
fullWidth
disabled={button.disabled}
>
<Group position="apart" w="100%" noWrap>
<Stack spacing={4} style={{ flex: '1' }}>
<Group spacing={8} noWrap>
{button?.icon && (
<Stack w={25} h={25} justify="center" align="center">
<FontAwesomeIcon icon={button.icon} fixedWidth size="lg" style={{ color: button.iconColor }} />
</Stack>
)}
<Text sx={{ overflowWrap: 'break-word' }}>
<ReactMarkdown>{button.title || buttonKey}</ReactMarkdown>
</Text>
</Group>
{button.description && (
<Text size={12}>
<ReactMarkdown>{button.description}</ReactMarkdown>
</Text>
)}
{button?.progress && (
<Progress value={button.progress} size="sm" color={button.colorScheme || 'dark.3'} />
)}
</Stack>
{(button.menu || button.arrow) && button.arrow !== false && (
<Stack justify="center" w={25} h={25} align="center">
<FontAwesomeIcon icon="chevron-right" fixedWidth />
</Stack>
)}
</Group>
</Button>
</HoverCard.Target>
<HoverCard.Dropdown className={classes.dropdown}>
{button.image && <Image src={button.image} />}
{Array.isArray(button.metadata) ? (
button.metadata.map((metadata: string | { label: string; value: any }, index: number) => (
<Text key={`context-metadata-${index}`}>
{typeof metadata === 'string' ? `${metadata}` : `${metadata.label}: ${metadata.value}`}
</Text>
))
) : (
<>
{typeof button.metadata === 'object' &&
Object.entries(button.metadata).map((metadata: { [key: string]: any }, index) => (
<Text key={`context-metadata-${index}`}>
{metadata[0]}: {metadata[1]}
</Text>
))}
</>
)}
</HoverCard.Dropdown>
</HoverCard>
</>
);
};
export default ContextButton;

View File

@@ -0,0 +1,44 @@
import { Button, createStyles } from '@mantine/core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
interface Props {
icon: IconProp;
canClose?: boolean;
iconSize: number;
handleClick: () => void;
}
const useStyles = createStyles((theme, params: { canClose?: boolean }) => ({
button: {
borderRadius: 4,
flex: '1 15%',
alignSelf: 'stretch',
height: 'auto',
textAlign: 'center',
justifyContent: 'center',
padding: 2,
},
label: {
color: params.canClose ? theme.colors.dark[0] : theme.colors.dark[2],
},
}));
const HeaderButton: React.FC<Props> = ({ icon, canClose, iconSize, handleClick }) => {
const { classes } = useStyles({ canClose });
return (
<Button
variant="default"
className={classes.button}
classNames={{ label: classes.label }}
disabled={canClose === false}
onClick={handleClick}
>
<FontAwesomeIcon icon={icon} fontSize={iconSize} fixedWidth />
</Button>
);
};
export default HeaderButton;