logo

Dialog

Um Dialog com animação de expansão

Código:


'use client';

import { forwardRef, useEffect, useRef } from 'react';

import { useOutsideClick } from '@/hook/useOutsideClick';
import { cn } from '@/lib/utils';

import { X } from 'lucide-react';
import { AnimatePresence, motion } from 'motion/react';

interface DialogProps extends React.HTMLAttributes<HTMLDivElement> {
	open: boolean;
	onClose: () => void;
	closeButton?: boolean;
}

export default function Dialog({ closeButton, open, onClose, children, ...props }: DialogProps) {
	const modalRef = useRef<HTMLDivElement>(null) as React.RefObject<HTMLDivElement>;
	useOutsideClick(modalRef, () => onClose());

	useEffect(() => {
		if (open) {
			document.body.style.overflow = 'hidden';
		} else {
			document.body.style.overflow = 'auto';
		}
	}, [open]);

	return (
		<>
			{open && (
				<div className='fixed inset-0 z-[9999] flex items-center justify-center overflow-y-auto bg-black/30 p-4 transition-all'>
					<AnimatePresence>
						{open && (
							<Wrapper
								ref={modalRef}
								className='bg-card text-card-foreground border-border relative w-full max-w-lg rounded-lg border p-4 shadow-2xl'
							>
								{closeButton && (
									<div className='text-foreground absolute right-4 top-4 cursor-pointer rounded-full p-1 transition-colors'>
										<X size={16} onClick={onClose} />
									</div>
								)}

								{children}
							</Wrapper>
						)}
					</AnimatePresence>
				</div>
			)}
		</>
	);
}

const Title = ({ children }: { children: React.ReactNode }) => {
	return (
		<>
			<h3 className='text-lg font-semibold leading-none tracking-tight'>{children}</h3>
		</>
	);
};

const Header = ({ children }: { children: React.ReactNode }) => {
	return (
		<>
			<div className='flex items-center pb-4'>{children}</div>
		</>
	);
};

const Body = ({ children }: { children: React.ReactNode }) => {
	return <div className='block py-2'>{children}</div>;
};

const Footer = ({ children }: { children: React.ReactNode }) => {
	return (
		<>
			<div className='flex items-center justify-end pt-4'>{children}</div>
		</>
	);
};

Dialog.Header = Header;
Dialog.Title = Title;
Dialog.Body = Body;
Dialog.Footer = Footer;

interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> {}

const Wrapper = forwardRef<HTMLDivElement, ContainerProps>(({ children, ...props }, ref) => {
	return (
		<>
			<motion.div
				ref={ref}
				className={cn('', props.className)}
				initial={{ opacity: 0.3, scaleX: 0.9, scaleY: 0.2 }}
				viewport={{ once: true }}
				animate={{ opacity: 1, scaleX: 1, scaleY: 1 }}
				exit={{ opacity: 0.3, scaleX: 0.9, scaleY: 0.5 }}
				transition={{
					type: 'spring',
					bounce: 0.4,
					duration: 0.5,
				}}
			>
				{children}
			</motion.div>
		</>
	);
});
yarn add motion lucide-react

Prévia: