optionSelector.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import {Component, createRef, Fragment} 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 FeatureBadge from 'app/components/featureBadge';
  10. import Tooltip from 'app/components/tooltip';
  11. import Truncate from 'app/components/truncate';
  12. import overflowEllipsis from 'app/styles/overflowEllipsis';
  13. import space from 'app/styles/space';
  14. import {SelectValue} from 'app/types';
  15. import {defined} from 'app/utils';
  16. const defaultProps = {
  17. menuWidth: 'auto',
  18. };
  19. type Props = {
  20. options: SelectValue<string>[];
  21. selected: string;
  22. onChange: (value: string) => void;
  23. title: string;
  24. featureType?: 'alpha' | 'beta' | 'new';
  25. } & typeof defaultProps;
  26. type State = {
  27. menuContainerWidth?: number;
  28. };
  29. class OptionSelector extends Component<Props, State> {
  30. static defaultProps = defaultProps;
  31. state: State = {};
  32. componentDidMount() {
  33. this.setMenuContainerWidth();
  34. }
  35. shouldComponentUpdate(nextProps: Props, nextState: State) {
  36. return !isEqual(nextProps, this.props) || !isEqual(nextState, this.state);
  37. }
  38. componentDidUpdate(prevProps: Props) {
  39. if (prevProps.selected !== this.props.selected) {
  40. this.setMenuContainerWidth();
  41. }
  42. }
  43. setMenuContainerWidth() {
  44. const menuContainerWidth = this.menuContainerRef?.current?.offsetWidth;
  45. if (menuContainerWidth) {
  46. this.setState({menuContainerWidth});
  47. }
  48. }
  49. menuContainerRef = createRef<HTMLDivElement>();
  50. render() {
  51. const {menuContainerWidth} = this.state;
  52. const {options, onChange, selected, title, menuWidth, featureType} = this.props;
  53. const selectedOption = options.find(opt => selected === opt.value) || options[0];
  54. return (
  55. <InlineContainer>
  56. <SectionHeading>
  57. {title}
  58. {defined(featureType) ? <StyledFeatureBadge type={featureType} /> : null}
  59. </SectionHeading>
  60. <MenuContainer ref={this.menuContainerRef}>
  61. <DropdownMenu alwaysRenderMenu={false}>
  62. {({isOpen, getMenuProps, getActorProps}) => (
  63. <Fragment>
  64. <StyledDropdownButton {...getActorProps()} size="zero" isOpen={isOpen}>
  65. <TruncatedLabel>{String(selectedOption.label)}</TruncatedLabel>
  66. </StyledDropdownButton>
  67. <StyledDropdownBubble
  68. {...getMenuProps()}
  69. alignMenu="right"
  70. width={menuWidth}
  71. minWidth={menuContainerWidth}
  72. isOpen={isOpen}
  73. blendWithActor={false}
  74. blendCorner
  75. >
  76. {options.map(opt => (
  77. <StyledDropdownItem
  78. key={opt.value}
  79. onSelect={onChange}
  80. eventKey={opt.value}
  81. disabled={opt.disabled}
  82. isActive={selected === opt.value}
  83. data-test-id={`option-${opt.value}`}
  84. >
  85. <Tooltip title={opt.tooltip} containerDisplayMode="inline">
  86. <StyledTruncate
  87. isActive={selected === opt.value}
  88. value={String(opt.label)}
  89. maxLength={60}
  90. expandDirection="left"
  91. />
  92. </Tooltip>
  93. </StyledDropdownItem>
  94. ))}
  95. </StyledDropdownBubble>
  96. </Fragment>
  97. )}
  98. </DropdownMenu>
  99. </MenuContainer>
  100. </InlineContainer>
  101. );
  102. }
  103. }
  104. const TruncatedLabel = styled('span')`
  105. ${overflowEllipsis};
  106. max-width: 400px;
  107. `;
  108. const StyledTruncate = styled(Truncate)<{
  109. isActive: boolean;
  110. }>`
  111. & span {
  112. ${p =>
  113. p.isActive &&
  114. `
  115. color: ${p.theme.white};
  116. background: ${p.theme.active};
  117. border: none;
  118. `}
  119. }
  120. `;
  121. const MenuContainer = styled('div')`
  122. display: inline-block;
  123. position: relative;
  124. `;
  125. const StyledDropdownButton = styled(DropdownButton)`
  126. padding: ${space(1)} ${space(2)};
  127. font-weight: normal;
  128. z-index: ${p => (p.isOpen ? p.theme.zIndex.dropdownAutocomplete.actor : 'auto')};
  129. `;
  130. const StyledDropdownBubble = styled(DropdownBubble)<{
  131. isOpen: boolean;
  132. minWidth?: number;
  133. }>`
  134. display: ${p => (p.isOpen ? 'block' : 'none')};
  135. overflow: visible;
  136. ${p =>
  137. p.minWidth && p.width === 'auto' && `min-width: calc(${p.minWidth}px + ${space(3)})`};
  138. `;
  139. const StyledDropdownItem = styled(DropdownItem)`
  140. line-height: ${p => p.theme.text.lineHeightBody};
  141. white-space: nowrap;
  142. `;
  143. const StyledFeatureBadge = styled(FeatureBadge)`
  144. margin-left: 0px;
  145. `;
  146. export default OptionSelector;