-
-
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> | |
| ); | |
| } |
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?
that wrapping div potentially breaks children styles.