aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/components/ui/drop_down.tsx
blob: 32105d353206e30d45e0b40d6313c26a4944d7ef (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import * as React from 'react';
import { Placement } from 'react-popper';
import { Popover } from 'ts/components/ui/popover';

const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300;
const DEFAULT_STYLE = {
    fontSize: 14,
};

export enum DropdownMouseEvent {
    Hover = 'hover',
    Click = 'click',
}

export interface DropDownProps {
    activeNode: React.ReactNode;
    popoverContent: React.ReactNode;
    placement?: Placement;
    style?: React.CSSProperties;
    zDepth?: number;
    activateEvent?: DropdownMouseEvent;
    closeEvent?: DropdownMouseEvent;
}

interface DropDownState {
    isDropDownOpen: boolean;
    isHovering: boolean;
    anchorEl?: HTMLInputElement;
}

export class DropDown extends React.Component<DropDownProps, DropDownState> {
    public static defaultProps: Partial<DropDownProps> = {
        style: DEFAULT_STYLE,
        zDepth: 1,
        activateEvent: DropdownMouseEvent.Hover,
        closeEvent: DropdownMouseEvent.Hover,
    };
    private _popoverCloseCheckIntervalId: number;
    public static getDerivedStateFromProps(props: DropDownProps, state: DropDownState): Partial<DropDownState> {
        switch (props.activateEvent) {
            case DropdownMouseEvent.Click:
                return { isDropDownOpen: state.isDropDownOpen };
            case DropdownMouseEvent.Hover:
                return { isDropDownOpen: state.isHovering };
            default:
                return {};
        }
    }
    constructor(props: DropDownProps) {
        super(props);
        this.state = {
            isHovering: false,
            isDropDownOpen: false,
        };
    }
    public componentDidMount(): void {
        this._popoverCloseCheckIntervalId = window.setInterval(() => {
            this._checkIfShouldClosePopover();
        }, CHECK_CLOSE_POPOVER_INTERVAL_MS);
    }
    public componentWillUnmount(): void {
        window.clearInterval(this._popoverCloseCheckIntervalId);
    }
    public render(): React.ReactNode {
        return (
            <div
                style={{ ...this.props.style, width: 'fit-content', height: '100%' }}
                onMouseEnter={this._onHover.bind(this)}
                onMouseLeave={this._onHoverOff.bind(this)}
            >
                <div onClick={this._onActiveNodeClick.bind(this)}>{this.props.activeNode}</div>
                {this.state.isDropDownOpen &&
                    <Popover
                        anchorEl={this.state.anchorEl}
                        placement={this.props.placement}
                        onRequestClose={this._closePopover.bind(this)}
                        zIndex={this.props.zDepth}
                    >
                        <div
                            onClick={this._closePopover.bind(this)}
                        >
                            {this.props.popoverContent}
                        </div>
                    </Popover>
                }
            </div>
        );
    }
    private _onActiveNodeClick(event: React.FormEvent<HTMLInputElement>): void {
        if (this.props.activateEvent === DropdownMouseEvent.Click) {
            this.setState({
                isDropDownOpen: true,
                anchorEl: event.currentTarget,
            });
        }
    }
    private _onHover(event: React.FormEvent<HTMLInputElement>): void {
        this.setState({
            isHovering: true,
            anchorEl: event.currentTarget,
        });
    }
    private _onHoverOff(): void {
        this.setState({ isHovering: false, anchorEl: undefined });
    }
    private _checkIfShouldClosePopover(): void {
        if (!this.state.isDropDownOpen) {
            return; // noop
        }
        if (this.props.closeEvent === DropdownMouseEvent.Hover && !this.state.isHovering) {
            this._closePopover();
        }
    }
    private _closePopover(): void {
        this.setState({
            isDropDownOpen: false,
        });
    }
}