Vercel/src/components/Modal.tsx
2025-01-27 20:58:22 -06:00

96 lines
2.3 KiB
TypeScript

'use client';
import { ReactNode, useEffect, useRef, useState } from 'react';
import { motion } from 'framer-motion';
import { clsx } from 'clsx/lite';
import useClickInsideOutside from '@/utility/useClickInsideOutside';
import { useRouter } from 'next/navigation';
import AnimateItems from './AnimateItems';
import { PATH_ROOT } from '@/site/paths';
import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion';
import useMetaThemeColor from '@/site/useMetaThemeColor';
import useEscapeHandler from '@/utility/useEscapeHandler';
export default function Modal({
onClosePath,
onClose,
className,
anchor = 'center',
children,
fast,
}: {
onClosePath?: string
onClose?: () => void
className?: string
anchor?: 'top' | 'center'
children: ReactNode
fast?: boolean
}) {
const router = useRouter();
const prefersReducedMotion = usePrefersReducedMotion();
const contentRef = useRef<HTMLDivElement>(null);
const [htmlElements, setHtmlElements] = useState<HTMLDivElement[]>([]);
useEffect(() => {
if (contentRef.current) {
setHtmlElements([contentRef.current]);
}
}, []);
useMetaThemeColor({ colorLight: '#333' });
useClickInsideOutside({
htmlElements,
onClickOutside: () => {
if (onClose) {
onClose();
} else {
router.push(
onClosePath ?? PATH_ROOT,
{ scroll: false },
);
}
},
});
useEscapeHandler(onClose, true);
return (
<motion.div
className={clsx(
'fixed inset-0 z-50 flex justify-center',
anchor === 'top'
? 'items-start pt-4 sm:pt-24'
: 'items-center',
'bg-black',
)}
initial={!prefersReducedMotion
? { backgroundColor: 'rgba(0, 0, 0, 0)' }
: false}
animate={{ backgroundColor: 'rgba(0, 0, 0, 0.80)' }}
transition={{ duration: 0.3, easing: 'easeOut' }}
>
<AnimateItems
duration={fast ? 0.1 : 0.3}
items={[<div
ref={contentRef}
key="modalContent"
className={clsx(
'w-[calc(100vw-1.5rem)] sm:w-[min(540px,90vw)]',
'p-3 rounded-lg',
'md:p-4 md:rounded-xl',
'bg-white dark:bg-black',
'dark:border dark:border-gray-800',
className,
)}
>
{children}
</div>]}
/>
</motion.div>
);
};