optionSelector.tsx 4.6 KB

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