Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COM-39: Collapsed menu #1233

Merged
merged 24 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0c7b336
feat(Menu): add MenuGroupSection component
jomunker Aug 18, 2023
ab4ed1c
fix(Menu): CometAdminMenu spelled wrong
jomunker Aug 18, 2023
d233db4
feat(demo): use MenuGroupSection component in MasterMenu
jomunker Aug 18, 2023
48d43b7
feat(stories): use MenuGroupSection component in Menu story
jomunker Aug 18, 2023
8b1d9cd
feat(GroupSection): Make defaultProps overwritable
jomunker Sep 25, 2023
44fc74d
feat(GroupSection): use class instead of padding props
jomunker Sep 25, 2023
a1cd1b0
refactor(GroupSection): rename group section and all types to item group
jomunker Sep 25, 2023
7d90f45
Merge branch 'next' into add-groups-to-menu
jomunker Sep 25, 2023
57ba18a
feat(GroupSection): show initials of group title if drawer closed
jomunker Aug 21, 2023
8458858
feat(MenuItem): only show text if drawer open
jomunker Aug 21, 2023
8af75c2
feat(Menu): keep icons in collapsed permanent menu variant
jomunker Aug 21, 2023
a3cf811
feat(changeset): add changeset for collapsed menu
jomunker Aug 22, 2023
771a146
feat(YoutubeVideoBlock): add ReactNode type to title in MenuItemGroup…
jomunker Oct 5, 2023
ae7c94d
refactor(ItemGroup): remove unnecessary string type from MenuItemGrou…
jomunker Oct 13, 2023
e661b51
Merge branch 'next' into add-groups-to-menu
jomunker Oct 13, 2023
bcd87c3
Merge branch 'add-groups-to-menu' into collapsed-menu
jomunker Oct 13, 2023
60f0ede
feat(MenuItem): remove drawerOpen and showText prop
jomunker Dec 11, 2023
096ef8a
refactor(Menu): prefix constants with DEFAULT_
jomunker Dec 11, 2023
db11d9e
refactor(ItemGroup): introduce shortTitle prop
jomunker Dec 11, 2023
d6dae21
remove: .DS_Store files
jomunker Dec 11, 2023
641ea70
Merge remote-tracking branch 'origin/feature/menu-rework' into collap…
jomunker Jan 3, 2024
fd9eb4d
feat(ItemGroup): add support for FormattedMessage React.Node as title…
jomunker Jan 12, 2024
563d5d5
feat(MenuItemGroup): change type of prop shortTitle to React.ReactNode
jomunker Feb 6, 2024
5785d7d
docs: Update changeset
jomunker Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
---

Permanent menu will show icons 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
Loading