123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- import styled from '@emotion/styled';
- import GuideAnchor from 'sentry/components/assistant/guideAnchor';
- import {DataSection} from 'sentry/components/events/styles';
- import Anchor from 'sentry/components/links/anchor';
- import QuestionTooltip from 'sentry/components/questionTooltip';
- import {IconLink} from 'sentry/icons';
- import {space} from 'sentry/styles/space';
- export interface EventDataSectionProps {
- children: React.ReactNode;
- /**
- * The title of the section
- */
- title: React.ReactNode;
- /**
- * Used as the `id` of the section. This powers the permalink
- */
- type: string;
- /**
- * Actions that appear to the far right of the title
- */
- actions?: React.ReactNode;
- className?: string;
- /**
- * If the section has a guide associated to it, you may specify the guide
- * target and it will wrap the title with a GuideAnchor
- */
- guideTarget?: string;
- /**
- * A description shown in a QuestionTooltip
- */
- help?: React.ReactNode;
- /**
- * If true, user is able to hover overlay without it disappearing. (nice if
- * you want the overlay to be interactive)
- */
- isHelpHoverable?: boolean;
- /**
- * Should the permalink be enabled for this section?
- *
- * @default true
- */
- showPermalink?: boolean;
- /**
- * Should the title be wrapped in a h3?
- */
- wrapTitle?: boolean;
- }
- function scrollToSection(element: HTMLDivElement) {
- if (window.location.hash && element) {
- const [, hash] = window.location.hash.split('#');
- try {
- const anchorElement = hash && element.querySelector('div#' + hash);
- if (anchorElement) {
- anchorElement.scrollIntoView();
- }
- } catch {
- // Since we're blindly taking the hash from the url and shoving
- // it into a querySelector, it's possible that this may
- // raise an exception if the input is invalid. So let's just ignore
- // this instead of blowing up.
- // e.g. `document.querySelector('div#=')`
- // > Uncaught DOMException: Failed to execute 'querySelector' on 'Document': 'div#=' is not a valid selector.
- }
- }
- }
- export function EventDataSection({
- children,
- className,
- type,
- title,
- help,
- actions,
- guideTarget,
- wrapTitle = true,
- showPermalink = true,
- isHelpHoverable = false,
- ...props
- }: EventDataSectionProps) {
- let titleNode = wrapTitle ? <h3>{title}</h3> : title;
- titleNode = guideTarget ? (
- <GuideAnchor target={guideTarget} position="bottom">
- {titleNode}
- </GuideAnchor>
- ) : (
- titleNode
- );
- return (
- <DataSection ref={scrollToSection} className={className || ''} {...props}>
- <SectionHeader id={type} data-test-id={`event-section-${type}`}>
- {title && (
- <Title>
- {showPermalink ? (
- <Permalink className="permalink">
- <PermalinkAnchor href={`#${type}`}>
- <StyledIconLink size="xs" color="subText" />
- </PermalinkAnchor>
- {titleNode}
- </Permalink>
- ) : (
- titleNode
- )}
- {help && (
- <QuestionTooltip size="xs" title={help} isHoverable={isHelpHoverable} />
- )}
- </Title>
- )}
- {actions && <ActionContainer>{actions}</ActionContainer>}
- </SectionHeader>
- <SectionContents>{children}</SectionContents>
- </DataSection>
- );
- }
- const Title = styled('div')`
- display: grid;
- grid-template-columns: max-content 1fr;
- align-items: center;
- gap: ${space(0.5)};
- `;
- const Permalink = styled('span')`
- width: 100%;
- position: relative;
- `;
- const StyledIconLink = styled(IconLink)`
- opacity: 0;
- transform: translateY(-1px);
- transition: opacity 100ms;
- `;
- const PermalinkAnchor = styled(Anchor)`
- display: flex;
- align-items: center;
- position: absolute;
- top: 0;
- left: 0;
- width: calc(100% + ${space(3)});
- height: 100%;
- padding-left: ${space(0.5)};
- transform: translateX(-${space(3)});
- :hover ${StyledIconLink}, :focus ${StyledIconLink} {
- opacity: 1;
- }
- `;
- const SectionHeader = styled('div')`
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: ${space(0.5)};
- margin-bottom: ${space(1)};
- & h3,
- & h3 a {
- color: ${p => p.theme.subText};
- font-size: ${p => p.theme.fontSizeMedium};
- font-weight: ${p => p.theme.fontWeightBold};
- }
- & h3 {
- padding: ${space(0.75)} 0;
- margin-bottom: 0;
- }
- & small {
- color: ${p => p.theme.textColor};
- font-size: ${p => p.theme.fontSizeMedium};
- margin-right: ${space(0.5)};
- margin-left: ${space(0.5)};
- }
- & small > span {
- color: ${p => p.theme.textColor};
- font-weight: ${p => p.theme.fontWeightNormal};
- }
- @media (min-width: ${p => p.theme.breakpoints.large}) {
- & > small {
- margin-left: ${space(1)};
- display: inline-block;
- }
- }
- > *:first-child {
- position: relative;
- flex-grow: 1;
- }
- `;
- export const SectionContents = styled('div')`
- position: relative;
- `;
- const ActionContainer = styled('div')`
- flex-shrink: 0;
- max-width: 100%;
- `;
|