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 }