Browse Source

ref(ui): Replace react-pose with framer-motion (#19656)

Evan Purkhiser 4 years ago
parent
commit
62e4507201

+ 1 - 1
package.json

@@ -72,6 +72,7 @@
     "file-loader": "^3.0.1",
     "focus-visible": "^5.0.2",
     "fork-ts-checker-webpack-plugin": "^1.5.1",
+    "framer-motion": "^1.11.1",
     "fuse.js": "^3.4.6",
     "gettext-parser": "1.3.1",
     "intersection-observer": "^0.7.0",
@@ -105,7 +106,6 @@
     "react-lazyload": "^2.3.0",
     "react-mentions": "^1.2.0",
     "react-popper": "^1.3.3",
-    "react-pose": "^4.0.8",
     "react-router": "3.2.0",
     "react-select": "^3.0.8",
     "react-select-legacy": "npm:react-select-legacy@1",

+ 23 - 22
src/sentry/static/sentry/app/components/alerts/toastIndicator.jsx

@@ -1,34 +1,15 @@
 import PropTypes from 'prop-types';
 import React from 'react';
 import classNames from 'classnames';
-import posed from 'react-pose';
 import styled from '@emotion/styled';
+import {motion} from 'framer-motion';
 
 import {t} from 'app/locale';
 import InlineSvg from 'app/components/inlineSvg';
 import LoadingIndicator from 'app/components/loadingIndicator';
-import testablePose from 'app/utils/testablePose';
+import testableTransition from 'app/utils/testableTransition';
 
-const transition = {
-  type: 'spring',
-  stiffness: 450,
-  damping: 25,
-};
-
-const toastAnimation = testablePose({
-  exit: {
-    transition,
-    opacity: 0,
-    y: 70,
-  },
-  enter: {
-    transition,
-    opacity: 1,
-    y: 0,
-  },
-});
-
-const Toast = styled(posed.div(toastAnimation))`
+const Toast = styled(motion.div)`
   display: flex;
   align-items: center;
   height: 40px;
@@ -41,6 +22,26 @@ const Toast = styled(posed.div(toastAnimation))`
   position: relative;
 `;
 
+Toast.defaultProps = {
+  initial: {
+    opacity: 0,
+    y: 70,
+  },
+  animate: {
+    opacity: 1,
+    y: 0,
+  },
+  exit: {
+    opacity: 0,
+    y: 70,
+  },
+  transition: testableTransition({
+    type: 'spring',
+    stiffness: 450,
+    damping: 25,
+  }),
+};
+
 const Icon = styled('div')`
   margin-right: 6px;
   svg {

+ 15 - 9
src/sentry/static/sentry/app/components/indicators.jsx

@@ -1,4 +1,4 @@
-import {PoseGroup} from 'react-pose';
+import {AnimatePresence} from 'framer-motion';
 import {ThemeProvider} from 'emotion-theming';
 import PropTypes from 'prop-types';
 import React from 'react';
@@ -11,7 +11,7 @@ import IndicatorStore from 'app/stores/indicatorStore';
 import ToastIndicator from 'app/components/alerts/toastIndicator';
 import theme from 'app/utils/theme';
 
-const Toasts = styled(PoseGroup)`
+const Toasts = styled('div')`
   position: fixed;
   right: 30px;
   bottom: 30px;
@@ -43,13 +43,19 @@ class Indicators extends React.Component {
 
     return (
       <Toasts {...props}>
-        {items.map((indicator, i) => (
-          // We purposefully use `i` as key here because of transitions
-          // Toasts can now queue up, so when we change from [firstToast] -> [secondToast],
-          // we don't want to  animate `firstToast` out and `secondToast` in, rather we want
-          // to replace `firstToast` with `secondToast`
-          <ToastIndicator onDismiss={this.handleDismiss} indicator={indicator} key={i} />
-        ))}
+        <AnimatePresence>
+          {items.map((indicator, i) => (
+            // We purposefully use `i` as key here because of transitions
+            // Toasts can now queue up, so when we change from [firstToast] -> [secondToast],
+            // we don't want to  animate `firstToast` out and `secondToast` in, rather we want
+            // to replace `firstToast` with `secondToast`
+            <ToastIndicator
+              onDismiss={this.handleDismiss}
+              indicator={indicator}
+              key={i}
+            />
+          ))}
+        </AnimatePresence>
       </Toasts>
     );
   }

+ 39 - 24
src/sentry/static/sentry/app/components/onboardingWizard/sidebar.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import styled from '@emotion/styled';
-import posed, {PoseGroup} from 'react-pose';
+import {motion, AnimatePresence} from 'framer-motion';
 
 import withApi from 'app/utils/withApi';
 import withOrganization from 'app/utils/withOrganization';
@@ -13,6 +13,7 @@ import {IconLightning, IconLock, IconCheckmark} from 'app/icons';
 import Tooltip from 'app/components/tooltip';
 import SidebarPanel from 'app/components/sidebar/sidebarPanel';
 import {CommonSidebarProps} from 'app/components/sidebar/types';
+import testableTransition from 'app/utils/testableTransition';
 
 import {findUpcomingTasks, findCompleteTasks, findActiveTasks, taskIsDone} from './utils';
 import {getMergedTasks} from './taskConfig';
@@ -38,7 +39,7 @@ const COMPLETION_SEEN_TIMEOUT = 800;
 const doTimeout = (timeout: number) =>
   new Promise(resolve => setTimeout(resolve, timeout));
 
-const Heading = styled(posed.h4())`
+const Heading = styled(motion.div)`
   display: grid;
   grid-template-columns: max-content 1fr;
   grid-gap: ${space(0.75)};
@@ -51,6 +52,11 @@ const Heading = styled(posed.h4())`
   padding-bottom: ${space(1)};
 `;
 
+Heading.defaultProps = {
+  positionTransition: true,
+  transition: testableTransition(),
+};
+
 const completeNowHeading = (
   <Heading key="now">
     <IconLightning size="xs" />
@@ -118,7 +124,7 @@ class OnboardingWizardSidebar extends React.Component<Props> {
   };
 
   renderItem = (task: OnboardingTask) => (
-    <PosedTaskItem
+    <AnimatedTaskItem
       task={task}
       key={`${task.task}`}
       onSkip={this.makeTaskUpdater('skipped')}
@@ -132,9 +138,7 @@ class OnboardingWizardSidebar extends React.Component<Props> {
 
     const completeList = (
       <CompleteList key="complete-group">
-        <PoseGroup flipMove={false} preEnterPose="completeInit" enterPose="complete">
-          {complete.map(this.renderItem)}
-        </PoseGroup>
+        <AnimatePresence initial={false}>{complete.map(this.renderItem)}</AnimatePresence>
       </CompleteList>
     );
 
@@ -155,7 +159,7 @@ class OnboardingWizardSidebar extends React.Component<Props> {
       >
         <ProgressHeader allTasks={all} completedTasks={complete} />
         <TaskList>
-          <PoseGroup exitPose="markComplete">{items}</PoseGroup>
+          <AnimatePresence initial={false}>{items}</AnimatePresence>
         </TaskList>
       </TaskSidebarPanel>
     );
@@ -165,24 +169,35 @@ const TaskSidebarPanel = styled(SidebarPanel)`
   width: 450px;
 `;
 
-const PosedTaskItem = posed(Task)({
-  markComplete: {
-    y: 20,
-    z: -10,
-    opacity: 0,
-    transition: {duration: 200},
-  },
-  completeInit: {
-    opacity: 0,
-    y: 40,
+const AnimatedTaskItem = motion.custom(Task);
+
+AnimatedTaskItem.defaultProps = {
+  initial: 'initial',
+  animate: 'animate',
+  exit: 'exit',
+  positionTransition: true,
+  variants: {
+    initial: {
+      opacity: 0,
+      y: 40,
+    },
+    animate: {
+      opacity: 1,
+      y: 0,
+      transition: testableTransition({
+        delay: 0.8,
+        when: 'beforeChildren',
+        staggerChildren: 0.3,
+      }),
+    },
+    exit: {
+      y: 20,
+      z: -10,
+      opacity: 0,
+      transition: {duration: 0.2},
+    },
   },
-  complete: {
-    beforeChildren: true,
-    transition: {delay: 800},
-    opacity: 1,
-    y: 0,
-  },
-});
+};
 
 const TaskList = styled('div')`
   display: grid;

+ 39 - 21
src/sentry/static/sentry/app/components/onboardingWizard/task.tsx

@@ -1,7 +1,7 @@
 import React from 'react';
 import styled from '@emotion/styled';
 import * as ReactRouter from 'react-router';
-import posed from 'react-pose';
+import {motion} from 'framer-motion';
 import moment from 'moment';
 
 import {tct, t} from 'app/locale';
@@ -16,6 +16,7 @@ import Button from 'app/components/button';
 import {IconLock, IconCheckmark, IconClose, IconEvent} from 'app/icons';
 import Avatar from 'app/components/avatar';
 import LetterAvatar from 'app/components/letterAvatar';
+import testableTransition from 'app/utils/testableTransition';
 
 import {taskIsDone} from './utils';
 import SkipConfirm from './skipConfirm';
@@ -81,8 +82,10 @@ function Task({router, task, onSkip, onMarkComplete, forwardedRef, organization}
 
     return (
       <ItemComplete ref={forwardedRef} onClick={handleClick}>
-        {task.status === 'complete' && <CompleteIndicator />}
-        {task.status === 'skipped' && <SkippedIndicator />}
+        <StatusIndicator>
+          {task.status === 'complete' && <CompleteIndicator />}
+          {task.status === 'skipped' && <SkippedIndicator />}
+        </StatusIndicator>
         {task.title}
         <CompletedDate title={completedOn.toString()}>
           {completedOn.fromNow()}
@@ -218,11 +221,7 @@ const SkipButton = styled(Button)`
   color: ${p => p.theme.gray500};
 `;
 
-const PosedItemComplete = posed(Card)({
-  complete: {staggerChildren: 500},
-});
-
-const ItemComplete = styled(PosedItemComplete)`
+const ItemComplete = styled(Card)`
   cursor: pointer;
   color: ${p => p.theme.gray600};
   padding: ${space(1)} ${space(1.5)};
@@ -232,39 +231,58 @@ const ItemComplete = styled(PosedItemComplete)`
   align-items: center;
 `;
 
-const completedItemPoses = {
-  completeInit: {
-    opacity: 0,
-    x: -10,
-  },
-  complete: {
-    opacity: 1,
-    x: 0,
+const transition = testableTransition();
+
+const StatusIndicator = styled(motion.div)`
+  display: flex;
+`;
+StatusIndicator.defaultProps = {
+  variants: {
+    initial: {opacity: 0, x: 10},
+    animate: {opacity: 1, x: 0},
   },
+  transition,
 };
 
-const CompleteIndicator = posed(IconCheckmark)(completedItemPoses);
+const CompleteIndicator = styled(IconCheckmark)``;
 CompleteIndicator.defaultProps = {
   isCircled: true,
   color: 'green400',
 };
 
-const SkippedIndicator = posed(IconClose)(completedItemPoses);
+const SkippedIndicator = styled(IconClose)``;
 SkippedIndicator.defaultProps = {
   isCircled: true,
   color: 'orange300',
 };
 
-const CompletedDate = styled(posed.div(completedItemPoses))`
+const completedItemAnimation = {
+  initial: {opacity: 0, x: -10},
+  animate: {opacity: 1, x: 0},
+};
+
+const CompletedDate = styled(motion.div)`
   color: ${p => p.theme.gray500};
   font-size: ${p => p.theme.fontSizeSmall};
 `;
+CompletedDate.defaultProps = {
+  variants: completedItemAnimation,
+  transition,
+};
 
-const TaskUserAvatar = posed(Avatar)(completedItemPoses);
+const TaskUserAvatar = motion.custom(Avatar);
+TaskUserAvatar.defaultProps = {
+  variants: completedItemAnimation,
+  transition,
+};
 
-const TaskBlankAvatar = styled(posed(LetterAvatar)(completedItemPoses))`
+const TaskBlankAvatar = styled(motion.custom(LetterAvatar))`
   position: unset;
 `;
+TaskBlankAvatar.defaultProps = {
+  variants: completedItemAnimation,
+  transition,
+};
 
 const WrappedTask = withOrganization(ReactRouter.withRouter(Task));
 

+ 13 - 12
src/sentry/static/sentry/app/components/progressRing.tsx

@@ -1,9 +1,9 @@
 import React from 'react';
 import styled, {SerializedStyles} from '@emotion/styled';
-import posed, {PoseGroup} from 'react-pose';
+import {motion, AnimatePresence} from 'framer-motion';
 
 import theme, {Theme} from 'app/utils/theme';
-import testablePose from 'app/utils/testablePose';
+import testableTransition from 'app/utils/testableTransition';
 
 type TextProps = {
   textCss?: Props['textCss'];
@@ -64,13 +64,14 @@ const Text = styled('div')<Omit<TextProps, 'theme'>>`
   ${p => p.textCss && p.textCss(p)}
 `;
 
-const PosedText = posed(Text)(
-  testablePose({
-    init: {opacity: 0, y: -10},
-    enter: {opacity: 1, y: 0},
-    exit: {opacity: 0, y: 10},
-  })
-);
+const AnimatedText = motion.custom(Text);
+
+AnimatedText.defaultProps = {
+  initial: {opacity: 0, y: -10},
+  animate: {opacity: 1, y: 0},
+  exit: {opacity: 0, y: 10},
+  transition: testableTransition(),
+};
 
 const ProgressRing = ({
   value,
@@ -94,16 +95,16 @@ const ProgressRing = ({
   const percent = progress * 100;
   const progressOffset = (1 - progress) * circumference;
 
-  const TextComponent = animateText ? PosedText : Text;
+  const TextComponent = animateText ? AnimatedText : Text;
 
   let textNode = (
-    <TextComponent key={text?.toString()} x="50%" y="50%" {...{textCss, percent}}>
+    <TextComponent key={text?.toString()} {...{textCss, percent}}>
       {text}
     </TextComponent>
   );
 
   textNode = animateText ? (
-    <PoseGroup preEnterPose="init">{textNode}</PoseGroup>
+    <AnimatePresence initial={false}>{textNode}</AnimatePresence>
   ) : (
     textNode
   );

+ 0 - 25
src/sentry/static/sentry/app/utils/testablePose.tsx

@@ -1,25 +0,0 @@
-/* global process */
-
-type PoseConfig = {[key: string]: any};
-
-/**
- * Use with a react-pose animation to disable the animation in testing
- * environments.
- *
- * This function simply sets delays and durations to 0.
- */
-const testablePose = !process.env.IS_CI
-  ? (a: PoseConfig) => a
-  : function(animation: PoseConfig) {
-      Object.keys(animation).forEach(pose => {
-        animation[pose].delay = 0;
-        animation[pose].delayChildren = 0;
-        animation[pose].staggerChildren = 0;
-
-        animation[pose].transition = {duration: 0};
-      });
-
-      return animation;
-    };
-
-export default testablePose;

+ 29 - 0
src/sentry/static/sentry/app/utils/testableTransition.tsx

@@ -0,0 +1,29 @@
+/* global process */
+import {Transition} from 'framer-motion';
+
+/**
+ * Use with a framer-motion transition to disable the animation in testing
+ * environments.
+ *
+ * If your animation has no transition you can simply specify
+ *
+ * ```tsx
+ * Component.defaultProps = {
+ *   transition: testableTransition(),
+ * }
+ * ```
+ *
+ * This function simply disables the animation `type`.
+ */
+const testableTransition = !process.env.IS_CI
+  ? (t?: Transition) => t
+  : function(transition?: Transition): Transition {
+      return {
+        ...transition,
+        delay: 0,
+        staggerChildren: 0,
+        type: false,
+      };
+    };
+
+export default testableTransition;

+ 25 - 22
src/sentry/static/sentry/app/views/onboarding/onboarding.jsx

@@ -3,7 +3,7 @@ import {browserHistory} from 'react-router';
 import DocumentTitle from 'react-document-title';
 import PropTypes from 'prop-types';
 import React from 'react';
-import posed, {PoseGroup} from 'react-pose';
+import {motion, AnimatePresence} from 'framer-motion';
 import scrollToElement from 'scroll-to-element';
 import styled from '@emotion/styled';
 
@@ -17,9 +17,9 @@ import OnboardingWelcome from 'app/views/onboarding/welcome';
 import PageHeading from 'app/components/pageHeading';
 import SentryTypes from 'app/sentryTypes';
 import space from 'app/styles/space';
-import testablePose from 'app/utils/testablePose';
 import withOrganization from 'app/utils/withOrganization';
 import withProjects from 'app/utils/withProjects';
+import testableTransition from 'app/utils/testableTransition';
 
 const recordAnalyticStepComplete = ({organization, project, step}) =>
   analytics('onboarding_v2.step_compete', {
@@ -158,7 +158,7 @@ class Onboarding extends React.Component {
       <OnboardingStep
         key={step.id}
         data-test-id={`onboarding-step-${step.id}`}
-        onPoseComplete={this.scrollToActiveStep}
+        onAnimationComplete={this.scrollToActiveStep}
         active={activeStepIndex === index}
       >
         <PageHeading withMargins>{step.title}</PageHeading>
@@ -188,15 +188,17 @@ class Onboarding extends React.Component {
           <Container>
             <LogoSvg src="logo" />
             {this.renderProgressBar()}
-            <PoseGroup preEnterPose="init">
+            <AnimatePresence initial={false}>
               <ProgressStatus key={this.activeStep.id}>
                 {this.activeStep.title}
               </ProgressStatus>
-            </PoseGroup>
+            </AnimatePresence>
           </Container>
         </Header>
         <Container>
-          <PoseGroup flipMove={false}>{this.renderOnboardingSteps()}</PoseGroup>
+          <AnimatePresence initial={false}>
+            {this.renderOnboardingSteps()}
+          </AnimatePresence>
         </Container>
         <Hook name="onboarding:extra-chrome" />
       </OnboardingWrapper>
@@ -266,28 +268,22 @@ const ProgressStep = styled('div')`
   background: #fff;
 `;
 
-const PosedProgressStatus = posed.div(
-  testablePose({
-    init: {opacity: 0, y: -10},
-    enter: {opacity: 1, y: 0},
-    exit: {opacity: 0, y: 10},
-  })
-);
-
-const ProgressStatus = styled(PosedProgressStatus)`
+const ProgressStatus = styled(motion.div)`
   color: ${p => p.theme.gray600};
   font-size: ${p => p.theme.fontSizeMedium};
   text-align: right;
+  grid-column: 3;
+  grid-row: 1;
 `;
 
-const PosedOnboardingStep = posed.div(
-  testablePose({
-    enter: {opacity: 1, y: 0},
-    exit: {opacity: 0, y: 100},
-  })
-);
+ProgressStatus.defaultProps = {
+  initial: {opacity: 0, y: -10},
+  animate: {opacity: 1, y: 0},
+  exit: {opacity: 0, y: 10},
+  transition: testableTransition(),
+};
 
-const OnboardingStep = styled(PosedOnboardingStep)`
+const OnboardingStep = styled(motion.div)`
   margin: 70px 0;
   margin-left: -20px;
   padding-left: 18px;
@@ -311,6 +307,13 @@ const OnboardingStep = styled(PosedOnboardingStep)`
   }
 `;
 
+OnboardingStep.defaultProps = {
+  initial: {opacity: 0, y: 100},
+  animate: {opacity: 1, y: 0},
+  exit: {opacity: 0, y: 100},
+  transition: testableTransition(),
+};
+
 export const stepPropTypes = {
   scrollTargetId: PropTypes.string,
   active: PropTypes.bool,

+ 65 - 36
src/sentry/static/sentry/app/views/onboarding/projectSetup/firstEventIndicator.tsx

@@ -1,15 +1,15 @@
 import React from 'react';
-import posed, {PoseGroup} from 'react-pose';
+import {motion, AnimatePresence, Variants} from 'framer-motion';
 import styled from '@emotion/styled';
 
 import {t} from 'app/locale';
 import Button from 'app/components/button';
 import EventWaiter from 'app/utils/eventWaiter';
-import InlineSvg from 'app/components/inlineSvg';
 import space from 'app/styles/space';
-import testablePose from 'app/utils/testablePose';
 import pulsingIndicatorStyles from 'app/styles/pulsingIndicator';
 import {Group, Organization} from 'app/types';
+import {IconCheckmark} from 'app/icons';
+import testableTransition from 'app/utils/testableTransition';
 
 type EventWaiterProps = Omit<React.ComponentProps<typeof EventWaiter>, 'children'>;
 type FirstIssue = null | true | Group;
@@ -24,16 +24,16 @@ const Indicator = ({
   firstIssue,
   ...props
 }: EventWaiterProps & {firstIssue: FirstIssue}) => (
-  <PoseGroup preEnterPose="init">
+  <AnimatePresence>
     {!firstIssue ? (
       <Waiting key="waiting" />
     ) : (
       <Success key="received" firstIssue={firstIssue} {...props} />
     )}
-  </PoseGroup>
+  </AnimatePresence>
 );
 
-const StatusWrapper = styled(posed.div(testablePose({enter: {staggerChildren: 350}})))`
+const StatusWrapper = styled(motion.div)`
   display: grid;
   grid-template-columns: max-content 1fr max-content;
   grid-gap: ${space(1)};
@@ -44,12 +44,30 @@ const StatusWrapper = styled(posed.div(testablePose({enter: {staggerChildren: 35
   line-height: calc(0.9em + 1px);
   /* Ensure the event waiter status is always the height of a button */
   height: ${space(4)};
+  /* Keep the wrapper in the parent grids first cell for transitions */
+  grid-column: 1;
+  grid-row: 1;
 `;
 
+StatusWrapper.defaultProps = {
+  initial: 'initial',
+  animate: 'animate',
+  exit: 'exit',
+  variants: {
+    initial: {opacity: 0, y: -10},
+    animate: {
+      opacity: 1,
+      y: 0,
+      transition: testableTransition({when: 'beforeChildren', staggerChildren: 0.35}),
+    },
+    exit: {opacity: 0, y: 10},
+  },
+};
+
 const Waiting = (props: React.ComponentProps<typeof StatusWrapper>) => (
   <StatusWrapper {...props}>
     <WaitingIndicator />
-    <PosedText>{t('Waiting for verification event')}</PosedText>
+    <AnimatedText>{t('Waiting for verification event')}</AnimatedText>
   </StatusWrapper>
 );
 
@@ -60,36 +78,46 @@ type SuccessProps = EventWaiterProps & {
 
 const Success = ({organization, firstIssue, ...props}: SuccessProps) => (
   <StatusWrapper {...props}>
-    <ReceivedIndicator src="icon-checkmark-sm" />
-    <PosedText>{t('Event was received!')}</PosedText>
+    <ReceivedIndicator />
+    <AnimatedText>{t('Event was received!')}</AnimatedText>
     {firstIssue && firstIssue !== true && (
-      <PosedButton
-        size="small"
-        priority="primary"
-        to={`/organizations/${organization.slug}/issues/${firstIssue.id}/`}
-      >
-        {t('Take me to my event')}
-      </PosedButton>
+      <EventAction>
+        <Button
+          size="small"
+          priority="primary"
+          to={`/organizations/${organization.slug}/issues/${firstIssue.id}/`}
+        >
+          {t('Take me to my event')}
+        </Button>
+      </EventAction>
     )}
   </StatusWrapper>
 );
 
-const indicatorPoses = testablePose({
-  init: {opacity: 0, y: -10},
-  enter: {opacity: 1, y: 0},
+const indicatorAnimation: Variants = {
+  initial: {opacity: 0, y: -10},
+  animate: {opacity: 1, y: 0},
   exit: {opacity: 0, y: 10},
-});
+};
 
-const PosedText = posed.div(indicatorPoses);
+const AnimatedText = styled(motion.div)``;
 
-const WaitingIndicator = styled(posed.div(indicatorPoses))`
+AnimatedText.defaultProps = {
+  variants: indicatorAnimation,
+  transition: testableTransition(),
+};
+
+const WaitingIndicator = styled(motion.div)`
   margin: 0 6px;
   ${pulsingIndicatorStyles};
 `;
 
-const PosedReceivedIndicator = posed(InlineSvg)(indicatorPoses);
+WaitingIndicator.defaultProps = {
+  variants: indicatorAnimation,
+  transition: testableTransition(),
+};
 
-const ReceivedIndicator = styled(PosedReceivedIndicator)`
+const ReceivedIndicator = styled(IconCheckmark)`
   color: #fff;
   background: ${p => p.theme.green400};
   border-radius: 50%;
@@ -99,18 +127,19 @@ const ReceivedIndicator = styled(PosedReceivedIndicator)`
   margin: 0 2px;
 `;
 
-const PosedButton = posed(
-  React.forwardRef<HTMLDivElement>((props, ref) => (
-    <div ref={ref}>
-      <Button {...props} />
-    </div>
-  ))
-)(
-  testablePose({
-    init: {x: -20, opacity: 0},
-    enter: {x: 0, opacity: 1},
-  })
-);
+ReceivedIndicator.defaultProps = {
+  size: 'sm',
+};
+
+const EventAction = styled(motion.div)``;
+
+EventAction.defaultProps = {
+  variants: {
+    initial: {x: -20, opacity: 0},
+    animate: {x: 0, opacity: 1},
+  },
+  transition: testableTransition(),
+};
 
 export {Indicator};
 

Some files were not shown because too many files changed in this diff