import React, { createRef } from 'react'; import styled from 'styled-components/macro'; import tw from 'twin.macro'; import Fade from '@/components/elements/Fade'; interface Props { children: React.ReactNode; renderToggle: (onClick: (e: React.MouseEvent) => void) => React.ReactChild; } export const DropdownButtonRow = styled.button<{ danger?: boolean }>` ${tw`p-2 flex items-center rounded w-full text-neutral-500`}; transition: 150ms all ease; &:hover { ${props => props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`}; } `; interface State { posX: number; visible: boolean; } class DropdownMenu extends React.PureComponent { menu = createRef(); state: State = { posX: 0, visible: false, }; componentWillUnmount () { this.removeListeners(); } componentDidUpdate (prevProps: Readonly, prevState: Readonly) { const menu = this.menu.current; if (this.state.visible && !prevState.visible && menu) { document.addEventListener('click', this.windowListener); document.addEventListener('contextmenu', this.contextMenuListener); menu.setAttribute( 'style', `left: ${Math.round(this.state.posX - menu.clientWidth)}px`, ); } if (!this.state.visible && prevState.visible) { this.removeListeners(); } } removeListeners = () => { document.removeEventListener('click', this.windowListener); document.removeEventListener('contextmenu', this.contextMenuListener); }; onClickHandler = (e: React.MouseEvent) => { e.preventDefault(); this.triggerMenu(e.clientX); }; contextMenuListener = () => this.setState({ visible: false }); windowListener = (e: MouseEvent) => { const menu = this.menu.current; console.log('windowListener:', e.button); if (e.button === 2 || !this.state.visible || !menu) { return; } if (e.target === menu || menu.contains(e.target as Node)) { return; } if (e.target !== menu && !menu.contains(e.target as Node)) { this.setState({ visible: false }); } }; triggerMenu = (posX: number) => this.setState(s => ({ posX: !s.visible ? posX : s.posX, visible: !s.visible, })); render () { return (
{this.props.renderToggle(this.onClickHandler)}
{ e.stopPropagation(); this.setState({ visible: false }); }} css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 min-w-48`} > {this.props.children}
); } } export default DropdownMenu;