Example usages
Modal
Let's create a nestable Modal component.
<Modal header={() => 'Modal First'} content={() => ( <p className="text-sm text-gray-500"> What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. </p> )} footer={(close) => ( <> <Button color="error" onClick={close}> Deactivate </Button> <Button color="secondary" variant="outlined" className="mr-3" onClick={close} > Cancel </Button> <Modal header={() => 'Modal Second'} content={() => ( <p className="text-sm text-gray-500"> Second Modal Content </p> )} footer={(close) => ( <> <Button color="error" onClick={close}> Deactivate </Button> <Button color="secondary" variant="outlined" className="mr-3" onClick={close} > Cancel </Button> <Modal header={() => 'Modal Third'} content={() => ( <div className="text-sm text-gray-500"> Third modal content <div> What is Lorem Ipsum? Lorem Ipsum is simply dummy text of the printing and typesetting industry. </div> </div> )} > {(isOpen) => ( <Button className="mr-auto"> Open Third Modal </Button> )} </Modal> </> )} > {(isOpen) => ( <Button className="mr-auto">Open Second Modal</Button> )} </Modal> </> )}> {(isOpen) => <Button className="mr-auto">Show Modal</Button>}</Modal>
1. Quick Way - using AttachedOverlay
component
// coming soon
2. Custom Way - using useOverlay
hook
import React, { cloneElement } from 'react';import clsx from 'clsx';import { XIcon } from '@heroicons/react/solid';import { FloatingTree, useFloatingNodeId, useFloatingParentNodeId,} from '@floating-ui/react-dom-interactions';
import { useOverlay, IOverlayProps } from 'react-useoverlay';
export interface IModalProps extends Pick< IOverlayProps, 'useDismiss' | 'interactive' | 'useMotion' | 'useFocusManager' > { header: (close: () => void) => JSX.Element | string; content: (close: () => void) => JSX.Element; footer?: (close: () => void) => JSX.Element; size?: 'small' | 'medium' | 'large' | 'x-large' | '2x-large'; className?: string; parentId?: string; children: (isOpen: boolean) => JSX.Element;}
export interface IModalTheme { initial: string[]; header: string[]; title: string[]; footer: string[]; closeIcon: string[]; overlay: string[]; size: Record<NonNullable<IModalProps['size']>, string[]>;}
export const styles: IModalTheme = { overlay: [ 'bg-black/70', 'grid', 'justify-items-center', 'items-center', 'z-50', ], initial: [ 'bg-white', 'rounded-md', 'mx-2', ' px-4 ', 'pt-5', 'pb-4', 'shadow-xl', ], header: ['flex', ' justify-between', 'pb-3'], closeIcon: ['cursor-pointer', 'p-3'], title: ['text-lg', 'leading-6', 'flex', 'items-center'], footer: ['pt-4'], size: { small: ['max-w-sm'], medium: ['max-w-md'], large: ['max-w-lg'], 'x-large': ['max-w-xl'], '2x-large': ['max-w-2xl'], },};const Modal: React.FC<IModalProps> = ({ children, ...rest }) => { const parentId = useFloatingParentNodeId();
if (parentId === null) { return ( <FloatingTree> <ModalComp {...rest}>{children}</ModalComp> </FloatingTree> ); } else { return ( <ModalComp {...rest} parentId={parentId}> {children} </ModalComp> ); }};
const ModalComp: React.FC<IModalProps> = ({ header, content, footer, useDismiss = true, interactive = true, useMotion = true, useFocusManager = true, size = 'large', className, parentId, children,}) => { const nodeId = useFloatingNodeId();
const { open, triggerProps, overlay } = useOverlay({ role: 'dialog', portalId: 'modal', nodeId, parentId, use: { click: true, dismiss: useDismiss, focus: false, hover: false }, usePortal: true, useBackdrop: true, useFocusManager, returnFocus: true, interactive, useMotion, animations: { open: { opacity: 1, transform: 'scale(1)', transition: { duration: 0.1, ease: [0.165, 0.84, 0.44, 1] }, }, close: { opacity: 0, transform: 'scale(0.95)', transition: { duration: 0.1, ease: [0.165, 0.84, 0.44, 1] }, }, }, backdropClassName: clsx( 'z-50 grid bg-slate-500 bg-opacity-30 transition-opacity items-center place-items-center', ), wrapperClassName: `${styles.size[size]} w-full`, className: clsx( `modal bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all m-2 ${styles.size[size]} w-full`, className, ), overlay: (close) => ( <> {useDismiss && ( <div className="block absolute top-0 right-0 pt-5 pr-5"> <button type="button" className="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" onClick={close} > <span className="sr-only">Close</span> <XIcon className="h-6 w-6" aria-hidden="true" /> </button> </div> )} <div className="bg-white px-4 pt-5 pb-4"> <div className="flex items-start"> <div className="mt-0 mx-4"> <h3 className="text-lg leading-6 font-medium text-gray-900"> {header(close)} </h3> <div className="my-3">{content(close)}</div> </div> </div> </div> {footer && ( <div className="bg-gray-50 px-4 py-4 sm:px-4 flex flex-row-reverse"> {footer(close)} </div> )} </> ), });
return ( <> {cloneElement(children(open), triggerProps)} {cloneElement(overlay)} </> );};
export default Modal;
What is React.cloneElement?