@@ -21,26 +21,27 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
2121 } = props ;
2222 const [ value , setValue ] = React . useState < string | null > ( ) ;
2323 const [ offset , setOffset ] = React . useState < number | null > ( null ) ;
24- const scrollableViewRef = React . useRef < HTMLDivElement > ( null ) ;
24+ const menuContainerRef = React . useRef < HTMLDivElement > ( null ) ;
2525
2626 const onNodeUpdate = ( trigger : HTMLButtonElement | null , itemValue : string , size = 0 ) => {
27+ const padding = 16 ;
28+ const margin = - 12 ; // Offsetting the menu container's negative margin
2729 const windowWidth = document . documentElement . clientWidth ;
30+ const windowBuffer = 16 ; // constrain to within the window with some buffer on the left and right we don't want the menu to enter
31+ const viewportWidth =
32+ size < MIN_ITEMS_FOR_COLS
33+ ? VIEWPORT_ITEM_WIDTH + padding
34+ : VIEWPORT_ITEM_WIDTH * 2 + padding ;
35+ const minOffset = 0 - ( menuContainerRef . current ?. offsetLeft ?? 0 ) + margin ;
36+ const maxOffset = minOffset + windowWidth - viewportWidth ;
37+
2838 if ( windowWidth < 768 ) {
2939 // if the screen is small don't offset the menu
30- setOffset ( 0 ) ;
40+ setOffset ( minOffset + windowBuffer ) ;
3141 } else if ( trigger && value === itemValue ) {
32- const padding = 16 ;
33- const viewportWidth =
34- size < MIN_ITEMS_FOR_COLS
35- ? VIEWPORT_ITEM_WIDTH + padding
36- : VIEWPORT_ITEM_WIDTH * 2 + padding ;
37- const scrollLeft = scrollableViewRef . current ?. scrollLeft ?? 0 ;
38- const triggerOffset = trigger . offsetLeft - scrollLeft ; // offset of the trigger from the left edge including scrolling
39- const bufferLeft = 2 ; // offset the menu viewport should not pass on the left side of window
40- const bufferRight = windowWidth - ( 16 + viewportWidth ) ; // offset the menu viewport should not pass on the right side of the window
42+ const position = minOffset + trigger ?. getBoundingClientRect ( ) . left ;
4143 setOffset (
42- // constrain to within the window with some buffer on the left and right we don't want the menu to enter
43- Math . min ( bufferRight , Math . max ( bufferLeft , Math . round ( triggerOffset ) ) )
44+ Math . min ( maxOffset - windowBuffer , Math . max ( minOffset + windowBuffer , position ) )
4445 ) ;
4546 } else if ( ! value ) {
4647 setOffset ( null ) ;
@@ -55,7 +56,7 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
5556 className = "z-10 flex w-full flex-nowrap items-center"
5657 >
5758 < div
58- ref = { scrollableViewRef }
59+ ref = { menuContainerRef }
5960 className = "-mx-3"
6061 // className="-mb-4 pb-4" /* Positive padding / negative margin allows the navigation menu indicator to show in a scroll view */
6162 >
@@ -123,11 +124,11 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
123124 className = "absolute top-full flex transition-transform duration-200 ease-in-out"
124125 style = { {
125126 display : offset === null ? 'none' : undefined ,
126- transform : offset ? `translateX(${ offset } px)` : undefined ,
127+ transform : offset ? `translateX(${ offset } px) translateZ(0) ` : 'translateZ(0)' , // TranslateZ is needed to force a stacking context, fixing a rendering bug on Safari
127128 } }
128129 >
129130 < NavigationMenu . Viewport
130- className = "relative mt-3 ml-4 h-[var(--radix-navigation-menu-viewport-height)] w-[calc(100vw_-_2rem)] origin-[top_center] overflow-hidden rounded straight-corners:rounded-none bg-tint shadow-1xs shadow-dark/1 duration-250 data-[state=closed]:duration-150 motion-safe:transition-[width,_height,_transform] data-[state=closed]:motion-safe:animate-scaleOut data-[state=open]:motion-safe:animate-scaleIn md:mx-0 md:w-[var(--radix-navigation-menu-viewport-width)] dark:shadow-dark/4"
131+ className = "relative mt-3 h-[var(--radix-navigation-menu-viewport-height)] w-[calc(100vw_-_2rem)] origin-[top_center] overflow-hidden rounded straight-corners:rounded-none bg-tint shadow-1xs shadow-dark/1 duration-250 data-[state=closed]:duration-150 motion-safe:transition-[width,_height,_transform] data-[state=closed]:motion-safe:animate-scaleOut data-[state=open]:motion-safe:animate-scaleIn md:mx-0 md:w-[var(--radix-navigation-menu-viewport-width)] dark:shadow-dark/4"
131132 style = { {
132133 translate :
133134 undefined /* don't move this to a Tailwind class as Radix renders viewport incorrectly for a few frames */ ,
0 commit comments