Skip to content

Commit

Permalink
feat: create email input pattern tckt-362 (#377)
Browse files Browse the repository at this point in the history
* feat: add email input pattern -tckt-362

* feat: add email input pattern form edit tckt-362

* feat: create email input icon for question dropdown tckt-362

* chore: update error message path for dob storybook tckt-362

* test: add unit tests for email-input config file tckt-362

* test: refactor test failure assertions tckt-362

---------

Co-authored-by: kalasgarov <[email protected]>
  • Loading branch information
kalasgarov and kalasgarov authored Nov 11, 2024
1 parent 4f3d810 commit 5ee1c8c
Show file tree
Hide file tree
Showing 19 changed files with 545 additions and 28 deletions.
6 changes: 6 additions & 0 deletions packages/common/src/locales/en/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,11 @@ export const en = {
hint: 'For example: January 19 2000',
errorTextMustContainChar: 'String must contain at least 1 character(s)',
},
emailInput: {
...defaults,
displayName: 'Email Input label',
fieldLabel: 'Email Input label',
errorTextMustContainChar: 'String must contain at least 1 character(s)',
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { type Meta, type StoryObj } from '@storybook/react';

import { EmailInputPattern } from './EmailInput.js';

const meta: Meta<typeof EmailInputPattern> = {
title: 'patterns/EmailInputPattern',
component: EmailInputPattern,
decorators: [
(Story, args) => {
const FormDecorator = () => {
const formMethods = useForm({
defaultValues: {
email: '',
},
});
return (
<FormProvider {...formMethods}>
<Story {...args} />
</FormProvider>
);
};
return <FormDecorator />;
},
],
tags: ['autodocs'],
};

export default meta;

export const Default: StoryObj<typeof EmailInputPattern> = {
args: {
emailId: 'email',
label: 'Email address',
required: true,
},
};

export const WithoutRequired: StoryObj<typeof EmailInputPattern> = {
args: {
emailId: 'email',
label: 'Email address',
required: false,
},
};

export const WithError: StoryObj<typeof EmailInputPattern> = {
args: {
emailId: 'email',
label: 'Email address with error',
required: true,
error: {
type: 'custom',
message: 'This field has an error',
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @vitest-environment jsdom
*/
import { describeStories } from '../../../test-helper.js';
import meta, * as stories from './EmailInput.stories.js';

describeStories(meta, stories);
37 changes: 37 additions & 0 deletions packages/design/src/Form/components/EmailInput/EmailInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { type EmailInputProps } from '@atj/forms';
import { type PatternComponent } from '../../index.js';

export const EmailInputPattern: PatternComponent<EmailInputProps> = ({
emailId,
label,
required,
error,
}) => {
const { register } = useFormContext();

return (
<fieldset className="usa-fieldset">
<div className="usa-form-group">
<label className="usa-label" htmlFor={emailId}>
{label}
{required && <span className="required-indicator">*</span>}
</label>
<input
className="usa-input margin-bottom-1"
id={emailId}
type="email"
autoCapitalize="off"
autoCorrect="off"
{...register(emailId, { required })}
/>
</div>
{error && (
<span className="error-message" style={{ color: 'red' }}>
{error.message}
</span>
)}
</fieldset>
);
};
3 changes: 3 additions & 0 deletions packages/design/src/Form/components/EmailInput/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { EmailInputPattern } from './EmailInput.js';

export default EmailInputPattern;
6 changes: 4 additions & 2 deletions packages/design/src/Form/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { PatternComponent, type ComponentForPattern } from '../index.js';

import Address from './Address/index.js';
import Checkbox from './Checkbox/index.js';
import DateOfBirth from './DateOfBirth/index.js';
import EmailInput from './EmailInput/index.js';
import Fieldset from './Fieldset/index.js';
import FormSummary from './FormSummary/index.js';
import PackageDownload from './PackageDownload/index.js';
Expand All @@ -12,13 +14,14 @@ import RadioGroup from './RadioGroup/index.js';
import RichText from './RichText/index.js';
import Sequence from './Sequence/index.js';
import SelectDropdown from './SelectDropdown/index.js';
import DateOfBirth from './DateOfBirth/index.js';
import SubmissionConfirmation from './SubmissionConfirmation/index.js';
import TextInput from './TextInput/index.js';

export const defaultPatternComponents: ComponentForPattern = {
address: Address as PatternComponent,
checkbox: Checkbox as PatternComponent,
'date-of-birth': DateOfBirth as PatternComponent,
'email-input': EmailInput as PatternComponent,
fieldset: Fieldset as PatternComponent,
'form-summary': FormSummary as PatternComponent,
input: TextInput as PatternComponent,
Expand All @@ -29,7 +32,6 @@ export const defaultPatternComponents: ComponentForPattern = {
'radio-group': RadioGroup as PatternComponent,
'rich-text': RichText as PatternComponent,
'select-dropdown': SelectDropdown as PatternComponent,
'date-of-birth': DateOfBirth as PatternComponent,
sequence: Sequence as PatternComponent,
'submission-confirmation': SubmissionConfirmation as PatternComponent,
};
27 changes: 16 additions & 11 deletions packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import checkboxIcon from './images/checkbox-icon.svg';
import dateIcon from './images/date-icon.svg';
import dropDownIcon from './images/dropdown-icon.svg';
import dropDownOptionIcon from './images/dropdownoption-icon.svg';
import richTextIcon from './images/richtext-icon.svg';
import emailInputIcon from './images/email-icon.svg';
import longanswerIcon from './images/longanswer-icon.svg';
import pageIcon from './images/page-icon.svg';
import richTextIcon from './images/richtext-icon.svg';
import shortanswerIcon from './images/shortanswer-icon.svg';
import singleselectIcon from './images/singleselect-icon.svg';
import templateIcon from './images/template-icon.svg';

import classNames from 'classnames';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -25,9 +27,10 @@ const icons: Record<string, string | any> = {
'date-icon.svg': dateIcon,
'dropdown-icon.svg': dropDownIcon,
'dropdownoption-icon.svg': dropDownOptionIcon,
'richtext-icon.svg': richTextIcon,
'email-icon.svg': emailInputIcon,
'longanswer-icon.svg': longanswerIcon,
'page-icon.svg': pageIcon,
'richtext-icon.svg': richTextIcon,
'shortanswer-icon.svg': shortanswerIcon,
'singleselect-icon.svg': singleselectIcon,
'template-icon.svg': templateIcon,
Expand Down Expand Up @@ -87,27 +90,29 @@ export const AddPatternMenu = () => {

type DropdownPattern = [string, PatternConfig];
const sidebarPatterns: DropdownPattern[] = [
['form-summary', defaultFormConfig.patterns['form-summary']],
['checkbox', defaultFormConfig.patterns['checkbox']],
['date-of-birth', defaultFormConfig.patterns['date-of-birth']],
['email-input', defaultFormConfig.patterns['email-input']],
['fieldset', defaultFormConfig.patterns['fieldset']],
['form-summary', defaultFormConfig.patterns['form-summary']],
['input', defaultFormConfig.patterns['input']],
['checkbox', defaultFormConfig.patterns['checkbox']],
['package-download', defaultFormConfig.patterns['package-download']],
['paragraph', defaultFormConfig.patterns['paragraph']],
['rich-text', defaultFormConfig.patterns['rich-text']],
['radio-group', defaultFormConfig.patterns['radio-group']],
['package-download', defaultFormConfig.patterns['package-download']],
['rich-text', defaultFormConfig.patterns['rich-text']],
['select-dropdown', defaultFormConfig.patterns['select-dropdown']],
['date-of-birth', defaultFormConfig.patterns['date-of-birth']],
] as const;
export const fieldsetPatterns: DropdownPattern[] = [
['checkbox', defaultFormConfig.patterns['checkbox']],
['date-of-birth', defaultFormConfig.patterns['date-of-birth']],
['email-input', defaultFormConfig.patterns['email-input']],
['form-summary', defaultFormConfig.patterns['form-summary']],
['input', defaultFormConfig.patterns['input']],
['checkbox', defaultFormConfig.patterns['checkbox']],
['package-download', defaultFormConfig.patterns['package-download']],
['paragraph', defaultFormConfig.patterns['paragraph']],
['rich-text', defaultFormConfig.patterns['rich-text']],
['radio-group', defaultFormConfig.patterns['radio-group']],
['package-download', defaultFormConfig.patterns['package-download']],
['rich-text', defaultFormConfig.patterns['rich-text']],
['select-dropdown', defaultFormConfig.patterns['select-dropdown']],
['date-of-birth', defaultFormConfig.patterns['date-of-birth']],
] as const;

export const SidebarAddPatternMenuItem = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const Error: StoryObj<typeof FormEdit> = {

await expect(
await canvas.findByText(
message.patterns.selectDropdown.errorTextMustContainChar
message.patterns.dateOfBirth.errorTextMustContainChar
)
).toBeInTheDocument();
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent } from '@storybook/test';
import { within } from '@testing-library/react';

import { type EmailInputPattern } from '@atj/forms';
import { createPatternEditStoryMeta } from './common/story-helper.js';
import FormEdit from '../index.js';
import { enLocale as message } from '@atj/common';

const pattern: EmailInputPattern = {
id: 'email-input-1',
type: 'email-input',
data: {
label: message.patterns.emailInput.displayName,
required: false,
},
};

const storyConfig: Meta = {
title: 'Edit components/EmailInputPattern',
...createPatternEditStoryMeta({
pattern,
}),
} as Meta<typeof FormEdit>;

export default storyConfig;

export const Basic: StoryObj<typeof FormEdit> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const updatedLabel = 'Test Email Update Label';

await userEvent.click(
canvas.getByText(message.patterns.emailInput.displayName)
);

const labelInput = canvas.getByLabelText(
message.patterns.emailInput.fieldLabel
);
await userEvent.clear(labelInput);
await userEvent.type(labelInput, updatedLabel);

const form = labelInput?.closest('form');
form?.requestSubmit();

await expect(await canvas.findByText(updatedLabel)).toBeInTheDocument();
},
};

export const Error: StoryObj<typeof FormEdit> = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);

await userEvent.click(
canvas.getByText(message.patterns.emailInput.displayName)
);

const labelInput = canvas.getByLabelText(
message.patterns.emailInput.fieldLabel
);
await userEvent.clear(labelInput);
labelInput.blur();

await expect(
await canvas.findByText(
message.patterns.emailInput.errorTextMustContainChar
)
).toBeInTheDocument();
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @vitest-environment jsdom
*/
import { describeStories } from '../../../test-helper.js';
import meta, * as stories from './EmailInputPatternEdit.stories.js';

describeStories(meta, stories);
Loading

0 comments on commit 5ee1c8c

Please sign in to comment.