@gechiui/components
Version:
UI components for GeChiUI.
119 lines (108 loc) • 2.66 kB
JavaScript
// @ts-nocheck
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* GeChiUI dependencies
*/
import { useRef, useEffect, useState } from '@gechiui/element';
/**
* Internal dependencies
*/
import Popover from '../popover';
function useObservableState( initialState, onStateChange ) {
const [ state, setState ] = useState( initialState );
return [
state,
( value ) => {
setState( value );
if ( onStateChange ) {
onStateChange( value );
}
},
];
}
export default function Dropdown( {
renderContent,
renderToggle,
position = 'bottom right',
className,
contentClassName,
expandOnMobile,
headerTitle,
focusOnMount,
popoverProps,
onClose,
onToggle,
} ) {
const containerRef = useRef();
const [ isOpen, setIsOpen ] = useObservableState( false, onToggle );
useEffect(
() => () => {
if ( onToggle ) {
onToggle( false );
}
},
[]
);
function toggle() {
setIsOpen( ! isOpen );
}
/**
* Closes the popover when focus leaves it unless the toggle was pressed or
* focus has moved to a separate dialog. The former is to let the toggle
* handle closing the popover and the latter is to preserve presence in
* case a dialog has opened, allowing focus to return when it's dismissed.
*/
function closeIfFocusOutside() {
const { ownerDocument } = containerRef.current;
const dialog = ownerDocument.activeElement.closest( '[role="dialog"]' );
if (
! containerRef.current.contains( ownerDocument.activeElement ) &&
( ! dialog || dialog.contains( containerRef.current ) )
) {
close();
}
}
function close() {
if ( onClose ) {
onClose();
}
setIsOpen( false );
}
const args = { isOpen, onToggle: toggle, onClose: close };
return (
<div
className={ classnames( 'components-dropdown', className ) }
ref={ containerRef }
// Some UAs focus the closest focusable parent when the toggle is
// clicked. Making this div focusable ensures such UAs will focus
// it and `closeIfFocusOutside` can tell if the toggle was clicked.
tabIndex="-1"
>
{ renderToggle( args ) }
{ isOpen && (
<Popover
position={ position }
onClose={ close }
onFocusOutside={ closeIfFocusOutside }
expandOnMobile={ expandOnMobile }
headerTitle={ headerTitle }
focusOnMount={ focusOnMount }
{ ...popoverProps }
anchorRef={
popoverProps?.anchorRef ?? containerRef.current
}
className={ classnames(
'components-dropdown__content',
popoverProps ? popoverProps.className : undefined,
contentClassName
) }
>
{ renderContent( args ) }
</Popover>
) }
</div>
);
}