@comicrelief/component-library 6.10.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cypress/integration/components/Organisms/EmailSignUp.spec.js +47 -132
- package/dist/components/Organisms/EmailSignUp/EmailSignUp.md +8 -123
- package/dist/components/Organisms/EmailSignUp/EmailSignUp.style.js +46 -29
- package/dist/components/Organisms/EmailSignUp/EmailSignUp.test.js +24 -69
- package/dist/components/Organisms/EmailSignUp/EmailSignUpForm.js +92 -0
- package/dist/components/Organisms/EmailSignUp/_Confetti.js +116 -0
- package/dist/components/Organisms/EmailSignUp/_EmailSignUp.js +107 -0
- package/dist/components/Organisms/EmailSignUp/_EmailSignUpConfig.js +51 -0
- package/dist/components/Organisms/EmailSignUp/_TextInput.js +51 -0
- package/dist/components/Organisms/EmailSignUp/__snapshots__/EmailSignUp.test.js.snap +249 -406
- package/dist/components/Organisms/Header/Header.md +1 -13
- package/dist/components/Organisms/Membership/Membership.test.js +1 -1
- package/dist/index.js +14 -10
- package/package.json +2 -1
- package/src/components/Organisms/EmailSignUp/EmailSignUp.md +8 -123
- package/src/components/Organisms/EmailSignUp/EmailSignUp.style.js +33 -13
- package/src/components/Organisms/EmailSignUp/EmailSignUp.test.js +35 -69
- package/src/components/Organisms/EmailSignUp/EmailSignUpForm.js +60 -0
- package/src/components/Organisms/EmailSignUp/_Confetti.js +106 -0
- package/src/components/Organisms/EmailSignUp/_EmailSignUp.js +138 -0
- package/src/components/Organisms/EmailSignUp/_EmailSignUpConfig.js +54 -0
- package/src/components/Organisms/EmailSignUp/_TextInput.js +45 -0
- package/src/components/Organisms/EmailSignUp/__snapshots__/EmailSignUp.test.js.snap +249 -406
- package/src/components/Organisms/Header/Header.md +1 -13
- package/src/components/Organisms/Membership/Membership.test.js +33 -33
- package/src/index.js +10 -4
- package/cypress/integration/components/Molecules/HeaderEsuWithIcon.spec.js +0 -69
- package/dist/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.js +0 -136
- package/dist/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.md +0 -47
- package/dist/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.style.js +0 -52
- package/dist/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.test.js +0 -99
- package/dist/components/Molecules/HeaderEsuWithIcon/__snapshots__/HeaderEsuWithIcon.test.js.snap +0 -1211
- package/dist/components/Molecules/HeaderEsuWithIcon/assets/HeaderIcons.js +0 -25
- package/dist/components/Molecules/HeaderEsuWithIcon/assets/icon--close.svg +0 -5
- package/dist/components/Molecules/HeaderEsuWithIcon/assets/icon--email.svg +0 -5
- package/dist/components/Organisms/EmailSignUp/EmailSignUp.js +0 -182
- package/src/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.js +0 -135
- package/src/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.md +0 -47
- package/src/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.style.js +0 -60
- package/src/components/Molecules/HeaderEsuWithIcon/HeaderEsuWithIcon.test.js +0 -103
- package/src/components/Molecules/HeaderEsuWithIcon/__snapshots__/HeaderEsuWithIcon.test.js.snap +0 -1211
- package/src/components/Molecules/HeaderEsuWithIcon/assets/HeaderIcons.js +0 -15
- package/src/components/Molecules/HeaderEsuWithIcon/assets/icon--close.svg +0 -5
- package/src/components/Molecules/HeaderEsuWithIcon/assets/icon--email.svg +0 -5
- package/src/components/Organisms/EmailSignUp/EmailSignUp.js +0 -197
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback, useEffect, useRef, useState
|
|
3
|
+
} from 'react';
|
|
4
|
+
import ReactCanvasConfetti from 'react-canvas-confetti';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
|
|
7
|
+
function randomInRange(min, max) {
|
|
8
|
+
return Math.random() * (max - min) + min;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const canvasStyles = {
|
|
12
|
+
position: 'fixed',
|
|
13
|
+
pointerEvents: 'none',
|
|
14
|
+
width: '100%',
|
|
15
|
+
height: '100%',
|
|
16
|
+
top: 0,
|
|
17
|
+
left: 0
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function getAnimationSettings(originXA, originXB) {
|
|
21
|
+
return {
|
|
22
|
+
startVelocity: 30,
|
|
23
|
+
spread: 360,
|
|
24
|
+
ticks: 60,
|
|
25
|
+
zIndex: 0,
|
|
26
|
+
particleCount: 150,
|
|
27
|
+
origin: {
|
|
28
|
+
x: randomInRange(originXA, originXB),
|
|
29
|
+
y: Math.random() - 0.2
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// TODO: Refactor this into an atom
|
|
34
|
+
export default function Confetti({ trigger, duration }) {
|
|
35
|
+
const refAnimationInstance = useRef(null);
|
|
36
|
+
const [intervalId, setIntervalId] = useState();
|
|
37
|
+
|
|
38
|
+
const getInstance = useCallback(instance => {
|
|
39
|
+
refAnimationInstance.current = instance;
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const nextTickAnimation = useCallback(() => {
|
|
43
|
+
if (refAnimationInstance.current) {
|
|
44
|
+
refAnimationInstance.current(getAnimationSettings(0.1, 0.3));
|
|
45
|
+
refAnimationInstance.current(getAnimationSettings(0.7, 0.9));
|
|
46
|
+
}
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const startAnimation = useCallback(() => {
|
|
50
|
+
if (!intervalId) {
|
|
51
|
+
setIntervalId(setInterval(nextTickAnimation, 400));
|
|
52
|
+
}
|
|
53
|
+
}, [intervalId, nextTickAnimation]);
|
|
54
|
+
|
|
55
|
+
const pauseAnimation = useCallback(() => {
|
|
56
|
+
clearInterval(intervalId);
|
|
57
|
+
setIntervalId(null);
|
|
58
|
+
}, [intervalId]);
|
|
59
|
+
|
|
60
|
+
const stopAnimation = useCallback(() => {
|
|
61
|
+
clearInterval(intervalId);
|
|
62
|
+
setIntervalId(null);
|
|
63
|
+
if (refAnimationInstance.current) {
|
|
64
|
+
refAnimationInstance.current.reset();
|
|
65
|
+
}
|
|
66
|
+
}, [intervalId]);
|
|
67
|
+
|
|
68
|
+
// eslint-disable-next-line
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
return () => {
|
|
71
|
+
clearInterval(intervalId);
|
|
72
|
+
};
|
|
73
|
+
}, [intervalId]);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
let timeOut;
|
|
77
|
+
if (trigger) {
|
|
78
|
+
startAnimation();
|
|
79
|
+
timeOut = setTimeout(() => {
|
|
80
|
+
// This gracefully ends the animation
|
|
81
|
+
pauseAnimation();
|
|
82
|
+
}, duration);
|
|
83
|
+
}
|
|
84
|
+
return () => {
|
|
85
|
+
if (timeOut) {
|
|
86
|
+
// this clears up the animation
|
|
87
|
+
stopAnimation();
|
|
88
|
+
}
|
|
89
|
+
}; // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
90
|
+
}, [trigger, duration]);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
<ReactCanvasConfetti refConfetti={getInstance} style={canvasStyles} />
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Confetti.defaultProps = {
|
|
100
|
+
duration: 3000
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
Confetti.propTypes = {
|
|
104
|
+
trigger: PropTypes.bool.isRequired,
|
|
105
|
+
duration: PropTypes.number
|
|
106
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import {
|
|
4
|
+
ESUWrapper,
|
|
5
|
+
TopCopyWrapper,
|
|
6
|
+
FormInner,
|
|
7
|
+
PrivacyCopyWrapper,
|
|
8
|
+
InputField,
|
|
9
|
+
ButtonWrapper,
|
|
10
|
+
Title,
|
|
11
|
+
NameWrapper
|
|
12
|
+
} from './EmailSignUp.style';
|
|
13
|
+
import ButtonWithStates from '../../Atoms/ButtonWithStates/ButtonWithStates';
|
|
14
|
+
|
|
15
|
+
import Text from '../../Atoms/Text/Text';
|
|
16
|
+
import { buildEsuValidationSchema, ESU_FIELDS } from './_EmailSignUpConfig';
|
|
17
|
+
import ErrorText from '../../Atoms/ErrorText/ErrorText';
|
|
18
|
+
import Confetti from './_Confetti';
|
|
19
|
+
|
|
20
|
+
const EmailSignUp = ({
|
|
21
|
+
title,
|
|
22
|
+
topCopy,
|
|
23
|
+
successCopy,
|
|
24
|
+
privacyCopy,
|
|
25
|
+
backgroundColour,
|
|
26
|
+
buttonColour,
|
|
27
|
+
formContext,
|
|
28
|
+
columnLayout,
|
|
29
|
+
...rest
|
|
30
|
+
}) => {
|
|
31
|
+
const {
|
|
32
|
+
formState: {
|
|
33
|
+
isValid,
|
|
34
|
+
isSubmitting,
|
|
35
|
+
isSubmitted,
|
|
36
|
+
isSubmitSuccessful,
|
|
37
|
+
errors
|
|
38
|
+
}
|
|
39
|
+
} = formContext;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ESUWrapper backgroundColour={backgroundColour} {...rest}>
|
|
43
|
+
<Title tag="h2" size="xxl" weight="400" family="Anton" uppercase>
|
|
44
|
+
{title}
|
|
45
|
+
</Title>
|
|
46
|
+
{!isSubmitted ? (
|
|
47
|
+
<TopCopyWrapper>
|
|
48
|
+
<Text>{topCopy}</Text>
|
|
49
|
+
</TopCopyWrapper>
|
|
50
|
+
) : (
|
|
51
|
+
isSubmitSuccessful && (
|
|
52
|
+
<>
|
|
53
|
+
<Confetti trigger={isSubmitSuccessful} />
|
|
54
|
+
<TopCopyWrapper>
|
|
55
|
+
<Text>{successCopy}</Text>
|
|
56
|
+
</TopCopyWrapper>
|
|
57
|
+
</>
|
|
58
|
+
)
|
|
59
|
+
)}
|
|
60
|
+
{!isSubmitSuccessful && (
|
|
61
|
+
<FormInner>
|
|
62
|
+
<NameWrapper columnLayout={columnLayout}>
|
|
63
|
+
<InputField
|
|
64
|
+
fieldName={ESU_FIELDS.FIRST_NAME}
|
|
65
|
+
id="first-name"
|
|
66
|
+
type="text"
|
|
67
|
+
label="First Name"
|
|
68
|
+
placeholder="Enter your first name"
|
|
69
|
+
formContext={formContext}
|
|
70
|
+
/>
|
|
71
|
+
<InputField
|
|
72
|
+
fieldName={ESU_FIELDS.LAST_NAME}
|
|
73
|
+
id="last-name"
|
|
74
|
+
type="text"
|
|
75
|
+
label="Last Name"
|
|
76
|
+
placeholder="Enter your last name"
|
|
77
|
+
formContext={formContext}
|
|
78
|
+
/>
|
|
79
|
+
</NameWrapper>
|
|
80
|
+
<InputField
|
|
81
|
+
fieldName={ESU_FIELDS.EMAIL}
|
|
82
|
+
id="email"
|
|
83
|
+
type="email"
|
|
84
|
+
label="Email Address"
|
|
85
|
+
placeholder="example@youremail.com"
|
|
86
|
+
formContext={formContext}
|
|
87
|
+
/>
|
|
88
|
+
<ButtonWrapper buttonColour={buttonColour}>
|
|
89
|
+
<ButtonWithStates
|
|
90
|
+
type="submit"
|
|
91
|
+
disabled={!isValid || isSubmitting}
|
|
92
|
+
loading={isSubmitting}
|
|
93
|
+
loadingText="Submitting..."
|
|
94
|
+
data-test="subscribe-button"
|
|
95
|
+
>
|
|
96
|
+
<Text>Subscribe</Text>
|
|
97
|
+
</ButtonWithStates>
|
|
98
|
+
</ButtonWrapper>
|
|
99
|
+
</FormInner>
|
|
100
|
+
)}
|
|
101
|
+
{isSubmitted && !isSubmitSuccessful && (
|
|
102
|
+
<>
|
|
103
|
+
{/*
|
|
104
|
+
Field errors will prevent submission,
|
|
105
|
+
so theoretically this should just be a single error set in the submission callback
|
|
106
|
+
with with RHF's `setError` method, but will neatly display multiple errors.
|
|
107
|
+
*/}
|
|
108
|
+
{Object.values(errors).map(error => (
|
|
109
|
+
<ErrorText>{error.message}</ErrorText>
|
|
110
|
+
))}
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
<PrivacyCopyWrapper>
|
|
115
|
+
<Text>{privacyCopy}</Text>
|
|
116
|
+
</PrivacyCopyWrapper>
|
|
117
|
+
</ESUWrapper>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
EmailSignUp.propTypes = {
|
|
122
|
+
title: PropTypes.string.isRequired,
|
|
123
|
+
topCopy: PropTypes.node.isRequired,
|
|
124
|
+
successCopy: PropTypes.node.isRequired,
|
|
125
|
+
privacyCopy: PropTypes.node.isRequired,
|
|
126
|
+
backgroundColour: PropTypes.string,
|
|
127
|
+
buttonColour: PropTypes.string,
|
|
128
|
+
formContext: PropTypes.shape().isRequired,
|
|
129
|
+
columnLayout: PropTypes.bool
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
EmailSignUp.defaultProps = {
|
|
133
|
+
backgroundColour: 'deep_violet_dark',
|
|
134
|
+
buttonColour: 'red',
|
|
135
|
+
columnLayout: false
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export { EmailSignUp, buildEsuValidationSchema, ESU_FIELDS };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { merge } from 'lodash';
|
|
2
|
+
import * as yup from 'yup';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ESU_FIELDS
|
|
6
|
+
*
|
|
7
|
+
* Exposes an enum to consumer of the component, to accurately access the underlying field names.
|
|
8
|
+
* can be used in conjunction with RHF or buildEsuValidationSchema
|
|
9
|
+
* to customise form validation or behaviour, as the fields are handled within the CL
|
|
10
|
+
* we just make this read-only to prevent any external changes of this object.
|
|
11
|
+
*/
|
|
12
|
+
const ESU_FIELDS = Object.freeze({
|
|
13
|
+
FIRST_NAME: 'firstName',
|
|
14
|
+
LAST_NAME: 'lastName',
|
|
15
|
+
EMAIL: 'email'
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* buildEsuValidationSchema
|
|
20
|
+
*
|
|
21
|
+
* Exposes a function that can be passed a partial or complete yup schema
|
|
22
|
+
* to extend or override the default buildEsuValidationSchema
|
|
23
|
+
*
|
|
24
|
+
* @param overrides {Object} - A yup schema object (or an empty object)
|
|
25
|
+
*/
|
|
26
|
+
const buildEsuValidationSchema = overrides => {
|
|
27
|
+
const defaultSchema = yup.object({
|
|
28
|
+
[ESU_FIELDS.FIRST_NAME]: yup
|
|
29
|
+
.string()
|
|
30
|
+
.required('Please enter your first name')
|
|
31
|
+
.matches(
|
|
32
|
+
/^[A-Za-z][A-Za-z' -]*$/,
|
|
33
|
+
"This field only accepts letters and ' - and must start with a letter"
|
|
34
|
+
)
|
|
35
|
+
.max(25, 'Your first name must be between 1 and 25 characters'),
|
|
36
|
+
[ESU_FIELDS.LAST_NAME]: yup
|
|
37
|
+
.string()
|
|
38
|
+
.required('Please enter your last name')
|
|
39
|
+
.matches(
|
|
40
|
+
/^[A-Za-z][A-Za-z' -]*$/,
|
|
41
|
+
"This field only accepts letters and ' - and must start with a letter"
|
|
42
|
+
)
|
|
43
|
+
.max(50, 'Your first name must be between 1 and 50 characters'),
|
|
44
|
+
[ESU_FIELDS.EMAIL]: yup
|
|
45
|
+
.string()
|
|
46
|
+
.required('Please enter your email address')
|
|
47
|
+
.email('Please enter a valid email address')
|
|
48
|
+
.max(100, 'Your email address must be between 1 and 100 characters long')
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return merge(defaultSchema, overrides);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { ESU_FIELDS, buildEsuValidationSchema };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import Input from '../../Atoms/Input/Input';
|
|
4
|
+
|
|
5
|
+
// TODO: This is a copy paste of the RHF friendly `TextInput` from Marketing Prefs.
|
|
6
|
+
// Perhaps it would be worthwhile refactoring this into a new `Atom` as a seperate PR.
|
|
7
|
+
const TextInput = ({
|
|
8
|
+
fieldName,
|
|
9
|
+
label,
|
|
10
|
+
optional,
|
|
11
|
+
fieldType,
|
|
12
|
+
formContext,
|
|
13
|
+
...rest
|
|
14
|
+
}) => {
|
|
15
|
+
const { errors, register } = formContext;
|
|
16
|
+
|
|
17
|
+
const props = {
|
|
18
|
+
name: fieldName,
|
|
19
|
+
type: fieldType,
|
|
20
|
+
label,
|
|
21
|
+
placeholder: label,
|
|
22
|
+
errorMsg: errors && errors[fieldName] && errors[fieldName].message,
|
|
23
|
+
optional,
|
|
24
|
+
'aria-required': !optional,
|
|
25
|
+
...rest
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return <Input {...props} ref={register} />;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
TextInput.defaultProps = {
|
|
32
|
+
optional: null,
|
|
33
|
+
fieldType: 'text',
|
|
34
|
+
formContext: null
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
TextInput.propTypes = {
|
|
38
|
+
fieldName: PropTypes.string.isRequired,
|
|
39
|
+
label: PropTypes.string.isRequired,
|
|
40
|
+
optional: PropTypes.bool,
|
|
41
|
+
fieldType: PropTypes.string,
|
|
42
|
+
formContext: PropTypes.shape()
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default TextInput;
|