From 29b80d4e7e77616d7b3827ef8908e535b53f7180 Mon Sep 17 00:00:00 2001 From: jialecl Date: Tue, 18 Aug 2026 12:30:36 +0200 Subject: [PATCH 1/3] Changing header and sidenav popover to our DxcPopover --- packages/lib/src/base-menu/GroupItem.tsx | 130 +++++++++-------------- 1 file changed, 49 insertions(+), 81 deletions(-) diff --git a/packages/lib/src/base-menu/GroupItem.tsx b/packages/lib/src/base-menu/GroupItem.tsx index 177e1dab3..f00fa023d 100644 --- a/packages/lib/src/base-menu/GroupItem.tsx +++ b/packages/lib/src/base-menu/GroupItem.tsx @@ -1,108 +1,76 @@ -import { useContext, useEffect, useId, useState } from "react"; +import { useContext, useId } from "react"; import DxcIcon from "../icon/Icon"; import SubMenu from "./SubMenu"; import ItemAction from "./ItemAction"; import MenuItem from "./MenuItem"; import { GroupItemProps } from "./types"; -import * as Popover from "@radix-ui/react-popover"; import { useGroupItem } from "./useGroupItem"; import BaseMenuContext from "./BaseMenuContext"; +import DxcPopover from "../popover/Popover"; const GroupItem = ({ items, ...props }: GroupItemProps) => { const groupMenuId = `group-menu-${useId()}`; - const navigationTreeId = `sidenav-${useId()}`; const contextValue = useContext(BaseMenuContext) ?? {}; const { groupSelected, isOpen, toggleOpen, hasPopOver, isHorizontal } = useGroupItem( items, contextValue, props.defaultOpen ); - const [portalContainer, setPortalContainer] = useState(null); - useEffect(() => { - setPortalContainer(document?.getElementById(`${navigationTreeId}-portal`)); - }, []); return hasPopOver ? ( <> - - - : } - onClick={() => { - toggleOpen(); - }} - selected={groupSelected && !isOpen} - {...props} - /> - - {portalContainer && ( - + { + if (isOpen) { + toggleOpen(); + } + }} + asChild + popoverContent={ + <> - { - if (event.key === "Escape") { - toggleOpen(); - } - }} - align="start" - side={isHorizontal ? "bottom" : "right"} - style={{ - zIndex: "var(--z-contextualmenu)", - padding: "var(--spacing-padding-xs)", - boxShadow: "var(--shadow-100)", - backgroundColor: "var(--color-bg-neutral-lightest)", - borderRadius: "var(--border-radius-m)", - ...(isHorizontal - ? {} - : { - display: "flex", - flexDirection: "column", - gap: "var(--spacing-gap-xxs)", - }), - }} - sideOffset={isHorizontal ? 16 : 0} - onInteractOutside={() => toggleOpen()} - > - {!isHorizontal && props.depthLevel === 0 && ( - : - } - onClick={() => toggleOpen()} - selected={groupSelected && !isOpen} - {...props} - icon={undefined} + {!isHorizontal && props.depthLevel === 0 && ( + : } + onClick={() => toggleOpen()} + selected={groupSelected && !isOpen} + {...props} + icon={undefined} + /> + )} + + {items.map((item, index) => ( + - )} - - {items.map((item, index) => ( - - ))} - - + ))} + - - )} - -
+ + } + isOpen={isOpen} + > + : } + onClick={() => { + toggleOpen(); + }} + selected={groupSelected && !isOpen} + {...props} + /> + ) : ( <> From 8134f2f6a9ce07dd7af810aeaa4115e393067bd2 Mon Sep 17 00:00:00 2001 From: jialecl Date: Wed, 26 Aug 2026 12:11:38 +0200 Subject: [PATCH 2/3] Fixed action width and fixed bug related to group toggling --- packages/lib/src/base-menu/GroupItem.tsx | 43 ++++++++++++++--------- packages/lib/src/base-menu/ItemAction.tsx | 2 +- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/lib/src/base-menu/GroupItem.tsx b/packages/lib/src/base-menu/GroupItem.tsx index f00fa023d..412c1b693 100644 --- a/packages/lib/src/base-menu/GroupItem.tsx +++ b/packages/lib/src/base-menu/GroupItem.tsx @@ -1,4 +1,4 @@ -import { useContext, useId } from "react"; +import { useContext, useId, useState } from "react"; import DxcIcon from "../icon/Icon"; import SubMenu from "./SubMenu"; import ItemAction from "./ItemAction"; @@ -16,6 +16,11 @@ const GroupItem = ({ items, ...props }: GroupItemProps) => { contextValue, props.defaultOpen ); + const [isSubMenuOpen, setIsSubMenuOpen] = useState(true); + + const toggleSubMenu = () => { + setIsSubMenuOpen((prev) => !prev); + }; return hasPopOver ? ( <> @@ -36,25 +41,31 @@ const GroupItem = ({ items, ...props }: GroupItemProps) => { > {!isHorizontal && props.depthLevel === 0 && ( : } - onClick={() => toggleOpen()} - selected={groupSelected && !isOpen} + aria-controls={isSubMenuOpen ? groupMenuId : undefined} + aria-expanded={isSubMenuOpen ? true : undefined} + aria-pressed={groupSelected && !isSubMenuOpen} + collapseIcon={ + isSubMenuOpen ? : + } + onClick={() => { + toggleSubMenu(); + }} + selected={groupSelected && !isSubMenuOpen} {...props} icon={undefined} /> )} - - {items.map((item, index) => ( - - ))} - + {isSubMenuOpen && ( + + {items.map((item, index) => ( + + ))} + + )} } diff --git a/packages/lib/src/base-menu/ItemAction.tsx b/packages/lib/src/base-menu/ItemAction.tsx index 984d9652a..6decb6905 100644 --- a/packages/lib/src/base-menu/ItemAction.tsx +++ b/packages/lib/src/base-menu/ItemAction.tsx @@ -28,7 +28,7 @@ const Action = styled.a<{ cursor: pointer; overflow: hidden; text-decoration: none; - + width: stretch; &:hover { background-color: ${({ selected }) => selected ? "var(--color-bg-primary-medium)" : "var(--color-bg-neutral-light)"}; From d0c68c890d594a42020fac80a982d766839a09fc Mon Sep 17 00:00:00 2001 From: jialecl Date: Wed, 26 Aug 2026 12:20:07 +0200 Subject: [PATCH 3/3] Added test to cover the bug --- packages/lib/src/sidenav/Sidenav.test.tsx | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/lib/src/sidenav/Sidenav.test.tsx b/packages/lib/src/sidenav/Sidenav.test.tsx index 0808a3016..48bf447a5 100644 --- a/packages/lib/src/sidenav/Sidenav.test.tsx +++ b/packages/lib/src/sidenav/Sidenav.test.tsx @@ -60,6 +60,31 @@ describe("DxcSidenav component", () => { expect(getByText("Dashboard")).toBeTruthy(); expect(getByText("Settings")).toBeTruthy(); }); + test("Sidenav opens and closes a group submenu in a collapsed navigation menu", () => { + const navItems = [ + { + label: "Parent group", + items: [ + { + label: "Nested group", + items: [{ label: "Nested item" }], + }, + ], + }, + ]; + const { getByLabelText, getByText, queryByText } = render( + + ); + + fireEvent.click(getByLabelText("Parent group").closest("button")!); + expect(getByText("Nested group")).toBeInTheDocument(); + + fireEvent.click(getByText("Parent group")); + expect(queryByText("Nested group")).not.toBeInTheDocument(); + + fireEvent.click(getByText("Parent group")); + expect(getByText("Nested group")).toBeInTheDocument(); + }); test("Sidenav renders link items correctly", () => { const navItems = [{ label: "Dashboard", href: "/dashboard" }];