| "use client"; |
|
|
| import { cn } from "@midday/ui/cn"; |
| import { Dialog, DialogTrigger } from "@midday/ui/dialog"; |
| import * as DialogPrimitive from "@radix-ui/react-dialog"; |
| import { Cross2Icon } from "@radix-ui/react-icons"; |
| import { motion, type Transition, type Variants } from "motion/react"; |
| import Image from "next/image"; |
| import type { ComponentProps, ReactNode } from "react"; |
|
|
| interface MorphingDialogProps { |
| children: ReactNode; |
| transition?: Transition; |
| } |
|
|
| export function MorphingDialog({ |
| children, |
| transition = { |
| type: "spring", |
| bounce: 0.05, |
| duration: 0.25, |
| }, |
| }: MorphingDialogProps) { |
| return <Dialog>{children}</Dialog>; |
| } |
|
|
| export const MorphingDialogTrigger = ({ |
| children, |
| className, |
| style, |
| ...props |
| }: ComponentProps<typeof DialogTrigger> & { |
| children: ReactNode; |
| style?: React.CSSProperties; |
| }) => { |
| return ( |
| <DialogTrigger asChild className={className} style={style} {...props}> |
| <motion.div style={{ cursor: "pointer" }}>{children}</motion.div> |
| </DialogTrigger> |
| ); |
| }; |
|
|
| export const MorphingDialogContainer = ({ |
| children, |
| }: { |
| children: ReactNode; |
| }) => <>{children}</>; |
|
|
| export const MorphingDialogContent = ({ |
| children, |
| className, |
| style, |
| ...props |
| }: ComponentProps<typeof DialogPrimitive.Content> & { |
| style?: React.CSSProperties; |
| }) => { |
| return ( |
| <DialogPrimitive.Portal> |
| <DialogPrimitive.Overlay className="fixed desktop:rounded-[10px] inset-0 z-50 bg-[#f6f6f3]/60 dark:bg-[#0C0C0C]/80 data-[state=closed]:animate-[dialog-overlay-hide_100ms] data-[state=open]:animate-[dialog-overlay-show_100ms]" /> |
| <DialogPrimitive.Content |
| className={cn( |
| "fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex h-auto flex-col overflow-hidden border border-border bg-background z-50 max-h-[calc(100svh-10vw)] overflow-y-scroll w-[90vw] max-w-xl data-[state=closed]:animate-[dialog-content-hide_100ms] data-[state=open]:animate-[dialog-content-show_100ms]", |
| className, |
| )} |
| style={style} |
| {...props} |
| > |
| {children} |
| </DialogPrimitive.Content> |
| </DialogPrimitive.Portal> |
| ); |
| }; |
|
|
| export const MorphingDialogImage = ({ |
| src, |
| alt, |
| className, |
| layoutId, |
| ...props |
| }: ComponentProps<typeof Image> & { |
| layoutId: string; |
| }) => { |
| return ( |
| <motion.div layoutId={layoutId} className={cn("relative", className)}> |
| <Image src={src} alt={alt} fill className="object-cover" {...props} /> |
| </motion.div> |
| ); |
| }; |
|
|
| export const MorphingDialogTitle = ({ |
| children, |
| className, |
| layoutId, |
| isInContent = false, |
| ...props |
| }: Omit<ComponentProps<"h2">, "onDrag"> & { |
| layoutId?: string; |
| isInContent?: boolean; |
| }) => { |
| if (layoutId) { |
| |
| if (isInContent) { |
| |
| return ( |
| <DialogPrimitive.Title asChild> |
| <motion.h2 |
| layoutId={layoutId} |
| className={className} |
| initial={{ opacity: 0, y: -10 }} |
| animate={{ opacity: 1, y: 0 }} |
| exit={{ opacity: 0, y: -10 }} |
| {...(props as any)} |
| > |
| {children} |
| </motion.h2> |
| </DialogPrimitive.Title> |
| ); |
| } |
| |
| return ( |
| <motion.h2 |
| layoutId={layoutId} |
| className={className} |
| initial={{ opacity: 0, y: -10 }} |
| animate={{ opacity: 1, y: 0 }} |
| exit={{ opacity: 0, y: -10 }} |
| {...(props as any)} |
| > |
| {children} |
| </motion.h2> |
| ); |
| } |
|
|
| |
| if (isInContent) { |
| return ( |
| <DialogPrimitive.Title className={className} {...props}> |
| {children} |
| </DialogPrimitive.Title> |
| ); |
| } |
|
|
| return ( |
| <h2 className={className} {...props}> |
| {children} |
| </h2> |
| ); |
| }; |
|
|
| export const MorphingDialogSubtitle = ({ |
| children, |
| className, |
| layoutId, |
| ...props |
| }: Omit<ComponentProps<"p">, "onDrag"> & { |
| layoutId?: string; |
| }) => { |
| if (layoutId) { |
| return ( |
| <motion.p |
| layoutId={layoutId} |
| className={className} |
| initial={{ opacity: 0, y: -10 }} |
| animate={{ opacity: 1, y: 0 }} |
| exit={{ opacity: 0, y: -10 }} |
| {...(props as any)} |
| > |
| {children} |
| </motion.p> |
| ); |
| } |
|
|
| return ( |
| <p className={className} {...props}> |
| {children} |
| </p> |
| ); |
| }; |
|
|
| export const MorphingDialogDescription = ({ |
| children, |
| className, |
| disableLayoutAnimation, |
| variants, |
| ...props |
| }: Omit<ComponentProps<"div">, "onDrag"> & { |
| disableLayoutAnimation?: boolean; |
| variants?: Variants; |
| }) => { |
| if (disableLayoutAnimation && variants) { |
| return ( |
| <motion.div |
| className={className} |
| initial="initial" |
| animate="animate" |
| exit="exit" |
| variants={variants} |
| {...(props as any)} |
| > |
| {children} |
| </motion.div> |
| ); |
| } |
|
|
| return ( |
| <div className={className} {...props}> |
| {children} |
| </div> |
| ); |
| }; |
|
|
| export const MorphingDialogClose = ({ className }: { className?: string }) => ( |
| <DialogPrimitive.Close |
| className={cn( |
| "absolute right-6 top-6 opacity-70 hover:opacity-100 transition-opacity focus:outline-none disabled:pointer-events-none", |
| className, |
| )} |
| > |
| <Cross2Icon className="h-4 w-4" /> |
| <span className="sr-only">Close</span> |
| </DialogPrimitive.Close> |
| ); |
|
|