diff --git a/packages/lib/src/dropdown/Dropdown.test.tsx b/packages/lib/src/dropdown/Dropdown.test.tsx index de333c8eb..1ea059b96 100644 --- a/packages/lib/src/dropdown/Dropdown.test.tsx +++ b/packages/lib/src/dropdown/Dropdown.test.tsx @@ -49,22 +49,6 @@ describe("Dropdown component tests", () => { expect(menu.getAttribute("aria-labelledby")).toBe(dropdown.id); expect(getAllByRole("menuitem").length).toBe(4); }); - test("Button trigger opens and closes the menu options when clicked", () => { - const onSelectOption = jest.fn(); - const { getByRole, queryByRole, getByText } = render( - - ); - const dropdown = getByRole("button"); - expect(queryByRole("menu")).toBeFalsy(); - userEvent.click(dropdown); - expect(queryByRole("menu")).toBeTruthy(); - expect(getByText("Amazon")).toBeTruthy(); - expect(getByText("Ebay")).toBeTruthy(); - expect(getByText("Wallapop")).toBeTruthy(); - expect(getByText("Aliexpress")).toBeTruthy(); - userEvent.click(dropdown); - expect(queryByRole("menu")).toBeFalsy(); - }); test("Button trigger is not interactive when disabled", () => { const onSelectOption = jest.fn(); const { getByRole, queryByRole, queryByText } = render( @@ -90,30 +74,6 @@ describe("Dropdown component tests", () => { userEvent.click(option); expect(onSelectOption).toHaveBeenCalledWith("4"); }); - test("When expandOnHover is true, the dropdown trigger shows and hides the menu when it is hovered", () => { - const onSelectOption = jest.fn(); - const { queryByText, getByRole, queryByRole } = render( - - ); - expect(queryByText("option-test")).toBeFalsy(); - expect(queryByRole("menu")).toBeFalsy(); - fireEvent.mouseOver(getByRole("button")); - const menu = getByRole("menu"); - expect(menu).toBeTruthy(); - expect(document.activeElement === menu).toBeTruthy(); - expect(menu.getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-0`); - }); - test("The menu is closed when the dropdown loses the focus (blur)", () => { - const onSelectOption = jest.fn(); - const { getByRole, queryByRole } = render( - - ); - const dropdown = getByRole("button"); - userEvent.click(dropdown); - expect(getByRole("menu")).toBeTruthy(); - fireEvent.blur(getByRole("menu")); - expect(queryByRole("menu")).toBeFalsy(); - }); test("Menu button key events — Arrow up opens the list and moves the focus to the last menu item", () => { const onSelectOption = jest.fn(); const { getByRole } = render( @@ -128,7 +88,6 @@ describe("Dropdown component tests", () => { }); const menu = getByRole("menu"); expect(menu).toBeTruthy(); - expect(document.activeElement === menu).toBeTruthy(); expect(getByRole("menu").getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-3`); }); test("Menu button key events — Arrow down opens the list and moves the focus to the first menu item", () => { @@ -145,7 +104,6 @@ describe("Dropdown component tests", () => { }); const menu = getByRole("menu"); expect(menu).toBeTruthy(); - expect(document.activeElement === menu).toBeTruthy(); expect(getByRole("menu").getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-0`); }); test("Menu button key events — Enter opens the list and moves the focus to the first menu item", () => { @@ -162,7 +120,6 @@ describe("Dropdown component tests", () => { }); const menu = getByRole("menu"); expect(menu).toBeTruthy(); - expect(document.activeElement === menu).toBeTruthy(); expect(getByRole("menu").getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-0`); }); test("Menu button key events — Space opens the list and moves the focus to the first menu item", () => { @@ -179,7 +136,6 @@ describe("Dropdown component tests", () => { }); const menu = getByRole("menu"); expect(menu).toBeTruthy(); - expect(document.activeElement === menu).toBeTruthy(); expect(getByRole("menu").getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-0`); }); test("Menu key events — Arrow up moves the focus to the previous menu item", () => { @@ -200,7 +156,6 @@ describe("Dropdown component tests", () => { keyCode: 38, charCode: 38, }); - expect(document.activeElement === menu).toBeTruthy(); expect(menu.getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-2`); fireEvent.keyDown(menu, { key: "Enter", @@ -223,7 +178,6 @@ describe("Dropdown component tests", () => { keyCode: 38, charCode: 38, }); - expect(document.activeElement === menu).toBeTruthy(); expect(menu.getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-3`); fireEvent.keyDown(menu, { key: "Enter", @@ -252,7 +206,6 @@ describe("Dropdown component tests", () => { keyCode: 40, charCode: 40, }); - expect(document.activeElement === menu).toBeTruthy(); expect(menu.getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-2`); fireEvent.keyDown(menu, { key: "Enter", @@ -280,7 +233,6 @@ describe("Dropdown component tests", () => { keyCode: 40, charCode: 40, }); - expect(document.activeElement === menu).toBeTruthy(); expect(menu.getAttribute("aria-activedescendant")).toBe(`${menu.id}-option-0`); fireEvent.keyDown(menu, { key: "Enter", diff --git a/packages/lib/src/dropdown/Dropdown.tsx b/packages/lib/src/dropdown/Dropdown.tsx index c67357e45..59ba44143 100644 --- a/packages/lib/src/dropdown/Dropdown.tsx +++ b/packages/lib/src/dropdown/Dropdown.tsx @@ -1,5 +1,4 @@ -import * as Popover from "@radix-ui/react-popover"; -import { FocusEvent, KeyboardEvent, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react"; +import { KeyboardEvent, useCallback, useId, useLayoutEffect, useRef, useState } from "react"; import styled from "@emotion/styled"; import { getMargin } from "../common/utils"; import { spaces } from "../common/variables"; @@ -7,7 +6,7 @@ import DxcIcon from "../icon/Icon"; import useWidth from "../utils/useWidth"; import DropdownMenu from "./DropdownMenu"; import DropdownPropsType from "./types"; -import { Tooltip } from "../tooltip/Tooltip"; +import DxcPopover from "../popover/Popover"; const sizes = { small: "60px", @@ -131,10 +130,6 @@ const DxcDropdown = ({ const menuId = `menu-${id}`; const [isOpen, changeIsOpen] = useState(false); const [visualFocusIndex, setVisualFocusIndex] = useState(0); - const [portalContainer, setPortalContainer] = useState(null); - useEffect(() => { - setPortalContainer(document?.getElementById(`${id}-portal`)); - }, []); const triggerRef = useRef(null); const menuRef = useRef(null); @@ -157,11 +152,6 @@ const DxcDropdown = ({ }, [onSelectOption] ); - const handleOnBlur = (event: FocusEvent) => { - if (!event.currentTarget.contains(event.relatedTarget)) { - handleOnCloseMenu(); - } - }; const handleTriggerOnClick = () => { changeIsOpen((isCurrentlyOpen) => !isCurrentlyOpen); @@ -256,76 +246,70 @@ const DxcDropdown = ({ return ( <> - - - - - { - event.stopPropagation(); - }} - disabled={disabled} - label={label} - margin={margin} - size={size} - id={triggerId} - aria-haspopup="true" - aria-controls={isOpen ? menuId : undefined} - aria-expanded={isOpen ? true : undefined} - aria-label="Show options" - tabIndex={tabIndex} - ref={triggerRef} - type="button" - > - - {icon && ( - - {typeof icon === "string" ? : icon} - - )} - {label && {label}} - - {!caretHidden && ( - - - - )} - - - - {portalContainer && ( - - - - - - )} - + + { + menuRef.current?.focus(); + }} + actionToOpen={expandOnHover ? "hover" : "click"} + popoverContent={ + + } + > + { + event.stopPropagation(); + }} + disabled={disabled} + label={label} + margin={margin} + size={size} + id={triggerId} + aria-haspopup="true" + aria-controls={isOpen ? menuId : undefined} + aria-expanded={isOpen ? true : undefined} + aria-label="Show options" + tabIndex={tabIndex} + ref={triggerRef} + type="button" + title={title} + > + + {icon && ( + + {typeof icon === "string" ? : icon} + + )} + {label && {label}} + + {!caretHidden && ( + + + + )} + + - -
); }; diff --git a/packages/lib/src/dropdown/DropdownMenu.tsx b/packages/lib/src/dropdown/DropdownMenu.tsx index a1587d9d6..8e8f7b700 100644 --- a/packages/lib/src/dropdown/DropdownMenu.tsx +++ b/packages/lib/src/dropdown/DropdownMenu.tsx @@ -3,53 +3,50 @@ import styled from "@emotion/styled"; import DropdownMenuItem from "./DropdownMenuItem"; import { DropdownMenuProps } from "./types"; import scrollbarStyles from "../styles/scroll"; +import DxcBleed from "../bleed/Bleed"; const DropdownMenuContainer = styled.ul` max-height: 230px; min-width: min-content; padding: 0; margin: 0; - background-color: var(--color-bg-neutral-lightest); - border-radius: var(--border-radius-s); - box-shadow: var(--shadow-100); outline: none; overflow-y: auto; - z-index: var(--z-dropdown); ${scrollbarStyles} `; const DropdownMenu = forwardRef( ({ id, dropdownTriggerId, iconsPosition, visualFocusIndex, menuItemOnClick, onKeyDown, options, styles }, ref) => ( - { - // Prevent the onBlur event from closing menu when clicking on the menu since - // it is implemented with a Portal and the menu is not a direct child of the container - event.preventDefault(); - }} - onKeyDown={onKeyDown} - id={id} - role="menu" - aria-labelledby={dropdownTriggerId} - aria-orientation="vertical" - aria-activedescendant={visualFocusIndex !== -1 ? `${id}-option-${visualFocusIndex}` : undefined} - tabIndex={-1} - ref={ref} - style={styles} - > - {options.map((option, index) => ( - - ))} - + + { + // Prevent the onBlur event from closing menu when clicking on the menu since + // it is implemented with a Portal and the menu is not a direct child of the container + event.preventDefault(); + }} + onKeyDown={onKeyDown} + id={id} + role="menu" + aria-labelledby={dropdownTriggerId} + aria-orientation="vertical" + aria-activedescendant={visualFocusIndex !== -1 ? `${id}-option-${visualFocusIndex}` : undefined} + tabIndex={0} + ref={ref} + style={styles} + > + {options.map((option, index) => ( + + ))} + + ) ); -DropdownMenu.displayName = "DropdownMenu"; - export default memo(DropdownMenu);