fix: super editor popover menus (#2041)
This commit is contained in:
@@ -20,7 +20,6 @@ const FileContextMenu: FunctionComponent<Props> = observer(({ filesController, s
|
||||
open={showFileContextMenu}
|
||||
anchorPoint={fileContextMenuLocation}
|
||||
togglePopover={() => setShowFileContextMenu(!showFileContextMenu)}
|
||||
side="right"
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
|
||||
@@ -40,7 +40,14 @@ const AccountMenuButton = ({
|
||||
</div>
|
||||
</button>
|
||||
</StyledTooltip>
|
||||
<Popover anchorElement={buttonRef.current} open={isOpen} togglePopover={toggleMenu} side="top" className="py-2">
|
||||
<Popover
|
||||
anchorElement={buttonRef.current}
|
||||
open={isOpen}
|
||||
togglePopover={toggleMenu}
|
||||
side="top"
|
||||
align="start"
|
||||
className="py-2"
|
||||
>
|
||||
<AccountMenu
|
||||
onClickOutside={onClickOutside}
|
||||
viewControllerManager={viewControllerManager}
|
||||
|
||||
@@ -113,16 +113,14 @@ export default function BlockPickerMenuPlugin(): JSX.Element {
|
||||
return (
|
||||
<Popover
|
||||
align="start"
|
||||
anchorPoint={{
|
||||
x: anchorElementRef.current.offsetLeft,
|
||||
y: anchorElementRef.current.offsetTop + (!isMobileScreen() ? anchorElementRef.current.offsetHeight : 0),
|
||||
}}
|
||||
anchorElement={anchorElementRef.current}
|
||||
open={popoverOpen}
|
||||
togglePopover={() => {
|
||||
setPopoverOpen((prevValue) => !prevValue)
|
||||
}}
|
||||
disableMobileFullscreenTakeover={true}
|
||||
side={isMobileScreen() ? 'top' : 'bottom'}
|
||||
maxHeight={(mh) => mh / 2}
|
||||
>
|
||||
<div className={PopoverClassNames}>
|
||||
<ul>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||
|
||||
export const PopoverClassNames = classNames(
|
||||
'z-dropdown-menu w-full',
|
||||
'cursor-auto flex-col overflow-y-auto rounded bg-default md:h-auto h-auto overflow-y-scroll',
|
||||
'cursor-auto flex-col overflow-y-auto rounded bg-default h-auto',
|
||||
)
|
||||
|
||||
export const PopoverItemClassNames = classNames(
|
||||
|
||||
@@ -105,16 +105,14 @@ export const ItemSelectionPlugin: FunctionComponent<Props> = ({ currentNote }) =
|
||||
return (
|
||||
<Popover
|
||||
align="start"
|
||||
anchorPoint={{
|
||||
x: anchorElementRef.current.offsetLeft,
|
||||
y: anchorElementRef.current.offsetTop + (!isMobileScreen() ? anchorElementRef.current.offsetHeight : 0),
|
||||
}}
|
||||
anchorElement={anchorElementRef.current}
|
||||
open={popoverOpen}
|
||||
togglePopover={() => {
|
||||
setPopoverOpen((prevValue) => !prevValue)
|
||||
}}
|
||||
disableMobileFullscreenTakeover={true}
|
||||
side={isMobileScreen() ? 'top' : 'bottom'}
|
||||
maxHeight={(mh) => mh / 2}
|
||||
>
|
||||
<div className={PopoverClassNames}>
|
||||
<ul>
|
||||
|
||||
@@ -38,7 +38,6 @@ const NotesContextMenu = ({
|
||||
}}
|
||||
className="py-2"
|
||||
open={contextMenuOpen}
|
||||
side="right"
|
||||
togglePopover={closeMenu}
|
||||
>
|
||||
<div className="select-none" ref={contextMenuRef}>
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import { MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
import { isMobileScreen } from '@/Utils'
|
||||
import { CSSProperties } from 'react'
|
||||
import { PopoverAlignment, PopoverSide } from './Types'
|
||||
import { OppositeSide, checkCollisions, getNonCollidingSide, getNonCollidingAlignment } from './Utils/Collisions'
|
||||
import { getPositionedPopoverRect } from './Utils/Rect'
|
||||
import { OppositeSide, checkCollisions, getNonCollidingAlignment, getOverflows } from './Utils/Collisions'
|
||||
import { getAppRect, getPopoverMaxHeight, getPositionedPopoverRect } from './Utils/Rect'
|
||||
|
||||
const getStylesFromRect = (
|
||||
rect: DOMRect,
|
||||
options: {
|
||||
disableMobileFullscreenTakeover?: boolean
|
||||
maxHeight?: number | 'none'
|
||||
},
|
||||
): CSSProperties => {
|
||||
const { disableMobileFullscreenTakeover = false, maxHeight = 'none' } = options
|
||||
|
||||
const canApplyMaxHeight = maxHeight !== 'none' && (!isMobileScreen() || disableMobileFullscreenTakeover)
|
||||
|
||||
const getStylesFromRect = (rect: DOMRect, disableMobileFullscreenTakeover?: boolean): CSSProperties => {
|
||||
return {
|
||||
willChange: 'transform',
|
||||
transform: `translate(${rect.x}px, ${rect.y}px)`,
|
||||
...(disableMobileFullscreenTakeover
|
||||
? {
|
||||
maxWidth: `${window.innerWidth - rect.x * 2}px`,
|
||||
}
|
||||
: {}),
|
||||
visibility: 'visible',
|
||||
...(canApplyMaxHeight && {
|
||||
maxHeight: `${maxHeight}px`,
|
||||
}),
|
||||
...(disableMobileFullscreenTakeover && {
|
||||
maxWidth: `${window.innerWidth - rect.x * 2}px`,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +36,7 @@ type Options = {
|
||||
popoverRect?: DOMRect
|
||||
side: PopoverSide
|
||||
disableMobileFullscreenTakeover?: boolean
|
||||
maxHeightFunction?: (calculatedMaxHeight: number) => number
|
||||
}
|
||||
|
||||
export const getPositionedPopoverStyles = ({
|
||||
@@ -32,31 +46,45 @@ export const getPositionedPopoverStyles = ({
|
||||
popoverRect,
|
||||
side,
|
||||
disableMobileFullscreenTakeover,
|
||||
}: Options): [CSSProperties | null, PopoverSide, PopoverAlignment] => {
|
||||
maxHeightFunction,
|
||||
}: Options): CSSProperties | null => {
|
||||
if (!popoverRect || !anchorRect) {
|
||||
return [null, side, align]
|
||||
return null
|
||||
}
|
||||
|
||||
const matchesMediumBreakpoint = matchMedia(MediaQueryBreakpoints.md).matches
|
||||
|
||||
if (!matchesMediumBreakpoint && !disableMobileFullscreenTakeover) {
|
||||
return [null, side, align]
|
||||
return null
|
||||
}
|
||||
|
||||
const rectForPreferredSide = getPositionedPopoverRect(popoverRect, anchorRect, side, align)
|
||||
const preferredSideRectCollisions = checkCollisions(rectForPreferredSide, documentRect)
|
||||
const preferredSideOverflows = getOverflows(rectForPreferredSide, documentRect)
|
||||
|
||||
const oppositeSide = OppositeSide[side]
|
||||
const rectForOppositeSide = getPositionedPopoverRect(popoverRect, anchorRect, oppositeSide, align)
|
||||
const oppositeSideRectCollisions = checkCollisions(rectForOppositeSide, documentRect)
|
||||
const oppositeSideOverflows = getOverflows(rectForOppositeSide, documentRect)
|
||||
|
||||
const finalSide = getNonCollidingSide(side, preferredSideRectCollisions, oppositeSideRectCollisions)
|
||||
const finalAlignment = getNonCollidingAlignment(finalSide, align, preferredSideRectCollisions, {
|
||||
const sideWithLessOverflows = preferredSideOverflows[side] < oppositeSideOverflows[oppositeSide] ? side : oppositeSide
|
||||
const finalAlignment = getNonCollidingAlignment(sideWithLessOverflows, align, preferredSideRectCollisions, {
|
||||
popoverRect,
|
||||
buttonRect: anchorRect,
|
||||
documentRect,
|
||||
})
|
||||
const finalPositionedRect = getPositionedPopoverRect(popoverRect, anchorRect, finalSide, finalAlignment)
|
||||
const finalPositionedRect = getPositionedPopoverRect(popoverRect, anchorRect, sideWithLessOverflows, finalAlignment)
|
||||
|
||||
return [getStylesFromRect(finalPositionedRect, disableMobileFullscreenTakeover), finalSide, finalAlignment]
|
||||
let maxHeight = getPopoverMaxHeight(
|
||||
getAppRect(),
|
||||
anchorRect,
|
||||
sideWithLessOverflows,
|
||||
finalAlignment,
|
||||
disableMobileFullscreenTakeover,
|
||||
)
|
||||
|
||||
if (maxHeightFunction && typeof maxHeight === 'number') {
|
||||
maxHeight = maxHeightFunction(maxHeight)
|
||||
}
|
||||
|
||||
return getStylesFromRect(finalPositionedRect, { disableMobileFullscreenTakeover, maxHeight })
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ const Popover = ({
|
||||
togglePopover,
|
||||
disableClickOutside,
|
||||
disableMobileFullscreenTakeover,
|
||||
maxHeight,
|
||||
}: Props) => {
|
||||
const popoverId = useRef(UuidGenerator.GenerateUuid())
|
||||
|
||||
@@ -100,6 +101,7 @@ const Popover = ({
|
||||
togglePopover={togglePopover}
|
||||
disableClickOutside={disableClickOutside}
|
||||
disableMobileFullscreenTakeover={disableMobileFullscreenTakeover}
|
||||
maxHeight={maxHeight}
|
||||
>
|
||||
{children}
|
||||
</PositionedPopoverContent>
|
||||
|
||||
@@ -7,7 +7,6 @@ import Portal from '../Portal/Portal'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
import { getPositionedPopoverStyles } from './GetPositionedPopoverStyles'
|
||||
import { PopoverContentProps } from './Types'
|
||||
import { getPopoverMaxHeight, getAppRect } from './Utils/Rect'
|
||||
import { usePopoverCloseOnClickOutside } from './Utils/usePopoverCloseOnClickOutside'
|
||||
import { useDisableBodyScrollOnMobile } from '@/Hooks/useDisableBodyScrollOnMobile'
|
||||
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||
@@ -25,6 +24,7 @@ const PositionedPopoverContent = ({
|
||||
togglePopover,
|
||||
disableClickOutside,
|
||||
disableMobileFullscreenTakeover,
|
||||
maxHeight,
|
||||
}: PopoverContentProps) => {
|
||||
const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
|
||||
const popoverRect = useAutoElementRect(popoverElement)
|
||||
@@ -39,13 +39,14 @@ const PositionedPopoverContent = ({
|
||||
const documentRect = useDocumentRect()
|
||||
const isDesktopScreen = useMediaQuery(MediaQueryBreakpoints.md)
|
||||
|
||||
const [styles, positionedSide, positionedAlignment] = getPositionedPopoverStyles({
|
||||
const styles = getPositionedPopoverStyles({
|
||||
align,
|
||||
anchorRect,
|
||||
documentRect,
|
||||
popoverRect: popoverRect ?? popoverElement?.getBoundingClientRect(),
|
||||
side,
|
||||
disableMobileFullscreenTakeover: disableMobileFullscreenTakeover,
|
||||
maxHeightFunction: maxHeight,
|
||||
})
|
||||
|
||||
usePopoverCloseOnClickOutside({
|
||||
@@ -79,20 +80,10 @@ const PositionedPopoverContent = ({
|
||||
!disableMobileFullscreenTakeover && 'h-full',
|
||||
overrideZIndex ? overrideZIndex : 'z-dropdown-menu',
|
||||
!isDesktopScreen && !disableMobileFullscreenTakeover ? 'pt-safe-top pb-safe-bottom' : '',
|
||||
!styles && 'md:invisible',
|
||||
isDesktopScreen || disableMobileFullscreenTakeover ? 'invisible' : '',
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
maxHeight: styles
|
||||
? getPopoverMaxHeight(
|
||||
getAppRect(documentRect),
|
||||
anchorRect,
|
||||
positionedSide,
|
||||
positionedAlignment,
|
||||
disableMobileFullscreenTakeover,
|
||||
)
|
||||
: '',
|
||||
top: !isDesktopScreen ? `${document.documentElement.scrollTop}px` : '',
|
||||
}}
|
||||
ref={setPopoverElement}
|
||||
data-popover={id}
|
||||
|
||||
@@ -39,6 +39,7 @@ type CommonPopoverProps = {
|
||||
className?: string
|
||||
disableClickOutside?: boolean
|
||||
disableMobileFullscreenTakeover?: boolean
|
||||
maxHeight?: (calculatedMaxHeight: number) => number
|
||||
}
|
||||
|
||||
export type PopoverContentProps = CommonPopoverProps & {
|
||||
|
||||
@@ -8,6 +8,17 @@ export const OppositeSide: Record<PopoverSide, PopoverSide> = {
|
||||
right: 'left',
|
||||
}
|
||||
|
||||
export const getOverflows = (popoverRect: DOMRect, documentRect: DOMRect): Record<PopoverSide, number> => {
|
||||
const overflows = {
|
||||
top: documentRect.top - popoverRect.top,
|
||||
bottom: popoverRect.height - (documentRect.bottom - popoverRect.top),
|
||||
left: documentRect.left - popoverRect.left,
|
||||
right: popoverRect.right - documentRect.right,
|
||||
}
|
||||
|
||||
return overflows
|
||||
}
|
||||
|
||||
export const checkCollisions = (popoverRect: DOMRect, containerRect: DOMRect): RectCollisions => {
|
||||
const appRect = getAppRect(containerRect)
|
||||
|
||||
|
||||
@@ -49,15 +49,6 @@ export const getPopoverMaxHeight = (
|
||||
return appRect.height - constraint - MarginFromAppBorderInPX
|
||||
}
|
||||
|
||||
export const getMaxHeightAdjustedRect = (rect: DOMRect, maxHeight: number) => {
|
||||
return DOMRect.fromRect({
|
||||
width: rect.width,
|
||||
height: rect.height < maxHeight ? rect.height : maxHeight,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
})
|
||||
}
|
||||
|
||||
export const getAppRect = (updatedDocumentRect?: DOMRect) => {
|
||||
const footerRect = document.querySelector('footer')?.getBoundingClientRect()
|
||||
const documentRect = updatedDocumentRect ? updatedDocumentRect : document.documentElement.getBoundingClientRect()
|
||||
|
||||
Reference in New Issue
Block a user