Path: blob/1.0-develop/resources/scripts/components/elements/DropdownMenu.tsx
7461 views
import React, { createRef } from 'react';1import styled from 'styled-components/macro';2import tw from 'twin.macro';3import Fade from '@/components/elements/Fade';45interface Props {6children: React.ReactNode;7renderToggle: (onClick: (e: React.MouseEvent<any, MouseEvent>) => void) => React.ReactChild;8}910export const DropdownButtonRow = styled.button<{ danger?: boolean }>`11${tw`p-2 flex items-center rounded w-full text-neutral-500`};12transition: 150ms all ease;1314&:hover {15${(props) => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)};16}17`;1819interface State {20posX: number;21visible: boolean;22}2324class DropdownMenu extends React.PureComponent<Props, State> {25menu = createRef<HTMLDivElement>();2627state: State = {28posX: 0,29visible: false,30};3132componentWillUnmount() {33this.removeListeners();34}3536componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>) {37const menu = this.menu.current;3839if (this.state.visible && !prevState.visible && menu) {40document.addEventListener('click', this.windowListener);41document.addEventListener('contextmenu', this.contextMenuListener);42menu.style.left = `${Math.round(this.state.posX - menu.clientWidth)}px`;43}4445if (!this.state.visible && prevState.visible) {46this.removeListeners();47}48}4950removeListeners = () => {51document.removeEventListener('click', this.windowListener);52document.removeEventListener('contextmenu', this.contextMenuListener);53};5455onClickHandler = (e: React.MouseEvent<any, MouseEvent>) => {56e.preventDefault();57this.triggerMenu(e.clientX);58};5960contextMenuListener = () => this.setState({ visible: false });6162windowListener = (e: MouseEvent) => {63const menu = this.menu.current;6465if (e.button === 2 || !this.state.visible || !menu) {66return;67}6869if (e.target === menu || menu.contains(e.target as Node)) {70return;71}7273if (e.target !== menu && !menu.contains(e.target as Node)) {74this.setState({ visible: false });75}76};7778triggerMenu = (posX: number) =>79this.setState((s) => ({80posX: !s.visible ? posX : s.posX,81visible: !s.visible,82}));8384render() {85return (86<div>87{this.props.renderToggle(this.onClickHandler)}88<Fade timeout={150} in={this.state.visible} unmountOnExit>89<div90ref={this.menu}91onClick={(e) => {92e.stopPropagation();93this.setState({ visible: false });94}}95style={{ width: '12rem' }}96css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 z-50`}97>98{this.props.children}99</div>100</Fade>101</div>102);103}104}105106export default DropdownMenu;107108109