Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/resources/scripts/components/elements/DropdownMenu.tsx
7461 views
1
import React, { createRef } from 'react';
2
import styled from 'styled-components/macro';
3
import tw from 'twin.macro';
4
import Fade from '@/components/elements/Fade';
5
6
interface Props {
7
children: React.ReactNode;
8
renderToggle: (onClick: (e: React.MouseEvent<any, MouseEvent>) => void) => React.ReactChild;
9
}
10
11
export const DropdownButtonRow = styled.button<{ danger?: boolean }>`
12
${tw`p-2 flex items-center rounded w-full text-neutral-500`};
13
transition: 150ms all ease;
14
15
&:hover {
16
${(props) => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)};
17
}
18
`;
19
20
interface State {
21
posX: number;
22
visible: boolean;
23
}
24
25
class DropdownMenu extends React.PureComponent<Props, State> {
26
menu = createRef<HTMLDivElement>();
27
28
state: State = {
29
posX: 0,
30
visible: false,
31
};
32
33
componentWillUnmount() {
34
this.removeListeners();
35
}
36
37
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>) {
38
const menu = this.menu.current;
39
40
if (this.state.visible && !prevState.visible && menu) {
41
document.addEventListener('click', this.windowListener);
42
document.addEventListener('contextmenu', this.contextMenuListener);
43
menu.style.left = `${Math.round(this.state.posX - menu.clientWidth)}px`;
44
}
45
46
if (!this.state.visible && prevState.visible) {
47
this.removeListeners();
48
}
49
}
50
51
removeListeners = () => {
52
document.removeEventListener('click', this.windowListener);
53
document.removeEventListener('contextmenu', this.contextMenuListener);
54
};
55
56
onClickHandler = (e: React.MouseEvent<any, MouseEvent>) => {
57
e.preventDefault();
58
this.triggerMenu(e.clientX);
59
};
60
61
contextMenuListener = () => this.setState({ visible: false });
62
63
windowListener = (e: MouseEvent) => {
64
const menu = this.menu.current;
65
66
if (e.button === 2 || !this.state.visible || !menu) {
67
return;
68
}
69
70
if (e.target === menu || menu.contains(e.target as Node)) {
71
return;
72
}
73
74
if (e.target !== menu && !menu.contains(e.target as Node)) {
75
this.setState({ visible: false });
76
}
77
};
78
79
triggerMenu = (posX: number) =>
80
this.setState((s) => ({
81
posX: !s.visible ? posX : s.posX,
82
visible: !s.visible,
83
}));
84
85
render() {
86
return (
87
<div>
88
{this.props.renderToggle(this.onClickHandler)}
89
<Fade timeout={150} in={this.state.visible} unmountOnExit>
90
<div
91
ref={this.menu}
92
onClick={(e) => {
93
e.stopPropagation();
94
this.setState({ visible: false });
95
}}
96
style={{ width: '12rem' }}
97
css={tw`absolute bg-white p-2 rounded border border-neutral-700 shadow-lg text-neutral-500 z-50`}
98
>
99
{this.props.children}
100
</div>
101
</Fade>
102
</div>
103
);
104
}
105
}
106
107
export default DropdownMenu;
108
109