diff --git a/packages/lib/src/base-menu/GroupItem.tsx b/packages/lib/src/base-menu/GroupItem.tsx index 17158c26c5..7817dfd214 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, useEffect, useId, useState } from "react"; import DxcIcon from "../icon/Icon"; import SubMenu from "./SubMenu"; import ItemAction from "./ItemAction"; @@ -10,14 +10,17 @@ import BaseMenuContext from "./BaseMenuContext"; const GroupItem = ({ items, ...props }: GroupItemProps) => { const groupMenuId = `group-menu-${useId()}`; - - const NavigationTreeId = `sidenav-${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 ? ( <> @@ -38,60 +41,64 @@ const GroupItem = ({ items, ...props }: GroupItemProps) => { {...props} /> - - - { - 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} - /> - )} - - {items.map((item, index) => ( - + + { + 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} /> - ))} - - - - + )} + + {items.map((item, index) => ( + + ))} + + + + + )} -
+
) : ( <> diff --git a/packages/lib/src/date-input/DateInput.tsx b/packages/lib/src/date-input/DateInput.tsx index 4fb9e426b0..d430eafc7f 100644 --- a/packages/lib/src/date-input/DateInput.tsx +++ b/packages/lib/src/date-input/DateInput.tsx @@ -150,6 +150,8 @@ const DxcDateInput = forwardRef( : null ); const [sideOffset, setSideOffset] = useState(SIDEOFFSET); + const [portalContainer, setPortalContainer] = useState(null); + const translatedLabels = useContext(HalstackLanguageContext); const dateRef = useRef(null); const popoverContentRef = useRef(null); @@ -258,6 +260,9 @@ const DxcDateInput = forwardRef( closeCalendar(); } }; + useEffect(() => { + setPortalContainer(document?.getElementById(`${calendarId}-portal`)); + }, []); useEffect(() => { window.addEventListener("scroll", adjustSideOffset); @@ -327,20 +332,23 @@ const DxcDateInput = forwardRef( ariaLabel={ariaLabel} /> - - - - - + {portalContainer && ( + + + + + + )} +
); diff --git a/packages/lib/src/dropdown/Dropdown.tsx b/packages/lib/src/dropdown/Dropdown.tsx index f2b10b12b1..f9d75012a4 100644 --- a/packages/lib/src/dropdown/Dropdown.tsx +++ b/packages/lib/src/dropdown/Dropdown.tsx @@ -1,5 +1,5 @@ import * as Popover from "@radix-ui/react-popover"; -import { FocusEvent, KeyboardEvent, useCallback, useId, useLayoutEffect, useRef, useState } from "react"; +import { FocusEvent, KeyboardEvent, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react"; import styled from "@emotion/styled"; import { getMargin } from "../common/utils"; import { spaces } from "../common/variables"; @@ -131,6 +131,10 @@ 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); @@ -300,23 +304,26 @@ const DxcDropdown = ({ - - - - - + {portalContainer && ( + + + + + + )} +
); diff --git a/packages/lib/src/select/Select.tsx b/packages/lib/src/select/Select.tsx index 5fa9a0e827..036cab2670 100644 --- a/packages/lib/src/select/Select.tsx +++ b/packages/lib/src/select/Select.tsx @@ -7,6 +7,7 @@ import { MouseEvent, useCallback, useContext, + useEffect, useId, useMemo, useRef, @@ -210,6 +211,10 @@ const DxcSelect = forwardRef( const [isOpen, changeIsOpen] = useState(false); const [searchValue, setSearchValue] = useState(""); const [visualFocusIndex, changeVisualFocusIndex] = useState(-1); + const [portalContainer, setPortalContainer] = useState(null); + useEffect(() => { + setPortalContainer(document?.getElementById(`${id}-portal`)); + }, []); const selectRef = useRef(null); const selectSearchInputRef = useRef(null); @@ -596,44 +601,47 @@ const DxcSelect = forwardRef( - - { - // Avoid select to lose focus when the list is closed - event.preventDefault(); - }} - onOpenAutoFocus={(event) => { - // Avoid select to lose focus when the list is opened - event.preventDefault(); - }} - sideOffset={4} - style={{ zIndex: "var(--z-dropdown)" }} - > - - - + {portalContainer && ( + + { + // Avoid select to lose focus when the list is closed + event.preventDefault(); + }} + onOpenAutoFocus={(event) => { + // Avoid select to lose focus when the list is opened + event.preventDefault(); + }} + sideOffset={4} + style={{ zIndex: "var(--z-dropdown)" }} + > + + + + )} {!disabled && typeof error === "string" && } +
); diff --git a/packages/lib/src/text-input/TextInput.tsx b/packages/lib/src/text-input/TextInput.tsx index 9594a8f98b..ab40af511e 100644 --- a/packages/lib/src/text-input/TextInput.tsx +++ b/packages/lib/src/text-input/TextInput.tsx @@ -154,7 +154,11 @@ const DxcTextInput = forwardRef( const [isAutosuggestError, changeIsAutosuggestError] = useState(false); const [filteredSuggestions, changeFilteredSuggestions] = useState([]); const [visualFocusIndex, changeVisualFocusIndex] = useState(-1); + const [portalContainer, setPortalContainer] = useState(null); const width = useWidth(inputContainerRef); + useEffect(() => { + setPortalContainer(document?.getElementById(`${inputId}-portal`)); + }, []); const autosuggestWrapperFunction = (children: ReactNode) => ( 0 || isSearching || isAutosuggestError)}> @@ -167,36 +171,38 @@ const DxcTextInput = forwardRef( > {children} - - { - // Avoid select to lose focus when the list is closed - event.preventDefault(); - }} - onOpenAutoFocus={(event) => { - // Avoid select to lose focus when the list is opened - event.preventDefault(); - }} - sideOffset={4} - style={{ zIndex: "var(--z-textinput)" }} - > - { - changeValue(suggestion); - closeSuggestions(); + {portalContainer && ( + + { + // Avoid select to lose focus when the list is closed + event.preventDefault(); + }} + onOpenAutoFocus={(event) => { + // Avoid select to lose focus when the list is opened + event.preventDefault(); }} - suggestions={filteredSuggestions} - styles={{ width }} - value={value ?? innerValue} - visualFocusIndex={visualFocusIndex} - /> - - + sideOffset={4} + style={{ zIndex: "var(--z-textinput)" }} + > + { + changeValue(suggestion); + closeSuggestions(); + }} + suggestions={filteredSuggestions} + styles={{ width }} + value={value ?? innerValue} + visualFocusIndex={visualFocusIndex} + /> + + + )} ); @@ -602,7 +608,7 @@ const DxcTextInput = forwardRef( {!disabled && typeof error === "string" && } -
+ {hasSuggestions(suggestions) &&
} ); } diff --git a/packages/lib/src/toast/ToastsQueue.tsx b/packages/lib/src/toast/ToastsQueue.tsx index be3337ded5..d7d06b569f 100644 --- a/packages/lib/src/toast/ToastsQueue.tsx +++ b/packages/lib/src/toast/ToastsQueue.tsx @@ -27,7 +27,7 @@ const ToastsQueue = styled.section` export default function DxcToastsQueue({ children, duration = 3000 }: ToastsQueuePropsType) { const [toasts, setToasts] = useState([]); - const [isMounted, setIsMounted] = useState(false); // Next.js SSR mounting issue + const [portalContainer, setPortalContainer] = useState(null); const adjustedDuration = useMemo(() => (duration > 5000 ? 5000 : duration < 3000 ? 3000 : duration), [duration]); const id = useId(); @@ -45,13 +45,13 @@ export default function DxcToastsQueue({ children, duration = 3000 }: ToastsQueu ); useEffect(() => { - setIsMounted(true); + setPortalContainer(document?.getElementById(`toasts-${id}-portal`)); }, []); return (
- {isMounted && + {portalContainer && createPortal( {toasts.map((t) => ( @@ -65,7 +65,7 @@ export default function DxcToastsQueue({ children, duration = 3000 }: ToastsQueu /> ))} , - document.getElementById(`toasts-${id}-portal`) || document.body + portalContainer )} {children}