Skip to content

Commit fd892b4

Browse files
authored
feat(breadcrumbs): Fix breadcrumbs styles + navigation (#76207)
Adds a design approved sticky header to the breadcrumb drawer: ![Screenshot 2024-08-14 at 2 52 00 PM](https://github.com/user-attachments/assets/a601cacb-24f9-4b04-895a-7ab3646050f1) After chatting with Vu also made some changes so align the drawer header and sticky header in the new UI: ![Screenshot 2024-08-14 at 3 08 34 PM](https://github.com/user-attachments/assets/cb16ffd1-4c73-4efc-b701-bb6bacdf4c21) (This involved updating some border colors and spacing) Lastly a few more bug fixes for breadcrumbs: Applying `meta` for HTTP and Exception crumbs: <img width="380" alt="Screenshot 2024-08-14 at 3 22 26 PM" src="https://github.com/user-attachments/assets/2df351a0-d382-4a84-9200-5e2fb2d7f58e"> Wrapping unformatted SQL <img width="722" alt="image" src="https://github.com/user-attachments/assets/67d3804e-db7d-42f0-b799-773635a656ad"> - Fixed virtualization and moved scrollbar to the left side to avoid double scroll issues. https://github.com/user-attachments/assets/026b4681-6e54-4100-8fa5-d480c0677f13
1 parent 79409ca commit fd892b4

File tree

11 files changed

+123
-50
lines changed

11 files changed

+123
-50
lines changed

static/app/components/events/breadcrumbs/breadcrumbItemContent.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ function HTTPCrumbContent({
145145
</BreadcrumbText>
146146
{Object.keys(otherData).length > 0 ? (
147147
<Timeline.Data>
148-
<StructuredData value={otherData} meta={meta} {...structuredDataProps} />
148+
<StructuredData value={otherData} meta={meta?.data} {...structuredDataProps} />
149149
</Timeline.Data>
150150
) : null}
151151
</Fragment>
@@ -163,7 +163,7 @@ function SQLCrumbContent({
163163
return (
164164
<Fragment>
165165
<Timeline.Data>
166-
<LightenTextColor className="language-sql">
166+
<SQLText className="language-sql">
167167
{tokens.map((line, i) => (
168168
<div key={i}>
169169
{line.map((token, j) => (
@@ -173,7 +173,7 @@ function SQLCrumbContent({
173173
))}
174174
</div>
175175
))}
176-
</LightenTextColor>
176+
</SQLText>
177177
</Timeline.Data>
178178
{children}
179179
</Fragment>
@@ -201,7 +201,7 @@ function ExceptionCrumbContent({
201201
{children}
202202
{Object.keys(otherData).length > 0 ? (
203203
<Timeline.Data>
204-
<StructuredData value={otherData} meta={meta} {...structuredDataProps} />
204+
<StructuredData value={otherData} meta={meta?.data} {...structuredDataProps} />
205205
</Timeline.Data>
206206
) : null}
207207
</Fragment>
@@ -215,12 +215,12 @@ const Link = styled('a')`
215215
word-break: break-all;
216216
`;
217217

218-
const LightenTextColor = styled('pre')`
219-
margin: 0;
218+
const SQLText = styled('pre')`
220219
&.language-sql {
221-
color: ${p => p.theme.subText};
220+
margin: 0;
222221
padding: ${space(0.25)} 0;
223222
font-size: ${p => p.theme.fontSizeSmall};
223+
white-space: pre-wrap;
224224
}
225225
`;
226226

static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useCallback, useMemo, useRef} from 'react';
1+
import {useCallback, useMemo, useRef, useState} from 'react';
22
import styled from '@emotion/styled';
33

44
import {Button} from 'sentry/components/button';
@@ -52,6 +52,7 @@ export default function BreadcrumbsDataSection({
5252
project,
5353
}: BreadcrumbsDataSectionProps) {
5454
const viewAllButtonRef = useRef<HTMLButtonElement>(null);
55+
const [container, setContainer] = useState<HTMLDivElement | null>(null);
5556
const openForm = useFeedbackForm();
5657
const {closeDrawer, isDrawerOpen, openDrawer} = useDrawer();
5758
const organization = useOrganization();
@@ -183,13 +184,16 @@ export default function BreadcrumbsDataSection({
183184
actions={actions}
184185
>
185186
<ErrorBoundary mini message={t('There was an error loading the event breadcrumbs')}>
186-
<BreadcrumbsTimeline
187-
breadcrumbs={summaryCrumbs}
188-
startTimeString={startTimeString}
189-
// We want the timeline to appear connected to the 'View All' button
190-
showLastLine={hasViewAll}
191-
fullyExpanded={false}
192-
/>
187+
<div ref={setContainer}>
188+
<BreadcrumbsTimeline
189+
breadcrumbs={summaryCrumbs}
190+
startTimeString={startTimeString}
191+
// We want the timeline to appear connected to the 'View All' button
192+
showLastLine={hasViewAll}
193+
fullyExpanded={false}
194+
containerElement={container}
195+
/>
196+
</div>
193197
{hasViewAll && (
194198
<ViewAllContainer>
195199
<VerticalEllipsis />

static/app/components/events/breadcrumbs/breadcrumbsDrawer.tsx

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Fragment, useCallback, useMemo, useState} from 'react';
1+
import {useCallback, useMemo, useState} from 'react';
22
import {useTheme} from '@emotion/react';
33
import styled from '@emotion/styled';
44

@@ -33,6 +33,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
3333
import {getShortEventId} from 'sentry/utils/events';
3434
import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
3535
import useOrganization from 'sentry/utils/useOrganization';
36+
import {MIN_NAV_HEIGHT} from 'sentry/views/issueDetails/streamline/eventNavigation';
3637

3738
export const enum BreadcrumbControlOptions {
3839
SEARCH = 'search',
@@ -72,6 +73,7 @@ export function BreadcrumbsDrawer({
7273
}: BreadcrumbsDrawerProps) {
7374
const organization = useOrganization();
7475
const theme = useTheme();
76+
const [container, setContainer] = useState<HTMLElement | null>(null);
7577

7678
const [search, setSearch] = useState('');
7779
const [filters, setFilters] = useState<string[]>([]);
@@ -205,8 +207,8 @@ export function BreadcrumbsDrawer({
205207
);
206208

207209
return (
208-
<Fragment>
209-
<DrawerHeader>
210+
<BreadcrumbDrawerContainer>
211+
<BreadcrumbDrawerHeader>
210212
<NavigationCrumbs
211213
crumbs={[
212214
{
@@ -221,12 +223,12 @@ export function BreadcrumbsDrawer({
221223
{label: t('Breadcrumbs')},
222224
]}
223225
/>
224-
</DrawerHeader>
225-
<DrawerBody>
226-
<HeaderGrid>
227-
<Header>{t('Breadcrumbs')}</Header>
228-
{actions}
229-
</HeaderGrid>
226+
</BreadcrumbDrawerHeader>
227+
<BreadcrumbNavigator>
228+
<Header>{t('Breadcrumbs')}</Header>
229+
{actions}
230+
</BreadcrumbNavigator>
231+
<BreadcrumbDrawerBody ref={setContainer}>
230232
<TimelineContainer>
231233
{displayCrumbs.length === 0 ? (
232234
<EmptyMessage>
@@ -249,11 +251,12 @@ export function BreadcrumbsDrawer({
249251
<BreadcrumbsTimeline
250252
breadcrumbs={displayCrumbs}
251253
startTimeString={startTimeString}
254+
containerElement={container}
252255
/>
253256
)}
254257
</TimelineContainer>
255-
</DrawerBody>
256-
</Fragment>
258+
</BreadcrumbDrawerBody>
259+
</BreadcrumbDrawerContainer>
257260
);
258261
}
259262

@@ -262,12 +265,40 @@ const VisibleFocusButton = styled(Button)`
262265
0 0 1px;
263266
`;
264267

265-
const HeaderGrid = styled('div')`
268+
const BreadcrumbDrawerContainer = styled('div')`
269+
height: 100%;
270+
display: grid;
271+
grid-template-rows: auto auto 1fr;
272+
`;
273+
274+
const BreadcrumbDrawerHeader = styled(DrawerHeader)`
275+
position: unset;
276+
max-height: ${MIN_NAV_HEIGHT}px;
277+
box-shadow: none;
278+
border-bottom: 1px solid ${p => p.theme.border};
279+
`;
280+
281+
const BreadcrumbNavigator = styled('div')`
266282
display: grid;
267283
grid-template-columns: 1fr auto;
268284
align-items: center;
269285
column-gap: ${space(1)};
270-
margin: ${space(1)} 0 ${space(2)};
286+
padding: ${space(0.75)} 24px;
287+
background: ${p => p.theme.background};
288+
z-index: 1;
289+
min-height: ${MIN_NAV_HEIGHT}px;
290+
box-shadow: ${p => p.theme.translucentBorder} 0 1px;
291+
`;
292+
293+
const BreadcrumbDrawerBody = styled(DrawerBody)`
294+
overflow: auto;
295+
overscroll-behavior: contain;
296+
/* Move the scrollbar to the left edge */
297+
scroll-margin: 0 ${space(2)};
298+
direction: rtl;
299+
* {
300+
direction: ltr;
301+
}
271302
`;
272303

273304
const Header = styled('h3')`

static/app/components/events/breadcrumbs/breadcrumbsTimeline.tsx

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {useRef} from 'react';
21
import styled from '@emotion/styled';
32
import {useVirtualizer} from '@tanstack/react-virtual';
43
import moment from 'moment-timezone';
@@ -17,6 +16,22 @@ import {shouldUse24Hours} from 'sentry/utils/dates';
1716

1817
interface BreadcrumbsTimelineProps {
1918
breadcrumbs: EnhancedCrumb[];
19+
/**
20+
* Required reference to parent container for virtualization. It's recommended to use state instead
21+
* of useRef since this component will not update when the ref changes, causing it to render empty initially.
22+
* To enable virtualization, set a fixed height on the `containerElement` node.
23+
*
24+
* Example:
25+
* ```
26+
* const [container, setContainer] = useState<HTMLElement | null>(null);
27+
* return (
28+
* <div ref={setContainer}>
29+
* <BreadcrumbsTimeline containerElement={container} />
30+
* </div>
31+
* )
32+
* ```
33+
*/
34+
containerElement: HTMLElement | null;
2035
/**
2136
* If true, expands the contents of the breadcrumbs' data payload
2237
*/
@@ -34,14 +49,14 @@ interface BreadcrumbsTimelineProps {
3449

3550
export default function BreadcrumbsTimeline({
3651
breadcrumbs,
52+
containerElement,
3753
startTimeString,
3854
fullyExpanded = true,
3955
showLastLine = false,
4056
}: BreadcrumbsTimelineProps) {
41-
const containerRef = useRef<HTMLDivElement>(null);
4257
const virtualizer = useVirtualizer({
4358
count: breadcrumbs.length,
44-
getScrollElement: () => containerRef.current,
59+
getScrollElement: () => containerElement,
4560
estimateSize: () => 35,
4661
// Must match rendered item margins.
4762
gap: 8,
@@ -114,17 +129,26 @@ export default function BreadcrumbsTimeline({
114129

115130
return (
116131
<div
117-
ref={containerRef}
118132
style={{
119133
height: virtualizer.getTotalSize(),
120-
contain: 'layout size',
134+
position: 'relative',
121135
}}
122136
>
123-
<Timeline.Container>{items}</Timeline.Container>
137+
<VirtualOffset offset={virtualItems?.[0]?.start ?? 0}>
138+
<Timeline.Container>{items}</Timeline.Container>
139+
</VirtualOffset>
124140
</div>
125141
);
126142
}
127143

144+
const VirtualOffset = styled('div')<{offset: number}>`
145+
position: absolute;
146+
top: 0;
147+
left: 0;
148+
width: 100%;
149+
transform: translateY(${p => p.offset}px);
150+
`;
151+
128152
const Header = styled('div')`
129153
display: grid;
130154
grid-template-columns: 1fr auto;

static/app/components/events/eventProcessingErrors.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ export function EventProcessingErrors({event, project, isShare}: Props) {
118118
}))
119119
);
120120

121+
if (!errors.length) {
122+
return null;
123+
}
124+
121125
return (
122126
<InterimSection
123127
title={t('Event Processing Errors')}

static/app/components/globalDrawer/components.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const DrawerPanel = forwardRef(function _DrawerPanel(
3737
) {
3838
return (
3939
<DrawerContainer>
40-
<SlideOverPanel
40+
<DrawerSlidePanel
4141
ariaLabel={ariaLabel}
4242
slidePosition="right"
4343
collapsed={false}
@@ -52,13 +52,14 @@ export const DrawerPanel = forwardRef(function _DrawerPanel(
5252
<DrawerContentContext.Provider value={{onClose, ariaLabel}}>
5353
{children}
5454
</DrawerContentContext.Provider>
55-
</SlideOverPanel>
55+
</DrawerSlidePanel>
5656
</DrawerContainer>
5757
);
5858
});
5959

6060
interface DrawerHeaderProps {
6161
children?: React.ReactNode;
62+
className?: string;
6263
/**
6364
* If true, hides the spacer bar separating close button from custom header content
6465
*/
@@ -70,13 +71,18 @@ interface DrawerHeaderProps {
7071
}
7172

7273
export const DrawerHeader = forwardRef(function _DrawerHeader(
73-
{children = null, hideBar = false, hideCloseButton = false}: DrawerHeaderProps,
74+
{
75+
className,
76+
children = null,
77+
hideBar = false,
78+
hideCloseButton = false,
79+
}: DrawerHeaderProps,
7480
ref: React.ForwardedRef<HTMLHeadingElement>
7581
) {
7682
const {onClose} = useDrawerContentContext();
7783

7884
return (
79-
<Header ref={ref}>
85+
<Header ref={ref} className={className}>
8086
{!hideCloseButton && (
8187
<Fragment>
8288
<CloseButton
@@ -117,7 +123,7 @@ const Header = styled('header')`
117123
justify-content: flex-start;
118124
display: flex;
119125
padding: ${space(1.5)};
120-
border-bottom: 1px solid ${p => p.theme.border};
126+
box-shadow: ${p => p.theme.border} 0 1px;
121127
padding-left: 24px;
122128
`;
123129

@@ -133,6 +139,10 @@ const DrawerContainer = styled('div')`
133139
pointer-events: none;
134140
`;
135141

142+
const DrawerSlidePanel = styled(SlideOverPanel)`
143+
box-shadow: 0 0 0 1px ${p => p.theme.translucentBorder};
144+
`;
145+
136146
export const DrawerComponents = {
137147
DrawerBody,
138148
DrawerHeader,

static/app/components/slideOverPanel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type SlideOverPanelProps = {
2424
children: React.ReactNode;
2525
collapsed: boolean;
2626
ariaLabel?: string;
27+
className?: string;
2728
onOpen?: () => void;
2829
slidePosition?: 'right' | 'bottom';
2930
transitionProps?: AnimationProps['transition'];
@@ -36,6 +37,7 @@ function SlideOverPanel(
3637
ariaLabel,
3738
collapsed,
3839
children,
40+
className,
3941
onOpen,
4042
slidePosition,
4143
transitionProps = {},
@@ -70,6 +72,7 @@ function SlideOverPanel(
7072
role="complementary"
7173
aria-hidden={collapsed}
7274
aria-label={ariaLabel ?? 'slide out drawer'}
75+
className={className}
7376
>
7477
{children}
7578
</_SlideOverPanel>

static/app/views/issueDetails/streamline/eventDetails.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const SearchFilter = styled(EventSearch)`
8585
`;
8686

8787
const GroupContent = styled('div')<{navHeight?: number}>`
88-
border: 1px solid ${p => p.theme.border};
88+
border: 1px solid ${p => p.theme.translucentBorder};
8989
background: ${p => p.theme.background};
9090
border-radius: ${p => p.theme.borderRadius};
9191
position: relative;

0 commit comments

Comments
 (0)