Skip to content

Commit

Permalink
✨ [#52] Implement selectboxes preview component
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-maertens committed Nov 14, 2023
1 parent 99473b0 commit 0a7285d
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 29 deletions.
71 changes: 70 additions & 1 deletion src/components/ComponentPreview.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {expect} from '@storybook/jest';
import {Meta, StoryFn, StoryObj} from '@storybook/react';
import {userEvent, within} from '@storybook/testing-library';
import {fireEvent, userEvent, within} from '@storybook/testing-library';

import ComponentPreview from './ComponentPreview';

Expand Down Expand Up @@ -615,3 +615,72 @@ export const File: Story = {
await canvas.findByText('A preview of the file Formio component');
},
};

export const SelectBoxes: Story = {
name: 'Selectboxes manual values',
render: Template,

args: {
component: {
type: 'selectboxes',
id: 'selectboxes',
key: 'selectboxesPreview',
label: 'Selectboxes preview',
description: 'A preview of the selectboxes Formio component',
openForms: {
dataSrc: 'manual',
translations: {},
},
values: [
{
value: 'option1',
label: 'Option 1',
},
{
value: 'option2',
label: 'Option 2',
},
],
},
},

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

// check that the user-controlled content is visible
await canvas.findByText('Selectboxes preview');
await canvas.findByText('A preview of the selectboxes Formio component');

// check that the input name is set correctly
const firstOptionInput = canvas.getByLabelText<HTMLInputElement>('Option 1');
// @ts-ignore
await expect(firstOptionInput.getAttribute('name').startsWith(args.component.key)).toBe(true);

// check the toggle state of a checkbox
await expect(firstOptionInput).not.toBeChecked();
// https://github.com/testing-library/user-event/issues/1149 applies to radio and
// checkbox inputs
fireEvent.click(canvas.getByText('Option 1'));
await expect(firstOptionInput).toBeChecked();
},
};

export const SelectBoxesVariable: Story = {
name: 'Selectboxes variable for values',
render: Template,

args: {
component: {
type: 'selectboxes',
id: 'selectboxes',
key: 'selectboxesPreview',
label: 'Selectboxes preview',
description: 'A preview of the selectboxes Formio component',
openForms: {
dataSrc: 'variable',
itemsExpression: {var: 'foo'},
translations: {},
},
},
},
};
67 changes: 39 additions & 28 deletions src/components/formio/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@ import Component from './component';
import Description from './description';
import Tooltip from './tooltip';

export interface CheckboxInputProps {
name: string;
label?: React.ReactNode;
onChange?: FormikHandlers['handleChange'];
}

export const CheckboxInput: React.FC<CheckboxInputProps> = ({name, label, onChange}) => {
const {getFieldProps} = useFormikContext();
const {onChange: formikOnChange} = getFieldProps(name);
const {hasErrors} = useValidationErrors(name);
return (
<>
<Field
name={name}
as="input"
type="checkbox"
className={clsx('form-check-input', {'is-invalid': hasErrors})}
onChange={(e: React.ChangeEvent<any>) => {
formikOnChange(e);
onChange?.(e);
}}
/>
<span>{label}</span>
</>
);
};

export interface CheckboxProps {
name: string;
label?: React.ReactNode;
Expand All @@ -23,32 +50,16 @@ const Checkbox: React.FC<CheckboxProps> = ({
tooltip = '',
description = '',
onChange,
}) => {
const {getFieldProps} = useFormikContext();
const {onChange: formikOnChange} = getFieldProps(name);
const {hasErrors} = useValidationErrors(name);
return (
<Component field={name} required={required} type="checkbox">
<div className="form-check checkbox">
<label className="form-check-label">
<Field
name={name}
as="input"
type="checkbox"
className={clsx('form-check-input', {'is-invalid': hasErrors})}
onChange={(e: React.ChangeEvent<any>) => {
formikOnChange(e);
onChange?.(e);
}}
/>
<span>{label}</span>
{tooltip && ' '}
<Tooltip text={tooltip} />
</label>
</div>
{description && <Description text={description} />}
</Component>
);
};

}) => (
<Component field={name} required={required} type="checkbox">
<div className="form-check checkbox">
<label className="form-check-label">
<CheckboxInput name={name} label={label} onChange={onChange} />
{tooltip && ' '}
<Tooltip text={tooltip} />
</label>
</div>
{description && <Description text={description} />}
</Component>
);
export default Checkbox;
2 changes: 2 additions & 0 deletions src/registry/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Licenseplate from './licenseplate';
import NumberField from './number';
import PhoneNumber from './phonenumber';
import Postcode from './postcode';
import Selectboxes from './selectboxes';
import TextField from './textfield';
import TimeField from './time';
import {Registry, RegistryEntry} from './types';
Expand Down Expand Up @@ -39,6 +40,7 @@ const REGISTRY: Registry = {
phoneNumber: PhoneNumber,
postcode: Postcode,
file: FileUpload,
selectboxes: Selectboxes,
currency: Currency,
// Special types:
iban: Iban,
Expand Down
10 changes: 10 additions & 0 deletions src/registry/selectboxes/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: '',
};
63 changes: 63 additions & 0 deletions src/registry/selectboxes/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {SelectboxesComponentSchema} from '@open-formulieren/types';
import {Option} from '@open-formulieren/types/lib/formio/common';
import {useIntl} from 'react-intl';

import {Component, Description} from '@/components/formio';
import {CheckboxInput} from '@/components/formio/checkbox';

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

// FIXME: this union does not seem to be properly inferred :(
const checkIsManualOptions = (
component: SelectboxesComponentSchema
): component is SelectboxesComponentSchema & {values: Option[]} => {
return component.openForms.dataSrc === 'manual';
};

/**
* Show a formio selectboxes 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<SelectboxesComponentSchema>> = ({component}) => {
const intl = useIntl();
const {key, label, description, tooltip, validate} = component;
const {required = false} = validate || {};
const isManualOptions = checkIsManualOptions(component);
const options = isManualOptions
? component.values
: [
{
value: 'itemsExpression',
label: intl.formatMessage(
{
description: 'Selectboxes dummy option for itemsExpression',
defaultMessage: 'Options from expression: <code>{expression}</code>',
},
{
expression: JSON.stringify(component.openForms.itemsExpression),
code: chunks => <code>{chunks}</code>,
}
),
},
];
return (
<Component type="selectboxes" field={key} label={label} tooltip={tooltip} required={required}>
<div className="form-radio radio">
{options.map(({value, label}, index) => (
<div key={`option-${value}-${index}`} className="form-check">
<label className="form-check-label">
<CheckboxInput name={`${key}.${value}`} label={label} />
</label>
</div>
))}
</div>

{description && <Description text={description} />}
</Component>
);
};

export default Preview;

0 comments on commit 0a7285d

Please sign in to comment.