github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/components/dropdown/dropdown.tsx (about) 1 // Copyright 2020 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 React from "react"; 12 import cn from "classnames"; 13 14 import { OutsideEventHandler } from "../outsideEventHandler"; 15 import "./dropdown.styl"; 16 import { Icon } from "antd"; 17 import { Button } from "src/components/button"; 18 19 export interface Item { 20 value: string; 21 name: React.ReactNode | string; 22 } 23 24 export interface DropdownProps { 25 items: Array<Item>; 26 onChange: (item: Item["value"]) => void; 27 children: React.ReactNode; 28 dropdownToggleButton?: () => React.ReactNode; 29 } 30 31 interface DropdownState { 32 isOpen: boolean; 33 } 34 35 interface DropdownButtonProps { 36 children: React.ReactNode; 37 isOpen: boolean; 38 onClick?: (event: React.MouseEvent<HTMLElement>) => void; 39 } 40 41 function DropdownButton(props: DropdownButtonProps) { 42 const { children } = props; 43 return ( 44 <Button 45 type="flat" 46 size="small" 47 iconPosition="right" 48 icon={() => ( 49 <Icon 50 className="collapse-toggle__icon" 51 type="caret-down" /> 52 )} 53 > 54 {children} 55 </Button> 56 ); 57 } 58 59 export class Dropdown extends React.Component<DropdownProps, DropdownState> { 60 state = { 61 isOpen: false, 62 }; 63 64 handleMenuOpen = () => { 65 this.setState({ 66 isOpen: !this.state.isOpen, 67 }); 68 } 69 70 changeMenuState = (nextState: boolean) => { 71 this.setState({ 72 isOpen: nextState, 73 }); 74 } 75 76 handleItemSelection = (value: string) => { 77 this.props.onChange(value); 78 this.handleMenuOpen(); 79 } 80 81 renderDropdownToggleButton = () => { 82 const { children, dropdownToggleButton } = this.props; 83 const { isOpen } = this.state; 84 85 if (dropdownToggleButton) { 86 return dropdownToggleButton(); 87 } else { 88 return ( 89 <DropdownButton 90 isOpen={isOpen} 91 > 92 {children} 93 </DropdownButton> 94 ); 95 } 96 } 97 98 render() { 99 const { items } = this.props; 100 const { isOpen } = this.state; 101 102 const menuStyles = cn( 103 "crl-dropdown__menu", 104 { 105 "crl-dropdown__menu--open": isOpen, 106 }, 107 ); 108 109 const menuItems = items.map((menuItem, idx) => ( 110 <DropdownItem 111 value={menuItem.value} 112 onClick={this.handleItemSelection} 113 key={idx} 114 > 115 { menuItem.name } 116 </DropdownItem> 117 )); 118 119 return ( 120 <div className="crl-dropdown"> 121 <OutsideEventHandler 122 onOutsideClick={() => this.changeMenuState(false)} 123 > 124 <div 125 className="crl-dropdown__handler" 126 onClick={this.handleMenuOpen}> 127 { this.renderDropdownToggleButton() } 128 </div> 129 <div className="crl-dropdown__overlay"> 130 <div className={menuStyles}> 131 <div className="crl-dropdown__container"> 132 {menuItems} 133 </div> 134 </div> 135 </div> 136 </OutsideEventHandler> 137 </div> 138 ); 139 } 140 } 141 142 export interface DropdownItemProps { 143 children: React.ReactNode; 144 value: string; 145 onClick: (value: string) => void; 146 } 147 148 export function DropdownItem(props: DropdownItemProps) { 149 const { children, value, onClick } = props; 150 return ( 151 <div 152 onClick={() => onClick(value)} 153 className="crl-dropdown__item" 154 > 155 { children } 156 </div> 157 ); 158 }