optionSelector.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import React from 'react';
  2. import styled from '@emotion/styled';
  3. import isEqual from 'lodash/isEqual';
  4. import {InlineContainer, SectionHeading} from 'app/components/charts/styles';
  5. import DropdownBubble from 'app/components/dropdownBubble';
  6. import DropdownButton from 'app/components/dropdownButton';
  7. import {DropdownItem} from 'app/components/dropdownControl';
  8. import DropdownMenu from 'app/components/dropdownMenu';
  9. import Tooltip from 'app/components/tooltip';
  10. import space from 'app/styles/space';
  11. import {SelectValue} from 'app/types';
  12. const defaultProps = {
  13. menuWidth: 'auto',
  14. };
  15. type Props = {
  16. options: SelectValue<string>[];
  17. selected: string;
  18. onChange: (value: string) => void;
  19. title: string;
  20. } & typeof defaultProps;
  21. type State = {
  22. menuContainerWidth?: number;
  23. };
  24. class OptionSelector extends React.Component<Props, State> {
  25. static defaultProps = defaultProps;
  26. state: State = {};
  27. componentDidMount() {
  28. this.setMenuContainerWidth();
  29. }
  30. shouldComponentUpdate(nextProps: Props, nextState: State) {
  31. return !isEqual(nextProps, this.props) || !isEqual(nextState, this.state);
  32. }
  33. componentDidUpdate(prevProps: Props) {
  34. if (prevProps.selected !== this.props.selected) {
  35. this.setMenuContainerWidth();
  36. }
  37. }
  38. setMenuContainerWidth() {
  39. const menuContainerWidth = this.menuContainerRef?.current?.offsetWidth;
  40. if (menuContainerWidth) {
  41. this.setState({menuContainerWidth});
  42. }
  43. }
  44. menuContainerRef = React.createRef<HTMLDivElement>();
  45. render() {
  46. const {menuContainerWidth} = this.state;
  47. const {options, onChange, selected, title, menuWidth} = this.props;
  48. const selectedOption = options.find(opt => selected === opt.value) || options[0];
  49. return (
  50. <InlineContainer>
  51. <SectionHeading>{title}</SectionHeading>
  52. <MenuContainer ref={this.menuContainerRef}>
  53. <DropdownMenu alwaysRenderMenu={false}>
  54. {({isOpen, getMenuProps, getActorProps}) => (
  55. <React.Fragment>
  56. <StyledDropdownButton {...getActorProps()} size="zero" isOpen={isOpen}>
  57. {selectedOption.label}
  58. </StyledDropdownButton>
  59. <StyledDropdownBubble
  60. {...getMenuProps()}
  61. alignMenu="right"
  62. width={menuWidth}
  63. minWidth={menuContainerWidth}
  64. isOpen={isOpen}
  65. blendWithActor={false}
  66. blendCorner
  67. >
  68. {options.map(opt => (
  69. <StyledDropdownItem
  70. key={opt.value}
  71. onSelect={onChange}
  72. eventKey={opt.value}
  73. disabled={opt.disabled}
  74. isActive={selected === opt.value}
  75. data-test-id={`option-${opt.value}`}
  76. >
  77. <Tooltip title={opt.tooltip} containerDisplayMode="inline">
  78. {opt.label}
  79. </Tooltip>
  80. </StyledDropdownItem>
  81. ))}
  82. </StyledDropdownBubble>
  83. </React.Fragment>
  84. )}
  85. </DropdownMenu>
  86. </MenuContainer>
  87. </InlineContainer>
  88. );
  89. }
  90. }
  91. const MenuContainer = styled('div')`
  92. display: inline-block;
  93. position: relative;
  94. `;
  95. const StyledDropdownButton = styled(DropdownButton)`
  96. padding: ${space(1)} ${space(2)};
  97. font-weight: normal;
  98. z-index: ${p => (p.isOpen ? p.theme.zIndex.dropdownAutocomplete.actor : 'auto')};
  99. `;
  100. const StyledDropdownBubble = styled(DropdownBubble)<{
  101. isOpen: boolean;
  102. minWidth?: number;
  103. }>`
  104. display: ${p => (p.isOpen ? 'block' : 'none')};
  105. ${p =>
  106. p.minWidth && p.width === 'auto' && `min-width: calc(${p.minWidth}px + ${space(3)})`};
  107. `;
  108. const StyledDropdownItem = styled(DropdownItem)`
  109. line-height: ${p => p.theme.text.lineHeightBody};
  110. white-space: nowrap;
  111. `;
  112. export default OptionSelector;