diff --git a/.changeset/two-lions-promise.md b/.changeset/two-lions-promise.md new file mode 100644 index 0000000000..4a57683e17 --- /dev/null +++ b/.changeset/two-lions-promise.md @@ -0,0 +1,5 @@ +--- +"@comet/admin": major +--- + +`MenuItem` no longer supports props from MUI's `ListItem` but those from `ListItemButton` instead diff --git a/packages/admin/admin/src/mui/menu/CollapsibleItem.tsx b/packages/admin/admin/src/mui/menu/CollapsibleItem.tsx index b115f4584d..408696896f 100644 --- a/packages/admin/admin/src/mui/menu/CollapsibleItem.tsx +++ b/packages/admin/admin/src/mui/menu/CollapsibleItem.tsx @@ -47,13 +47,23 @@ const ListItem = styled("div", { `, ); +const Item = styled(MenuItem, { + name: "CometAdminMenuCollapsibleItem", + slot: "menuItem", + overridesResolver(_, styles) { + return [styles.menuItem]; + }, +})(); + export interface MenuLevel { level?: 1 | 2; } type MenuChild = React.ReactElement; -export interface MenuCollapsibleItemProps extends ThemedComponentBaseProps<{ root: "div"; listItem: "div" }>, MenuItemProps { +export interface MenuCollapsibleItemProps + extends Omit, + ThemedComponentBaseProps<{ root: "div"; listItem: "div"; menuItem: typeof MenuItem }> { children: MenuChild | MenuChild[]; openByDefault?: boolean; openedIcon?: React.ReactNode; @@ -100,13 +110,14 @@ export function MenuCollapsibleItem(inProps: MenuCollapsibleItemProps) { return ( - setOpen(!open)} secondaryAction={open ? openedIcon : closedIcon} + {...slotProps?.menuItem} /> diff --git a/packages/admin/admin/src/mui/menu/Item.tsx b/packages/admin/admin/src/mui/menu/Item.tsx index f23cf11ecb..c03962ae21 100644 --- a/packages/admin/admin/src/mui/menu/Item.tsx +++ b/packages/admin/admin/src/mui/menu/Item.tsx @@ -1,5 +1,6 @@ -import { ComponentsOverrides, ListItem, ListItemIcon, ListItemProps, ListItemText, Theme } from "@mui/material"; -import { createStyles, WithStyles, withStyles } from "@mui/styles"; +import { ListItemButton, ListItemButtonProps, ListItemIcon, ListItemText } from "@mui/material"; +import { ComponentsOverrides, css, styled, Theme, useThemeProps } from "@mui/material/styles"; +import { ThemedComponentBaseProps } from "helpers/ThemedComponentBaseProps"; import * as React from "react"; import { MenuLevel } from "./CollapsibleItem"; @@ -7,138 +8,170 @@ import { MenuContext } from "./Context"; export type MenuItemClassKey = "root" | "level1" | "level2" | "hasIcon" | "hasSecondaryText" | "hasSecondaryAction"; +type OwnerState = Pick; + const colors = { textLevel1: "#242424", textLevel2: "#17181A", }; -const styles = (theme: Theme) => - createStyles({ - root: { - flexShrink: 0, - "&:after": { - content: "''", - position: "absolute", - top: 0, - right: 0, - bottom: 0, - width: 2, - }, - "& [class*='MuiListItemIcon-root']": { - color: colors.textLevel1, - minWidth: 28, - }, - "& [class*='MuiListItemText-inset']": { - paddingLeft: 28, - }, - "& [class*='MuiSvgIcon-root']": { - fontSize: 16, - }, - }, - level1: { - borderBottom: `1px solid ${theme.palette.grey[50]}`, - boxSizing: "border-box", - color: colors.textLevel1, - paddingLeft: 20, - paddingRight: 20, - paddingTop: 16, - paddingBottom: 16, - "&[class*='Mui-selected']": { - backgroundColor: theme.palette.grey[50], - color: theme.palette.primary.main, - "&:after": { - backgroundColor: theme.palette.primary.main, - }, - "& [class*='MuiListItemIcon-root']": { - color: theme.palette.primary.main, - }, - }, - "& [class*='MuiListItemText-primary']": { - fontWeight: theme.typography.fontWeightMedium, - fontSize: 16, - lineHeight: "20px", - }, - }, - level2: { - color: colors.textLevel2, - paddingLeft: 20, - paddingRight: 20, - paddingTop: 10, - paddingBottom: 10, - "&:last-child": { - borderBottom: `1px solid ${theme.palette.grey[50]}`, - boxSizing: "border-box", - }, - "&[class*='Mui-selected']": { - backgroundColor: theme.palette.primary.main, - color: "#fff", - "&:after": { - backgroundColor: theme.palette.primary.dark, - }, - "&:hover": { - backgroundColor: theme.palette.primary.dark, - }, - "& [class*='MuiListItemText-primary']": { - fontWeight: theme.typography.fontWeightBold, - }, - }, - "& [class*='MuiListItemText-root']": { - margin: 0, - }, - "& [class*='MuiListItemText-primary']": { - fontWeight: theme.typography.fontWeightRegular, - fontSize: 14, - lineHeight: "20px", - }, - }, - hasIcon: {}, - hasSecondaryText: {}, - hasSecondaryAction: { - paddingRight: 18, - }, - }); +const Root = styled(ListItemButton, { + name: "CometAdminMenuItem", + slot: "root", + overridesResolver({ ownerState }: { ownerState: OwnerState }, styles) { + return [ + styles.root, + ownerState.level === 1 && styles.level1, + ownerState.level === 2 && styles.level2, + ownerState.icon && styles.hasIcon, + ownerState.secondaryAction && styles.hasSecondaryAction, + ownerState.secondary && styles.hasSecondaryText, + ]; + }, +})<{ ownerState: OwnerState }>( + ({ theme, ownerState }) => css` + flex-shrink: 0; + flex-grow: 0; + + &:after { + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 2px; + } + + .MuiListItemIcon-root { + color: ${colors.textLevel1}; + min-width: 28px; + } + + .MuiListItemText-inset { + padding-left: 28px; + } + + .MuiSvgIcon-root { + font-size: 16px; + } + + ${ownerState.level === 1 && + css` + border-bottom: 1px solid ${theme.palette.grey[50]}; + box-sizing: border-box; + color: ${colors.textLevel1}; + padding-left: 20px; + padding-right: 20px; + padding-top: 16px; + padding-bottom: 16px; + + &.Mui-selected { + background-color: ${theme.palette.grey[50]}; + color: ${theme.palette.primary.main}; + + &:after { + background-color: ${theme.palette.primary.main}; + } + .MuiListItemIcon-root { + color: ${theme.palette.primary.main}; + } + } + + .MuiListItemText-primary { + font-weight: ${theme.typography.fontWeightMedium}; + font-size: 16px; + line-height: 20px; + } + `} + + ${ownerState.level === 2 && + css` + color: ${colors.textLevel2}; + padding-left: 20px; + padding-right: 20px; + padding-top: 10px; + padding-bottom: 10px; + + &:last-child { + border-bottom: 1px solid ${theme.palette.grey[50]}; + box-sizing: border-box; + } + + &.Mui-selected { + background-color: ${theme.palette.primary.main}; + color: #fff; + + &:after { + background-color: ${theme.palette.primary.dark}; + } + &:hover { + background-color: ${theme.palette.primary.dark}; + } + & .MuiListItemText-primary { + font-weight: ${theme.typography.fontWeightBold}; + } + } -export interface MenuItemProps extends MenuLevel { + .MuiListItemText-root { + margin: 0; + } + + .MuiListItemText-primary { + font-weight: ${theme.typography.fontWeightRegular}; + font-size: 14px; + line-height: 20px; + } + `}; + + ${ownerState.secondaryAction && + css` + padding-right: 18px; + `} + `, +); + +export interface MenuItemProps extends ThemedComponentBaseProps<{ root: typeof ListItemButton }>, MenuLevel, ListItemButtonProps { primary: React.ReactNode; secondary?: React.ReactNode; icon?: React.ReactElement; secondaryAction?: React.ReactNode; } -type MuiListItemProps = Pick> & { component?: React.ElementType }; - -const Item: React.FC & MenuItemProps & MuiListItemProps> = ({ - classes, - primary, - secondary, - icon, - level = 1, - secondaryAction, - ...otherProps -}) => { - const context = React.useContext(MenuContext); - if (!context) throw new Error("Could not find context for menu"); - if (level > 2) throw new Error("Maximum nesting level of 2 exceeded."); +export function MenuItem(inProps: MenuItemProps) { + const { + primary, + secondary, + icon, + level = 1, + secondaryAction, + slotProps, + ...otherProps + } = useThemeProps({ + props: inProps, + name: "CometAdminMenuItem", + }); + + const ownerState: OwnerState = { + level, + icon, + secondaryAction, + secondary, + }; const hasIcon = !!icon; - const listItemClasses = [classes.root]; - if (level === 1) listItemClasses.push(classes.level1); - if (level === 2) listItemClasses.push(classes.level2); - if (hasIcon) listItemClasses.push(classes.hasIcon); - if (secondary) listItemClasses.push(classes.hasSecondaryText); - if (secondaryAction) listItemClasses.push(classes.hasSecondaryAction); + const context = React.useContext(MenuContext); + if (!context) throw new Error("Could not find context for menu"); + if (level > 2) throw new Error("Maximum nesting level of 2 exceeded."); return ( - + {hasIcon && {icon}} {!!secondaryAction && secondaryAction} - + ); -}; - -export const MenuItem = withStyles(styles, { name: "CometAdminMenuItem" })(Item); +} declare module "@mui/material/styles" { interface ComponentNameToClassKey { diff --git a/packages/admin/admin/src/mui/menu/ItemAnchorLink.tsx b/packages/admin/admin/src/mui/menu/ItemAnchorLink.tsx index 5b086194b8..7473ba2f59 100644 --- a/packages/admin/admin/src/mui/menu/ItemAnchorLink.tsx +++ b/packages/admin/admin/src/mui/menu/ItemAnchorLink.tsx @@ -1,8 +1,9 @@ -import { ListItemProps } from "@mui/material/ListItem"; +import { ListItemButtonProps } from "@mui/material"; import * as React from "react"; import { MenuItem, MenuItemProps } from "./Item"; -export type MenuItemAnchorLinkProps = MenuItemProps & ListItemProps & React.HTMLProps; +export type MenuItemAnchorLinkProps = MenuItemProps & ListItemButtonProps & React.HTMLProps; +// @ts-expect-error "component"-property is used as described in the documentation https://mui.com/material-ui/react-list/, but type is missing in ListItemButtonProps export const MenuItemAnchorLink: React.FC = (props) => ;