diff --git a/package.json b/package.json index 4fcabc246..374e5a750 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bldrs", - "version": "1.0.0-r730", + "version": "1.0.0-r702", "main": "src/index.jsx", "license": "MIT", "homepage": "https://github.com/bldrs-ai/Share", diff --git a/src/Components/ControlsGroup.jsx b/src/Components/ControlsGroup.jsx index d42ad82bd..73f0f48b4 100644 --- a/src/Components/ControlsGroup.jsx +++ b/src/Components/ControlsGroup.jsx @@ -3,64 +3,43 @@ import ButtonGroup from '@mui/material/ButtonGroup' import OpenModelControl from './OpenModelControl' import useStore from '../store/useStore' import {TooltipIconButton} from './Buttons' -import HistoryIcon from '@mui/icons-material/History' -import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined' +import SearchIcon from '@mui/icons-material/Search' import TreeIcon from '../assets/icons/Tree.svg' /** - * OperationsGroup contains tools for sharing, notes, properties, cut - * plane, deselect, theme change and about. + * Controls gropup contains visibility toggle for serach, spatial navigation and search * - * @property {Function} deselectItems deselects currently selected element + * @property {Function} fileOpen function that is passed to to the openControl for open localfiles * @return {React.Component} */ -export default function OperationsGroup({fileOpen}) { - const cutPlanes = useStore((state) => state.cutPlanes) - const levelInstance = useStore((state) => state.levelInstance) - const selectedElement = useStore((state) => state.selectedElement) - - const isSelected = () => { - const ifSelected = ( - selectedElement !== null || - cutPlanes.length !== 0 || - levelInstance !== null - ) - return ifSelected - } +export default function ControlsGroup({fileOpen}) { + const toggleIsNavigationVisible = useStore((state) => state.toggleIsNavigationVisible) + const isNavigationVisible = useStore((state) => state.isNavigationVisible) + const toggleIsSearchVisible = useStore((state) => state.toggleIsSearchVisible) + const isSearchVisible = useStore((state) => state.isSearchVisible) return ( - - {/* } - placement='bottom' - selected={true} - onClick={() => (isSelected)} - /> */} + } + icon={} placement='bottom' aboutInfo={false} - selected={true} - onClick={() => (isSelected)} + selected={isSearchVisible} + onClick={toggleIsSearchVisible} /> } placement='bottom' aboutInfo={false} - selected={true} - onClick={() => (isSelected)} - /> - } - placement='bottom' - selected={true} - onClick={() => (isSelected)} + selected={isNavigationVisible} + onClick={toggleIsNavigationVisible} /> ) diff --git a/src/Components/ControlsGroup.test.jsx b/src/Components/ControlsGroup.test.jsx new file mode 100644 index 000000000..68f8f163c --- /dev/null +++ b/src/Components/ControlsGroup.test.jsx @@ -0,0 +1,29 @@ +import React from 'react' +import {render, screen} from '@testing-library/react' +import ControlsGroup from './ControlsGroup' + + +jest.mock('./Buttons', () => ({ + TooltipIconButton: (props) => , +})) + + +describe('ControlsGroup', () => { + it('renders the search toggle', () => { + render() + const searchToggle = screen.getByText('Search') + expect(searchToggle).toBeInTheDocument() + }) + + it('renders the navigation toggle', () => { + render() + const navigationToggle = screen.getByText('Navigation') + expect(navigationToggle).toBeInTheDocument() + }) + + it('renders open model control button', () => { + render() + const navigationToggle = screen.getByText('Open IFC') + expect(navigationToggle).toBeInTheDocument() + }) +}) diff --git a/src/Components/InputAutocomplete.jsx b/src/Components/InputAutocomplete.jsx index ef7b24a02..d2f8390d0 100644 --- a/src/Components/InputAutocomplete.jsx +++ b/src/Components/InputAutocomplete.jsx @@ -1,38 +1,107 @@ import React from 'react' +import Paper from '@mui/material/Paper' import Autocomplete from '@mui/material/Autocomplete' +import InputAdornment from '@mui/material/InputAdornment' +import IconButton from '@mui/material/IconButton' import TextField from '@mui/material/TextField' -import Stack from '@mui/material/Stack' -import {assertDefined} from '../utils/assert' +import HighlightOffIcon from '@mui/icons-material/HighlightOff' +import useTheme from '@mui/styles/useTheme' + /** - * Input with autocomplete feature. + * InputAutocomplete Component. * - * @property {Array} elements suggested elements used to autocomple input,the object is in a shape of {title:'suggestion'} - * @property {string} placeholder Input placeholder - * @property {string} size MUI size of the input component + * @param {string} inputText - The input text value. + * @param {Function} setInputText - Function to set the input text. + * @param {string} error - The error message. + * @param {Function} onClear - Function to be executed when the clear button is clicked. + * @param {Array} options - Array of options for the autocomplete. + * @param {React.ReactNode} startAdornment - Start adornment for the input. + * @param {string} placeholder - Placeholder text for the input. * @return {React.Component} */ -export default function InputAutocomplete({elements, placeholder, size = 'small'}) { - assertDefined(elements, placeholder) +function InputAutocomplete({ + inputText, + setInputText, + error = '', // Set default value + onClear, + options = [], + startAdornment = null, + placeholder = '', +}) { + const theme = useTheme() + return ( - + option.title} - filterSelectedOptions - size={size} - renderInput={(params) => { - return ( - - ) - } - } + fullWidth + freeSolo + options={options} + value={inputText} + onChange={(_, newValue) => setInputText(newValue || '')} + onInputChange={(_, newInputValue) => setInputText(newInputValue || '')} + inputValue={inputText} + PaperComponent={({children}) => ( + + {children} + + )} + renderInput={(params) => ( + 0 ? ( + + + + + + ) : null, + }} + /> + )} /> - + ) } + +export default InputAutocomplete diff --git a/src/Components/InputAutocomplete.test.jsx b/src/Components/InputAutocomplete.test.jsx index 906df40a9..8f9188f6f 100644 --- a/src/Components/InputAutocomplete.test.jsx +++ b/src/Components/InputAutocomplete.test.jsx @@ -1,41 +1,130 @@ import React from 'react' import {render, fireEvent} from '@testing-library/react' -import InputAutocomplete from './InputAutocomplete' // Adjust the import path +import {ThemeProvider, createTheme} from '@mui/material/styles' +import InputAutocomplete from './InputAutocomplete' describe('InputAutocomplete', () => { - const elements = [ - {title: 'Option 1'}, - {title: 'Option 2'}, - {title: 'Option 3'}, - ] - - it('renders the input with placeholder', () => { - const placeholderText = 'Type something' - const {getByPlaceholderText} = render( - , + const theme = createTheme({ + palette: { + scene: { + background: '#fafafa', + }, + }, + }) + + const renderWithTheme = (component) => { + return render({component}) + } + const mockOnClear = jest.fn() + const setInputTextMock = jest.fn() + const initialText = 'Option 1' + + it('renders the component', () => { + const {getByRole} = renderWithTheme( + ) + expect(getByRole('combobox')).toBeInTheDocument() + }) + + it('renders with the correct initial input text', () => { + const {getByRole} = renderWithTheme( + , + ) + const input = getByRole('combobox') + expect(input.value).toBe(initialText) + }) + + it('updates the displayed input value when prop changes', () => { + const {getByRole, rerender} = renderWithTheme( + , + ) + + const updatedText = 'Option 2' + rerender( + + + , + ) + + const input = getByRole('combobox') + expect(input.value).toBe(updatedText) + }) + + it('renders input with given placeholder', () => { + const placeholderText = 'Search something...' + const {getByPlaceholderText} = renderWithTheme( + , + ) + expect(getByPlaceholderText(placeholderText)).toBeInTheDocument() + }) + + it('displays the error state when error prop is passed', () => { + const errorMsg = 'Sample error' + const {getByRole} = renderWithTheme( + , ) - const inputElement = getByPlaceholderText(placeholderText) - expect(inputElement).toBeInTheDocument() + const input = getByRole('combobox') + expect(input).toHaveAttribute('aria-invalid', 'true') }) - it('displays suggestions when typing', () => { - const {getByPlaceholderText, getByText} = render( - , + it('displays options based on partial input string', () => { + const {getByRole, getByText} = renderWithTheme( + , ) - const inputElement = getByPlaceholderText('Type something') + const input = getByRole('combobox') - // Type some text into the input - fireEvent.change(inputElement, {target: {value: 'Option'}}) + // Simulating user typing 'Opt' in the input + fireEvent.change(input, {target: {value: 'Opt'}}) - // Wait for suggestions to appear - const suggestion1 = getByText('Option 1') - const suggestion2 = getByText('Option 2') - const suggestion3 = getByText('Option 3') + // Simulating focus on the input to trigger the dropdown + fireEvent.focus(input) - expect(suggestion1).toBeInTheDocument() - expect(suggestion2).toBeInTheDocument() - expect(suggestion3).toBeInTheDocument() + // Check if the options that contain 'Opt' are present in the document + expect(getByText('Option 1')).toBeInTheDocument() + expect(getByText('Option 2')).toBeInTheDocument() }) }) diff --git a/src/Components/Logo.jsx b/src/Components/Logo.jsx index 05a27a036..841e2d6f8 100644 --- a/src/Components/Logo.jsx +++ b/src/Components/Logo.jsx @@ -18,6 +18,7 @@ export default function Logo({onClick}) { 'position': 'fixed', 'bottom': '1em', 'left': '1em', + 'boxShadow': theme.shadows[1], '& svg': { 'marginBottom': '4px', 'marginTop': '4px', diff --git a/src/Components/NavPanel.jsx b/src/Components/NavPanel.jsx index c4f6f82ec..c4dedf82c 100644 --- a/src/Components/NavPanel.jsx +++ b/src/Components/NavPanel.jsx @@ -1,10 +1,14 @@ import React from 'react' import AccountTreeIcon from '@mui/icons-material/AccountTree' +import Box from '@mui/material/Box' +import IconButton from '@mui/material/IconButton' import ListIcon from '@mui/icons-material/List' import Paper from '@mui/material/Paper' +import Stack from '@mui/material/Stack' import ToggleButton from '@mui/material/ToggleButton' import ToggleButtonGroup from '@mui/material/ToggleButtonGroup' import Tooltip from '@mui/material/Tooltip' +import Typography from '@mui/material/Typography' import TreeView from '@mui/lab/TreeView' import {styled} from '@mui/material/styles' import NavTree from './NavTree' @@ -14,6 +18,7 @@ import {assertDefined} from '../utils/assert' import {useExistInFeature} from '../hooks/useExistInFeature' import NodeClosedIcon from '../assets/icons/NodeClosed.svg' import NodeOpenIcon from '../assets/icons/NodeOpened.svg' +import CloseIcon from '@mui/icons-material/Close' /** @@ -42,12 +47,8 @@ export default function NavPanel({ }) { assertDefined(...arguments) const selectedElements = useStore((state) => state.selectedElements) - // TODO(pablo): the defaultExpanded array can contain bogus IDs with - // no error. Not sure of a better way to pre-open the first few - // nodes besides hardcoding. - const elementTypesMap = useStore((state) => state.elementTypesMap) - + const toggleIsNavigationVisible = useStore((state) => state.toggleIsNavigationVisible) const existNavTypesInFeature = useExistInFeature('navtypes') const onTreeViewChanged = (event, value) => { @@ -74,119 +75,137 @@ export default function NavPanel({ })) const isNavTree = existNavTypesInFeature ? navigationMode === 'spatial-tree' : true + return ( -
+
-
- {existNavTypesInFeature && - + {/* Sticky Header */} + - - - - - - - Spatial Navigation + + + + + + + + {/* Content */} + + {existNavTypesInFeature && ( + - - - - } - } - defaultExpandIcon={} - defaultExpanded={isNavTree ? defaultExpandedElements : defaultExpandedTypes} - expanded={isNavTree ? expandedElements : expandedTypes} - selected={selectedElements} - onNodeToggle={(event, nodeIds) => { - if (isNavTree) { - setExpandedElements(nodeIds) - } else { - setExpandedTypes(nodeIds) - } - }} - key='tree' - sx={{ - 'padding': existNavTypesInFeature ? '7px 0 14px 0' : '14px 0', - 'maxWidth': '400px', - 'overflowY': 'auto', - 'overflowX': 'hidden', - 'flexGrow': 1, - '&:focus svg': { - visibility: 'visible !important', - }, - }} - > - {isNavTree ? - : - } - -
+ + + + + + + + + + + + )} + } + defaultExpandIcon={} + defaultExpanded={isNavTree ? defaultExpandedElements : defaultExpandedTypes} + expanded={isNavTree ? expandedElements : expandedTypes} + selected={selectedElements} + onNodeToggle={(event, nodeIds) => { + if (isNavTree) { + setExpandedElements(nodeIds) + } else { + setExpandedTypes(nodeIds) + } + }} + key='tree' + sx={{ + 'padding': existNavTypesInFeature ? '7px 0 14px 0' : '4px 0px 10px 0px', + 'maxWidth': '400px', + 'overflowY': 'auto', + 'overflowX': 'hidden', + 'flexGrow': 1, + '&:focus svg': { + visibility: 'visible !important', + }, + }} + > + {isNavTree ? ( + + ) : ( + + )} + + +
) diff --git a/src/Components/NoContent.jsx b/src/Components/NoContent.jsx index 47846775c..48462b8cc 100644 --- a/src/Components/NoContent.jsx +++ b/src/Components/NoContent.jsx @@ -12,7 +12,7 @@ import AttentionIcon from '../assets/icons/Attention.svg' */ export default function NoContent({message = 'no content'}) { return ( - + {message} diff --git a/src/Components/OpenModelControl.jsx b/src/Components/OpenModelControl.jsx index 256e14a41..530c6e4ee 100644 --- a/src/Components/OpenModelControl.jsx +++ b/src/Components/OpenModelControl.jsx @@ -17,6 +17,7 @@ import {getOrganizations, getRepositories, getFiles, getUserRepositories} from ' import {RectangularButton} from '../Components/Buttons' import UploadIcon from '../assets/icons/Upload.svg' import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder' +import CreateNewFolderOutlinedIcon from '@mui/icons-material/CreateNewFolderOutlined' /** @@ -54,9 +55,9 @@ export default function OpenModelControl({fileOpen}) { setIsDialogDisplayed(true)} - icon={} - placement={'right'} - selected={true} + icon={} + placement={'bottom'} + selected={isDialogDisplayed} dataTestId='open-ifc' /> {isDialogDisplayed && diff --git a/src/Components/OperationsGroup.jsx b/src/Components/OperationsGroup.jsx index 82eb9a061..2f0b5b9b4 100644 --- a/src/Components/OperationsGroup.jsx +++ b/src/Components/OperationsGroup.jsx @@ -3,8 +3,6 @@ import Box from '@mui/material/Box' import ButtonGroup from '@mui/material/ButtonGroup' import useTheme from '@mui/styles/useTheme' import useStore from '../store/useStore' -import {assertDefined} from '../utils/assert' -import {hexToRgba} from '../utils/color' import {useIsMobile} from './Hooks' import AboutControl from './About/AboutControl' import CameraControl from './CameraControl' @@ -73,95 +71,81 @@ export default function OperationsGroup({deselectItems}) { } const theme = useTheme() - // When the model has dark/black colors, then the icons (also dark) - // disappear. This keeps them visible. - const bgOpacity = 0.2 - const bgColor = hexToRgba(assertDefined(theme.palette.scene.background), bgOpacity) return ( - {isLoginVisible && - - - + } {isCollaborationGroupVisible && - - - + + + } {isModelInteractionGroupVisible && - - } - selected={isNotesOn} - onClick={() => { - turnOffTooltips() - toggle('Notes') - }} - /> - { - turnOffTooltips() - toggle('Properties') - }} - selected={isPropertiesOn} - icon={} - /> - - {/* */} - } - /> - - + <> + } + selected={isNotesOn} + onClick={() => { + turnOffTooltips() + toggle('Notes') + }} + /> + { + turnOffTooltips() + toggle('Properties') + }} + selected={isPropertiesOn} + icon={} + /> + + } + /> + } {isSettingsVisible && - - {isAppStoreEnabled && - } - selected={isAppStoreOpen} - onClick={() => toggleAppStoreDrawer()} - /> - } - theme.toggleColorMode()} - icon={ - theme.palette.mode === 'light' ? - : - } - /> - - toggleIsHelpTooltips()} - selected={isHelpTooltips} - icon={} - /> - + <> + {isAppStoreEnabled && + } + selected={isAppStoreOpen} + onClick={() => toggleAppStoreDrawer()} + /> + } + theme.toggleColorMode()} + icon={ + theme.palette.mode === 'light' ? + : + } + /> + + toggleIsHelpTooltips()} + selected={isHelpTooltips} + icon={} + /> + } {/* Invisible */} - + ) } diff --git a/src/Components/SearchBar.jsx b/src/Components/SearchBar.jsx index 3a347dad5..4ef8be65c 100644 --- a/src/Components/SearchBar.jsx +++ b/src/Components/SearchBar.jsx @@ -1,13 +1,11 @@ import React, {useRef, useEffect, useState} from 'react' import {useLocation, useNavigate, useSearchParams} from 'react-router-dom' import InputAdornment from '@mui/material/InputAdornment' -import IconButton from '@mui/material/IconButton' -import TextField from '@mui/material/TextField' +import InputAutocomplete from './InputAutocomplete' import {looksLikeLink, githubUrlOrPathToSharePath} from '../ShareRoutes' import debug from '../utils/debug' import {navWithSearchParamRemoved} from '../utils/navigate' import {handleBeforeUnload} from '../utils/event' -import HighlightOffIcon from '@mui/icons-material/HighlightOff' import SearchIcon from '@mui/icons-material/Search' @@ -23,7 +21,6 @@ export default function SearchBar({fileOpen}) { const [searchParams, setSearchParams] = useSearchParams() const [inputText, setInputText] = useState('') const [error, setError] = useState('') - const onInputChange = (event) => setInputText(event.target.value) const searchInputRef = useRef(null) @@ -77,6 +74,12 @@ export default function SearchBar({fileOpen}) { searchInputRef.current.blur() } + const handleClear = () => { + setInputText('') + setError('') + navWithSearchParamRemoved(navigate, location.pathname, QUERY_PARAM) + } + // The container and paper are set to 100% width to fill the // container SearchBar shares with NavPanel. This is an easier way @@ -84,37 +87,18 @@ export default function SearchBar({fileOpen}) { // container (CadView). return (
- - - - ), - endAdornment: inputText.length > 0 ? ( - - { - setInputText('') - setError('') - navWithSearchParamRemoved(navigate, location.pathname, QUERY_PARAM) - }} - > - - - - ) : null, - }} + + + + } + placeholder="Search" /> ) diff --git a/src/Components/ShareControl.jsx b/src/Components/ShareControl.jsx index 509be194a..fe54ac27e 100644 --- a/src/Components/ShareControl.jsx +++ b/src/Components/ShareControl.jsx @@ -34,7 +34,7 @@ export default function ShareControl() { return ( } + icon={} isDialogDisplayed={openedDialog} setIsDialogDisplayed={setIsDialogDisplayed} dialog={ diff --git a/src/Components/UserProfile.jsx b/src/Components/UserProfile.jsx index f024ad515..0ee5c4bd2 100644 --- a/src/Components/UserProfile.jsx +++ b/src/Components/UserProfile.jsx @@ -29,8 +29,8 @@ const UserProfile = ({size = 'medium'}) => { className={'no-hover'} {...bindTrigger(popupState)} sx={{ - 'width': '50px', - 'height': '50px', + 'width': '46px', + 'height': '46px', 'border': 'none', '&.Mui-selected, &.Mui-selected:hover': { opacity: .8, diff --git a/src/Containers/CadView.jsx b/src/Containers/CadView.jsx index 70d80bb0b..fa38aa04b 100644 --- a/src/Containers/CadView.jsx +++ b/src/Containers/CadView.jsx @@ -100,6 +100,8 @@ export default function CadView({ // Granular visibility controls for the UI components const isSearchBarVisible = useStore((state) => state.isSearchBarVisible) const isNavigationPanelVisible = useStore((state) => state.isNavigationPanelVisible) + const isSearchVisible = useStore((state) => state.isSearchVisible) + const isNavigationVisible = useStore((state) => state.isNavigationVisible) // Place Mark @@ -592,7 +594,7 @@ export default function CadView({ const windowDimensions = useWindowDimensions() const spacingBetweenSearchAndOpsGroupPx = 20 - const operationsGroupWidthPx = 60 + const operationsGroupWidthPx = 100 const searchAndNavWidthPx = windowDimensions.width - (operationsGroupWidthPx + spacingBetweenSearchAndOpsGroupPx) const searchAndNavMaxWidthPx = 300 return ( @@ -646,8 +648,8 @@ export default function CadView({ }} > loadLocalFile(navigate)}/> - {isSearchBarVisible && - + {isSearchBarVisible && isSearchVisible && + loadLocalFile(navigate, appPrefix, handleBeforeUnload)}/> } @@ -657,6 +659,7 @@ export default function CadView({ } {isNavPanelOpen && isNavigationPanelVisible && + isNavigationVisible && - + + + diff --git a/src/store/UISlice.js b/src/store/UISlice.js index d53c8e4d7..8f7defe0d 100644 --- a/src/store/UISlice.js +++ b/src/store/UISlice.js @@ -14,6 +14,8 @@ export default function createUISlice(set, get) { isNotesOn: false, isDrawerOpen: false, isNavPanelOpen: true, + isNavigationVisible: false, + isSearchVisible: false, isOpenControlHighlighted: true, isPropertiesOn: false, snackMessage: null, @@ -60,6 +62,8 @@ export default function createUISlice(set, get) { setSidebarHeight: (newSidebarHeight) => set(() => ({sidebarHeight: newSidebarHeight})), setDrawer: (newDrawer) => set(() => ({drawer: newDrawer})), toggleAppStoreDrawer: () => set((state) => ({isAppStoreOpen: !state.isAppStoreOpen})), + toggleIsNavigationVisible: () => set((state) => ({isNavigationVisible: !state.isNavigationVisible})), + toggleIsSearchVisible: () => set((state) => ({isSearchVisible: !state.isSearchVisible})), setAppStoreSidebarWidth: (newSidebarWidth) => set(() => ({appStoreSidebarWidth: newSidebarWidth})), setAppStoreSidebarHeight: (newSidebarHeight) => set(() => ({appStoreSidebarHeight: newSidebarHeight})), setSelectedStoreApp: (appInfo) => set(() => ({selectedStoreApp: appInfo})), diff --git a/src/theme/Components.js b/src/theme/Components.js index 9e71e8ccb..1bb141239 100644 --- a/src/theme/Components.js +++ b/src/theme/Components.js @@ -42,15 +42,27 @@ export function getComponentOverrides(palette, typography) { disableRipple: true, }, }, + MuiButtonGroup: { + variants: [ + { + props: {variant: 'contained'}, + style: ({theme}) => ({ + backgroundColor: palette.scene.background, + boxShadow: theme.shadows[1], + opacity: .9, + }), + }, + ], + }, MuiToggleButton: { styleOverrides: { sizeMedium: { - 'width': '50px', - 'height': '50px', + 'width': '46px', + 'height': '46px', 'border': 'none', '&.Mui-selected, &.Mui-selected:hover': { backgroundColor: palette.primary.background, - opacity: .8, + opacity: .9, }, }, sizeSmall: { @@ -77,9 +89,10 @@ export function getComponentOverrides(palette, typography) { variants: [ { props: {variant: 'control'}, - style: { - backgroundColor: palette.primary.background, - }, + style: ({ownerState, theme}) => ({ + backgroundColor: palette.secondary.background, + boxShadow: theme.shadows[ownerState.elevation], + }), }, { props: {variant: 'background'}, diff --git a/src/theme/Palette.js b/src/theme/Palette.js index 6c50df36c..711fa7729 100644 --- a/src/theme/Palette.js +++ b/src/theme/Palette.js @@ -14,7 +14,7 @@ export const day = { }, secondary: { main: colors.grey.dark, - background: colors.green.lightest, + background: colors.grey.lightest, contrastText: colors.green.dark, }, background: { @@ -36,7 +36,7 @@ export const night = { }, secondary: { main: colors.grey.light, - background: colors.green.medium, + background: '#282828', contrastText: colors.green.lightest, }, background: { diff --git a/src/theme/Theme.jsx b/src/theme/Theme.jsx index 18c803ada..e9ae49c3b 100644 --- a/src/theme/Theme.jsx +++ b/src/theme/Theme.jsx @@ -55,6 +55,9 @@ function loadTheme(mode, setMode, themeChangeListeners) { components: getComponentOverrides(activePalette), shape: {borderRadius: 0}, palette: activePalette, + zIndex: { + modal: 2000, + }, toggleColorMode: () => { setMode((prevMode) => { const newMode = prevMode === Themes.Day ? Themes.Night : Themes.Day