eventDataSection.tsx 4.7 KB

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