4
4
forwardRef ,
5
5
Fragment ,
6
6
InputHTMLAttributes ,
7
+ memo ,
7
8
ReactNode ,
8
9
useCallback ,
9
10
useMemo ,
@@ -13,7 +14,6 @@ import { addDays, formatDate } from 'date-fns';
13
14
import {
14
15
CalendarIcon ,
15
16
CheckIcon ,
16
- ChevronRight ,
17
17
ChevronRightIcon ,
18
18
CircleXIcon ,
19
19
MinusIcon ,
@@ -88,22 +88,27 @@ export function FilterTitle(props: { children: ReactNode; changes?: number; onRe
88
88
className = "group/label text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground w-full text-sm"
89
89
>
90
90
< CollapsibleTrigger >
91
- < ChevronRightIcon className = "mr-2 transition-transform group-data-[state=open]/collapsible:rotate-90" />
92
- { props . children }
93
- { props . changes ? (
94
- < Button
95
- variant = "secondary"
96
- size = "sm"
97
- className = "hover:bg-secondary group ml-auto h-6 w-8 px-1 py-0 text-xs text-gray-500"
98
- onClick = { e => {
99
- e . preventDefault ( ) ;
100
- props . onReset ( ) ;
101
- } }
102
- >
103
- < CircleXIcon className = "hidden size-3 group-hover:block" />
104
- < span className = "block group-hover:hidden" > { props . changes } </ span >
105
- </ Button >
106
- ) : null }
91
+ < >
92
+ < ChevronRightIcon className = "mr-2 transition-transform group-data-[state=open]/collapsible:rotate-90" />
93
+ { props . children }
94
+ { props . changes ? (
95
+ < Button
96
+ variant = "secondary"
97
+ size = "sm"
98
+ className = "hover:bg-secondary group ml-auto h-6 w-8 px-1 py-0 text-xs text-gray-500"
99
+ onClick = { e => {
100
+ e . preventDefault ( ) ;
101
+ props . onReset ( ) ;
102
+ } }
103
+ asChild
104
+ >
105
+ < >
106
+ < CircleXIcon className = "hidden size-3 group-hover:block" />
107
+ < span className = "block group-hover:hidden" > { props . changes } </ span >
108
+ </ >
109
+ </ Button >
110
+ ) : null }
111
+ </ >
107
112
</ CollapsibleTrigger >
108
113
</ SidebarGroupLabel >
109
114
) ;
@@ -119,89 +124,91 @@ export function FilterContent(props: { children: ReactNode }) {
119
124
) ;
120
125
}
121
126
122
- export function MultiInputFilter ( props : {
123
- name : string ;
124
- /**
125
- * Filter's key for the backend and url state
126
- */
127
- key : string ;
128
- selectedValues : string [ ] ;
129
- onChange ( selectedValues : string [ ] ) : void ;
130
- } ) {
131
- const [ traceId , setTraceId ] = useState ( '' ) ;
132
- const handleTraceIdChange = useCallback (
133
- ( e : React . ChangeEvent < HTMLInputElement > ) => {
134
- setTraceId ( e . target . value ) ;
135
- } ,
136
- [ setTraceId ] ,
137
- ) ;
127
+ export const MultiInputFilter = memo (
128
+ ( props : {
129
+ name : string ;
130
+ /**
131
+ * Filter's key for the backend and url state
132
+ */
133
+ key : string ;
134
+ selectedValues : string [ ] ;
135
+ onChange ( selectedValues : string [ ] ) : void ;
136
+ } ) => {
137
+ const [ traceId , setTraceId ] = useState ( '' ) ;
138
+ const handleTraceIdChange = useCallback (
139
+ ( e : React . ChangeEvent < HTMLInputElement > ) => {
140
+ setTraceId ( e . target . value ) ;
141
+ } ,
142
+ [ setTraceId ] ,
143
+ ) ;
138
144
139
- const addTraceId = useCallback ( ( ) => {
140
- if ( ! traceId ) {
141
- return ;
142
- }
145
+ const addTraceId = useCallback ( ( ) => {
146
+ if ( ! traceId ) {
147
+ return ;
148
+ }
143
149
144
- if ( ! props . selectedValues . includes ( traceId ) ) {
145
- props . onChange ( props . selectedValues . concat ( traceId ) ) ;
146
- }
150
+ if ( ! props . selectedValues . includes ( traceId ) ) {
151
+ props . onChange ( props . selectedValues . concat ( traceId ) ) ;
152
+ }
147
153
148
- setTraceId ( '' ) ;
149
- } , [ traceId , setTraceId ] ) ;
154
+ setTraceId ( '' ) ;
155
+ } , [ traceId , setTraceId ] ) ;
150
156
151
- return (
152
- < Filter name = { props . name } >
153
- < FilterTitle
154
- changes = { props . selectedValues . length }
155
- children = { props . name }
156
- onReset = { ( ) => props . onChange ( [ ] ) }
157
- />
158
- < FilterContent >
159
- < form
160
- className = "mt-4 flex w-full max-w-sm items-center space-x-2"
161
- onSubmit = { e => {
162
- e . preventDefault ( ) ;
163
- addTraceId ( ) ;
164
- } }
165
- >
166
- < FilterInput
167
- type = "text"
168
- placeholder = "Trace ID..."
169
- value = { traceId }
170
- onChange = { handleTraceIdChange }
171
- />
172
- < Button
173
- variant = "secondary"
174
- className = "h-9 w-9 p-0"
175
- type = "submit"
176
- onClick = { ( ) => {
157
+ return (
158
+ < Filter name = { props . name } >
159
+ < FilterTitle
160
+ changes = { props . selectedValues . length }
161
+ children = { props . name }
162
+ onReset = { ( ) => props . onChange ( [ ] ) }
163
+ />
164
+ < FilterContent >
165
+ < form
166
+ className = "mt-4 flex w-full max-w-sm items-center space-x-2"
167
+ onSubmit = { e => {
168
+ e . preventDefault ( ) ;
177
169
addTraceId ( ) ;
178
170
} }
179
171
>
180
- < PlusIcon className = "size-4" />
181
- </ Button >
182
- </ form >
183
- { props . selectedValues . map ( value => (
184
- < SidebarMenuButton
185
- key = { value }
186
- onClick = { ( ) => props . onChange ( props . selectedValues . filter ( val => val !== value ) ) }
187
- className = "group/trace-id hover:bg-sidebar-accent/50"
188
- >
189
- < div
190
- data-active
191
- className = "text-sidebar-primary-foreground border-sidebar-primary bg-sidebar-primary group-hover/trace-id:border-sidebar-border flex aspect-square size-4 shrink-0 items-center justify-center rounded-sm border group-hover/trace-id:bg-transparent"
172
+ < FilterInput
173
+ type = "text"
174
+ placeholder = "Trace ID..."
175
+ value = { traceId }
176
+ onChange = { handleTraceIdChange }
177
+ />
178
+ < Button
179
+ variant = "secondary"
180
+ className = "h-9 w-9 p-0"
181
+ type = "submit"
182
+ onClick = { ( ) => {
183
+ addTraceId ( ) ;
184
+ } }
192
185
>
193
- < CheckIcon className = "block size-3 group-hover/trace-id:hidden" />
194
- < MinusIcon className = "hidden size-3 group-hover/trace-id:block" />
195
- </ div >
196
- { value }
197
- </ SidebarMenuButton >
198
- ) ) }
199
- </ FilterContent >
200
- </ Filter >
201
- ) ;
202
- }
186
+ < PlusIcon className = "size-4" />
187
+ </ Button >
188
+ </ form >
189
+ { props . selectedValues . map ( value => (
190
+ < SidebarMenuButton
191
+ key = { value }
192
+ onClick = { ( ) => props . onChange ( props . selectedValues . filter ( val => val !== value ) ) }
193
+ className = "group/trace-id hover:bg-sidebar-accent/50"
194
+ >
195
+ < div
196
+ data-active
197
+ className = "text-sidebar-primary-foreground border-sidebar-primary bg-sidebar-primary group-hover/trace-id:border-sidebar-border flex aspect-square size-4 shrink-0 items-center justify-center rounded-sm border group-hover/trace-id:bg-transparent"
198
+ >
199
+ < CheckIcon className = "block size-3 group-hover/trace-id:hidden" />
200
+ < MinusIcon className = "hidden size-3 group-hover/trace-id:block" />
201
+ </ div >
202
+ { value }
203
+ </ SidebarMenuButton >
204
+ ) ) }
205
+ </ FilterContent >
206
+ </ Filter >
207
+ ) ;
208
+ } ,
209
+ ) ;
203
210
204
- export function MultiSelectFilter < $Value > ( props : {
211
+ export const MultiSelectFilter = memo ( function < $Value > ( props : {
205
212
name : string ;
206
213
/**
207
214
* Filter's key for the backend and url state
@@ -270,7 +277,7 @@ export function MultiSelectFilter<$Value>(props: {
270
277
</ FilterContent >
271
278
</ Filter >
272
279
) ;
273
- }
280
+ } ) ;
274
281
275
282
function FilterOption ( props : {
276
283
onClick ( ) : void ;
@@ -333,81 +340,80 @@ const DoubleSlider = forwardRef<
333
340
</ SliderPrimitive . Root >
334
341
) ) ;
335
342
336
- export function DurationFilter ( props : {
337
- value : [ number , number ] | [ ] ;
338
- onChange ( value : [ number , number ] ) : void ;
339
- } ) {
340
- const minValue = 0 ;
341
- const maxValue = 100000 ;
342
- const defaultValues : [ number , number ] = [ minValue , maxValue ] ;
343
- const values : [ number , number ] = props . value . length ? props . value : defaultValues ;
343
+ export const DurationFilter = memo (
344
+ ( props : { value : [ number , number ] | [ ] ; onChange ( value : [ number , number ] ) : void } ) => {
345
+ const minValue = 0 ;
346
+ const maxValue = 100000 ;
347
+ const defaultValues : [ number , number ] = [ minValue , maxValue ] ;
348
+ const values : [ number , number ] = props . value . length ? props . value : defaultValues ;
344
349
345
- const handleSliderChange = ( newValues : [ number , number ] ) => {
346
- props . onChange ( newValues ) ;
347
- } ;
350
+ const handleSliderChange = ( newValues : [ number , number ] ) => {
351
+ props . onChange ( newValues ) ;
352
+ } ;
348
353
349
- const handleInputChange = ( index : number , value : string ) => {
350
- const numValue = Number . parseInt ( value ) || minValue ;
351
- const newValues : [ number , number ] = [ ...values ] ;
352
- newValues [ index ] = Math . min ( Math . max ( numValue , minValue ) , maxValue ) ;
353
- props . onChange ( newValues ) ;
354
- } ;
354
+ const handleInputChange = ( index : number , value : string ) => {
355
+ const numValue = Number . parseInt ( value ) || minValue ;
356
+ const newValues : [ number , number ] = [ ...values ] ;
357
+ newValues [ index ] = Math . min ( Math . max ( numValue , minValue ) , maxValue ) ;
358
+ props . onChange ( newValues ) ;
359
+ } ;
355
360
356
- return (
357
- < Filter name = "Duration" >
358
- < FilterTitle
359
- changes = { values [ 0 ] === minValue && values [ 1 ] === maxValue ? 0 : 1 }
360
- children = { 'Duration' }
361
- onReset = { ( ) => props . onChange ( defaultValues ) }
362
- />
363
- < FilterContent >
364
- < div className = "space-y-6 p-2" >
365
- < div className = "space-y-2" >
366
- < div className = "space-y-1" >
367
- < label className = "font-mono text-xs text-zinc-400" > MIN</ label >
368
- < div className = "relative" >
369
- < FilterInput
370
- type = "number"
371
- value = { values [ 0 ] }
372
- onChange = { e => handleInputChange ( 0 , e . target . value ) }
373
- className = "h-7 border-zinc-800 bg-transparent px-2 pr-8 font-mono text-white"
374
- />
375
- < span className = "absolute right-2 top-1/2 -translate-y-1/2 font-mono text-xs text-zinc-400" >
376
- ms
377
- </ span >
361
+ return (
362
+ < Filter name = "Duration" >
363
+ < FilterTitle
364
+ changes = { values [ 0 ] === minValue && values [ 1 ] === maxValue ? 0 : 1 }
365
+ children = { 'Duration' }
366
+ onReset = { ( ) => props . onChange ( defaultValues ) }
367
+ />
368
+ < FilterContent >
369
+ < div className = "space-y-6 p-2" >
370
+ < div className = "space-y-2" >
371
+ < div className = "space-y-1" >
372
+ < label className = "font-mono text-xs text-zinc-400" > MIN</ label >
373
+ < div className = "relative" >
374
+ < FilterInput
375
+ type = "number"
376
+ value = { values [ 0 ] }
377
+ onChange = { e => handleInputChange ( 0 , e . target . value ) }
378
+ className = "h-7 border-zinc-800 bg-transparent px-2 pr-8 font-mono text-white"
379
+ />
380
+ < span className = "absolute right-2 top-1/2 -translate-y-1/2 font-mono text-xs text-zinc-400" >
381
+ ms
382
+ </ span >
383
+ </ div >
378
384
</ div >
379
- </ div >
380
- < div className = "space-y-1" >
381
- < label className = "font-mono text-xs text-zinc-400" > MAX </ label >
382
- < div className = "relative" >
383
- < FilterInput
384
- type = "number"
385
- value = { values [ 1 ] }
386
- onChange = { e => handleInputChange ( 1 , e . target . value ) }
387
- className = "h-7 border-gray-800 bg-transparent px-2 pr-8 font-mono text-white"
388
- / >
389
- < span className = "absolute right-2 top-1/2 -translate-y-1/2 font-mono text-xs text-gray-400" >
390
- ms
391
- </ span >
385
+ < div className = "space-y-1" >
386
+ < label className = "font-mono text-xs text-zinc-400" > MAX </ label >
387
+ < div className = "relative" >
388
+ < FilterInput
389
+ type = "number"
390
+ value = { values [ 1 ] }
391
+ onChange = { e => handleInputChange ( 1 , e . target . value ) }
392
+ className = "h-7 border-gray-800 bg-transparent px-2 pr-8 font-mono text-white"
393
+ />
394
+ < span className = "absolute right-2 top-1/2 -translate-y-1/2 font-mono text-xs text-gray-400" >
395
+ ms
396
+ </ span >
397
+ </ div >
392
398
</ div >
393
399
</ div >
400
+ < DoubleSlider
401
+ defaultValue = { defaultValues }
402
+ max = { maxValue }
403
+ min = { minValue }
404
+ step = { 1 }
405
+ value = { values }
406
+ onValueChange = { handleSliderChange }
407
+ className = "[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
408
+ />
394
409
</ div >
395
- < DoubleSlider
396
- defaultValue = { defaultValues }
397
- max = { maxValue }
398
- min = { minValue }
399
- step = { 1 }
400
- value = { values }
401
- onValueChange = { handleSliderChange }
402
- className = "[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
403
- />
404
- </ div >
405
- </ FilterContent >
406
- </ Filter >
407
- ) ;
408
- }
410
+ </ FilterContent >
411
+ </ Filter >
412
+ ) ;
413
+ } ,
414
+ ) ;
409
415
410
- export function TimelineFilter ( ) {
416
+ export const TimelineFilter = memo ( ( ) => {
411
417
const [ selectedPreset , setSelectedPreset ] = useState < string | null > ( null ) ;
412
418
const [ dateRange , setDateRange ] = useState < DateRange | undefined > ( {
413
419
from : addDays ( new Date ( ) , - 3 ) ,
@@ -510,4 +516,4 @@ export function TimelineFilter() {
510
516
</ FilterContent >
511
517
</ Filter >
512
518
) ;
513
- }
519
+ } ) ;
0 commit comments