-
-
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> | |
); | |
} |
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?
Updated to use
react-transition-group
4.4.1. Be sure to update your types as well. This uses a reference instead of the deprecatedfindDOMNode
that throws a warning in TS Strict mode.