import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react'; import { createRef, PureComponent } from 'react'; import styled from 'styled-components'; import tw from 'twin.macro'; import FadeTransition from '@/components/elements/transitions/FadeTransition'; interface Props { children: ReactNode; renderToggle: (onClick: (e: ReactMouseEvent) => void) => any; } 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 PureComponent { menu = createRef(); override state: State = { posX: 0, visible: false, }; override componentWillUnmount() { this.removeListeners(); } override 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.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: ReactMouseEvent) { e.preventDefault(); this.triggerMenu(e.clientX); } contextMenuListener() { this.setState({ visible: false }); } windowListener(e: MouseEvent): any { const menu = this.menu.current; 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, })); } override render() { return (
{this.props.renderToggle(this.onClickHandler)}
{ e.stopPropagation(); this.setState({ visible: false }); }} style={{ width: '12rem' }} css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 z-50`} > {this.props.children}
); } } export default DropdownMenu;