mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Add useDropdown hook (#4228)
GitOrigin-RevId: a16762139049aed1e309b1330602c3b291d41f81
This commit is contained in:
parent
2328dd1705
commit
bb55fc2e32
2 changed files with 60 additions and 18 deletions
|
@ -1,25 +1,13 @@
|
||||||
import React, { useCallback, useState } from 'react'
|
import React from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown } from 'react-bootstrap'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import useDropdown from '../hooks/use-dropdown'
|
||||||
|
|
||||||
export default function ControlledDropdown(props) {
|
export default function ControlledDropdown(props) {
|
||||||
const [open, setOpen] = useState(Boolean(props.defaultOpen))
|
const dropdownProps = useDropdown(Boolean(props.defaultOpen))
|
||||||
|
|
||||||
const handleClick = useCallback(event => {
|
|
||||||
event.stopPropagation()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleToggle = useCallback(value => {
|
|
||||||
setOpen(value)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown {...props} {...dropdownProps}>
|
||||||
{...props}
|
|
||||||
open={open}
|
|
||||||
onToggle={handleToggle}
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
{React.Children.map(props.children, child => {
|
{React.Children.map(props.children, child => {
|
||||||
if (!React.isValidElement(child)) {
|
if (!React.isValidElement(child)) {
|
||||||
return child
|
return child
|
||||||
|
@ -27,12 +15,12 @@ export default function ControlledDropdown(props) {
|
||||||
|
|
||||||
// Dropdown.Menu
|
// Dropdown.Menu
|
||||||
if ('open' in child.props) {
|
if ('open' in child.props) {
|
||||||
return React.cloneElement(child, { open })
|
return React.cloneElement(child, { open: dropdownProps.open })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlay
|
// Overlay
|
||||||
if ('show' in child.props) {
|
if ('show' in child.props) {
|
||||||
return React.cloneElement(child, { show: open })
|
return React.cloneElement(child, { show: dropdownProps.open })
|
||||||
}
|
}
|
||||||
|
|
||||||
// anything else
|
// anything else
|
||||||
|
|
54
services/web/frontend/js/shared/hooks/use-dropdown.js
Normal file
54
services/web/frontend/js/shared/hooks/use-dropdown.js
Normal 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 }
|
||||||
|
}
|
Loading…
Reference in a new issue