|
@@ -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};
|
|
|
|