refactor: mobile modals (#2173)
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import { useDisableBodyScrollOnMobile } from '@/Hooks/useDisableBodyScrollOnMobile'
|
||||
import { useLifecycleAnimation } from '@/Hooks/useLifecycleAnimation'
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
import { ReactNode } from 'react'
|
||||
import Portal from '../Portal/Portal'
|
||||
import { useModalAnimation } from '../Shared/useModalAnimation'
|
||||
|
||||
const DisableScroll = () => {
|
||||
useDisableBodyScrollOnMobile()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const MobilePopoverContent = ({
|
||||
open,
|
||||
@@ -17,54 +23,7 @@ const MobilePopoverContent = ({
|
||||
title: string
|
||||
className?: string
|
||||
}) => {
|
||||
const [isMounted, setPopoverElement] = useLifecycleAnimation({
|
||||
open,
|
||||
enter: {
|
||||
keyframes: [
|
||||
{
|
||||
opacity: 0.25,
|
||||
transform: 'translateY(1rem)',
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
],
|
||||
options: {
|
||||
easing: 'cubic-bezier(.36,.66,.04,1)',
|
||||
duration: 150,
|
||||
fill: 'forwards',
|
||||
},
|
||||
initialStyle: {
|
||||
transformOrigin: 'bottom',
|
||||
},
|
||||
},
|
||||
enterCallback: (element) => {
|
||||
element.scrollTop = 0
|
||||
},
|
||||
exit: {
|
||||
keyframes: [
|
||||
{
|
||||
opacity: 1,
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
{
|
||||
opacity: 0,
|
||||
transform: 'translateY(1rem)',
|
||||
},
|
||||
],
|
||||
options: {
|
||||
easing: 'cubic-bezier(.36,.66,.04,1)',
|
||||
duration: 150,
|
||||
fill: 'forwards',
|
||||
},
|
||||
initialStyle: {
|
||||
transformOrigin: 'bottom',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
useDisableBodyScrollOnMobile()
|
||||
const [isMounted, setPopoverElement] = useModalAnimation(open)
|
||||
|
||||
if (!isMounted) {
|
||||
return null
|
||||
@@ -72,14 +31,15 @@ const MobilePopoverContent = ({
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<DisableScroll />
|
||||
<div
|
||||
ref={setPopoverElement}
|
||||
className="absolute top-0 left-0 z-modal flex h-full w-full origin-bottom flex-col bg-default pt-safe-top pb-safe-bottom opacity-0"
|
||||
className="fixed top-0 left-0 z-modal flex h-full w-full flex-col bg-default pt-safe-top pb-safe-bottom"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-border py-2.5 px-3 text-base">
|
||||
<div />
|
||||
<div className="font-semibold">{title}</div>
|
||||
<button className="font-semibold active:shadow-none active:outline-none" onClick={requestClose}>
|
||||
<button className="font-semibold text-info active:shadow-none active:outline-none" onClick={requestClose}>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,7 @@ const Popover = ({
|
||||
disableClickOutside,
|
||||
disableMobileFullscreenTakeover,
|
||||
maxHeight,
|
||||
portal,
|
||||
}: Props) => {
|
||||
const popoverId = useRef(UuidGenerator.GenerateUuid())
|
||||
|
||||
@@ -123,6 +124,7 @@ const Popover = ({
|
||||
side={side}
|
||||
title={title}
|
||||
togglePopover={togglePopover}
|
||||
portal={portal}
|
||||
>
|
||||
{children}
|
||||
</PositionedPopoverContent>
|
||||
|
||||
@@ -2,9 +2,7 @@ import { useDocumentRect } from '@/Hooks/useDocumentRect'
|
||||
import { useAutoElementRect } from '@/Hooks/useElementRect'
|
||||
import { classNames } from '@standardnotes/utils'
|
||||
import { useCallback, useLayoutEffect, useState } from 'react'
|
||||
import Icon from '../Icon/Icon'
|
||||
import Portal from '../Portal/Portal'
|
||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||
import { getPositionedPopoverStyles } from './GetPositionedPopoverStyles'
|
||||
import { PopoverContentProps } from './Types'
|
||||
import { usePopoverCloseOnClickOutside } from './Utils/usePopoverCloseOnClickOutside'
|
||||
@@ -25,6 +23,7 @@ const PositionedPopoverContent = ({
|
||||
disableClickOutside,
|
||||
disableMobileFullscreenTakeover,
|
||||
maxHeight,
|
||||
portal = true,
|
||||
}: PopoverContentProps) => {
|
||||
const [popoverElement, setPopoverElement] = useState<HTMLDivElement | null>(null)
|
||||
const popoverRect = useAutoElementRect(popoverElement)
|
||||
@@ -72,7 +71,7 @@ const PositionedPopoverContent = ({
|
||||
}, [popoverElement, correctInitialScrollForOverflowedContent])
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Portal disabled={!portal}>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute top-0 left-0 flex w-full min-w-80 cursor-auto flex-col',
|
||||
@@ -81,6 +80,7 @@ const PositionedPopoverContent = ({
|
||||
overrideZIndex ? overrideZIndex : 'z-dropdown-menu',
|
||||
!isDesktopScreen && !disableMobileFullscreenTakeover ? 'pt-safe-top pb-safe-bottom' : '',
|
||||
isDesktopScreen || disableMobileFullscreenTakeover ? 'invisible' : '',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
@@ -97,15 +97,7 @@ const PositionedPopoverContent = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className={classNames(disableMobileFullscreenTakeover && 'hidden', 'md:hidden')}>
|
||||
<div className="flex items-center justify-end px-3 pt-2">
|
||||
<button className="rounded-full border border-border p-1" onClick={togglePopover}>
|
||||
<Icon type="close" className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<HorizontalSeparator classes="my-2" />
|
||||
</div>
|
||||
<div className={className}>{children}</div>
|
||||
{children}
|
||||
</div>
|
||||
</Portal>
|
||||
)
|
||||
|
||||
@@ -30,16 +30,6 @@ type PopoverAnchorPointProps = {
|
||||
anchorElement?: never
|
||||
}
|
||||
|
||||
type PopoverMutuallyExclusiveProps =
|
||||
| {
|
||||
togglePopover: () => void
|
||||
disableMobileFullscreenTakeover?: never
|
||||
}
|
||||
| {
|
||||
togglePopover?: never
|
||||
disableMobileFullscreenTakeover: boolean
|
||||
}
|
||||
|
||||
type CommonPopoverProps = {
|
||||
align?: PopoverAlignment
|
||||
children: ReactNode
|
||||
@@ -48,7 +38,10 @@ type CommonPopoverProps = {
|
||||
className?: string
|
||||
disableClickOutside?: boolean
|
||||
maxHeight?: (calculatedMaxHeight: number) => number
|
||||
togglePopover?: () => void
|
||||
disableMobileFullscreenTakeover?: boolean
|
||||
title: string
|
||||
portal?: boolean
|
||||
}
|
||||
|
||||
export type PopoverContentProps = CommonPopoverProps & {
|
||||
@@ -61,5 +54,5 @@ export type PopoverContentProps = CommonPopoverProps & {
|
||||
}
|
||||
|
||||
export type PopoverProps =
|
||||
| (CommonPopoverProps & PopoverMutuallyExclusiveProps & PopoverAnchorElementProps)
|
||||
| (CommonPopoverProps & PopoverMutuallyExclusiveProps & PopoverAnchorPointProps)
|
||||
| (CommonPopoverProps & PopoverAnchorElementProps)
|
||||
| (CommonPopoverProps & PopoverAnchorPointProps)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
type Options = {
|
||||
@@ -18,19 +17,14 @@ export const usePopoverCloseOnClickOutside = ({
|
||||
}: Options) => {
|
||||
useEffect(() => {
|
||||
const closeIfClickedOutside = (event: MouseEvent) => {
|
||||
const matchesMediumBreakpoint = matchMedia(MediaQueryBreakpoints.md).matches
|
||||
|
||||
if (!matchesMediumBreakpoint) {
|
||||
return
|
||||
}
|
||||
|
||||
const target = event.target as Element
|
||||
|
||||
const isDescendantOfMenu = popoverElement?.contains(target)
|
||||
const isAnchorElement = anchorElement ? anchorElement === event.target || anchorElement.contains(target) : false
|
||||
const closestPopoverId = target.closest('[data-popover]')?.getAttribute('data-popover')
|
||||
const isDescendantOfChildPopover = closestPopoverId && childPopovers.has(closestPopoverId)
|
||||
const isDescendantOfModal = !!target.closest('[aria-modal="true"]')
|
||||
const isPopoverInModal = popoverElement?.closest('[aria-modal="true"]')
|
||||
const isDescendantOfModal = isPopoverInModal ? false : !!target.closest('[aria-modal="true"]')
|
||||
|
||||
if (!isDescendantOfMenu && !isAnchorElement && !isDescendantOfChildPopover && !isDescendantOfModal) {
|
||||
if (!disabled) {
|
||||
|
||||
Reference in New Issue
Block a user