From 8b78e02ddbb9e4c8d6fd0b0f656ca28810d0ac47 Mon Sep 17 00:00:00 2001 From: kalasgarov Date: Wed, 27 Nov 2024 08:21:10 -0800 Subject: [PATCH 1/6] feat: create gender id pattern tckt-365 --- .../components/GenderId/GenderId.stories.tsx | 90 +++++++++++++++ .../components/GenderId/GenderId.test.tsx | 7 ++ .../src/Form/components/GenderId/GenderId.tsx | 104 ++++++++++++++++++ .../src/Form/components/GenderId/index.tsx | 3 + packages/design/src/Form/components/index.tsx | 2 + 5 files changed, 206 insertions(+) create mode 100644 packages/design/src/Form/components/GenderId/GenderId.stories.tsx create mode 100644 packages/design/src/Form/components/GenderId/GenderId.test.tsx create mode 100644 packages/design/src/Form/components/GenderId/GenderId.tsx create mode 100644 packages/design/src/Form/components/GenderId/index.tsx diff --git a/packages/design/src/Form/components/GenderId/GenderId.stories.tsx b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx new file mode 100644 index 000000000..c773c611b --- /dev/null +++ b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { type Meta, type StoryObj } from '@storybook/react'; +import { GenderIdPattern } from './GenderId.js'; + +const meta: Meta = { + title: 'patterns/GenderIdPattern', + component: GenderIdPattern, + decorators: [ + (Story, args) => { + const FormDecorator = () => { + const formMethods = useForm(); + return ( + + + + ); + }; + return ; + }, + ], + tags: ['autodocs'], +}; + +export default meta; + +export const Default: StoryObj = { + args: { + genderId: 'gender-identity', + label: 'Gender identity', + hint: 'For example, man, woman, non-binary', + required: false, + preferNotToAnswerText: 'Prefer not to share my gender identity', + }, +}; + +export const WithRequired: StoryObj = { + args: { + genderId: 'gender-identity', + label: 'Gender identity', + hint: 'For example, man, woman, non-binary', + required: true, + preferNotToAnswerText: 'Prefer not to share my gender identity', + }, +}; + +export const WithError: StoryObj = { + args: { + genderId: 'gender-identity', + label: 'Gender identity with error', + hint: 'For example, man, woman, non-binary', + required: true, + error: { + type: 'custom', + message: 'This field has an error', + }, + preferNotToAnswerText: 'Prefer not to share my gender identity', + }, +}; + +export const WithHint: StoryObj = { + args: { + genderId: 'gender-identity', + label: 'Gender identity', + hint: 'For example, man, woman, non-binary', + required: true, + preferNotToAnswerText: 'Prefer not to share my gender identity', + }, +}; + +export const WithCheckboxChecked: StoryObj = { + args: { + genderId: 'gender-identity', + label: 'Gender identity', + hint: 'For example, man, woman, non-binary', + required: false, + preferNotToAnswerText: 'Prefer not to share my gender identity', + preferNotToAnswerChecked: true, + }, +}; + +export const WithoutPreferNotToAnswerText: StoryObj = { + args: { + genderId: 'gender-identity', + label: 'Gender identity', + hint: 'For example, man, woman, non-binary', + required: false, + preferNotToAnswerText: undefined, + }, +}; diff --git a/packages/design/src/Form/components/GenderId/GenderId.test.tsx b/packages/design/src/Form/components/GenderId/GenderId.test.tsx new file mode 100644 index 000000000..7b92cfa28 --- /dev/null +++ b/packages/design/src/Form/components/GenderId/GenderId.test.tsx @@ -0,0 +1,7 @@ +/** + * @vitest-environment jsdom + */ +import { describeStories } from '../../../test-helper.js'; +import meta, * as stories from './GenderId.stories.js'; + +describeStories(meta, stories); diff --git a/packages/design/src/Form/components/GenderId/GenderId.tsx b/packages/design/src/Form/components/GenderId/GenderId.tsx new file mode 100644 index 000000000..56f24a8f2 --- /dev/null +++ b/packages/design/src/Form/components/GenderId/GenderId.tsx @@ -0,0 +1,104 @@ +import React, { useEffect, useState } from 'react'; +import classNames from 'classnames'; +import { useFormContext } from 'react-hook-form'; +import { type GenderIdProps } from '@atj/forms'; + +import { type PatternComponent } from '../../index.js'; + +export const GenderIdPattern: PatternComponent = ({ + genderId, + hint, + label, + required, + error, + value, + preferNotToAnswerText, + preferNotToAnswerChecked: initialPreferNotToAnswerChecked = false, +}) => { + const { register, setValue } = useFormContext(); + const [preferNotToAnswerChecked, setPreferNotToAnswerChecked] = useState( + initialPreferNotToAnswerChecked + ); + + const errorId = `input-error-message-${genderId}`; + const hintId = `hint-${genderId}`; + const preferNotToAnswerId = `prefer-not-to-answer-${genderId}`; + + useEffect(() => { + if (preferNotToAnswerChecked) { + setValue(genderId, preferNotToAnswerText, { shouldValidate: true }); + } else { + setValue(genderId, value, { shouldValidate: true }); + } + }, [ + preferNotToAnswerChecked, + setValue, + genderId, + preferNotToAnswerText, + value, + ]); + + const handleCheckboxChange = (event: React.ChangeEvent) => { + setPreferNotToAnswerChecked(event.target.checked); + setValue(preferNotToAnswerId, event.target.checked); + }; + + return ( +
+
+ + {hint && ( +
+ {hint} +
+ )} + {error && ( + + )} + + {preferNotToAnswerText && ( +
+ + +
+ )} +
+
+ ); +}; diff --git a/packages/design/src/Form/components/GenderId/index.tsx b/packages/design/src/Form/components/GenderId/index.tsx new file mode 100644 index 000000000..0ed40becd --- /dev/null +++ b/packages/design/src/Form/components/GenderId/index.tsx @@ -0,0 +1,3 @@ +import { GenderIdPattern } from './GenderId.js'; + +export default GenderIdPattern; diff --git a/packages/design/src/Form/components/index.tsx b/packages/design/src/Form/components/index.tsx index 1dfb8adcc..205bdf432 100644 --- a/packages/design/src/Form/components/index.tsx +++ b/packages/design/src/Form/components/index.tsx @@ -7,6 +7,7 @@ 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 GenderId from './GenderId/index.js'; import PackageDownload from './PackageDownload/index.js'; import Page from './Page/index.js'; import PageSet from './PageSet/index.js'; @@ -28,6 +29,7 @@ export const defaultPatternComponents: ComponentForPattern = { 'email-input': EmailInput as PatternComponent, fieldset: Fieldset as PatternComponent, 'form-summary': FormSummary as PatternComponent, + 'gender-id': GenderId as PatternComponent, input: TextInput as PatternComponent, 'package-download': PackageDownload as PatternComponent, page: Page as PatternComponent, From d101e7ed2da788b21f31828b83418ae7ccd2e098 Mon Sep 17 00:00:00 2001 From: kalasgarov Date: Wed, 27 Nov 2024 08:24:40 -0800 Subject: [PATCH 2/6] feat: create gender id pattern edit form tckt-365 --- packages/common/src/locales/en/app.ts | 10 ++ .../FormEdit/AddPatternDropdown.tsx | 4 + .../GenderIdPatternEdit.stories.tsx | 159 ++++++++++++++++++ .../GenderIdPatternEdit.test.tsx | 7 + .../components/GenderIdPatternEdit/index.tsx | 136 +++++++++++++++ .../FormManager/FormEdit/components/index.ts | 2 + .../FormEdit/images/gender-id-icon.svg | 4 + packages/forms/src/components.ts | 12 ++ 8 files changed, 334 insertions(+) create mode 100644 packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx create mode 100644 packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.test.tsx create mode 100644 packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/index.tsx create mode 100644 packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg diff --git a/packages/common/src/locales/en/app.ts b/packages/common/src/locales/en/app.ts index fa7155801..969babc37 100644 --- a/packages/common/src/locales/en/app.ts +++ b/packages/common/src/locales/en/app.ts @@ -89,5 +89,15 @@ export const en = { hint: 'For example, 555-11-0000', errorTextMustContainChar: 'String must contain at least 1 character(s)', }, + genderId: { + ...defaults, + displayName: 'Gender Identity label', + fieldLabel: 'Gender Identity label', + hintLabel: 'Gender Identity hint label', + hint: 'For example, man, woman, non-binary', + errorTextMustContainChar: 'String must contain at least 1 character(s)', + preferNotToAnswerTextLabel: + 'Prefer not to share my gender identity label', + }, }, }; diff --git a/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx b/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx index d4104e29b..a763df688 100644 --- a/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx +++ b/packages/design/src/FormManager/FormEdit/AddPatternDropdown.tsx @@ -12,6 +12,7 @@ import dateIcon from './images/date-icon.svg'; import dropDownIcon from './images/dropdown-icon.svg'; import dropDownOptionIcon from './images/dropdownoption-icon.svg'; import emailInputIcon from './images/email-icon.svg'; +import genderId from './images/gender-id-icon.svg'; import longanswerIcon from './images/longanswer-icon.svg'; import pageIcon from './images/page-icon.svg'; import phoneIcon from './images/phone-icon.svg'; @@ -32,6 +33,7 @@ const icons: Record = { 'dropdown-icon.svg': dropDownIcon, 'dropdownoption-icon.svg': dropDownOptionIcon, 'email-icon.svg': emailInputIcon, + 'gender-id-icon.svg': genderId, 'longanswer-icon.svg': longanswerIcon, 'page-icon.svg': pageIcon, 'phone-icon.svg': phoneIcon, @@ -101,6 +103,7 @@ const sidebarPatterns: DropdownPattern[] = [ ['email-input', defaultFormConfig.patterns['email-input']], ['fieldset', defaultFormConfig.patterns['fieldset']], ['form-summary', defaultFormConfig.patterns['form-summary']], + ['gender-id', defaultFormConfig.patterns['gender-id']], ['input', defaultFormConfig.patterns['input']], ['package-download', defaultFormConfig.patterns['package-download']], ['paragraph', defaultFormConfig.patterns['paragraph']], @@ -120,6 +123,7 @@ export const fieldsetPatterns: DropdownPattern[] = [ ['date-of-birth', defaultFormConfig.patterns['date-of-birth']], ['email-input', defaultFormConfig.patterns['email-input']], ['form-summary', defaultFormConfig.patterns['form-summary']], + ['gender-id', defaultFormConfig.patterns['gender-id']], ['input', defaultFormConfig.patterns['input']], ['package-download', defaultFormConfig.patterns['package-download']], ['paragraph', defaultFormConfig.patterns['paragraph']], diff --git a/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx new file mode 100644 index 000000000..891fc8f60 --- /dev/null +++ b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx @@ -0,0 +1,159 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { userEvent, expect } from '@storybook/test'; +import { within } from '@testing-library/react'; + +import { type GenderIdPattern } from '@atj/forms'; +import { createPatternEditStoryMeta } from '../common/story-helper.js'; +import FormEdit from '../../index.js'; +import { enLocale as message } from '@atj/common'; + +const pattern: GenderIdPattern = { + id: 'gender-identity-1', + type: 'gender-id', + data: { + label: message.patterns.genderId.displayName, + required: false, + hint: undefined, + preferNotToAnswerText: message.patterns.genderId.preferNotToAnswerTextLabel, + }, +}; + +const storyConfig: Meta = { + title: 'Edit components/GenderIdPattern', + ...createPatternEditStoryMeta({ + pattern, + }), +} as Meta; + +export default storyConfig; + +export const Basic: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const updatedLabel = 'Gender identity update'; + const updatedHint = 'Updated hint for Gender identity'; + const updatedPreferNotToAnswerText = + 'Updated prefer not to share my gender identity text'; + + await userEvent.click( + canvas.getByText(message.patterns.genderId.displayName) + ); + + const labelInput = canvas.getByLabelText( + message.patterns.genderId.fieldLabel + ); + await userEvent.clear(labelInput); + await userEvent.type(labelInput, updatedLabel); + + const hintInput = canvas.getByLabelText( + message.patterns.genderId.hintLabel + ); + await userEvent.clear(hintInput); + await userEvent.type(hintInput, updatedHint); + + const preferNotToAnswerInput = canvas.getByLabelText( + message.patterns.genderId.preferNotToAnswerTextLabel + ); + await userEvent.clear(preferNotToAnswerInput); + await userEvent.type(preferNotToAnswerInput, updatedPreferNotToAnswerText); + + const form = labelInput?.closest('form'); + form?.requestSubmit(); + + await expect(await canvas.findByText(updatedLabel)).toBeInTheDocument(); + await expect(await canvas.findByText(updatedHint)).toBeInTheDocument(); + await expect( + await canvas.findByText(updatedPreferNotToAnswerText) + ).toBeInTheDocument(); + }, +}; + +export const WithoutHint: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const updatedLabel = 'Gender identity update'; + const updatedPreferNotToAnswerText = + 'Prefer not to update my gender identity'; + + await userEvent.click( + canvas.getByText(message.patterns.genderId.displayName) + ); + + const labelInput = canvas.getByLabelText( + message.patterns.genderId.fieldLabel + ); + await userEvent.clear(labelInput); + await userEvent.type(labelInput, updatedLabel); + + const preferNotToAnswerInput = canvas.getByLabelText( + message.patterns.genderId.preferNotToAnswerTextLabel + ); + await userEvent.clear(preferNotToAnswerInput); + await userEvent.type(preferNotToAnswerInput, updatedPreferNotToAnswerText); + + const form = labelInput?.closest('form'); + form?.requestSubmit(); + + await expect(await canvas.findByText(updatedLabel)).toBeInTheDocument(); + await expect( + await canvas.findByText(updatedPreferNotToAnswerText) + ).toBeInTheDocument(); + await expect( + await canvas.queryByLabelText(message.patterns.genderId.hintLabel) + ).toBeNull(); + }, +}; + +export const WithoutCheckbox: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const updatedLabel = 'Gender identity update'; + const updatedHint = 'Updated hint for Gender identity'; + + await userEvent.click( + canvas.getByText(message.patterns.genderId.displayName) + ); + + const labelInput = canvas.getByLabelText( + message.patterns.genderId.fieldLabel + ); + await userEvent.clear(labelInput); + await userEvent.type(labelInput, updatedLabel); + + const hintInput = canvas.getByLabelText( + message.patterns.genderId.hintLabel + ); + await userEvent.clear(hintInput); + await userEvent.type(hintInput, updatedHint); + + const form = labelInput?.closest('form'); + form?.requestSubmit(); + + await expect(await canvas.findByText(updatedLabel)).toBeInTheDocument(); + await expect( + await canvas.queryByLabelText(message.patterns.genderId.hintLabel) + ).toBeNull(); + }, +}; + +export const Error: StoryObj = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + await userEvent.click( + canvas.getByText(message.patterns.genderId.displayName) + ); + + const labelInput = canvas.getByLabelText( + message.patterns.genderId.fieldLabel + ); + await userEvent.clear(labelInput); + labelInput.blur(); + + await expect( + await canvas.findByText( + message.patterns.selectDropdown.errorTextMustContainChar + ) + ).toBeInTheDocument(); + }, +}; diff --git a/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.test.tsx b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.test.tsx new file mode 100644 index 000000000..8b7113f2c --- /dev/null +++ b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.test.tsx @@ -0,0 +1,7 @@ +/** + * @vitest-environment jsdom + */ +import { describeStories } from '../../../../test-helper.js'; +import meta, * as stories from './GenderIdPatternEdit.stories.js'; + +describeStories(meta, stories); diff --git a/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/index.tsx b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/index.tsx new file mode 100644 index 000000000..b2c294000 --- /dev/null +++ b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/index.tsx @@ -0,0 +1,136 @@ +import classnames from 'classnames'; +import React from 'react'; + +import { type GenderIdProps } from '@atj/forms'; +import { type GenderIdPattern } from '@atj/forms'; + +import GenderId from '../../../../Form/components/GenderId/index.js'; +import { PatternEditComponent } from '../../types.js'; + +import { PatternEditActions } from '../common/PatternEditActions.js'; +import { PatternEditForm } from '../common/PatternEditForm.js'; +import { usePatternEditFormContext } from '../common/hooks.js'; +import { enLocale as message } from '@atj/common'; +import styles from '../../formEditStyles.module.css'; + +const GenderIdPatternEdit: PatternEditComponent = ({ + focus, + previewProps, +}) => { + return ( + <> + {focus ? ( + } + > + ) : ( +
+ +
+ )} + + ); +}; + +const EditComponent = ({ pattern }: { pattern: GenderIdPattern }) => { + const { fieldId, getFieldState, register } = + usePatternEditFormContext(pattern.id); + const label = getFieldState('label'); + const hint = getFieldState('hint'); + const preferNotToAnswerText = getFieldState('preferNotToAnswerText'); + + return ( +
+
+ +
+
+ +
+
+ +
+
+ + + + + + +
+
+ ); +}; + +export default GenderIdPatternEdit; diff --git a/packages/design/src/FormManager/FormEdit/components/index.ts b/packages/design/src/FormManager/FormEdit/components/index.ts index a7e33acad..2dffbaf0e 100644 --- a/packages/design/src/FormManager/FormEdit/components/index.ts +++ b/packages/design/src/FormManager/FormEdit/components/index.ts @@ -10,6 +10,7 @@ import EmailInputPatternEdit from './EmailInputPatternEdit/index.js'; import FieldsetEdit from './FieldsetEdit/index.js'; import FormSummaryEdit from './FormSummaryEdit.js'; import InputPatternEdit from './InputPatternEdit/index.js'; +import GenderIdPatternEdit from './GenderIdPatternEdit/index.js'; import PackageDownloadPatternEdit from './PackageDownloadPatternEdit.js'; import PageSetEdit from './PageSetEdit/index.js'; import { PageEdit } from './PageEdit.js'; @@ -29,6 +30,7 @@ export const defaultPatternEditComponents: EditComponentForPattern = { 'email-input': EmailInputPatternEdit as PatternEditComponent, fieldset: FieldsetEdit as PatternEditComponent, 'form-summary': FormSummaryEdit as PatternEditComponent, + 'gender-id': GenderIdPatternEdit as PatternEditComponent, input: InputPatternEdit as PatternEditComponent, 'package-download': PackageDownloadPatternEdit as PatternEditComponent, page: PageEdit as PatternEditComponent, diff --git a/packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg b/packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg new file mode 100644 index 000000000..e47888e91 --- /dev/null +++ b/packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts index c5364c04e..a3da04802 100644 --- a/packages/forms/src/components.ts +++ b/packages/forms/src/components.ts @@ -152,6 +152,18 @@ export type SocialSecurityNumberProps = PatternProps<{ value: string; }>; +export type GenderIdProps = PatternProps<{ + type: 'gender-id'; + genderId: string; + hint?: string; + label: string; + required: boolean; + error?: FormError; + value: string; + preferNotToAnswerText?: string; + preferNotToAnswerChecked?: boolean; +}>; + export type SequenceProps = PatternProps<{ type: 'sequence'; }>; From bccea83cd3640b0e12b98a6881b15b906be51322 Mon Sep 17 00:00:00 2001 From: kalasgarov Date: Fri, 29 Nov 2024 13:17:11 -0800 Subject: [PATCH 3/6] feat: add input and schema validation tckt-365 --- packages/common/src/locales/en/app.ts | 2 +- .../components/GenderId/GenderId.stories.tsx | 14 +- .../src/Form/components/GenderId/GenderId.tsx | 10 +- .../GenderIdPatternEdit.stories.tsx | 2 +- .../FormEdit/formEditStyles.module.css | 1 + .../src/patterns/gender-id/gender-id.test.ts | 125 ++++++++++++++++++ .../forms/src/patterns/gender-id/gender-id.ts | 86 ++++++++++++ packages/forms/src/patterns/index.ts | 3 + 8 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 packages/forms/src/patterns/gender-id/gender-id.test.ts create mode 100644 packages/forms/src/patterns/gender-id/gender-id.ts diff --git a/packages/common/src/locales/en/app.ts b/packages/common/src/locales/en/app.ts index 969babc37..bb5f7c689 100644 --- a/packages/common/src/locales/en/app.ts +++ b/packages/common/src/locales/en/app.ts @@ -97,7 +97,7 @@ export const en = { hint: 'For example, man, woman, non-binary', errorTextMustContainChar: 'String must contain at least 1 character(s)', preferNotToAnswerTextLabel: - 'Prefer not to share my gender identity label', + 'Prefer not to share my gender identity checkbox label', }, }, }; diff --git a/packages/design/src/Form/components/GenderId/GenderId.stories.tsx b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx index c773c611b..9614f1e6c 100644 --- a/packages/design/src/Form/components/GenderId/GenderId.stories.tsx +++ b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx @@ -29,17 +29,17 @@ export const Default: StoryObj = { genderId: 'gender-identity', label: 'Gender identity', hint: 'For example, man, woman, non-binary', - required: false, + required: true, preferNotToAnswerText: 'Prefer not to share my gender identity', }, }; -export const WithRequired: StoryObj = { +export const Optional: StoryObj = { args: { genderId: 'gender-identity', label: 'Gender identity', hint: 'For example, man, woman, non-binary', - required: true, + required: false, preferNotToAnswerText: 'Prefer not to share my gender identity', }, }; @@ -73,18 +73,18 @@ export const WithCheckboxChecked: StoryObj = { genderId: 'gender-identity', label: 'Gender identity', hint: 'For example, man, woman, non-binary', - required: false, + required: true, preferNotToAnswerText: 'Prefer not to share my gender identity', preferNotToAnswerChecked: true, }, }; -export const WithoutPreferNotToAnswerText: StoryObj = { +export const WithoutCheckbox: StoryObj = { args: { genderId: 'gender-identity', label: 'Gender identity', hint: 'For example, man, woman, non-binary', - required: false, + required: true, preferNotToAnswerText: undefined, }, -}; +}; \ No newline at end of file diff --git a/packages/design/src/Form/components/GenderId/GenderId.tsx b/packages/design/src/Form/components/GenderId/GenderId.tsx index 56f24a8f2..6755e4dac 100644 --- a/packages/design/src/Form/components/GenderId/GenderId.tsx +++ b/packages/design/src/Form/components/GenderId/GenderId.tsx @@ -69,6 +69,15 @@ export const GenderIdPattern: PatternComponent = ({ className={classNames('usa-input', { 'usa-input--error': error, })} + style={ + preferNotToAnswerChecked + ? { + backgroundColor: '#e9ecef', + pointerEvents: 'none', + opacity: 0.65, + } + : {} + } id={genderId} type="text" defaultValue={value} @@ -77,7 +86,6 @@ export const GenderIdPattern: PatternComponent = ({ `${hint ? `${hintId}` : ''}${error ? ` ${errorId}` : ''}`.trim() || undefined } - disabled={preferNotToAnswerChecked} /> {preferNotToAnswerText && (
diff --git a/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx index 891fc8f60..b3858166a 100644 --- a/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx +++ b/packages/design/src/FormManager/FormEdit/components/GenderIdPatternEdit/GenderIdPatternEdit.stories.tsx @@ -12,7 +12,7 @@ const pattern: GenderIdPattern = { type: 'gender-id', data: { label: message.patterns.genderId.displayName, - required: false, + required: true, hint: undefined, preferNotToAnswerText: message.patterns.genderId.preferNotToAnswerTextLabel, }, diff --git a/packages/design/src/FormManager/FormEdit/formEditStyles.module.css b/packages/design/src/FormManager/FormEdit/formEditStyles.module.css index 9d7dbef5e..fe72a96b0 100644 --- a/packages/design/src/FormManager/FormEdit/formEditStyles.module.css +++ b/packages/design/src/FormManager/FormEdit/formEditStyles.module.css @@ -55,6 +55,7 @@ .draggableListItemWrapper .dateOfBirthPattern legend, .draggableListItemWrapper .phoneNumberPattern legend, .draggableListItemWrapper .ssnPattern legend, +.draggableListItemWrapper .genderIdPattern legend, .draggableListItemWrapper .emailInputPattern legend { padding-left: 0; } diff --git a/packages/forms/src/patterns/gender-id/gender-id.test.ts b/packages/forms/src/patterns/gender-id/gender-id.test.ts new file mode 100644 index 000000000..e46caea27 --- /dev/null +++ b/packages/forms/src/patterns/gender-id/gender-id.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, it } from 'vitest'; +import { + createGenderIdSchema, + genderIdConfig, + type GenderIdPattern, +} from './gender-id'; + +describe('GenderIdPattern tests', () => { + describe('createGenderIdSchema', () => { + it('should create schema for required gender identity input', () => { + const data: GenderIdPattern['data'] = { + label: 'Test Gender Identity Label', + required: true, + }; + + const schema = createGenderIdSchema(data); + const validInput = 'Test Gender'; + const invalidInput = ''; + + expect(schema.safeParse(validInput).success).toBe(true); + expect(schema.safeParse(invalidInput).success).toBe(false); + }); + + it('should create schema for optional gender identity input', () => { + const data: GenderIdPattern['data'] = { + label: 'Test Gender Identity Label', + required: false, + }; + + const schema = createGenderIdSchema(data); + const validInput = 'Test Gender'; + const emptyInput = ''; + + expect(schema.safeParse(validInput).success).toBe(true); + expect(schema.safeParse(emptyInput).success).toBe(true); + }); + }); + + describe('genderIdConfig', () => { + it('should parse user input correctly', () => { + const pattern: GenderIdPattern = { + id: 'gender-identity-1', + type: 'gender-id', + data: { + label: 'Test Gender Identity Label', + required: true, + }, + }; + + const inputValue = 'Test Gender'; + if (!genderIdConfig.parseUserInput) { + expect.fail('genderIdConfig.parseUserInput is undefined'); + } + const result = genderIdConfig.parseUserInput(pattern, inputValue); + if (result.success) { + expect(result.data).toEqual(inputValue); + } else { + expect.fail('Unexpected validation failure'); + } + }); + + it('should handle validation error for user input', () => { + const pattern: GenderIdPattern = { + id: 'gender-identity-1', + type: 'gender-id', + data: { + label: 'Test Gender Identity Label', + required: true, + }, + }; + + const inputValue = ''; + if (!genderIdConfig.parseUserInput) { + expect.fail('genderIdConfig.parseUserInput is undefined'); + } + const result = genderIdConfig.parseUserInput(pattern, inputValue); + if (!result.success) { + expect(result.error).toBeDefined(); + } else { + expect.fail('Unexpected validation success'); + } + }); + + it('should parse config data correctly', () => { + const obj = { + label: 'Test Gender Identity Label', + required: true, + hint: 'For example, man, woman, non-binary', + preferNotToAnswerText: 'Prefer not to share my gender identity', + }; + + if (!genderIdConfig.parseConfigData) { + expect.fail('genderIdConfig.parseConfigData is undefined'); + } + const result = genderIdConfig.parseConfigData(obj); + if (result.success) { + expect(result.data.label).toBe('Test Gender Identity Label'); + expect(result.data.required).toBe(true); + expect(result.data.hint).toBe('For example, man, woman, non-binary'); + expect(result.data.preferNotToAnswerText).toBe( + 'Prefer not to share my gender identity' + ); + } else { + expect.fail('Unexpected validation failure'); + } + }); + + it('should handle invalid config data', () => { + const obj = { + label: '', + required: true, + }; + + if (!genderIdConfig.parseConfigData) { + expect.fail('genderIdConfig.parseConfigData is undefined'); + } + const result = genderIdConfig.parseConfigData(obj); + if (!result.success) { + expect(result.error).toBeDefined(); + } else { + expect.fail('Unexpected validation success'); + } + }); + }); +}); diff --git a/packages/forms/src/patterns/gender-id/gender-id.ts b/packages/forms/src/patterns/gender-id/gender-id.ts new file mode 100644 index 000000000..208625c3c --- /dev/null +++ b/packages/forms/src/patterns/gender-id/gender-id.ts @@ -0,0 +1,86 @@ +import * as z from 'zod'; +import { type GenderIdProps } from '../../components.js'; +import { type Pattern, type PatternConfig } from '../../pattern.js'; +import { getFormSessionValue } from '../../session.js'; +import { + safeZodParseFormErrors, + safeZodParseToFormError, +} from '../../util/zod.js'; + +const configSchema = z.object({ + label: z.string().min(1), + required: z.boolean(), + hint: z.string().optional(), + preferNotToAnswerText: z.string().optional(), +}); + +export type GenderIdPattern = Pattern>; + +export type GenderIdPatternOutput = z.infer< + ReturnType +>; + +export const createGenderIdSchema = (data: GenderIdPattern['data']) => { + return z.string().superRefine((value, ctx) => { + if (value === data.preferNotToAnswerText) { + return; + } + if (data.required && value.trim() === '') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'This field is required', + }); + } + }); +}; + +export const genderIdConfig: PatternConfig< + GenderIdPattern, + GenderIdPatternOutput +> = { + displayName: 'Gender ID', + iconPath: 'gender-id-icon.svg', + initial: { + label: 'Gender identity', + required: true, + hint: 'For example, man, woman, non-binary', + preferNotToAnswerText: 'Prefer not to share my gender identity', + }, + + parseUserInput: (pattern, inputValue) => { + const result = safeZodParseToFormError( + createGenderIdSchema(pattern.data), + inputValue + ); + return result; + }, + + parseConfigData: obj => { + return safeZodParseFormErrors(configSchema, obj); + }, + getChildren() { + return []; + }, + + createPrompt(_, session, pattern, options) { + const extraAttributes: Record = {}; + const sessionValue = getFormSessionValue(session, pattern.id); + const error = session.data.errors[pattern.id]; + + return { + props: { + _patternId: pattern.id, + type: 'gender-id', + label: pattern.data.label, + genderId: pattern.id, + required: pattern.data.required, + hint: pattern.data.hint, + preferNotToAnswerText: pattern.data.preferNotToAnswerText, + value: sessionValue, + error, + ...extraAttributes, + } as GenderIdProps, + children: [], + }; + }, +}; diff --git a/packages/forms/src/patterns/index.ts b/packages/forms/src/patterns/index.ts index bb58d988d..dc5f5fbdc 100644 --- a/packages/forms/src/patterns/index.ts +++ b/packages/forms/src/patterns/index.ts @@ -7,6 +7,7 @@ import { dateOfBirthConfig } from './date-of-birth/date-of-birth.js'; import { emailInputConfig } from './email-input/email-input.js'; import { fieldsetConfig } from './fieldset/index.js'; import { formSummaryConfig } from './form-summary.js'; +import { genderIdConfig } from './gender-id/gender-id.js'; import { inputConfig } from './input/index.js'; import { packageDownloadConfig } from './package-download/index.js'; import { pageConfig } from './page/index.js'; @@ -31,6 +32,7 @@ export const defaultFormConfig: FormConfig = { 'email-input': emailInputConfig, fieldset: fieldsetConfig, 'form-summary': formSummaryConfig, + 'gender-id': genderIdConfig, input: inputConfig, 'package-download': packageDownloadConfig, page: pageConfig, @@ -55,6 +57,7 @@ export * from './email-input/email-input.js'; export * from './fieldset/index.js'; export { type FieldsetPattern } from './fieldset/config.js'; export * from './form-summary.js'; +export * from './gender-id/gender-id.js'; export * from './input/index.js'; export { type InputPattern } from './input/config.js'; export * from './package-download/index.js'; From 6cc6d4735a4d8ff6256dc37bf9a5339b5691ffc9 Mon Sep 17 00:00:00 2001 From: kalasgarov Date: Fri, 29 Nov 2024 13:43:32 -0800 Subject: [PATCH 4/6] feat: update gender identity icon svg tckt-365 --- .../src/Form/components/GenderId/GenderId.stories.tsx | 2 +- .../src/FormManager/FormEdit/images/gender-id-icon.svg | 6 +++--- packages/forms/src/patterns/gender-id/gender-id.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/design/src/Form/components/GenderId/GenderId.stories.tsx b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx index 9614f1e6c..28d2e1a4e 100644 --- a/packages/design/src/Form/components/GenderId/GenderId.stories.tsx +++ b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx @@ -87,4 +87,4 @@ export const WithoutCheckbox: StoryObj = { required: true, preferNotToAnswerText: undefined, }, -}; \ No newline at end of file +}; diff --git a/packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg b/packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg index e47888e91..a36825407 100644 --- a/packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg +++ b/packages/design/src/FormManager/FormEdit/images/gender-id-icon.svg @@ -1,4 +1,4 @@ - - - + + + GI \ No newline at end of file diff --git a/packages/forms/src/patterns/gender-id/gender-id.ts b/packages/forms/src/patterns/gender-id/gender-id.ts index 208625c3c..dc24f38ca 100644 --- a/packages/forms/src/patterns/gender-id/gender-id.ts +++ b/packages/forms/src/patterns/gender-id/gender-id.ts @@ -38,7 +38,7 @@ export const genderIdConfig: PatternConfig< GenderIdPattern, GenderIdPatternOutput > = { - displayName: 'Gender ID', + displayName: 'Gender Identity', iconPath: 'gender-id-icon.svg', initial: { label: 'Gender identity', From 491d00c648b489374011048816e7dd7d54738232 Mon Sep 17 00:00:00 2001 From: kalasgarov Date: Fri, 6 Dec 2024 11:35:32 -0800 Subject: [PATCH 5/6] refactor: update story and enhanch component file structure for gender id -tckt-365 --- .../components/GenderId/GenderId.stories.tsx | 56 +++------ .../src/Form/components/GenderId/GenderId.tsx | 112 ----------------- .../src/Form/components/GenderId/index.tsx | 113 +++++++++++++++++- 3 files changed, 127 insertions(+), 154 deletions(-) delete mode 100644 packages/design/src/Form/components/GenderId/GenderId.tsx diff --git a/packages/design/src/Form/components/GenderId/GenderId.stories.tsx b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx index 28d2e1a4e..d9d863cb7 100644 --- a/packages/design/src/Form/components/GenderId/GenderId.stories.tsx +++ b/packages/design/src/Form/components/GenderId/GenderId.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { type Meta, type StoryObj } from '@storybook/react'; -import { GenderIdPattern } from './GenderId.js'; +import GenderIdPattern from './index.js'; const meta: Meta = { title: 'patterns/GenderIdPattern', @@ -24,67 +24,41 @@ const meta: Meta = { export default meta; +const defaultArgs = { + genderId: 'gender-identity', + label: 'Gender identity', + hint: 'For example, man, woman, non-binary', + required: true, + preferNotToAnswerText: 'Prefer not to share my gender identity', +}; + export const Default: StoryObj = { - args: { - genderId: 'gender-identity', - label: 'Gender identity', - hint: 'For example, man, woman, non-binary', - required: true, - preferNotToAnswerText: 'Prefer not to share my gender identity', - }, + args: { ...defaultArgs }, }; export const Optional: StoryObj = { - args: { - genderId: 'gender-identity', - label: 'Gender identity', - hint: 'For example, man, woman, non-binary', - required: false, - preferNotToAnswerText: 'Prefer not to share my gender identity', - }, + args: { ...defaultArgs, required: false }, }; export const WithError: StoryObj = { args: { - genderId: 'gender-identity', + ...defaultArgs, label: 'Gender identity with error', - hint: 'For example, man, woman, non-binary', - required: true, error: { type: 'custom', message: 'This field has an error', }, - preferNotToAnswerText: 'Prefer not to share my gender identity', }, }; export const WithHint: StoryObj = { - args: { - genderId: 'gender-identity', - label: 'Gender identity', - hint: 'For example, man, woman, non-binary', - required: true, - preferNotToAnswerText: 'Prefer not to share my gender identity', - }, + args: { ...defaultArgs }, }; export const WithCheckboxChecked: StoryObj = { - args: { - genderId: 'gender-identity', - label: 'Gender identity', - hint: 'For example, man, woman, non-binary', - required: true, - preferNotToAnswerText: 'Prefer not to share my gender identity', - preferNotToAnswerChecked: true, - }, + args: { ...defaultArgs, preferNotToAnswerChecked: true }, }; export const WithoutCheckbox: StoryObj = { - args: { - genderId: 'gender-identity', - label: 'Gender identity', - hint: 'For example, man, woman, non-binary', - required: true, - preferNotToAnswerText: undefined, - }, + args: { ...defaultArgs, preferNotToAnswerText: undefined }, }; diff --git a/packages/design/src/Form/components/GenderId/GenderId.tsx b/packages/design/src/Form/components/GenderId/GenderId.tsx deleted file mode 100644 index 6755e4dac..000000000 --- a/packages/design/src/Form/components/GenderId/GenderId.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import classNames from 'classnames'; -import { useFormContext } from 'react-hook-form'; -import { type GenderIdProps } from '@atj/forms'; - -import { type PatternComponent } from '../../index.js'; - -export const GenderIdPattern: PatternComponent = ({ - genderId, - hint, - label, - required, - error, - value, - preferNotToAnswerText, - preferNotToAnswerChecked: initialPreferNotToAnswerChecked = false, -}) => { - const { register, setValue } = useFormContext(); - const [preferNotToAnswerChecked, setPreferNotToAnswerChecked] = useState( - initialPreferNotToAnswerChecked - ); - - const errorId = `input-error-message-${genderId}`; - const hintId = `hint-${genderId}`; - const preferNotToAnswerId = `prefer-not-to-answer-${genderId}`; - - useEffect(() => { - if (preferNotToAnswerChecked) { - setValue(genderId, preferNotToAnswerText, { shouldValidate: true }); - } else { - setValue(genderId, value, { shouldValidate: true }); - } - }, [ - preferNotToAnswerChecked, - setValue, - genderId, - preferNotToAnswerText, - value, - ]); - - const handleCheckboxChange = (event: React.ChangeEvent) => { - setPreferNotToAnswerChecked(event.target.checked); - setValue(preferNotToAnswerId, event.target.checked); - }; - - return ( -
-
- - {hint && ( -
- {hint} -
- )} - {error && ( - - )} - - {preferNotToAnswerText && ( -
- - -
- )} -
- - ); -}; diff --git a/packages/design/src/Form/components/GenderId/index.tsx b/packages/design/src/Form/components/GenderId/index.tsx index 0ed40becd..0709523bc 100644 --- a/packages/design/src/Form/components/GenderId/index.tsx +++ b/packages/design/src/Form/components/GenderId/index.tsx @@ -1,3 +1,114 @@ -import { GenderIdPattern } from './GenderId.js'; +import React, { useEffect, useState } from 'react'; +import classNames from 'classnames'; +import { useFormContext } from 'react-hook-form'; +import { type GenderIdProps } from '@atj/forms'; + +import { type PatternComponent } from '../../index.js'; + +const GenderIdPattern: PatternComponent = ({ + genderId, + hint, + label, + required, + error, + value, + preferNotToAnswerText, + preferNotToAnswerChecked: initialPreferNotToAnswerChecked = false, +}) => { + const { register, setValue } = useFormContext(); + const [preferNotToAnswerChecked, setPreferNotToAnswerChecked] = useState( + initialPreferNotToAnswerChecked + ); + + const errorId = `input-error-message-${genderId}`; + const hintId = `hint-${genderId}`; + const preferNotToAnswerId = `prefer-not-to-answer-${genderId}`; + + useEffect(() => { + if (preferNotToAnswerChecked) { + setValue(genderId, preferNotToAnswerText, { shouldValidate: true }); + } else { + setValue(genderId, value, { shouldValidate: true }); + } + }, [ + preferNotToAnswerChecked, + setValue, + genderId, + preferNotToAnswerText, + value, + ]); + + const handleCheckboxChange = (event: React.ChangeEvent) => { + setPreferNotToAnswerChecked(event.target.checked); + setValue(preferNotToAnswerId, event.target.checked); + }; + + return ( +
+
+ + {hint && ( +
+ {hint} +
+ )} + {error && ( + + )} + + {preferNotToAnswerText && ( +
+ + +
+ )} +
+
+ ); +}; export default GenderIdPattern; From 928f945807439295c5c84e855ff50d307e128dfe Mon Sep 17 00:00:00 2001 From: kalasgarov Date: Mon, 9 Dec 2024 11:48:09 -0800 Subject: [PATCH 6/6] fix: resolve checkbox issue to persist the checked state tckt-365 --- .../src/Form/components/GenderId/index.tsx | 56 +++++++------------ .../src/patterns/gender-id/gender-id.test.ts | 20 +++++-- .../forms/src/patterns/gender-id/gender-id.ts | 37 ++++++++---- 3 files changed, 59 insertions(+), 54 deletions(-) diff --git a/packages/design/src/Form/components/GenderId/index.tsx b/packages/design/src/Form/components/GenderId/index.tsx index 0709523bc..565b4e35b 100644 --- a/packages/design/src/Form/components/GenderId/index.tsx +++ b/packages/design/src/Form/components/GenderId/index.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import classNames from 'classnames'; -import { useFormContext } from 'react-hook-form'; +import { useFormContext, useWatch } from 'react-hook-form'; import { type GenderIdProps } from '@atj/forms'; - import { type PatternComponent } from '../../index.js'; const GenderIdPattern: PatternComponent = ({ @@ -11,7 +10,7 @@ const GenderIdPattern: PatternComponent = ({ label, required, error, - value, + value = '', preferNotToAnswerText, preferNotToAnswerChecked: initialPreferNotToAnswerChecked = false, }) => { @@ -22,25 +21,17 @@ const GenderIdPattern: PatternComponent = ({ const errorId = `input-error-message-${genderId}`; const hintId = `hint-${genderId}`; - const preferNotToAnswerId = `prefer-not-to-answer-${genderId}`; + const preferNotToAnswerId = `${genderId}.preferNotToAnswer`; + const inputId = `${genderId}.input`; - useEffect(() => { - if (preferNotToAnswerChecked) { - setValue(genderId, preferNotToAnswerText, { shouldValidate: true }); - } else { - setValue(genderId, value, { shouldValidate: true }); - } - }, [ - preferNotToAnswerChecked, - setValue, - genderId, - preferNotToAnswerText, - value, - ]); + const watchedValue = useWatch({ name: inputId, defaultValue: value }); const handleCheckboxChange = (event: React.ChangeEvent) => { - setPreferNotToAnswerChecked(event.target.checked); - setValue(preferNotToAnswerId, event.target.checked); + const isChecked = event.target.checked; + setPreferNotToAnswerChecked(isChecked); + setValue(genderId, isChecked ? preferNotToAnswerText : value, { + shouldValidate: true, + }); }; return ( @@ -66,35 +57,28 @@ const GenderIdPattern: PatternComponent = ({
)} {preferNotToAnswerText && ( -
+
diff --git a/packages/forms/src/patterns/gender-id/gender-id.test.ts b/packages/forms/src/patterns/gender-id/gender-id.test.ts index e46caea27..d6a5c5745 100644 --- a/packages/forms/src/patterns/gender-id/gender-id.test.ts +++ b/packages/forms/src/patterns/gender-id/gender-id.test.ts @@ -3,6 +3,7 @@ import { createGenderIdSchema, genderIdConfig, type GenderIdPattern, + type GenderIdPatternOutput, } from './gender-id'; describe('GenderIdPattern tests', () => { @@ -11,14 +12,19 @@ describe('GenderIdPattern tests', () => { const data: GenderIdPattern['data'] = { label: 'Test Gender Identity Label', required: true, + preferNotToAnswerText: 'Prefer not to share my gender identity', }; const schema = createGenderIdSchema(data); - const validInput = 'Test Gender'; - const invalidInput = ''; + const validInput = { input: 'Test Gender' }; + const invalidInput = { input: '' }; + const preferNotToAnswerInput = { + preferNotToAnswer: 'Prefer not to share my gender identity', + }; expect(schema.safeParse(validInput).success).toBe(true); expect(schema.safeParse(invalidInput).success).toBe(false); + expect(schema.safeParse(preferNotToAnswerInput).success).toBe(true); }); it('should create schema for optional gender identity input', () => { @@ -28,8 +34,8 @@ describe('GenderIdPattern tests', () => { }; const schema = createGenderIdSchema(data); - const validInput = 'Test Gender'; - const emptyInput = ''; + const validInput = { input: 'Test Gender' }; + const emptyInput = { input: '' }; expect(schema.safeParse(validInput).success).toBe(true); expect(schema.safeParse(emptyInput).success).toBe(true); @@ -44,10 +50,11 @@ describe('GenderIdPattern tests', () => { data: { label: 'Test Gender Identity Label', required: true, + preferNotToAnswerText: 'Prefer not to share my gender identity', }, }; - const inputValue = 'Test Gender'; + const inputValue = { input: 'Test Gender' }; if (!genderIdConfig.parseUserInput) { expect.fail('genderIdConfig.parseUserInput is undefined'); } @@ -66,10 +73,11 @@ describe('GenderIdPattern tests', () => { data: { label: 'Test Gender Identity Label', required: true, + preferNotToAnswerText: 'Prefer not to share my gender identity', }, }; - const inputValue = ''; + const inputValue = { input: '' }; if (!genderIdConfig.parseUserInput) { expect.fail('genderIdConfig.parseUserInput is undefined'); } diff --git a/packages/forms/src/patterns/gender-id/gender-id.ts b/packages/forms/src/patterns/gender-id/gender-id.ts index dc24f38ca..93bf7b680 100644 --- a/packages/forms/src/patterns/gender-id/gender-id.ts +++ b/packages/forms/src/patterns/gender-id/gender-id.ts @@ -21,17 +21,26 @@ export type GenderIdPatternOutput = z.infer< >; export const createGenderIdSchema = (data: GenderIdPattern['data']) => { - return z.string().superRefine((value, ctx) => { - if (value === data.preferNotToAnswerText) { - return; - } - if (data.required && value.trim() === '') { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'This field is required', - }); - } - }); + return z + .object({ + input: z.string().optional(), + preferNotToAnswer: z.string().optional(), + }) + .superRefine((value, ctx) => { + const { input, preferNotToAnswer } = value; + + if ( + data.required && + !input?.trim() && + preferNotToAnswer !== data.preferNotToAnswerText + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'This field is required', + }); + return; + } + }); }; export const genderIdConfig: PatternConfig< @@ -65,6 +74,9 @@ export const genderIdConfig: PatternConfig< createPrompt(_, session, pattern, options) { const extraAttributes: Record = {}; const sessionValue = getFormSessionValue(session, pattern.id); + const value = sessionValue?.input || ''; + const preferNotToAnswerChecked = + sessionValue?.preferNotToAnswer === pattern.data.preferNotToAnswerText; const error = session.data.errors[pattern.id]; return { @@ -76,7 +88,8 @@ export const genderIdConfig: PatternConfig< required: pattern.data.required, hint: pattern.data.hint, preferNotToAnswerText: pattern.data.preferNotToAnswerText, - value: sessionValue, + preferNotToAnswerChecked, + value, error, ...extraAttributes, } as GenderIdProps,