breadcrumbDropdown.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import {Component} from 'react';
  2. import DropdownAutoCompleteMenu from 'sentry/components/dropdownAutoComplete/menu';
  3. import {Item} from 'sentry/components/dropdownAutoComplete/types';
  4. import Crumb from 'sentry/views/settings/components/settingsBreadcrumb/crumb';
  5. import Divider from 'sentry/views/settings/components/settingsBreadcrumb/divider';
  6. import {RouteWithName} from './types';
  7. const EXIT_DELAY = 0;
  8. interface AdditionalDropdownProps
  9. extends Pick<
  10. React.ComponentProps<typeof DropdownAutoCompleteMenu>,
  11. 'onChange' | 'busyItemsStillVisible'
  12. > {}
  13. export interface BreadcrumbDropdownProps extends AdditionalDropdownProps {
  14. items: Item[];
  15. name: React.ReactNode;
  16. onSelect: (item: Item) => void;
  17. route: RouteWithName;
  18. enterDelay?: number;
  19. hasMenu?: boolean;
  20. isLast?: boolean;
  21. }
  22. type State = {
  23. isOpen: boolean;
  24. };
  25. class BreadcrumbDropdown extends Component<BreadcrumbDropdownProps, State> {
  26. state: State = {
  27. isOpen: false,
  28. };
  29. componentWillUnmount() {
  30. window.clearTimeout(this.enteringTimeout);
  31. window.clearTimeout(this.leavingTimeout);
  32. }
  33. enteringTimeout: number | undefined = undefined;
  34. leavingTimeout: number | undefined = undefined;
  35. open = () => {
  36. this.setState({isOpen: true});
  37. };
  38. close = () => {
  39. this.setState({isOpen: false});
  40. };
  41. handleStateChange = () => {};
  42. // Adds a delay when mouse hovers on actor (in this case the breadcrumb)
  43. handleMouseEnterActor = () => {
  44. window.clearTimeout(this.leavingTimeout);
  45. window.clearTimeout(this.enteringTimeout);
  46. this.enteringTimeout = window.setTimeout(
  47. () => this.open(),
  48. this.props.enterDelay ?? 0
  49. );
  50. };
  51. // handles mouseEnter event on actor and menu, should clear the leaving timeout and keep menu open
  52. handleMouseEnter = () => {
  53. window.clearTimeout(this.leavingTimeout);
  54. window.clearTimeout(this.enteringTimeout);
  55. this.open();
  56. };
  57. // handles mouseLeave event on actor and menu, adds a timeout before updating state to account for
  58. // mouseLeave into
  59. handleMouseLeave = () => {
  60. window.clearTimeout(this.enteringTimeout);
  61. this.leavingTimeout = window.setTimeout(() => this.close(), EXIT_DELAY);
  62. };
  63. // Close immediately when actor is clicked clicked
  64. handleClickActor = () => {
  65. this.close();
  66. };
  67. // Close immediately when clicked outside
  68. handleClose = () => {
  69. this.close();
  70. };
  71. render() {
  72. const {hasMenu, route, isLast, name, items, onSelect, ...dropdownProps} = this.props;
  73. return (
  74. <DropdownAutoCompleteMenu
  75. blendCorner={false}
  76. onOpen={this.handleMouseEnter}
  77. onClose={this.close}
  78. isOpen={this.state.isOpen}
  79. menuProps={{
  80. onMouseEnter: this.handleMouseEnter,
  81. onMouseLeave: this.handleMouseLeave,
  82. }}
  83. items={items}
  84. onSelect={onSelect}
  85. virtualizedHeight={41}
  86. {...dropdownProps}
  87. >
  88. {({getActorProps, actions, isOpen}) => (
  89. <Crumb
  90. {...getActorProps({
  91. onClick: this.handleClickActor.bind(this, actions),
  92. onMouseEnter: this.handleMouseEnterActor.bind(this, actions),
  93. onMouseLeave: this.handleMouseLeave.bind(this, actions),
  94. })}
  95. >
  96. <span>{name || route.name} </span>
  97. <Divider isHover={hasMenu && isOpen} isLast={isLast} />
  98. </Crumb>
  99. )}
  100. </DropdownAutoCompleteMenu>
  101. );
  102. }
  103. }
  104. export default BreadcrumbDropdown;