Skip to content

Commit a955ad1

Browse files
Animate hero screens (#529)
1 parent f0a5da4 commit a955ad1

File tree

30 files changed

+561
-137
lines changed

30 files changed

+561
-137
lines changed

Diff for: gatsby-browser.ts

+4
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,7 @@ exports.shouldUpdateScroll = ({
3030

3131
return true;
3232
};
33+
34+
exports.onPreRouteUpdate = ({ prevLocation }) => {
35+
window.prevLocation = prevLocation;
36+
};

Diff for: src/components/AnimatedHeader/AnimatedHeader.tsx

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,35 @@
1-
import React, { FC, PropsWithChildren } from 'react';
1+
import { createElement, FC, PropsWithChildren } from 'react';
22
import { motion, Transition } from 'framer-motion';
33
import { useInView } from '@app/hooks/useInView';
44
import { useMotionEnterAnimation } from '@app/hooks/useMotionEnterAnimation';
55
import { easeInOutTransition, opacityScaleAnimationProps } from '@app/utils';
66

77
interface AnimatedHeaderProps {
88
transition?: Transition;
9+
delay?: number;
10+
headerLevel?: 1 | 2 | 3 | 4 | 5 | 6;
911
className?: string;
1012
isAnimationEnabled?: boolean;
1113
}
1214

1315
export const AnimatedHeader: FC<PropsWithChildren<AnimatedHeaderProps>> = ({
14-
children,
1516
transition = easeInOutTransition,
17+
delay,
18+
headerLevel = 2,
1619
className,
1720
isAnimationEnabled = true,
21+
children,
1822
}) => {
19-
const [titleRef, isTitleInView] = useInView({ once: true });
23+
const [titleRef, isTitleInView] = useInView();
2024

2125
const getAnimation = useMotionEnterAnimation(
2226
{ ...opacityScaleAnimationProps, transition },
2327
isAnimationEnabled,
2428
);
2529

26-
return (
27-
<motion.h2 className={className} ref={titleRef} {...getAnimation({ inView: isTitleInView })}>
28-
{children}
29-
</motion.h2>
30+
return createElement(
31+
motion[`h${headerLevel}`],
32+
{ className, ref: titleRef, ...getAnimation({ inView: isTitleInView, delay }) },
33+
children,
3034
);
3135
};

Diff for: src/components/AnimatedList/AnimatedList.tsx

+6-11
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { motion } from 'framer-motion';
33
import classNames from 'classnames';
44
import {
55
createBemBlockBuilder,
6+
defaultSpringTransition,
67
easeInOutOpacityScaleAnimationProps,
7-
getSpringTransition,
8+
getEaseInOutTransition,
89
LIST_ANIMATION_DELAY,
910
opacityScaleAnimationProps,
1011
} from '@app/utils';
@@ -41,10 +42,7 @@ const cardAnimationProps = {
4142
opacity: 1,
4243
x: 0,
4344
},
44-
transition: {
45-
ease: 'easeInOut',
46-
duration: 0.7,
47-
},
45+
...getEaseInOutTransition(0.7),
4846
};
4947

5048
export const AnimatedList: FC<AnimatedListProps> = ({
@@ -65,20 +63,17 @@ export const AnimatedList: FC<AnimatedListProps> = ({
6563
const getCardAnimation = useMotionEnterAnimation(cardAnimationProps);
6664
const getButtonsAnimation = useMotionEnterAnimation({
6765
...opacityScaleAnimationProps,
68-
...getSpringTransition(400, 30),
66+
...defaultSpringTransition,
6967
});
7068
const getImageAnimation = useMotionEnterAnimation({
7169
...opacityScaleAnimationProps,
72-
transition: {
73-
ease: 'easeInOut',
74-
duration: 1,
75-
},
70+
...getEaseInOutTransition(1),
7671
});
7772

7873
return (
7974
<section ref={ref} className={classNames(getBlocksWith(), 'container')}>
8075
<div>
81-
<AnimatedHeader transition={getSpringTransition(400, 30)}>{title}</AnimatedHeader>
76+
<AnimatedHeader transition={defaultSpringTransition}>{title}</AnimatedHeader>
8277
<motion.h3 {...getSubtitleAnimation({ delay: 0.1, inView })}>{subtitle}</motion.h3>
8378
</div>
8479
<div className={getBlocksWith('__content')}>

Diff for: src/components/ArticlePreview/ArticlePreview.tsx

+25-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
import React, { FC } from 'react';
22
import isEmpty from 'lodash/isEmpty';
3-
import { createBemBlockBuilder, BlogPostDto } from '@app/utils';
3+
import chunk from 'lodash/chunk';
4+
import { useMediaQuery } from 'react-responsive';
5+
import { BlogPostDto, MEDIA_DESKTOP_SM, MEDIA_TABLET_SM } from '@app/utils';
46

5-
import { ArticlePreviewItem } from './ArticlePreviewItem';
7+
import { ArticlePreviewRow } from './ArticlePreviewRow';
68

79
import './ArticlePreview.scss';
810

911
interface ArticlePreviewProps {
1012
posts: BlogPostDto[];
1113
}
1214

13-
const getBlocksWith = createBemBlockBuilder(['article-preview-list']);
15+
const getItemsPerRow = (isDesktop: boolean, isTablet: boolean) => {
16+
if (isDesktop) {
17+
return 3;
18+
}
1419

15-
export const ArticlePreview: FC<ArticlePreviewProps> = ({ posts }) =>
16-
!isEmpty(posts) ? (
17-
<ul className={getBlocksWith()}>
18-
{posts.map(post => (
19-
<ArticlePreviewItem key={post.id} post={post} />
20+
if (isTablet) {
21+
return 2;
22+
}
23+
24+
return 1;
25+
};
26+
27+
export const ArticlePreview: FC<ArticlePreviewProps> = ({ posts }) => {
28+
const isDesktop = useMediaQuery({ query: MEDIA_DESKTOP_SM });
29+
const isTablet = useMediaQuery({ query: MEDIA_TABLET_SM });
30+
const rows = chunk(posts, getItemsPerRow(isDesktop, isTablet));
31+
32+
return !isEmpty(posts) ? (
33+
<ul>
34+
{rows.map((row, rowIndex) => (
35+
<ArticlePreviewRow key={rowIndex} row={row} />
2036
))}
2137
</ul>
2238
) : null;
39+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { FC } from 'react';
2+
import { motion } from 'framer-motion';
3+
import {
4+
BlogPostDto,
5+
createBemBlockBuilder,
6+
getEaseInOutTransition,
7+
opacityScaleAnimationProps,
8+
} from '@app/utils';
9+
import { useInView } from '@app/hooks/useInView';
10+
import { useMotionEnterAnimation } from '@app/hooks/useMotionEnterAnimation';
11+
12+
import { ArticlePreviewItem } from '../ArticlePreviewItem';
13+
14+
interface ArticlePreviewRowProps {
15+
row: BlogPostDto[];
16+
}
17+
18+
const getBlocksWith = createBemBlockBuilder(['article-preview-list']);
19+
20+
export const ArticlePreviewRow: FC<ArticlePreviewRowProps> = ({ row }) => {
21+
const [rowRef, isInView] = useInView();
22+
const getAnimation = useMotionEnterAnimation({
23+
...opacityScaleAnimationProps,
24+
...getEaseInOutTransition(0.7),
25+
});
26+
27+
return (
28+
<motion.div
29+
ref={rowRef}
30+
className={getBlocksWith()}
31+
{...getAnimation({
32+
inView: isInView,
33+
additionalEffects: {
34+
hiddenAdditional: {
35+
y: 150,
36+
},
37+
enterAdditional: {
38+
y: 0,
39+
},
40+
},
41+
})}
42+
>
43+
{row.map(post => (
44+
<ArticlePreviewItem key={post.id} post={post} />
45+
))}
46+
</motion.div>
47+
);
48+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ArticlePreviewRow';

Diff for: src/components/HeroSwitching/HeroSwitching.tsx

+49-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import React, { FC, ReactNode } from 'react';
2+
import { motion } from 'framer-motion';
23
import { ButtonSwitcher, ButtonSwitcherProps } from '@app/components/ButtonSwitcher';
3-
import { createBemBlockBuilder } from '@app/utils';
4+
import {
5+
createBemBlockBuilder,
6+
defaultSpringTransition,
7+
opacityScaleAnimationProps,
8+
} from '@app/utils';
9+
import { AnimatedHeader } from '@app/components/AnimatedHeader';
10+
import { useMotionEnterAnimation } from '@app/hooks/useMotionEnterAnimation';
411

512
import './HeroSwitching.scss';
613

@@ -10,6 +17,8 @@ interface HeroSwitchingProps {
1017
buttons: ButtonSwitcherProps['buttons'];
1118
switchActiveBtn?: (text: string) => void;
1219
subtitle?: string;
20+
isHeroInView?: boolean;
21+
isAnimationEnabled?: boolean;
1322
children?: ReactNode;
1423
}
1524

@@ -21,14 +30,43 @@ export const HeroSwitching: FC<HeroSwitchingProps> = ({
2130
buttons,
2231
activeButton,
2332
switchActiveBtn,
33+
isHeroInView = true,
34+
isAnimationEnabled = true,
2435
children,
25-
}) => (
26-
<>
27-
<h1 className={getBlocksWith('__title')}>{title}</h1>
28-
{subtitle && <p className={getBlocksWith('__subtitle')}>{subtitle}</p>}
29-
{children}
30-
<div className={getBlocksWith('__btn-box')}>
31-
<ButtonSwitcher buttons={buttons} activeBtnName={activeButton} onSwitch={switchActiveBtn} />
32-
</div>
33-
</>
34-
);
36+
}) => {
37+
const getSubtitleAnimation = useMotionEnterAnimation(
38+
{
39+
...opacityScaleAnimationProps,
40+
...defaultSpringTransition,
41+
},
42+
isAnimationEnabled,
43+
);
44+
45+
return (
46+
<>
47+
<AnimatedHeader
48+
headerLevel={1}
49+
transition={defaultSpringTransition}
50+
className={getBlocksWith('__title')}
51+
isAnimationEnabled={isAnimationEnabled}
52+
>
53+
{title}
54+
</AnimatedHeader>
55+
{subtitle && (
56+
<motion.p
57+
className={getBlocksWith('__subtitle')}
58+
{...getSubtitleAnimation({ delay: 0.1, inView: isHeroInView })}
59+
>
60+
{subtitle}
61+
</motion.p>
62+
)}
63+
{children}
64+
<motion.div
65+
className={getBlocksWith('__btn-box')}
66+
{...getSubtitleAnimation({ delay: 0.2, inView: isHeroInView })}
67+
>
68+
<ButtonSwitcher buttons={buttons} activeBtnName={activeButton} onSwitch={switchActiveBtn} />
69+
</motion.div>
70+
</>
71+
);
72+
};

Diff for: src/components/OfferPageWrapper/OfferPageWrapper.tsx

+36-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import React, { FC } from 'react';
2+
import { motion } from 'framer-motion';
23
import classNames from 'classnames';
3-
import { createBemBlockBuilder, FormattedComparePlansDto, OfferingPlansDto } from '@app/utils';
4+
import {
5+
createBemBlockBuilder,
6+
easeInOutOpacityScaleAnimationProps,
7+
FormattedComparePlansDto,
8+
OfferingPlansDto,
9+
} from '@app/utils';
410
import { usePricingHeroProps } from '@app/hooks/usePricingHeroProps';
511
import { FooterContent } from '@app/components/Layout';
612
import { TrustedOrganizations } from '@app/components/TrustedOrganizations';
@@ -10,9 +16,12 @@ import { PricingHero } from '@app/components/PricingHero';
1016
import { ComparePlans } from '@app/components/ComparePlans';
1117
import { Faq } from '@app/components/Faq';
1218
import InfoIcon from '@app/svg/infoIcon.inline.svg';
19+
import { useInView } from '@app/hooks/useInView';
20+
import { useMotionEnterAnimation } from '@app/hooks/useMotionEnterAnimation';
21+
import { useAnimationEnabledForSiblingRoutes } from '@app/hooks/useAnimationEnabledForSiblingRoutes';
1322

14-
import { TimeScale } from './TimeScale';
1523
import { PentagonCard } from './PentagonCard';
24+
import { TimeScale } from './TimeScale';
1625

1726
import './OfferPageWrapper.scss';
1827

@@ -60,9 +69,16 @@ export const OfferPageWrapper: FC<OfferPageWrapperProps> = ({
6069
isAccelerator = false,
6170
}) => {
6271
const { buttons, isDiscount, toggleDiscount } = usePricingHeroProps(page);
72+
const [cardsRef, areCardsInView] = useInView();
73+
const isAnimationEnabled = useAnimationEnabledForSiblingRoutes();
6374

6475
const discount = isDiscount ? 'yearly' : 'quarterly';
6576

77+
const getCardsAnimation = useMotionEnterAnimation(
78+
easeInOutOpacityScaleAnimationProps,
79+
isAnimationEnabled,
80+
);
81+
6682
return (
6783
<>
6884
<PricingHero
@@ -78,8 +94,24 @@ export const OfferPageWrapper: FC<OfferPageWrapperProps> = ({
7894
messageInactive: 'Quarterly',
7995
messageActive: 'Yearly (Save 5%)',
8096
}}
97+
isAnimationEnabled={isAnimationEnabled}
8198
/>
82-
<div className={getBlocksWith('__pentagons')}>
99+
<motion.div
100+
className={getBlocksWith('__pentagons')}
101+
ref={cardsRef}
102+
{...getCardsAnimation({
103+
inView: areCardsInView,
104+
delay: 0.6,
105+
additionalEffects: {
106+
hiddenAdditional: {
107+
y: 50,
108+
},
109+
enterAdditional: {
110+
y: 0,
111+
},
112+
},
113+
})}
114+
>
83115
{plans.items.map((plan, index) => {
84116
const pricingValue = plan.price?.[discount];
85117
const href = plan.cta.link.url;
@@ -95,7 +127,7 @@ export const OfferPageWrapper: FC<OfferPageWrapperProps> = ({
95127
/>
96128
);
97129
})}
98-
</div>
130+
</motion.div>
99131
<div className={getBlocksWith('__utilization')}>
100132
<h2>Indicative Professional Service Point utilization</h2>
101133
<div className={getBlocksWith('__utilization-subtitle')}>{utilizationDescription}</div>

Diff for: src/components/OfferPageWrapper/TimeScale/TimeScale.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import './TimeScale.scss';
66

77
interface TimeScaleProps {
88
data: {
9-
items: string[];
10-
time: string;
9+
items: string[] | React.ReactNode[];
10+
time: string | number;
1111
}[];
1212
isShifted?: boolean;
1313
}

0 commit comments

Comments
 (0)