@@ -12,6 +12,7 @@ import {useRefChildrenVisibility} from 'sentry/utils/useRefChildrenVisibility';
12
12
interface ScrollCarouselProps {
13
13
children : React . ReactNode ;
14
14
className ?: string ;
15
+ 'data-test-id' ?: string ;
15
16
gap ?: ValidSize ;
16
17
}
17
18
@@ -32,7 +33,26 @@ const DEFAULT_VISIBLE_RATIO = 0.85;
32
33
*/
33
34
const DEFAULT_JUMP_ITEM_COUNT = 2 ;
34
35
35
- export function ScrollCarousel ( { children, className, gap = 1 } : ScrollCarouselProps ) {
36
+ /**
37
+ * Calculates the offset rectangle of an element relative to another element.
38
+ */
39
+ const getOffsetRect = ( el : HTMLElement , relativeTo : HTMLElement ) => {
40
+ const rect = el . getBoundingClientRect ( ) ;
41
+ if ( ! relativeTo ) {
42
+ return rect ;
43
+ }
44
+ const relativeRect = relativeTo . getBoundingClientRect ( ) ;
45
+ return {
46
+ left : rect . left - relativeRect . left ,
47
+ top : rect . top - relativeRect . top ,
48
+ right : rect . right - relativeRect . left ,
49
+ bottom : rect . bottom - relativeRect . top ,
50
+ width : rect . width ,
51
+ height : rect . height ,
52
+ } ;
53
+ } ;
54
+
55
+ export function ScrollCarousel ( { children, gap = 1 , ...props } : ScrollCarouselProps ) {
36
56
const scrollContainerRef = useRef < HTMLDivElement | null > ( null ) ;
37
57
const { visibility, childrenEls} = useRefChildrenVisibility ( {
38
58
children,
@@ -47,10 +67,11 @@ export function ScrollCarousel({children, className, gap = 1}: ScrollCarouselPro
47
67
const scrollIndex = visibility . findIndex ( Boolean ) ;
48
68
// Clamp the scroll index to the first visible item
49
69
const clampedIndex = Math . max ( scrollIndex - DEFAULT_JUMP_ITEM_COUNT , 0 ) ;
50
- childrenEls [ clampedIndex ] ?. scrollIntoView ( {
70
+ // scrollIntoView scrolls the entire page on some browsers
71
+ scrollContainerRef . current ?. scrollTo ( {
51
72
behavior : 'smooth' ,
52
- block : 'nearest' ,
53
- inline : 'start' ,
73
+ // We don't need to do any fancy math for the left edge
74
+ left : getOffsetRect ( childrenEls [ clampedIndex ] , childrenEls [ 0 ] ) . left ,
54
75
} ) ;
55
76
} , [ visibility , childrenEls ] ) ;
56
77
@@ -61,20 +82,20 @@ export function ScrollCarousel({children, className, gap = 1}: ScrollCarouselPro
61
82
scrollIndex + DEFAULT_JUMP_ITEM_COUNT ,
62
83
visibility . length - 1
63
84
) ;
64
- childrenEls [ clampedIndex ] ?. scrollIntoView ( {
85
+
86
+ const targetElement = childrenEls [ clampedIndex ] ;
87
+ const targetElementRight = getOffsetRect ( targetElement , childrenEls [ 0 ] ) . right ;
88
+ const containerRight = scrollContainerRef . current ?. clientWidth ?? 0 ;
89
+ // scrollIntoView scrolls the entire page on some browsers
90
+ scrollContainerRef . current ?. scrollTo ( {
65
91
behavior : 'smooth' ,
66
- block : 'nearest' ,
67
- inline : 'end' ,
92
+ left : Math . max ( targetElementRight - containerRight , 0 ) ,
68
93
} ) ;
69
94
} , [ visibility , childrenEls ] ) ;
70
95
71
96
return (
72
97
< ScrollCarouselWrapper >
73
- < ScrollContainer
74
- ref = { scrollContainerRef }
75
- className = { className }
76
- style = { { gap : space ( gap ) } }
77
- >
98
+ < ScrollContainer ref = { scrollContainerRef } style = { { gap : space ( gap ) } } { ...props } >
78
99
{ children }
79
100
</ ScrollContainer >
80
101
{ ! isAtStart && < LeftMask /> }
0 commit comments