import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { IconClose } from '@/components/icons';
import { Transition } from 'react-transition-group';
// Used to detect clicked inside popup body
function hasSomeParentTheClass(element, classname) {
if (element && element.classList && element.classList.length && element.classList.value.split(' ').indexOf(classname) >= 0) return true;
return element.parentNode && element.parentNode !== document && hasSomeParentTheClass(element.parentNode, classname);
}
// Popup tailwind classes, these classes will not change
const defaultClasses = 'pl-popup fixed top-0 left-0 right-0 bottom-0 z-[60] bg-black/30 backdrop-blur-sm transition-opacity duration-800 opacity-0 lg:pr-1';
// Popup tailwind classes for each transition state, these classes will change according to transition state
const transitionStyles = {
entering: 'opacity-100',
entered: 'opacity-100',
exiting: 'opacity-0',
exited: 'opacity-0'
};
// Popup body tailwind classes for each transition state, these classes will change according to transition state
const transformStyles = {
entering: 'translate-y-0 md:scale-100',
entered: 'translate-y-0 md:scale-100',
exiting: 'translate-y-10 md:scale-90',
exited: 'translate-y-10 md:scale-90'
};
/*
@params
* size - controls width of the popup body
* open - controls open state of popup
* title - popup title or popup header
* actions - popup action buttons on footer
* onClose - callback function on popup close
* onOpen - callback function before popup open
* children - popup body html
* customScrolling - controls scroll behavior, if false popup header and footer will be static and popup body scoll
*/
export default function Popup({ size, open, title, actions, onClose, onOpen, children, customScrolling }) {
const [show, setShow] = useState(false);
const closeHandler = (e) => {
setShow(false);
onClose();
};
const handleBodyScroll = (status) => {
// check body scrollbar is visible
var hasVerticalScrollbar = document.body.scrollHeight > document.documentElement.clientHeight;
// classes added to body when popup opened to prevent body scroll
const bodyClasses = hasVerticalScrollbar ? ['overflow-hidden', 'lg:pr-1'] : ['overflow-hidden'];
if (typeof status !== 'undefined') {
document.querySelector('body').classList.add(...bodyClasses);
} else {
if (document.querySelectorAll('.pl-popup').length === 1) {
document.querySelector('body').classList.remove(...bodyClasses);
}
}
};
// Watch open prop to open and close popup
useEffect(() => {
setShow(open);
if (open && onOpen) {
onOpen();
}
}, [open]);
// Close popup when click outside popup body
const clickOutside = (event) => {
if (!hasSomeParentTheClass(event.target, 'pl-popup-body')) {
closeHandler();
}
};
const popupWrap = useRef(null);
// createPortal will append popup to <body>
return createPortal(
<Transition in={show} timeout={300} unmountOnExit nodeRef={popupWrap} onEnter={handleBodyScroll} onExited={handleBodyScroll}>
{(state) => (
<div ref={popupWrap} className={`${defaultClasses} ${transitionStyles[state]}`} onClick={clickOutside}>
<div className={`${transformStyles[state]} ${size === 'sm' ? 'sm:max-w-[424px]' : 'sm:max-w-[424px]'} flex h-full w-full mx-auto md:py-16 pt-16 transition-transform duration-600`}>
<div className="mt-auto w-full rounded border border-white/5 bg-secondary-bg pl-popup-body sm:m-auto" ref={popupBody}>
{title && (
<div className="flex items-center border-b border-white/5 p-4 leading-6">
<h2 className="text-xl font-medium sm:text-2xl">{title}</h2>
<button onClick={closeHandler} className="ml-auto text-2xl transition-colors duration-200 text-secondary-text hover:text-white">
<IconClose />
</button>
</div>
)}
<div className={`${customScrolling ? `p-3 sm:pb-0` : `py-3 px-4 sm:pb-0 overflow-y-auto max-h-[calc(100vh-176px)] sm:max-h-[calc(100vh-286px)]`}`}>{children}</div>
{actions && <div className="sm:p-4">{actions}</div>}
</div>
</div>
</div>
)}
</Transition>,
document.body
);
}
S
Shameem
June 8, 2023
A custom popup component with react.js, tailwindcss and react transition group
Read next
V
Customize Lenis scroll configuration, depending upon each section.
Vinod - November 13, 2024
S
Web Content Accessibility Guidelines (WCAG)
Sreenivas S Pai - August 7, 2024
A
CSS Selector : nth-child selector
Aswin - July 24, 2024
A
Image Blur Loader : Next.js
Aswin - July 22, 2024
Top comments