Skip to content

Commit

Permalink
anchored overlay can take an external ref for the anchor
Browse files Browse the repository at this point in the history
  • Loading branch information
VanAnderson committed May 28, 2021
1 parent 5cf6ec1 commit 0fd5278
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 116 deletions.
67 changes: 52 additions & 15 deletions src/AnchoredOverlay/AnchoredOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React, {useCallback, useMemo, useRef} from 'react'
import {OverlayProps} from '../Overlay'
import {FocusZoneHookSettings} from '../hooks/useFocusZone'
import Overlay, {OverlayProps} from '../Overlay'
import {useFocusTrap} from '../hooks/useFocusTrap'
import {FocusZoneHookSettings, useFocusZone} from '../hooks/useFocusZone'
import {useAnchoredPosition, useRenderForcingRef} from '../hooks'
import {uniqueId} from '../utils/uniqueId'
import {RefAnchoredOverlay} from './RefAnchoredOverlay'

export interface AnchoredOverlayProps extends Pick<OverlayProps, 'height' | 'width'> {
/**
* A custom function component used to render the anchor element.
* Will receive the selected text as `children` prop when an item is activated.
*/
renderAnchor: <T extends React.HTMLAttributes<HTMLElement>>(props: T) => JSX.Element
renderAnchor: <T extends React.HTMLAttributes<HTMLElement>>(props: T) => JSX.Element | null

/**
* An override to the internal ref that will be spread on to the renderAnchor
*/
anchorRef?: React.RefObject<HTMLElement>

/**
* Determines whether the overlay portion of the component should be shown or not
Expand Down Expand Up @@ -42,6 +49,7 @@ export interface AnchoredOverlayProps extends Pick<OverlayProps, 'height' | 'wid
*/
export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
renderAnchor,
anchorRef: externalAnchorRef,
children,
open,
onOpen,
Expand All @@ -51,9 +59,14 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
overlayProps,
focusZoneSettings
}) => {
const anchorRef = useRef<HTMLElement>(null)
const internalAnchorRef = useRef<HTMLElement>(null)
const anchorRef = externalAnchorRef || internalAnchorRef
const [overlayRef, updateOverlayRef] = useRenderForcingRef<HTMLDivElement>()
const anchorId = useMemo(uniqueId, [])

const onClickOutside = useCallback(() => onClose?.('click-outside'), [onClose])
const onEscape = useCallback(() => onClose?.('escape'), [onClose])

const onAnchorKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLElement>) => {
if (!event.defaultPrevented) {
Expand All @@ -74,6 +87,24 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
[open, onOpen]
)

const {position} = useAnchoredPosition(
{
anchorElementRef: anchorRef,
floatingElementRef: overlayRef
},
[overlayRef.current]
)
const overlayPosition = useMemo(() => {
return position && {top: `${position.top}px`, left: `${position.left}px`}
}, [position])

useFocusZone({
containerRef: overlayRef,
disabled: !open || !position,
...focusZoneSettings
})
useFocusTrap({containerRef: overlayRef, disabled: !open || !position})

return (
<>
{renderAnchor({
Expand All @@ -85,16 +116,22 @@ export const AnchoredOverlay: React.FC<AnchoredOverlayProps> = ({
onClick: onAnchorClick,
onKeyDown: onAnchorKeyDown
})}
<RefAnchoredOverlay
anchorRef={anchorRef}
children={children}
open={open}
onClose={onClose}
height={height}
width={width}
overlayProps={overlayProps}
focusZoneSettings={focusZoneSettings}
/>
{open ? (
<Overlay
returnFocusRef={anchorRef}
onClickOutside={onClickOutside}
onEscape={onEscape}
ref={updateOverlayRef}
role="listbox"
visibility={position ? 'visible' : 'hidden'}
height={height}
width={width}
{...overlayPosition}
{...overlayProps}
>
{children}
</Overlay>
) : null}
</>
)
}
Expand Down
95 changes: 0 additions & 95 deletions src/AnchoredOverlay/RefAnchoredOverlay.tsx

This file was deleted.

19 changes: 13 additions & 6 deletions src/stories/RefAnchoredOverlay.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, {useState, useRef} from 'react'
import {Button, Text, ButtonDanger, Position, Flex, BaseStyles, ThemeProvider} from '..'
import {Meta} from '@storybook/react'

import {RefAnchoredOverlay} from '../AnchoredOverlay/RefAnchoredOverlay'
import {AnchoredOverlay} from '../AnchoredOverlay'

export default {
title: 'Internal components/RefAnchoredOverlay',
component: RefAnchoredOverlay,
title: 'Internal components/AnchoredOverlay',
component: AnchoredOverlay,
decorators: [
// @ts-ignore
Story => {
Expand All @@ -20,7 +21,7 @@ export default {
]
} as Meta

export const RefAnchoredOverlayStory = () => {
export const RefAnchoredOverlay = () => {
const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const confirmButtonRef = useRef<HTMLButtonElement>(null)
Expand All @@ -32,15 +33,21 @@ export const RefAnchoredOverlayStory = () => {
open overlay
</Button>
{/* @ts-ignore */}
<RefAnchoredOverlay onClose={closeOverlay} anchorRef={buttonRef} open={isOpen} width="small">
<AnchoredOverlay
renderAnchor={() => null}
anchorRef={buttonRef}
onClose={closeOverlay}
open={isOpen}
width="small"
>
<Flex flexDirection="column" p={2}>
<Text>Are you sure?</Text>
<ButtonDanger onClick={closeOverlay}>Cancel</ButtonDanger>
<Button onClick={closeOverlay} ref={confirmButtonRef}>
Confirm
</Button>
</Flex>
</RefAnchoredOverlay>
</AnchoredOverlay>
</Position>
)
}

0 comments on commit 0fd5278

Please sign in to comment.