Compare commits
3 Commits
d9c96e85f7
...
3de26c0fff
| Author | SHA1 | Date | |
|---|---|---|---|
| 3de26c0fff | |||
| 79b5a767f3 | |||
| fdcd565431 |
@@ -11,8 +11,8 @@ import { LoginRoute } from "./routes/auth/LoginRoute";
|
||||
import { OIDCCbRoute } from "./routes/auth/OIDCCbRoute";
|
||||
import { HomeRoute } from "./routes/HomeRoute";
|
||||
import { NotFoundRoute } from "./routes/NotFoundRoute";
|
||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
|
||||
import { BaseLoginPage } from "./widgets/auth/BaseLoginPage";
|
||||
import BaseAuthenticatedPage from "./widgets/dashboard/BaseAuthenticatedPage";
|
||||
|
||||
interface AuthContext {
|
||||
signedIn: boolean;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export function BaseAuthenticatedPage(): React.ReactElement {
|
||||
return <p>todo authenticated</p>;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import Box from "@mui/material/Box";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import * as React from "react";
|
||||
import { Outlet } from "react-router";
|
||||
import DashboardHeader from "./DashboardHeader";
|
||||
import DashboardSidebar from "./DashboardSidebar";
|
||||
|
||||
export default function BaseAuthenticatedPage(): React.ReactElement {
|
||||
const theme = useTheme();
|
||||
|
||||
const [isDesktopNavigationExpanded, setIsDesktopNavigationExpanded] =
|
||||
React.useState(false);
|
||||
const [isMobileNavigationExpanded, setIsMobileNavigationExpanded] =
|
||||
React.useState(false);
|
||||
|
||||
const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
const isNavigationExpanded = isOverMdViewport
|
||||
? isDesktopNavigationExpanded
|
||||
: isMobileNavigationExpanded;
|
||||
|
||||
const setIsNavigationExpanded = React.useCallback(
|
||||
(newExpanded: boolean) => {
|
||||
if (isOverMdViewport) {
|
||||
setIsDesktopNavigationExpanded(newExpanded);
|
||||
} else {
|
||||
setIsMobileNavigationExpanded(newExpanded);
|
||||
}
|
||||
},
|
||||
[
|
||||
isOverMdViewport,
|
||||
setIsDesktopNavigationExpanded,
|
||||
setIsMobileNavigationExpanded,
|
||||
]
|
||||
);
|
||||
|
||||
const handleToggleHeaderMenu = React.useCallback(
|
||||
(isExpanded: boolean) => {
|
||||
setIsNavigationExpanded(isExpanded);
|
||||
},
|
||||
[setIsNavigationExpanded]
|
||||
);
|
||||
|
||||
const layoutRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={layoutRef}
|
||||
sx={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
overflow: "hidden",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<DashboardHeader
|
||||
menuOpen={isNavigationExpanded}
|
||||
onToggleMenu={handleToggleHeaderMenu}
|
||||
/>
|
||||
<DashboardSidebar
|
||||
expanded={isNavigationExpanded}
|
||||
setExpanded={setIsNavigationExpanded}
|
||||
container={layoutRef?.current ?? undefined}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<Toolbar sx={{ displayPrint: "none" }} />
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flex: 1,
|
||||
overflow: "auto",
|
||||
padding: "50px",
|
||||
}}
|
||||
>
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
125
matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx
Normal file
125
matrixgw_frontend/src/widgets/dashboard/DashboardHeader.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
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 * as React from "react";
|
||||
import { RouterLink } from "../RouterLink";
|
||||
import ThemeSwitcher from "./ThemeSwitcher";
|
||||
|
||||
const AppBar = styled(MuiAppBar)(({ theme }) => ({
|
||||
borderWidth: 0,
|
||||
borderBottomWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: (theme.vars ?? theme).palette.divider,
|
||||
boxShadow: "none",
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
}));
|
||||
|
||||
const LogoContainer = styled("div")({
|
||||
position: "relative",
|
||||
height: 40,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& img": {
|
||||
maxHeight: 40,
|
||||
},
|
||||
});
|
||||
|
||||
export interface DashboardHeaderProps {
|
||||
menuOpen: boolean;
|
||||
onToggleMenu: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export default function DashboardHeader({
|
||||
menuOpen,
|
||||
onToggleMenu,
|
||||
}: DashboardHeaderProps) {
|
||||
const handleMenuOpen = React.useCallback(() => {
|
||||
onToggleMenu(!menuOpen);
|
||||
}, [menuOpen, onToggleMenu]);
|
||||
|
||||
const getMenuIcon = React.useCallback(
|
||||
(isExpanded: boolean) => {
|
||||
const expandMenuActionText = "Expand";
|
||||
const collapseMenuActionText = "Collapse";
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={`${
|
||||
isExpanded ? collapseMenuActionText : expandMenuActionText
|
||||
} menu`}
|
||||
enterDelay={200}
|
||||
>
|
||||
<div>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={`${
|
||||
isExpanded ? collapseMenuActionText : expandMenuActionText
|
||||
} navigation menu`}
|
||||
onClick={handleMenuOpen}
|
||||
>
|
||||
{isExpanded ? <MenuOpenIcon /> : <MenuIcon />}
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
[handleMenuOpen]
|
||||
);
|
||||
|
||||
return (
|
||||
<AppBar color="inherit" position="absolute" sx={{ displayPrint: "none" }}>
|
||||
<Toolbar sx={{ backgroundColor: "inherit", mx: { xs: -0.75, sm: -1 } }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
sx={{
|
||||
flexWrap: "wrap",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<Box sx={{ mr: 3 }}>{getMenuIcon(menuOpen)}</Box>
|
||||
<RouterLink to="/">
|
||||
<Stack direction="row" alignItems="center">
|
||||
<LogoContainer>
|
||||
<Icon path={mdiMessageTextFast} size="2em" />
|
||||
</LogoContainer>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontWeight: "700",
|
||||
ml: 1,
|
||||
whiteSpace: "nowrap",
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
MatrixGW
|
||||
</Typography>
|
||||
</Stack>
|
||||
</RouterLink>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
sx={{ marginLeft: "auto" }}
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<ThemeSwitcher />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
205
matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx
Normal file
205
matrixgw_frontend/src/widgets/dashboard/DashboardSidebar.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
import { mdiBug, mdiForum, mdiKeyVariant, mdiLinkLock } from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
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 useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import * as React from "react";
|
||||
import DashboardSidebarContext from "./DashboardSidebarContext";
|
||||
import DashboardSidebarDividerItem from "./DashboardSidebarDividerItem";
|
||||
import DashboardSidebarPageItem from "./DashboardSidebarPageItem";
|
||||
import { DRAWER_WIDTH, MINI_DRAWER_WIDTH } from "./constants";
|
||||
import {
|
||||
getDrawerSxTransitionMixin,
|
||||
getDrawerWidthTransitionMixin,
|
||||
} from "./mixins";
|
||||
|
||||
export interface DashboardSidebarProps {
|
||||
expanded?: boolean;
|
||||
setExpanded: (expanded: boolean) => void;
|
||||
disableCollapsibleSidebar?: boolean;
|
||||
container?: Element;
|
||||
}
|
||||
|
||||
export default function DashboardSidebar({
|
||||
expanded = true,
|
||||
setExpanded,
|
||||
disableCollapsibleSidebar = false,
|
||||
container,
|
||||
}: DashboardSidebarProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const isOverSmViewport = useMediaQuery(theme.breakpoints.up("sm"));
|
||||
const isOverMdViewport = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
const [isFullyExpanded, setIsFullyExpanded] = React.useState(expanded);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (expanded) {
|
||||
const drawerWidthTransitionTimeout = setTimeout(() => {
|
||||
setIsFullyExpanded(true);
|
||||
}, theme.transitions.duration.enteringScreen);
|
||||
|
||||
return () => clearTimeout(drawerWidthTransitionTimeout);
|
||||
}
|
||||
|
||||
setIsFullyExpanded(false);
|
||||
|
||||
return () => {};
|
||||
}, [expanded, theme.transitions.duration.enteringScreen]);
|
||||
|
||||
const mini = !disableCollapsibleSidebar && !expanded;
|
||||
|
||||
const handleSetSidebarExpanded = React.useCallback(
|
||||
(newExpanded: boolean) => () => {
|
||||
setExpanded(newExpanded);
|
||||
},
|
||||
[setExpanded]
|
||||
);
|
||||
|
||||
const handlePageItemClick = React.useCallback(() => {
|
||||
if (!isOverSmViewport) {
|
||||
setExpanded(false);
|
||||
}
|
||||
}, [mini, setExpanded, isOverSmViewport]);
|
||||
|
||||
const hasDrawerTransitions =
|
||||
isOverSmViewport && (!disableCollapsibleSidebar || isOverMdViewport);
|
||||
|
||||
const getDrawerContent = React.useCallback(
|
||||
(viewport: "phone" | "tablet" | "desktop") => (
|
||||
<React.Fragment>
|
||||
<Toolbar />
|
||||
<Box
|
||||
component="nav"
|
||||
aria-label={`${viewport.charAt(0).toUpperCase()}${viewport.slice(1)}`}
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
overflow: "auto",
|
||||
scrollbarGutter: mini ? "stable" : "auto",
|
||||
overflowX: "hidden",
|
||||
pt: !mini ? 0 : 2,
|
||||
...(hasDrawerTransitions
|
||||
? getDrawerSxTransitionMixin(isFullyExpanded, "padding")
|
||||
: {}),
|
||||
}}
|
||||
>
|
||||
<List
|
||||
dense
|
||||
sx={{
|
||||
padding: mini ? 0 : 0.5,
|
||||
mb: 4,
|
||||
width: mini ? MINI_DRAWER_WIDTH : "auto",
|
||||
}}
|
||||
>
|
||||
<DashboardSidebarPageItem
|
||||
title="Messages"
|
||||
icon={<Icon path={mdiForum} size={"1.5em"} />}
|
||||
href="/"
|
||||
/>
|
||||
<DashboardSidebarDividerItem />
|
||||
<DashboardSidebarPageItem
|
||||
title="Matrix link"
|
||||
icon={<Icon path={mdiLinkLock} size={"1.5em"} />}
|
||||
href="/matrixlink"
|
||||
/>
|
||||
<DashboardSidebarPageItem
|
||||
title="API tokens"
|
||||
icon={<Icon path={mdiKeyVariant} size={"1.5em"} />}
|
||||
href="/tokens"
|
||||
/>
|
||||
<DashboardSidebarPageItem
|
||||
title="WS Debug"
|
||||
icon={<Icon path={mdiBug} size={"1.5em"} />}
|
||||
href="/wsdebug"
|
||||
/>
|
||||
</List>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
),
|
||||
[mini, hasDrawerTransitions, isFullyExpanded]
|
||||
);
|
||||
|
||||
const getDrawerSharedSx = React.useCallback(
|
||||
(isTemporary: boolean) => {
|
||||
const drawerWidth = mini ? MINI_DRAWER_WIDTH : DRAWER_WIDTH;
|
||||
|
||||
return {
|
||||
displayPrint: "none",
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
...getDrawerWidthTransitionMixin(expanded),
|
||||
...(isTemporary ? { position: "absolute" } : {}),
|
||||
[`& .MuiDrawer-paper`]: {
|
||||
position: "absolute",
|
||||
width: drawerWidth,
|
||||
boxSizing: "border-box",
|
||||
backgroundImage: "none",
|
||||
...getDrawerWidthTransitionMixin(expanded),
|
||||
},
|
||||
};
|
||||
},
|
||||
[expanded, mini]
|
||||
);
|
||||
|
||||
const sidebarContextValue = React.useMemo(() => {
|
||||
return {
|
||||
onPageItemClick: handlePageItemClick,
|
||||
mini,
|
||||
fullyExpanded: isFullyExpanded,
|
||||
hasDrawerTransitions,
|
||||
};
|
||||
}, [handlePageItemClick, mini, isFullyExpanded, hasDrawerTransitions]);
|
||||
|
||||
return (
|
||||
<DashboardSidebarContext.Provider value={sidebarContextValue}>
|
||||
<Drawer
|
||||
container={container}
|
||||
variant="temporary"
|
||||
open={expanded}
|
||||
onClose={handleSetSidebarExpanded(false)}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
}}
|
||||
sx={{
|
||||
display: {
|
||||
xs: "block",
|
||||
sm: disableCollapsibleSidebar ? "block" : "none",
|
||||
md: "none",
|
||||
},
|
||||
...getDrawerSharedSx(true),
|
||||
}}
|
||||
>
|
||||
{getDrawerContent("phone")}
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: {
|
||||
xs: "none",
|
||||
sm: disableCollapsibleSidebar ? "none" : "block",
|
||||
md: "none",
|
||||
},
|
||||
...getDrawerSharedSx(false),
|
||||
}}
|
||||
>
|
||||
{getDrawerContent("tablet")}
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: "none", md: "block" },
|
||||
...getDrawerSharedSx(false),
|
||||
}}
|
||||
>
|
||||
{getDrawerContent("desktop")}
|
||||
</Drawer>
|
||||
</DashboardSidebarContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import * as React from "react";
|
||||
|
||||
const DashboardSidebarContext = React.createContext<{
|
||||
onPageItemClick: () => void;
|
||||
mini: boolean;
|
||||
fullyExpanded: boolean;
|
||||
hasDrawerTransitions: boolean;
|
||||
} | null>(null);
|
||||
|
||||
export default DashboardSidebarContext;
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import type {} from "@mui/material/themeCssVarsAugmentation";
|
||||
import DashboardSidebarContext from "./DashboardSidebarContext";
|
||||
import { getDrawerSxTransitionMixin } from "./mixins";
|
||||
|
||||
export default function DashboardSidebarDividerItem() {
|
||||
const sidebarContext = React.useContext(DashboardSidebarContext);
|
||||
if (!sidebarContext) {
|
||||
throw new Error("Sidebar context was used without a provider.");
|
||||
}
|
||||
const { fullyExpanded = true, hasDrawerTransitions } = sidebarContext;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Divider
|
||||
sx={{
|
||||
borderBottomWidth: 1,
|
||||
my: 1,
|
||||
mx: -0.5,
|
||||
...(hasDrawerTransitions
|
||||
? getDrawerSxTransitionMixin(fullyExpanded, "margin")
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
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 Typography from "@mui/material/Typography";
|
||||
import type {} from "@mui/material/themeCssVarsAugmentation";
|
||||
import * as React from "react";
|
||||
import { Link, matchPath, useLocation } from "react-router";
|
||||
import DashboardSidebarContext from "./DashboardSidebarContext";
|
||||
import { MINI_DRAWER_WIDTH } from "./constants";
|
||||
|
||||
export interface DashboardSidebarPageItemProps {
|
||||
title: string;
|
||||
icon?: React.ReactNode;
|
||||
href: string;
|
||||
action?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function DashboardSidebarPageItem({
|
||||
title,
|
||||
icon,
|
||||
href,
|
||||
action,
|
||||
disabled = false,
|
||||
}: DashboardSidebarPageItemProps) {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const sidebarContext = React.useContext(DashboardSidebarContext);
|
||||
if (!sidebarContext) {
|
||||
throw new Error("Sidebar context was used without a provider.");
|
||||
}
|
||||
const {
|
||||
onPageItemClick,
|
||||
mini = false,
|
||||
fullyExpanded = true,
|
||||
} = sidebarContext;
|
||||
|
||||
const hasExternalHref = href
|
||||
? href.startsWith("http://") || href.startsWith("https://")
|
||||
: false;
|
||||
|
||||
const LinkComponent = hasExternalHref ? "a" : Link;
|
||||
|
||||
const selected = !!matchPath(href, pathname);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ListItem disablePadding style={{ padding: "5px" }}>
|
||||
<ListItemButton
|
||||
selected={selected}
|
||||
disabled={disabled}
|
||||
sx={{
|
||||
height: mini ? 50 : "auto",
|
||||
}}
|
||||
{...{
|
||||
LinkComponent,
|
||||
...(hasExternalHref
|
||||
? {
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
}
|
||||
: {}),
|
||||
to: href,
|
||||
onClick: onPageItemClick,
|
||||
}}
|
||||
>
|
||||
{icon || mini ? (
|
||||
<Box
|
||||
sx={
|
||||
mini
|
||||
? {
|
||||
position: "absolute",
|
||||
left: "50%",
|
||||
top: "calc(50% - 6px)",
|
||||
transform: "translate(-50%, -50%)",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: mini ? "center" : "auto",
|
||||
}}
|
||||
>
|
||||
{icon ?? null}
|
||||
{!icon && mini ? (
|
||||
<Avatar
|
||||
sx={{
|
||||
fontSize: 10,
|
||||
height: 16,
|
||||
width: 16,
|
||||
}}
|
||||
>
|
||||
{title
|
||||
.split(" ")
|
||||
.slice(0, 2)
|
||||
.map((titleWord) => titleWord.charAt(0).toUpperCase())}
|
||||
</Avatar>
|
||||
) : null}
|
||||
</ListItemIcon>
|
||||
{mini ? (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: -18,
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
fontSize: 10,
|
||||
fontWeight: 500,
|
||||
textAlign: "center",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: MINI_DRAWER_WIDTH - 28,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
) : null}
|
||||
</Box>
|
||||
) : null}
|
||||
{!mini ? (
|
||||
<ListItemText
|
||||
primary={title}
|
||||
sx={{
|
||||
whiteSpace: "nowrap",
|
||||
zIndex: 1,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{action && !mini && fullyExpanded ? action : null}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as React from "react";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import type {} from "@mui/material/themeCssVarsAugmentation";
|
||||
import DashboardSidebarContext from "./DashboardSidebarContext";
|
||||
import { getDrawerSxTransitionMixin } from "./mixins";
|
||||
|
||||
export default function DashboardSidebarDividerItem() {
|
||||
const sidebarContext = React.useContext(DashboardSidebarContext);
|
||||
if (!sidebarContext) {
|
||||
throw new Error("Sidebar context was used without a provider.");
|
||||
}
|
||||
const { fullyExpanded = true, hasDrawerTransitions } = sidebarContext;
|
||||
|
||||
return (
|
||||
<li>
|
||||
<Divider
|
||||
sx={{
|
||||
borderBottomWidth: 1,
|
||||
my: 1,
|
||||
mx: -0.5,
|
||||
...(hasDrawerTransitions
|
||||
? getDrawerSxTransitionMixin(fullyExpanded, "margin")
|
||||
: {}),
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
59
matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx
Normal file
59
matrixgw_frontend/src/widgets/dashboard/ThemeSwitcher.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as React from "react";
|
||||
import { useTheme, useColorScheme } from "@mui/material/styles";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import DarkModeIcon from "@mui/icons-material/DarkMode";
|
||||
import LightModeIcon from "@mui/icons-material/LightMode";
|
||||
import type {} from "@mui/material/themeCssVarsAugmentation";
|
||||
|
||||
export default function ThemeSwitcher() {
|
||||
const theme = useTheme();
|
||||
|
||||
const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
|
||||
const preferredMode = prefersDarkMode ? "dark" : "light";
|
||||
|
||||
const { mode, setMode } = useColorScheme();
|
||||
|
||||
const paletteMode = !mode || mode === "system" ? preferredMode : mode;
|
||||
|
||||
const toggleMode = React.useCallback(() => {
|
||||
setMode(paletteMode === "dark" ? "light" : "dark");
|
||||
}, [setMode, paletteMode]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={`${paletteMode === "dark" ? "Light" : "Dark"} mode`}
|
||||
enterDelay={1000}
|
||||
>
|
||||
<div>
|
||||
<IconButton
|
||||
size="small"
|
||||
aria-label={`Switch to ${
|
||||
paletteMode === "dark" ? "light" : "dark"
|
||||
} mode`}
|
||||
onClick={toggleMode}
|
||||
>
|
||||
<React.Fragment>
|
||||
<LightModeIcon
|
||||
sx={{
|
||||
display: "inline",
|
||||
[theme.getColorSchemeSelector("dark")]: {
|
||||
display: "none",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<DarkModeIcon
|
||||
sx={{
|
||||
display: "none",
|
||||
[theme.getColorSchemeSelector("dark")]: {
|
||||
display: "inline",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
2
matrixgw_frontend/src/widgets/dashboard/constants.ts
Normal file
2
matrixgw_frontend/src/widgets/dashboard/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const DRAWER_WIDTH = 240; // px
|
||||
export const MINI_DRAWER_WIDTH = 90; // px
|
||||
23
matrixgw_frontend/src/widgets/dashboard/mixins.ts
Normal file
23
matrixgw_frontend/src/widgets/dashboard/mixins.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { type Theme } from "@mui/material/styles";
|
||||
|
||||
export function getDrawerSxTransitionMixin(
|
||||
isExpanded: boolean,
|
||||
property: string
|
||||
) {
|
||||
return {
|
||||
transition: (theme: Theme) =>
|
||||
theme.transitions.create(property, {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: isExpanded
|
||||
? theme.transitions.duration.enteringScreen
|
||||
: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function getDrawerWidthTransitionMixin(isExpanded: boolean) {
|
||||
return {
|
||||
...getDrawerSxTransitionMixin(isExpanded, "width"),
|
||||
overflowX: "hidden",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user