dropdownLink.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import * as React from 'react';
  2. import {css, withTheme} from '@emotion/react';
  3. import classNames from 'classnames';
  4. import DropdownMenu from 'app/components/dropdownMenu';
  5. import {IconChevron} from 'app/icons';
  6. import {Theme} from 'app/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.focus};
  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.focus};
  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. theme: Theme;
  39. children: React.ReactNode;
  40. title?: React.ReactNode;
  41. customTitle?: React.ReactNode;
  42. /**
  43. * display dropdown caret
  44. */
  45. caret?: boolean;
  46. disabled?: boolean;
  47. /**
  48. * Anchors menu to the right
  49. */
  50. anchorRight?: boolean;
  51. anchorMiddle?: boolean;
  52. /**
  53. * Always render children of dropdown menu, this is included to support menu
  54. * items that open a confirm modal. Otherwise when dropdown menu hides, the
  55. * modal also gets unmounted
  56. */
  57. alwaysRenderMenu?: boolean;
  58. topLevelClasses?: string;
  59. menuClasses?: string;
  60. className?: string;
  61. };
  62. const DropdownLink = withTheme(
  63. ({
  64. anchorRight,
  65. anchorMiddle,
  66. disabled,
  67. title,
  68. customTitle,
  69. caret,
  70. children,
  71. menuClasses,
  72. className,
  73. alwaysRenderMenu,
  74. topLevelClasses,
  75. theme,
  76. ...otherProps
  77. }: Props) => (
  78. <DropdownMenu alwaysRenderMenu={alwaysRenderMenu} {...otherProps}>
  79. {({isOpen, getRootProps, getActorProps, getMenuProps}) => {
  80. const shouldRenderMenu = alwaysRenderMenu || isOpen;
  81. const cx = classNames('dropdown-actor', className, {
  82. 'dropdown-menu-right': anchorRight,
  83. 'dropdown-toggle': true,
  84. hover: isOpen,
  85. disabled,
  86. });
  87. const topLevelCx = classNames('dropdown', topLevelClasses, {
  88. 'pull-right': anchorRight,
  89. 'anchor-right': anchorRight,
  90. 'anchor-middle': anchorMiddle,
  91. open: isOpen,
  92. });
  93. return (
  94. <span
  95. css={getRootCss(theme)}
  96. {...getRootProps({
  97. className: topLevelCx,
  98. })}
  99. >
  100. <a
  101. {...getActorProps({
  102. className: cx,
  103. })}
  104. >
  105. {customTitle || (
  106. <div className="dropdown-actor-title">
  107. {title}
  108. {caret && <IconChevron direction={isOpen ? 'up' : 'down'} size="xs" />}
  109. </div>
  110. )}
  111. </a>
  112. {shouldRenderMenu && (
  113. <ul
  114. {...getMenuProps({
  115. className: classNames(menuClasses, 'dropdown-menu'),
  116. })}
  117. >
  118. {children}
  119. </ul>
  120. )}
  121. </span>
  122. );
  123. }}
  124. </DropdownMenu>
  125. )
  126. );
  127. DropdownLink.defaultProps = {
  128. alwaysRenderMenu: true,
  129. disabled: false,
  130. anchorRight: false,
  131. caret: true,
  132. };
  133. DropdownLink.displayName = 'DropdownLink';
  134. export default DropdownLink;