Select.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. // Libraries
  2. import classNames from 'classnames';
  3. import React, { PureComponent } from 'react';
  4. // Ignoring because I couldn't get @types/react-select work wih Torkel's fork
  5. // @ts-ignore
  6. import { default as ReactSelect } from '@torkelo/react-select';
  7. // @ts-ignore
  8. import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
  9. // @ts-ignore
  10. import { components } from '@torkelo/react-select';
  11. // Components
  12. import { SelectOption, SingleValue } from './SelectOption';
  13. import SelectOptionGroup from './SelectOptionGroup';
  14. import IndicatorsContainer from './IndicatorsContainer';
  15. import NoOptionsMessage from './NoOptionsMessage';
  16. import resetSelectStyles from './resetSelectStyles';
  17. import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
  18. import { PopperContent } from '../Tooltip/PopperController';
  19. import { Tooltip } from '../Tooltip/Tooltip';
  20. export interface SelectOptionItem<T> {
  21. label?: string;
  22. value?: T;
  23. imgUrl?: string;
  24. description?: string;
  25. [key: string]: any;
  26. }
  27. export interface CommonProps<T> {
  28. defaultValue?: any;
  29. getOptionLabel?: (item: SelectOptionItem<T>) => string;
  30. getOptionValue?: (item: SelectOptionItem<T>) => string;
  31. onChange: (item: SelectOptionItem<T>) => {} | void;
  32. placeholder?: string;
  33. width?: number;
  34. value?: SelectOptionItem<T>;
  35. className?: string;
  36. isDisabled?: boolean;
  37. isSearchable?: boolean;
  38. isClearable?: boolean;
  39. autoFocus?: boolean;
  40. openMenuOnFocus?: boolean;
  41. onBlur?: () => void;
  42. maxMenuHeight?: number;
  43. isLoading?: boolean;
  44. noOptionsMessage?: () => string;
  45. isMulti?: boolean;
  46. backspaceRemovesValue?: boolean;
  47. isOpen?: boolean;
  48. components?: any;
  49. tooltipContent?: PopperContent<any>;
  50. onOpenMenu?: () => void;
  51. onCloseMenu?: () => void;
  52. }
  53. export interface SelectProps<T> extends CommonProps<T> {
  54. options: Array<SelectOptionItem<T>>;
  55. }
  56. interface AsyncProps<T> extends CommonProps<T> {
  57. defaultOptions: boolean;
  58. loadOptions: (query: string) => Promise<Array<SelectOptionItem<T>>>;
  59. loadingMessage?: () => string;
  60. }
  61. const wrapInTooltip = (
  62. component: React.ReactElement,
  63. tooltipContent: PopperContent<any> | undefined,
  64. isMenuOpen: boolean | undefined
  65. ) => {
  66. const showTooltip = isMenuOpen ? false : undefined;
  67. if (tooltipContent) {
  68. return (
  69. <Tooltip show={showTooltip} content={tooltipContent} placement="bottom">
  70. <div>
  71. {/* div needed for tooltip */}
  72. {component}
  73. </div>
  74. </Tooltip>
  75. );
  76. } else {
  77. return <div>{component}</div>;
  78. }
  79. };
  80. export const MenuList = (props: any) => {
  81. return (
  82. <components.MenuList {...props}>
  83. <CustomScrollbar autoHide={false} autoHeightMax="inherit">
  84. {props.children}
  85. </CustomScrollbar>
  86. </components.MenuList>
  87. );
  88. };
  89. export class Select<T> extends PureComponent<SelectProps<T>> {
  90. static defaultProps: Partial<SelectProps<any>> = {
  91. className: '',
  92. isDisabled: false,
  93. isSearchable: true,
  94. isClearable: false,
  95. isMulti: false,
  96. openMenuOnFocus: false,
  97. autoFocus: false,
  98. isLoading: false,
  99. backspaceRemovesValue: true,
  100. maxMenuHeight: 300,
  101. components: {
  102. Option: SelectOption,
  103. SingleValue,
  104. IndicatorsContainer,
  105. MenuList,
  106. Group: SelectOptionGroup,
  107. },
  108. };
  109. onOpenMenu = () => {
  110. const { onOpenMenu } = this.props;
  111. if (onOpenMenu) {
  112. onOpenMenu();
  113. }
  114. };
  115. onCloseMenu = () => {
  116. const { onCloseMenu } = this.props;
  117. if (onCloseMenu) {
  118. onCloseMenu();
  119. }
  120. };
  121. render() {
  122. const {
  123. defaultValue,
  124. getOptionLabel,
  125. getOptionValue,
  126. onChange,
  127. options,
  128. placeholder,
  129. width,
  130. value,
  131. className,
  132. isDisabled,
  133. isLoading,
  134. isSearchable,
  135. isClearable,
  136. backspaceRemovesValue,
  137. isMulti,
  138. autoFocus,
  139. openMenuOnFocus,
  140. onBlur,
  141. maxMenuHeight,
  142. noOptionsMessage,
  143. isOpen,
  144. components,
  145. tooltipContent,
  146. } = this.props;
  147. let widthClass = '';
  148. if (width) {
  149. widthClass = 'width-' + width;
  150. }
  151. const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
  152. const selectComponents = { ...Select.defaultProps.components, ...components };
  153. return wrapInTooltip(
  154. <ReactSelect
  155. classNamePrefix="gf-form-select-box"
  156. className={selectClassNames}
  157. components={selectComponents}
  158. defaultValue={defaultValue}
  159. value={value}
  160. getOptionLabel={getOptionLabel}
  161. getOptionValue={getOptionValue}
  162. menuShouldScrollIntoView={false}
  163. isSearchable={isSearchable}
  164. onChange={onChange}
  165. options={options}
  166. placeholder={placeholder || 'Choose'}
  167. styles={resetSelectStyles()}
  168. isDisabled={isDisabled}
  169. isLoading={isLoading}
  170. isClearable={isClearable}
  171. autoFocus={autoFocus}
  172. onBlur={onBlur}
  173. openMenuOnFocus={openMenuOnFocus}
  174. maxMenuHeight={maxMenuHeight}
  175. noOptionsMessage={noOptionsMessage}
  176. isMulti={isMulti}
  177. backspaceRemovesValue={backspaceRemovesValue}
  178. menuIsOpen={isOpen}
  179. onMenuOpen={this.onOpenMenu}
  180. onMenuClose={this.onCloseMenu}
  181. />,
  182. tooltipContent,
  183. isOpen
  184. );
  185. }
  186. }
  187. export class AsyncSelect<T> extends PureComponent<AsyncProps<T>> {
  188. static defaultProps: Partial<AsyncProps<any>> = {
  189. className: '',
  190. components: {},
  191. loadingMessage: () => 'Loading...',
  192. isDisabled: false,
  193. isClearable: false,
  194. isMulti: false,
  195. isSearchable: true,
  196. backspaceRemovesValue: true,
  197. autoFocus: false,
  198. openMenuOnFocus: false,
  199. maxMenuHeight: 300,
  200. };
  201. render() {
  202. const {
  203. defaultValue,
  204. getOptionLabel,
  205. getOptionValue,
  206. onChange,
  207. placeholder,
  208. width,
  209. value,
  210. className,
  211. loadOptions,
  212. defaultOptions,
  213. isLoading,
  214. loadingMessage,
  215. noOptionsMessage,
  216. isDisabled,
  217. isSearchable,
  218. isClearable,
  219. backspaceRemovesValue,
  220. autoFocus,
  221. onBlur,
  222. openMenuOnFocus,
  223. maxMenuHeight,
  224. isMulti,
  225. tooltipContent,
  226. } = this.props;
  227. let widthClass = '';
  228. if (width) {
  229. widthClass = 'width-' + width;
  230. }
  231. const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
  232. return wrapInTooltip(
  233. <ReactAsyncSelect
  234. classNamePrefix="gf-form-select-box"
  235. className={selectClassNames}
  236. components={{
  237. Option: SelectOption,
  238. SingleValue,
  239. IndicatorsContainer,
  240. NoOptionsMessage,
  241. }}
  242. defaultValue={defaultValue}
  243. value={value}
  244. getOptionLabel={getOptionLabel}
  245. getOptionValue={getOptionValue}
  246. menuShouldScrollIntoView={false}
  247. onChange={onChange}
  248. loadOptions={loadOptions}
  249. isLoading={isLoading}
  250. defaultOptions={defaultOptions}
  251. placeholder={placeholder || 'Choose'}
  252. styles={resetSelectStyles()}
  253. loadingMessage={loadingMessage}
  254. noOptionsMessage={noOptionsMessage}
  255. isDisabled={isDisabled}
  256. isSearchable={isSearchable}
  257. isClearable={isClearable}
  258. autoFocus={autoFocus}
  259. onBlur={onBlur}
  260. openMenuOnFocus={openMenuOnFocus}
  261. maxMenuHeight={maxMenuHeight}
  262. isMulti={isMulti}
  263. backspaceRemovesValue={backspaceRemovesValue}
  264. />,
  265. tooltipContent,
  266. false
  267. );
  268. }
  269. }
  270. export default Select;