eventDataSection.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import {css} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import {Button} from 'sentry/components/button';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import {DataSection} from 'sentry/components/events/styles';
  6. import Anchor from 'sentry/components/links/anchor';
  7. import {IconLink} from 'sentry/icons';
  8. import {t} from 'sentry/locale';
  9. import space from 'sentry/styles/space';
  10. type Props = {
  11. children: React.ReactNode;
  12. title: React.ReactNode;
  13. type: string;
  14. actions?: React.ReactNode;
  15. className?: string;
  16. isCentered?: boolean;
  17. raw?: boolean;
  18. showPermalink?: boolean;
  19. toggleRaw?: (enable: boolean) => void;
  20. wrapTitle?: boolean;
  21. };
  22. function scrollToSection(element: HTMLDivElement) {
  23. if (window.location.hash && element) {
  24. const [, hash] = window.location.hash.split('#');
  25. try {
  26. const anchorElement = hash && element.querySelector('div#' + hash);
  27. if (anchorElement) {
  28. anchorElement.scrollIntoView();
  29. }
  30. } catch {
  31. // Since we're blindly taking the hash from the url and shoving
  32. // it into a querySelector, it's possible that this may
  33. // raise an exception if the input is invalid. So let's just ignore
  34. // this instead of blowing up.
  35. // e.g. `document.querySelector('div#=')`
  36. // > Uncaught DOMException: Failed to execute 'querySelector' on 'Document': 'div#=' is not a valid selector.
  37. }
  38. }
  39. }
  40. export function EventDataSection({
  41. children,
  42. className,
  43. type,
  44. title,
  45. toggleRaw,
  46. raw = false,
  47. wrapTitle = true,
  48. actions,
  49. isCentered = false,
  50. showPermalink = true,
  51. ...props
  52. }: Props) {
  53. const titleNode = wrapTitle ? <h3>{title}</h3> : title;
  54. return (
  55. <DataSection ref={scrollToSection} className={className || ''} {...props}>
  56. {title && (
  57. <SectionHeader id={type} isCentered={isCentered}>
  58. <Title>
  59. {showPermalink ? (
  60. <Permalink className="permalink">
  61. <PermalinkAnchor href={`#${type}`}>
  62. <StyledIconLink size="xs" color="subText" />
  63. </PermalinkAnchor>
  64. {titleNode}
  65. </Permalink>
  66. ) : (
  67. titleNode
  68. )}
  69. </Title>
  70. {type === 'extra' && (
  71. <ButtonBar merged active={raw ? 'raw' : 'formatted'}>
  72. <Button barId="formatted" size="xs" onClick={() => toggleRaw?.(false)}>
  73. {t('Formatted')}
  74. </Button>
  75. <Button barId="raw" size="xs" onClick={() => toggleRaw?.(true)}>
  76. {t('Raw')}
  77. </Button>
  78. </ButtonBar>
  79. )}
  80. {actions && <ActionContainer>{actions}</ActionContainer>}
  81. </SectionHeader>
  82. )}
  83. <SectionContents>{children}</SectionContents>
  84. </DataSection>
  85. );
  86. }
  87. const Title = styled('div')`
  88. display: flex;
  89. `;
  90. const Permalink = styled('span')`
  91. width: 100%;
  92. position: relative;
  93. `;
  94. const StyledIconLink = styled(IconLink)`
  95. opacity: 0;
  96. transform: translateY(-1px);
  97. transition: opacity 100ms;
  98. `;
  99. const PermalinkAnchor = styled(Anchor)`
  100. display: flex;
  101. align-items: center;
  102. position: absolute;
  103. top: 0;
  104. left: 0;
  105. width: calc(100% + ${space(3)});
  106. height: 100%;
  107. padding-left: ${space(0.5)};
  108. transform: translateX(-${space(3)});
  109. :hover ${StyledIconLink}, :focus ${StyledIconLink} {
  110. opacity: 1;
  111. }
  112. `;
  113. const SectionHeader = styled('div')<{isCentered?: boolean}>`
  114. display: flex;
  115. flex-wrap: wrap;
  116. align-items: center;
  117. margin-bottom: ${space(1)};
  118. & h3,
  119. & h3 a {
  120. color: ${p => p.theme.subText};
  121. font-size: ${p => p.theme.fontSizeMedium};
  122. font-weight: 600;
  123. }
  124. & h3 {
  125. padding: ${space(0.75)} 0;
  126. margin-bottom: 0;
  127. }
  128. & small {
  129. color: ${p => p.theme.textColor};
  130. font-size: ${p => p.theme.fontSizeMedium};
  131. margin-right: ${space(0.5)};
  132. margin-left: ${space(0.5)};
  133. text-transform: none;
  134. }
  135. & small > span {
  136. color: ${p => p.theme.textColor};
  137. font-weight: normal;
  138. }
  139. @media (min-width: ${props => props.theme.breakpoints.large}) {
  140. & > small {
  141. margin-left: ${space(1)};
  142. display: inline-block;
  143. }
  144. }
  145. ${p =>
  146. p.isCentered &&
  147. css`
  148. align-items: center;
  149. @media (max-width: ${p.theme.breakpoints.small}) {
  150. display: block;
  151. }
  152. `}
  153. >*:first-child {
  154. position: relative;
  155. flex-grow: 1;
  156. }
  157. `;
  158. export const SectionContents = styled('div')`
  159. position: relative;
  160. `;
  161. const ActionContainer = styled('div')`
  162. flex-shrink: 0;
  163. max-width: 100%;
  164. `;