aboutsummaryrefslogtreecommitdiffstats
path: root/packages/website/ts/components/ui/drop_down.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/website/ts/components/ui/drop_down.tsx')
-rw-r--r--packages/website/ts/components/ui/drop_down.tsx110
1 files changed, 110 insertions, 0 deletions
diff --git a/packages/website/ts/components/ui/drop_down.tsx b/packages/website/ts/components/ui/drop_down.tsx
new file mode 100644
index 000000000..31a67f0d7
--- /dev/null
+++ b/packages/website/ts/components/ui/drop_down.tsx
@@ -0,0 +1,110 @@
+import * as _ from 'lodash';
+import Menu from 'material-ui/Menu';
+import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
+import * as React from 'react';
+import { MaterialUIPosition, Styles, WebsitePaths } from 'ts/types';
+import { colors } from 'ts/utils/colors';
+
+const CHECK_CLOSE_POPOVER_INTERVAL_MS = 300;
+const DEFAULT_STYLE = {
+ fontSize: 14,
+};
+
+interface DropDownProps {
+ hoverActiveNode: React.ReactNode;
+ popoverContent: React.ReactNode;
+ anchorOrigin: MaterialUIPosition;
+ targetOrigin: MaterialUIPosition;
+ style?: React.CSSProperties;
+ zDepth?: number;
+}
+
+interface DropDownState {
+ isDropDownOpen: boolean;
+ anchorEl?: HTMLInputElement;
+}
+
+export class DropDown extends React.Component<DropDownProps, DropDownState> {
+ public static defaultProps: Partial<DropDownProps> = {
+ style: DEFAULT_STYLE,
+ zDepth: 1,
+ };
+ private _isHovering: boolean;
+ private _popoverCloseCheckIntervalId: number;
+ constructor(props: DropDownProps) {
+ super(props);
+ this.state = {
+ isDropDownOpen: false,
+ };
+ }
+ public componentDidMount() {
+ this._popoverCloseCheckIntervalId = window.setInterval(() => {
+ this._checkIfShouldClosePopover();
+ }, CHECK_CLOSE_POPOVER_INTERVAL_MS);
+ }
+ public componentWillUnmount() {
+ window.clearInterval(this._popoverCloseCheckIntervalId);
+ }
+ public componentWillReceiveProps(nextProps: DropDownProps) {
+ // HACK: If the popoverContent is updated to a different dimension and the users
+ // mouse is no longer above it, the dropdown can enter an inconsistent state where
+ // it believes the user is still hovering over it. In order to remedy this, we
+ // call hoverOff whenever the dropdown receives updated props. This is a hack
+ // because it will effectively close the dropdown on any prop update, barring
+ // dropdowns from having dynamic content.
+ this._onHoverOff();
+ }
+ public render() {
+ return (
+ <div
+ style={{ ...this.props.style, width: 'fit-content', height: '100%' }}
+ onMouseEnter={this._onHover.bind(this)}
+ onMouseLeave={this._onHoverOff.bind(this)}
+ >
+ {this.props.hoverActiveNode}
+ <Popover
+ open={this.state.isDropDownOpen}
+ anchorEl={this.state.anchorEl}
+ anchorOrigin={this.props.anchorOrigin}
+ targetOrigin={this.props.targetOrigin}
+ onRequestClose={this._closePopover.bind(this)}
+ useLayerForClickAway={false}
+ animation={PopoverAnimationVertical}
+ zDepth={this.props.zDepth}
+ >
+ <div onMouseEnter={this._onHover.bind(this)} onMouseLeave={this._onHoverOff.bind(this)}>
+ {this.props.popoverContent}
+ </div>
+ </Popover>
+ </div>
+ );
+ }
+ private _onHover(event: React.FormEvent<HTMLInputElement>) {
+ this._isHovering = true;
+ this._checkIfShouldOpenPopover(event);
+ }
+ private _checkIfShouldOpenPopover(event: React.FormEvent<HTMLInputElement>) {
+ if (this.state.isDropDownOpen) {
+ return; // noop
+ }
+
+ this.setState({
+ isDropDownOpen: true,
+ anchorEl: event.currentTarget,
+ });
+ }
+ private _onHoverOff() {
+ this._isHovering = false;
+ }
+ private _checkIfShouldClosePopover() {
+ if (!this.state.isDropDownOpen || this._isHovering) {
+ return; // noop
+ }
+ this._closePopover();
+ }
+ private _closePopover() {
+ this.setState({
+ isDropDownOpen: false,
+ });
+ }
+}