Skip to content

Commit

Permalink
Collapsed menu (#1233)
Browse files Browse the repository at this point in the history
With this PR the Menu will stay open in collapsed variant showing only
the icons.

**Note:** The chevron arrows which are currently cut of will be fixed in
another PR.

## Screen recording


https://github.com/vivid-planet/comet/assets/56400587/6034a7e2-3f25-4c6d-b4a8-2b17331574ec

---------

Co-authored-by: Ricky James Smith <jamesricky@me.com>
Co-authored-by: Johannes Obermair <48853629+johnnyomair@users.noreply.github.com>
Co-authored-by: Thomas Dax <thomas.dax@vivid-planet.com>
  • Loading branch information
4 people authored Feb 22, 2024
1 parent f06f4be commit 02d33e2
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 71 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-waves-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@comet/admin": minor
---

Show icons in permanent menu even in closed state.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ lang/
.pnp.*
junit.xml
.env.local
/.idea/**
5 changes: 3 additions & 2 deletions packages/admin/admin/src/mui/menu/Item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const Item: React.FC<WithStyles<typeof styles> & MenuItemProps & MuiListItemProp
if (level > 2) throw new Error("Maximum nesting level of 2 exceeded.");

const hasIcon = !!icon;
const showText = context.open || level !== 1;

const listItemClasses = [classes.root];
if (level === 1) listItemClasses.push(classes.level1);
Expand All @@ -131,8 +132,8 @@ const Item: React.FC<WithStyles<typeof styles> & MenuItemProps & MuiListItemProp

return (
<ListItem component="div" button classes={{ root: listItemClasses.join(" ") }} {...otherProps}>
{hasIcon && <ListItemIcon>{icon}</ListItemIcon>}
<ListItemText primary={primary} secondary={secondary} inset={!icon} />
{hasIcon && <ListItemIcon sx={{ my: 1.25 }}>{icon}</ListItemIcon>}
{showText && <ListItemText primary={primary} secondary={secondary} inset={!icon} />}
{!!secondaryAction && secondaryAction}
</ListItem>
);
Expand Down
78 changes: 70 additions & 8 deletions packages/admin/admin/src/mui/menu/ItemGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,93 @@
import { Box, ComponentsOverrides, Theme, Typography } from "@mui/material";
import { Box, ComponentsOverrides, Theme, Tooltip, Typography } from "@mui/material";
import { createStyles, WithStyles, withStyles } from "@mui/styles";
import clsx from "clsx";
import * as React from "react";
import { FormattedMessage, MessageDescriptor, useIntl } from "react-intl";

export type MenuItemGroupClassKey = "root" | "title";
import { MenuContext } from "./Context";

export type MenuItemGroupClassKey = "root" | "title" | "titleMenuOpen" | "titleContainer" | "titleContainerMenuOpen";

const styles = (theme: Theme) =>
createStyles<MenuItemGroupClassKey, MenuItemGroupProps>({
root: { marginTop: theme.spacing(8) },
title: {
fontWeight: theme.typography.fontWeightBold,
fontSize: 14,
fontSize: 12,
border: `2px solid ${theme.palette.grey[100]}`,
borderRadius: 20,
padding: theme.spacing(0.5, 2),
lineHeight: "20px",
color: `${theme.palette.grey[300]}`,
},
titleMenuOpen: {
fontSize: 14,
border: `2px solid ${theme.palette.common.white}`,
borderRadius: "initial",
padding: 0,
color: theme.palette.common.black,
},
titleContainer: {
borderBottom: `1px solid ${theme.palette.grey[50]}`,
display: "flex",
justifyContent: "center",
padding: `${theme.spacing(2)} 0`,
},
titleContainerMenuOpen: {
justifyContent: "flex-start",
padding: theme.spacing(2, 4),
},
});

export interface MenuItemGroupProps {
title?: React.ReactNode;
title: React.ReactNode;
shortTitle?: React.ReactNode;
}

const ItemGroup: React.FC<React.PropsWithChildren<WithStyles<typeof styles> & MenuItemGroupProps>> = ({ title, children, classes }) => {
const ItemGroup: React.FC<React.PropsWithChildren<WithStyles<typeof styles> & MenuItemGroupProps>> = ({ title, shortTitle, children, classes }) => {
const { open: menuOpen } = React.useContext(MenuContext);
const intl = useIntl();
let displayedTitle = title;

function isFormattedMessage(node: React.ReactNode): node is React.ReactElement<MessageDescriptor> {
return !!node && React.isValidElement(node) && node.type === FormattedMessage;
}

function getInitials(title: React.ReactNode) {
let titleAsString: string;
if (typeof title === "string") {
titleAsString = title;
} else if (isFormattedMessage(title)) {
titleAsString = intl.formatMessage(title.props);
} else {
throw new TypeError("Title must be either a string or a FormattedMessage");
}
const words = titleAsString.split(/\s+/).filter((word) => word.match(/[A-Za-z]/));

if (words.length > 3) {
console.warn("Title has more than 3 words, only the first 3 will be used.");

return words
.slice(0, 3)
.map((word) => word[0].toUpperCase())
.join("");
}
return words.map((word) => word[0].toUpperCase()).join("");
}

if (!menuOpen) {
displayedTitle = shortTitle || getInitials(title);
}

return (
<Box className={classes.root}>
<Typography className={classes.title} variant="h3">
{title}
</Typography>
<Tooltip placement="right" disableHoverListener={menuOpen} disableFocusListener={menuOpen} disableTouchListener={menuOpen} title={title}>
<Box className={clsx(classes.titleContainer, menuOpen && classes.titleContainerMenuOpen)}>
<Typography className={clsx(classes.title, menuOpen && classes.titleMenuOpen)} variant="h3">
{displayedTitle}
</Typography>
</Box>
</Tooltip>
{children}
</Box>
);
Expand Down
104 changes: 56 additions & 48 deletions packages/admin/admin/src/mui/menu/Menu.styles.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,66 @@
import { Theme } from "@mui/material";
import { createStyles } from "@mui/styles";
import { CSSObject, Drawer as MuiDrawer, DrawerProps, Theme } from "@mui/material";
import { styled } from "@mui/material/styles";
import { createStyles, StyledComponent } from "@mui/styles";
import { MUIStyledCommonProps } from "@mui/system";
import * as React from "react";

import { MenuProps } from "./Menu";
import { MasterLayoutContext } from "../MasterLayoutContext";
import { DEFAULT_DRAWER_WIDTH, DEFAULT_DRAWER_WIDTH_COLLAPSED, MenuProps } from "./Menu";

export type MenuClassKey = "drawer" | "permanent" | "temporary" | "open" | "closed";
const openedMixin = (theme: Theme, drawerWidth?: number): CSSObject => ({
width: drawerWidth ?? DEFAULT_DRAWER_WIDTH,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: "hidden",
});
const closedMixin = (theme: Theme, drawerVariant: DrawerProps["variant"], drawerWidth?: number, drawerWidthCollapsed?: number): CSSObject => ({
width: drawerVariant === "temporary" ? drawerWidth ?? DEFAULT_DRAWER_WIDTH : drawerWidthCollapsed ?? DEFAULT_DRAWER_WIDTH_COLLAPSED,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: "hidden",
});
export const Drawer: StyledComponent<DrawerProps & MUIStyledCommonProps<Theme> & Pick<MenuProps, "drawerWidth" | "drawerWidthCollapsed">> = styled(
MuiDrawer,
{ shouldForwardProp: (prop) => prop !== "drawerWidth" && prop !== "drawerWidthCollapsed" },
)<DrawerProps & MUIStyledCommonProps<Theme> & Pick<MenuProps, "drawerWidth" | "drawerWidthCollapsed">>(
({ theme, open, variant, drawerWidth, drawerWidthCollapsed }) => {
const { headerHeight } = React.useContext(MasterLayoutContext);

export const styles = (theme: Theme) =>
createStyles<MenuClassKey, MenuProps>({
drawer: {
"& [class*='MuiDrawer-paper']": {
backgroundColor: "#fff",
},
"& [class*='MuiPaper-root']": {
flexGrow: 1,
overflowX: "hidden",
return {
...(variant === "permanent" && {
backgroundColor: theme.palette.common.white,
flexShrink: 0,
whiteSpace: "nowrap",
boxSizing: "border-box",
...(open ? openedMixin(theme, drawerWidth) : closedMixin(theme, variant, drawerWidth, drawerWidthCollapsed)),
}),
"& .MuiDrawer-paper": {
backgroundColor: theme.palette.common.white,
...(variant === "permanent" && {
top: headerHeight,
height: `calc(100% - ${headerHeight}px)`,
}),
...(open ? openedMixin(theme, drawerWidth) : closedMixin(theme, variant, drawerWidth, drawerWidthCollapsed)),
},
"& [class*='MuiDrawer-paperAnchorLeft']": {
"& .MuiDrawer-paperAnchorLeft": {
borderRight: "none",
},
"&$permanent": {
"&$open": {
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
"& [class*='MuiPaper-root']": {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
},
"&$closed": {
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.leavingScreen,
}),
"& [class*='MuiPaper-root']": {
transition: theme.transitions.create("margin", {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
},
},
},
},
};
},
);

export type MenuClassKey = "drawer" | "permanent" | "temporary" | "open" | "closed";

export const styles = () => {
return createStyles<MenuClassKey, MenuProps>({
drawer: {},
permanent: {},
temporary: {},
open: {},
closed: {
"&$permanent": {
"& [class*='MuiPaper']": {
boxShadow: "none",
},
},
},
closed: {},
});
};
26 changes: 13 additions & 13 deletions packages/admin/admin/src/mui/menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { ComponentsOverrides, Drawer, DrawerProps, PaperProps, Theme } from "@mui/material";
import { ComponentsOverrides, DrawerProps, PaperProps, Theme } from "@mui/material";
import { WithStyles, withStyles } from "@mui/styles";
import * as React from "react";
import { useHistory } from "react-router";

import { MasterLayoutContext } from "../MasterLayoutContext";
import { MenuContext } from "./Context";
import { MenuClassKey, styles } from "./Menu.styles";
import { Drawer, MenuClassKey, styles } from "./Menu.styles";

export const DEFAULT_DRAWER_WIDTH = 300;
export const DEFAULT_DRAWER_WIDTH_COLLAPSED = 60;

export interface MenuProps {
children: React.ReactNode;
variant?: "permanent" | "temporary";
drawerWidth?: number;
drawerWidthCollapsed?: number;
temporaryDrawerProps?: DrawerProps;
permanentDrawerProps?: DrawerProps;
temporaryDrawerPaperProps?: PaperProps;
Expand All @@ -20,7 +23,8 @@ export interface MenuProps {
const MenuDrawer: React.FC<WithStyles<typeof styles> & MenuProps> = ({
classes,
children,
drawerWidth = 300,
drawerWidth = DEFAULT_DRAWER_WIDTH,
drawerWidthCollapsed = DEFAULT_DRAWER_WIDTH_COLLAPSED,
variant = "permanent",
temporaryDrawerProps = {},
permanentDrawerProps = {},
Expand All @@ -29,7 +33,6 @@ const MenuDrawer: React.FC<WithStyles<typeof styles> & MenuProps> = ({
}) => {
const history = useHistory();
const { open, toggleOpen } = React.useContext(MenuContext);
const { headerHeight } = React.useContext(MasterLayoutContext);
const initialRender = React.useRef(true);

// Close the menu on initial render if it is temporary to prevent a page-overlay when initially loading the page.
Expand Down Expand Up @@ -67,10 +70,11 @@ const MenuDrawer: React.FC<WithStyles<typeof styles> & MenuProps> = ({
<>
<Drawer
variant="temporary"
drawerWidth={drawerWidth}
className={temporaryDrawerClasses.join(" ")}
// workaround for issue: https://github.com/mui/material-ui/issues/35793
open={initialRender.current ? false : temporaryOpen}
PaperProps={{ style: { width: drawerWidth }, ...temporaryDrawerPaperProps }}
PaperProps={{ ...temporaryDrawerPaperProps }}
onClose={toggleOpen}
{...temporaryDrawerProps}
>
Expand All @@ -79,17 +83,13 @@ const MenuDrawer: React.FC<WithStyles<typeof styles> & MenuProps> = ({

<Drawer
variant="permanent"
drawerWidth={drawerWidth}
drawerWidthCollapsed={drawerWidthCollapsed}
className={permanentDrawerClasses.join(" ")}
open={permanentOpen}
style={{ width: permanentOpen ? drawerWidth : 0 }}
hidden={variant === "temporary"}
PaperProps={{
elevation: 2,
style: {
top: headerHeight,
height: `calc(100% - ${headerHeight}px)`,
width: drawerWidth,
marginLeft: permanentOpen ? 0 : -drawerWidth,
},
...permanentDrawerPaperProps,
}}
{...permanentDrawerProps}
Expand Down

0 comments on commit 02d33e2

Please sign in to comment.