dropdownLink.tsx 3.5 KB

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