-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
index.js
113 lines (103 loc) · 2.35 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useRef, useEffect, useState } from '@wordpress/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 dropdown if a focus leaves the dropdown wrapper. This is
* intentionally distinct from `onClose` since focus loss from the popover
* is expected to occur when using the Dropdown's toggle button, in which
* case the correct behavior is to keep the dropdown closed. The same applies
* in case when focus is moved to the modal dialog.
*/
function closeIfFocusOutside() {
const { ownerDocument } = containerRef.current;
if (
! containerRef.current.contains( ownerDocument.activeElement ) &&
! ownerDocument.activeElement.closest( '[role="dialog"]' )
) {
close();
}
}
function close() {
if ( onClose ) {
onClose();
}
setIsOpen( false );
}
const args = { isOpen, onToggle: toggle, onClose: close };
return (
<div
className={ classnames( 'components-dropdown', className ) }
ref={ containerRef }
>
{ 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>
);
}