diff --git a/docs/content/Overlay.mdx b/docs/content/Overlay.mdx index fc97fff6c14..34e4e8ba9fa 100644 --- a/docs/content/Overlay.mdx +++ b/docs/content/Overlay.mdx @@ -77,3 +77,4 @@ render() | onEscape | `function` | `undefined` | Required. Function to call when user presses `Escape`. Typically this function sets the `Overlay` visibility state to `false`. | | width | `'sm', 'md', 'lg', 'xl', 'auto'` | `auto` | Sets the width of the `Overlay`, pick from our set list of widths, or pass `auto` to automatically set the width based on the content of the `Overlay`. `sm` corresponds to `256px`, `md` corresponds to `320px`, `lg` corresponds to `480px`, and `xl` corresponds to `640px`. | | height | `'sm', 'md', 'auto'` | `auto` | Sets the height of the `Overlay`, pick from our set list of heights, or pass `auto` to automatically set the height based on the content of the `Overlay`. `sm` corresponds to `480px` and `md` corresponds to `640px`. | +| visibility | `'visible', 'hidden'` | `visible` | Sets the visibility of the `Overlay`. | diff --git a/src/ActionMenu.tsx b/src/ActionMenu.tsx index 88b4b3066c8..3b77bb77889 100644 --- a/src/ActionMenu.tsx +++ b/src/ActionMenu.tsx @@ -75,7 +75,13 @@ const ActionMenuBase = ({ [open] ) - const {position} = useAnchoredPosition({anchorElementRef: anchorRef, floatingElementRef: overlayRef}) + const {position} = useAnchoredPosition( + { + anchorElementRef: anchorRef, + floatingElementRef: overlayRef + }, + [overlayRef.current] + ) useFocusZone({containerRef: overlayRef, disabled: !open || state !== 'listFocus'}, [position]) useFocusTrap({containerRef: overlayRef, disabled: !open || state !== 'listFocus'}, [position]) @@ -100,6 +106,7 @@ const ActionMenuBase = ({ onClickOutside={onDismiss} onEscape={onDismiss} ref={updateOverlayRef} + visibility={position ? 'visible' : 'hidden'} {...position} > diff --git a/src/Overlay.tsx b/src/Overlay.tsx index 3eb50a7154e..fa88c725153 100644 --- a/src/Overlay.tsx +++ b/src/Overlay.tsx @@ -10,6 +10,7 @@ import {useCombinedRefs} from './hooks/useCombinedRefs' type StyledOverlayProps = { width?: keyof typeof widthMap height?: keyof typeof heightMap + visibility?: 'visible' | 'hidden' } const heightMap = { @@ -48,6 +49,7 @@ const StyledOverlay = styled.div props.visibility || 'visible'}; ${COMMON}; ${POSITION}; ${sx}; @@ -58,7 +60,8 @@ export type OverlayProps = { returnFocusRef: React.RefObject onClickOutside: (e: TouchOrMouseEvent) => void onEscape: (e: KeyboardEvent) => void -} & Omit, keyof SystemPositionProps> + visibility?: 'visible' | 'hidden' +} & Omit, 'visibility' | keyof SystemPositionProps> /** * An `Overlay` is a flexible floating surface, used to display transient content such as menus, @@ -71,10 +74,11 @@ export type OverlayProps = { * @param onEscape Required. Function to call when user presses `Escape`. Typically this function sets the `Overlay` visibility state to `false`. * @param width Sets the width of the `Overlay`, pick from our set list of widths, or pass `auto` to automatically set the width based on the content of the `Overlay`. `sm` corresponds to `256px`, `md` corresponds to `320px`, `lg` corresponds to `480px`, and `xl` corresponds to `640px`. * @param height Sets the height of the `Overlay`, pick from our set list of heights, or pass `auto` to automatically set the height based on the content of the `Overlay`. `sm` corresponds to `480px` and `md` corresponds to `640px`. + * @param visibility Sets the visibility of the `Overlay` */ const Overlay = React.forwardRef( ( - {onClickOutside, role = 'dialog', initialFocusRef, returnFocusRef, ignoreClickRefs, onEscape, ...rest}, + {onClickOutside, role = 'dialog', initialFocusRef, returnFocusRef, ignoreClickRefs, onEscape, visibility, ...rest}, forwardedRef ): ReactElement => { const overlayRef = useRef(null) @@ -90,7 +94,14 @@ const Overlay = React.forwardRef( }) return ( - + ) } diff --git a/src/hooks/useAnchoredPosition.ts b/src/hooks/useAnchoredPosition.ts index 1e5f6e6be43..7ba0a40a4fb 100644 --- a/src/hooks/useAnchoredPosition.ts +++ b/src/hooks/useAnchoredPosition.ts @@ -34,7 +34,7 @@ export function useAnchoredPosition( setPosition(undefined) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [floatingElementRef.current, anchorElementRef.current, ...dependencies]) + }, [floatingElementRef, anchorElementRef, ...dependencies]) return { floatingElementRef, anchorElementRef, diff --git a/src/hooks/useCombinedRefs.ts b/src/hooks/useCombinedRefs.ts index c5915b152c2..0fef707de7d 100644 --- a/src/hooks/useCombinedRefs.ts +++ b/src/hooks/useCombinedRefs.ts @@ -12,16 +12,27 @@ export function useCombinedRefs(...refs: (ForwardedRef | null | undefined) const combinedRef = useRef(null) React.useEffect(() => { - for (const ref of refs) { - if (!ref) { - return - } - if (typeof ref === 'function') { - ref(combinedRef.current ?? null) - } else { - ref.current = combinedRef.current ?? null + function setRefs(current: T | null = null) { + for (const ref of refs) { + if (!ref) { + return + } + if (typeof ref === 'function') { + ref(current) + } else { + ref.current = current + } } } + + setRefs(combinedRef.current) + + return () => { + // ensure the refs get updated on unmount + // eslint-disable-next-line react-hooks/exhaustive-deps + setRefs(combinedRef.current) + } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [...refs, combinedRef.current])