dropdownBubble.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import {css} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import {Theme} from 'app/utils/theme';
  4. import SettingsHeader from 'app/views/settings/components/settingsHeader';
  5. type Params = {
  6. /**
  7. * If this is true, will make a single corner blended with actor (depends on anchor orientation)
  8. */
  9. blendCorner: boolean;
  10. /**
  11. * Menu alignment
  12. */
  13. alignMenu: 'left' | 'right';
  14. /**
  15. * The width of the menu
  16. */
  17. width?: string;
  18. /**
  19. * enable the arrow on the menu
  20. */
  21. menuWithArrow?: boolean;
  22. /**
  23. * If this is true, will make corners blend with its opener (so no border radius)
  24. */
  25. blendWithActor?: boolean;
  26. };
  27. /**
  28. * If `blendCorner` is false, then we apply border-radius to all corners
  29. *
  30. * Otherwise apply radius to opposite side of `alignMenu` *unles it is fixed width*
  31. */
  32. const getMenuBorderRadius = ({
  33. blendWithActor,
  34. blendCorner,
  35. alignMenu,
  36. width,
  37. theme,
  38. }: Params & {theme: Theme}) => {
  39. const radius = theme.borderRadius;
  40. if (!blendCorner) {
  41. return css`
  42. border-radius: ${radius};
  43. `;
  44. }
  45. // If menu width is the same width as the control
  46. const isFullWidth = width === '100%';
  47. // No top border radius if widths match
  48. const hasTopLeftRadius = !blendWithActor && !isFullWidth && alignMenu !== 'left';
  49. const hasTopRightRadius = !blendWithActor && !isFullWidth && !hasTopLeftRadius;
  50. return css`
  51. border-radius: ${hasTopLeftRadius ? radius : 0} ${hasTopRightRadius ? radius : 0}
  52. ${radius} ${radius};
  53. `;
  54. };
  55. const getMenuArrow = ({menuWithArrow, alignMenu, theme}: Params & {theme: Theme}) => {
  56. if (!menuWithArrow) {
  57. return '';
  58. }
  59. const alignRight = alignMenu === 'right';
  60. return css`
  61. top: 32px;
  62. &::before {
  63. width: 0;
  64. height: 0;
  65. border-left: 9px solid transparent;
  66. border-right: 9px solid transparent;
  67. border-bottom: 9px solid rgba(52, 60, 69, 0.35);
  68. content: '';
  69. display: block;
  70. position: absolute;
  71. top: -9px;
  72. left: 10px;
  73. z-index: -2;
  74. ${alignRight && 'left: auto;'};
  75. ${alignRight && 'right: 10px;'};
  76. }
  77. &:after {
  78. width: 0;
  79. height: 0;
  80. border-left: 8px solid transparent;
  81. border-right: 8px solid transparent;
  82. border-bottom: 8px solid ${theme.background};
  83. content: '';
  84. display: block;
  85. position: absolute;
  86. top: -8px;
  87. left: 11px;
  88. z-index: -1;
  89. ${alignRight && 'left: auto;'};
  90. ${alignRight && 'right: 11px;'};
  91. }
  92. `;
  93. };
  94. const DropdownBubble = styled('div')<Params>`
  95. background: ${p => p.theme.background};
  96. color: ${p => p.theme.textColor};
  97. border: 1px solid ${p => p.theme.border};
  98. position: absolute;
  99. top: calc(100% - 1px);
  100. ${p => (p.width ? `width: ${p.width}` : '')};
  101. right: 0;
  102. box-shadow: ${p => p.theme.dropShadowLight};
  103. overflow: hidden;
  104. ${getMenuBorderRadius};
  105. ${({alignMenu}) => (alignMenu === 'left' ? 'left: 0;' : '')};
  106. ${getMenuArrow};
  107. /* This is needed to be able to cover e.g. pagination buttons, but also be
  108. * below dropdown actor button's zindex */
  109. z-index: ${p => p.theme.zIndex.dropdownAutocomplete.menu};
  110. ${/* sc-selector */ SettingsHeader} & {
  111. z-index: ${p => p.theme.zIndex.dropdownAutocomplete.menu + 2};
  112. }
  113. `;
  114. export default DropdownBubble;