@digigov/form 2.0.0-254f7786 → 2.0.0-2a26e869

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.
Files changed (100) hide show
  1. package/Field/ErrorGroup/index.js +48 -0
  2. package/Field/ErrorGroup/package.json +6 -0
  3. package/Field/ErrorGroup.d.ts +15 -0
  4. package/Field/ErrorGroup.js.map +7 -0
  5. package/Field/FieldBase/index.js +30 -22
  6. package/Field/FieldBase.js.map +2 -2
  7. package/Field/FieldBaseContainer/index.js +3 -2
  8. package/Field/FieldBaseContainer.js.map +2 -2
  9. package/Field/types.d.ts +2 -0
  10. package/FieldArray/index.js +1 -0
  11. package/FieldArray/index.js.map +2 -2
  12. package/FormBuilder/FormBuilder.stories.d.ts +2 -0
  13. package/FormBuilder/__stories__/AutoErrorGrouping.d.ts +3 -0
  14. package/FormBuilder/__stories__/ErrorGrouping.d.ts +3 -0
  15. package/FormBuilder/index.js +83 -4
  16. package/FormBuilder/index.js.map +2 -2
  17. package/FormBuilder/interaction.test.d.ts +1 -0
  18. package/MultiplicityField/index.js +1 -0
  19. package/MultiplicityField/index.js.map +2 -2
  20. package/cjs/Field/ErrorGroup/index.js +82 -0
  21. package/cjs/Field/ErrorGroup.js.map +7 -0
  22. package/cjs/Field/FieldBase/index.js +29 -21
  23. package/cjs/Field/FieldBase.js.map +2 -2
  24. package/cjs/Field/FieldBaseContainer/index.js +3 -2
  25. package/cjs/Field/FieldBaseContainer.js.map +2 -2
  26. package/cjs/Field/types.js.map +1 -1
  27. package/cjs/FieldArray/index.js +1 -0
  28. package/cjs/FieldArray/index.js.map +2 -2
  29. package/cjs/FormBuilder/index.js +83 -4
  30. package/cjs/FormBuilder/index.js.map +2 -2
  31. package/cjs/MultiplicityField/index.js +1 -0
  32. package/cjs/MultiplicityField/index.js.map +2 -2
  33. package/cjs/inputs/DateInput/index.js +3 -3
  34. package/cjs/inputs/DateInput/index.js.map +2 -2
  35. package/cjs/inputs/Input/index.js +1 -0
  36. package/cjs/inputs/Input/index.js.map +2 -2
  37. package/cjs/lazy/index.js +4 -2
  38. package/cjs/lazy.js.map +2 -2
  39. package/cjs/registry/index.js +8 -6
  40. package/cjs/registry.js.map +2 -2
  41. package/cjs/validators/index.js +19 -13
  42. package/cjs/validators/index.js.map +2 -2
  43. package/cjs/validators/utils/int/index.js +1 -1
  44. package/cjs/validators/utils/int.js.map +2 -2
  45. package/cjs/validators/utils/number/index.js +1 -1
  46. package/cjs/validators/utils/number.js.map +2 -2
  47. package/index.js +1 -1
  48. package/inputs/DateInput/index.js +3 -3
  49. package/inputs/DateInput/index.js.map +2 -2
  50. package/inputs/Input/Input.stories.d.ts +1 -0
  51. package/inputs/Input/__stories__/StringWithTrimValidation.d.ts +3 -0
  52. package/inputs/Input/index.js +1 -0
  53. package/inputs/Input/index.js.map +2 -2
  54. package/lazy/index.js +4 -2
  55. package/package.json +4 -4
  56. package/registry/index.js +8 -6
  57. package/src/Field/ErrorGroup.tsx +84 -0
  58. package/src/Field/FieldBase.tsx +33 -22
  59. package/src/Field/FieldBaseContainer.tsx +3 -2
  60. package/src/Field/types.tsx +2 -0
  61. package/src/FieldArray/index.test.tsx +13 -14
  62. package/src/FieldArray/index.tsx +1 -0
  63. package/src/FormBuilder/FormBuilder.stories.js +2 -0
  64. package/src/FormBuilder/__stories__/AutoErrorGrouping.tsx +63 -0
  65. package/src/FormBuilder/__stories__/ErrorGrouping.tsx +43 -0
  66. package/src/FormBuilder/index.test.tsx +9 -1
  67. package/src/FormBuilder/index.tsx +92 -6
  68. package/src/FormBuilder/interaction.test.tsx +32 -0
  69. package/src/FormBuilder/scenarios.test.tsx +111 -4
  70. package/src/MultiplicityField/index.test.tsx +25 -26
  71. package/src/MultiplicityField/index.tsx +1 -0
  72. package/src/Questions/__snapshots__/index.spec.tsx.snap +2 -1
  73. package/src/Questions/index.spec.tsx +8 -0
  74. package/src/Questions/index.test.tsx +10 -11
  75. package/src/inputs/AutoCompleteInput/index.test.tsx +13 -14
  76. package/src/inputs/Checkboxes/index.test.tsx +16 -17
  77. package/src/inputs/DateInput/index.test.tsx +10 -11
  78. package/src/inputs/DateInput/index.tsx +3 -3
  79. package/src/inputs/FileInput/index.test.tsx +10 -11
  80. package/src/inputs/ImageInput/index.test.tsx +19 -20
  81. package/src/inputs/Input/Input.stories.js +1 -0
  82. package/src/inputs/Input/__stories__/StringWithTrimValidation.tsx +26 -0
  83. package/src/inputs/Input/__stories__/TextWithLimit.tsx +1 -0
  84. package/src/inputs/Input/index.test.tsx +41 -38
  85. package/src/inputs/Input/index.tsx +1 -0
  86. package/src/inputs/Label/index.test.tsx +10 -11
  87. package/src/inputs/OtpInput/index.test.tsx +10 -11
  88. package/src/inputs/Radio/index.test.tsx +16 -17
  89. package/src/inputs/Select/index.test.tsx +10 -11
  90. package/src/lazy.js +69 -0
  91. package/src/registry.js +159 -0
  92. package/src/validators/index.ts +41 -14
  93. package/src/validators/utils/int.ts +1 -1
  94. package/src/validators/utils/number.ts +1 -1
  95. package/validators/index.js +19 -13
  96. package/validators/index.js.map +2 -2
  97. package/validators/utils/int/index.js +1 -1
  98. package/validators/utils/int.js.map +2 -2
  99. package/validators/utils/number/index.js +1 -1
  100. package/validators/utils/number.js.map +2 -2
package/registry/index.js CHANGED
@@ -4,6 +4,7 @@ import * as _digigov_form from "@digigov/form";
4
4
  import * as _digigov_form_internal from "@digigov/form/internal";
5
5
  import * as _digigov_form_types from "@digigov/form/types";
6
6
  import * as _digigov_form_utils from "@digigov/form/utils";
7
+ import * as _digigov_form_Field_ErrorGroup from "@digigov/form/Field/ErrorGroup";
7
8
  import * as _digigov_form_Field_FieldBase from "@digigov/form/Field/FieldBase";
8
9
  import * as _digigov_form_Field_FieldBaseContainer from "@digigov/form/Field/FieldBaseContainer";
9
10
  import * as _digigov_form_Field_FieldConditional from "@digigov/form/Field/FieldConditional";
@@ -44,15 +45,15 @@ import * as _digigov_form_Questions_Step_getAddMoreFields from "@digigov/form/Qu
44
45
  import * as _digigov_form_Questions_Step from "@digigov/form/Questions/Step";
45
46
  import * as _digigov_form_Questions_Step_types from "@digigov/form/Questions/Step/types";
46
47
  import * as _digigov_form_inputs_AutoCompleteInput from "@digigov/form/inputs/AutoCompleteInput";
47
- import * as _digigov_form_inputs_DateInput from "@digigov/form/inputs/DateInput";
48
48
  import * as _digigov_form_inputs_Checkboxes from "@digigov/form/inputs/Checkboxes";
49
- import * as _digigov_form_inputs_FileInput from "@digigov/form/inputs/FileInput";
50
49
  import * as _digigov_form_inputs_ImageInput from "@digigov/form/inputs/ImageInput";
50
+ import * as _digigov_form_inputs_DateInput from "@digigov/form/inputs/DateInput";
51
+ import * as _digigov_form_inputs_FileInput from "@digigov/form/inputs/FileInput";
51
52
  import * as _digigov_form_inputs_Input from "@digigov/form/inputs/Input";
52
53
  import * as _digigov_form_inputs_Input_inputsInputScenarios from "@digigov/form/inputs/Input/inputsInputScenarios";
53
54
  import * as _digigov_form_inputs_Label from "@digigov/form/inputs/Label";
54
- import * as _digigov_form_inputs_Radio from "@digigov/form/inputs/Radio";
55
55
  import * as _digigov_form_inputs_OtpInput from "@digigov/form/inputs/OtpInput";
56
+ import * as _digigov_form_inputs_Radio from "@digigov/form/inputs/Radio";
56
57
  import * as _digigov_form_inputs_Select from "@digigov/form/inputs/Select";
57
58
  import * as _digigov_form_validators_utils_afm from "@digigov/form/validators/utils/afm";
58
59
  import * as _digigov_form_validators_utils_date from "@digigov/form/validators/utils/date";
@@ -91,6 +92,7 @@ export default {
91
92
  '@digigov/form/internal': lazyImport(_digigov_form_internal),
92
93
  '@digigov/form/types': lazyImport(_digigov_form_types),
93
94
  '@digigov/form/utils': lazyImport(_digigov_form_utils),
95
+ '@digigov/form/Field/ErrorGroup': lazyImport(_digigov_form_Field_ErrorGroup),
94
96
  '@digigov/form/Field/FieldBase': lazyImport(_digigov_form_Field_FieldBase),
95
97
  '@digigov/form/Field/FieldBaseContainer': lazyImport(_digigov_form_Field_FieldBaseContainer),
96
98
  '@digigov/form/Field/FieldConditional': lazyImport(_digigov_form_Field_FieldConditional),
@@ -131,15 +133,15 @@ export default {
131
133
  '@digigov/form/Questions/Step': lazyImport(_digigov_form_Questions_Step),
132
134
  '@digigov/form/Questions/Step/types': lazyImport(_digigov_form_Questions_Step_types),
133
135
  '@digigov/form/inputs/AutoCompleteInput': lazyImport(_digigov_form_inputs_AutoCompleteInput),
134
- '@digigov/form/inputs/DateInput': lazyImport(_digigov_form_inputs_DateInput),
135
136
  '@digigov/form/inputs/Checkboxes': lazyImport(_digigov_form_inputs_Checkboxes),
136
- '@digigov/form/inputs/FileInput': lazyImport(_digigov_form_inputs_FileInput),
137
137
  '@digigov/form/inputs/ImageInput': lazyImport(_digigov_form_inputs_ImageInput),
138
+ '@digigov/form/inputs/DateInput': lazyImport(_digigov_form_inputs_DateInput),
139
+ '@digigov/form/inputs/FileInput': lazyImport(_digigov_form_inputs_FileInput),
138
140
  '@digigov/form/inputs/Input': lazyImport(_digigov_form_inputs_Input),
139
141
  '@digigov/form/inputs/Input/inputsInputScenarios': lazyImport(_digigov_form_inputs_Input_inputsInputScenarios),
140
142
  '@digigov/form/inputs/Label': lazyImport(_digigov_form_inputs_Label),
141
- '@digigov/form/inputs/Radio': lazyImport(_digigov_form_inputs_Radio),
142
143
  '@digigov/form/inputs/OtpInput': lazyImport(_digigov_form_inputs_OtpInput),
144
+ '@digigov/form/inputs/Radio': lazyImport(_digigov_form_inputs_Radio),
143
145
  '@digigov/form/inputs/Select': lazyImport(_digigov_form_inputs_Select),
144
146
  '@digigov/form/validators/utils/afm': lazyImport(_digigov_form_validators_utils_afm),
145
147
  '@digigov/form/validators/utils/date': lazyImport(_digigov_form_validators_utils_date),
@@ -0,0 +1,84 @@
1
+ import React, { createContext, useEffect, useState } from 'react';
2
+ import { ErrorOption } from 'react-hook-form';
3
+ import { FieldLabelProps } from '@digigov/form/Field/types';
4
+ import { List, ListItem } from '@digigov/ui/content/List';
5
+ import { ErrorSummary } from '@digigov/ui/feedback/ErrorSummary';
6
+ import { useTranslation } from '@digigov/ui/i18n';
7
+ import { Link } from '@digigov/ui/navigation/Link';
8
+ import { Heading } from '@digigov/ui/typography/Heading';
9
+ import { Base } from '@digigov/ui/utils/Base';
10
+ import { Breakpoints } from '@digigov/ui/utils/hooks/useScreen';
11
+ interface ErrorGroupContextType {
12
+ setError: (
13
+ name?: string,
14
+ label?: FieldLabelProps,
15
+ error?: ErrorOption
16
+ ) => void;
17
+ }
18
+ export const ErrorGroupContext = createContext<ErrorGroupContextType | null>(
19
+ null
20
+ );
21
+
22
+ export interface ErrorGroupProps {
23
+ children: React.ReactNode;
24
+ screenSize?: Breakpoints | undefined;
25
+ fieldOrder?: any[];
26
+ }
27
+
28
+ export const ErrorGroup: React.FC<ErrorGroupProps> = ({
29
+ children,
30
+ screenSize,
31
+ fieldOrder,
32
+ }) => {
33
+ const [errors, setErrors] = useState<ErrorOption>({});
34
+ const { t } = useTranslation();
35
+ const setError = (name, label, error) => {
36
+ if (errors[name] !== error && errors[name]?.message !== error?.message) {
37
+ if (!error?.message) {
38
+ const updatedErrors = { ...errors };
39
+ delete updatedErrors[name];
40
+ setErrors(updatedErrors);
41
+ } else {
42
+ setErrors({
43
+ ...errors,
44
+ [name]: {
45
+ ...error,
46
+ label: label.primary,
47
+ },
48
+ });
49
+ }
50
+ }
51
+ };
52
+
53
+ useEffect(() => {
54
+ setErrors({});
55
+ }, [screenSize]);
56
+ return (
57
+ <ErrorGroupContext.Provider value={{ setError }}>
58
+ {Object.keys(errors).length !== 0 && (
59
+ <ErrorSummary variant="error-group">
60
+ <Heading size="sm">
61
+ Τα παρακάτω πεδία της φόρμας ειναι λάθος.{' '}
62
+ </Heading>
63
+ <List listStyle="bullet">
64
+ {(fieldOrder || Object.keys(errors).reverse())
65
+ .filter((fieldName) => errors[fieldName])
66
+ .map((fieldName, index) => {
67
+ return (
68
+ <ListItem key={index} id={`${fieldName}-error`}>
69
+ <Base as="b">{t(errors[fieldName]?.label)}:</Base>{' '}
70
+ <Link href={`#${fieldName}`}>
71
+ {t(errors[fieldName]?.message)}
72
+ </Link>
73
+ </ListItem>
74
+ );
75
+ })}
76
+ </List>
77
+ </ErrorSummary>
78
+ )}
79
+ {children}
80
+ </ErrorGroupContext.Provider>
81
+ );
82
+ };
83
+
84
+ export default ErrorGroup;
@@ -1,36 +1,46 @@
1
- import React from 'react';
1
+ import React, { useContext, useEffect } from 'react';
2
2
  import { Controller } from 'react-hook-form';
3
+ import { ErrorGroupContext } from '@digigov/form/Field/ErrorGroup';
3
4
  import FieldBaseContainer from '@digigov/form/Field/FieldBaseContainer';
4
5
  import { FieldBaseProps } from '@digigov/form/Field/types';
5
6
 
6
- export const FieldBase: React.FC<FieldBaseProps> = (props) => {
7
- const {
8
- required,
9
- name,
10
- component: Component,
11
- wrapper,
12
- control,
13
- type,
14
- controlled = false,
15
- enabled = true,
16
- editable,
17
- defaultValue,
18
- label,
19
- extra = {},
20
- layout,
21
- error,
22
- register,
23
- ...componentProps
24
- } = props;
7
+ export const FieldBase: React.FC<FieldBaseProps> = ({
8
+ required,
9
+ name,
10
+ component: Component,
11
+ wrapper,
12
+ control,
13
+ type,
14
+ controlled = false,
15
+ enabled = true,
16
+ editable,
17
+ defaultValue,
18
+ label,
19
+ extra = {},
20
+ layout,
21
+ error,
22
+ register,
23
+ ...componentProps
24
+ }) => {
25
+ const errorGroupContext = useContext(ErrorGroupContext);
26
+
27
+ useEffect(() => {
28
+ if (errorGroupContext) {
29
+ errorGroupContext.setError(name, label, error);
30
+ }
31
+ }, [error, name, errorGroupContext?.setError]);
32
+
25
33
  if (!enabled) {
26
34
  return null;
27
35
  }
36
+
28
37
  if (controlled) {
29
38
  return (
30
39
  <FieldBaseContainer
31
40
  label={label}
32
41
  layout={layout}
33
- error={error}
42
+ error={errorGroupContext ? undefined : error}
43
+ hasError={!!error}
34
44
  wrapper={wrapper}
35
45
  name={name}
36
46
  >
@@ -64,7 +74,8 @@ export const FieldBase: React.FC<FieldBaseProps> = (props) => {
64
74
  <FieldBaseContainer
65
75
  label={label}
66
76
  layout={layout}
67
- error={error}
77
+ error={errorGroupContext ? undefined : error}
78
+ hasError={!!error}
68
79
  wrapper={wrapper}
69
80
  name={name}
70
81
  >
@@ -15,6 +15,7 @@ export const FieldBaseContainer: React.FC<FieldContainerProps> = ({
15
15
  label,
16
16
  children,
17
17
  error,
18
+ hasError,
18
19
  layout,
19
20
  }) => {
20
21
  const { t } = useTranslation();
@@ -24,7 +25,7 @@ export const FieldBaseContainer: React.FC<FieldContainerProps> = ({
24
25
  const errorContext = error?.message.context || {};
25
26
  if (wrapper === 'fieldset') {
26
27
  return (
27
- <FieldContainer error={!!error} {...layout}>
28
+ <FieldContainer error={hasError} {...layout}>
28
29
  <CoreFieldset>
29
30
  <FieldsetLegend size="sm">{label && label.primary}</FieldsetLegend>
30
31
  {label && label.secondary && <Hint>{t(label.secondary)}</Hint>}
@@ -39,7 +40,7 @@ export const FieldBaseContainer: React.FC<FieldContainerProps> = ({
39
40
  );
40
41
  } else {
41
42
  return (
42
- <FieldContainer error={!!error} {...layout}>
43
+ <FieldContainer error={hasError} {...layout}>
43
44
  <LabelContainer>
44
45
  {label && <Label label={label} />}
45
46
  {error && (
@@ -30,6 +30,7 @@ export interface FieldSpec {
30
30
  | 'phone_number'
31
31
  | 'array'
32
32
  | 'object';
33
+ trim?: boolean;
33
34
  component?: any;
34
35
  autoComplete?: string;
35
36
  maxLength?: number;
@@ -91,6 +92,7 @@ export interface FieldContainerProps {
91
92
  layout?: FieldSpec['layout'];
92
93
  label?: FieldSpec['label'];
93
94
  error?: ErrorOption | Record<string, any>;
95
+ hasError?: boolean;
94
96
  children?: React.ReactNode;
95
97
  wrapper?: FieldSpec['wrapper'];
96
98
  }
@@ -1,25 +1,24 @@
1
1
  import React from 'react';
2
2
  import { test, expect } from '@playwright/experimental-ct-react';
3
+ import TestVariant from '@digigov/ui/utils/TestVariant'
3
4
  import { Default } from '@digigov/form/FieldArray/__stories__/Default';
4
5
  import { WithExactLength } from '@digigov/form/FieldArray/__stories__/WithExactLength';
5
- import TestVariant from '@digigov/ui/utils/TestVariant';
6
6
 
7
7
  test('renders the All FieldArray variants', async ({ mount, page }) => {
8
8
  await mount(
9
- <div>
10
- <TestVariant title="Default">
11
- <Default />
12
- </TestVariant>
13
- <TestVariant title="WithExactLength">
14
- <WithExactLength />
15
- </TestVariant>
16
- </div>
17
- );
9
+
10
+ <div>
11
+ <TestVariant title="Default">
12
+ <Default />
13
+ </TestVariant>
14
+ <TestVariant title="WithExactLength">
15
+ <WithExactLength />
16
+ </TestVariant>
17
+ </div>
18
+ )
18
19
  await page.evaluate(() => document.fonts.ready);
19
20
 
20
- const screenshot = await page.screenshot({
21
- fullPage: true,
22
- animations: 'disabled',
23
- });
21
+ const screenshot = await page.screenshot({ fullPage: true, animations: 'disabled' });
24
22
  expect(screenshot).toMatchSnapshot();
25
23
  });
24
+
@@ -43,6 +43,7 @@ export const FieldArray: React.FC<FieldArrayProps> = ({
43
43
  ? error
44
44
  : undefined
45
45
  }
46
+ hasError={formState.isSubmitted && !formState.isSubmitting && !!error}
46
47
  wrapper="fieldset"
47
48
  name={name}
48
49
  >
@@ -5,3 +5,5 @@ export default {
5
5
  displayName: 'FormBuilder',
6
6
  };
7
7
  export { Default } from '@digigov/form/FormBuilder/__stories__/Default';
8
+ export { ErrorGrouping } from '@digigov/form/FormBuilder/__stories__/ErrorGrouping';
9
+ export { AutoErrorGrouping } from '@digigov/form/FormBuilder/__stories__/AutoErrorGrouping';
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import FormBuilder, { FieldSpec } from '@digigov/form';
3
+
4
+ const FIELDS: FieldSpec[] = [
5
+ {
6
+ key: 'name',
7
+ type: 'string',
8
+ required: true,
9
+ layout: { xl: 4, lg: 4, md: 6, xs: 6 },
10
+ label: {
11
+ primary: 'Όνομα',
12
+ },
13
+ },
14
+ {
15
+ key: 'surname',
16
+ type: 'string',
17
+ required: true,
18
+ layout: { xl: 4, lg: 4, md: 6, xs: 6 },
19
+ label: {
20
+ primary: 'Επίθετο',
21
+ },
22
+ },
23
+ {
24
+ key: 'address',
25
+ type: 'string',
26
+ required: true,
27
+ layout: { xl: 4, lg: 2, md: 6, xs: 6 },
28
+ label: {
29
+ primary: 'Διεύθυνση',
30
+ },
31
+ },
32
+ {
33
+ key: 'town',
34
+ type: 'string',
35
+ required: true,
36
+ layout: { xl: 4, lg: 4, md: 6, xs: 6 },
37
+ label: {
38
+ primary: 'Πόλη',
39
+ },
40
+ },
41
+ {
42
+ key: 'country',
43
+ type: 'string',
44
+ required: true,
45
+ layout: { xs: 12 },
46
+ label: {
47
+ primary: 'Χώρα',
48
+ },
49
+ },
50
+ ];
51
+
52
+ export const AutoErrorGrouping = () => (
53
+ <FormBuilder
54
+ grid={true}
55
+ auto={true}
56
+ onSubmit={(data) => {
57
+ console.log(data);
58
+ }}
59
+ fields={FIELDS}
60
+ ></FormBuilder>
61
+ );
62
+
63
+ export default AutoErrorGrouping;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import FormBuilder, { Field } from '@digigov/form';
3
+ import { ErrorGroup } from '@digigov/form/Field/ErrorGroup';
4
+ import { Button } from '@digigov/ui/form/Button';
5
+
6
+ export const ErrorGrouping = () => (
7
+ <FormBuilder
8
+ grid={true}
9
+ onSubmit={(data) => {
10
+ console.log(data);
11
+ }}
12
+ >
13
+ <ErrorGroup>
14
+ <Field
15
+ key="name"
16
+ name="name"
17
+ type="string"
18
+ label={{ primary: 'Όνομα' }}
19
+ layout={{ xs: 4 }}
20
+ required
21
+ />
22
+ <Field
23
+ key="surname"
24
+ name="surname"
25
+ type="string"
26
+ label={{ primary: 'Επίθετο' }}
27
+ layout={{ xs: 4 }}
28
+ required
29
+ />
30
+ <Field
31
+ key="address"
32
+ name="address"
33
+ type="string"
34
+ label={{ primary: 'Διεύθυνση' }}
35
+ layout={{ xs: 4 }}
36
+ required
37
+ />
38
+ </ErrorGroup>
39
+ <Button type="submit">Συνέχεια</Button>
40
+ </FormBuilder>
41
+ );
42
+
43
+ export default ErrorGrouping;
@@ -1,14 +1,22 @@
1
1
  import React from 'react';
2
2
  import { test, expect } from '@playwright/experimental-ct-react';
3
- import { Default } from '@digigov/form/FormBuilder/__stories__/Default';
4
3
  import TestVariant from '@digigov/ui/utils/TestVariant';
4
+ import { AutoErrorGrouping } from '@digigov/form/FormBuilder/__stories__/AutoErrorGrouping';
5
+ import { Default } from '@digigov/form/FormBuilder/__stories__/Default';
6
+ import { ErrorGrouping } from '@digigov/form/FormBuilder/__stories__/ErrorGrouping';
5
7
 
6
8
  test('renders the All FormBuilder variants', async ({ mount, page }) => {
7
9
  await mount(
8
10
  <div>
11
+ <TestVariant title="AutoErrorGrouping">
12
+ <AutoErrorGrouping />
13
+ </TestVariant>
9
14
  <TestVariant title="Default">
10
15
  <Default />
11
16
  </TestVariant>
17
+ <TestVariant title="ErrorGrouping">
18
+ <ErrorGrouping />
19
+ </TestVariant>
12
20
  </div>
13
21
  );
14
22
  await page.evaluate(() => document.fonts.ready);
@@ -1,14 +1,16 @@
1
1
  import React, { useCallback, useContext, useRef } from 'react';
2
2
  import { useForm } from 'react-hook-form';
3
3
  import { Field } from '@digigov/form/Field';
4
+ import { ErrorGroup } from '@digigov/form/Field/ErrorGroup';
4
5
  import { CONTROLLED_FIELD_COMPONENTS } from '@digigov/form/Field/utils';
5
- import { Fieldset } from '@digigov/form/Fieldset';
6
+ import { FieldsetWithContext } from '@digigov/form/Fieldset/FieldsetWithContext';
6
7
  import { FormContext } from '@digigov/form/FormContext';
7
8
  import { FormBaseProps, FormData, FormBuilderProps } from '@digigov/form/types';
8
9
  import { yupResolver } from '@digigov/form/utils';
9
10
  import { useValidationSchema } from '@digigov/form/validators';
10
11
  import { Button } from '@digigov/ui/form/Button';
11
12
  import { Form } from '@digigov/ui/form/Form';
13
+ import { useScreenSize } from '@digigov/ui/utils/hooks/useScreen';
12
14
 
13
15
  const FormBase = React.forwardRef(function FormBase(
14
16
  {
@@ -40,7 +42,10 @@ const FormBase = React.forwardRef(function FormBase(
40
42
 
41
43
  const handleSubmit = useCallback(
42
44
  (data: FormData): void => {
43
- if (onSubmit) onSubmit(data);
45
+ if (onSubmit) {
46
+ form.reset(data);
47
+ onSubmit(data);
48
+ }
44
49
  },
45
50
  [onSubmit]
46
51
  );
@@ -76,6 +81,43 @@ const FormBase = React.forwardRef(function FormBase(
76
81
  );
77
82
  });
78
83
 
84
+ const DEFAULT_LAYOUT = {
85
+ xs: 12,
86
+ sm: 12,
87
+ md: 12,
88
+ lg: 12,
89
+ xl: 12,
90
+ };
91
+
92
+ const BREAKPOINTS = ['xs', 'sm', 'md', 'lg', 'xl'];
93
+
94
+ const groupFieldsByLayout = (fields) => {
95
+ return BREAKPOINTS.reduce((sizes, size) => {
96
+ sizes[size] = fields
97
+ .reduce((acc, item) => {
98
+ const layoutValue = item.layout?.[size] || 12;
99
+ if (layoutValue === 0) {
100
+ return [...acc, item];
101
+ }
102
+ const lastElement = acc[acc.length - 1];
103
+ const currentSum = Array.isArray(lastElement)
104
+ ? lastElement.reduce((sum, i) => sum + (i.layout?.[size] || 0), 0)
105
+ : 0;
106
+ if (!Array.isArray(lastElement) || currentSum + layoutValue > 12) {
107
+ return layoutValue === 12 ? [...acc, item] : [...acc, [item]];
108
+ }
109
+
110
+ return [...acc.slice(0, -1), [...lastElement, item]];
111
+ }, [])
112
+ .map((item) => {
113
+ if (Array.isArray(item) && item.length === 1) {
114
+ return item[0];
115
+ }
116
+ return item;
117
+ });
118
+ return sizes;
119
+ }, {});
120
+ };
79
121
  const useFormContext = () => {
80
122
  return useContext(FormContext);
81
123
  };
@@ -102,6 +144,34 @@ const FormBuilder = React.forwardRef(function FormBuilder(
102
144
  }: FormBuilderProps,
103
145
  ref: React.Ref<HTMLFormElement>
104
146
  ): React.ReactElement {
147
+ const { screenSize } = useScreenSize();
148
+ fields = fields.map((field) => {
149
+ const layout = field.layout;
150
+ if (!layout) {
151
+ return {
152
+ ...field,
153
+ layout: DEFAULT_LAYOUT,
154
+ };
155
+ }
156
+ return {
157
+ ...field,
158
+ layout: BREAKPOINTS.reduce(
159
+ (expandedLayout, breakpoint, breakpointIndex) => {
160
+ if (field?.layout?.[breakpoint]) {
161
+ expandedLayout[breakpoint] = field?.layout[breakpoint];
162
+ } else if (expandedLayout[BREAKPOINTS[breakpointIndex - 1]]) {
163
+ expandedLayout[breakpoint] =
164
+ expandedLayout[BREAKPOINTS[breakpointIndex - 1]];
165
+ } else {
166
+ expandedLayout[breakpoint] = 12;
167
+ }
168
+ return expandedLayout;
169
+ },
170
+ {}
171
+ ),
172
+ };
173
+ });
174
+
105
175
  const fieldsState = useRef(fields);
106
176
  const setFieldsState = useCallback((newFields) => {
107
177
  fieldsState.current = newFields;
@@ -137,12 +207,28 @@ const FormBuilder = React.forwardRef(function FormBuilder(
137
207
  if (auto) {
138
208
  if (fieldsets) {
139
209
  fieldChildren = fieldsets.map((fieldset) => (
140
- <Fieldset key={fieldset.key} />
210
+ <FieldsetWithContext key={fieldset.key} name={fieldset.key} />
141
211
  ));
142
212
  } else if (fields) {
143
- fieldChildren = fields.map((field) => (
144
- <Field key={field.key} name={field.key} />
145
- ));
213
+ const fieldsByLayouts = groupFieldsByLayout(fields);
214
+ const fieldsByLayoutSize = fieldsByLayouts[screenSize];
215
+ fieldChildren = fieldsByLayoutSize.map((item, index) => {
216
+ if (Array.isArray(item)) {
217
+ return (
218
+ <ErrorGroup
219
+ key={index}
220
+ screenSize={screenSize}
221
+ fieldOrder={item.map((field) => field.key)}
222
+ >
223
+ {item.map((f) => {
224
+ return <Field key={f.key} name={f.key} />;
225
+ })}
226
+ </ErrorGroup>
227
+ );
228
+ } else {
229
+ return <Field key={item.key} name={item.key} />;
230
+ }
231
+ });
146
232
  }
147
233
  }
148
234
  }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { test, expect } from '@playwright/experimental-ct-react';
3
+
4
+ import { AutoErrorGrouping } from '@digigov/form/FormBuilder/__stories__/AutoErrorGrouping';
5
+ import { ErrorGrouping } from '@digigov/form/FormBuilder/__stories__/ErrorGrouping';
6
+
7
+ test('renders the ErrorGrouping and click submit', async ({ mount, page }) => {
8
+ await mount(<ErrorGrouping />);
9
+ await page.evaluate(() => document.fonts.ready);
10
+ await page.click('text="Συνέχεια"');
11
+
12
+ const screenshot = await page.screenshot({
13
+ fullPage: true,
14
+ animations: 'disabled',
15
+ });
16
+ expect(screenshot).toMatchSnapshot();
17
+ });
18
+
19
+ test('renders the AutoErrorGrouping and click submit', async ({
20
+ mount,
21
+ page,
22
+ }) => {
23
+ await mount(<AutoErrorGrouping />);
24
+ await page.evaluate(() => document.fonts.ready);
25
+ await page.click('text="Συνέχεια"');
26
+
27
+ const screenshot = await page.screenshot({
28
+ fullPage: true,
29
+ animations: 'disabled',
30
+ });
31
+ expect(screenshot).toMatchSnapshot();
32
+ });