From fda00713388e05f59ddb4992d9405787315a7e87 Mon Sep 17 00:00:00 2001 From: Pedro Ladaria Date: Thu, 19 Dec 2024 17:36:00 +0100 Subject: [PATCH] WEB-2113 fix portal, show dividers while scrolling content --- src/__stories__/drawer-story.tsx | 4 +- src/drawer.css.ts | 11 +++- src/drawer.tsx | 88 +++++++++++++++++++++++++++----- src/portal.tsx | 25 ++++----- 4 files changed, 96 insertions(+), 32 deletions(-) diff --git a/src/__stories__/drawer-story.tsx b/src/__stories__/drawer-story.tsx index 9ee6c5262..b915235ed 100644 --- a/src/__stories__/drawer-story.tsx +++ b/src/__stories__/drawer-story.tsx @@ -41,7 +41,9 @@ export const Default = ({ <> setIsOpen(true)}>Open Drawer - Result: {result} + + Result: {result} + {isOpen && ( + show ? :
; + +/** + * Restores the focus to the element that was focused before the Drawer was opened + */ +const useRestoreFocus = () => { + const activeElementRef = React.useRef(document.activeElement as HTMLElement); + React.useEffect(() => { + const elementToFocus = activeElementRef.current; + return () => { + elementToFocus?.focus?.(); + }; + }, []); +}; + type DrawerLayoutProps = { width?: number; children: React.ReactNode; @@ -40,6 +62,7 @@ type DrawerPropsRef = { const DrawerLayout = React.forwardRef( ({width, children, onClose, onDismiss}, ref) => { useSetModalStateEffect(); + useRestoreFocus(); const {isMobile, isTablet} = useScreenSize(); const [isOpen, setIsOpen] = React.useState(false); @@ -85,6 +108,7 @@ const DrawerLayout = React.forwardRef( styles.overlay, isOpen ? styles.overlayOpen : styles.overlayClosed )} + {...getPrefixedDataAttributes({}, 'DrawerOverlay')} />
( width: isMobile ? 'unset' : width || (isTablet ? WIDTH_TABLET : WIDTH_DESKTOP), }} className={classnames(styles.container, isOpen ? styles.open : styles.closed)} + {...getPrefixedDataAttributes({}, 'DrawerLayout')} > {children}
@@ -128,6 +153,7 @@ type DrawerProps = { button?: ButtonProps; secondaryButton?: ButtonProps; buttonLink?: ButtonProps; + dataAttributes?: DataAttributes; }; const Drawer = ({ @@ -141,9 +167,15 @@ const Drawer = ({ button, secondaryButton, buttonLink, + dataAttributes, }: DrawerProps): JSX.Element => { const layoutRef = React.useRef(null); const hasButtons = !!(button || secondaryButton || buttonLink); + const [scrollableParentElement, setScrollableParentElement] = React.useState(null); + const topScrollSignalRef = React.useRef(null); + const bottomScrollSignalRef = React.useRef(null); + const {t, texts} = useTheme(); + const paddingX = { mobile: PADDING_X_MOBILE, tablet: PADDING_X_TABLET, @@ -154,46 +186,74 @@ const Drawer = ({ layoutRef.current?.close().then(pressHandlerFromProps); }; + const showTitleDivider = !useIsInViewport(topScrollSignalRef, true, { + root: scrollableParentElement, + }); + + const showButtonsDivider = !useIsInViewport(bottomScrollSignalRef, true, { + rootMargin: '1px', // bottomScrollSignal div has 0px height so we need a 1px margin to trigger the intersection observer + root: scrollableParentElement, + }); + return ( -
+
{onDismiss && (
layoutRef.current?.dismiss()} Icon={IconCloseRegular} - aria-label="Close drawer" + aria-label={texts.modalClose || t(tokens.modalClose)} type="neutral" backgroundType="transparent" />
)} {title && ( -
+
- {title} + {title}
)} +
+
- {subtitle && {subtitle}} + {subtitle && ( + + {subtitle} + + )} {description && ( - + {description} )} {children} +
+ {hasButtons && ( {button.text} - ) : undefined + ) } secondaryButton={ - secondaryButton ? ( + secondaryButton && ( {secondaryButton.text} - ) : undefined + ) } link={ - buttonLink ? ( + buttonLink && ( {buttonLink.text} - ) : undefined + ) } /> )} -
+
); }; diff --git a/src/portal.tsx b/src/portal.tsx index 78473c9f5..75a6225c0 100644 --- a/src/portal.tsx +++ b/src/portal.tsx @@ -20,29 +20,22 @@ export const Portal = ({children, className}: Props): JSX.Element | null => { const [container, setContainer] = React.useState(null); React.useEffect(() => { - if (!container) { - const newContainer = document.createElement('div'); - newContainer.style.isolation = 'isolate'; - setContainer(newContainer); - document.body.appendChild(newContainer); - } + const newContainer = document.createElement('div'); + newContainer.style.isolation = 'isolate'; + setContainer(newContainer); + document.body.appendChild(newContainer); return () => { - if (container) { - document.body.removeChild(container); - } + document.body.removeChild(newContainer); }; - }, [container]); + }, []); React.useEffect(() => { - if (container && className) { - container.classList.add(...className.split(' ')); - } + const classes = className?.split(' ') || []; + container?.classList.add(...classes); return () => { - if (container && className) { - container.classList.remove(...className.split(' ')); - } + container?.classList.remove(...classes); }; }, [className, container]);