GitHub

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?

Previous
Popover