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