misc_pterodactyl-panel/resources/scripts/components/elements/DropdownMenu.tsx

114 lines
3.3 KiB
TypeScript
Raw Normal View History

2022-11-25 20:25:03 +00:00
import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
import { createRef, PureComponent } from 'react';
import styled from 'styled-components';
2020-07-03 21:19:05 +00:00
import tw from 'twin.macro';
2022-11-25 20:25:03 +00:00
import FadeTransition from '@/components/elements/transitions/FadeTransition';
interface Props {
2022-11-25 20:25:03 +00:00
children: ReactNode;
renderToggle: (onClick: (e: ReactMouseEvent<unknown>) => 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 {
2022-11-25 20:25:03 +00:00
${props => (props.danger ? tw`text-red-700 bg-red-100` : tw`text-neutral-700 bg-neutral-100`)};
}
`;
interface State {
posX: number;
visible: boolean;
}
2022-11-25 20:25:03 +00:00
class DropdownMenu extends PureComponent<Props, State> {
menu = createRef<HTMLDivElement>();
2022-11-25 20:25:03 +00:00
override state: State = {
posX: 0,
visible: false,
};
2022-11-25 20:25:03 +00:00
override componentWillUnmount() {
this.removeListeners();
}
2022-11-25 20:25:03 +00:00
override componentDidUpdate(_prevProps: Readonly<Props>, prevState: Readonly<State>) {
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 18:49:33 +00:00
menu.style.left = `${Math.round(this.state.posX - menu.clientWidth)}px`;
}
if (!this.state.visible && prevState.visible) {
this.removeListeners();
}
}
2022-11-25 20:25:03 +00:00
removeListeners() {
document.removeEventListener('click', this.windowListener);
document.removeEventListener('contextmenu', this.contextMenuListener);
2022-11-25 20:25:03 +00:00
}
2022-11-25 20:25:03 +00:00
onClickHandler(e: ReactMouseEvent<unknown>) {
e.preventDefault();
this.triggerMenu(e.clientX);
2022-11-25 20:25:03 +00:00
}
2022-11-25 20:25:03 +00:00
contextMenuListener() {
this.setState({ visible: false });
}
2022-11-25 20:25:03 +00:00
windowListener(e: MouseEvent): any {
const menu = this.menu.current;
2020-07-11 18:19:38 +00:00
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 });
}
2022-11-25 20:25:03 +00:00
}
2022-11-25 20:25:03 +00:00
triggerMenu(posX: number) {
this.setState(s => ({
posX: !s.visible ? posX : s.posX,
visible: !s.visible,
}));
2022-11-25 20:25:03 +00:00
}
2022-11-25 20:25:03 +00:00
override render() {
return (
<div>
{this.props.renderToggle(this.onClickHandler)}
2022-11-25 20:25:03 +00:00
<FadeTransition duration="duration-150" show={this.state.visible} appear unmount>
<div
ref={this.menu}
2022-11-25 20:25:03 +00:00
onClick={e => {
e.stopPropagation();
this.setState({ visible: false });
}}
2020-12-27 18:49:33 +00:00
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}
</div>
2022-11-25 20:25:03 +00:00
</FadeTransition>
</div>
);
}
}
export default DropdownMenu;