import { insertCSS } from "./insert-css"; import { hashObject } from "./hash-object"; /** * Animation transition can be used in TransitionGroup * */ export interface Transition { classNames: string; timeout: { enter: number; exit: number; }; mountOnEnter: boolean; unmountOnExit: boolean; } export type Position = [number, number]; const transitionPrefix = "tea-transition-"; const defaultEnterTimeout = 300; const defaultLeaveTimeout = 300; const generateFadeCSS = (transition: Transition, visibleOpacity: number) => ` .${transition.classNames}-enter { opacity: 0 !important; } .${transition.classNames}-enter-active { opacity: ${visibleOpacity} !important; transition: opacity ${transition.timeout.enter}ms ease; } .${transition.classNames}-exit { opacity: ${visibleOpacity} !important; } .${transition.classNames}-exit-active { opacity: 0 !important; transition: opacity ${transition.timeout.exit}ms ease; } `; const generateSlideCSS = ( transition: Transition, enterPosition: Position, leavePosition: Position ) => ` .${transition.classNames}-enter { opacity: 0 !important; transform: translate3d(${enterPosition[0]}px, ${enterPosition[1]}px, 0); } .${transition.classNames}-enter-active { opacity: 1 !important; transform: translate3d(0, 0, 0); transition: opacity ${transition.timeout.enter}ms ease, transform ${transition.timeout.enter}ms ease; } .${transition.classNames}-exit { opacity: 1 !important; transform: translate3d(0, 0, 0); } .${transition.classNames}-exit-active { opacity: 0 !important; transform: translate3d(${leavePosition[0]}px, ${leavePosition[1]}px, 0); transition: opacity ${transition.timeout.exit}ms ease, transform ${transition.timeout.exit}ms ease; } `; const generateCollapseCSS = ( transition: Transition, targetMaxHeight: number ) => ` .${transition.classNames}-enter { max-height: 0; overflow: hidden; } .${transition.classNames}-enter-active { max-height: ${targetMaxHeight}px; transition: max-height ${transition.timeout.enter}ms ease; overflow: hidden; } .${transition.classNames}-exit { max-height: ${targetMaxHeight}px; overflow: hidden; } .${transition.classNames}-exit-active { max-height: 0; transition: max-height ${transition.timeout.exit}ms ease; overflow: hidden; } `; const generateScaleCSS = ( transition: Transition, enterScale: number, exitScale: number, origin: string ) => ` .${transition.classNames}-enter { opacity: 0 !important; transform: scale3d(${enterScale}, ${enterScale}, ${enterScale}); transform-origin: ${origin}; } .${transition.classNames}-enter-active { opacity: 1 !important; transform: scale3d(1, 1, 1); transition: opacity ${transition.timeout.enter}ms ease, transform ${transition.timeout.enter}ms ease; transform-origin: ${origin}; } .${transition.classNames}-exit { opacity: 1 !important; transform: scale3d(1, 1, 1); transform-origin: ${origin}; } .${transition.classNames}-exit-active { opacity: 0 !important; transform: scale3d(${exitScale}, ${exitScale}, ${exitScale}); transition: opacity ${transition.timeout.exit}ms ease, transform ${transition.timeout.exit}ms ease; transform-origin: ${origin}; } `; const setupTransitions: { [hash: number]: Transition } = {}; /** * generate a fade transition, enjoy it with TransitionGroup * */ export function fade( visibleOpacity = 1, enterTimeout = defaultEnterTimeout, leaveTimeout = defaultLeaveTimeout ) { const name = `${transitionPrefix}fade`; const hash = hashObject({ name, visibleOpacity, enterTimeout, leaveTimeout }); if (!setupTransitions[hash]) { setupTransitions[hash] = { classNames: name + hash, timeout: { enter: enterTimeout, exit: leaveTimeout + 0.0001, }, unmountOnExit: true, mountOnEnter: true, }; const transition = setupTransitions[hash]; const css = generateFadeCSS(transition, visibleOpacity); insertCSS(name + hash, css); } return setupTransitions[hash]; } /** * generate a slide transition, enjoy it with TransitionGroup * */ export function slide( enterPosition: Position = [0, -30], leavePosition: Position = enterPosition, enterTimeout = defaultEnterTimeout, leaveTimeout = defaultLeaveTimeout ) { const name = `${transitionPrefix}slide`; const hash = hashObject({ name, enterPosition, leavePosition, enterTimeout, leaveTimeout, }); if (!setupTransitions[hash]) { setupTransitions[hash] = { classNames: name + hash, timeout: { enter: enterTimeout, exit: leaveTimeout, }, unmountOnExit: true, mountOnEnter: true, }; const transition = setupTransitions[hash]; const css = generateSlideCSS(transition, enterPosition, leavePosition); insertCSS(name + hash, css); } return setupTransitions[hash]; } /** * generate a slide transition, enjoy it with TransitionGroup * */ export function collapse( targetHeight: number, enterTimeout = defaultEnterTimeout, leaveTimeout = defaultLeaveTimeout ) { const name = `${transitionPrefix}collapse`; const hash = hashObject({ name, targetHeight, enterTimeout, leaveTimeout }); if (!setupTransitions[hash]) { setupTransitions[hash] = { classNames: name + hash, timeout: { enter: enterTimeout, exit: leaveTimeout, }, unmountOnExit: true, mountOnEnter: true, }; const transition = setupTransitions[hash]; const css = generateCollapseCSS(transition, targetHeight); insertCSS(name + hash, css); } return setupTransitions[hash]; } /** * generate a scale transition, enjoy it with TransitionGroup * */ export function scale( enterScale = 0.5, exitScale = enterScale, enterTimeout = defaultEnterTimeout, leaveTimeout = defaultLeaveTimeout, origin = "center" ) { const name = `${transitionPrefix}scale`; const hash = hashObject({ name, enterScale, exitScale, enterTimeout, leaveTimeout, origin, }); if (!setupTransitions[hash]) { setupTransitions[hash] = { classNames: name + hash, timeout: { enter: enterTimeout, exit: leaveTimeout, }, unmountOnExit: true, mountOnEnter: true, }; const transition = setupTransitions[hash]; const css = generateScaleCSS(transition, enterScale, exitScale, origin); insertCSS(name + hash, css); } return setupTransitions[hash]; }