dropdownBubble.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import {forwardRef} from 'react';
  2. import isPropValid from '@emotion/is-prop-valid';
  3. import {css, Theme} from '@emotion/react';
  4. import styled from '@emotion/styled';
  5. import space from 'sentry/styles/space';
  6. import PanelProvider from 'sentry/utils/panelProvider';
  7. import SettingsHeader from 'sentry/views/settings/components/settingsHeader';
  8. interface DropdownBubbleProps extends React.HTMLAttributes<HTMLDivElement> {
  9. /**
  10. * Menu alignment
  11. */
  12. alignMenu: 'left' | 'right';
  13. /**
  14. * If this is true, will make a single corner blended with actor (depends on anchor orientation)
  15. */
  16. blendCorner: boolean;
  17. /**
  18. * If this is true, will make corners blend with its opener (so no border radius)
  19. */
  20. blendWithActor?: boolean;
  21. /**
  22. * If true, the menu will be visually detached from actor.
  23. */
  24. detached?: boolean;
  25. /**
  26. * The width of the menu
  27. */
  28. width?: string;
  29. }
  30. /**
  31. * If `blendCorner` is false, then we apply border-radius to all corners
  32. *
  33. * Otherwise apply radius to opposite side of `alignMenu` *unless it is fixed width*
  34. */
  35. const getMenuBorderRadius = ({
  36. blendWithActor,
  37. blendCorner,
  38. detached,
  39. alignMenu,
  40. width,
  41. theme,
  42. }: DropdownBubbleProps & {theme: Theme}) => {
  43. const radius = theme.panelBorderRadius;
  44. if (!blendCorner || detached) {
  45. return css`
  46. border-radius: ${radius};
  47. `;
  48. }
  49. // If menu width is the same width as the control
  50. const isFullWidth = width === '100%';
  51. // No top border radius if widths match
  52. const hasTopLeftRadius = !blendWithActor && !isFullWidth && alignMenu !== 'left';
  53. const hasTopRightRadius = !blendWithActor && !isFullWidth && !hasTopLeftRadius;
  54. return css`
  55. border-radius: ${hasTopLeftRadius ? radius : 0} ${hasTopRightRadius ? radius : 0}
  56. ${radius} ${radius};
  57. `;
  58. };
  59. const DropdownBubble = styled(
  60. forwardRef<HTMLDivElement, DropdownBubbleProps>(
  61. ({children, ...props}, forwardedRef) => (
  62. <div ref={forwardedRef} {...props}>
  63. <PanelProvider>{children}</PanelProvider>
  64. </div>
  65. )
  66. ),
  67. {shouldForwardProp: prop => typeof prop === 'string' && isPropValid(prop)}
  68. )`
  69. background: ${p => p.theme.background};
  70. color: ${p => p.theme.textColor};
  71. border: 1px solid ${p => p.theme.border};
  72. position: absolute;
  73. right: 0;
  74. ${p => (p.width ? `width: ${p.width}` : '')};
  75. ${p => (p.alignMenu === 'left' ? 'left: 0;' : '')};
  76. ${p =>
  77. p.detached
  78. ? `
  79. top: 100%;
  80. margin-top: ${space(1)};
  81. box-shadow: ${p.theme.dropShadowHeavy};
  82. `
  83. : `
  84. top: calc(100% - 1px);
  85. box-shadow: ${p.theme.dropShadowMedium};
  86. `};
  87. ${getMenuBorderRadius};
  88. /* This is needed to be able to cover e.g. pagination buttons, but also be
  89. * below dropdown actor button's zindex */
  90. z-index: ${p => p.theme.zIndex.dropdownAutocomplete.menu};
  91. ${SettingsHeader} & {
  92. z-index: ${p => p.theme.zIndex.dropdownAutocomplete.menu + 2};
  93. }
  94. `;
  95. export default DropdownBubble;