2020-07-11 20:12:59 +02:00
|
|
|
import React, { createRef } from 'react';
|
2020-07-03 23:19:05 +02:00
|
|
|
import styled from 'styled-components/macro';
|
|
|
|
import tw from 'twin.macro';
|
2020-07-05 01:26:07 +02:00
|
|
|
import Fade from '@/components/elements/Fade';
|
2020-04-10 07:08:09 +02:00
|
|
|
|
|
|
|
interface Props {
|
|
|
|
children: React.ReactNode;
|
|
|
|
renderToggle: (onClick: (e: React.MouseEvent<any, 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 {
|
2022-06-26 21:13:52 +02:00
|
|
|
${(props) => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)};
|
2020-04-10 07:08:09 +02:00
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2020-07-11 20:12:59 +02:00
|
|
|
interface State {
|
|
|
|
posX: number;
|
|
|
|
visible: boolean;
|
|
|
|
}
|
2020-04-10 07:08:09 +02:00
|
|
|
|
2020-07-11 20:12:59 +02:00
|
|
|
class DropdownMenu extends React.PureComponent<Props, State> {
|
|
|
|
menu = createRef<HTMLDivElement>();
|
|
|
|
|
|
|
|
state: State = {
|
|
|
|
posX: 0,
|
|
|
|
visible: false,
|
|
|
|
};
|
|
|
|
|
2022-06-26 21:13:52 +02:00
|
|
|
componentWillUnmount() {
|
2020-07-11 20:12:59 +02:00
|
|
|
this.removeListeners();
|
|
|
|
}
|
|
|
|
|
2022-06-26 21:13:52 +02:00
|
|
|
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>) {
|
2020-07-11 20:12:59 +02:00
|
|
|
const menu = this.menu.current;
|
|
|
|
|
|
|
|
if (this.state.visible && !prevState.visible && menu) {
|
|
|
|
document.addEventListener('click', this.windowListener);
|
|
|
|
document.addEventListener('contextmenu', this.contextMenuListener);
|
2020-12-27 19:49:33 +01:00
|
|
|
menu.style.left = `${Math.round(this.state.posX - menu.clientWidth)}px`;
|
2020-07-11 20:12:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.state.visible && prevState.visible) {
|
|
|
|
this.removeListeners();
|
|
|
|
}
|
|
|
|
}
|
2020-04-10 07:08:09 +02:00
|
|
|
|
2020-07-11 20:12:59 +02:00
|
|
|
removeListeners = () => {
|
|
|
|
document.removeEventListener('click', this.windowListener);
|
|
|
|
document.removeEventListener('contextmenu', this.contextMenuListener);
|
2020-04-10 07:08:09 +02:00
|
|
|
};
|
|
|
|
|
2020-07-11 20:12:59 +02:00
|
|
|
onClickHandler = (e: React.MouseEvent<any, MouseEvent>) => {
|
|
|
|
e.preventDefault();
|
|
|
|
this.triggerMenu(e.clientX);
|
|
|
|
};
|
|
|
|
|
|
|
|
contextMenuListener = () => this.setState({ visible: false });
|
|
|
|
|
|
|
|
windowListener = (e: MouseEvent) => {
|
|
|
|
const menu = this.menu.current;
|
2020-07-11 20:19:38 +02:00
|
|
|
|
2020-07-11 20:12:59 +02:00
|
|
|
if (e.button === 2 || !this.state.visible || !menu) {
|
2020-04-10 07:08:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:12:59 +02:00
|
|
|
if (e.target === menu || menu.contains(e.target as Node)) {
|
2020-04-10 07:08:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:12:59 +02:00
|
|
|
if (e.target !== menu && !menu.contains(e.target as Node)) {
|
|
|
|
this.setState({ visible: false });
|
2020-04-10 07:08:09 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-06-26 21:13:52 +02:00
|
|
|
triggerMenu = (posX: number) =>
|
|
|
|
this.setState((s) => ({
|
|
|
|
posX: !s.visible ? posX : s.posX,
|
|
|
|
visible: !s.visible,
|
|
|
|
}));
|
2020-04-10 07:08:09 +02:00
|
|
|
|
2022-06-26 21:13:52 +02:00
|
|
|
render() {
|
2020-07-11 20:12:59 +02:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
{this.props.renderToggle(this.onClickHandler)}
|
|
|
|
<Fade timeout={150} in={this.state.visible} unmountOnExit>
|
|
|
|
<div
|
|
|
|
ref={this.menu}
|
2022-06-26 21:13:52 +02:00
|
|
|
onClick={(e) => {
|
2020-07-11 20:12:59 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
this.setState({ visible: false });
|
|
|
|
}}
|
2020-12-27 19:49:33 +01:00
|
|
|
style={{ width: '12rem' }}
|
2020-12-26 19:41:25 +01:00
|
|
|
css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 z-50`}
|
2020-07-11 20:12:59 +02:00
|
|
|
>
|
|
|
|
{this.props.children}
|
|
|
|
</div>
|
|
|
|
</Fade>
|
|
|
|
</div>
|
2020-04-10 07:08:09 +02:00
|
|
|
);
|
2020-07-11 20:12:59 +02:00
|
|
|
}
|
|
|
|
}
|
2020-04-10 07:08:09 +02:00
|
|
|
|
|
|
|
export default DropdownMenu;
|