fix: super editor popover menus (#2041)

This commit is contained in:
Aman Harwara
2022-11-22 19:16:59 +05:30
committed by GitHub
parent b3140f1623
commit 8c8f045b9a
12 changed files with 75 additions and 50 deletions

View File

@@ -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"
>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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(

View File

@@ -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>

View File

@@ -38,7 +38,6 @@ const NotesContextMenu = ({
}}
className="py-2"
open={contextMenuOpen}
side="right"
togglePopover={closeMenu}
>
<div className="select-none" ref={contextMenuRef}>

View File

@@ -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 })
}

View File

@@ -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>

View File

@@ -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}

View File

@@ -39,6 +39,7 @@ type CommonPopoverProps = {
className?: string
disableClickOutside?: boolean
disableMobileFullscreenTakeover?: boolean
maxHeight?: (calculatedMaxHeight: number) => number
}
export type PopoverContentProps = CommonPopoverProps & {

View File

@@ -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)

View File

@@ -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()