Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control field validation behaviour #74

Merged
merged 7 commits into from
Mar 11, 2025
10 changes: 10 additions & 0 deletions .storybook/decorators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {fn} from '@storybook/test';
import {Form, Formik} from 'formik';
import {CSSProperties} from 'react';

import RendererSettingsProvider from '../src/components/RendererSettingsProvider';

/**
* Wrap stories so that they are inside a container with the class name "utrecht-document", used
* to wrap some 'page-global' styling.
Expand Down Expand Up @@ -50,3 +52,11 @@ export const withFormik: Decorator = (Story, context) => {
</Formik>
);
};

export const withRenderSettingsProvider: Decorator = (Story, {parameters}) => (
<RendererSettingsProvider
requiredFieldsWithAsterisk={parameters?.renderSettings?.requiredFieldsWithAsterisk ?? true}
>
<Story />
</RendererSettingsProvider>
);
6 changes: 5 additions & 1 deletion src/components/FormioForm.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,17 @@ export const InitialErrorsRevalidation: Story = {
expect(await canvas.findByText('External error for field 2')).toBeVisible();
});

// NOTE - this doesn't work properly if the browser window is not focused. Chrome
// and Firefox appear not to dispatch the focus/blur events if another window on your
// device has focus.
await step('Edit field 2', async () => {
const input = canvas.getByLabelText('Field 2');
await userEvent.type(input, 'invalid input');
input.blur();

expect(await canvas.findByText('Invalid')).toBeVisible();
expect(canvas.queryByText('External error for field 2')).not.toBeInTheDocument();
// may not be removed
expect(canvas.getByText('External error for field 1')).toBeVisible();
});
},
};
30 changes: 15 additions & 15 deletions src/components/forms/RadioField/RadioField.stories.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Meta, StoryObj} from '@storybook/react';
import {expect, userEvent, within} from '@storybook/test';

import {withFormik} from '@/sb-decorators';
import {withFormik, withRenderSettingsProvider} from '@/sb-decorators';

import RadioField from './RadioField';

Expand Down Expand Up @@ -74,17 +74,17 @@ export const ValidationError: Story = {
},
};

// export const NoAsterisks: Story = {
// name: 'No asterisk for required',
// decorators: [ConfigDecorator],
// parameters: {
// config: {
// requiredFieldsWithAsterisk: false,
// },
// },
// args: {
// name: 'test',
// label: 'Default required',
// isRequired: true,
// },
// };
export const NoAsterisks: Story = {
name: 'No asterisk for required',
decorators: [withRenderSettingsProvider],
parameters: {
renderSettings: {
requiredFieldsWithAsterisk: false,
},
},
args: {
name: 'test',
label: 'Default required',
isRequired: true,
},
};
2 changes: 1 addition & 1 deletion src/components/forms/TextField/TextField.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ Validation errors are tracked in the Formik state and displayed if any are prese
The backend can be configured to treat fields as required by default and instead mark optional
fields explicitly, through the `ConfigContext`.

{/* <Story of={TextFieldStories.NoAsterisks} /> */}
<Story of={TextFieldStories.NoAsterisks} />
30 changes: 15 additions & 15 deletions src/components/forms/TextField/TextField.stories.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Meta, StoryObj} from '@storybook/react';
import {expect, userEvent, within} from '@storybook/test';

import {withFormik} from '@/sb-decorators';
import {withFormik, withRenderSettingsProvider} from '@/sb-decorators';

import TextField from './TextField';

Expand Down Expand Up @@ -68,17 +68,17 @@ export const ValidationError: Story = {
},
};

// export const NoAsterisks = {
// name: 'No asterisk for required',
// decorators: [ConfigDecorator],
// parameters: {
// config: {
// requiredFieldsWithAsterisk: false,
// },
// },
// args: {
// name: 'test',
// label: 'Default required',
// isRequired: true,
// },
// };
export const NoAsterisks = {
name: 'No asterisk for required',
decorators: [withRenderSettingsProvider],
parameters: {
renderSettings: {
requiredFieldsWithAsterisk: false,
},
},
args: {
name: 'test',
label: 'Default required',
isRequired: true,
},
};
10 changes: 9 additions & 1 deletion src/components/forms/TextField/TextField.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {FormField, Paragraph, Textbox} from '@utrecht/component-library-react';
import type {TextboxProps} from '@utrecht/component-library-react/dist/Textbox';
import {useField} from 'formik';
import {useField, useFormikContext} from 'formik';
import {useId} from 'react';

import HelpText from '@/components/forms/HelpText';
Expand Down Expand Up @@ -56,6 +56,7 @@ const TextField: React.FC<TextFieldProps & TextboxProps> = ({
placeholder,
...extraProps
}) => {
const {validateField} = useFormikContext();
const [props, {error = '', touched}] = useField({name, type: 'text'});
const id = useId();

Expand All @@ -70,6 +71,13 @@ const TextField: React.FC<TextFieldProps & TextboxProps> = ({
<Paragraph>
<Textbox
{...props}
onBlur={async e => {
console.log('onBlur');
props.onBlur(e);
console.log('validating');
await validateField(name);
console.log('validation done');
}}
className="utrecht-textbox--openforms"
id={id}
disabled={isDisabled}
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
},
"skipLibCheck": true
},
"include": ["src"],
"include": ["src/**/*", ".storybook/**/*"],
"exclude": ["node_modules", "dist"]
}