-
-
Save stevecastaneda/e71a8465c3f290b98982e5d160260de2 to your computer and use it in GitHub Desktop.
// Modal Source: https://tailwindui.com/components/application-ui/overlays/modals | |
import React, { ReactNode } from "react"; | |
import { Transition } from "components/transition"; | |
interface Props { | |
/** The modal open/close state */ | |
open: boolean; | |
} | |
export function ModalExample({ open }: Props) { | |
return ( | |
<Transition show={open} className="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center"> | |
<Transition | |
enter="ease-out duration-300" | |
enterFrom="opacity-0" | |
enterTo="opacity-100" | |
leave="ease-in duration-200" | |
leaveFrom="opacity-100" | |
leaveTo="opacity-0" | |
className="fixed inset-0 transition-opacity" | |
> | |
<div className="absolute inset-0 bg-gray-800 opacity-75"></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" | |
className="bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full" | |
> | |
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
<div className="sm:flex sm:items-start"> | |
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"> | |
<svg className="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> | |
</svg> | |
</div> | |
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> | |
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-headline"> | |
Deactivate account | |
</h3> | |
<div className="mt-2"> | |
<p className="text-sm leading-5 text-gray-500"> | |
Are you sure you want to deactivate your account? All of your data will be permanently removed. This action cannot be undone. | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | |
<span className="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"> | |
<button type="button" className="inline-flex justify-center w-full rounded-md border border-transparent px-4 py-2 bg-red-600 text-base leading-6 font-medium text-white shadow-sm hover:bg-red-500 focus:outline-none focus:border-red-700 focus:shadow-outline-red transition ease-in-out duration-150 sm:text-sm sm:leading-5"> | |
Deactivate | |
</button> | |
</span> | |
<span className="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"> | |
<button type="button" className="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-base leading-6 font-medium text-gray-700 shadow-sm hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue transition ease-in-out duration-150 sm:text-sm sm:leading-5"> | |
Cancel | |
</button> | |
</span> | |
</div> | |
</Transition> | |
</Transition> | |
); | |
} |
// JSX Version by Adam Wathan: https://gist.github.com/adamwathan/e0a791aa0419098a7ece70028b2e641e | |
import React, { ReactNode, useRef, useEffect, useContext } from "react"; | |
import { CSSTransition as ReactCSSTransition } from "react-transition-group"; | |
interface TransitionProps { | |
show?: boolean; | |
enter?: string; | |
enterFrom?: string; | |
enterTo?: string; | |
leave?: string; | |
leaveFrom?: string; | |
leaveTo?: string; | |
appear?: string | boolean; | |
className?: string; | |
children: ReactNode; | |
} | |
interface ParentContextProps { | |
parent: { | |
show?: boolean; | |
appear?: string | boolean; | |
isInitialRender?: boolean; | |
}; | |
} | |
const TransitionContext = React.createContext<ParentContextProps>({ | |
parent: {}, | |
}); | |
function useIsInitialRender() { | |
const isInitialRender = useRef(true); | |
useEffect(() => { | |
isInitialRender.current = false; | |
}, []); | |
return isInitialRender.current; | |
} | |
function CSSTransition({ | |
show, | |
enter = "", | |
enterFrom = "", | |
enterTo = "", | |
leave = "", | |
leaveFrom = "", | |
leaveTo = "", | |
appear, | |
className, | |
children, | |
}: TransitionProps) { | |
const nodeRef = React.useRef<HTMLDivElement>(null); | |
const enterClasses = enter.split(" ").filter((s) => s.length); | |
const enterFromClasses = enterFrom.split(" ").filter((s) => s.length); | |
const enterToClasses = enterTo.split(" ").filter((s) => s.length); | |
const leaveClasses = leave.split(" ").filter((s) => s.length); | |
const leaveFromClasses = leaveFrom.split(" ").filter((s) => s.length); | |
const leaveToClasses = leaveTo.split(" ").filter((s) => s.length); | |
function addClasses(classes: string[]) { | |
if (nodeRef.current) nodeRef.current.classList.add(...classes); | |
} | |
function removeClasses(classes: string[]) { | |
if (nodeRef.current) nodeRef.current.classList.remove(...classes); | |
} | |
return ( | |
<ReactCSSTransition | |
appear={appear} | |
unmountOnExit | |
in={show} | |
nodeRef={nodeRef} | |
addEndListener={(done) => { | |
nodeRef.current?.addEventListener("transitionend", done, false); | |
}} | |
onEnter={() => { | |
addClasses([...enterClasses, ...enterFromClasses]); | |
}} | |
onEntering={() => { | |
removeClasses(enterFromClasses); | |
addClasses(enterToClasses); | |
}} | |
onEntered={() => { | |
removeClasses([...enterToClasses, ...enterClasses]); | |
}} | |
onExit={() => { | |
addClasses([...leaveClasses, ...leaveFromClasses]); | |
}} | |
onExiting={() => { | |
removeClasses(leaveFromClasses); | |
addClasses(leaveToClasses); | |
}} | |
onExited={() => { | |
removeClasses([...leaveToClasses, ...leaveClasses]); | |
}} | |
> | |
<div ref={nodeRef} className={className}> | |
{children} | |
</div> | |
</ReactCSSTransition> | |
); | |
} | |
export function Transition({ show, appear, ...rest }: TransitionProps) { | |
const { parent } = useContext(TransitionContext); | |
const isInitialRender = useIsInitialRender(); | |
const isChild = show === undefined; | |
if (isChild) { | |
return <CSSTransition appear={parent.appear || !parent.isInitialRender} show={parent.show} {...rest} />; | |
} | |
return ( | |
<TransitionContext.Provider | |
value={{ | |
parent: { | |
show, | |
isInitialRender, | |
appear, | |
}, | |
}} | |
> | |
<CSSTransition appear={appear} show={show} {...rest} /> | |
</TransitionContext.Provider> | |
); | |
} |
Added some missing declarations that popped up in the latest version of Typescript.
Updated to use react-transition-group
4.4.1. Be sure to update your types as well. This uses a reference instead of the deprecated findDOMNode
that throws a warning in TS Strict mode.
Refactored with help of @RobinMalfait. Thanks!
that wrapping div potentially breaks children styles.
that wrapping div potentially breaks children styles.
The TransitionGroup
component will take a component={null}
prop but I haven't figured out how to get that on the Transition or CSSTransition components. Any ideas there?
I agree, ideally, you wouldn't render any container at all once the transition was complete.
@vincaslt Try out the new version I just posted.
A bit more verbose, but seems to work alright, thanks
@vincaslt Yup, pros and cons but I don't see any other way since they each require their own reference. Open to opinions on how we can improve it.
I wonder if you would know how this could be solved: https://gist.github.com/adamwathan/3b9f3ad1a285a2d1b482769aeb862467#gistcomment-3391161
I wonder if you would know how this could be solved: https://gist.github.com/adamwathan/3b9f3ad1a285a2d1b482769aeb862467#gistcomment-3391161
@vincaslt I believe I ran into this. Does this solve your issue?
For nested transitions, if you don't pass show it's based on the parent show state. The parent automatically waits for all children to finish transitioning before removing, so it needs no props except show.