11'use client' ;
22
33import React , {
4- FC ,
5- MouseEventHandler ,
6- useCallback ,
7- useMemo ,
8- useState ,
4+ FC , MouseEventHandler , useCallback , useLayoutEffect , useMemo , useRef , useState
95} from 'react' ;
106import { useClickOutside } from '@mantine/hooks' ;
117import { useFetch } from '@gitroom/helpers/utils/custom.fetch' ;
@@ -60,17 +56,39 @@ export const Menu: FC<{
6056 const { integrations, reloadCalendarView } = useCalendar ( ) ;
6157 const toast = useToaster ( ) ;
6258 const modal = useModals ( ) ;
63- const [ show , setShow ] = useState ( false ) ;
59+ const [ show , setShow ] = useState < false | { x : number , y : number } > ( false ) ;
60+ const menuRef = useRef < HTMLDivElement > ( null ) ;
6461 const ref = useClickOutside < HTMLDivElement > ( ( ) => {
6562 setShow ( false ) ;
6663 } ) ;
64+ const showRef = useRef ( ) ;
65+
66+ // Adjust menu position if it would overflow viewport
67+ useLayoutEffect ( ( ) => {
68+ if ( show && menuRef . current ) {
69+ const menuRect = menuRef . current . getBoundingClientRect ( ) ;
70+ const viewportHeight = window . innerHeight ;
71+ const padding = 10 ;
72+
73+ // Check if menu overflows bottom of viewport
74+ if ( menuRect . bottom > viewportHeight - padding ) {
75+ const newY = Math . max ( padding , viewportHeight - menuRect . height - padding ) ;
76+ // Only update if position actually changed significantly to avoid infinite loop
77+ if ( Math . abs ( show . y - newY ) > 1 ) {
78+ setShow ( prev => prev ? { ...prev , y : newY } : false ) ;
79+ }
80+ }
81+ }
82+ } , [ show ] ) ;
6783 const findIntegration : any = useMemo ( ( ) => {
6884 return integrations . find ( ( integration ) => integration . id === id ) ;
6985 } , [ integrations , id ] ) ;
7086 const changeShow : MouseEventHandler < HTMLDivElement > = useCallback (
7187 ( e ) => {
7288 e . stopPropagation ( ) ;
73- setShow ( ! show ) ;
89+ // @ts -ignore
90+ const boundBox = showRef ?. current ?. getBoundingClientRect ( ) ;
91+ setShow ( show ? false : { x : boundBox ?. left , y : boundBox ?. top + boundBox ?. height } ) ;
7492 } ,
7593 [ show ]
7694 ) ;
@@ -284,9 +302,10 @@ export const Menu: FC<{
284302 ) ,
285303 } ) ;
286304 } , [ ] ) ;
305+
287306 return (
288307 < div
289- className = "cursor-pointer relative select-none"
308+ className = "cursor-pointer relative select-none flex "
290309 onClick = { changeShow }
291310 ref = { ref }
292311 >
@@ -303,10 +322,15 @@ export const Menu: FC<{
303322 fill = "currentColor"
304323 />
305324 </ svg >
325+ < div >
326+ < div ref = { showRef } />
327+ </ div >
306328 { show && (
307329 < div
330+ ref = { menuRef }
308331 onClick = { ( e ) => e . stopPropagation ( ) }
309- className = { `absolute top-[100%] start-0 p-[12px] bg-newBgColorInner shadow-menu flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder text-nowrap` }
332+ style = { { left : show . x , top : show . y } }
333+ className = { `fixed p-[12px] bg-newBgColorInner shadow-menu flex flex-col gap-[16px] z-[100] rounded-[8px] border border-tableBorder text-nowrap` }
310334 >
311335 { canDisable && ! findIntegration ?. refreshNeeded && (
312336 < div
0 commit comments