-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Copy over the TextField form component from the SDK
Uses the underlying NL DS utrecht components, but without any styling for now. This sets up the MVP pure-react form rendering to be used with the Formio component definitions.
- Loading branch information
1 parent
ea285fa
commit ddba1bf
Showing
12 changed files
with
369 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type {Decorator} from '@storybook/react'; | ||
import {Formik} from 'formik'; | ||
|
||
export const withFormik: Decorator = (Story, context) => { | ||
const isDisabled = context.parameters?.formik?.disable ?? false; | ||
if (isDisabled) { | ||
return <Story />; | ||
} | ||
const initialValues = context.parameters?.formik?.initialValues || {}; | ||
const initialErrors = context.parameters?.formik?.initialErrors || {}; | ||
const initialTouched = context.parameters?.formik?.initialTouched || {}; | ||
const wrapForm = context.parameters?.formik?.wrapForm ?? true; | ||
return ( | ||
<Formik | ||
initialValues={initialValues} | ||
initialErrors={initialErrors} | ||
initialTouched={initialTouched} | ||
enableReinitialize | ||
onSubmit={(values, formikHelpers) => console.log(values, formikHelpers)} | ||
> | ||
{wrapForm ? ( | ||
<form id="storybook-withFormik-decorator-form" data-testid="storybook-formik-form"> | ||
<Story /> | ||
</form> | ||
) : ( | ||
<Story /> | ||
)} | ||
</Formik> | ||
); | ||
}; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import {FormFieldDescription} from '@utrecht/component-library-react'; | ||
import clsx from 'clsx'; | ||
|
||
export type HelpTextProps = { | ||
children: React.ReactNode; | ||
}; | ||
|
||
const HelpText: React.FC<HelpTextProps> = ({children}) => { | ||
if (!children) return null; | ||
return ( | ||
<FormFieldDescription | ||
className={clsx( | ||
'utrecht-form-field-description--openforms-helptext', | ||
'utrecht-form-field__description' | ||
)} | ||
> | ||
{children} | ||
</FormFieldDescription> | ||
); | ||
}; | ||
|
||
export default HelpText; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import {FormLabel, Paragraph} from '@utrecht/component-library-react'; | ||
import clsx from 'clsx'; | ||
|
||
export interface LabelContentProps { | ||
id: string; | ||
children: React.ReactNode; | ||
isDisabled?: boolean; | ||
isRequired?: boolean; | ||
type?: string; | ||
} | ||
|
||
export const LabelContent: React.FC<LabelContentProps> = ({ | ||
id, | ||
isDisabled = false, | ||
isRequired = false, | ||
type, | ||
children, | ||
}) => { | ||
// TODO: add support for required-fields-with-asterisk configuration (via context) | ||
return ( | ||
<FormLabel | ||
htmlFor={id} | ||
disabled={isDisabled} | ||
className={clsx({ | ||
'utrecht-form-label--openforms': true, | ||
'utrecht-form-label--openforms-required': isRequired, | ||
[`utrecht-form-label--${type}`]: type, | ||
})} | ||
> | ||
{children} | ||
</FormLabel> | ||
); | ||
}; | ||
|
||
export interface LabelProps { | ||
id: string; | ||
children: React.ReactNode; | ||
isDisabled?: boolean; | ||
isRequired?: boolean; | ||
} | ||
|
||
const Label: React.FC<LabelProps> = ({id, isRequired = false, isDisabled = false, children}) => ( | ||
<Paragraph className="utrecht-form-field__label"> | ||
<LabelContent id={id} isRequired={isRequired} isDisabled={isDisabled}> | ||
{children} | ||
</LabelContent> | ||
</Paragraph> | ||
); | ||
|
||
export default Label; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import {ArgTypes, Canvas, Meta, Story} from '@storybook/blocks'; | ||
|
||
import * as TextFieldStories from './TextField.stories'; | ||
|
||
<Meta of={TextFieldStories} /> | ||
|
||
# TextField | ||
|
||
A text-based input field with label, help text and validation errors. | ||
|
||
The value of the field is tracked in the Formik parent state, linked through the `name` prop. You | ||
can use `lodash.get` syntax for the name, e.g. `foo.bar` will look up the key `bar` inside the | ||
object `foo`. | ||
|
||
<Canvas of={TextFieldStories.Default} /> | ||
|
||
## Props | ||
|
||
<ArgTypes of={TextFieldStories} /> | ||
|
||
## Validation errors | ||
|
||
Validation errors are tracked in the Formik state and displayed if any are present. | ||
|
||
<Story of={TextFieldStories.ValidationError} /> | ||
|
||
## No asterisks | ||
|
||
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} /> */} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import {Meta, StoryObj} from '@storybook/react'; | ||
import {expect, userEvent, within} from '@storybook/test'; | ||
|
||
import {withFormik} from '@/sb-decorators'; | ||
|
||
import TextField from './TextField'; | ||
|
||
export default { | ||
title: 'Internal API / Forms / TextField', | ||
component: TextField, | ||
decorators: [withFormik], | ||
parameters: { | ||
formik: { | ||
initialValues: { | ||
test: '', | ||
}, | ||
}, | ||
}, | ||
} satisfies Meta<typeof TextField>; | ||
|
||
type Story = StoryObj<typeof TextField>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
name: 'test', | ||
label: 'test', | ||
description: 'This is a custom description', | ||
isDisabled: false, | ||
isRequired: true, | ||
}, | ||
play: async ({canvasElement}) => { | ||
const canvas = within(canvasElement); | ||
await expect(canvas.getByRole('textbox')).toBeVisible(); | ||
await expect(canvas.getByText('test')).toBeVisible(); | ||
await expect(canvas.getByText('This is a custom description')).toBeVisible(); | ||
// Check if clicking on the label focuses the input | ||
const label = canvas.getByText('test'); | ||
await userEvent.click(label); | ||
await expect(canvas.getByRole('textbox')).toHaveFocus(); | ||
}, | ||
}; | ||
|
||
export const ValidationError: Story = { | ||
name: 'Validation error', | ||
parameters: { | ||
formik: { | ||
initialValues: { | ||
textinput: 'some text', | ||
}, | ||
initialErrors: { | ||
textinput: 'invalid', | ||
}, | ||
initialTouched: { | ||
textinput: true, | ||
}, | ||
}, | ||
}, | ||
args: { | ||
name: 'textinput', | ||
label: 'Text field', | ||
description: 'Description above the errors', | ||
isDisabled: false, | ||
isRequired: true, | ||
}, | ||
play: async ({canvasElement}) => { | ||
const canvas = within(canvasElement); | ||
await expect(canvas.getByText('invalid')).toBeVisible(); | ||
}, | ||
}; | ||
|
||
// export const NoAsterisks = { | ||
// name: 'No asterisk for required', | ||
// decorators: [ConfigDecorator], | ||
// parameters: { | ||
// config: { | ||
// requiredFieldsWithAsterisk: false, | ||
// }, | ||
// }, | ||
// args: { | ||
// name: 'test', | ||
// label: 'Default required', | ||
// isRequired: true, | ||
// }, | ||
// }; |
Oops, something went wrong.