Skip to content

Commit

Permalink
[#65] Add new address component
Browse files Browse the repository at this point in the history
  • Loading branch information
Viicos committed Nov 24, 2023
1 parent b609211 commit e29c09a
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 39 deletions.
58 changes: 57 additions & 1 deletion src/components/ComponentConfiguration.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ export const TextField: Story = {
await userEvent.type(canvas.getByLabelText('Label'), 'Updated preview label');
expect(await preview.findByText('Updated preview label'));

const previewInput = preview.getByLabelText('Updated preview label');
const previewInput = preview.getByText('Updated preview label');
await expect(previewInput).toHaveDisplayValue('');

// Ensure that the manually entered key is kept instead of derived from the label,
Expand Down Expand Up @@ -1386,3 +1386,59 @@ export const Radio: Story = {
});
},
};

export const Address: Story = {
render: Template,
name: 'type: address',

args: {
component: {
id: 'wekruya',
type: 'address',
key: 'address',
label: 'An address',
validate: {
required: false,
},
},
},

play: async ({canvasElement, args}) => {
const canvas = within(canvasElement);

await expect(canvas.getByLabelText('Label')).toHaveValue('An address');
await waitFor(async () => {
await expect(canvas.getByLabelText('Property Name')).toHaveValue('anAddress');
});
await expect(canvas.getByLabelText('Description')).toHaveValue('');
await expect(canvas.getByLabelText('Tooltip')).toHaveValue('');
await expect(canvas.getByLabelText('Show in summary')).toBeChecked();
await expect(canvas.getByLabelText('Show in email')).not.toBeChecked();
await expect(canvas.getByLabelText('Show in PDF')).toBeChecked();

// ensure that changing fields in the edit form properly update the preview
const preview = within(canvas.getByTestId('componentPreview'));

await userEvent.clear(canvas.getByLabelText('Label'));
await userEvent.type(canvas.getByLabelText('Label'), 'Updated preview label');
expect(await preview.findByText('Updated preview label'));

const previewInput = preview.getByText('Updated preview label');
await expect(previewInput).toHaveDisplayValue('');

// Ensure that the manually entered key is kept instead of derived from the label,
// even when key/label components are not mounted.
const keyInput = canvas.getByLabelText('Property Name');
// fireEvent is deliberate, as userEvent.clear + userEvent.type briefly makes the field
// not have any value, which triggers the generate-key-from-label behaviour.
fireEvent.change(keyInput, {target: {value: 'customKey'}});
await userEvent.click(canvas.getByRole('tab', {name: 'Location'}));
await userEvent.click(canvas.getByRole('tab', {name: 'Basic'}));
await userEvent.clear(canvas.getByLabelText('Label'));
await userEvent.type(canvas.getByLabelText('Label'), 'Other label', {delay: 50});
await expect(canvas.getByLabelText('Property Name')).toHaveDisplayValue('customKey');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));
expect(args.onSubmit).toHaveBeenCalled();
},
};
64 changes: 64 additions & 0 deletions src/components/formio/fieldset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {css} from '@emotion/css';
import clsx from 'clsx';
import React from 'react';

import {AnyComponentSchema} from '@/types';
import {useValidationErrors} from '@/utils/errors';
import {ErrorList} from '@/utils/errors';

import Tooltip from './tooltip';

export interface FieldsetProps {
// XXX: eventually (most) of these literals will be included in AnyComponentType
type: AnyComponentSchema['type'] | 'datagrid' | 'datamap' | 'select' | 'columns' | 'textarea';
field?: string;
required?: boolean;
label?: React.ReactNode;
tooltip?: string;
htmlId?: string;
children: React.ReactNode;
}

// Fix the overlapping icons/text when the error icon is shown.
// XXX: once we've moved away from bootstrap/formio 'component library', this fix and
// @emotion/css can be removed again.
const PAD_ERROR_ICON = css`
.form-control.is-invalid {
padding-inline-end: calc(1.5em + 0.75rem);
}
`;

const Fieldset: React.FC<FieldsetProps> = ({
type,
field = '',
required = false,
label,
tooltip = '',
children,
}) => {
const {errors} = useValidationErrors(field);
const className = clsx('form-group', 'has-feedback', 'formio-component', PAD_ERROR_ICON, {
[`formio-component-${type}`]: type,
'has-error': field && errors.length > 0,
required: required,
});
// const labelClassName = clsx('col-form-label', {'field-required': required});

return (
<div className={className}>
<fieldset>
{label && (
<legend>
{label}
{tooltip && ' '}
<Tooltip text={tooltip} />
</legend>
)}
{children}
<ErrorList errors={errors} />
</fieldset>
</div>
);
};

export default Fieldset;
1 change: 1 addition & 0 deletions src/components/formio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export {default as TextArea} from './textarea';
export * from './datagrid';
export {default as DataGrid} from './datagrid';
export {default as DataMap} from './datamap';
export {default as FieldSet} from './fieldset';
7 changes: 5 additions & 2 deletions src/registry/address/edit-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import {buildCommonSchema} from '@/registry/validation';
// Constraints taken from the BRK API (apart from postcode which comes from our postcode component)
const addressSchema = z.object({
postcode: z.string().regex(/^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$/),
housenumber: z.number().int().min(1).max(99999),
housenumber: z.string().regex(/^\d{1,5}$/),
houseletter: z.string().regex(/^[a-zA-Z]$/),
housenumberaddition: z.string().regex(/^([a-z,A-Z,0-9]){1,4}$/).optional(),
housenumberaddition: z
.string()
.regex(/^([a-z,A-Z,0-9]){1,4}$/)
.optional(),
});

const defaultValueSchema = z.object({defaultValue: addressSchema});
Expand Down
29 changes: 4 additions & 25 deletions src/registry/address/edit.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {AddressComponentSchema} from '@open-formulieren/types';
import {useFormikContext} from 'formik';
import {FormattedMessage, useIntl} from 'react-intl';
import {useIntl} from 'react-intl';

import {
BuilderTabs,
Expand All @@ -10,7 +10,6 @@ import {
IsSensitiveData,
Key,
Label,
Multiple,
PresentationConfig,
Registration,
SimpleConditional,
Expand All @@ -20,18 +19,18 @@ import {
useDeriveComponentKey,
} from '@/components/builder';
import {LABELS} from '@/components/builder/messages';
import {TabList, TabPanel, Tabs, TextField} from '@/components/formio';
import {TabList, TabPanel, Tabs} from '@/components/formio';
import {getErrorNames} from '@/utils/errors';

import {EditFormDefinition} from '../types';

/**
* Form to configure a Formio 'licenseplate' type component.
* Form to configure a Formio 'address' type component.
*/
const EditForm: EditFormDefinition<AddressComponentSchema> = () => {
const intl = useIntl();
const [isKeyManuallySetRef, generatedKey] = useDeriveComponentKey();
const {values, errors} = useFormikContext<AddressComponentSchema>();
const {errors} = useFormikContext<AddressComponentSchema>();

const erroredFields = Object.keys(errors).length
? getErrorNames<AddressComponentSchema>(errors)
Expand Down Expand Up @@ -77,11 +76,9 @@ const EditForm: EditFormDefinition<AddressComponentSchema> = () => {
<Description />
<Tooltip />
<PresentationConfig />
<Multiple<AddressComponentSchema> />
<Hidden />
<ClearOnHide />
<IsSensitiveData />
<DefaultValue />
</TabPanel>

{/* Advanced tab */}
Expand Down Expand Up @@ -132,7 +129,6 @@ EditForm.defaultValues = {
showInSummary: true,
showInEmail: false,
showInPDF: true,
multiple: false,
hidden: false,
clearOnHide: true,
isSensitiveData: true,
Expand Down Expand Up @@ -160,21 +156,4 @@ EditForm.defaultValues = {
},
};


const DefaultValue: React.FC<DefaultValueProps> = ({multiple}) => {
const intl = useIntl();
const tooltip = intl.formatMessage({
description: "Tooltip for 'defaultValue' builder field",
defaultMessage: 'This will be the initial value for this field before user interaction.',
});
return (
<TextField
name="defaultValue"
label={<FormattedMessage {...LABELS.defaultValue} />}
tooltip={tooltip}
multiple={multiple}
/>
);
};

export default EditForm;
57 changes: 46 additions & 11 deletions src/registry/address/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,69 @@
import {LicensePlateComponentSchema} from '@open-formulieren/types';

import {Component, Description} from '@/components/formio';
import {AddressComponentSchema} from '@open-formulieren/types';
import {FormattedMessage} from 'react-intl';

import {Description, FieldSet, TextField} from '@/components/formio';

import {ComponentPreviewProps} from '../types';

/**
* Show a formio iban component preview.
* Show a formio address component preview.
*
* NOTE: for the time being, this is rendered in the default Formio bootstrap style,
* however at some point this should use the components of
* @open-formulieren/formio-renderer instead for a more accurate preview.
*/
const Preview: React.FC<ComponentPreviewProps<LicensePlateComponentSchema>> = ({component}) => {
const {key, label, description, tooltip, validate} = component;
const Preview: React.FC<ComponentPreviewProps<AddressComponentSchema>> = ({component}) => {
const {key, label, description, tooltip, validate = {}} = component;

const {required = false} = validate;
return (
<Component
<FieldSet
type="address"
field={key}
required={required}
htmlId={`editform-${key}`}
label={label}
tooltip={tooltip}
>
<fieldset>

</fieldset>
</Component>
{description && <Description text={description} />}
<TextField
name={`${key}.postcode`}
label={
<FormattedMessage description="Label for address postcode" defaultMessage="Postcode" />
}
inputMask="9999 AA"
required={required}
/>
<TextField
name={`${key}.housenumber`}
label={
<FormattedMessage
description="Label for address housenumber"
defaultMessage="House number"
/>
}
required={required}
/>
<TextField
name={`${key}.houseletter`}
label={
<FormattedMessage
description="Label for address houseletter"
defaultMessage="House letter addition"
/>
}
inputMask="A"
/>
<TextField
name={`${key}.housenumberaddition`}
label={
<FormattedMessage
description="Label for address housenumberaddition"
defaultMessage="House number addition"
/>
}
/>
</FieldSet>
);
};

Expand Down
3 changes: 3 additions & 0 deletions src/registry/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {AnyComponentSchema, FallbackSchema, hasOwnProperty} from '@/types';

import Address from './address';
import Currency from './currency';
import DateField from './date';
import DateTimeField from './datetime';
Expand Down Expand Up @@ -46,6 +47,8 @@ const REGISTRY: Registry = {
selectboxes: Selectboxes,
currency: Currency,
radio: Radio,
// Composed types:
address: Address,
// Special types:
iban: Iban,
licenseplate: Licenseplate,
Expand Down

0 comments on commit e29c09a

Please sign in to comment.