From d495f7af37b5b0911c214090cf5d88e7c2fb201c Mon Sep 17 00:00:00 2001 From: Damien Robson Date: Tue, 26 Nov 2024 18:25:50 +0000 Subject: [PATCH] feat(menu-fullscreen): fix colours when menuitem used as child of menusegmenttitle --- .../menu/menu-item/menu-item.component.tsx | 19 ++++- .../menu/menu-item/menu-item.test.tsx | 17 ++++- .../menu-segment-title.component.tsx | 28 ++++++- .../menu-segment-title.context.tsx | 13 ++++ .../menu-segment-title.style.ts | 32 +++++--- .../menu-segment-title.test.tsx | 21 ++++- src/components/menu/menu.mdx | 12 ++- src/components/menu/menu.stories.tsx | 76 +++++++++++++++++++ 8 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 src/components/menu/menu-segment-title/menu-segment-title.context.tsx diff --git a/src/components/menu/menu-item/menu-item.component.tsx b/src/components/menu/menu-item/menu-item.component.tsx index 6530ab82a1..996a2c70fa 100644 --- a/src/components/menu/menu-item/menu-item.component.tsx +++ b/src/components/menu/menu-item/menu-item.component.tsx @@ -14,6 +14,9 @@ import Submenu from "../__internal__/submenu/submenu.component"; import SubmenuContext, { SubmenuContextProps, } from "../__internal__/submenu/submenu.context"; +import MenuSegmentContext, { + MenuSegmentContextProps, +} from "../menu-segment-title/menu-segment-title.context"; import { StyledMenuItem } from "../menu.style"; import guid from "../../../__internal__/utils/helpers/guid"; import { IconType } from "../../icon"; @@ -62,7 +65,7 @@ interface MenuItemBaseProps onSubmenuOpen?: () => void; /** Callback triggered when submenu closes. Only valid with submenu prop */ onSubmenuClose?: () => void; - /** + /** @ignore @private private prop, used inside ScrollableBlock to ensure the MenuItem's color variant overrides the CSS for other MenuItems inside the block @@ -138,6 +141,9 @@ export const MenuItem = ({ "You should not pass `children` when `submenu` is an empty string", ); + const { isChildOfSegment, overriddenVariant } = + useContext(MenuSegmentContext); + const { inFullscreenView, registerItem, @@ -264,6 +270,15 @@ export const MenuItem = ({ ref, }; + if ( + overriddenVariant === "alternate" && + isChildOfSegment && + variant === "alternate" && + ["white", "black"].includes(menuType) + ) { + elementProps.overrideColor = true; + } + const getTitle = (title: React.ReactNode) => maxWidth && typeof title === "string" ? title : undefined; @@ -345,6 +360,4 @@ export const MenuItem = ({ ); }; -MenuItem.displayName = "MenuItem"; - export default MenuItem; diff --git a/src/components/menu/menu-item/menu-item.test.tsx b/src/components/menu/menu-item/menu-item.test.tsx index a66cd2cf30..ea00b3f044 100644 --- a/src/components/menu/menu-item/menu-item.test.tsx +++ b/src/components/menu/menu-item/menu-item.test.tsx @@ -2,7 +2,7 @@ import React from "react"; import { fireEvent, render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { MenuItem } from ".."; +import { MenuItem, MenuSegmentTitle } from ".."; import { testStyledSystemFlexBox, testStyledSystemPadding, @@ -1076,3 +1076,18 @@ test("should throw when `children` passed and `submenu` is an empty string", () consoleSpy.mockRestore(); }); + +// coverage +test("should set the correct colour when a child of `MenuSegmentTitle` and `variant` is 'alternate'", async () => { + render( + + + Item One + + , + ); + + expect(screen.getByTestId("menu-item-wrapper")).toHaveStyle({ + backgroundColor: menuConfigVariants.black.alternate, + }); +}); diff --git a/src/components/menu/menu-segment-title/menu-segment-title.component.tsx b/src/components/menu/menu-segment-title/menu-segment-title.component.tsx index d591a876de..1320aa39e2 100644 --- a/src/components/menu/menu-segment-title/menu-segment-title.component.tsx +++ b/src/components/menu/menu-segment-title/menu-segment-title.component.tsx @@ -1,5 +1,6 @@ -import React, { useContext } from "react"; +import React, { useContext, useMemo } from "react"; import { StyledTitle, StyledSegmentChildren } from "./menu-segment-title.style"; + import MenuContext from "../__internal__/menu.context"; import { StyledMenuItem } from "../menu.style"; import { VariantType } from "../menu-item"; @@ -7,6 +8,7 @@ import tagComponent, { TagProps, } from "../../../__internal__/utils/helpers/tags"; import SubmenuContext from "../__internal__/submenu/submenu.context"; +import MenuSegmentContext from "./menu-segment-title.context"; const AS_VALUES = ["h2", "h3", "h4", "h5", "h6"] as const; @@ -28,6 +30,16 @@ const MenuSegmentTitle = React.forwardRef( const menuContext = useContext(MenuContext); const { submenuHasMaxWidth } = useContext(SubmenuContext); + const isChildOfFullscreenMenu = menuContext?.inFullscreenView || false; + + const overriddenVariant = useMemo(() => { + return isChildOfFullscreenMenu && + variant === "alternate" && + ["white", "black"].includes(menuContext.menuType) + ? "default" + : variant; + }, [isChildOfFullscreenMenu, menuContext.menuType, variant]); + return ( ( {...tagComponent("menu-segment-title", rest)} menuType={menuContext.menuType} ref={ref} - variant={variant} + variant={overriddenVariant} shouldWrap={submenuHasMaxWidth} > {text} {children && ( - - {children} + + + {children} + )} diff --git a/src/components/menu/menu-segment-title/menu-segment-title.context.tsx b/src/components/menu/menu-segment-title/menu-segment-title.context.tsx new file mode 100644 index 0000000000..093fab70c4 --- /dev/null +++ b/src/components/menu/menu-segment-title/menu-segment-title.context.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export interface MenuSegmentContextProps { + isChildOfSegment: boolean; + overriddenVariant: string; +} + +export const MenuSegmentContext = React.createContext({ + isChildOfSegment: false, + overriddenVariant: "default", +}); + +export default MenuSegmentContext; diff --git a/src/components/menu/menu-segment-title/menu-segment-title.style.ts b/src/components/menu/menu-segment-title/menu-segment-title.style.ts index 59f9033c97..9756674c18 100644 --- a/src/components/menu/menu-segment-title/menu-segment-title.style.ts +++ b/src/components/menu/menu-segment-title/menu-segment-title.style.ts @@ -1,6 +1,7 @@ import styled, { css } from "styled-components"; -import menuConfigVariants from "../menu.config"; + import { VariantType } from "../menu-item"; +import menuConfigVariants from "../menu.config"; import { MenuType } from "../__internal__/menu.context"; interface StyledTitleProps { @@ -25,19 +26,28 @@ const StyledTitle = styled.h2` `} `; -const StyledSegmentChildren = styled.ul` - padding: 0; +const StyledSegmentChildren = styled.ul<{ + variant?: VariantType; + menuType: MenuType; +}>` + ${({ menuType, variant }) => css` + padding: 0; - li { - list-style: none; + li { + list-style: none; + ${variant === "alternate" && + css` + background-color: ${menuConfigVariants[menuType].alternate}; + `} - &:not(&:last-child) a, - &:not(&:last-child) button, - &:not(&:last-child) > span, - &:not(&:last-child) > div { - border-radius: var(--borderRadius000); + &:not(&:last-child) a, + &:not(&:last-child) button, + &:not(&:last-child) > span, + &:not(&:last-child) > div { + border-radius: var(--borderRadius000); + } } - } + `} `; export { StyledTitle, StyledSegmentChildren }; diff --git a/src/components/menu/menu-segment-title/menu-segment-title.test.tsx b/src/components/menu/menu-segment-title/menu-segment-title.test.tsx index 568c5033e1..a57ebedd14 100644 --- a/src/components/menu/menu-segment-title/menu-segment-title.test.tsx +++ b/src/components/menu/menu-segment-title/menu-segment-title.test.tsx @@ -10,11 +10,15 @@ import MenuContext, { import { MenuItem } from ".."; import menuConfigVariants from "../menu.config"; -const menuContextValues = (menuType: MenuType): MenuContextProps => ({ +const menuContextValues = ( + menuType: MenuType, + inFullscreenView = false, +): MenuContextProps => ({ menuType, setOpenSubmenuId: () => null, openSubmenuId: null, inMenu: true, + inFullscreenView, }); test("should render with correct colour when 'light' `menuType` received from context", async () => { @@ -385,3 +389,18 @@ test("should wrap when the submenu parent has a max-width set", async () => { await screen.findByRole("heading", { level: 2, name: "Title" }), ).toHaveStyle("white-space: normal"); }); + +// coverage +test("should set the correct colour when a child of `MenuSegmentTitle` and `variant` is 'alternate'", async () => { + render( + + + Item One + + , + ); + + expect(screen.getByTestId("menu-item-wrapper")).toHaveStyle({ + backgroundColor: menuConfigVariants.black.alternate, + }); +}); diff --git a/src/components/menu/menu.mdx b/src/components/menu/menu.mdx index 2108870f13..fe65f49907 100644 --- a/src/components/menu/menu.mdx +++ b/src/components/menu/menu.mdx @@ -133,7 +133,7 @@ A title attribute is added to the item when using this prop, containing the full ### Submenu maxWidth -By default the submenu will have the same width as the widest `MenuItem`. This behaviour can be overridden +By default the submenu will have the same width as the widest `MenuItem`. This behaviour can be overridden by setting the `submenuMaxWidth` prop on the submenu's parent `MenuItem` component. @@ -156,6 +156,16 @@ due to specific browser design choices. +### Full-screen Menu with segment styling + +When using the `alternate` variant for a `MenuItem` that is a child of a `MenuSegmentTitle`, the menu item will automatically +update its styling to match the segment title. Note that this only applies to the `MenuItem` instances within a normal `Menu` component; +when found within a `MenuFullscreen` component, the `alternate` variant will not be styled differently. + +This story is best viewed in the `canvas` view. + + + ## Props ### Menu diff --git a/src/components/menu/menu.stories.tsx b/src/components/menu/menu.stories.tsx index ef0e3143a1..4a52f23f1b 100644 --- a/src/components/menu/menu.stories.tsx +++ b/src/components/menu/menu.stories.tsx @@ -780,3 +780,79 @@ export const TruncationAndSubmenuWidth: Story = () => { TruncationAndSubmenuWidth.storyName = "Truncation and Submenu Width"; TruncationAndSubmenuWidth.parameters = { chromatic: { disableSnapshot: true } }; + +export const MenuFullscreenWithSegmentStyling = () => { + const [isOpen, setIsOpen] = useState(false); + const [isSegmentedOpen, setIsSegmentedOpen] = useState(false); + + return ( + + + setIsOpen(true)} icon="entry"> + Open Normal Menu + + setIsOpen(false)}> + Default Menu Item + Alternate Menu Item + + + setIsSegmentedOpen(true)} + icon="stacked_squares" + > + Open Segmented Menu + + setIsSegmentedOpen(false)} + > + + Segmented Default Menu Item + + + + Segmented Alternate Menu Item + + + + + + + Default Menu Item + Alternate Menu Item + + Segmented Default Menu Item + + + + Segmented Alternate Menu Item + + + + + + + ); +}; + +MenuFullscreenWithSegmentStyling.storyName = + "Full-screen Menu with segment styling"; +MenuFullscreenWithSegmentStyling.parameters = { + chromatic: { disableSnapshot: true }, +};