Skip to content

Instantly share code, notes, and snippets.

@stevecastaneda
Created July 16, 2020 00:49
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save stevecastaneda/aa3734f8e9fd4b8b49ed07a3630b8ade to your computer and use it in GitHub Desktop.
Save stevecastaneda/aa3734f8e9fd4b8b49ed07a3630b8ade to your computer and use it in GitHub Desktop.
A Tailwind-ready Modal using React Aria from Adobe
import React, { useState, useContext, useRef } from "react";
import { useOverlayTriggerState } from "@react-stately/overlays";
import { useButton } from "@react-aria/button";
import { Modal } from "components/overlays/Modal";
export function Example() {
let state = useOverlayTriggerState({});
function onOpen() {
state.open();
}
function onClose() {
state.close();
}
// useButton ensures that focus management is handled correctly,
// across all browsers. Focus is restored to the button once the
// dialog closes.
let buttonRef = useRef(null);
let { buttonProps: openButtonProps } = useButton({ onPress: () => onOpen() }, buttonRef);
let title = "Example Title"
return (
<>
<button
className="text-xs font-medium text-blue-500 transition duration-200 ease-in-out hover:text-blue-400 active:text-blue-600 sm:text-sm focus:outline-none"
ref={buttonRef}
{...openButtonProps}
>
Open Modal
</button>
<Modal state={state}>
<Modal.Dialog
isOpen={state.isOpen}
onClose={onClose}
isDismissable
className="flex flex-col overflow-hidden rounded-lg sm:max-w-md md:max-w-4xl max-h-96 sm:max-h-9/12 focus:outline-none"
renderHeader={(titleProps) => (
<div className="p-4 bg-white border border-b border-gray-100">
<h3
{...titleProps}
className="text-sm font-medium leading-6 tracking-wide text-gray-600 uppercase sm:text-base"
>
{title}
</h3>
</div>
)}
>
<div className="relative bg-white overflow-y-auto scrolling-touch rounded-b-lg">
<p>Modal content goes here...</p>
</div>
</Modal.Dialog>
</Modal>
</>
);
}
import React, { ReactNode } from "react";
import { OverlayTriggerState } from "@react-stately/overlays";
import { Transition } from "components/transition";
import { OverlayContainer } from "@react-aria/overlays";
import { ModalDialog } from "./ModalDialog";
interface Props {
/** Pass the state of the component. */
state: OverlayTriggerState;
/** Optionally disable transitions */
disableTransitions?: boolean;
/** Children of this component. */
children: ReactNode;
}
export function Modal({ state, disableTransitions, children }: Props) {
if (disableTransitions) {
return (
<>
{state.isOpen && (
<OverlayContainer>
<div className="fixed inset-0 transition-opacity">
<div className="absolute inset-0 bg-gray-800 opacity-75"></div>
</div>
{children}
</OverlayContainer>
)}
</>
);
}
return (
<Transition show={state.isOpen}>
<OverlayContainer>
<Transition
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 transition-opacity">
<div className="absolute inset-0 bg-gray-800 opacity-75"></div>
</div>
</Transition>
<Transition
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
{children}
</Transition>
</OverlayContainer>
</Transition>
);
}
Modal.Dialog = ModalDialog;
import React, { ReactNode } from "react";
import { useOverlay, OverlayProps, usePreventScroll, useModal } from "@react-aria/overlays";
import { useDialog } from "@react-aria/dialog";
import { AriaDialogProps } from "@react-types/dialog";
import { FocusScope } from "@react-aria/focus";
interface Props extends OverlayProps, AriaDialogProps {
/** Passed to the parent component of component children, best for the dialog sizing. */
className?: string;
/** Render a header with passed in title props *//
renderHeader?: (titleProps: React.HTMLAttributes<HTMLElement>) => JSX.Element;
/** Children of this component. */
children: ReactNode;
}
export function ModalDialog(props: Props) {
// Handle interacting outside the dialog and pressing
// the the Escape key to close the modal.
let ref = React.useRef(null);
let { overlayProps } = useOverlay(props, ref);
// Prevent scrolling while the modal is open, and hide content
// outside the modal from screen readers.
usePreventScroll();
useModal();
// Get props for the dialog and its title
let { dialogProps, titleProps } = useDialog(props, ref);
return (
<div className="fixed inset-x-0 bottom-0 px-4 pb-6 sm:inset-0 sm:p-0 sm:flex sm:flex-col sm:items-center sm:justify-center">
<FocusScope contain restoreFocus autoFocus>
<div ref={ref} {...overlayProps} {...dialogProps} className={props.className}>
{props.renderHeader && props.renderHeader(titleProps)}
{props.children}
</div>
</FocusScope>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment