Skip to content

Commit

Permalink
Fieldset edit UI wiring (#158)
Browse files Browse the repository at this point in the history
* Wire fieldset up to edit UI

* Update fieldset error story to trigger an actual error
  • Loading branch information
danielnaab authored Jun 5, 2024
1 parent 18f1701 commit 6616eb5
Show file tree
Hide file tree
Showing 13 changed files with 10,188 additions and 12,922 deletions.
15 changes: 10 additions & 5 deletions packages/common/src/locales/en/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@ const defaults = {

export const en = {
patterns: {
input: {
...defaults,
displayName: 'Short answer',
maxLength: 'Maximum length',
},
checkbox: {
...defaults,
displayName: 'Checkbox',
errorTextMustContainChar: 'String must contain at least 1 character(s)',
},
fieldset: {
...defaults,
displayName: 'Fieldset',
errorTextMustContainChar: 'String must contain at least 1 character(s)',
},
input: {
...defaults,
displayName: 'Short answer',
maxLength: 'Maximum length',
},
page: {
fieldLabel: 'Page title',
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import type { Meta, StoryObj } from '@storybook/react';
import { within } from '@testing-library/react';

import { en as message } from '@atj/common/src/locales/en/app';
import { type FieldsetPattern } from '@atj/forms/src/patterns/fieldset';

import { createPatternEditStoryMeta } from './common/story-helper';
import {
createPatternEditStoryMeta,
testEmptyFormLabelErrorByElement,
testUpdateFormFieldOnSubmitByElement,
} from './common/story-helper';
import FormEdit from '..';

const pattern: FieldsetPattern = {
Expand All @@ -21,4 +27,26 @@ const storyConfig: Meta = {
} as Meta<typeof FormEdit>;
export default storyConfig;

export const Basic: StoryObj<typeof FormEdit> = {};
export const Basic: StoryObj<typeof FormEdit> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await testUpdateFormFieldOnSubmitByElement(
canvasElement,
await canvas.findByText('Empty sections will not display.'),
'Legend Text Element',
'Updated fieldset pattern'
);
},
};

export const Error: StoryObj<typeof FormEdit> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await testEmptyFormLabelErrorByElement(
canvasElement,
await canvas.findByText('Empty sections will not display.'),
'Legend Text Element',
message.patterns.fieldset.errorTextMustContainChar
);
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PatternEditActions } from './common/PatternEditActions';
import { PatternEditForm } from './common/PatternEditForm';
import { usePatternEditFormContext } from './common/hooks';
import { FieldsetPattern } from '@atj/forms/src/patterns/fieldset';
import classNames from 'classnames';

const FieldsetEdit: PatternEditComponent<FieldsetProps> = ({
focus,
Expand Down Expand Up @@ -65,14 +66,33 @@ const FieldsetPreview: PatternComponent<FieldsetProps> = props => {
};

const EditComponent = ({ patternId }: { patternId: PatternId }) => {
const { register } = usePatternEditFormContext<FieldsetPattern>(patternId);
const pattern = useFormManagerStore<FieldsetPattern>(
state => state.session.form.patterns[patternId]
);
const { fieldId, getFieldState, register } =
usePatternEditFormContext<FieldsetPattern>(patternId);
const legend = getFieldState('legend');
return (
<div className="grid-row">
<div className="grid-col-12 margin-bottom-3 flex-align-self-end">
<label className="usa-label width-full maxw-full">
<label
className={classNames('usa-label width-full maxw-full', {
'usa-label--error': legend.error,
})}
htmlFor={fieldId('legend')}
>
Legend Text Element
{legend.error ? (
<span className="usa-error-message" role="alert">
{legend.error.message}
</span>
) : null}
<input
className="usa-input bg-primary-lighter text-bold"
className={classNames('usa-input bg-primary-lighter text-bold', {
'usa-input--error': legend.error,
})}
id={fieldId('legend')}
defaultValue={pattern.data.legend}
{...register('legend')}
type="text"
></input>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import classnames from 'classnames';
import classNames from 'classnames';
import React from 'react';

import { PatternId, TextInputProps } from '@atj/forms';
Expand Down Expand Up @@ -53,7 +53,7 @@ const EditComponent = ({ patternId }: { patternId: PatternId }) => {
<div className="grid-row grid-gap-1">
<div className="tablet:grid-col-6 mobile-lg:grid-col-12">
<label
className={classnames('usa-label', {
className={classNames('usa-label', {
'usa-label--error': label.error,
})}
htmlFor={fieldId('label')}
Expand All @@ -65,7 +65,7 @@ const EditComponent = ({ patternId }: { patternId: PatternId }) => {
</span>
) : null}
<input
className={classnames('usa-input bg-primary-lighter text-bold', {
className={classNames('usa-input bg-primary-lighter text-bold', {
'usa-input--error': label.error,
})}
id={fieldId('label')}
Expand All @@ -77,7 +77,7 @@ const EditComponent = ({ patternId }: { patternId: PatternId }) => {
</div>
<div className="tablet:grid-col-6 mobile-lg:grid-col-12 ohio">
<label
className={classnames('usa-label', {
className={classNames('usa-label', {
'usa-label--error': initial.error,
})}
htmlFor={fieldId('initial')}
Expand All @@ -99,7 +99,7 @@ const EditComponent = ({ patternId }: { patternId: PatternId }) => {
</div>
<div className="tablet:grid-col-6 mobile-lg:grid-col-12">
<label
className={classnames('usa-label', {
className={classNames('usa-label', {
'usa-label--error': maxLength.error,
})}
htmlFor={fieldId('maxLength')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ export const PatternEditForm = ({
updateActivePattern(formData);
})}
onSubmit={methods.handleSubmit(formData => {
updateActivePattern(formData);
clearFocus();
const success = updateActivePattern(formData);
if (success) {
clearFocus();
}
})}
>
<div className="border-1 radius-md border-primary-light padding-1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,29 @@ export const testEmptyFormLabelError = async (
displayName: string,
fieldLabel: string,
errorText: string
): Promise<void> => {
const canvas = within(canvasElement);

const element = await canvas.findByLabelText(displayName);
return await testEmptyFormLabelErrorByElement(
canvasElement,
element,
fieldLabel,
errorText
);
};

export const testEmptyFormLabelErrorByElement = async (
canvasElement: HTMLElement,
element: HTMLElement,
fieldLabel: string,
errorText: string
): Promise<void> => {
userEvent.setup();

const canvas = within(canvasElement);

await userEvent.click(await canvas.findByLabelText(displayName));
await userEvent.click(element);
const input = canvas.getByLabelText(fieldLabel);

// Clear input, remove focus, and wait for error
Expand Down
6 changes: 4 additions & 2 deletions packages/design/src/FormManager/FormEdit/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type FormEditSlice = {
setFocus: (patternId: PatternId) => boolean;
setRouteParams: (routeParams: string) => void;
updatePattern: (data: Pattern) => void;
updateActivePattern: (formData: PatternMap) => void;
updateActivePattern: (formData: PatternMap) => boolean;
} & NotificationSlice;

type FormEditStoreContext = {
Expand Down Expand Up @@ -150,7 +150,7 @@ export const createFormEditSlice =
updateActivePattern: formData => {
const state = get();
if (state.focus === undefined) {
return;
return false;
}
const builder = new BlueprintBuilder(
state.context.config,
Expand All @@ -168,13 +168,15 @@ export const createFormEditSlice =
errors: undefined,
},
});
return true;
} else {
set({
focus: {
pattern: state.focus.pattern,
errors: result.error,
},
});
return false;
}
},
});
1 change: 1 addition & 0 deletions packages/forms/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type FieldsetProps = PatternProps<{
type: 'fieldset';
legend?: string;
subHeading?: string;
error?: FormError;
}>;

export type ZipcodeProps = PatternProps<{
Expand Down
16 changes: 16 additions & 0 deletions packages/forms/src/patterns/fieldset/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { z } from 'zod';

import { safeZodParseFormErrors } from '../../util/zod';
import { ParsePatternConfigData } from '../../pattern';

const configSchema = z.object({
legend: z.string().min(1),
patterns: z.array(z.string()).default([]),
});
export type FieldsetConfigSchema = z.infer<typeof configSchema>;

export const parseConfigData: ParsePatternConfigData<
FieldsetConfigSchema
> = obj => {
return safeZodParseFormErrors(configSchema, obj);
};
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import * as z from 'zod';

import {
type Pattern,
type PatternConfig,
type PatternId,
getPattern,
} from '../pattern';
import { type FieldsetProps, createPromptForPattern } from '../components';
import { safeZodParseFormErrors } from '../util/zod';
} from '../../pattern';
import { parseConfigData } from './config';
import { createPrompt } from './prompt';

export type FieldsetPattern = Pattern<{
legend?: string;
patterns: PatternId[];
}>;

const configSchema = z.object({
legend: z.string().optional(),
patterns: z.array(z.string()),
});

export const fieldsetConfig: PatternConfig<FieldsetPattern> = {
displayName: 'Fieldset',
iconPath: 'block-icon.svg',
initial: {
legend: 'Default Heading',
patterns: [],
},
parseConfigData: obj => safeZodParseFormErrors(configSchema, obj),
parseConfigData,
getChildren(pattern, patterns) {
return pattern.data.patterns.map(
(patternId: string) => patterns[patternId]
Expand All @@ -47,18 +39,5 @@ export const fieldsetConfig: PatternConfig<FieldsetPattern> = {
},
};
},
createPrompt(config, session, pattern, options) {
const children = pattern.data.patterns.map((patternId: string) => {
const childPattern = getPattern(session.form, patternId);
return createPromptForPattern(config, session, childPattern, options);
});
return {
props: {
_patternId: pattern.id,
type: 'fieldset',
legend: pattern.data.legend,
} satisfies FieldsetProps,
children,
};
},
createPrompt,
};
27 changes: 27 additions & 0 deletions packages/forms/src/patterns/fieldset/prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { type FieldsetPattern } from '.';
import {
type CreatePrompt,
type FieldsetProps,
createPromptForPattern,
getPattern,
} from '../..';

export const createPrompt: CreatePrompt<FieldsetPattern> = (
config,
session,
pattern,
options
) => {
const children = pattern.data.patterns.map((patternId: string) => {
const childPattern = getPattern(session.form, patternId);
return createPromptForPattern(config, session, childPattern, options);
});
return {
props: {
_patternId: pattern.id,
type: 'fieldset',
legend: pattern.data.legend,
} satisfies FieldsetProps,
children,
};
};
2 changes: 1 addition & 1 deletion packages/forms/src/patterns/input/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';

import { en as message } from '@atj/common/src/locales/en/app';

import { ParsePatternConfigData, Pattern } from '../../pattern';
import { ParsePatternConfigData } from '../../pattern';
import { safeZodParseFormErrors } from '../../util/zod';

const configSchema = z.object({
Expand Down
Loading

0 comments on commit 6616eb5

Please sign in to comment.