parent
41c7e80873
commit
d007d90392
|
|
@ -79,8 +79,9 @@ export function SwipeImages({
|
|||
height: 600,
|
||||
}}
|
||||
disableContextMenu
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
onPreview?.(images, i)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
|
|
@ -104,7 +105,10 @@ export function SwipeImages({
|
|||
) : uniqImages?.length >= 1 ?
|
||||
(
|
||||
<Image
|
||||
onClick={() => onPreview?.(uniqImages)}
|
||||
onClick={(e) => {
|
||||
onPreview?.(uniqImages)
|
||||
e.stopPropagation()
|
||||
}}
|
||||
className="size-full rounded-none object-cover sm:transition-transform sm:duration-300 sm:ease-in-out sm:group-hover:scale-105"
|
||||
alt="cover"
|
||||
src={uniqImages[0]}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useSetAtom } from "jotai"
|
|||
import type { PointerEventHandler, SyntheticEvent } from "react"
|
||||
import {
|
||||
createElement,
|
||||
forwardRef,
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
|
|
@ -33,220 +34,229 @@ export const ModalInternal: Component<{
|
|||
|
||||
isTop: boolean
|
||||
onClose?: (open: boolean) => void
|
||||
}> = memo(function Modal({
|
||||
item,
|
||||
index,
|
||||
onClose: onPropsClose,
|
||||
children,
|
||||
isTop,
|
||||
}) {
|
||||
const setStack = useSetAtom(modalStackAtom)
|
||||
const close = useEventCallback(() => {
|
||||
setStack((p) => p.filter((modal) => modal.id !== item.id))
|
||||
onPropsClose?.(false)
|
||||
})
|
||||
}> = memo(
|
||||
forwardRef(function Modal(
|
||||
{ item, index, onClose: onPropsClose, children, isTop },
|
||||
ref: any,
|
||||
) {
|
||||
const setStack = useSetAtom(modalStackAtom)
|
||||
const close = useEventCallback(() => {
|
||||
setStack((p) => p.filter((modal) => modal.id !== item.id))
|
||||
onPropsClose?.(false)
|
||||
})
|
||||
|
||||
const onClose = useCallback(
|
||||
(open: boolean): void => {
|
||||
if (!open) {
|
||||
const onClose = useCallback(
|
||||
(open: boolean): void => {
|
||||
if (!open) {
|
||||
close()
|
||||
}
|
||||
},
|
||||
[close],
|
||||
)
|
||||
|
||||
const opaque = useUISettingKey("modalOpaque")
|
||||
|
||||
const {
|
||||
CustomModalComponent,
|
||||
modalClassName,
|
||||
content,
|
||||
title,
|
||||
clickOutsideToDismiss,
|
||||
modalContainerClassName,
|
||||
wrapper: Wrapper = Fragment,
|
||||
max,
|
||||
icon,
|
||||
|
||||
draggable = false,
|
||||
} = item
|
||||
const zIndexStyle = useMemo(
|
||||
() => ({ zIndex: MODAL_STACK_Z_INDEX + index + 1 }),
|
||||
[index],
|
||||
)
|
||||
const dismiss = useCallback(
|
||||
(e: SyntheticEvent) => {
|
||||
e.stopPropagation()
|
||||
close()
|
||||
}
|
||||
},
|
||||
[close],
|
||||
)
|
||||
},
|
||||
[close],
|
||||
)
|
||||
|
||||
const opaque = useUISettingKey("modalOpaque")
|
||||
|
||||
const {
|
||||
CustomModalComponent,
|
||||
modalClassName,
|
||||
content,
|
||||
title,
|
||||
clickOutsideToDismiss,
|
||||
modalContainerClassName,
|
||||
wrapper: Wrapper = Fragment,
|
||||
max,
|
||||
icon,
|
||||
|
||||
draggable = false,
|
||||
} = item
|
||||
const zIndexStyle = useMemo(() => ({ zIndex: MODAL_STACK_Z_INDEX + index + 1 }), [index])
|
||||
const dismiss = useCallback(
|
||||
(e: SyntheticEvent) => {
|
||||
e.stopPropagation()
|
||||
close()
|
||||
},
|
||||
[close],
|
||||
)
|
||||
|
||||
const animateController = useAnimationControls()
|
||||
useEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
animateController.start(modalMontionConfig.animate)
|
||||
})
|
||||
}, [animateController])
|
||||
const noticeModal = useCallback(() => {
|
||||
animateController
|
||||
.start({
|
||||
scale: 1.05,
|
||||
transition: {
|
||||
duration: 0.06,
|
||||
},
|
||||
const animateController = useAnimationControls()
|
||||
useEffect(() => {
|
||||
requestAnimationFrame(() => {
|
||||
animateController.start(modalMontionConfig.animate)
|
||||
})
|
||||
.then(() => {
|
||||
animateController.start({
|
||||
scale: 1,
|
||||
}, [animateController])
|
||||
const noticeModal = useCallback(() => {
|
||||
animateController
|
||||
.start({
|
||||
scale: 1.05,
|
||||
transition: {
|
||||
duration: 0.06,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
animateController.start({
|
||||
scale: 1,
|
||||
})
|
||||
})
|
||||
}, [animateController])
|
||||
|
||||
const dragController = useDragControls()
|
||||
const handleDrag: PointerEventHandler<HTMLDivElement> = useCallback(
|
||||
(e) => {
|
||||
if (draggable) {
|
||||
dragController.start(e)
|
||||
}
|
||||
},
|
||||
[dragController, draggable],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (isTop) return
|
||||
animateController.start({
|
||||
scale: 0.96,
|
||||
y: 10,
|
||||
})
|
||||
}, [animateController])
|
||||
|
||||
const dragController = useDragControls()
|
||||
const handleDrag: PointerEventHandler<HTMLDivElement> = useCallback(
|
||||
(e) => {
|
||||
if (draggable) {
|
||||
dragController.start(e)
|
||||
return () => {
|
||||
try {
|
||||
animateController.stop()
|
||||
animateController.start({
|
||||
scale: 1,
|
||||
y: 0,
|
||||
})
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
},
|
||||
[dragController, draggable],
|
||||
)
|
||||
}, [isTop])
|
||||
|
||||
useEffect(() => {
|
||||
if (isTop) return
|
||||
animateController.start({
|
||||
scale: 0.96,
|
||||
y: 10,
|
||||
})
|
||||
return () => {
|
||||
try {
|
||||
animateController.stop()
|
||||
animateController.start({
|
||||
scale: 1,
|
||||
y: 0,
|
||||
})
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
const modalContentRef = useRef<HTMLDivElement>(null)
|
||||
const ModalProps: ModalContentPropsInternal = useMemo(
|
||||
() => ({
|
||||
dismiss: close,
|
||||
}),
|
||||
[close],
|
||||
)
|
||||
|
||||
const ModalContextProps = useMemo<CurrentModalContentProps>(
|
||||
() => ({
|
||||
...ModalProps,
|
||||
ref: modalContentRef,
|
||||
}),
|
||||
[ModalProps],
|
||||
)
|
||||
const finalChildren = (
|
||||
<CurrentModalContext.Provider value={ModalContextProps}>
|
||||
{children ?? createElement(content, ModalProps)}
|
||||
</CurrentModalContext.Provider>
|
||||
)
|
||||
|
||||
const edgeElementRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
if (CustomModalComponent) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Dialog.Root open onOpenChange={onClose}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.DialogTitle className="sr-only">
|
||||
{title}
|
||||
</Dialog.DialogTitle>
|
||||
<Dialog.Content asChild>
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 z-20 overflow-auto",
|
||||
modalContainerClassName,
|
||||
)}
|
||||
onClick={clickOutsideToDismiss ? dismiss : undefined}
|
||||
style={zIndexStyle}
|
||||
>
|
||||
<div
|
||||
className={cn("contents", modalClassName)}
|
||||
onClick={stopPropagation}
|
||||
ref={ref}
|
||||
>
|
||||
<CustomModalComponent>{finalChildren}</CustomModalComponent>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
}, [isTop])
|
||||
|
||||
const modalContentRef = useRef<HTMLDivElement>(null)
|
||||
const ModalProps: ModalContentPropsInternal = useMemo(
|
||||
() => ({
|
||||
dismiss: close,
|
||||
}),
|
||||
[close],
|
||||
)
|
||||
|
||||
const ModalContextProps = useMemo<CurrentModalContentProps>(
|
||||
() => ({
|
||||
...ModalProps,
|
||||
ref: modalContentRef,
|
||||
}),
|
||||
[ModalProps],
|
||||
)
|
||||
const finalChildren = (
|
||||
<CurrentModalContext.Provider value={ModalContextProps}>
|
||||
{children ?? createElement(content, ModalProps)}
|
||||
</CurrentModalContext.Provider>
|
||||
)
|
||||
|
||||
const edgeElementRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
if (CustomModalComponent) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Dialog.Root open onOpenChange={onClose}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.DialogTitle className="sr-only">{title}</Dialog.DialogTitle>
|
||||
<Dialog.Content asChild>
|
||||
<div
|
||||
ref={edgeElementRef}
|
||||
style={zIndexStyle}
|
||||
className={cn(
|
||||
"fixed inset-0 z-20 overflow-auto",
|
||||
"center fixed inset-0 z-20 flex",
|
||||
modalContainerClassName,
|
||||
)}
|
||||
onClick={clickOutsideToDismiss ? dismiss : undefined}
|
||||
style={zIndexStyle}
|
||||
onClick={clickOutsideToDismiss ? dismiss : noticeModal}
|
||||
>
|
||||
<div
|
||||
className={cn("contents", modalClassName)}
|
||||
<m.div
|
||||
ref={ref}
|
||||
style={zIndexStyle}
|
||||
{...modalMontionConfig}
|
||||
animate={animateController}
|
||||
className={cn(
|
||||
"relative flex flex-col overflow-hidden rounded-lg",
|
||||
opaque ?
|
||||
"bg-theme-modal-background-opaque" :
|
||||
"bg-theme-modal-background backdrop-blur-sm",
|
||||
"shadow-modal p-2",
|
||||
max ?
|
||||
"h-[90vh] w-[90vw]" :
|
||||
"max-h-[70vh] min-w-[300px] max-w-[90vw] lg:max-h-[calc(100vh-20rem)] lg:max-w-[70vw]",
|
||||
|
||||
"border border-slate-200 dark:border-neutral-800",
|
||||
modalClassName,
|
||||
)}
|
||||
onClick={stopPropagation}
|
||||
drag
|
||||
dragControls={dragController}
|
||||
dragElastic={0}
|
||||
dragListener={false}
|
||||
dragMomentum={false}
|
||||
dragConstraints={edgeElementRef}
|
||||
whileDrag={{
|
||||
cursor: "grabbing",
|
||||
}}
|
||||
>
|
||||
<CustomModalComponent>{finalChildren}</CustomModalComponent>
|
||||
</div>
|
||||
<div
|
||||
className="relative flex items-center"
|
||||
onPointerDownCapture={handleDrag}
|
||||
>
|
||||
<Dialog.Title className="flex shrink-0 grow items-center gap-2 px-4 py-1 text-lg font-semibold">
|
||||
{icon && <span className="size-4">{icon}</span>}
|
||||
|
||||
<span>{title}</span>
|
||||
</Dialog.Title>
|
||||
<Dialog.DialogClose
|
||||
className="center p-2"
|
||||
tabIndex={1}
|
||||
onClick={close}
|
||||
>
|
||||
<i className="i-mgc-close-cute-re" />
|
||||
</Dialog.DialogClose>
|
||||
</div>
|
||||
<Divider className="my-2 shrink-0 border-slate-200 opacity-80 dark:border-neutral-800" />
|
||||
|
||||
<div className="min-h-0 shrink grow overflow-auto px-4 py-2">
|
||||
{finalChildren}
|
||||
</div>
|
||||
</m.div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Dialog.Root open onOpenChange={onClose}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content asChild>
|
||||
<div
|
||||
ref={edgeElementRef}
|
||||
style={zIndexStyle}
|
||||
className={cn(
|
||||
"center fixed inset-0 z-20 flex",
|
||||
modalContainerClassName,
|
||||
)}
|
||||
onClick={clickOutsideToDismiss ? dismiss : noticeModal}
|
||||
>
|
||||
<m.div
|
||||
style={zIndexStyle}
|
||||
{...modalMontionConfig}
|
||||
animate={animateController}
|
||||
className={cn(
|
||||
"relative flex flex-col overflow-hidden rounded-lg",
|
||||
opaque ?
|
||||
"bg-theme-modal-background-opaque" :
|
||||
"bg-theme-modal-background backdrop-blur-sm",
|
||||
"shadow-modal p-2",
|
||||
max ?
|
||||
"h-[90vh] w-[90vw]" :
|
||||
"max-h-[70vh] min-w-[300px] max-w-[90vw] lg:max-h-[calc(100vh-20rem)] lg:max-w-[70vw]",
|
||||
|
||||
"border border-slate-200 dark:border-neutral-800",
|
||||
modalClassName,
|
||||
)}
|
||||
onClick={stopPropagation}
|
||||
drag
|
||||
dragControls={dragController}
|
||||
dragElastic={0}
|
||||
dragListener={false}
|
||||
dragMomentum={false}
|
||||
dragConstraints={edgeElementRef}
|
||||
whileDrag={{
|
||||
cursor: "grabbing",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="relative flex items-center"
|
||||
onPointerDownCapture={handleDrag}
|
||||
>
|
||||
<Dialog.Title className="flex shrink-0 grow items-center gap-2 px-4 py-1 text-lg font-semibold">
|
||||
{icon && <span className="size-4">{icon}</span>}
|
||||
|
||||
<span>{title}</span>
|
||||
</Dialog.Title>
|
||||
<Dialog.DialogClose className="center p-2" tabIndex={1} onClick={close}>
|
||||
<i className="i-mgc-close-cute-re" />
|
||||
</Dialog.DialogClose>
|
||||
</div>
|
||||
<Divider className="my-2 shrink-0 border-slate-200 opacity-80 dark:border-neutral-800" />
|
||||
|
||||
<div className="min-h-0 shrink grow overflow-auto px-4 py-2">
|
||||
{finalChildren}
|
||||
</div>
|
||||
|
||||
</m.div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
</Wrapper>
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,23 +1,25 @@
|
|||
import { m } from "@renderer/components/common/Motion"
|
||||
import type { ForwardedRef } from "react"
|
||||
import { forwardRef } from "react"
|
||||
|
||||
import { RootPortal } from "../../portal"
|
||||
|
||||
export const ModalOverlay = ({
|
||||
onClick,
|
||||
zIndex,
|
||||
}: {
|
||||
onClick?: () => void
|
||||
zIndex?: number
|
||||
}) => (
|
||||
<RootPortal>
|
||||
<m.div
|
||||
id="modal-overlay"
|
||||
onClick={onClick}
|
||||
className="fixed inset-0 z-[11] bg-zinc-50/80 dark:bg-neutral-900/80"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
style={{ zIndex }}
|
||||
/>
|
||||
</RootPortal>
|
||||
export const ModalOverlay = forwardRef(
|
||||
(
|
||||
{ onClick, zIndex }: { onClick?: () => void, zIndex?: number },
|
||||
ref: ForwardedRef<HTMLDivElement>,
|
||||
) => (
|
||||
<RootPortal>
|
||||
<m.div
|
||||
ref={ref}
|
||||
id="modal-overlay"
|
||||
onClick={onClick}
|
||||
className="fixed inset-0 z-[11] bg-zinc-50/80 dark:bg-neutral-900/80"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
style={{ zIndex }}
|
||||
/>
|
||||
</RootPortal>
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,19 +8,32 @@ import { useEntry } from "@renderer/store/entry/hooks"
|
|||
import { usePreviewImages } from "../../components/ui/image/hooks"
|
||||
import type { UniversalItemProps } from "./types"
|
||||
|
||||
export function PictureItem({ entryId, entryPreview, translation }: UniversalItemProps) {
|
||||
export function PictureItem({
|
||||
entryId,
|
||||
entryPreview,
|
||||
translation,
|
||||
}: UniversalItemProps) {
|
||||
const entry = useEntry(entryId) || entryPreview
|
||||
|
||||
const isActive = useRouteParamsSelector(({ entryId }) => entryId === entry?.entries.id)
|
||||
const isActive = useRouteParamsSelector(
|
||||
({ entryId }) => entryId === entry?.entries.id,
|
||||
)
|
||||
|
||||
const previewImage = usePreviewImages()
|
||||
if (!entry) return <ReactVirtuosoItemPlaceholder />
|
||||
return (
|
||||
<GridItem entryId={entryId} entryPreview={entryPreview} translation={translation}>
|
||||
<GridItem
|
||||
entryId={entryId}
|
||||
entryPreview={entryPreview}
|
||||
translation={translation}
|
||||
>
|
||||
<div className="relative flex gap-2 overflow-x-auto">
|
||||
<SwipeImages
|
||||
images={entry.entries.images}
|
||||
className={cn("aspect-square w-full shrink-0 rounded-md", isActive && "rounded-b-none")}
|
||||
className={cn(
|
||||
"aspect-square w-full shrink-0 rounded-md",
|
||||
isActive && "rounded-b-none",
|
||||
)}
|
||||
imgClassName="object-cover"
|
||||
uniqueKey={entryId}
|
||||
onPreview={(images, i) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue