gatsby-core-theme 25.0.14 → 26.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/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ # [26.0.0](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/compare/v25.0.14...v26.0.0) (2023-08-16)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fix storybook form json ([da6d479](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/da6d479186108e875c53e0160bb99a1d9d06fdd1))
7
+ * moved contact form story to template block ([ddd4f02](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/ddd4f02c9720dcca58d57a595547bc02af0990f6))
8
+
9
+
10
+ ### Code Refactoring
11
+
12
+ * dynamic form with validations, storybook and test. constant form json ([d727a64](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/d727a64af6466c6a92422437783232a1fa203d63))
13
+ * remove unwanted code and add responsive css ([d091869](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/d091869597e07c438c2938693a2b3aa9c7a81682))
14
+ * temp changes ([241386b](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/241386bb27caaa433fce62b29a5355384a1b770f))
15
+
16
+
17
+ * Merge branch 'tm-3556-form' into 'master' ([325d44e](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/325d44e55bd835c97490965214d7e2eb60b89877))
18
+ * Merge branch 'master' into tm-3556-form ([8b16477](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/commit/8b164778193dd10cb663bf0069f1029e68d241ac))
19
+
1
20
  ## [25.0.14](https://git.ilcd.rocks/team-floyd/themes/gatsby-themes/compare/v25.0.13...v25.0.14) (2023-08-10)
2
21
 
3
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gatsby-core-theme",
3
- "version": "25.0.14",
3
+ "version": "26.0.0",
4
4
  "description": "Gatsby Theme NPM Package",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -0,0 +1,88 @@
1
+ .contactForm {
2
+ @include flex-align(center, center);
3
+ }
4
+
5
+ .contactBox {
6
+ width: 100%;
7
+ max-width: var(--main-container-max);
8
+ margin: 0 auto;
9
+ gap: 2.4rem;
10
+ @include flex-direction(column);
11
+
12
+ @include min(tablet) {
13
+ @include flex-direction(row);
14
+ }
15
+
16
+ @include min(laptop) {
17
+ margin: 0;
18
+ }
19
+ }
20
+
21
+ .formWrapper {
22
+ flex-basis: 100%;
23
+ padding: 0;
24
+ gap: 2.4rem;
25
+ @include flex-direction(column);
26
+
27
+ p {
28
+ font-weight: normal;
29
+ font-size: 1.6rem;
30
+ line-height: 2.7rem;
31
+ color: #515156;
32
+ }
33
+
34
+ h1 {
35
+ margin: 0;
36
+ font-weight: bold;
37
+ font-size: 2.4rem;
38
+ line-height: 3.2rem;
39
+ color: #1b1b1c;
40
+ text-transform: capitalize;
41
+
42
+ @include min(tablet) {
43
+ font-size: 3.2rem;
44
+ line-height: 4rem;
45
+ }
46
+ }
47
+ }
48
+
49
+ .socialSection {
50
+ @include flex-direction(column);
51
+ gap: 2rem;
52
+ flex-grow: 1;
53
+ flex-basis: 40rem;
54
+ }
55
+
56
+ .emailContainer {
57
+ @include flex-align(flex-start, center);
58
+ @include flex-direction(column);
59
+ gap: 1.6rem;
60
+
61
+ h2 {
62
+ margin: 0;
63
+ font-weight: bold;
64
+ font-size: 3.2rem;
65
+ line-height: 4.6rem;
66
+ color: var(--color-33);
67
+ }
68
+
69
+ p {
70
+ @include flex-align(center, center);
71
+ gap: 1rem;
72
+ }
73
+
74
+ span {
75
+ display: inline-block;
76
+ width: 4rem;
77
+ height: 4rem;
78
+ line-height: 4.7rem;
79
+ border-radius: 50%;
80
+ background: var(--color-52);
81
+ text-align: center;
82
+ font-size: 2rem;
83
+
84
+ svg {
85
+ color: #000;
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ // eslint-disable-next-line import/no-extraneous-dependencies
3
+ import {
4
+ Title,
5
+ Description,
6
+ Primary,
7
+ PRIMARY_STORY,
8
+ ArgsTable,
9
+ } from '@storybook/addon-docs/blocks';
10
+ import { siteSchema } from 'gatsby-core-theme/tests/factories/modules/site-schema.factory';
11
+ import ContactForm from '.';
12
+
13
+ const page = {};
14
+ siteSchema.support_email = 'contact@irishluck.ie';
15
+ page.siteSchema = { ...siteSchema };
16
+
17
+ export default {
18
+ title: 'Theme/Modules/Template Blocks/Contact Form',
19
+ component: ContactForm,
20
+ argTypes: {
21
+ page: {
22
+ name: 'page',
23
+ type: { name: 'object', required: true },
24
+ defaultValue: '',
25
+ description: 'page object',
26
+ siteSchema: {
27
+ type: { summary: 'object' },
28
+ defaultValue: { summary: '' },
29
+ },
30
+ },
31
+ submitUrl: {
32
+ name: 'submitUrl',
33
+ type: { name: 'string', required: false },
34
+ defaultValue: null,
35
+ description: 'submit URL',
36
+ },
37
+ successMessage: {
38
+ name: 'successMessage',
39
+ type: { name: 'string', required: false },
40
+ defaultValue: 'Message sent successfully.',
41
+ description: 'sent message text',
42
+ },
43
+ failMessage: {
44
+ name: 'failMessage',
45
+ type: { name: 'string', required: false },
46
+ defaultValue: 'Message not sent.',
47
+ description: 'fail message text',
48
+ },
49
+ validationMessage: {
50
+ name: 'validationMessage',
51
+ type: { name: 'string', required: false },
52
+ defaultValue: 'Fill all the required fields.',
53
+ description: 'sent message info',
54
+ },
55
+ },
56
+ parameters: {
57
+ docs: {
58
+ description: {
59
+ component: 'A component that displays authors cards',
60
+ },
61
+ page: () => (
62
+ <>
63
+ <Title />
64
+ <Description />
65
+ <Primary />
66
+ <ArgsTable story={PRIMARY_STORY} />
67
+ </>
68
+ ),
69
+ },
70
+ },
71
+ };
72
+
73
+ const Template = (args) => <ContactForm {...args} />;
74
+ export const Default = Template.bind({});
75
+ Default.args = {
76
+ page,
77
+ };
@@ -0,0 +1,40 @@
1
+ /* eslint-disable camelcase */
2
+ import React from 'react';
3
+ import { render, cleanup, waitFor } from '@testing-library/react';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+ import { siteSchema } from 'gatsby-core-theme/tests/factories/modules/site-schema.factory';
6
+ import ContactForm from '.';
7
+
8
+ const page = {};
9
+
10
+ beforeEach(() => {
11
+ page.siteSchema = { ...siteSchema };
12
+ });
13
+ describe('Contact Us form Component', () => {
14
+ test('render contact us form and follow us titles', async () => {
15
+ const { container } = render(<ContactForm page={page} submitUrl="submit" />);
16
+
17
+ await waitFor(() => {
18
+ expect(container).toBeTruthy();
19
+ expect(container.querySelector('H1').innerHTML).toBe('Contact Us');
20
+ });
21
+ });
22
+ test('render contact us form ', async () => {
23
+ const { container } = render(<ContactForm page={page} submitUrl="submit" />);
24
+
25
+ await waitFor(() => {
26
+ expect(container.querySelector('.contactForm')).toBeTruthy();
27
+ expect(container.querySelector('.contactForm').querySelectorAll('input')).toHaveLength(3);
28
+ expect(
29
+ container.querySelector('.contactForm').querySelectorAll('input[type="text"]')
30
+ ).toHaveLength(1);
31
+ expect(
32
+ container.querySelector('.contactForm').querySelectorAll('input[type="email"]')
33
+ ).toHaveLength(1);
34
+ expect(container.querySelector('.contactForm').querySelectorAll('textarea')).toHaveLength(1);
35
+ });
36
+ });
37
+ });
38
+ afterEach(() => {
39
+ cleanup();
40
+ });
@@ -0,0 +1,55 @@
1
+ import React, { useContext } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Context } from 'gatsby-core-theme/src/context/MainProvider';
4
+ import Form from 'gatsby-core-theme/src/components/organisms/form';
5
+ import { translate } from 'gatsby-core-theme/src/helpers/getters';
6
+ import { contactUsForm } from '../../../constants/forms';
7
+ import styles from './contact-form.module.scss';
8
+
9
+ const ContactForm = ({
10
+ submitUrl,
11
+ successMessage = 'Message sent successfully.',
12
+ failMessage = 'Message failed to send.',
13
+ validationMessage = 'Fill all the required fields.',
14
+ }) => {
15
+ const { translations } = useContext(Context) || {};
16
+
17
+ return (
18
+ <div className={styles.contactForm}>
19
+ <div className={styles.contactBox}>
20
+ <div className={styles.formWrapper}>
21
+ <h1>{translate(translations, 'contact_us', 'Contact Us')}</h1>
22
+ <p>
23
+ {translate(
24
+ translations,
25
+ 'contact_us_questions',
26
+ `If you have questions about our reviews, games or content, or just want to leave some
27
+ feedback, the Irish Luck team would love to hear from you. You can contact us any time
28
+ using the details below and we'll endeavour to get back to you within 48 hours.`
29
+ )}
30
+ </p>
31
+ <Form
32
+ formOptions={contactUsForm}
33
+ type="contact"
34
+ hasButton
35
+ buttonLabel={translate(translations, 'send_msg', 'Send Message')}
36
+ submitUrl={submitUrl}
37
+ successMessage={successMessage}
38
+ failMessage={failMessage}
39
+ validationMessage={validationMessage}
40
+ />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ );
45
+ };
46
+
47
+ export default ContactForm;
48
+
49
+ ContactForm.propTypes = {
50
+ page: PropTypes.shape({}).isRequired,
51
+ submitUrl: PropTypes.string,
52
+ successMessage: PropTypes.string,
53
+ failMessage: PropTypes.string,
54
+ validationMessage: PropTypes.string,
55
+ };
@@ -1,31 +1,3 @@
1
- @mixin buttonsColor($color1, $color2, $color3, $textColor: 'white') {
2
- display: inline-flex;
3
- align-items: center;
4
- justify-content: center;
5
- text-align: center;
6
- background-color: $color1;
7
- color: $textColor;
8
- padding: 0.9rem 3rem;
9
- font-weight: 700;
10
- font-size: 1.8rem;
11
- border-radius: var(--border-radius);
12
-
13
- >svg {
14
- flex: none;
15
- margin-left: .8rem;
16
- }
17
-
18
- &:hover {
19
- background-color: $color2;
20
- color: $textColor;
21
- }
22
-
23
- &:active {
24
- background-color: $color3;
25
- color: $textColor;
26
- }
27
- }
28
-
29
1
  .primary {
30
2
  @include buttonsColor(var(--primary-button-color, #6E33E5), var(--primary-button-color-hover, #776ABA), var(--primary-button-color-active, #998FCB), var(--primary-button-color-text, #FFFFFF));
31
3
  white-space: nowrap;
@@ -25,6 +25,8 @@ const Modules = ({ module, page, pageContext }) => {
25
25
  case 'cards':
26
26
  case 'cards_v2':
27
27
  return loadable(() => import('~organisms/cards'));
28
+ case 'contact_form':
29
+ return loadable(() => import('~atoms/contact-form'));
28
30
  case 'pros_and_cons':
29
31
  return loadable(() => import('~molecules/pros-cons'));
30
32
  case 'faq':
@@ -1,150 +1,223 @@
1
- .contactForm {
2
- width: 100%;
3
- padding-top: 1.6rem;
4
- form {
5
- width: 100%;
6
- @include flex-direction(column);
7
- @include min(tablet) {
8
- display: block;
9
- }
1
+ :global {
2
+ .contact_form {
10
3
  > div {
11
- margin-bottom: 1.1rem;
4
+ margin: 3.2rem auto;
5
+
12
6
  @include min(tablet) {
13
- margin-bottom: 1.6rem;
7
+ margin: 4rem auto;
14
8
  }
15
9
  }
16
10
  }
11
+ }
12
+
13
+ .formComponent {
14
+ padding: 2.4rem;
15
+ width: 100%;
16
+ box-shadow: 0px 4px 6px -2px rgba(27, 27, 28, 0.02), 0px 12px 16px -4px rgba(27, 27, 28, 0.05);
17
+ border-radius: 16px;
18
+ background: #f4f4f4;
19
+ @include flex-direction(column);
20
+
17
21
  @include min(tablet) {
18
- .formGroup {
19
- width: 100%;
20
- display: inline-block;
21
- background-color: var(--color-48);
22
- &.name {
23
- width: calc(50% - 0.8rem);
24
- margin-right: 1.6rem;
25
- }
26
- &.email {
27
- width: calc(50% - 0.8rem);
28
- }
29
- &.message {
30
- width: 100%;
31
- margin-bottom: 1.6rem;
32
- }
33
- }
22
+ padding: 2.4rem;
34
23
  }
35
- @include min(desktop) {
36
- .formGroup {
37
- width: 100%;
38
- &.name {
39
- width: calc(50% - 0.8rem);
40
- margin-right: 1.6rem;
41
- }
42
- &.email {
43
- width: calc(50% - 0.8rem);
44
- }
45
- &.message {
46
- width: 100%;
47
- }
48
- }
24
+
25
+ h2 {
26
+ font-size: 2rem;
27
+ line-height: 2.8rem;
28
+ font-weight: 700;
29
+ text-transform: capitalize;
30
+ margin: 0;
31
+
32
+ @include min(tablet) {
33
+ font-size: 2.8rem;
34
+ line-height: 3.6rem;
35
+ }
36
+ }
37
+
38
+ form {
39
+ width: 100%;
49
40
  }
50
41
 
51
42
  input,
52
- textarea {
53
- background: var(--color-48);
54
- border-radius: 0.3rem;
43
+ textarea,
44
+ select {
45
+ background: var(--color-4);
55
46
  font-size: 1.4rem;
56
47
  line-height: 2.1rem;
57
48
  padding: 1.6rem;
58
49
  width: 100%;
59
50
  max-width: 100%;
60
- border: none;
61
51
  outline: none;
52
+ border-radius: 0.8rem;
53
+ border: 1.5px solid #F4F4F4;
54
+
55
+
62
56
  &:invalid,
63
57
  &.invalid {
64
58
  border: 1px solid var(--color-39);
65
59
  color: var(--color-39);
66
60
  }
67
61
  }
62
+
68
63
  input {
69
64
  height: 4.4rem;
70
65
  }
66
+
67
+ input[type="file"] {
68
+ height: auto;
69
+ }
70
+
71
+ input[type="color"],
72
+ input[type="range"] {
73
+ padding: 0;
74
+ }
75
+
76
+ input[type="checkbox"],
77
+ input[type="radio"] {
78
+ width: auto;
79
+ height: auto;
80
+ }
81
+
71
82
  textarea {
72
83
  height: 12rem;
73
84
  display: block;
74
85
  resize: vertical;
75
86
  }
87
+
76
88
  label {
77
- display: none;
89
+ font-size: 1.6rem;
90
+ font-weight: 700;
91
+ line-height: 2.7rem;
92
+ color: #262629;
93
+
94
+ span {
95
+ color: red;
96
+ }
97
+
98
+ &:invalid,
99
+ &.invalid {
100
+ color: var(--color-39);
101
+ }
102
+ }
103
+
104
+ button {
105
+ @include buttonsColor(var(--primary-button-color, #6E33E5), var(--primary-button-color-hover, #776ABA), var(--primary-button-color-active, #998FCB), var(--primary-button-color-text, #FFFFFF));
106
+ line-height: 2.7rem;
107
+ text-transform: capitalize;
108
+ gap: 0.4rem;
109
+ padding: 1.6rem 2.4rem;
110
+ border-radius: 100px;
78
111
  }
79
112
  }
80
113
 
81
- .formButton {
82
- display: inline-block;
114
+ .formGroup {
83
115
  width: 100%;
84
- margin-bottom: .8rem;
85
- button {
86
- margin: 0;
87
- width: 100%;
88
- text-align: center;
89
- height: 4.8rem;
90
- &:focus {
91
- border: none;
92
- outline: none;
93
- }
116
+ @include flex-direction(column);
117
+ gap: 0.4rem;
118
+ }
119
+
120
+ .radioGroup,
121
+ .checkboxGroup,
122
+ .textareaGroup {
123
+ @include flex-direction(column);
124
+
125
+ > label {
126
+ @include flex-align(center, flex-start);
127
+ gap: 0.8rem;
128
+ font-weight: normal;
94
129
  }
95
- @include min(tablet) {
96
- width: 100%;
97
- button {
98
- min-width: 16.2rem;
99
- }
130
+ }
131
+
132
+ .textareaGroup {
133
+ @include flex-align(flex-end, flex-start);
134
+
135
+ > span {
136
+ font-size: 1.4rem;
137
+ font-weight: 400;
138
+ line-height: 2.2rem;
100
139
  }
101
- @include min(tablet) {
102
- width: auto;
103
- margin-right: 1.6rem;
104
- display: inline-block;
140
+ }
141
+
142
+ .rangeValues {
143
+ @include flex-align(center, space-between);
144
+
145
+ .invalid {
146
+ color: var(--color-39);
105
147
  }
106
148
  }
149
+
150
+
107
151
  .formAlerts {
108
152
  display: inline-block;
153
+ grid-column: span 2 / span 2;
109
154
  order: -1;
155
+
110
156
  @include min(tablet) {
111
157
  width: calc(100% - 18rem);
112
158
  order: unset;
113
159
  }
160
+
114
161
  @include min(desktop) {
115
162
  display: inline-block;
116
163
  }
164
+
117
165
  div {
118
166
  border-radius: 0.4rem;
119
- border: 1px solid var(--color-33);
167
+ border-width: 1px;
168
+ border-style: solid;
120
169
  font-size: 1.4rem;
121
170
  font-weight: 400;
122
171
  width: 100%;
123
172
  height: 4.4rem;
124
173
  padding: 1rem 1.6rem;
125
- &.alertSuccess {
126
- border-color: var(--color-44);
127
- color: var(--color-44) ;
128
- }
129
- &.alertDanger {
130
- border-color: var(--color-11);
131
- color: var(--color-11);
132
- }
133
- &.alertWarning {
134
- border-color: var(--color-39);
135
- color: var(--color-39);
136
- }
137
174
  }
138
175
  }
139
- .newsLetterForm {
140
- max-width: 40rem;
141
- .formGroup {
176
+
177
+
178
+ .alertSuccess {
179
+ border-color: var(--color-44);
180
+ color: var(--color-44) ;
181
+ }
182
+
183
+ .alertDanger {
184
+ border-color: var(--color-11);
185
+ color: var(--color-11);
186
+ }
187
+
188
+ .alertWarning {
189
+ border-color: var(--color-39);
190
+ color: var(--color-39);
191
+ }
192
+
193
+ .twoCol {
194
+ @include flex-direction(column);
195
+ gap: 2.4rem;
196
+
197
+ @include min(tablet) {
198
+ display: grid;
199
+ grid-template-columns: repeat(2, 1fr);
200
+ column-gap: 4.8rem;
201
+ row-gap: 1.6rem;
202
+ }
203
+
204
+ button {
142
205
  width: 100%;
143
- &.name {
144
- width: 100%;
145
- }
146
- &.email {
147
- width: 100%;
148
- }
149
206
  }
150
207
  }
208
+
209
+ .singleCol {
210
+ @include flex-direction(column);
211
+ gap: 2.4rem;
212
+ }
213
+
214
+ .fullWidth {
215
+ grid-column: span 2 / span 2;
216
+ }
217
+
218
+ .formButton {
219
+ grid-column: span 2 / span 2;
220
+ button {
221
+ width: auto;
222
+ }
223
+ }
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ // eslint-disable-next-line import/no-extraneous-dependencies
2
3
  import {
3
4
  Title,
4
5
  Description,
@@ -6,6 +7,7 @@ import {
6
7
  PRIMARY_STORY,
7
8
  ArgsTable,
8
9
  } from '@storybook/addon-docs/blocks';
10
+ import { contactUsForm } from '../../../constants/forms';
9
11
 
10
12
  import Form from '.';
11
13
 
@@ -13,47 +15,13 @@ export default {
13
15
  title: 'Theme/Organisms/Form',
14
16
  component: Form,
15
17
  argTypes: {
16
- type: {
17
- name: 'type',
18
- type: { name: 'string', required: true },
19
- defaultValue: 'contact',
20
- description: 'Type of the form.',
21
- table: {
22
- type: { summary: 'string' },
23
- defaultValue: { summary: 'contact' },
24
- },
25
- control: {
26
- type: 'inline-radio',
27
- options: ['contact', 'newsletter'],
28
- },
29
- },
30
- hasButton: {
31
- name: 'hasButton',
32
- type: { name: 'boolean', required: false },
33
- defaultValue: true,
34
- description: 'From has or has not a submit button.',
35
- table: {
36
- type: { summary: 'boolean' },
37
- defaultValue: { summary: true },
38
- },
39
- },
40
- buttonLabel: {
41
- name: 'buttonLabel',
42
- type: { name: 'string', required: false },
43
- defaultValue: 'Submit',
44
- description: `The submit button's text.`,
45
- table: {
46
- type: { summary: 'string' },
47
- defaultValue: { summary: 'Submit' },
48
- },
49
- },
50
- submitUrl: {
51
- name: 'submitUrl',
52
- type: { name: 'string', required: true },
18
+ formOptions: {
19
+ name: 'formOptions',
20
+ type: { name: 'object', required: true },
53
21
  defaultValue: '',
54
- description: `The form data will send to this URL on submit.`,
55
- table: {
56
- type: { summary: 'string' },
22
+ description: 'Form Options object from contstants',
23
+ siteSchema: {
24
+ type: { summary: 'object' },
57
25
  defaultValue: { summary: '' },
58
26
  },
59
27
  },
@@ -87,6 +55,56 @@ export default {
87
55
  defaultValue: { summary: 'Fill all the required fields' },
88
56
  },
89
57
  },
58
+ submitUrl: {
59
+ name: 'submitUrl',
60
+ type: { name: 'string', required: true },
61
+ defaultValue: '',
62
+ description: `The form data will send to this URL on submit.`,
63
+ table: {
64
+ type: { summary: 'string' },
65
+ defaultValue: { summary: '' },
66
+ },
67
+ },
68
+ hasButton: {
69
+ name: 'hasButton',
70
+ type: { name: 'boolean', required: false },
71
+ defaultValue: true,
72
+ description: 'From has or has not a submit button.',
73
+ table: {
74
+ type: { summary: 'boolean' },
75
+ defaultValue: { summary: true },
76
+ },
77
+ },
78
+ buttonLabel: {
79
+ name: 'buttonLabel',
80
+ type: { name: 'string', required: false },
81
+ defaultValue: 'Submit',
82
+ description: `The submit button's text.`,
83
+ table: {
84
+ type: { summary: 'string' },
85
+ defaultValue: { summary: 'Submit' },
86
+ },
87
+ },
88
+ disabled: {
89
+ name: 'disabled',
90
+ type: { name: 'boolean', required: false },
91
+ defaultValue: true,
92
+ description: 'Form disabled',
93
+ table: {
94
+ type: { summary: 'boolean' },
95
+ defaultValue: { summary: true },
96
+ },
97
+ },
98
+ showLabels: {
99
+ name: 'showLabels',
100
+ type: { name: 'boolean', required: false },
101
+ defaultValue: true,
102
+ description: 'Show field lables or hide',
103
+ table: {
104
+ type: { summary: 'boolean' },
105
+ defaultValue: { summary: true },
106
+ },
107
+ },
90
108
  },
91
109
  parameters: {
92
110
  docs: {
@@ -109,11 +127,5 @@ const Template = (args) => <Form {...args} />;
109
127
 
110
128
  export const Default = Template.bind({});
111
129
  Default.args = {
112
- type: 'contact',
113
- successMessage: 'Form has sent successfully',
114
- failMessage: 'Form has not sent, Got some error',
115
- validationMessage: 'Fill all the required fields.',
116
- submitUrl: 'https://your-end-point.com',
117
- hasButton: true,
118
- buttonLabel: 'Submit',
130
+ formOptions: contactUsForm,
119
131
  };
@@ -3,13 +3,19 @@ import ReactDOM from 'react-dom';
3
3
  import { render, cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
4
4
  import '@testing-library/jest-dom/extend-expect';
5
5
  import { act } from 'react-dom/test-utils';
6
+ import { contactUsForm, newsLetterForm } from '../../../constants/forms';
6
7
 
7
8
  import Form from '.';
8
9
 
9
10
  describe('Form Component', () => {
10
11
  test('render contact form ', () => {
11
12
  const { container } = render(
12
- <Form hasButton type="contact" submitUrl="https://submit-form.com" />
13
+ <Form
14
+ formOptions={contactUsForm}
15
+ hasButton
16
+ type="contact"
17
+ submitUrl="https://submit-form.com"
18
+ />
13
19
  );
14
20
  expect(container.querySelector('input[type="text"]')).toBeTruthy();
15
21
  expect(container.querySelector('input[type="email"]')).toBeTruthy();
@@ -18,7 +24,12 @@ describe('Form Component', () => {
18
24
  });
19
25
  test('render newsletter form ', () => {
20
26
  const { container } = render(
21
- <Form hasButton type="newsletter" submitUrl="https://submit-form.com" />
27
+ <Form
28
+ formOptions={newsLetterForm}
29
+ hasButton
30
+ type="newsletter"
31
+ submitUrl="https://submit-form.com"
32
+ />
22
33
  );
23
34
  expect(container.querySelector('input[type="text"]')).toBeTruthy();
24
35
  expect(container.querySelector('input[type="email"]')).toBeTruthy();
@@ -27,7 +38,14 @@ describe('Form Component', () => {
27
38
  });
28
39
 
29
40
  test('on change', async () => {
30
- render(<Form hasButton type="contact" submitUrl="https://submit-form.com" />);
41
+ render(
42
+ <Form
43
+ formOptions={contactUsForm}
44
+ hasButton
45
+ type="contact"
46
+ submitUrl="https://submit-form.com"
47
+ />
48
+ );
31
49
  const input = document.querySelector('input[type="text"]');
32
50
  const messageInput = document.querySelector('textarea');
33
51
 
@@ -43,7 +61,12 @@ describe('Form Component', () => {
43
61
  const container = document.createElement('div');
44
62
  act(() => {
45
63
  ReactDOM.render(
46
- <Form hasButton type="contact" submitUrl="https://submit-form.com" />,
64
+ <Form
65
+ formOptions={contactUsForm}
66
+ hasButton
67
+ type="contact"
68
+ submitUrl="https://submit-form.com"
69
+ />,
47
70
  container
48
71
  );
49
72
  });
@@ -58,7 +81,13 @@ describe('Form Component', () => {
58
81
 
59
82
  test('handle submit with filled fields', async () => {
60
83
  const { container } = render(
61
- <Form hasButton disabled={false} type="newsletter" submitUrl="https://submit-form.com" />
84
+ <Form
85
+ formOptions={newsLetterForm}
86
+ hasButton
87
+ disabled={false}
88
+ type="newsletter"
89
+ submitUrl="https://submit-form.com"
90
+ />
62
91
  );
63
92
  const nameInput = document.querySelector('input[type="text"]');
64
93
  const emailInput = document.querySelector('input[type="email"]');
@@ -1,11 +1,14 @@
1
- import React, { useState, useRef } from 'react';
1
+ import React, { useState, useRef, useContext } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import ReCAPTCHA from 'react-google-recaptcha';
4
+ import { FaArrowRight } from '@react-icons/all-files/fa/FaArrowRight';
5
+ import { translate } from 'gatsby-core-theme/src/helpers/getters';
6
+ import { Context } from 'gatsby-core-theme/src/context/MainProvider';
4
7
  import Button from '~atoms/button';
5
8
  import styles from './form.module.scss';
6
9
 
7
10
  const FormComponent = ({
8
- type = 'contact',
11
+ formOptions = {},
9
12
  successMessage = 'Form has sent successfully',
10
13
  failMessage = 'Form has not sent, Got some error',
11
14
  validationMessage = 'Fill all the required fields.',
@@ -13,8 +16,9 @@ const FormComponent = ({
13
16
  hasButton = true,
14
17
  buttonLabel = 'Submit',
15
18
  disabled = true,
16
- isPrimaryButton = true,
19
+ showLabels = true,
17
20
  }) => {
21
+ const { translations } = useContext(Context) || {};
18
22
  const recaptchaRef = useRef();
19
23
  const [state, setState] = useState({
20
24
  loading: false,
@@ -26,10 +30,14 @@ const FormComponent = ({
26
30
  email: '',
27
31
  message: '',
28
32
  });
33
+ const values = formOptions.fields.map((field) => ({
34
+ [field.id]: field.value ? field.value : '',
35
+ }));
36
+ const [elements, setElements] = useState(Object.assign({}, ...values));
29
37
 
30
- const handleChange = (e) => {
31
- const { name, value } = e.target;
32
- setState({ ...state, [name]: value, isValid: true, success: false, failed: false });
38
+ const handleChange = (name, value) => {
39
+ setState({ ...state, [name]: value });
40
+ setElements({ ...elements, [name]: value });
33
41
  };
34
42
 
35
43
  async function postData(url = '', data = {}) {
@@ -43,21 +51,19 @@ const FormComponent = ({
43
51
  body: JSON.stringify(Object.fromEntries(data)),
44
52
  });
45
53
  }
54
+
46
55
  const handleSubmit = (e) => {
47
56
  e.preventDefault();
48
57
 
49
- const { name, email, message, isDisabled } = state;
58
+ // check if all required fields are empty
59
+ const inValid = Object.values(elements).some((value) => value === '');
50
60
 
51
- if (
52
- name === '' ||
53
- email === '' ||
54
- (type === 'contact' && message === '') ||
55
- isDisabled === true
56
- ) {
61
+ if (inValid) {
57
62
  setState({ ...state, isValid: false, success: false });
58
63
  return;
59
64
  }
60
65
 
66
+ // form is valid
61
67
  const form = e.target;
62
68
  setState({ ...state, loading: true, success: false, failed: false, isDisabled: true });
63
69
  postData(submitUrl, new FormData(form))
@@ -81,7 +87,7 @@ const FormComponent = ({
81
87
  });
82
88
  }
83
89
  })
84
- .catch(() => {
90
+ .catch((err) => {
85
91
  // on error
86
92
  setState({
87
93
  ...state,
@@ -98,56 +104,176 @@ const FormComponent = ({
98
104
  value && setState({ ...state, isDisabled: false });
99
105
  }
100
106
 
101
- return (
102
- <div
103
- className={`${styles.formComponent || ''} ${
104
- type === 'contact' ? styles.contactForm : styles.newsLetterForm
105
- }`}
106
- >
107
- <form onSubmit={(e) => handleSubmit(e)} action={submitUrl} target="_self">
108
- <div className={`${styles.formGroup || ''} ${styles.name || ''}`}>
109
- <input
110
- name="name"
111
- type="text"
112
- value={state.name}
113
- placeholder="Name"
114
- onChange={(e) => handleChange(e)}
115
- disabled={state.loading}
116
- className={`${!state.isValid && state.name === '' && styles.invalid}`}
117
- />
118
- </div>
119
- <div className={`${styles.formGroup || ''} ${styles.email || ''}`}>
107
+ const getField = (field) => {
108
+ const { type, id, translationKey, label, ...props } = field;
109
+
110
+ switch (type) {
111
+ case 'textarea':
112
+ return (
113
+ <div className={styles.textareaGroup}>
114
+ <textarea
115
+ name={id}
116
+ id={id}
117
+ {...props}
118
+ value={elements[id]}
119
+ onChange={(e) => handleChange(e.target.name, e.target.value)}
120
+ className={`${!state.isValid && elements[id] === '' && styles.invalid}`}
121
+ />
122
+ {field.maxLength && <span>{`${elements[id].length}/${field.maxLength}`}</span>}
123
+ </div>
124
+ );
125
+ case 'checkbox':
126
+ return (
127
+ <div className={styles.checkboxGroup}>
128
+ {field.options.map((option) => (
129
+ <label
130
+ key={option.id}
131
+ htmlFor={option.id}
132
+ className={`${!state.isValid && !elements[option.id].length && styles.invalid}`}
133
+ >
134
+ <input
135
+ id={option.id}
136
+ name={option.id}
137
+ type={type}
138
+ checked={elements[option.id] === 'true'}
139
+ onChange={(e) => handleChange(e.target.name, e.target.checked.toString())}
140
+ />
141
+ {translate(translations, option.translationKey, option.label)}
142
+ </label>
143
+ ))}
144
+ </div>
145
+ );
146
+ case 'radio':
147
+ return (
148
+ <div className={styles.radioGroup}>
149
+ {field.options.map((option) => (
150
+ <label
151
+ key={option.id}
152
+ htmlFor={option.id}
153
+ className={`${!state.isValid && elements[id] === '' && styles.invalid}`}
154
+ >
155
+ <input
156
+ id={option.id}
157
+ name={id}
158
+ type={type}
159
+ checked={elements[id] === option.label}
160
+ onChange={(e) => handleChange(e.target.name, option.label)}
161
+ />{' '}
162
+ {translate(translations, option.translationKey, option.label)}
163
+ </label>
164
+ ))}
165
+ </div>
166
+ );
167
+ case 'select':
168
+ return (
169
+ <select
170
+ id={id}
171
+ aria-label={id}
172
+ name={id}
173
+ defaultValue={elements[id]}
174
+ onChange={(e) => handleChange(e.target.name, e.target.value)}
175
+ className={`${!state.isValid && elements[id] === '' && styles.invalid}`}
176
+ >
177
+ {field.showDefault && (
178
+ <option value="">
179
+ {translate(translations, field.defaultTranslationKey, field.defaultLabel)}
180
+ </option>
181
+ )}
182
+ {field.options.map((option) => (
183
+ <option key={option.id} value={option.label}>
184
+ {translate(translations, option.translationKey, option.label)}
185
+ </option>
186
+ ))}
187
+ </select>
188
+ );
189
+ case 'range':
190
+ return (
191
+ <div className={styles.radioGroup}>
192
+ <input
193
+ name={id}
194
+ id={id}
195
+ type={type}
196
+ aria-describedby={id}
197
+ {...props}
198
+ value={elements[id]}
199
+ onChange={(e) => handleChange(e.target.name, e.target.value)}
200
+ />
201
+ <div className={styles.rangeValues}>
202
+ <span>{field.min}</span>
203
+ <span className={`${!state.isValid && elements[id] === '' && styles.invalid}`}>
204
+ {elements[id] || 'Selected Value'}
205
+ </span>
206
+ <span>{field.max}</span>
207
+ </div>
208
+ </div>
209
+ );
210
+ default:
211
+ return (
120
212
  <input
121
- name="email"
122
- type="email"
123
- value={state.email}
124
- placeholder="Email Address"
125
- onChange={(e) => handleChange(e)}
126
- disabled={state.loading}
127
- className={`${!state.isValid && state.email === '' && styles.invalid}`}
213
+ name={id}
214
+ id={id}
215
+ type={type}
216
+ aria-describedby={id}
217
+ {...props}
218
+ value={elements[id]}
219
+ onChange={(e) => handleChange(e.target.name, e.target.value)}
220
+ className={`${!state.isValid && elements[id] === '' && styles.invalid}`}
128
221
  />
129
- </div>
130
- {type === 'contact' && (
131
- <div className={`${styles.formGroup || ''} ${styles.message || ''}`}>
132
- <textarea
133
- name="message"
134
- value={state.message}
135
- placeholder="Message"
136
- onChange={(e) => handleChange(e)}
137
- disabled={state.loading}
138
- className={`${!state.isValid && state.message === '' && styles.invalid}`}
222
+ );
223
+ }
224
+ };
225
+
226
+ return (
227
+ <div className={`${styles.formComponent || ''} `}>
228
+ {formOptions.title && (
229
+ <h2>
230
+ {translate(translations, formOptions.title?.translationKey, formOptions.title?.label)}
231
+ </h2>
232
+ )}
233
+ {formOptions.description && (
234
+ <p>
235
+ {translate(
236
+ translations,
237
+ formOptions.description?.translationKey,
238
+ formOptions.description?.label
239
+ )}
240
+ </p>
241
+ )}
242
+ <form
243
+ onSubmit={(e) => handleSubmit(e)}
244
+ action={submitUrl}
245
+ className={formOptions.twoCol ? styles.twoCol || '' : styles.singleCol || ''}
246
+ target="_self"
247
+ >
248
+ {formOptions.fields &&
249
+ formOptions.fields.map((field) => {
250
+ const { id, translationKey, label } = field;
251
+
252
+ return (
253
+ <div
254
+ key={id}
255
+ className={`${styles.formGroup || ''} ${styles[id] ? styles[id] : ''} ${
256
+ (!field.twoCol && styles.fullWidth) || ''
257
+ }`}
258
+ >
259
+ {showLabels && label && (
260
+ <label htmlFor={id}>{translate(translations, translationKey, label)}</label>
261
+ )}
262
+ {elements && getField(field)}
263
+ </div>
264
+ );
265
+ })}
266
+
267
+ {formOptions.hasReCAPTCHA && (
268
+ <div className={styles.recaptcha || ''}>
269
+ <ReCAPTCHA
270
+ ref={recaptchaRef}
271
+ sitekey={`${process.env.RECAPTCHA_SITE_KEY}`}
272
+ // eslint-disable-next-line react/jsx-no-bind
273
+ onChange={recaptchaOnChange}
139
274
  />
140
275
  </div>
141
276
  )}
142
- {/* if has button */}
143
- <div className={styles.recaptcha || ''}>
144
- <ReCAPTCHA
145
- ref={recaptchaRef}
146
- sitekey={`${process.env.RECAPTCHA_SITE_KEY}`}
147
- // eslint-disable-next-line react/jsx-no-bind
148
- onChange={recaptchaOnChange}
149
- />
150
- </div>
151
277
  {hasButton && (
152
278
  <div className={styles.formButton || ''}>
153
279
  <Button
@@ -155,22 +281,25 @@ const FormComponent = ({
155
281
  btnText={state.loading ? 'sending...' : buttonLabel}
156
282
  disabled={state.loading}
157
283
  gtmClass="form-gtm btn-cta"
158
- primaryColor={isPrimaryButton}
284
+ noStyle
285
+ icon={<FaArrowRight fontSize={20} />}
159
286
  />
160
287
  </div>
161
288
  )}
162
- <div className={styles.formAlerts || ''}>
163
- {state.success && <div className={styles.alertSuccess || ''}>{successMessage}</div>}
164
- {state.failed && <div className={styles.alertDanger || ''}>{failMessage}</div>}
165
- {!state.isValid && <div className={styles.alertWarning || ''}>{validationMessage}</div>}
166
- </div>
289
+ {(state.success || state.failed || state.isValid) && (
290
+ <div className={styles.formAlerts || ''}>
291
+ {state.success && <div className={styles.alertSuccess || ''}>{successMessage}</div>}
292
+ {state.failed && <div className={styles.alertDanger || ''}>{failMessage}</div>}
293
+ {!state.isValid && <div className={styles.alertWarning || ''}>{validationMessage}</div>}
294
+ </div>
295
+ )}
167
296
  </form>
168
297
  </div>
169
298
  );
170
299
  };
171
300
 
172
301
  FormComponent.propTypes = {
173
- type: PropTypes.oneOf(['contact', 'newsletter']).isRequired,
302
+ formOptions: PropTypes.shape({}),
174
303
  successMessage: PropTypes.string,
175
304
  failMessage: PropTypes.string,
176
305
  validationMessage: PropTypes.string,
@@ -178,6 +307,6 @@ FormComponent.propTypes = {
178
307
  hasButton: PropTypes.bool,
179
308
  buttonLabel: PropTypes.string,
180
309
  disabled: PropTypes.bool,
181
- isPrimaryButton: PropTypes.bool,
310
+ showLabels: PropTypes.bool,
182
311
  };
183
312
  export default FormComponent;
@@ -0,0 +1,100 @@
1
+ export const contactUsForm = {
2
+ title: {
3
+ label: 'Send us a message',
4
+ translationKey: 'send_us_a_msg',
5
+ },
6
+ twoCol: true,
7
+ hasReCAPTCHA: true,
8
+ fields: [
9
+ {
10
+ label: 'Name',
11
+ id: 'name',
12
+ type: 'text',
13
+ placeholder: 'Text placeholder',
14
+ translationKey: 'text_translation',
15
+ twoCol: true,
16
+ },
17
+ {
18
+ label: 'Email Address',
19
+ id: 'email',
20
+ type: 'email',
21
+ placeholder: 'Email@placeholder.com',
22
+ translationKey: 'email_translation',
23
+ twoCol: true,
24
+ },
25
+ {
26
+ label: 'Textarea',
27
+ id: 'comment',
28
+ type: 'textarea',
29
+ placeholder: 'Textarea placeholder',
30
+ translationKey: 'textarea_translation',
31
+ maxLength: '10',
32
+ twoCol: false,
33
+ },
34
+ {
35
+ id: 'tnc',
36
+ type: 'checkbox',
37
+ required: true,
38
+ translationKey: 'checkbox_translation',
39
+ twoCol: false,
40
+ options: [
41
+ {
42
+ id: 'tnc',
43
+ label:
44
+ '+18 Lorem ipsum dolor sit amet consectetur. Egestas nibh ullamcorper venenatis vulputate. Sed elit diam at id feugiat orci ornare Privacy Policy',
45
+ translationKey: 'option_one_translation',
46
+ },
47
+ ],
48
+ },
49
+ ],
50
+ };
51
+
52
+ export const newsLetterForm = {
53
+ title: {
54
+ label: 'Sign up To our newsletters ',
55
+ translationKey: 'sign_up_newsletter',
56
+ },
57
+ description: {
58
+ label:
59
+ "If you have questions about our reviews, games or content, or just want to leave some feedback, the Irish Luck team would love to hear from you. You can contact us any time using the details below and we'll endeavour to get back to you within 48 hours.",
60
+ translationKey: 'contact_us_questions',
61
+ },
62
+ twoCol: true,
63
+ hasReCAPTCHA: false,
64
+ fields: [
65
+ {
66
+ label: 'Name',
67
+ id: 'name',
68
+ type: 'text',
69
+ placeholder: 'Text placeholder',
70
+ translationKey: 'text_translation',
71
+ twoCol: true,
72
+ // required: true,
73
+ },
74
+ {
75
+ label: 'Email Address',
76
+ id: 'email',
77
+ type: 'email',
78
+ placeholder: 'Email@placeholder.com',
79
+ translationKey: 'email_translation',
80
+ twoCol: true,
81
+ // required: true,
82
+ },
83
+ {
84
+ label: 'Terms',
85
+ id: 'tnc',
86
+ type: 'checkbox',
87
+ required: true,
88
+ translationKey: 'checkbox_translation',
89
+ twoCol: false,
90
+ options: [
91
+ {
92
+ id: 'tnc',
93
+ label:
94
+ '+18 Lorem ipsum dolor sit amet consectetur. Egestas nibh ullamcorper venenatis vulputate. Sed elit diam at id feugiat orci ornare Privacy Policy',
95
+ translationKey: 'option_one_translation',
96
+ },
97
+ ],
98
+ },
99
+ ],
100
+ };
@@ -143,4 +143,32 @@
143
143
  -webkit-mask-composite: destination-out;
144
144
  mask-composite: exclude;
145
145
  }
146
+ }
147
+
148
+ @mixin buttonsColor($color1, $color2, $color3, $textColor: 'white') {
149
+ display: inline-flex;
150
+ align-items: center;
151
+ justify-content: center;
152
+ text-align: center;
153
+ background-color: $color1;
154
+ color: $textColor;
155
+ padding: 0.9rem 3rem;
156
+ font-weight: 700;
157
+ font-size: 1.8rem;
158
+ border-radius: var(--border-radius);
159
+
160
+ >svg {
161
+ flex: none;
162
+ margin-left: .8rem;
163
+ }
164
+
165
+ &:hover {
166
+ background-color: $color2;
167
+ color: $textColor;
168
+ }
169
+
170
+ &:active {
171
+ background-color: $color3;
172
+ color: $textColor;
173
+ }
146
174
  }