@@ -22,26 +22,30 @@ export function SiteSectionTabs(props: { sections: SectionsList }) {
22
22
} = props ;
23
23
const [ value , setValue ] = React . useState < string | null > ( ) ;
24
24
const [ offset , setOffset ] = React . useState < number | null > ( null ) ;
25
+ const scrollableViewRef = React . useRef < HTMLDivElement > ( null ) ;
25
26
26
27
const onNodeUpdate = (
27
28
trigger : HTMLButtonElement | null ,
28
29
itemValue : string ,
29
30
size : number = 0 ,
30
31
) => {
31
- const windowWidth = window . innerWidth ;
32
+ const windowWidth = document . documentElement . clientWidth ;
32
33
if ( windowWidth < 768 ) {
34
+ // if the screen is small don't offset the menu
33
35
setOffset ( 0 ) ;
34
36
} else if ( trigger && value === itemValue ) {
37
+ const padding = 16 ;
35
38
const viewportWidth =
36
- size < MIN_ITEMS_FOR_COLS ? VIEWPORT_ITEM_WIDTH : VIEWPORT_ITEM_WIDTH * 2 ;
37
- const halfViewportWidth = viewportWidth / 2 ;
38
- const viewportFreeZone = 10 /* buffer */ + 8 /* padding */ + halfViewportWidth ;
39
- const triggerOffsetRight = trigger . offsetLeft + trigger . offsetWidth / 2 ;
39
+ size < MIN_ITEMS_FOR_COLS
40
+ ? VIEWPORT_ITEM_WIDTH + padding
41
+ : VIEWPORT_ITEM_WIDTH * 2 + padding ;
42
+ const scrollLeft = scrollableViewRef . current ?. scrollLeft ?? 0 ;
43
+ const triggerOffset = trigger . offsetLeft - scrollLeft ; // offset of the trigger from the left edge including scrolling
44
+ const bufferLeft = 2 ; // offset the menu viewport should not pass on the left side of window
45
+ const bufferRight = windowWidth - ( 16 + viewportWidth ) ; // offset the menu viewport should not pass on the right side of the window
40
46
setOffset (
41
- Math . min (
42
- Math . max ( viewportFreeZone , Math . round ( triggerOffsetRight ) ) ,
43
- windowWidth - viewportFreeZone ,
44
- ) ,
47
+ // constrain to within the window with some buffer on the left and right we don't want the menu to enter
48
+ Math . min ( bufferRight , Math . max ( bufferLeft , Math . round ( triggerOffset ) ) ) ,
45
49
) ;
46
50
} else if ( ! value ) {
47
51
setOffset ( null ) ;
@@ -55,6 +59,7 @@ export function SiteSectionTabs(props: { sections: SectionsList }) {
55
59
className = "w-full relative z-10 flex flex-nowrap items-center max-w-screen-2xl mx-auto page-full-width:max-w-full"
56
60
>
57
61
< div
62
+ ref = { scrollableViewRef }
58
63
className = "w-full hide-scroll overflow-x-scroll overflow-y-hidden pb-4 -mb-4" /* Positive padding / negative margin allows the navigation menu indicator to show in a scroll view */
59
64
>
60
65
< NavigationMenu . List className = "center m-0 flex list-none bg-transparent px-1 sm:px-3 md:px-5 gap-2" >
@@ -112,26 +117,25 @@ export function SiteSectionTabs(props: { sections: SectionsList }) {
112
117
) ;
113
118
} ) }
114
119
< NavigationMenu . Indicator
115
- className = "top-full z-0 flex h-3 items-end justify-center motion-safe:transition-[width,_transform] data-[state=hidden]:motion-safe:animate-fadeOut data-[state=visible]:motion-safe:animate-fadeIn"
120
+ className = "top-full z-0 flex h-3 items-end justify-center duration-150 motion-safe:transition-[width,_transform] data-[state=hidden]:motion-safe:animate-fadeOut data-[state=visible]:motion-safe:animate-fadeIn"
116
121
aria-hidden
117
122
>
118
123
< div className = "bg-tint shadow-1xs shadow-dark/1 dark:shadow-dark/4 relative top-[70%] size-3 rotate-[225deg] rounded-tl-sm" />
119
124
</ NavigationMenu . Indicator >
120
125
</ NavigationMenu . List >
121
126
</ div >
122
127
< div
123
- className = "absolute mx-4 top-full flex transition-transform duration-200 ease-in-out"
128
+ className = "absolute top-full flex transition-transform duration-200 ease-in-out"
124
129
style = { {
125
130
display : offset === null ? 'none' : undefined ,
126
131
transform : offset ? `translateX(${ offset } px)` : undefined ,
127
132
} }
128
133
>
129
134
< NavigationMenu . Viewport
130
- className = "bg-tint rounded straight-corners:rounded-none shadow-1xs shadow-dark/1 dark:shadow-dark/4 relative mt-3 w-[calc(100vw_-_2rem)] md:w-[var(--radix-navigation-menu-viewport-width)] h-[var(--radix-navigation-menu-viewport-height)] origin-[top_center] overflow-hidden motion-safe:transition-[width,_height,_transform] duration-300 data-[state=closed]:motion-safe:animate-scaleOut data-[state=open]:motion-safe:animate-scaleIn"
135
+ className = "bg-tint rounded straight-corners:rounded-none shadow-1xs shadow-dark/1 dark:shadow-dark/4 relative mt-3 ml-4 md:mx-0 w-[calc(100vw_-_2rem)] md:w-[var(--radix-navigation-menu-viewport-width)] h-[var(--radix-navigation-menu-viewport-height)] origin-[top_center] overflow-hidden motion-safe:transition-[width,_height,_transform] duration-250 data-[state=closed]:duration-150 data-[state=closed]:motion-safe:animate-scaleOut data-[state=open]:motion-safe:animate-scaleIn"
131
136
style = { {
132
- translate : offset
133
- ? '-50% 0'
134
- : undefined /* don't move this to a Tailwind class as Radix renders viewport incorrectly for a few frames */ ,
137
+ translate :
138
+ undefined /* don't move this to a Tailwind class as Radix renders viewport incorrectly for a few frames */ ,
135
139
} }
136
140
/>
137
141
</ div >
@@ -254,7 +258,7 @@ function SectionGroupTile(props: { section: SiteSection; isActive: boolean }) {
254
258
{ icon ? < SectionIcon isActive = { false } icon = { icon as IconName } /> : null }
255
259
< span className = "truncate min-w-0" > { title } </ span >
256
260
</ div >
257
- < p className = "text-tint-subtle min-h-[2lh] " > { section . description } </ p >
261
+ < p className = "text-tint-subtle" > { section . description } </ p >
258
262
</ Link >
259
263
</ li >
260
264
) ;
0 commit comments