Improve sidebar
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import { mdiMessageTextFast } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
@@ -59,8 +57,6 @@ export default function BaseAuthenticatedPage(): React.ReactElement {
|
||||
}}
|
||||
>
|
||||
<DashboardHeader
|
||||
logo={<Icon path={mdiMessageTextFast} size="2em" color={"white"} />}
|
||||
title=""
|
||||
menuOpen={isNavigationExpanded}
|
||||
onToggleMenu={handleToggleHeaderMenu}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import * as React from "react";
|
||||
import { styled, useTheme } from "@mui/material/styles";
|
||||
import Box from "@mui/material/Box";
|
||||
import { mdiMessageTextFast } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import MenuOpenIcon from "@mui/icons-material/MenuOpen";
|
||||
import MuiAppBar from "@mui/material/AppBar";
|
||||
import Box from "@mui/material/Box";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import MenuOpenIcon from "@mui/icons-material/MenuOpen";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { Link } from "react-router";
|
||||
import * as React from "react";
|
||||
import { RouterLink } from "../RouterLink";
|
||||
import ThemeSwitcher from "./ThemeSwitcher";
|
||||
|
||||
const AppBar = styled(MuiAppBar)(({ theme }) => ({
|
||||
@@ -32,20 +34,14 @@ const LogoContainer = styled("div")({
|
||||
});
|
||||
|
||||
export interface DashboardHeaderProps {
|
||||
logo?: React.ReactNode;
|
||||
title?: string;
|
||||
menuOpen: boolean;
|
||||
onToggleMenu: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export default function DashboardHeader({
|
||||
logo,
|
||||
title,
|
||||
menuOpen,
|
||||
onToggleMenu,
|
||||
}: DashboardHeaderProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const handleMenuOpen = React.useCallback(() => {
|
||||
onToggleMenu(!menuOpen);
|
||||
}, [menuOpen, onToggleMenu]);
|
||||
@@ -60,7 +56,7 @@ export default function DashboardHeader({
|
||||
title={`${
|
||||
isExpanded ? collapseMenuActionText : expandMenuActionText
|
||||
} menu`}
|
||||
enterDelay={1000}
|
||||
enterDelay={200}
|
||||
>
|
||||
<div>
|
||||
<IconButton
|
||||
@@ -92,26 +88,25 @@ export default function DashboardHeader({
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<Box sx={{ mr: 1 }}>{getMenuIcon(menuOpen)}</Box>
|
||||
<Link to="/" style={{ textDecoration: "none" }}>
|
||||
<Box sx={{ mr: 3 }}>{getMenuIcon(menuOpen)}</Box>
|
||||
<RouterLink to="/">
|
||||
<Stack direction="row" alignItems="center">
|
||||
{logo ? <LogoContainer>{logo}</LogoContainer> : null}
|
||||
{title ? (
|
||||
<LogoContainer>
|
||||
<Icon path={mdiMessageTextFast} size="2em" />
|
||||
</LogoContainer>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
color: (theme.vars ?? theme).palette.primary.main,
|
||||
fontWeight: "700",
|
||||
ml: 1,
|
||||
whiteSpace: "nowrap",
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
MatrixGW
|
||||
</Typography>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Link>
|
||||
</RouterLink>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import BarChartIcon from "@mui/icons-material/BarChart";
|
||||
import LayersIcon from "@mui/icons-material/Layers";
|
||||
import PersonIcon from "@mui/icons-material/Person";
|
||||
import Box from "@mui/material/Box";
|
||||
import Drawer from "@mui/material/Drawer";
|
||||
import List from "@mui/material/List";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import type {} from "@mui/material/themeCssVarsAugmentation";
|
||||
import PersonIcon from "@mui/icons-material/Person";
|
||||
import BarChartIcon from "@mui/icons-material/BarChart";
|
||||
import DescriptionIcon from "@mui/icons-material/Description";
|
||||
import LayersIcon from "@mui/icons-material/Layers";
|
||||
import { matchPath, useLocation } from "react-router";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import * as React from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import DashboardSidebarContext from "./DashboardSidebarContext";
|
||||
import { DRAWER_WIDTH, MINI_DRAWER_WIDTH } from "./constants";
|
||||
import DashboardSidebarPageItem from "./DashboardSidebarPageItem";
|
||||
import DashboardSidebarHeaderItem from "./DashboardSidebarHeaderItem";
|
||||
import DashboardSidebarDividerItem from "./DashboardSidebarDividerItem";
|
||||
import DashboardSidebarPageItem from "./DashboardSidebarPageItem";
|
||||
import { DRAWER_WIDTH, MINI_DRAWER_WIDTH } from "./constants";
|
||||
import {
|
||||
getDrawerSxTransitionMixin,
|
||||
getDrawerWidthTransitionMixin,
|
||||
@@ -36,10 +34,6 @@ export default function DashboardSidebar({
|
||||
}: DashboardSidebarProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [expandedItemIds, setExpandedItemIds] = React.useState<string[]>([]);
|
||||
|
||||
const isOverSmViewport = useMediaQuery(theme.breakpoints.up("sm"));
|
||||
const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
@@ -83,22 +77,11 @@ export default function DashboardSidebar({
|
||||
[setExpanded]
|
||||
);
|
||||
|
||||
const handlePageItemClick = React.useCallback(
|
||||
(itemId: string, hasNestedNavigation: boolean) => {
|
||||
if (hasNestedNavigation && !mini) {
|
||||
setExpandedItemIds((previousValue) =>
|
||||
previousValue.includes(itemId)
|
||||
? previousValue.filter(
|
||||
(previousValueItemId) => previousValueItemId !== itemId
|
||||
)
|
||||
: [...previousValue, itemId]
|
||||
);
|
||||
} else if (!isOverSmViewport && !hasNestedNavigation) {
|
||||
const handlePageItemClick = React.useCallback(() => {
|
||||
if (!isOverSmViewport) {
|
||||
setExpanded(false);
|
||||
}
|
||||
},
|
||||
[mini, setExpanded, isOverSmViewport]
|
||||
);
|
||||
}, [mini, setExpanded, isOverSmViewport]);
|
||||
|
||||
const hasDrawerTransitions =
|
||||
isOverSmViewport && (!disableCollapsibleSidebar || isOverMdViewport);
|
||||
@@ -132,67 +115,30 @@ export default function DashboardSidebar({
|
||||
width: mini ? MINI_DRAWER_WIDTH : "auto",
|
||||
}}
|
||||
>
|
||||
<DashboardSidebarHeaderItem>Main items</DashboardSidebarHeaderItem>
|
||||
<DashboardSidebarPageItem
|
||||
id="employees"
|
||||
title="Employees"
|
||||
icon={<PersonIcon />}
|
||||
href="/employees"
|
||||
selected={
|
||||
!!matchPath("/employees/*", pathname) || pathname === "/"
|
||||
}
|
||||
/>
|
||||
<DashboardSidebarDividerItem />
|
||||
<DashboardSidebarHeaderItem>
|
||||
Example items
|
||||
</DashboardSidebarHeaderItem>
|
||||
<DashboardSidebarPageItem
|
||||
id="reports"
|
||||
title="Reports"
|
||||
icon={<BarChartIcon />}
|
||||
href="/reports"
|
||||
selected={!!matchPath("/reports", pathname)}
|
||||
defaultExpanded={!!matchPath("/reports", pathname)}
|
||||
expanded={expandedItemIds.includes("reports")}
|
||||
nestedNavigation={
|
||||
<List
|
||||
dense
|
||||
sx={{
|
||||
padding: 0,
|
||||
my: 1,
|
||||
pl: mini ? 0 : 1,
|
||||
minWidth: 240,
|
||||
}}
|
||||
>
|
||||
<DashboardSidebarPageItem
|
||||
id="sales"
|
||||
title="Sales"
|
||||
icon={<DescriptionIcon />}
|
||||
href="/reports/sales"
|
||||
selected={!!matchPath("/reports/sales", pathname)}
|
||||
/>
|
||||
<DashboardSidebarPageItem
|
||||
id="traffic"
|
||||
title="Traffic"
|
||||
icon={<DescriptionIcon />}
|
||||
href="/reports/traffic"
|
||||
selected={!!matchPath("/reports/traffic", pathname)}
|
||||
/>
|
||||
</List>
|
||||
}
|
||||
/>
|
||||
<DashboardSidebarPageItem
|
||||
id="integrations"
|
||||
title="Integrations"
|
||||
icon={<LayersIcon />}
|
||||
href="/integrations"
|
||||
selected={!!matchPath("/integrations", pathname)}
|
||||
/>
|
||||
</List>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
),
|
||||
[mini, hasDrawerTransitions, isFullyExpanded, expandedItemIds, pathname]
|
||||
[mini, hasDrawerTransitions, isFullyExpanded]
|
||||
);
|
||||
|
||||
const getDrawerSharedSx = React.useCallback(
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import * as React from "react";
|
||||
|
||||
const DashboardSidebarContext = React.createContext(null);
|
||||
const DashboardSidebarContext = React.createContext<{
|
||||
onPageItemClick: () => void;
|
||||
mini: boolean;
|
||||
fullyExpanded: boolean;
|
||||
fullyCollapsed: boolean;
|
||||
hasDrawerTransitions: boolean;
|
||||
} | null>(null);
|
||||
|
||||
export default DashboardSidebarContext;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import * as React from "react";
|
||||
import ListSubheader from "@mui/material/ListSubheader";
|
||||
import type {} from "@mui/material/themeCssVarsAugmentation";
|
||||
import DashboardSidebarContext from "./DashboardSidebarContext";
|
||||
import { DRAWER_WIDTH } from "./constants";
|
||||
import { getDrawerSxTransitionMixin } from "./mixins";
|
||||
|
||||
export interface DashboardSidebarHeaderItemProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function DashboardSidebarHeaderItem({
|
||||
children,
|
||||
}: DashboardSidebarHeaderItemProps) {
|
||||
const sidebarContext = React.useContext(DashboardSidebarContext);
|
||||
if (!sidebarContext) {
|
||||
throw new Error("Sidebar context was used without a provider.");
|
||||
}
|
||||
const {
|
||||
mini = false,
|
||||
fullyExpanded = true,
|
||||
hasDrawerTransitions,
|
||||
} = sidebarContext;
|
||||
|
||||
return (
|
||||
<ListSubheader
|
||||
sx={{
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
height: mini ? 0 : 36,
|
||||
...(hasDrawerTransitions
|
||||
? getDrawerSxTransitionMixin(fullyExpanded, "height")
|
||||
: {}),
|
||||
px: 1.5,
|
||||
py: 0,
|
||||
minWidth: DRAWER_WIDTH,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ListSubheader>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
import * as React from "react";
|
||||
import { type Theme, type SxProps } from "@mui/material/styles";
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import Grow from "@mui/material/Grow";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import type {} from "@mui/material/themeCssVarsAugmentation";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import { Link } from "react-router";
|
||||
import * as React from "react";
|
||||
import { Link, matchPath, useLocation } from "react-router";
|
||||
import DashboardSidebarContext from "./DashboardSidebarContext";
|
||||
import { MINI_DRAWER_WIDTH } from "./constants";
|
||||
|
||||
@@ -22,11 +17,7 @@ export interface DashboardSidebarPageItemProps {
|
||||
icon?: React.ReactNode;
|
||||
href: string;
|
||||
action?: React.ReactNode;
|
||||
defaultExpanded?: boolean;
|
||||
expanded?: boolean;
|
||||
selected?: boolean;
|
||||
disabled?: boolean;
|
||||
nestedNavigation?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function DashboardSidebarPageItem({
|
||||
@@ -35,12 +26,10 @@ export default function DashboardSidebarPageItem({
|
||||
icon,
|
||||
href,
|
||||
action,
|
||||
defaultExpanded = false,
|
||||
expanded = defaultExpanded,
|
||||
selected = false,
|
||||
disabled = false,
|
||||
nestedNavigation,
|
||||
}: DashboardSidebarPageItemProps) {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const sidebarContext = React.useContext(DashboardSidebarContext);
|
||||
if (!sidebarContext) {
|
||||
throw new Error("Sidebar context was used without a provider.");
|
||||
@@ -49,38 +38,13 @@ export default function DashboardSidebarPageItem({
|
||||
onPageItemClick,
|
||||
mini = false,
|
||||
fullyExpanded = true,
|
||||
fullyCollapsed = false,
|
||||
} = sidebarContext;
|
||||
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
if (onPageItemClick) {
|
||||
onPageItemClick(id, !!nestedNavigation);
|
||||
}
|
||||
}, [onPageItemClick, id, nestedNavigation]);
|
||||
|
||||
let nestedNavigationCollapseSx: SxProps<Theme> = { display: "none" };
|
||||
if (mini && fullyCollapsed) {
|
||||
nestedNavigationCollapseSx = {
|
||||
fontSize: 18,
|
||||
position: "absolute",
|
||||
top: "41.5%",
|
||||
right: "2px",
|
||||
transform: "translateY(-50%) rotate(-90deg)",
|
||||
};
|
||||
} else if (!mini && fullyExpanded) {
|
||||
nestedNavigationCollapseSx = {
|
||||
ml: 0.5,
|
||||
fontSize: 20,
|
||||
transform: `rotate(${expanded ? 0 : -90}deg)`,
|
||||
transition: (theme: Theme) =>
|
||||
theme.transitions.create("transform", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: 100,
|
||||
}),
|
||||
};
|
||||
onPageItemClick();
|
||||
}
|
||||
}, [onPageItemClick, id]);
|
||||
|
||||
const hasExternalHref = href
|
||||
? href.startsWith("http://") || href.startsWith("https://")
|
||||
@@ -88,50 +52,18 @@ export default function DashboardSidebarPageItem({
|
||||
|
||||
const LinkComponent = hasExternalHref ? "a" : Link;
|
||||
|
||||
const miniNestedNavigationSidebarContextValue = React.useMemo(() => {
|
||||
return {
|
||||
onPageItemClick: onPageItemClick ?? (() => {}),
|
||||
mini: false,
|
||||
fullyExpanded: true,
|
||||
fullyCollapsed: false,
|
||||
hasDrawerTransitions: false,
|
||||
};
|
||||
}, [onPageItemClick]);
|
||||
const selected = !!matchPath(href, pathname);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListItem
|
||||
disablePadding
|
||||
{...(nestedNavigation && mini
|
||||
? {
|
||||
onMouseEnter: () => {
|
||||
setIsHovered(true);
|
||||
},
|
||||
onMouseLeave: () => {
|
||||
setIsHovered(false);
|
||||
},
|
||||
}
|
||||
: {})}
|
||||
sx={{
|
||||
display: "block",
|
||||
py: 0,
|
||||
px: 1,
|
||||
overflowX: "hidden",
|
||||
}}
|
||||
>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
sx={{
|
||||
height: mini ? 50 : "auto",
|
||||
}}
|
||||
{...(nestedNavigation && !mini
|
||||
? {
|
||||
onClick: handleClick,
|
||||
}
|
||||
: {})}
|
||||
{...(!nestedNavigation
|
||||
? {
|
||||
{...{
|
||||
LinkComponent,
|
||||
...(hasExternalHref
|
||||
? {
|
||||
@@ -141,8 +73,7 @@ export default function DashboardSidebarPageItem({
|
||||
: {}),
|
||||
to: href,
|
||||
onClick: handleClick,
|
||||
}
|
||||
: {})}
|
||||
}}
|
||||
>
|
||||
{icon || mini ? (
|
||||
<Box
|
||||
@@ -212,42 +143,8 @@ export default function DashboardSidebarPageItem({
|
||||
/>
|
||||
) : null}
|
||||
{action && !mini && fullyExpanded ? action : null}
|
||||
{nestedNavigation ? (
|
||||
<ExpandMoreIcon sx={nestedNavigationCollapseSx} />
|
||||
) : null}
|
||||
</ListItemButton>
|
||||
{nestedNavigation && mini ? (
|
||||
<Grow in={isHovered}>
|
||||
<Box
|
||||
sx={{
|
||||
position: "fixed",
|
||||
left: MINI_DRAWER_WIDTH - 2,
|
||||
pl: "6px",
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={8}
|
||||
sx={{
|
||||
pt: 0.2,
|
||||
pb: 0.2,
|
||||
transform: "translateY(-50px)",
|
||||
}}
|
||||
>
|
||||
<DashboardSidebarContext.Provider
|
||||
value={miniNestedNavigationSidebarContextValue}
|
||||
>
|
||||
{nestedNavigation}
|
||||
</DashboardSidebarContext.Provider>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Grow>
|
||||
) : null}
|
||||
</ListItem>
|
||||
{nestedNavigation && !mini ? (
|
||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||
{nestedNavigation}
|
||||
</Collapse>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user