diff --git a/src/CSSMotion.tsx b/src/CSSMotion.tsx index da82ba4..cc2f329 100644 --- a/src/CSSMotion.tsx +++ b/src/CSSMotion.tsx @@ -15,6 +15,7 @@ import { STATUS_NONE, STEP_PREPARE, STEP_START } from './interface'; import useStatus from './hooks/useStatus'; import DomWrapper from './DomWrapper'; import { isActive } from './hooks/useStepQueue'; +import { Context } from './context'; export type CSSMotionConfig = | boolean @@ -112,8 +113,8 @@ export function genCSSMotion( ({ transitionSupport } = config); } - function isSupportTransition(props: CSSMotionProps) { - return !!(props.motionName && transitionSupport); + function isSupportTransition(props: CSSMotionProps, contextMotion?: boolean) { + return !!(props.motionName && transitionSupport && contextMotion !== false); } const CSSMotion = React.forwardRef((props, ref) => { @@ -129,7 +130,9 @@ export function genCSSMotion( eventProps, } = props; - const supportMotion = isSupportTransition(props); + const { motion: contextMotion } = React.useContext(Context); + + const supportMotion = isSupportTransition(props, contextMotion); // Ref to the react node, it may be a HTMLElement const nodeRef = useRef(); @@ -181,7 +184,10 @@ export function genCSSMotion( if (!children) { // No children motionChildren = null; - } else if (status === STATUS_NONE || !isSupportTransition(props)) { + } else if ( + status === STATUS_NONE || + !isSupportTransition(props, contextMotion) + ) { // Stable children if (mergedVisible) { motionChildren = children({ ...mergedProps }, setNodeRef); @@ -190,7 +196,7 @@ export function genCSSMotion( { ...mergedProps, className: leavedClassName }, setNodeRef, ); - } else if ( forceRender || (!removeOnLeave && !leavedClassName)) { + } else if (forceRender || (!removeOnLeave && !leavedClassName)) { motionChildren = children( { ...mergedProps, style: { display: 'none' } }, setNodeRef, diff --git a/src/context.tsx b/src/context.tsx new file mode 100644 index 0000000..937bb75 --- /dev/null +++ b/src/context.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; + +interface MotionContextProps { + motion?: boolean; +} + +export const Context = React.createContext({}); + +export default function MotionProvider({ + children, + ...props +}: MotionContextProps & { children?: React.ReactNode }) { + return {children}; +} diff --git a/src/index.tsx b/src/index.tsx index 95530ba..31854d8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,6 +3,7 @@ import CSSMotionList from './CSSMotionList'; import type { CSSMotionProps } from './CSSMotion'; import type { CSSMotionListProps } from './CSSMotionList'; import type { MotionEventHandler, MotionEndEventHandler } from './interface'; +export { default as Provider } from './context'; export { CSSMotionList }; diff --git a/tests/CSSMotion.spec.tsx b/tests/CSSMotion.spec.tsx index fcdf673..d89266d 100644 --- a/tests/CSSMotion.spec.tsx +++ b/tests/CSSMotion.spec.tsx @@ -6,7 +6,8 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import classNames from 'classnames'; import { render, fireEvent } from '@testing-library/react'; -import type { CSSMotionProps } from '../src/CSSMotion'; +import type { CSSMotionProps } from '../src'; +import { Provider } from '../src'; import RefCSSMotion, { genCSSMotion } from '../src/CSSMotion'; import ReactDOM from 'react-dom'; @@ -160,27 +161,27 @@ describe('CSSMotion', () => { ); it('leaveClassName should add to dom', () => { - const genMotion = (props) => { - const {visible,leavedClassName} = props + const genMotion = props => { + const { visible, leavedClassName } = props; return ( - - {({ style, className }) => { - return ( -
- ); - }} - + + {({ style, className }) => { + return ( +
+ ); + }} + ); }; - const { container, rerender } = render(genMotion({visible:false})); + const { container, rerender } = render(genMotion({ visible: false })); rerender(genMotion({ visible: true })); @@ -189,7 +190,7 @@ describe('CSSMotion', () => { }); expect(container.querySelector('.motion-box')).toBeTruthy(); - rerender(genMotion({ visible: false,leavedClassName:'removed'})); + rerender(genMotion({ visible: false, leavedClassName: 'removed' })); act(() => { jest.runAllTimers(); }); @@ -208,8 +209,9 @@ describe('CSSMotion', () => { }); fireEvent.transitionEnd(container.querySelector('.motion-box')); - expect(container.querySelector('.motion-box')?.classList.contains('removed')).toBeFalsy(); - + expect( + container.querySelector('.motion-box')?.classList.contains('removed'), + ).toBeFalsy(); }); it('stop transition if config motion to false', () => { @@ -479,6 +481,39 @@ describe('CSSMotion', () => { }); }); + it('MotionProvider to disable motion', () => { + const Demo = ({ + motion, + visible, + }: { + motion?: boolean; + visible?: boolean; + }) => ( + + + {({ style, className }) => ( +
+ )} + + + ); + + const { container, rerender } = render(); + expect(container.querySelector('.motion-box')).toBeTruthy(); + + // hide immediately since motion is disabled + rerender(); + expect(container.querySelector('.hidden')).toBeTruthy(); + }); + it('no transition', () => { const NoCSSTransition = genCSSMotion({ transitionSupport: false,