dropdownLink.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import * as React from 'react';
  2. import {css, useTheme} from '@emotion/react';
  3. import classNames from 'classnames';
  4. import DropdownMenu from 'sentry/components/dropdownMenu';
  5. import {IconChevron} from 'sentry/icons';
  6. import {Theme} from 'sentry/utils/theme';
  7. const getRootCss = (theme: Theme) => css`
  8. .dropdown-menu {
  9. & > li > a {
  10. color: ${theme.textColor};
  11. &:hover,
  12. &:focus {
  13. color: inherit;
  14. background-color: ${theme.hover};
  15. }
  16. }
  17. & .disabled {
  18. cursor: not-allowed;
  19. &:hover {
  20. background: inherit;
  21. color: inherit;
  22. }
  23. }
  24. }
  25. .dropdown-submenu:hover > span {
  26. color: ${theme.textColor};
  27. background: ${theme.hover};
  28. }
  29. `;
  30. // .dropdown-actor-title = flexbox to fix vertical alignment on firefox Need
  31. // the extra container because dropdown-menu alignment is off if
  32. // `dropdown-actor` is a flexbox
  33. type Props = Omit<
  34. Omit<DropdownMenu['props'], 'children'>,
  35. keyof typeof DropdownMenu.defaultProps
  36. > &
  37. Partial<typeof DropdownMenu.defaultProps> & {
  38. children: React.ReactNode;
  39. /**
  40. * Always render children of dropdown menu, this is included to support menu
  41. * items that open a confirm modal. Otherwise when dropdown menu hides, the
  42. * modal also gets unmounted
  43. */
  44. alwaysRenderMenu?: boolean;
  45. anchorMiddle?: boolean;
  46. /**
  47. * Anchors menu to the right
  48. */
  49. anchorRight?: boolean;
  50. /**
  51. * display dropdown caret
  52. */
  53. caret?: boolean;
  54. className?: string;
  55. customTitle?: React.ReactNode;
  56. disabled?: boolean;
  57. menuClasses?: string;
  58. title?: React.ReactNode;
  59. topLevelClasses?: string;
  60. };
  61. function DropdownLink({
  62. anchorMiddle,
  63. title,
  64. customTitle,
  65. children,
  66. menuClasses,
  67. className,
  68. topLevelClasses,
  69. anchorRight = false,
  70. disabled = false,
  71. caret = true,
  72. alwaysRenderMenu = true,
  73. ...otherProps
  74. }: Props) {
  75. const theme = useTheme();
  76. return (
  77. <DropdownMenu alwaysRenderMenu={alwaysRenderMenu} {...otherProps}>
  78. {({isOpen, getRootProps, getActorProps, getMenuProps}) => {
  79. const shouldRenderMenu = alwaysRenderMenu || isOpen;
  80. const cx = classNames('dropdown-actor', className, {
  81. 'dropdown-menu-right': anchorRight,
  82. 'dropdown-toggle': true,
  83. hover: isOpen,
  84. disabled,
  85. });
  86. const topLevelCx = classNames('dropdown', topLevelClasses, {
  87. 'pull-right': anchorRight,
  88. 'anchor-right': anchorRight,
  89. 'anchor-middle': anchorMiddle,
  90. open: isOpen,
  91. });
  92. const {onClick: onClickActor, ...actorProps} = getActorProps({
  93. className: cx,
  94. });
  95. return (
  96. <span
  97. css={getRootCss(theme)}
  98. {...getRootProps({
  99. className: topLevelCx,
  100. })}
  101. >
  102. <a onClick={disabled ? undefined : onClickActor} {...actorProps}>
  103. {customTitle || (
  104. <div className="dropdown-actor-title">
  105. {title}
  106. {caret && <IconChevron direction={isOpen ? 'up' : 'down'} size="xs" />}
  107. </div>
  108. )}
  109. </a>
  110. {shouldRenderMenu && (
  111. <ul
  112. {...getMenuProps({
  113. className: classNames(menuClasses, 'dropdown-menu'),
  114. })}
  115. >
  116. {children}
  117. </ul>
  118. )}
  119. </span>
  120. );
  121. }}
  122. </DropdownMenu>
  123. );
  124. }
  125. export default DropdownLink;