diff --git a/scss/_menu.scss b/scss/_menu.scss index 8ba2283..099d502 100644 --- a/scss/_menu.scss +++ b/scss/_menu.scss @@ -1,6 +1,7 @@ :root{ --contexify-zIndex: 666; --contexify-menu-minWidth: 220px; + --contexify-menu-maxHeight: 400px; --contexify-menu-padding: 6px; --contexify-menu-radius: 6px; --contexify-menu-bgColor: #fff; @@ -72,6 +73,12 @@ opacity: 1; } + & &_wrapper { + overflow-x: hidden; + overflow-y: auto; + max-height: var(--contexify-menu-maxHeight); + } + & &_submenu { position: absolute; pointer-events: none; @@ -111,7 +118,7 @@ &_item { cursor: pointer; - position: relative; + position: static; &:focus{ outline: 0; diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx index d85670e..4a69c0e 100644 --- a/src/components/Menu.tsx +++ b/src/components/Menu.tsx @@ -235,6 +235,7 @@ export const Menu: React.FC = ({ visible: state.visible ? false : state.visible, })); + // @ts-ignore visibilityId.current = setTimeout(() => { isFn(onVisibilityChange) && onVisibilityChange(false); wasVisible.current = false; @@ -292,10 +293,12 @@ export const Menu: React.FC = ({ ref={nodeRef} role="menu" > - {cloneItems(children, { - propsFromTrigger, - triggerEvent, - })} +
+ {cloneItems(children, { + propsFromTrigger, + triggerEvent, + })} +
)} diff --git a/src/components/Submenu.tsx b/src/components/Submenu.tsx index faac4d4..d454b13 100644 --- a/src/components/Submenu.tsx +++ b/src/components/Submenu.tsx @@ -63,7 +63,7 @@ export const Submenu: React.FC = ({ const isDisabled = getPredicateValue(disabled, handlerParams); const isHidden = getPredicateValue(hidden, handlerParams); - function setPosition() { + function setPosition(event: any) { const node = submenuNode.current; if (node) { const bottom = `${CssClass.submenu}-bottom`; @@ -74,9 +74,28 @@ export const Submenu: React.FC = ({ const rect = node.getBoundingClientRect(); - if (rect.right > window.innerWidth) node.classList.add(right); - - if (rect.bottom > window.innerHeight) node.classList.add(bottom); + // get the menu item element + const menuItemElement = event.currentTarget; + // get the parent wrapper element + const menuWrapperElement = menuItemElement.parentNode; + + const menuItemRect = menuItemElement.getBoundingClientRect(); + const menuWrapperRect = menuWrapperElement.getBoundingClientRect(); + + // if the menu item right position + submenu width is too close to the right edge of the window, then + if ((menuItemRect.right + rect.width) > window.innerWidth) { + // node.classList.add(right); + node.style.left = `-${rect.width}px`; + } else { + node.style.left = `${menuItemRect.width}px`; + } + + if (rect.bottom > window.innerHeight) { + node.classList.add(bottom); + } else { + node.style.top = `${menuItemRect.top - menuWrapperRect.top}px`; + node.style.bottom = 'unset'; + } } } diff --git a/src/constants.ts b/src/constants.ts index 730ba59..750e193 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,6 +3,7 @@ * */ export const enum CssClass { menu = 'contexify', + menuWrapper = 'contexify_wrapper', submenu = 'contexify_submenu', submenuOpen = 'contexify_submenu-isOpen', rightSlot = 'contexify_rightSlot', @@ -29,5 +30,5 @@ export const hideOnEvents: (keyof GlobalEventHandlersEventMap)[] = [ 'scroll', // comment blur in dev so you can toggle console without closing the menu - 'blur', + // 'blur', ]; diff --git a/src/hooks/useItemTracker.ts b/src/hooks/useItemTracker.ts index ea73f67..1c00494 100644 --- a/src/hooks/useItemTracker.ts +++ b/src/hooks/useItemTracker.ts @@ -4,7 +4,7 @@ export interface ItemTrackerRecord { node: HTMLElement; isSubmenu: boolean; submenuRefTracker?: ItemTracker; - setSubmenuPosition?: () => void; + setSubmenuPosition?: (event: any) => void; keyMatcher?: false | ((e: KeyboardEvent) => void); }