1
- import { useCallback , useMemo } from 'react' ;
1
+ import { useCallback , useMemo , useState } from 'react' ;
2
2
import styled from '@emotion/styled' ;
3
3
import classNames from 'classnames' ;
4
4
5
5
import { Alert } from 'sentry/components/core/alert' ;
6
6
import EmptyMessage from 'sentry/components/emptyMessage' ;
7
7
import { useReplayContext } from 'sentry/components/replays/replayContext' ;
8
- import { IconChevron } from 'sentry/icons' ;
8
+ import { IconChevron , IconFire , IconMegaphone } from 'sentry/icons' ;
9
9
import { t } from 'sentry/locale' ;
10
10
import { space } from 'sentry/styles/space' ;
11
11
import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers' ;
@@ -33,10 +33,12 @@ export function ChapterList({summaryData}: Props) {
33
33
const chapterData = useMemo (
34
34
( ) =>
35
35
summaryData ?. data . time_ranges
36
- . map ( ( { period_title, period_start, period_end} ) => ( {
36
+ . map ( ( { period_title, period_start, period_end, error , feedback } ) => ( {
37
37
title : period_title ,
38
38
start : period_start ,
39
39
end : period_end ,
40
+ error,
41
+ feedback,
40
42
breadcrumbs :
41
43
replay
42
44
?. getChapterFrames ( )
@@ -60,14 +62,16 @@ export function ChapterList({summaryData}: Props) {
60
62
61
63
return (
62
64
< ChaptersList >
63
- { chapterData . map ( ( { title, start, end, breadcrumbs} , i ) => (
65
+ { chapterData . map ( ( { title, start, end, breadcrumbs, error , feedback } , i ) => (
64
66
< ChapterRow
65
67
key = { i }
66
68
title = { title }
67
69
start = { start }
68
70
end = { end }
69
71
breadcrumbs = { breadcrumbs }
70
72
onClickChapterTimestamp = { onClickChapterTimestamp }
73
+ error = { error }
74
+ feedback = { feedback }
71
75
/>
72
76
) ) }
73
77
</ ChaptersList >
@@ -81,9 +85,13 @@ function ChapterRow({
81
85
breadcrumbs,
82
86
onClickChapterTimestamp,
83
87
className,
88
+ error,
89
+ feedback,
84
90
} : {
85
91
breadcrumbs : ReplayFrame [ ] ;
86
92
end : number ;
93
+ error : boolean ;
94
+ feedback : boolean ;
87
95
onClickChapterTimestamp : ( event : React . MouseEvent < Element > , start : number ) => void ;
88
96
start : number ;
89
97
title : string ;
@@ -92,6 +100,7 @@ function ChapterRow({
92
100
const { replay, currentTime} = useReplayContext ( ) ;
93
101
const { onClickTimestamp} = useCrumbHandlers ( ) ;
94
102
const [ currentHoverTime ] = useCurrentHoverTime ( ) ;
103
+ const [ isHovered , setIsHovered ] = useState ( false ) ;
95
104
96
105
const startOffset = Math . max ( start - ( replay ?. getStartTimestampMs ( ) ?? 0 ) , 0 ) ;
97
106
const endOffset = Math . max ( end - ( replay ?. getStartTimestampMs ( ) ?? 0 ) , 0 ) ;
@@ -107,11 +116,27 @@ function ChapterRow({
107
116
beforeHoverTime : currentHoverTime === undefined ? undefined : isBeforeHover ,
108
117
afterHoverTime : currentHoverTime === undefined ? undefined : ! isBeforeHover ,
109
118
} ) }
119
+ onMouseEnter = { ( ) => setIsHovered ( true ) }
120
+ onMouseLeave = { ( ) => setIsHovered ( false ) }
110
121
>
111
- < Chapter >
112
- < ChapterIconArrowWrapper >
113
- < ChapterIconArrow direction = "right" size = "xs" />
114
- </ ChapterIconArrowWrapper >
122
+ < Chapter error = { error } feedback = { feedback } >
123
+ < ChapterIconWrapper >
124
+ { error ? (
125
+ isHovered ? (
126
+ < ChapterIconArrow direction = "right" size = "xs" color = "errorText" />
127
+ ) : (
128
+ < IconFire size = "xs" color = "errorText" />
129
+ )
130
+ ) : feedback ? (
131
+ isHovered ? (
132
+ < ChapterIconArrow direction = "right" size = "xs" color = "pink300" />
133
+ ) : (
134
+ < IconMegaphone size = "xs" color = "pink300" />
135
+ )
136
+ ) : (
137
+ < ChapterIconArrow direction = "right" size = "xs" />
138
+ ) }
139
+ </ ChapterIconWrapper >
115
140
< ChapterTitle >
116
141
< span > { title } </ span >
117
142
@@ -149,12 +174,14 @@ function ChapterRow({
149
174
) ;
150
175
}
151
176
152
- const ChapterIconArrowWrapper = styled ( 'div' ) `
177
+ const ChapterIconWrapper = styled ( 'div' ) `
153
178
display: flex;
154
179
align-items: center;
155
180
justify-content: center;
156
- padding: ${ space ( 1 ) } ${ space ( 0.5 ) } ;
181
+ padding: ${ space ( 0.5 ) } ;
182
+ margin-right: ${ space ( 1 ) } ;
157
183
background-color: ${ p => p . theme . background } ;
184
+ border-radius: 50%;
158
185
z-index: 2; /* needs to be above "ChapterWrapper summary::after" */
159
186
` ;
160
187
@@ -191,6 +218,7 @@ const ChapterWrapper = styled('details')`
191
218
192
219
&:last-child summary::after {
193
220
bottom: 0;
221
+ display: none; /* hide the vertical line for the last chapter */
194
222
}
195
223
196
224
&.activeChapter .beforeCurrentTime:last-child {
@@ -212,12 +240,22 @@ const ChapterBreadcrumbRow = styled(BreadcrumbRow)`
212
240
}
213
241
` ;
214
242
215
- const Chapter = styled ( 'summary' ) `
243
+ const Chapter = styled ( 'summary' ) < { error ?: boolean ; feedback ?: boolean } > `
216
244
cursor: pointer;
217
245
display: flex;
218
246
align-items: center;
219
247
font-size: ${ p => p . theme . fontSize . lg } ;
220
248
padding: 0 ${ space ( 0.75 ) } ;
249
+ color: ${ p =>
250
+ p . error ? p . theme . errorText : p . feedback ? p . theme . pink300 : p . theme . textColor } ;
251
+ &:hover {
252
+ background-color: ${ p =>
253
+ p . error
254
+ ? p . theme . red100
255
+ : p . feedback
256
+ ? p . theme . pink100
257
+ : p . theme . backgroundSecondary } ;
258
+ }
221
259
222
260
/* sorry */
223
261
&:focus-visible {
@@ -243,9 +281,8 @@ const ChapterTitle = styled('div')`
243
281
font-weight: ${ p => p . theme . fontWeight . bold } ;
244
282
}
245
283
246
- :not(details[open] &) {
247
- border-bottom: 1px solid ${ p => p . theme . innerBorder } ;
248
- }
284
+ border-bottom: 1px solid ${ p => p . theme . innerBorder } ;
285
+
249
286
details:last-child:not([open]) & {
250
287
border-bottom: none;
251
288
}
0 commit comments