From d48b5e199b85af68c24b28ba62f5f146234beac8 Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 2 Jan 2023 13:45:19 +0100 Subject: [PATCH] refactor(web/menu): list menu mantine rewrite --- web/src/features/menu/list/CustomCheckbox.tsx | 52 ++++----- web/src/features/menu/list/Header.tsx | 37 +++--- web/src/features/menu/list/ListItem.tsx | 110 +++++++++++++----- web/src/features/menu/list/index.tsx | 87 ++++++++------ 4 files changed, 179 insertions(+), 107 deletions(-) diff --git a/web/src/features/menu/list/CustomCheckbox.tsx b/web/src/features/menu/list/CustomCheckbox.tsx index 1a28e0c..99c0961 100644 --- a/web/src/features/menu/list/CustomCheckbox.tsx +++ b/web/src/features/menu/list/CustomCheckbox.tsx @@ -1,35 +1,29 @@ -import { Box, Flex, useCheckbox, chakra, CheckboxIcon } from '@chakra-ui/react'; +import { Checkbox, createStyles, Stack } from '@mantine/core'; + +const useStyles = createStyles((theme) => ({ + root: { + display: 'flex', + alignItems: 'center', + }, + input: { + backgroundColor: theme.colors.dark[7], + '&:checked': { backgroundColor: theme.colors.dark[2], borderColor: theme.colors.dark[2] }, + }, + inner: { + '> svg > path': { + fill: theme.colors.dark[6], + }, + }, +})); const CustomCheckbox: React.FC<{ checked: boolean }> = ({ checked }) => { - const { getCheckboxProps, getInputProps, htmlProps } = useCheckbox(); + const { classes } = useStyles(); return ( - - - - {checked && ( - - - - )} - - + ); }; diff --git a/web/src/features/menu/list/Header.tsx b/web/src/features/menu/list/Header.tsx index 7a3ca1a..4d4198b 100644 --- a/web/src/features/menu/list/Header.tsx +++ b/web/src/features/menu/list/Header.tsx @@ -1,20 +1,31 @@ -import { Box, Text } from '@chakra-ui/react'; +import { Box, createStyles, Text } from '@mantine/core'; import React from 'react'; +const useStyles = createStyles((theme) => ({ + container: { + textAlign: 'center', + borderTopLeftRadius: theme.radius.md, + borderTopRightRadius: theme.radius.md, + backgroundColor: theme.colors.dark[6], + height: 60, + width: 384, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, + heading: { + fontSize: 24, + textTransform: 'uppercase', + fontWeight: 500, + }, +})); + const Header: React.FC<{ title: string }> = ({ title }) => { + const { classes } = useStyles(); + return ( - - - {title} - + + {title} ); }; diff --git a/web/src/features/menu/list/ListItem.tsx b/web/src/features/menu/list/ListItem.tsx index 8b1ec32..51caa9a 100644 --- a/web/src/features/menu/list/ListItem.tsx +++ b/web/src/features/menu/list/ListItem.tsx @@ -1,8 +1,9 @@ -import { Box, Flex, Stack, Text, Progress } from '@chakra-ui/react'; +import { Box, Group, Stack, Text, Progress } from '@mantine/core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React, { forwardRef } from 'react'; import CustomCheckbox from './CustomCheckbox'; import type { MenuItem } from './index'; +import { createStyles } from '@mantine/core'; interface Props { item: MenuItem; @@ -11,35 +12,80 @@ interface Props { checked: boolean; } +const useStyles = createStyles((theme, params: { iconColor?: string }) => ({ + buttonContainer: { + backgroundColor: theme.colors.dark[6], + borderRadius: theme.radius.md, + padding: 2, + height: 60, + scrollMargin: 8, + '&:focus': { + backgroundColor: theme.colors.dark[4], + outline: 'none', + }, + }, + buttonWrapper: { + paddingLeft: 5, + paddingRight: 12, + height: '100%', + }, + iconContainer: { + display: 'flex', + alignItems: 'center', + }, + icon: { + fontSize: 24, + color: params.iconColor || theme.colors.dark[2], + }, + label: { + color: theme.colors.dark[2], + textTransform: 'uppercase', + fontSize: 12, + verticalAlign: 'middle', + }, + chevronIcon: { + fontSize: 14, + color: theme.colors.dark[2], + }, + scrollIndexValue: { + color: theme.colors.dark[2], + textTransform: 'uppercase', + fontSize: 14, + }, + progressStack: { + width: '100%', + marginRight: 5, + }, + progressLabel: { + verticalAlign: 'middle', + marginBottom: 3, + }, +})); + const ListItem = forwardRef, Props>(({ item, index, scrollIndex, checked }, ref) => { + const { classes } = useStyles({ iconColor: item.iconColor }); + return ( { + ref={(element: HTMLDivElement) => { if (ref) // @ts-ignore i cba return (ref.current = [...ref.current, element]); }} > - + {item.icon && ( - - + + )} {Array.isArray(item.values) ? ( - - - - {item.label} - + + + {item.label} {typeof item.values[scrollIndex] === 'object' ? // @ts-ignore for some reason even checking the type TS still thinks it's a string @@ -47,30 +93,32 @@ const ListItem = forwardRef, Props>(({ item, index, : item.values[scrollIndex]} - - - + + + {scrollIndex + 1}/{item.values.length} - - - + + + ) : item.checked !== undefined ? ( - + {item.label} - + ) : item.progress !== undefined ? ( - - - {item.label} - - - + + {item.label} + ({ root: { backgroundColor: theme.colors.dark[3] } })} + /> + ) : ( {item.label} )} - + ); }); diff --git a/web/src/features/menu/list/index.tsx b/web/src/features/menu/list/index.tsx index e36d6bf..c4ffff2 100644 --- a/web/src/features/menu/list/index.tsx +++ b/web/src/features/menu/list/index.tsx @@ -1,4 +1,4 @@ -import { Box, Stack, Tooltip } from '@chakra-ui/react'; +import { Box, createStyles, Stack, Tooltip } from '@mantine/core'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useNuiEvent } from '../../../hooks/useNuiEvent'; import ListItem from './ListItem'; @@ -9,6 +9,8 @@ import { fetchNui } from '../../../utils/fetchNui'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React from 'react'; +type MenuPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + export interface MenuItem { label: string; progress?: number; @@ -23,13 +25,53 @@ export interface MenuItem { } export interface MenuSettings { - position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + position?: MenuPosition; title: string; canClose?: boolean; items: Array; startItemIndex?: number; } +const useStyles = createStyles((theme, params: { position?: MenuPosition; itemCount: number; selected: number }) => ({ + tooltip: { + backgroundColor: theme.colors.dark[6], + color: theme.colors.dark[2], + borderRadius: theme.radius.sm, + }, + container: { + position: 'absolute', + pointerEvents: 'none', + marginTop: params.position === 'top-left' || params.position === 'top-right' ? 5 : 0, + marginLeft: params.position === 'top-left' || params.position === 'bottom-left' ? 5 : 0, + marginRight: params.position === 'top-right' || params.position === 'bottom-right' ? 5 : 0, + marginBottom: params.position === 'bottom-left' || params.position === 'bottom-right' ? 5 : 0, + right: params.position === 'top-right' || params.position === 'bottom-right' ? 1 : undefined, + left: params.position === 'bottom-left' ? 1 : undefined, + bottom: params.position === 'bottom-left' || params.position === 'bottom-right' ? 1 : undefined, + fontFamily: 'Roboto', + }, + buttonsWrapper: { + height: 'fit-content', + maxHeight: 415, + overflow: 'hidden', + borderRadius: params.itemCount <= 6 || params.selected === params.itemCount - 1 ? theme.radius.md : undefined, + backgroundColor: theme.colors.dark[8], + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + }, + scrollArrow: { + backgroundColor: theme.colors.dark[8], + textAlign: 'center', + borderBottomLeftRadius: theme.radius.md, + borderBottomRightRadius: theme.radius.md, + height: 25, + }, + scrollArrowIcon: { + color: theme.colors.dark[2], + fontSize: 20, + }, +})); + const ListMenu: React.FC = () => { const [menu, setMenu] = useState({ position: 'top-left', @@ -42,6 +84,7 @@ const ListMenu: React.FC = () => { const [checkedStates, setCheckedStates] = useState>({}); const listRefs = useRef>([]); const firstRenderRef = useRef(false); + const { classes } = useStyles({ position: menu.position, itemCount: menu.items.length, selected }); const closeMenu = (ignoreFetch?: boolean, keyPressed?: string, forceClose?: boolean) => { if (menu.canClose === false && !forceClose) return; @@ -184,44 +227,20 @@ const ListMenu: React.FC = () => { menu.items[selected].values[indexStates[selected]].description : menu.items[selected].description } - isOpen={ + opened={ isValuesObject(menu.items[selected].values) ? // @ts-ignore !!menu.items[selected].values[indexStates[selected]].description : !!menu.items[selected].description } - bg="#25262B" - color="#909296" - placement="bottom" - borderRadius="md" - fontFamily="Nunito" + transitionDuration={0} + classNames={{ tooltip: classes.tooltip }} > - +
- moveMenu(e)} - > + ) => moveMenu(e)}> - + {menu.items.map((item, index) => ( {item.label && ( @@ -239,8 +258,8 @@ const ListMenu: React.FC = () => { {menu.items.length > 6 && selected !== menu.items.length - 1 && ( - - + + )}