Last active
July 26, 2020 20:17
-
-
Save stevecastaneda/e71a8465c3f290b98982e5d160260de2 to your computer and use it in GitHub Desktop.
[Typescript] Modified Transition React Component to Support Nested Transitions w/ Tailwind
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
// 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> | |
); | |
} |
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
// 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> | |
); | |
} |
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?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@vincaslt Try out the new version I just posted.