Created
July 16, 2020 00:49
-
-
Save stevecastaneda/aa3734f8e9fd4b8b49ed07a3630b8ade to your computer and use it in GitHub Desktop.
A Tailwind-ready Modal using React Aria from Adobe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
</> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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