diff --git a/src/components/menu/__internal__/submenu/submenu.component.tsx b/src/components/menu/__internal__/submenu/submenu.component.tsx index e71f02ed58..a904562e42 100644 --- a/src/components/menu/__internal__/submenu/submenu.component.tsx +++ b/src/components/menu/__internal__/submenu/submenu.component.tsx @@ -114,6 +114,10 @@ const Submenu = React.forwardRef< const [submenuItemIds, setSubmenuItemIds] = useState<(string | null)[]>([]); const [characterString, setCharacterString] = useState(""); const [applyFocusRadius, setApplyFocusRadius] = useState(false); + const [ + applyFocusRadiusToLastItem, + setApplyFocusRadiusToLastItem, + ] = useState(false); const shiftTabPressed = useRef(false); const focusFirstMenuItemOnOpen = useRef(false); @@ -192,6 +196,12 @@ const Submenu = React.forwardRef< // Get the last segment block const lastSegmentBlock = ulElements.pop(); + // Check if the last segment block is a scrollable block + const isLastSegmentBlockScrollableBlock = Boolean( + lastSegmentBlock?.parentElement?.dataset.component === + SCROLLABLE_BLOCK + ); + // Get all the menu items from the last segment block const segmentBlockMenuItems = Array.from( lastSegmentBlock?.querySelectorAll("[data-component='menu-item']") || @@ -200,18 +210,36 @@ const Submenu = React.forwardRef< // Get the last menu item in the last segment block const lastMenuItemInSegmentBlock = segmentBlockMenuItems.pop(); + + // Check to see if the last menu item in the last segment block is visible + let isLastMenuItemInSegmentBlockVisible = false; + if (lastMenuItemInSegmentBlock && lastSegmentBlock) { + isLastMenuItemInSegmentBlockVisible = + lastMenuItemInSegmentBlock.getBoundingClientRect().bottom < + lastSegmentBlock.getBoundingClientRect().bottom; + } + // Check if the last item in the segment block is the same as the last MenuItem in the submenu const menuItemsMatch = !!lastMenuItemInSegmentBlock && lastMenuItemInSegmentBlock === lastMenuItem; + // Applies the focus radius to the StyledBox of the StyledScrollableBlock setApplyFocusRadius(menuItemsMatch); + + // Applies border radius to the last item in the segment block + setApplyFocusRadiusToLastItem( + (menuItemsMatch && !isLastSegmentBlockScrollableBlock) || + (menuItemsMatch && + isLastSegmentBlockScrollableBlock && + !isLastMenuItemInSegmentBlockVisible) + ); }; if (submenuOpen && submenuRef) { handleBorderRadiusStyling(); } - }, [submenuOpen, submenuRef]); + }, [submenuOpen, submenuRef, numberOfChildren]); useEffect(() => { if (submenuOpen && onSubmenuOpen) { @@ -470,6 +498,7 @@ const Submenu = React.forwardRef< inFullscreenView={inFullscreenView} ref={setSubmenuRef} applyFocusRadiusStyling={false} + applyFocusRadiusStylingToLastItem={applyFocusRadiusToLastItem} > { menuType="light" variant="default" applyFocusRadiusStyling={false} + applyFocusRadiusStylingToLastItem={false} > Apple Banana diff --git a/src/components/menu/__internal__/submenu/submenu.style.ts b/src/components/menu/__internal__/submenu/submenu.style.ts index 859c7bf020..d58c6e7cd1 100644 --- a/src/components/menu/__internal__/submenu/submenu.style.ts +++ b/src/components/menu/__internal__/submenu/submenu.style.ts @@ -27,6 +27,7 @@ interface StyledSubmenuProps submenuDirection?: string; maxHeight?: string; applyFocusRadiusStyling: boolean; + applyFocusRadiusStylingToLastItem: boolean; } const StyledSubmenuWrapper = styled.div` @@ -64,6 +65,7 @@ const StyledSubmenu = styled.ul` inFullscreenView, maxHeight, applyFocusRadiusStyling, + applyFocusRadiusStylingToLastItem, }) => css` ${!inFullscreenView && menuType && @@ -111,10 +113,10 @@ const StyledSubmenu = styled.ul` border-bottom-left-radius: var(--borderRadius000); :focus { - border-bottom-right-radius: ${applyFocusRadiusStyling + border-bottom-right-radius: ${applyFocusRadiusStylingToLastItem ? "var(--borderRadius100)" : "var(--borderRadius000)"}; - border-bottom-left-radius: ${applyFocusRadiusStyling + border-bottom-left-radius: ${applyFocusRadiusStylingToLastItem ? "var(--borderRadius100)" : "var(--borderRadius000)"}; } @@ -130,7 +132,7 @@ const StyledSubmenu = styled.ul` ${StyledMenuItem}:last-child ${StyledLink}, ${StyledMenuItem}:last-child a, ${StyledMenuItem}:last-child button { border-bottom-right-radius: var(--borderRadius000); - border-bottom-left-radius: ${applyFocusRadiusStyling + border-bottom-left-radius: ${applyFocusRadiusStylingToLastItem ? "var(--borderRadius100)" : "var(--borderRadius000)"}; } diff --git a/src/components/menu/component.test-pw.tsx b/src/components/menu/component.test-pw.tsx index 2b526904f1..856518a0e2 100644 --- a/src/components/menu/component.test-pw.tsx +++ b/src/components/menu/component.test-pw.tsx @@ -14,7 +14,7 @@ import { ScrollableBlockProps, } from "."; import { MenuType } from "./menu.context"; -import Search from "../search"; +import Search, { SearchEvent } from "../search"; import GlobalHeader from "../global-header"; import Box from "../box/box.component"; import Typography from "../typography/typography.component"; @@ -106,6 +106,60 @@ export const MenuComponentScrollable = ( ); }; +export const MenuComponentScrollableWithSearch = () => { + const items = [ + "apple", + "banana", + "carrot", + "grapefruit", + "melon", + "orange", + "pear", + "strawberry", + ]; + const [itemSearch, setItemSearch] = React.useState(items); + const [searchString, setSearchString] = React.useState(""); + const handleTextChange = (e: SearchEvent) => { + const searchStr = e.target.value; + setSearchString(searchStr); + let found; + if (searchStr.length > 0) { + found = items.filter((item) => item.includes(searchStr)); + } else { + found = items; + } + setItemSearch(found); + }; + return ( + + + {}}>Menu Item One + Menu Item Two + + Item Submenu One + + } + > + {itemSearch.map((item) => ( + + {item} + + ))} + + + + + ); +}; + export const MenuComponentSearch = () => { return ( diff --git a/src/components/menu/menu.pw.tsx b/src/components/menu/menu.pw.tsx index caf32de3ba..32748d4898 100644 --- a/src/components/menu/menu.pw.tsx +++ b/src/components/menu/menu.pw.tsx @@ -64,6 +64,7 @@ import { MenuDividerComponent, InGlobalHeaderStory, SubMenuWithVeryLongLabel, + MenuComponentScrollableWithSearch, MenuSegmentTitleComponentWithAdditionalMenuItem, } from "./component.test-pw"; import { NavigationBarWithSubmenuAndChangingHeight } from "../navigation-bar/navigation-bar-test.stories"; @@ -2393,6 +2394,53 @@ test.describe( ); }); + test(`renders last MenuItem in a scrollable block without rounded corner, if there is no overflow in the block`, async ({ + mount, + page, + }) => { + await mount(); + + const subMenu = submenu(page).first(); + await subMenu.hover(); + const searchInput = searchDefaultInput(page); + await searchInput.fill("app"); + const scrollableBlock = scrollBlock(page); + + await expect(scrollableBlock).toHaveCSS( + "border-radius", + "0px 0px 0px 8px" + ); + + const scrollableItem = scrollBlock(page).locator("a").last(); + + await expect(scrollableItem).toHaveCSS("border-radius", "0px"); + }); + + test(`renders last MenuItem in a scrollable block with rounded corner, if there is overflow within the block`, async ({ + mount, + page, + }) => { + await mount(); + + const subMenu = submenu(page).first(); + await subMenu.hover(); + const searchInput = searchDefaultInput(page); + await searchInput.fill("r"); + const scrollableBlock = scrollBlock(page); + + await expect(scrollableBlock).toHaveCSS( + "border-radius", + "0px 0px 0px 8px" + ); + + const scrollableItem = scrollBlock(page).locator("a").last(); + + await expect(scrollableItem).toHaveCSS( + "border-radius", + "0px 0px 0px 8px" + ); + }); + test(`should verify that tabbing forward through the menu and back to the start should not make the background scroll to the bottom`, async ({ mount, page,