chore: modal animations
This commit is contained in:
@@ -239,18 +239,16 @@ const ChallengeModal: FunctionComponent<Props> = ({ application, mainApplication
|
||||
ref={setModalElement}
|
||||
close={cancelChallenge}
|
||||
hideOnInteractOutside={false}
|
||||
backdropClassName={isFullScreenBlocker ? 'bg-passive-5' : ''}
|
||||
className={classNames(
|
||||
'sn-component challenge-modal relative m-0 flex h-full w-full flex-col items-center rounded border-solid border-border bg-default p-0 md:h-auto md:!w-max',
|
||||
!isMobileScreen && 'shadow-overlay-light',
|
||||
isMobileOverlay && 'shadow-overlay-light border border-solid border-border',
|
||||
)}
|
||||
>
|
||||
<Modal
|
||||
title="Authenticate"
|
||||
close={cancelChallenge}
|
||||
className={{
|
||||
content: classNames(
|
||||
'sn-component challenge-modal relative m-0 flex h-full w-full flex-col items-center rounded border-solid border-border bg-default p-0 md:h-auto md:!w-max md:border md:translucent-ui:bg-[--popover-background-color] translucent-ui:[backdrop-filter:var(--popover-backdrop-filter)]',
|
||||
!isMobileScreen && 'shadow-overlay-light',
|
||||
isMobileOverlay && 'shadow-overlay-light border border-solid border-border',
|
||||
),
|
||||
backdrop: isFullScreenBlocker ? 'bg-passive-5' : '',
|
||||
}}
|
||||
customHeader={<></>}
|
||||
customFooter={<></>}
|
||||
disableCustomHeader={isMobileScreen}
|
||||
|
||||
@@ -253,7 +253,11 @@ const ChangeEditorMenu: FunctionComponent<ChangeEditorMenuProps> = ({
|
||||
/>
|
||||
)}
|
||||
</ModalOverlay>
|
||||
<ModalOverlay isOpen={showSuperNoteConverter} close={closeSuperNoteConverter}>
|
||||
<ModalOverlay
|
||||
isOpen={showSuperNoteConverter}
|
||||
close={closeSuperNoteConverter}
|
||||
className="md:h-full md:max-h-[90%]"
|
||||
>
|
||||
{note && pendingConversionItem && (
|
||||
<SuperNoteConverter
|
||||
note={note}
|
||||
|
||||
@@ -103,10 +103,7 @@ const EditorWidthSelectionModal = ({
|
||||
customFooter={<></>}
|
||||
disableCustomHeader={isMobileScreen}
|
||||
actions={actions}
|
||||
className={{
|
||||
content: 'select-none md:min-w-[40vw]',
|
||||
description: 'flex min-h-[50vh] flex-col',
|
||||
}}
|
||||
className="flex min-h-[50vh] flex-col"
|
||||
>
|
||||
<div className="flex min-h-0 flex-grow flex-col overflow-hidden rounded bg-passive-5 p-4 pb-0">
|
||||
<div
|
||||
@@ -195,7 +192,7 @@ const EditorWidthSelectionModalWrapper = () => {
|
||||
}, [application, toggle])
|
||||
|
||||
return (
|
||||
<ModalOverlay isOpen={isOpen} close={toggle}>
|
||||
<ModalOverlay isOpen={isOpen} close={toggle} className="select-none md:min-w-[40vw]">
|
||||
<EditorWidthSelectionModal initialValue={lineWidth} handleChange={setLineWidth} close={toggle} note={note} />
|
||||
</ModalOverlay>
|
||||
)
|
||||
|
||||
@@ -117,11 +117,6 @@ const FilePreviewModal = observer(({ application }: Props) => {
|
||||
<Modal
|
||||
title={currentFile.name}
|
||||
close={dismiss}
|
||||
className={{
|
||||
content: classNames(
|
||||
'm-0 flex h-full w-full flex-col rounded bg-[--popover-background-color] p-0 shadow-main md:!h-full md:max-h-[90%] md:!w-full md:max-w-[90%]',
|
||||
),
|
||||
}}
|
||||
actions={[
|
||||
{
|
||||
label: 'Done',
|
||||
@@ -281,6 +276,7 @@ const FilePreviewModalWrapper: FunctionComponent<Props> = ({ application }) => {
|
||||
aria-label="File preview modal"
|
||||
isOpen={application.filePreviewModalController.isOpen}
|
||||
close={application.filePreviewModalController.dismiss}
|
||||
className="md:!h-full md:max-h-[90%] md:!w-full md:max-w-[90%]"
|
||||
>
|
||||
<FilePreviewModal application={application} />
|
||||
</ModalOverlay>
|
||||
|
||||
@@ -22,11 +22,7 @@ type Props = {
|
||||
title: string
|
||||
close: () => void
|
||||
actions?: ModalAction[]
|
||||
className?: {
|
||||
content?: string
|
||||
description?: string
|
||||
backdrop?: string
|
||||
}
|
||||
className?: string
|
||||
customHeader?: ReactNode
|
||||
disableCustomHeader?: boolean
|
||||
customFooter?: ReactNode
|
||||
@@ -37,7 +33,7 @@ const Modal = ({
|
||||
title,
|
||||
close,
|
||||
actions = [],
|
||||
className = {},
|
||||
className,
|
||||
customHeader,
|
||||
disableCustomHeader = false,
|
||||
customFooter,
|
||||
@@ -97,133 +93,121 @@ const Modal = ({
|
||||
return (
|
||||
<>
|
||||
<ModalAndroidBackHandler close={close} />
|
||||
<div
|
||||
className={classNames('absolute z-0 h-full w-full bg-passive-5 opacity-0 md:opacity-75', className?.backdrop)}
|
||||
role="presentation"
|
||||
onClick={close}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'z-[1] m-0 flex h-full w-full flex-col border-solid border-[--popover-border-color] bg-[--popover-background-color] [backdrop-filter:var(--popover-backdrop-filter)] p-0 md:h-auto md:max-h-[85vh] md:w-160 md:rounded md:border md:shadow-main',
|
||||
className?.content,
|
||||
)}
|
||||
>
|
||||
{customHeader && !disableCustomHeader ? (
|
||||
customHeader
|
||||
) : (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex w-full flex-shrink-0 select-none items-center justify-between rounded-t border-b border-solid border-border bg-default px-2 text-text md:px-4.5 md:py-3 md:translucent-ui:bg-transparent',
|
||||
hasTopInset ? 'pb-1.5 pt-safe-top' : 'py-1.5',
|
||||
)}
|
||||
>
|
||||
<MobileModalHeader className="flex-row items-center justify-between md:flex md:gap-0">
|
||||
{leftSlotAction ? (
|
||||
<MobileModalAction
|
||||
type={leftSlotAction.type}
|
||||
action={leftSlotAction.onClick}
|
||||
disabled={leftSlotAction.disabled}
|
||||
slot="left"
|
||||
>
|
||||
{leftSlotAction.label}
|
||||
</MobileModalAction>
|
||||
) : (
|
||||
<div className="md:hidden" />
|
||||
)}
|
||||
<div className="flex items-center justify-center gap-2 overflow-hidden text-center font-semibold text-text md:flex-grow md:text-left md:text-lg">
|
||||
{extraActions.length > 0 && (
|
||||
<>
|
||||
<MobileModalAction
|
||||
type="secondary"
|
||||
action={() => setShowAdvanced((show) => !show)}
|
||||
slot="left"
|
||||
ref={advancedOptionRef}
|
||||
>
|
||||
<div className="rounded-full border border-border p-0.5">
|
||||
<Icon type="more" />
|
||||
</div>
|
||||
</MobileModalAction>
|
||||
<Popover
|
||||
title="Advanced"
|
||||
open={showAdvanced}
|
||||
anchorElement={advancedOptionRef.current}
|
||||
disableMobileFullscreenTakeover={true}
|
||||
togglePopover={() => setShowAdvanced((show) => !show)}
|
||||
align="start"
|
||||
portal={false}
|
||||
className="!fixed divide-y divide-border border border-border"
|
||||
>
|
||||
{extraActions
|
||||
.filter((action) => action.type !== 'cancel')
|
||||
.map((action, index) => (
|
||||
<button
|
||||
className={classNames(
|
||||
'p-2 text-base font-semibold hover:bg-contrast focus:bg-info-backdrop focus:shadow-none focus:outline-none',
|
||||
action.type === 'destructive' && 'text-danger',
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
action.onClick()
|
||||
setShowAdvanced(false)
|
||||
}}
|
||||
disabled={action.disabled}
|
||||
>
|
||||
{action.label}
|
||||
</button>
|
||||
))}
|
||||
</Popover>
|
||||
</>
|
||||
)}
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap ">{title}</span>
|
||||
</div>
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
<button tabIndex={0} className="ml-2 rounded p-1 font-bold hover:bg-contrast" onClick={close}>
|
||||
<Icon type="close" />
|
||||
</button>
|
||||
</div>
|
||||
{rightSlotAction ? (
|
||||
<MobileModalAction
|
||||
type={rightSlotAction.type}
|
||||
action={rightSlotAction.onClick}
|
||||
disabled={rightSlotAction.disabled}
|
||||
slot="right"
|
||||
>
|
||||
{rightSlotAction.label}
|
||||
</MobileModalAction>
|
||||
) : null}
|
||||
</MobileModalHeader>
|
||||
</div>
|
||||
)}
|
||||
<div className={classNames('flex-grow overflow-y-auto', className.description)}>{children}</div>
|
||||
{customFooter
|
||||
? customFooter
|
||||
: sortedActions.length > 0 && (
|
||||
<div
|
||||
className={classNames(
|
||||
'hidden items-center justify-start gap-3 border-t border-border px-2.5 py-2 md:flex md:px-4 md:py-4',
|
||||
hasBottomInset && 'pb-safe-bottom',
|
||||
)}
|
||||
{customHeader && !disableCustomHeader ? (
|
||||
customHeader
|
||||
) : (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex w-full flex-shrink-0 select-none items-center justify-between rounded-t border-b border-solid border-border bg-default px-2 text-text md:px-4.5 md:py-3 md:translucent-ui:bg-transparent',
|
||||
hasTopInset ? 'pb-1.5 pt-safe-top' : 'py-1.5',
|
||||
)}
|
||||
>
|
||||
<MobileModalHeader className="flex-row items-center justify-between md:flex md:gap-0">
|
||||
{leftSlotAction ? (
|
||||
<MobileModalAction
|
||||
type={leftSlotAction.type}
|
||||
action={leftSlotAction.onClick}
|
||||
disabled={leftSlotAction.disabled}
|
||||
slot="left"
|
||||
>
|
||||
{sortedActions.map((action, index) => (
|
||||
<Button
|
||||
primary={action.type === 'primary'}
|
||||
colorStyle={action.type === 'destructive' ? 'danger' : undefined}
|
||||
key={index}
|
||||
onClick={action.onClick}
|
||||
className={classNames(
|
||||
action.mobileSlot ? 'hidden md:block' : '',
|
||||
index === firstPrimaryActionIndex && 'ml-auto',
|
||||
)}
|
||||
data-type={action.type}
|
||||
disabled={action.disabled}
|
||||
small={isMobileScreen}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{leftSlotAction.label}
|
||||
</MobileModalAction>
|
||||
) : (
|
||||
<div className="md:hidden" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-2 overflow-hidden text-center font-semibold text-text md:flex-grow md:text-left md:text-lg">
|
||||
{extraActions.length > 0 && (
|
||||
<>
|
||||
<MobileModalAction
|
||||
type="secondary"
|
||||
action={() => setShowAdvanced((show) => !show)}
|
||||
slot="left"
|
||||
ref={advancedOptionRef}
|
||||
>
|
||||
<div className="rounded-full border border-border p-0.5">
|
||||
<Icon type="more" />
|
||||
</div>
|
||||
</MobileModalAction>
|
||||
<Popover
|
||||
title="Advanced"
|
||||
open={showAdvanced}
|
||||
anchorElement={advancedOptionRef.current}
|
||||
disableMobileFullscreenTakeover={true}
|
||||
togglePopover={() => setShowAdvanced((show) => !show)}
|
||||
align="start"
|
||||
portal={false}
|
||||
className="!fixed divide-y divide-border border border-border"
|
||||
>
|
||||
{extraActions
|
||||
.filter((action) => action.type !== 'cancel')
|
||||
.map((action, index) => (
|
||||
<button
|
||||
className={classNames(
|
||||
'p-2 text-base font-semibold hover:bg-contrast focus:bg-info-backdrop focus:shadow-none focus:outline-none',
|
||||
action.type === 'destructive' && 'text-danger',
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => {
|
||||
action.onClick()
|
||||
setShowAdvanced(false)
|
||||
}}
|
||||
disabled={action.disabled}
|
||||
>
|
||||
{action.label}
|
||||
</button>
|
||||
))}
|
||||
</Popover>
|
||||
</>
|
||||
)}
|
||||
<span className="overflow-hidden text-ellipsis whitespace-nowrap ">{title}</span>
|
||||
</div>
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
<button tabIndex={0} className="ml-2 rounded p-1 font-bold hover:bg-contrast" onClick={close}>
|
||||
<Icon type="close" />
|
||||
</button>
|
||||
</div>
|
||||
{rightSlotAction ? (
|
||||
<MobileModalAction
|
||||
type={rightSlotAction.type}
|
||||
action={rightSlotAction.onClick}
|
||||
disabled={rightSlotAction.disabled}
|
||||
slot="right"
|
||||
>
|
||||
{rightSlotAction.label}
|
||||
</MobileModalAction>
|
||||
) : null}
|
||||
</MobileModalHeader>
|
||||
</div>
|
||||
)}
|
||||
<div className={classNames('flex-grow overflow-y-auto', className)}>{children}</div>
|
||||
{customFooter
|
||||
? customFooter
|
||||
: sortedActions.length > 0 && (
|
||||
<div
|
||||
className={classNames(
|
||||
'hidden items-center justify-start gap-3 border-t border-border px-2.5 py-2 md:flex md:px-4 md:py-4',
|
||||
hasBottomInset && 'pb-safe-bottom',
|
||||
)}
|
||||
>
|
||||
{sortedActions.map((action, index) => (
|
||||
<Button
|
||||
primary={action.type === 'primary'}
|
||||
colorStyle={action.type === 'destructive' ? 'danger' : undefined}
|
||||
key={index}
|
||||
onClick={action.onClick}
|
||||
className={classNames(
|
||||
action.mobileSlot ? 'hidden md:block' : '',
|
||||
index === firstPrimaryActionIndex && 'ml-auto',
|
||||
)}
|
||||
data-type={action.type}
|
||||
disabled={action.disabled}
|
||||
small={isMobileScreen}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,22 +1,42 @@
|
||||
import { mergeRefs } from '@/Hooks/mergeRefs'
|
||||
import { Dialog, DialogOptions, useDialogStore } from '@ariakit/react'
|
||||
import { ForwardedRef, forwardRef, ReactNode, useCallback } from 'react'
|
||||
import { ForwardedRef, forwardRef, ReactNode, useCallback, useId } from 'react'
|
||||
import { useModalAnimation } from '../Modal/useModalAnimation'
|
||||
import { DialogWithClose } from '@/Utils/CloseOpenModalsAndPopovers'
|
||||
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
import { classNames } from '@standardnotes/snjs'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
children: ReactNode
|
||||
animate?: 'mobile' | 'desktop' | 'both'
|
||||
animationVariant?: 'horizontal' | 'vertical'
|
||||
close: () => void
|
||||
className?: string
|
||||
backdropClassName?: string
|
||||
}
|
||||
|
||||
const ModalOverlay = forwardRef(
|
||||
(
|
||||
{ isOpen, children, animationVariant, close, ...props }: Props & Partial<DialogOptions>,
|
||||
{
|
||||
isOpen,
|
||||
children,
|
||||
animationVariant,
|
||||
close,
|
||||
className,
|
||||
backdropClassName,
|
||||
animate,
|
||||
...props
|
||||
}: Props & Partial<DialogOptions>,
|
||||
ref: ForwardedRef<HTMLDivElement>,
|
||||
) => {
|
||||
const [isMounted, setElement] = useModalAnimation(isOpen, animationVariant)
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const [isMounted, setElement] = useModalAnimation(
|
||||
isOpen,
|
||||
isMobileScreen,
|
||||
animationVariant,
|
||||
(animate === 'mobile' && !isMobileScreen) || (animate === 'desktop' && isMobileScreen),
|
||||
)
|
||||
const dialog = useDialogStore({
|
||||
open: isMounted,
|
||||
setOpen: (open) => {
|
||||
@@ -24,8 +44,23 @@ const ModalOverlay = forwardRef(
|
||||
close()
|
||||
}
|
||||
},
|
||||
animated: !isMobileScreen,
|
||||
})
|
||||
|
||||
const portalId = useId()
|
||||
const getPortalElement = useCallback(() => {
|
||||
const id = 'portal/' + portalId
|
||||
const existing = document.getElementById(id)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
const div = document.createElement('div')
|
||||
div.id = id
|
||||
div.className = 'fixed flex items-center justify-center left-0 top-0 z-modal h-full w-full pointer-events-none'
|
||||
document.body.appendChild(div)
|
||||
return div
|
||||
}, [portalId])
|
||||
|
||||
const addCloseMethod = useCallback(
|
||||
(element: HTMLDivElement | null) => {
|
||||
if (element) {
|
||||
@@ -42,11 +77,25 @@ const ModalOverlay = forwardRef(
|
||||
return (
|
||||
<Dialog
|
||||
tabIndex={0}
|
||||
className="fixed flex items-center justify-center left-0 top-0 z-modal h-full w-full"
|
||||
className={classNames(
|
||||
'pointer-events-auto m-0 flex h-full w-full flex-col border-[--popover-border-color] bg-default md:bg-[--popover-background-color] md:[backdrop-filter:var(--popover-backdrop-filter)] p-0 md:h-auto md:max-h-[85vh] md:w-160 md:rounded md:border md:shadow-main',
|
||||
className,
|
||||
)}
|
||||
backdrop={
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute z-0 h-full w-full bg-passive-5 opacity-0 pointer-events-auto',
|
||||
'md:transition-opacity md:duration-75 md:opacity-50 [&[data-enter]]:md:opacity-75',
|
||||
backdropClassName,
|
||||
)}
|
||||
onClick={close}
|
||||
/>
|
||||
}
|
||||
ref={mergeRefs([setElement, addCloseMethod, ref])}
|
||||
store={dialog}
|
||||
modal={false}
|
||||
portal={true}
|
||||
portalElement={getPortalElement}
|
||||
preventBodyScroll={true}
|
||||
hideOnInteractOutside={false}
|
||||
{...props}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useLifecycleAnimation } from '@/Hooks/useLifecycleAnimation'
|
||||
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
|
||||
export const IosModalAnimationEasing = 'cubic-bezier(.36,.66,.04,1)'
|
||||
|
||||
@@ -52,40 +51,80 @@ const Animations = {
|
||||
transformOrigin: 'right',
|
||||
},
|
||||
},
|
||||
nonMobile: {
|
||||
enter: {
|
||||
keyframes: [
|
||||
{
|
||||
transform: 'scale(0.95)',
|
||||
opacity: 0,
|
||||
},
|
||||
{
|
||||
transform: 'scale(1)',
|
||||
opacity: 1,
|
||||
},
|
||||
],
|
||||
transformOrigin: 'center',
|
||||
},
|
||||
exit: {
|
||||
keyframes: [
|
||||
{
|
||||
transform: 'scale(1)',
|
||||
opacity: 1,
|
||||
},
|
||||
{
|
||||
transform: 'scale(0.95)',
|
||||
opacity: 0,
|
||||
},
|
||||
],
|
||||
transformOrigin: 'center',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const useModalAnimation = (isOpen: boolean, variant: 'horizontal' | 'vertical' = 'vertical') => {
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const MobileOptions = {
|
||||
easing: IosModalAnimationEasing,
|
||||
duration: 250,
|
||||
fill: 'forwards',
|
||||
}
|
||||
|
||||
const NonMobileOptions = {
|
||||
duration: 75,
|
||||
}
|
||||
|
||||
export const useModalAnimation = (
|
||||
isOpen: boolean,
|
||||
isMobileScreen: boolean,
|
||||
variant: 'horizontal' | 'vertical' = 'vertical',
|
||||
disabled = false,
|
||||
) => {
|
||||
return useLifecycleAnimation(
|
||||
{
|
||||
open: isOpen,
|
||||
enter: {
|
||||
keyframes: Animations[variant].enter.keyframes,
|
||||
options: {
|
||||
easing: IosModalAnimationEasing,
|
||||
duration: 250,
|
||||
fill: 'forwards',
|
||||
},
|
||||
keyframes: isMobileScreen ? Animations[variant].enter.keyframes : Animations.nonMobile.enter.keyframes,
|
||||
options: isMobileScreen ? MobileOptions : NonMobileOptions,
|
||||
initialStyle: {
|
||||
transformOrigin: Animations[variant].enter.transformOrigin,
|
||||
transformOrigin: isMobileScreen
|
||||
? Animations[variant].enter.transformOrigin
|
||||
: Animations.nonMobile.enter.transformOrigin,
|
||||
},
|
||||
},
|
||||
enterCallback: (element) => {
|
||||
if (!isMobileScreen) {
|
||||
return
|
||||
}
|
||||
element.scrollTop = 0
|
||||
},
|
||||
exit: {
|
||||
keyframes: Animations[variant].exit.keyframes,
|
||||
options: {
|
||||
easing: IosModalAnimationEasing,
|
||||
duration: 250,
|
||||
fill: 'forwards',
|
||||
},
|
||||
keyframes: isMobileScreen ? Animations[variant].exit.keyframes : Animations.nonMobile.exit.keyframes,
|
||||
options: isMobileScreen ? MobileOptions : NonMobileOptions,
|
||||
initialStyle: {
|
||||
transformOrigin: Animations[variant].exit.transformOrigin,
|
||||
transformOrigin: isMobileScreen
|
||||
? Animations[variant].exit.transformOrigin
|
||||
: Animations.nonMobile.exit.transformOrigin,
|
||||
},
|
||||
},
|
||||
},
|
||||
!isMobileScreen,
|
||||
disabled,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,10 +165,7 @@ const NoteConflictResolutionModal = ({
|
||||
return (
|
||||
<Modal
|
||||
title="Resolve conflicts"
|
||||
className={{
|
||||
content: 'md:h-full md:w-[70vw]',
|
||||
description: 'flex flex-col overflow-x-hidden md:flex-row',
|
||||
}}
|
||||
className="flex flex-col overflow-x-hidden md:flex-row"
|
||||
actions={actions}
|
||||
close={close}
|
||||
customFooter={
|
||||
|
||||
@@ -1031,7 +1031,11 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalOverlay isOpen={this.state.showConflictResolutionModal} close={this.toggleConflictResolutionModal}>
|
||||
<ModalOverlay
|
||||
isOpen={this.state.showConflictResolutionModal}
|
||||
close={this.toggleConflictResolutionModal}
|
||||
className="md:h-full md:w-[70vw]"
|
||||
>
|
||||
<NoteConflictResolutionModal
|
||||
currentNote={this.note}
|
||||
conflictedNotes={this.state.conflictedNotes}
|
||||
|
||||
@@ -16,9 +16,7 @@ const SuperExportModal = ({ exportNotes, close }: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
title="Export notes"
|
||||
className={{
|
||||
description: 'p-4',
|
||||
}}
|
||||
className="p-4"
|
||||
close={close}
|
||||
actions={[
|
||||
{
|
||||
|
||||
@@ -35,7 +35,6 @@ const PermissionsModal = ({ callback, component, dismiss, permissionsString }: P
|
||||
mobileSlot: 'right',
|
||||
},
|
||||
]}
|
||||
className={{ content: 'md:!w-[350px]' }}
|
||||
customFooter={
|
||||
<ModalDialogButtons className="hidden md:flex">
|
||||
<Button primary fullWidth onClick={accept} className="block">
|
||||
|
||||
@@ -40,7 +40,7 @@ const PermissionsModalWrapper: FunctionComponent<Props> = ({ application }) => {
|
||||
}, [application, onAppStart])
|
||||
|
||||
return (
|
||||
<ModalOverlay isOpen={!!dialog} close={dismissPermissionsDialog}>
|
||||
<ModalOverlay isOpen={!!dialog} close={dismissPermissionsDialog} className="md:!w-[350px]">
|
||||
{dialog && (
|
||||
<PermissionsModal
|
||||
callback={dialog.callback}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useModalAnimation } from '../Modal/useModalAnimation'
|
||||
import MobileModalHeader from '../Modal/MobileModalHeader'
|
||||
import { mergeRefs } from '@/Hooks/mergeRefs'
|
||||
import { DialogWithClose } from '@/Utils/CloseOpenModalsAndPopovers'
|
||||
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
|
||||
const DisableScroll = () => {
|
||||
useDisableBodyScrollOnMobile()
|
||||
@@ -29,7 +30,8 @@ const MobilePopoverContent = ({
|
||||
id: string
|
||||
className?: string
|
||||
}) => {
|
||||
const [isMounted, setPopoverElement] = useModalAnimation(open)
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const [isMounted, setPopoverElement] = useModalAnimation(open, isMobileScreen)
|
||||
|
||||
const addCloseMethod = useCallback(
|
||||
(element: HTMLDivElement | null) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactNode, useState, useEffect } from 'react'
|
||||
import { ReactNode, useState, useEffect, useId } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
||||
type Props = {
|
||||
@@ -6,18 +6,17 @@ type Props = {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const randomPortalId = () => Math.random()
|
||||
|
||||
const Portal = ({ children, disabled = false }: Props) => {
|
||||
const [container, setContainer] = useState<HTMLElement>()
|
||||
const id = 'portal/' + useId()
|
||||
|
||||
useEffect(() => {
|
||||
const container = document.createElement('div')
|
||||
container.id = `react-portal-${randomPortalId()}`
|
||||
container.id = id
|
||||
document.body.append(container)
|
||||
setContainer(container)
|
||||
return () => container.remove()
|
||||
}, [])
|
||||
}, [id])
|
||||
|
||||
if (disabled) {
|
||||
return <>{children}</>
|
||||
|
||||
@@ -60,10 +60,7 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({ application, clo
|
||||
<Modal
|
||||
close={closePreferences}
|
||||
title="Preferences"
|
||||
className={{
|
||||
content: 'md:h-full md:!max-h-full md:!w-full',
|
||||
description: 'flex flex-col',
|
||||
}}
|
||||
className="flex flex-col"
|
||||
customHeader={
|
||||
<div
|
||||
className={classNames(
|
||||
|
||||
@@ -47,8 +47,10 @@ const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = (
|
||||
<ModalOverlay
|
||||
isOpen={application.preferencesController.isOpen}
|
||||
ref={setElement}
|
||||
animate="mobile"
|
||||
animationVariant="horizontal"
|
||||
close={application.preferencesController.closePreferences}
|
||||
className="md:h-full md:!max-h-full md:!w-full"
|
||||
>
|
||||
<PreferencesView
|
||||
closePreferences={application.preferencesController.closePreferences}
|
||||
|
||||
@@ -5,6 +5,7 @@ import HistoryModalDialog from './HistoryModalDialog'
|
||||
import { RevisionHistoryModalProps } from './RevisionHistoryModalProps'
|
||||
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
|
||||
import { useModalAnimation } from '../Modal/useModalAnimation'
|
||||
import { useMediaQuery, MutuallyExclusiveMediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
||||
|
||||
const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps> = ({ application }) => {
|
||||
const addAndroidBackHandler = useAndroidBackHandler()
|
||||
@@ -31,7 +32,8 @@ const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps> = ({ ap
|
||||
}
|
||||
}, [addAndroidBackHandler, application, isOpen])
|
||||
|
||||
const [isMounted, setElement] = useModalAnimation(isOpen)
|
||||
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||
const [isMounted, setElement] = useModalAnimation(isOpen, isMobileScreen)
|
||||
|
||||
if (!isMounted) {
|
||||
return null
|
||||
|
||||
@@ -129,14 +129,7 @@ const SessionsModalContent: FunctionComponent<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title="Active Sessions"
|
||||
close={application.closeSessionsModal}
|
||||
actions={sessionModalActions}
|
||||
className={{
|
||||
content: 'sessions-modal',
|
||||
}}
|
||||
>
|
||||
<Modal title="Active Sessions" close={application.closeSessionsModal} actions={sessionModalActions}>
|
||||
<div className="px-4 py-4">
|
||||
{refreshing ? (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -215,7 +208,11 @@ const SessionsModal: FunctionComponent<{
|
||||
application: WebApplication
|
||||
}> = ({ application }) => {
|
||||
return (
|
||||
<ModalOverlay isOpen={application.isSessionsModalVisible} close={application.closeSessionsModal}>
|
||||
<ModalOverlay
|
||||
isOpen={application.isSessionsModalVisible}
|
||||
close={application.closeSessionsModal}
|
||||
className="sessions-modal"
|
||||
>
|
||||
<SessionsModalContent application={application} />
|
||||
</ModalOverlay>
|
||||
)
|
||||
|
||||
@@ -175,10 +175,7 @@ const SuperNoteConverter = ({
|
||||
title={`Convert to ${uiFeature.displayName}`}
|
||||
close={closeDialog}
|
||||
actions={modalActions}
|
||||
className={{
|
||||
content: 'md:h-full md:max-h-[90%]',
|
||||
description: 'flex flex-col !overflow-hidden',
|
||||
}}
|
||||
className="flex flex-col !overflow-hidden"
|
||||
>
|
||||
{format === 'txt' || format === 'md' ? (
|
||||
<div className="flex items-start border-b border-border p-4 text-sm">
|
||||
|
||||
Reference in New Issue
Block a user