Skip to content

Commit

Permalink
Merge pull request #51 from open-formulieren/feature/licenseplate-iba…
Browse files Browse the repository at this point in the history
…n-components

✨ Add `iban` and `licenseplate` component
  • Loading branch information
sergei-maertens authored Nov 10, 2023
2 parents 0f8d280 + f1920f2 commit edc8a6f
Show file tree
Hide file tree
Showing 9 changed files with 488 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/registry/iban/edit-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {IntlShape} from 'react-intl';
import {z} from 'zod';

import {buildCommonSchema} from '@/registry/validation';

const ibanSchema = z.string().optional();

// case for when component.multiple=false
const singleValueSchema = z
.object({multiple: z.literal(false)})
.and(z.object({defaultValue: ibanSchema}));

// case for when component.multiple=true
const multipleValueSchema = z
.object({multiple: z.literal(true)})
.and(z.object({defaultValue: ibanSchema.array()}));

const defaultValueSchema = singleValueSchema.or(multipleValueSchema);

const schema = (intl: IntlShape) => buildCommonSchema(intl).and(defaultValueSchema);

export default schema;
177 changes: 177 additions & 0 deletions src/registry/iban/edit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import {IbanComponentSchema} from '@open-formulieren/types';
import {useFormikContext} from 'formik';
import {FormattedMessage, useIntl} from 'react-intl';

import {
BuilderTabs,
ClearOnHide,
Description,
Hidden,
IsSensitiveData,
Key,
Label,
Multiple,
PresentationConfig,
Registration,
SimpleConditional,
Tooltip,
Translations,
Validate,
useDeriveComponentKey,
} from '@/components/builder';
import {LABELS} from '@/components/builder/messages';
import {TabList, TabPanel, Tabs, TextField} from '@/components/formio';
import {getErrorNames} from '@/utils/errors';

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

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

const erroredFields = Object.keys(errors).length
? getErrorNames<IbanComponentSchema>(errors)
: [];
// TODO: pattern match instead of just string inclusion?
// TODO: move into more generically usuable utility when we implement other component
// types
const hasAnyError = (...fieldNames: string[]): boolean => {
if (!erroredFields.length) return false;
return fieldNames.some(name => erroredFields.includes(name));
};

Validate.useManageValidatorsTranslations<IbanComponentSchema>(['required']);
return (
<Tabs>
<TabList>
<BuilderTabs.Basic
hasErrors={hasAnyError(
'label',
'key',
'description',
'tooltip',
'showInSummary',
'showInEmail',
'showInPDF',
'multiple',
'hidden',
'clearOnHide',
'isSensitiveData',
'defaultValue'
)}
/>
<BuilderTabs.Advanced hasErrors={hasAnyError('conditional')} />
<BuilderTabs.Validation hasErrors={hasAnyError('validate')} />
<BuilderTabs.Registration hasErrors={hasAnyError('registration')} />
<BuilderTabs.Translations hasErrors={hasAnyError('openForms.translations')} />
</TabList>

{/* Basic tab */}
<TabPanel>
<Label />
<Key isManuallySetRef={isKeyManuallySetRef} generatedValue={generatedKey} />
<Description />
<Tooltip />
<PresentationConfig />
<Multiple<IbanComponentSchema> />
<Hidden />
<ClearOnHide />
<IsSensitiveData />
<DefaultValue multiple={!!values.multiple} />
</TabPanel>

{/* Advanced tab */}
<TabPanel>
<SimpleConditional />
</TabPanel>

{/* Validation tab */}
<TabPanel>
<Validate.Required />
<Validate.ValidatorPluginSelect />
<Validate.ValidationErrorTranslations />
</TabPanel>

{/* Registration tab */}
<TabPanel>
<Registration.RegistrationAttributeSelect />
</TabPanel>

{/* Translations */}
<TabPanel>
<Translations.ComponentTranslations<IbanComponentSchema>
propertyLabels={{
label: intl.formatMessage(LABELS.label),
description: intl.formatMessage(LABELS.description),
tooltip: intl.formatMessage(LABELS.tooltip),
}}
/>
</TabPanel>
</Tabs>
);
};

/*
Making this introspected or declarative doesn't seem advisable, as React is calling
React.Children and related API's legacy API - this may get removed in future
versions.
Explicitly specifying the schema and default values is therefore probbaly best, at
the cost of some repetition.
*/
EditForm.defaultValues = {
// basic tab
label: '',
key: '',
description: '',
tooltip: '',
showInSummary: true,
showInEmail: false,
showInPDF: true,
multiple: false,
hidden: false,
clearOnHide: true,
isSensitiveData: true,
// Advanced tab
conditional: {
show: undefined,
when: '',
eq: '',
},
// Validation tab
validate: {
required: false,
plugins: [],
},
validateOn: 'blur',
translatedErrors: {},
registration: {
attribute: '',
},
};

interface DefaultValueProps {
multiple: boolean;
}

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;
10 changes: 10 additions & 0 deletions src/registry/iban/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import EditForm from './edit';
import validationSchema from './edit-validation';
import Preview from './preview';

export default {
edit: EditForm,
editSchema: validationSchema,
preview: Preview,
defaultValue: '',
};
30 changes: 30 additions & 0 deletions src/registry/iban/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {IbanComponentSchema} from '@open-formulieren/types';

import {TextField} from '@/components/formio';

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

/**
* Show a formio iban 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<IbanComponentSchema>> = ({component}) => {
const {key, label, description, placeholder, tooltip, validate = {}, multiple} = component;
const {required = false} = validate;
return (
<TextField
name={key}
multiple={!!multiple}
label={label}
description={description}
tooltip={tooltip}
placeholder={placeholder}
required={required}
/>
);
};

export default Preview;
5 changes: 5 additions & 0 deletions src/registry/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import DateTimeField from './datetime';
import Email from './email';
import Fallback from './fallback';
import FileUpload from './file';
import Iban from './iban';
import Licenseplate from './licenseplate';
import NumberField from './number';
import PhoneNumber from './phonenumber';
import Postcode from './postcode';
Expand Down Expand Up @@ -38,6 +40,9 @@ const REGISTRY: Registry = {
postcode: Postcode,
file: FileUpload,
currency: Currency,
// Special types:
iban: Iban,
licenseplate: Licenseplate,
};

export {Fallback};
Expand Down
25 changes: 25 additions & 0 deletions src/registry/licenseplate/edit-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {IntlShape} from 'react-intl';
import {z} from 'zod';

import {buildCommonSchema} from '@/registry/validation';

const licenseplateSchema = z
.string()
.regex(/^[a-zA-Z0-9]{1,3}\-[a-zA-Z0-9]{1,3}\-[a-zA-Z0-9]{1,3}$/)
.optional();

// case for when component.multiple=false
const singleValueSchema = z
.object({multiple: z.literal(false)})
.and(z.object({defaultValue: licenseplateSchema}));

// case for when component.multiple=true
const multipleValueSchema = z
.object({multiple: z.literal(true)})
.and(z.object({defaultValue: licenseplateSchema.array()}));

const defaultValueSchema = singleValueSchema.or(multipleValueSchema);

const schema = (intl: IntlShape) => buildCommonSchema(intl).and(defaultValueSchema);

export default schema;
Loading

0 comments on commit edc8a6f

Please sign in to comment.