Skip to content

Commit

Permalink
feat(menu-fullscreen): fix colours when menuitem used as child of men…
Browse files Browse the repository at this point in the history
…usegmenttitle
  • Loading branch information
damienrobson-sage committed Dec 4, 2024
1 parent 6675b86 commit d495f7a
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 21 deletions.
19 changes: 16 additions & 3 deletions src/components/menu/menu-item/menu-item.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -138,6 +141,9 @@ export const MenuItem = ({
"You should not pass `children` when `submenu` is an empty string",
);

const { isChildOfSegment, overriddenVariant } =
useContext<MenuSegmentContextProps>(MenuSegmentContext);

const {
inFullscreenView,
registerItem,
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -345,6 +360,4 @@ export const MenuItem = ({
);
};

MenuItem.displayName = "MenuItem";

export default MenuItem;
17 changes: 16 additions & 1 deletion src/components/menu/menu-item/menu-item.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
<MenuContext.Provider value={{ ...menuContextValues, menuType: "black" }}>
<MenuSegmentTitle text="Test">
<MenuItem variant="alternate">Item One</MenuItem>
</MenuSegmentTitle>
</MenuContext.Provider>,
);

expect(screen.getByTestId("menu-item-wrapper")).toHaveStyle({
backgroundColor: menuConfigVariants.black.alternate,
});
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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";
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;

Expand All @@ -28,21 +30,39 @@ const MenuSegmentTitle = React.forwardRef<HTMLDivElement, MenuTitleProps>(
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 (
<StyledMenuItem inSubmenu>
<StyledTitle
as={AS_VALUES.includes(as) ? as : /* istanbul ignore next */ "h2"}
{...tagComponent("menu-segment-title", rest)}
menuType={menuContext.menuType}
ref={ref}
variant={variant}
variant={overriddenVariant}
shouldWrap={submenuHasMaxWidth}
>
{text}
</StyledTitle>
{children && (
<StyledSegmentChildren data-role="menu-segment-children">
{children}
<StyledSegmentChildren
data-role="menu-segment-children"
menuType={menuContext.menuType}
variant={overriddenVariant}
>
<MenuSegmentContext.Provider
value={{ isChildOfSegment: true, overriddenVariant }}
>
{children}
</MenuSegmentContext.Provider>
</StyledSegmentChildren>
)}
</StyledMenuItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react";

export interface MenuSegmentContextProps {
isChildOfSegment: boolean;
overriddenVariant: string;
}

export const MenuSegmentContext = React.createContext<MenuSegmentContextProps>({
isChildOfSegment: false,
overriddenVariant: "default",
});

export default MenuSegmentContext;
32 changes: 21 additions & 11 deletions src/components/menu/menu-segment-title/menu-segment-title.style.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -25,19 +26,28 @@ const StyledTitle = styled.h2<StyledTitleProps>`
`}
`;

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 };
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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(
<MenuContext.Provider value={menuContextValues("black", true)}>
<MenuSegmentTitle text="Title" variant="alternate">
<MenuItem variant="alternate">Item One</MenuItem>
</MenuSegmentTitle>
</MenuContext.Provider>,
);

expect(screen.getByTestId("menu-item-wrapper")).toHaveStyle({
backgroundColor: menuConfigVariants.black.alternate,
});
});
12 changes: 11 additions & 1 deletion src/components/menu/menu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<Canvas of={MenuStories.TruncationAndSubmenuWidth} />
Expand All @@ -156,6 +156,16 @@ due to specific browser design choices.

<Canvas of={MenuStories.FullscreenViewStory} />

### 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.

<Canvas of={MenuStories.MenuFullscreenWithSegmentStyling} />

## Props

### Menu
Expand Down
76 changes: 76 additions & 0 deletions src/components/menu/menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Box minHeight="250px">
<Menu menuType="black">
<MenuItem onClick={() => setIsOpen(true)} icon="entry">
Open Normal Menu
</MenuItem>
<MenuFullscreen isOpen={isOpen} onClose={() => setIsOpen(false)}>
<MenuItem variant="default">Default Menu Item</MenuItem>
<MenuItem variant="alternate">Alternate Menu Item</MenuItem>
</MenuFullscreen>

<MenuItem
onClick={() => setIsSegmentedOpen(true)}
icon="stacked_squares"
>
Open Segmented Menu
</MenuItem>
<MenuFullscreen
isOpen={isSegmentedOpen}
onClose={() => setIsSegmentedOpen(false)}
>
<MenuSegmentTitle
key="default-variant"
variant="default"
text="Menu items (default variant)"
>
<MenuItem variant="default">Segmented Default Menu Item</MenuItem>
</MenuSegmentTitle>
<MenuSegmentTitle
key="alternate-variant"
variant="alternate"
text="Menu items (alternate variant)"
>
<MenuItem variant="alternate">
Segmented Alternate Menu Item
</MenuItem>
</MenuSegmentTitle>
</MenuFullscreen>

<Menu menuType="black">
<MenuItem submenu="Standard Menu">
<MenuItem variant="default">Default Menu Item</MenuItem>
<MenuItem variant="alternate">Alternate Menu Item</MenuItem>
<MenuSegmentTitle
key="default-variant"
variant="default"
text="Menu items (default variant)"
>
<MenuItem variant="default">Segmented Default Menu Item</MenuItem>
</MenuSegmentTitle>
<MenuSegmentTitle
key="alternate-variant"
variant="alternate"
text="Menu items (alternate variant)"
>
<MenuItem variant="alternate">
Segmented Alternate Menu Item
</MenuItem>
</MenuSegmentTitle>
</MenuItem>
</Menu>
</Menu>
</Box>
);
};

MenuFullscreenWithSegmentStyling.storyName =
"Full-screen Menu with segment styling";
MenuFullscreenWithSegmentStyling.parameters = {
chromatic: { disableSnapshot: true },
};

0 comments on commit d495f7a

Please sign in to comment.