profilingContextMenu.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import {forwardRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import {IconCheckmark} from 'sentry/icons';
  4. import space from 'sentry/styles/space';
  5. interface MenuProps
  6. extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
  7. children: React.ReactNode;
  8. }
  9. const Menu = styled(
  10. forwardRef((props: MenuProps, ref: React.Ref<HTMLDivElement> | undefined) => {
  11. return <div ref={ref} role="menu" {...props} />;
  12. })
  13. )`
  14. position: absolute;
  15. font-size: ${p => p.theme.fontSizeMedium};
  16. z-index: ${p => p.theme.zIndex.dropdown};
  17. background: ${p => p.theme.backgroundElevated};
  18. border: 1px solid ${p => p.theme.border};
  19. border-radius: ${p => p.theme.borderRadius};
  20. box-shadow: ${p => p.theme.dropShadowHeavy};
  21. width: auto;
  22. min-width: 164px;
  23. overflow: auto;
  24. padding-bottom: ${space(0.5)};
  25. `;
  26. export {Menu as ProfilingContextMenu};
  27. const MenuContentContainer = styled('div')`
  28. cursor: pointer;
  29. display: flex;
  30. align-items: center;
  31. font-weight: normal;
  32. padding: 0 ${space(1)};
  33. border-radius: ${p => p.theme.borderRadius};
  34. box-sizing: border-box;
  35. background: ${p => (p.tabIndex === 0 ? p.theme.hover : undefined)};
  36. &:focus {
  37. color: ${p => p.theme.textColor};
  38. background: ${p => p.theme.hover};
  39. outline: none;
  40. }
  41. `;
  42. const MenuItemCheckboxLabel = styled('label')`
  43. display: flex;
  44. align-items: center;
  45. font-weight: normal;
  46. margin: 0;
  47. cursor: pointer;
  48. flex: 1 1 100%;
  49. `;
  50. interface MenuItemCheckboxProps
  51. extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
  52. checked?: boolean;
  53. }
  54. const MenuItemCheckbox = forwardRef(
  55. (props: MenuItemCheckboxProps, ref: React.Ref<HTMLDivElement> | undefined) => {
  56. const {children, checked, ...rest} = props;
  57. return (
  58. <MenuContentOuterContainer>
  59. <MenuContentContainer ref={ref} role="menuitem" {...rest}>
  60. <MenuItemCheckboxLabel>
  61. <MenuLeadingItem>
  62. <Input type="checkbox" checked={checked} onChange={() => void 0} />
  63. <IconCheckmark />
  64. </MenuLeadingItem>
  65. <MenuContent>{children}</MenuContent>
  66. </MenuItemCheckboxLabel>
  67. </MenuContentContainer>
  68. </MenuContentOuterContainer>
  69. );
  70. }
  71. );
  72. export {MenuItemCheckbox as ProfilingContextMenuItemCheckbox};
  73. const MenuLeadingItem = styled('div')`
  74. display: flex;
  75. align-items: center;
  76. height: 1.4em;
  77. width: 1em;
  78. gap: ${space(1)};
  79. padding: ${space(1)} 0;
  80. position: relative;
  81. `;
  82. const MenuContent = styled('div')`
  83. position: relative;
  84. width: 100%;
  85. display: flex;
  86. gap: ${space(0.5)};
  87. justify-content: space-between;
  88. padding: ${space(0.5)} 0;
  89. margin-left: ${space(0.5)};
  90. text-transform: capitalize;
  91. margin-bottom: 0;
  92. line-height: 1.4;
  93. white-space: nowrap;
  94. overflow: hidden;
  95. text-overflow: ellipsis;
  96. `;
  97. const Input = styled('input')`
  98. position: absolute;
  99. opacity: 0;
  100. cursor: pointer;
  101. height: 0;
  102. padding-right: ${space(1)};
  103. & + svg {
  104. position: absolute;
  105. left: 50%;
  106. top: 50%;
  107. transform: translate(-50%, -50%);
  108. width: 1em;
  109. height: 1.4em;
  110. display: none;
  111. }
  112. &:checked + svg {
  113. display: block;
  114. }
  115. `;
  116. interface MenuItemProps
  117. extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
  118. children: React.ReactNode;
  119. }
  120. const MenuItem = styled(
  121. forwardRef((props: MenuItemProps, ref: React.Ref<HTMLDivElement> | undefined) => {
  122. const {children, ...rest} = props;
  123. return (
  124. <MenuContentOuterContainer>
  125. <MenuContentContainer ref={ref} role="menuitem" {...rest}>
  126. <MenuContent>{children}</MenuContent>
  127. </MenuContentContainer>
  128. </MenuContentOuterContainer>
  129. );
  130. })
  131. )`
  132. cursor: pointer;
  133. color: ${p => p.theme.textColor};
  134. background: transparent;
  135. padding: 0 ${space(0.5)};
  136. &:focus {
  137. outline: none;
  138. }
  139. &:active: {
  140. background: transparent;
  141. }
  142. `;
  143. export {MenuItem as ProfilingContextMenuItem};
  144. const MenuContentOuterContainer = styled('div')`
  145. padding: 0 ${space(0.5)};
  146. `;
  147. const MenuGroup = styled('div')`
  148. padding-top: 0;
  149. padding-bottom: ${space(1)};
  150. &:last-of-type {
  151. padding-bottom: 0;
  152. }
  153. `;
  154. export {MenuGroup as ProfilingContextMenuGroup};
  155. interface MenuHeadingProps
  156. extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
  157. children: React.ReactNode;
  158. }
  159. const MenuHeading = styled((props: MenuHeadingProps) => {
  160. const {children, ...rest} = props;
  161. return <div {...rest}>{children}</div>;
  162. })`
  163. text-transform: uppercase;
  164. line-height: 1.5;
  165. font-weight: 600;
  166. color: ${p => p.theme.subText};
  167. margin-bottom: 0;
  168. cursor: default;
  169. font-size: 75%;
  170. padding: ${space(0.5)} ${space(1.5)};
  171. `;
  172. export {MenuHeading as ProfilingContextMenuHeading};
  173. const Layer = styled('div')`
  174. width: 100%;
  175. height: 100%;
  176. position: absolute;
  177. left: 0;
  178. top: 0;
  179. z-index: ${p => p.theme.zIndex.dropdown - 1};
  180. `;
  181. export {Layer as ProfilingContextMenuLayer};