Add useDropdown hook (#4228)

GitOrigin-RevId: a16762139049aed1e309b1330602c3b291d41f81
This commit is contained in:
Alf Eaton 2021-06-23 09:09:46 +01:00 committed by Copybot
parent 2328dd1705
commit bb55fc2e32
2 changed files with 60 additions and 18 deletions

View file

@ -1,25 +1,13 @@
import React, { useCallback, useState } from 'react'
import React from 'react'
import { Dropdown } from 'react-bootstrap'
import PropTypes from 'prop-types'
import useDropdown from '../hooks/use-dropdown'
export default function ControlledDropdown(props) {
const [open, setOpen] = useState(Boolean(props.defaultOpen))
const handleClick = useCallback(event => {
event.stopPropagation()
}, [])
const handleToggle = useCallback(value => {
setOpen(value)
}, [])
const dropdownProps = useDropdown(Boolean(props.defaultOpen))
return (
<Dropdown
{...props}
open={open}
onToggle={handleToggle}
onClick={handleClick}
>
<Dropdown {...props} {...dropdownProps}>
{React.Children.map(props.children, child => {
if (!React.isValidElement(child)) {
return child
@ -27,12 +15,12 @@ export default function ControlledDropdown(props) {
// Dropdown.Menu
if ('open' in child.props) {
return React.cloneElement(child, { open })
return React.cloneElement(child, { open: dropdownProps.open })
}
// Overlay
if ('show' in child.props) {
return React.cloneElement(child, { show: open })
return React.cloneElement(child, { show: dropdownProps.open })
}
// anything else

View file

@ -0,0 +1,54 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { findDOMNode } from 'react-dom'
export default function useDropdown(defaultOpen = false) {
const [open, setOpen] = useState(defaultOpen)
// store the dropdown node for use in the "click outside" event listener
const ref = useRef(null)
// react-bootstrap v0.x passes `component` instead of `node` to the ref callback
const handleRef = useCallback(
component => {
if (component) {
// eslint-disable-next-line react/no-find-dom-node
ref.current = findDOMNode(component)
}
},
[ref]
)
// prevent a click on the dropdown toggle propagating to the original handler
const handleClick = useCallback(event => {
event.stopPropagation()
}, [])
// handle dropdown toggle
const handleToggle = useCallback(value => {
setOpen(value)
}, [])
// close the dropdown on click outside the dropdown
const handleDocumentClick = useCallback(
event => {
if (ref.current && !ref.current.contains(event.target)) {
setOpen(false)
}
},
[ref]
)
// add/remove listener for click anywhere in document
useEffect(() => {
if (open) {
document.addEventListener('mousedown', handleDocumentClick)
}
return () => {
document.removeEventListener('mousedown', handleDocumentClick)
}
}, [open, handleDocumentClick])
// return props for the Dropdown component
return { ref: handleRef, onClick: handleClick, onToggle: handleToggle, open }
}