dropdownLink.tsx 3.5 KB

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