github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/shared/components/dropdown/index.tsx (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  import classNames from "classnames";
    12  import Select from "react-select";
    13  import React from "react";
    14  import _ from "lodash";
    15  
    16  import "./dropdown.styl";
    17  
    18  import {leftArrow, rightArrow} from "src/views/shared/components/icons";
    19  import { trustIcon } from "src/util/trust";
    20  import ReactSelectClass from "react-select";
    21  import { CaretDown } from "src/components/icon/caretDown";
    22  
    23  export interface DropdownOption {
    24    value: string;
    25    label: string;
    26  }
    27  
    28  export enum ArrowDirection {
    29    LEFT, RIGHT, CENTER,
    30  }
    31  
    32  interface DropdownOwnProps {
    33    title: string;
    34    selected: string;
    35    options: DropdownOption[];
    36    onChange?: (selected: DropdownOption) => void; // Callback when the value changes.
    37    // If onArrowClick exists, don't display the arrow next to the dropdown,
    38    // display left and right arrows to either side instead.
    39    onArrowClick?: (direction: ArrowDirection) => void;
    40    // Disable any arrows in the arrow direction array.
    41    disabledArrows?: ArrowDirection[];
    42    content?: any;
    43    isTimeRange?: boolean;
    44    className?: string;
    45    type?: "primary" | "secondary";
    46  }
    47  
    48  export const arrowRenderer = ({ isOpen }: { isOpen: boolean }) => <span className={classNames("caret-down", { "active": isOpen })}><CaretDown /></span>;
    49  
    50  /**
    51   * Dropdown component that uses the URL query string for state.
    52   */
    53  export default class Dropdown extends React.Component<DropdownOwnProps, {}> {
    54    state = {
    55      is_focused: false,
    56    };
    57  
    58    dropdownRef: React.RefObject<HTMLDivElement> = React.createRef();
    59    titleRef: React.RefObject<HTMLDivElement> = React.createRef();
    60    selectRef: React.RefObject<ReactSelectClass> = React.createRef();
    61  
    62    triggerSelectClick = (e: any) => {
    63      const dropdownNode = this.dropdownRef.current as Node;
    64      const titleNode = this.titleRef.current as Node;
    65      const selectNode = this.selectRef.current;
    66  
    67      if (e.target.isSameNode(dropdownNode) || e.target.isSameNode(titleNode) || e.target.className.indexOf("dropdown__select") > -1) {
    68        // This is a far-less-than-ideal solution to the need to trigger
    69        // the react-select dropdown from the entirety of the dropdown area
    70        // instead of just the nodes rendered by the component itself
    71        // the approach borrows from:
    72        // https://github.com/JedWatson/react-select/issues/305#issuecomment-172607534
    73        //
    74        // a broader discussion on the status of a possible feature addition that
    75        // would render this hack moot can be found here:
    76        // https://github.com/JedWatson/react-select/issues/1989
    77        (selectNode as any).handleMouseDownOnMenu(e);
    78      }
    79    }
    80  
    81    onFocus = () => this.setState({ is_focused: true });
    82  
    83    onClose = () => this.setState({ is_focused: false });
    84  
    85    render() {
    86      const { selected, options, onChange, onArrowClick, disabledArrows, content, isTimeRange, type = "secondary" } = this.props;
    87  
    88      const className = classNames(
    89        "dropdown",
    90        `dropdown--type-${type}`,
    91        isTimeRange ? "_range" : "",
    92        { "dropdown--side-arrows": !_.isNil(onArrowClick), "dropdown__focused": this.state.is_focused },
    93        this.props.className,
    94      );
    95      const leftClassName = classNames(
    96        "dropdown__side-arrow",
    97        { "dropdown__side-arrow--disabled": _.includes(disabledArrows, ArrowDirection.LEFT) },
    98      );
    99      const rightClassName = classNames(
   100        "dropdown__side-arrow",
   101        { "dropdown__side-arrow--disabled": _.includes(disabledArrows, ArrowDirection.RIGHT) },
   102      );
   103  
   104      return <div className={className} onClick={this.triggerSelectClick} ref={this.dropdownRef}>
   105        {/* TODO (maxlang): consider moving arrows outside the dropdown component */}
   106        <span
   107          className={leftClassName}
   108          dangerouslySetInnerHTML={trustIcon(leftArrow)}
   109          onClick={() => this.props.onArrowClick(ArrowDirection.LEFT)}>
   110        </span>
   111        <span
   112          className={isTimeRange ? "dropdown__range-title" : "dropdown__title"}
   113          ref={this.titleRef}>
   114            {this.props.title}{this.props.title && !isTimeRange ? ":" : ""}
   115        </span>
   116        {content ? content : <Select
   117          className="dropdown__select"
   118          arrowRenderer={arrowRenderer}
   119          clearable={false}
   120          searchable={false}
   121          options={options}
   122          value={selected}
   123          onChange={onChange}
   124          onFocus={this.onFocus}
   125          onClose={this.onClose}
   126          ref={this.selectRef}
   127        />}
   128        <span
   129          className={rightClassName}
   130          dangerouslySetInnerHTML={trustIcon(rightArrow)}
   131          onClick={() => this.props.onArrowClick(ArrowDirection.RIGHT)}>
   132        </span>
   133      </div>;
   134    }
   135  }