diff --git a/.prettierrc b/.prettierrc index 39eed9eea..55ea2b2d1 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,4 @@ { "tabWidth": 4, - "useTabs": false, + "useTabs": false } diff --git a/src/App.tsx b/src/App.tsx index d1e4ed967..5bd54bcb3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,5 @@ import React from "react"; import { APIProvider } from "@vis.gl/react-google-maps"; -import { MenuMainContainer } from "./navigation/MenuMainContainer"; import "./index.css"; import "./App.css"; import CssBaseline from "@mui/material/CssBaseline"; @@ -24,6 +23,7 @@ import SnackNotificationBar from "./components/SnackNotificationBar"; import TenantListProvider from "./scenes/TenantPicker/TenantListProvider"; import { initialiseApp } from "./redux/initialise/initialiseActions"; import * as Sentry from "@sentry/react"; +import { MenuMainContainer } from "./navigation/MenuMainContainer"; const GOOGLE_MAPS_API_KEY = process.env.REACT_APP_GOOGLE_MAPS_API_KEY as string; @@ -124,41 +124,30 @@ const App = (props: any) => { }); } - if (process.env.REACT_APP_DEMO_MODE === "true") { - return ( - - - - + return ( + + + + + + {process.env.REACT_APP_DEMO_MODE === "true" ? ( - - - - - - ); - } else { - return ( - - - - + ) : ( - - - - - - ); - } + )} + + + + + ); }; export default App; diff --git a/src/navigation/MainWindow.js b/src/navigation/MainWindow.js index e4275ea82..ab67cf707 100644 --- a/src/navigation/MainWindow.js +++ b/src/navigation/MainWindow.js @@ -1,4 +1,3 @@ -import React from "react"; import "../index.css"; import { Route, Switch, useLocation } from "react-router-dom"; import Dashboard from "../scenes/Dashboard/Dashboard"; @@ -31,12 +30,17 @@ import ScheduledTaskOverviewRoute from "../scenes/ScheduledTasks/components/Sche const useStyles = makeStyles()((theme, { navIndex, guidedSetupOpen }) => ({ root: { + [theme.breakpoints.up("sm")]: { + display: "flex", + flex: 1, + }, marginRight: guidedSetupOpen && navIndex === "dashboard" ? 0 : "auto", marginLeft: navIndex === "dashboard" ? 0 : 20, - paddingTop: 20, + paddingTop: 78, paddingBottom: 10, + minWidth: 0, [theme.breakpoints.down("md")]: { - paddingTop: 5, + // paddingTop: 5, marginLeft: 0, marginRight: 0, }, @@ -57,7 +61,7 @@ export default function MainWindow(_props) { // whenever returning an item, set the MenuIndex to update the drawer menu return ( -
+
{ +const useStyles2 = makeStyles()((theme) => { return { appBarComponents: { margin: "auto", @@ -40,54 +47,82 @@ const useStyles = makeStyles()((theme) => { }; }); +interface AppBarProps extends MuiAppBarProps { + isXs: boolean; + open: boolean; +} + +const AppBar = styled(MuiAppBar, { + shouldForwardProp: (prop) => prop !== "open" && prop !== "isXs", +})(({ theme, open, isXs }) => ({ + zIndex: theme.zIndex.drawer + 1, + background: theme.palette.background.paper, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + boxShadow: + "0px 2px 0px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12)", + ...(open && + !isXs && { + // marginLeft: drawerWidth, + // width: `calc(100% - ${drawerWidth}px)`, + transition: theme.transitions.create(["width", "margin"], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + }), +})); + export function MenuMainContainer() { - const { classes } = useStyles(); - const [searchMode, setSearchMode] = useState(false); - const dashboardTabIndex = useSelector(dashboardTabIndexSelector); const menuIndex = useSelector(menuIndexSelector); - const currentFilter = useSelector(dashboardFilterTermSelector); - const toggleIcon = searchMode ? : ; + const dashboardTabIndex = useSelector(dashboardTabIndexSelector); + const { classes } = useStyles2(); + const theme = useTheme(); + const isXs = useMediaQuery(theme.breakpoints.down("sm")); + const [open, setOpen] = React.useState(false); + const [searchMode, setSearchMode] = React.useState(false); const dispatch = useDispatch(); - + const toggleIcon = searchMode ? : ; const toggleSearchMode = () => { if (searchMode) dispatch(clearDashboardFilter()); setSearchMode(!searchMode); }; - const theme = useTheme(); - const isSm = useMediaQuery(theme.breakpoints.down("md")); - const isXs = useMediaQuery(theme.breakpoints.down("sm")); + useCordovaBackButton(() => { + if (isXs) { + setOpen(false); + } + }, open); - const updateSearchMode = React.useCallback( - (currentFilter, menuIndex, isSm) => { - if (menuIndex !== "dashboard") { - setSearchMode(false); - dispatch(clearDashboardFilter()); - } else if (currentFilter && isSm) { - setSearchMode((prevState) => { - if (!prevState) return true; - else return prevState; - }); - } - }, - [dispatch] - ); + const handleDrawerOpen = () => { + setOpen(true); + }; - useEffect( - () => updateSearchMode(currentFilter, menuIndex, isSm), - [currentFilter, isSm, menuIndex, updateSearchMode] - ); + const toggleOpen = () => { + if (open) { + handleDrawerClose(); + } else { + handleDrawerOpen(); + } + }; - useEffect(() => { - if (!isSm && searchMode) setSearchMode(false); - }, [isSm, searchMode]); + const handleDrawerClose = () => { + setOpen(false); + }; return ( - - + + + - + {menuIndex === "dashboard" && ( @@ -153,7 +188,18 @@ export function MenuMainContainer() { )} - - + + + + + ); } diff --git a/src/navigation/MobileNavigationDrawer.js b/src/navigation/MobileNavigationDrawer.js deleted file mode 100644 index 46b426e86..000000000 --- a/src/navigation/MobileNavigationDrawer.js +++ /dev/null @@ -1,62 +0,0 @@ -import React, { useState } from "react"; -import NavDrawerItems from "./NavDrawerItems"; -import IconButton from "@mui/material/IconButton"; -import MenuIcon from "@mui/icons-material/Menu"; -import { makeStyles } from 'tss-react/mui'; -import ArrowBackIcon from "@mui/icons-material/ArrowBack"; -import { Stack, SwipeableDrawer } from "@mui/material"; -import { useCordovaBackButton } from "../hooks/useCordovaBackButton"; - -const useStyles = makeStyles()({ - list: { - width: 250, - }, -}); - -export default function MobileNavigationDrawer() { - const [open, setOpen] = useState(false); - const { classes } = useStyles(); - - useCordovaBackButton(() => { - setOpen(false); - }, open); - - const toggleDrawer = (event) => { - if ( - event && - event.type === "keydown" && - (event.key === "Tab" || event.key === "Shift") - ) { - return; - } - setOpen(!open); - }; - - return ( -
- - - - toggleDrawer(false)} - onOpen={() => toggleDrawer(true)} - > - - toggleDrawer(false)}> - - - setOpen(false)} - /> - - -
- ); -} diff --git a/src/navigation/sidebar/Sidebar.tsx b/src/navigation/sidebar/Sidebar.tsx new file mode 100644 index 000000000..90148d341 --- /dev/null +++ b/src/navigation/sidebar/Sidebar.tsx @@ -0,0 +1,130 @@ +import { + Stack, + useMediaQuery, + useTheme, + styled, + CSSObject, + Theme, + Drawer as MuiDrawer, +} from "@mui/material"; +import { makeStyles } from "tss-react/mui"; +import { SidebarDrawerItems } from "./SidebarDrawerItems"; + +export const drawerWidth = 250; + +const useStyles = makeStyles()({ + list: { + width: drawerWidth, + }, + closedList: { + width: "100%", + }, + // topBox: { + // height: 58, + // alignContent: "center", + // }, + stack: { + paddingTop: 58, + }, +}); + +const openedMixin = (theme: Theme): CSSObject => ({ + width: drawerWidth, + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.enteringScreen, + }), + overflowX: "hidden", +}); + +const closedMixin = (theme: Theme): CSSObject => ({ + transition: theme.transitions.create("width", { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + overflowX: "hidden", + width: `calc(${theme.spacing(7)} + 1px)`, + [theme.breakpoints.up("sm")]: { + width: `calc(${theme.spacing(8)} + 1px)`, + }, +}); + +const Drawer = styled(MuiDrawer, { + shouldForwardProp: (prop) => prop !== "isXs", +})<{ + open: boolean; + isXs: boolean; +}>(({ theme, open, isXs }) => { + if (isXs) { + // Do not override styles for temporary drawer (mobile) + return {}; + } + // Only apply mini-variant styles for permanent drawer + return { + display: "flex", + width: drawerWidth, + flexShrink: 0, + whiteSpace: "nowrap", + boxSizing: "border-box", + background: theme.palette.background.paper, + borderRight: 0, + ...(open + ? { + ...openedMixin(theme), + "& .MuiDrawer-paper": openedMixin(theme), + } + : { + ...closedMixin(theme), + "& .MuiDrawer-paper": closedMixin(theme), + }), + }; +}); + +export interface SidebarProps { + onClose: () => void; + open: boolean; // Optional prop to control the open state +} + +export const Sidebar: React.FC = ({ onClose, open }) => { + const theme = useTheme(); + const isXs = useMediaQuery(theme.breakpoints.down("sm")); + const { classes } = useStyles(); + + // For temporary drawer, pass onClose prop so it can close on backdrop click + return ( + + + {/* + + + + */} + + + + ); +}; diff --git a/src/navigation/NavDrawerItems.js b/src/navigation/sidebar/SidebarDrawerItems.tsx similarity index 50% rename from src/navigation/NavDrawerItems.js rename to src/navigation/sidebar/SidebarDrawerItems.tsx index 0af6c0811..a5a34e8a3 100644 --- a/src/navigation/NavDrawerItems.js +++ b/src/navigation/sidebar/SidebarDrawerItems.tsx @@ -1,25 +1,35 @@ -import React from "react"; +import { FC } from "react"; import { useSelector } from "react-redux"; -import { ListItem, ListItemIcon, ListItemText } from "@mui/material"; -import TwoWheelerIcon from "@mui/icons-material/TwoWheeler"; -import PeopleAltIcon from "@mui/icons-material/PeopleAlt"; -import SupervisorAccountIcon from "@mui/icons-material/SupervisorAccount"; +import { getWhoami } from "../../redux/Selectors"; +import { + ListItemIcon, + ListItemText, + List, + ListItemButton, +} from "@mui/material"; import { Link } from "react-router-dom"; -import Divider from "@mui/material/Divider"; -import * as models from "../models"; -import List from "@mui/material/List"; -import PropTypes from "prop-types"; -import DashboardIcon from "@mui/icons-material/Dashboard"; -import LocationCityIcon from "@mui/icons-material/LocationCity"; -import BarChartIcon from "@mui/icons-material/BarChart"; -import DescriptionIcon from "@mui/icons-material/Description"; -import { getWhoami } from "../redux/Selectors"; -import HistoryIcon from "@mui/icons-material/History"; -import CalendarMonthIcon from "@mui/icons-material/CalendarMonth"; +import { + SupervisorAccount, + Dashboard, + LocationCity, + BarChart, + Description, + History, + CalendarMonth, + PeopleAlt, + TwoWheeler, +} from "@mui/icons-material"; +import * as models from "../../models"; -function NavDrawerItems(props) { +export interface SidebarDrawerItemsProps { + open?: boolean; + className?: string; + onSelect: () => void; +} + +export const SidebarDrawerItems: FC = (props) => { const whoami = useSelector(getWhoami); - const menuIndex = useSelector((state) => state.menuIndex); + const menuIndex = useSelector((state) => state.menuIndex); const onSelect = props.onSelect; let adminLink = <>; let historyLink = <>; @@ -29,18 +39,17 @@ function NavDrawerItems(props) { if (whoami.roles) { if (whoami.roles.includes("ADMIN")) { adminLink = ( - - + - - + {props.open ? : <>} + ); } if ( @@ -48,114 +57,105 @@ function NavDrawerItems(props) { whoami.roles.includes(models.Role.COORDINATOR) ) { historyLink = ( - - + - - + {props.open ? : <>} + ); statisticsLink = ( - - + - - + {props.open ? : <>} + ); scheduledTasksLink = ( - - + - - + {props.open ? : <>} + ); } } return (
- - - + - + - - - : <>} + + - + - - - : <>} + + - + - - - : <>} + + - + - - - : <>} + + - + - - + {props.open ? : <>} + {scheduledTasksLink} {statisticsLink} {historyLink} @@ -163,15 +163,4 @@ function NavDrawerItems(props) {
); -} - -NavDrawerItems.propTypes = { - onSelect: PropTypes.func, - className: PropTypes.string, }; - -NavDrawerItems.defaultProps = { - onSelect: () => {}, -}; - -export default NavDrawerItems; diff --git a/src/navigation/sidebar/TopbarButton.tsx b/src/navigation/sidebar/TopbarButton.tsx new file mode 100644 index 000000000..088c7e2cb --- /dev/null +++ b/src/navigation/sidebar/TopbarButton.tsx @@ -0,0 +1,14 @@ +import { IconButton } from "@mui/material"; +import MenuIcon from "@mui/icons-material/Menu"; + +export interface TopbarButtonProps { + onClick: () => void; +} + +export const TopbarButton: React.FC = ({ onClick }) => { + return ( + + + + ); +};