From 6ad648ee06cc7112b4989159588d11b2f67ef17b Mon Sep 17 00:00:00 2001 From: madou Date: Thu, 23 May 2019 21:07:51 +1000 Subject: [PATCH] feat(animator): adds first class self targeted animations api --- README.md | 4 +- doczrc.js | 8 +- packages/yubaba/package.json | 2 +- .../yubaba/src/Animator/__docs__/docs.mdx | 77 +++++++-- .../src/Animator/__snapshots__/test.tsx.snap | 18 +++ packages/yubaba/src/Animator/index.tsx | 110 ++++++++++--- packages/yubaba/src/Animator/test.tsx | 149 ++++++++++++++++++ .../yubaba/src/VisibilityManager/test.tsx | 30 ++++ .../src/__docs__/1-introduction/docs.mdx | 2 +- .../src/__docs__/2-getting-started/docs.mdx | 2 +- .../src/__docs__/3-advanced-usage/docs.mdx | 2 +- .../src/__docs__/4-custom-animations/docs.mdx | 6 +- packages/yubaba/src/__tests__/utils.tsx | 2 + packages/yubaba/src/animations/Move/index.tsx | 9 +- .../__docz__/TripeHoverMenu.tsx | 2 +- .../ReshapingContainer/__docz__/docs.mdx | 22 ++- .../ReshapingContainer/__docz__/styled.tsx | 1 - .../animations/ReshapingContainer/index.tsx | 4 +- .../yubaba/src/animations/Reveal/index.tsx | 9 +- .../yubaba/src/animations/Reveal/stories.tsx | 2 +- .../__docz__/docs.mdx | 2 +- .../RevealReshapingContainer/index.tsx | 2 +- .../src/animations/Swipe/__docs__/docs.mdx | 2 +- packages/yubaba/src/lib/dom.tsx | 14 +- packages/yubaba/src/lib/noop.tsx | 3 +- 25 files changed, 411 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 0699860..7c12235 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # yubaba 🧙✨ -/juːba:ba/ out of the box animated experiences for [React.js](https://reactjs.org/) 🧙✨ +/juːbaːba/ out of the box animated experiences for [React.js](https://reactjs.org/) 🧙✨ [![npm](https://img.shields.io/npm/v/yubaba.svg)](https://www.npmjs.com/package/yubaba) [![npm bundle size (minified + gzip)](https://badgen.net/bundlephobia/minzip/yubaba)](https://bundlephobia.com/result?p=yubaba) @@ -44,7 +44,7 @@ yarn add yubaba react@^16.4.x react-dom@^16.4.x emotion@^10.x.x import Animator, { Move } from 'yubaba'; ({ isLarge }) => ( - + {anim =>
} ); diff --git a/doczrc.js b/doczrc.js index 4aad412..3cba2c5 100644 --- a/doczrc.js +++ b/doczrc.js @@ -7,7 +7,7 @@ const primaryText = 'rgba(255, 255, 255, 0.95)'; const background = `linear-gradient(135deg, ${altPrimary} 25%, ${primary} 100%)`; module.exports = { - title: 'yubaba 🧙✨', + title: `yubaba ${pkg.description}`, description: `yubaba ${pkg.description}`, typescript: true, dest: '/docs', @@ -44,6 +44,7 @@ module.exports = { display: none; } + a[class^='MenuLink__LinkAnchor-'], a[class^='MenuLink__createLink-'] { font-weight: 400; @@ -55,6 +56,11 @@ module.exports = { a[class^='SmallLink__Link'] { opacity: 0.65; + + :hover, + :focus { + opacity: 0.9; + } } `, h1: css` diff --git a/packages/yubaba/package.json b/packages/yubaba/package.json index 236863f..38302c3 100644 --- a/packages/yubaba/package.json +++ b/packages/yubaba/package.json @@ -7,7 +7,7 @@ "main": "dist/cjs/packages/yubaba/src/index.js", "module": "dist/esm/packages/yubaba/src/index.js", "sideEffects": false, - "description": "/juːba:ba/ out of the box animated experiences for React.js 🧙✨", + "description": "/juːbaːba/ out of the box animated experiences for React.js 🧙✨", "keywords": [ "react", "flip", diff --git a/packages/yubaba/src/Animator/__docs__/docs.mdx b/packages/yubaba/src/Animator/__docs__/docs.mdx index 1bd6c06..27b733f 100644 --- a/packages/yubaba/src/Animator/__docs__/docs.mdx +++ b/packages/yubaba/src/Animator/__docs__/docs.mdx @@ -8,24 +8,83 @@ import Animator from '../index'; # Animator -This is the brains component. -When unmounting or flipping the `in` prop from `true` to `false`, -it will execute all the animations **top to bottom** if a matching Animator pair (either itself or another Animator element) is found within `50ms`. +You will use this component with most animations. +It does a few things: -> **Tip -** See [Getting started](/getting-started) for more information on how to use this component. +- take snapshots of the DOM +- pass data to the animations +- execute and orchestrate the animations -## Usage +You'll really only need to be concerned with _execute and orchestrate the animations_. +There are three ways you can execute animations, +listed below. + +> **Tip -** Missing some context? Have a look at [Getting started](/getting-started) first. + +## Animate unmounted to mounted + +You'll find this is the goto way for triggering animations. +It will animate between an element that unmounts and an element that mounts over a state change. +[Moving to another element](/getting-started#moving-to-another-element) is a good example of this. + +```js +import Animator, { Move } from 'yubaba'; + +({ isShown }) => [ + !isShown && ( + + {children} + + ), + isShown && ( + + {children} + + ), +]; +``` + +## Animate self + +Using the `triggerSelfKey` prop to force an animation on itself over a state change. +[Moving to the same element](/getting-started#moving-to-the-same-element) is a good example of this. ```js -import Animator from 'yubaba'; +import Animator, { Move } from 'yubaba'; -const ListItem = ({ index }) => ( - - {({ ref, style, className }) =>
} +({ children, itemId }) => ( + + {children} ); ``` +> **Tip -** You can't use this with the `in` prop, +> if you try you'll get a dev error. + +## Animate persisted to mounted + +Animate between a react element that never unmounts and one that mounts/unmounts over a state change. +Using the `in` prop to mark a persisted component if it is considered in or not. +[Moving from a persisted element](/getting-started#moving-from-a-persisted-element) is a good example of this. + +```js +import Animator, { Move } from 'yubaba'; + +({ isShown }) => [ + + {children} + , + isShown && ( + + {children} + + ), +]; +``` + +> **Tip -** You can also use the same method to animate over unmounted to persisted. + ## Props diff --git a/packages/yubaba/src/Animator/__snapshots__/test.tsx.snap b/packages/yubaba/src/Animator/__snapshots__/test.tsx.snap index 3196ee5..a90302a 100644 --- a/packages/yubaba/src/Animator/__snapshots__/test.tsx.snap +++ b/packages/yubaba/src/Animator/__snapshots__/test.tsx.snap @@ -1,5 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[` persisted animations should throw when changing into "in" after initial mount 1`] = ` +"yubaba v0.0.0 + +You're switching between persisted and unpersisted, don't do this. Either always set the \\"in\\" prop as true or false, or keep as undefined." +`; + +exports[` self targetted animations should throw when changing into "triggerSelfKey" after initial mount 1`] = ` +"yubaba v0.0.0 + +You're switching between self triggering modes, don't do this. Either always set the \\"triggerSelfKey\\" prop, or keep as undefined." +`; + +exports[` self targetted animations should throw when using both "in" and "triggerSelfKey" props after initial mount 1`] = ` +"yubaba v0.0.0 + +Don't use \\"in\\" and \\"triggerSelfKey\\" together. If your element is persisted use \\"in\\". If your element is targeting itself for animations use \\"triggerSelfKey\\"." +`; + exports[` should pass dom data to child animation 1`] = ` Array [ Object { diff --git a/packages/yubaba/src/Animator/index.tsx b/packages/yubaba/src/Animator/index.tsx index cbc89e8..4c53b9e 100644 --- a/packages/yubaba/src/Animator/index.tsx +++ b/packages/yubaba/src/Animator/index.tsx @@ -36,7 +36,7 @@ export interface ChildProps { className?: string; } -export interface State { +export interface AnimatorState { childProps: ChildProps; animationsMarkup: React.ReactPortal[]; } @@ -48,11 +48,18 @@ export interface AnimatorProps extends CollectorChildrenProps, InjectedProps { name: string; /** - * Used alternatively to the implicit animation triggering via unmounting or mounting of Animator components. - * Only use `in` if your component is expected to persist through the entire lifecyle of the app. - * When you transition to the "next page" make sure to set your "in" to false. When you transition - * back to the original page set the "in" prop back to true. This lets the Animator components know when to - * execute the animations. + * Will trigger animations over itself when this prop changes. + * + * You can't use the with the "in" prop. + */ + triggerSelfKey?: string; + + /** + * Use if your element is expected to persist through an animation. + * When you transition to the next state set your "in" to false and vice versa. + * This lets the Animator components know when to execute the animations. + * + * You can't use this with the "triggerSelfKey". */ in?: boolean; @@ -75,7 +82,7 @@ export interface AnimatorProps extends CollectorChildrenProps, InjectedProps { container: HTMLElement | (() => HTMLElement); } -export default class Animator extends React.PureComponent { +export default class Animator extends React.PureComponent { static displayName = 'Animator'; static defaultProps = { @@ -84,7 +91,7 @@ export default class Animator extends React.PureComponent container: document.body, }; - state: State = { + state: AnimatorState = { animationsMarkup: [], childProps: {}, }; @@ -115,55 +122,95 @@ export default class Animator extends React.PureComponent if (componentIn === undefined || componentIn) { // Ok nothing is there yet, show ourself and store DOM data for later. // We'll be waiting for another Animator to mount. - this.showSelfAndNotifyManager(); + this.notifyVisibilityManagerAnimationsAreFinished(); } } - componentWillUpdate(prevProps: AnimatorProps) { - const { in: isIn } = this.props; - if (prevProps.in === false && isIn === true) { - // We're being removed from "in". Let's recalculate our DOM position. + getSnapshotBeforeUpdate(prevProps: AnimatorProps) { + if (prevProps.in === true && this.props.in === false) { this.storeDOMData(); this.delayedClearStore(); this.abortAnimations(); } + + if (prevProps.triggerSelfKey !== this.props.triggerSelfKey) { + this.storeDOMData(); + this.delayedClearStore(); + } + + // we can return snapshot here to circumvent the entire storing of dom data. + // would remove the need for setting a name! + return null; } - componentDidUpdate(prevProps: AnimatorProps) { - const { in: isIn, name } = this.props; + componentDidUpdate(prevProps: AnimatorProps, _: AnimatorState) { + const inPropSame = this.props.in === prevProps.in; + const triggerSelfKeyPropSame = this.props.triggerSelfKey === prevProps.triggerSelfKey; - if (isIn === prevProps.in) { + if (inPropSame && triggerSelfKeyPropSame) { // Nothing has changed, return early. return; } - if ( - process.env.NODE_ENV === 'development' && - (isIn === undefined || prevProps.in === undefined) - ) { - warn( - `You're switching between controlled and uncontrolled, don't do this. Either always set the "in" prop as true or false, or keep as undefined.` + if (process.env.NODE_ENV === 'development') { + precondition( + !(this.props.in !== undefined && this.props.triggerSelfKey !== undefined), + `Don't use "in" and "triggerSelfKey" together. If your element is persisted use "in". If your element is targeting itself for animations use "triggerSelfKey".` + ); + } + + if (process.env.NODE_ENV === 'development') { + precondition( + !((this.props.in === undefined || prevProps.in === undefined) && !inPropSame), + `You're switching between persisted and unpersisted, don't do this. Either always set the "in" prop as true or false, or keep as undefined.` + ); + } + + if (process.env.NODE_ENV === 'development') { + precondition( + !( + (this.props.triggerSelfKey === undefined || prevProps.triggerSelfKey === undefined) && + !triggerSelfKeyPropSame + ), + `You're switching between self triggering modes, don't do this. Either always set the "triggerSelfKey" prop, or keep as undefined.` ); } - if (isIn) { - if (store.has(name)) { + if (this.props.in) { + if (store.has(this.props.name)) { this.executeAnimations(); + // return early dont tell manager yet dawg return; } + // No animation to trigger, tell manager we're all good regardless. + this.notifyVisibilityManagerAnimationsAreFinished(); + return; + } - this.showSelfAndNotifyManager(); + if (!triggerSelfKeyPropSame) { + // Defer execution to the next frame to capture correctly. + // Make sure to keep react state the same for any inflight animations to be captured correctly. + requestAnimationFrame(() => { + this.abortAnimations(); + this.executeAnimations(); + }); } } componentWillUnmount() { + if (this.props.triggerSelfKey) { + this.abortAnimations(); + this.unmounting = true; + return; + } + this.storeDOMData(); this.delayedClearStore(); this.abortAnimations(); this.unmounting = true; } - showSelfAndNotifyManager() { + notifyVisibilityManagerAnimationsAreFinished() { const { context, name } = this.props; // If a VisibilityManager is a parent up the tree context will be available. @@ -233,6 +280,7 @@ If it's an image, try and have the image loaded before mounting, or set a static const { name, container: getContainer, context } = this.props; const container = typeof getContainer === 'function' ? getContainer() : getContainer; const fromTarget = store.get(name); + let aborted = false; if (fromTarget) { const { collectorData, elementData } = fromTarget; @@ -332,6 +380,10 @@ If it's an image, try and have the image loaded before mounting, or set a static container.removeChild(elementToMountChildren); } + if (targetData.payload.abort) { + targetData.payload.abort(); + } + if (this.unmounting) { return; } @@ -370,6 +422,8 @@ If it's an image, try and have the image loaded before mounting, or set a static ); this.abortAnimations = () => { + aborted = true; + if (this.animating) { this.animating = false; blocks.forEach(block => block.forEach(anim => anim.cleanup())); @@ -423,6 +477,10 @@ If it's an image, try and have the image loaded before mounting, or set a static ); }) .then(() => { + if (aborted) { + return; + } + blocks.forEach(block => block.forEach(anim => anim.cleanup())); }) .then(() => { diff --git a/packages/yubaba/src/Animator/test.tsx b/packages/yubaba/src/Animator/test.tsx index 911fec0..85efb82 100644 --- a/packages/yubaba/src/Animator/test.tsx +++ b/packages/yubaba/src/Animator/test.tsx @@ -5,8 +5,10 @@ import { WrappedAnimator as Animator } from '../Animator'; import Target from '../FocalTarget'; import { getElementBoundingBox } from '../lib/dom'; import defer from '../lib/defer'; +import * as store from '../lib/animatorStore'; import * as utils from '../__tests__/utils'; +jest.mock('../../package.json', () => ({ default: { version: '0.0.0' } })); jest.mock('../lib/dom'); window.requestAnimationFrame = (cb: Function) => cb(); @@ -109,6 +111,7 @@ describe('', () => { startAnimation(wrapper); await deferred.promise; + // element and render will be undefined. expect(callback.mock.calls[0]).toMatchSnapshot(); }); @@ -223,6 +226,7 @@ describe('', () => { await enterNextPhase(wrapper); expect(wrapper.find('Portal')).toMatchSnapshot(); + jest.useRealTimers(); }); it('should update markup created in an animation in animate phase', async () => { @@ -248,6 +252,7 @@ describe('', () => { await enterNextPhase(wrapper); expect(wrapper.find('Portal')).toMatchSnapshot(); + jest.useRealTimers(); }); it('should update markup created in an animation in after animate phase', async () => { @@ -274,6 +279,7 @@ describe('', () => { await enterNextPhase(wrapper); expect(wrapper.find('Portal')).toMatchSnapshot(); + jest.useRealTimers(); }); it('should pass through context to animations created react elements', () => { @@ -304,4 +310,147 @@ describe('', () => { }) ).not.toThrow(); }); + + describe('persisted animations', () => { + it('should animate from persisted', done => { + const Animation = utils.createTestAnimation(); + const wrapper = mount( + ( + + {props =>
} + + )} + to={ + + {props =>
} + + } + start={false} + /> + ); + + wrapper.setProps({ start: true }); + }); + + it('should throw when changing into "in" after initial mount', () => { + process.env.NODE_ENV = 'development'; + const Animation = utils.createTestAnimation(); + const wrapper = mount( + + {props =>
} + + ); + + expect(() => + wrapper.setProps({ + in: true, + }) + ).toThrowErrorMatchingSnapshot(); + }); + }); + + describe('self targetted animations', () => { + it('should animate over self', done => { + const Animation = utils.createTestAnimation(); + const wrapper = mount( + + {anim =>
} + + ); + + wrapper.setProps({ triggerSelfKey: 'update-pls' }); + }); + + it('should throw when changing into "triggerSelfKey" after initial mount', () => { + process.env.NODE_ENV = 'development'; + const Animation = utils.createTestAnimation(); + const wrapper = mount( + + {props =>
} + + ); + + expect(() => + wrapper.setProps({ + triggerSelfKey: 'id-123', + }) + ).toThrowErrorMatchingSnapshot(); + }); + + it('should throw when using both "in" and "triggerSelfKey" props after initial mount', () => { + process.env.NODE_ENV = 'development'; + const Animation = utils.createTestAnimation(); + const wrapper = mount( + + {props =>
} + + ); + + expect(() => + wrapper.setProps({ + triggerSelfKey: 'id-123', + in: true, + }) + ).toThrowErrorMatchingSnapshot(); + }); + + it('should abort when unmounting after beginning animation', () => { + const onCleanup = jest.fn(); + const Animation = utils.createTestAnimation({ onCleanup }); + const wrapper = mount( + + {props =>
} + + ); + wrapper.setProps({ + triggerSelfKey: '1', + }); + + wrapper.unmount(); + + expect(onCleanup).toHaveBeenCalled(); + }); + + it('should not store dom data when unmounting when trigger self key is set', () => { + const onCleanup = jest.fn(); + const Animation = utils.createTestAnimation({ onCleanup }); + const wrapper = mount( + + {props =>
} + + ); + + wrapper.unmount(); + + expect(store.has('hello-world')).toEqual(false); + }); + + describe('aborting animations', () => { + it('should block cleanup if animations have aborted previously', async () => { + // This stops the double cleanup apparent in self targetted animations. + jest.useFakeTimers(); + const onCleanup = jest.fn(); + const Animation = utils.createTestAnimation({ onCleanup }); + const wrapper = mount( + + {props =>
} + + ); + + wrapper.setProps({ + triggerSelfKey: '1', + }); + await enterNextPhase(wrapper); + wrapper.setProps({ + triggerSelfKey: '2', + }); + await enterNextPhase(wrapper); + await enterNextPhase(wrapper); + + expect(onCleanup).toHaveBeenCalledTimes(1); + jest.useRealTimers(); + }); + }); + }); }); diff --git a/packages/yubaba/src/VisibilityManager/test.tsx b/packages/yubaba/src/VisibilityManager/test.tsx index 4c1a72a..7e5a75f 100644 --- a/packages/yubaba/src/VisibilityManager/test.tsx +++ b/packages/yubaba/src/VisibilityManager/test.tsx @@ -5,6 +5,8 @@ import { WrappedVisibilityManager as VisibilityManager } from '../VisibilityMana import * as utils from '../__tests__/utils'; import defer from '../lib/defer'; +window.requestAnimationFrame = (cb: Function) => cb(); + describe('', () => { it('should be visible after start animation has been mounted', () => { const Animation = utils.createTestAnimation(); @@ -41,6 +43,34 @@ describe('', () => { expect(wrapper.find('span').prop('style')).toEqual({ visibility: 'visible' }); }); + it('should be hidden during animation via self animation', () => { + const Animation = utils.createTestAnimation(); + const wrapper = mount( + ( + + {props => ( + + + {({ ref, style }) =>
} + + + )} + + )} + to={null} + start={false} + /> + ); + + wrapper.setProps({ + start: true, + }); + wrapper.update(); + + expect(wrapper.find('span').prop('style')).toEqual({ visibility: 'hidden' }); + }); + it('should be hidden during animation', () => { const Animation = utils.createTestAnimation(); const wrapper = mount( diff --git a/packages/yubaba/src/__docs__/1-introduction/docs.mdx b/packages/yubaba/src/__docs__/1-introduction/docs.mdx index c9ef879..8be815d 100644 --- a/packages/yubaba/src/__docs__/1-introduction/docs.mdx +++ b/packages/yubaba/src/__docs__/1-introduction/docs.mdx @@ -51,7 +51,7 @@ yarn add yubaba react@^16.4.x react-dom@^16.4.x emotion@^10.x.x import Animator, { Move } from 'yubaba'; ({ isLarge }) => ( - + {anim =>
} ); diff --git a/packages/yubaba/src/__docs__/2-getting-started/docs.mdx b/packages/yubaba/src/__docs__/2-getting-started/docs.mdx index 572efc2..e69e276 100644 --- a/packages/yubaba/src/__docs__/2-getting-started/docs.mdx +++ b/packages/yubaba/src/__docs__/2-getting-started/docs.mdx @@ -154,7 +154,7 @@ To do that we can abuse how `key`'s work in react - we just update the key at th {({ shown, toggle }) => ( - + {({ ref, style }) => ( <> diff --git a/packages/yubaba/src/__docs__/3-advanced-usage/docs.mdx b/packages/yubaba/src/__docs__/3-advanced-usage/docs.mdx index e548c23..5bca04b 100644 --- a/packages/yubaba/src/__docs__/3-advanced-usage/docs.mdx +++ b/packages/yubaba/src/__docs__/3-advanced-usage/docs.mdx @@ -81,7 +81,7 @@ Here we use Popmotion to do the animating. {toggler => ( - + {anim => } )} diff --git a/packages/yubaba/src/__docs__/4-custom-animations/docs.mdx b/packages/yubaba/src/__docs__/4-custom-animations/docs.mdx index 67fabbc..2bf02a8 100644 --- a/packages/yubaba/src/__docs__/4-custom-animations/docs.mdx +++ b/packages/yubaba/src/__docs__/4-custom-animations/docs.mdx @@ -161,7 +161,7 @@ export default class OneFullRotation extends Component { {toggler => ( - + {({ ref, ...props }) => ( { {toggler => ( - + {({ ref, ...props }) => ( @@ -336,7 +336,7 @@ export default class SupportingAnimation extends React.Component { {toggler => ( - + {({ ref, ...props }) => ( { onBeforeAnimate(data); setTimeout(onFinish, 0); diff --git a/packages/yubaba/src/animations/Move/index.tsx b/packages/yubaba/src/animations/Move/index.tsx index 405ebc7..4c267c7 100644 --- a/packages/yubaba/src/animations/Move/index.tsx +++ b/packages/yubaba/src/animations/Move/index.tsx @@ -10,6 +10,7 @@ import { standard } from '../../lib/curves'; import { combine, zIndexStack } from '../../lib/style'; import { Duration } from '../types'; import { dynamic } from '../../lib/duration'; +import noop from '../../lib/noop'; export interface MoveProps extends CollectorChildrenProps { /** @@ -56,6 +57,8 @@ export default class Move extends React.Component { transformY: true, }; + abort = noop; + beforeAnimate: AnimationCallback = (data, onFinish, setChildProps) => { const { zIndex, useFocalTarget, transformX, transformY } = this.props; @@ -119,7 +122,10 @@ export default class Move extends React.Component { }), }); - setTimeout(() => onFinish(), calculatedDuration); + const id = window.setTimeout(() => onFinish(), calculatedDuration); + this.abort = () => { + window.clearTimeout(id); + }; }; render() { @@ -132,6 +138,7 @@ export default class Move extends React.Component { payload: { beforeAnimate: this.beforeAnimate, animate: this.animate, + abort: this.abort, }, }} > diff --git a/packages/yubaba/src/animations/ReshapingContainer/__docz__/TripeHoverMenu.tsx b/packages/yubaba/src/animations/ReshapingContainer/__docz__/TripeHoverMenu.tsx index 08a3f84..79b3cd1 100644 --- a/packages/yubaba/src/animations/ReshapingContainer/__docz__/TripeHoverMenu.tsx +++ b/packages/yubaba/src/animations/ReshapingContainer/__docz__/TripeHoverMenu.tsx @@ -5,7 +5,7 @@ import ReshapingContainer from '../index'; import * as Styled from './styled'; const menuPosition = [200, 100, 20]; -const innerTimeout = { enter: 0, exit: 200 }; +const innerTimeout = { enter: 200, exit: 300 }; const TripeHoverMenu = () => ( +```js +import { ReshapingContainer } from 'yubaba'; + +({ children }) => ( + + {anim => {children}} + +); +``` + ## Props diff --git a/packages/yubaba/src/animations/ReshapingContainer/__docz__/styled.tsx b/packages/yubaba/src/animations/ReshapingContainer/__docz__/styled.tsx index 7bee8e7..32cb644 100644 --- a/packages/yubaba/src/animations/ReshapingContainer/__docz__/styled.tsx +++ b/packages/yubaba/src/animations/ReshapingContainer/__docz__/styled.tsx @@ -17,7 +17,6 @@ const pos = { }; export const InnerMenu = styled.div<{ state: string }>` - transition: transform 0.2, opacity 0.2; opacity: ${props => opac[props.state]}; position: ${props => pos[props.state]}; top: 0; diff --git a/packages/yubaba/src/animations/ReshapingContainer/index.tsx b/packages/yubaba/src/animations/ReshapingContainer/index.tsx index 6e4d69b..0cf12bd 100644 --- a/packages/yubaba/src/animations/ReshapingContainer/index.tsx +++ b/packages/yubaba/src/animations/ReshapingContainer/index.tsx @@ -136,11 +136,10 @@ export default class ReshapingContainer extends React.PureComponent + {anim => (