Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add employer page #56

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/components/form/PhoneNumberField/PhoneNumberField.stories.tsx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is the "old" (storybook changes their syntax every year it seems 🙄) storybook format, let's update to the new format/syntax

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brandonlenz Which format/syntax are you referring to? I originally changed it to match ssn and the other existing stories but since it expects a form to already exists it throws an error. The examples I see on the storybook site don't use a form provider, so I updated it from the old version with this versions equivalents, StoryFn instead of story etc; which I also got from online examples where people were using storybook with a react-hook-form subcomponent. The storyFn is needed because to simulate it as part of a form and those hook calls to make a temporary form need to happen in a function. Open to hear what else your suggesting or if you have a better way to set it as a form as I tried to not spend too long on it so could have easily overlooked something.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { yupResolver } from '@hookform/resolvers/yup'
import { Meta, StoryFn } from '@storybook/react'
import { noop } from 'helpers/noop/noop'
import { FormProvider, useForm } from 'react-hook-form'
import { PhoneInput } from 'types/input'
import { yupPhoneWithSMS } from 'validations/phone'
import * as yup from 'yup'

import { PhoneNumberField } from './PhoneNumberField'

export default {
title: 'Components/Form/PhoneNumberField',
component: PhoneNumberField,
} as Meta<typeof PhoneNumberField>

const Template: StoryFn<typeof PhoneNumberField> = (args) => {
const initialValues = {
[args.name]: {
number: '',
sms: false,
},
}

const validationSchema = yup.object().shape({ [args.name]: yupPhoneWithSMS })
const hookFormMethods = useForm<PhoneInput>({
defaultValues: initialValues,
resolver: yupResolver(validationSchema),
})

return (
<FormProvider {...hookFormMethods}>
<form onSubmit={noop}>
<PhoneNumberField {...args} />
</form>
</FormProvider>
)
}

export const Default = Template.bind({})
Default.args = {
name: 'sample_phone',
label: 'Enter your phone number:',
}
46 changes: 46 additions & 0 deletions src/components/form/PhoneNumberField/PhoneNumberField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { ChangeEventHandler, ReactNode } from 'react'
import { useTranslation } from 'react-i18next'

import TextField from '../fields/TextField/TextField'
import { YesNoQuestion } from '../fields/YesNoQuestion/YesNoQuestion'

type PhoneNumberFieldProps = {
id?: string
name: string
label: ReactNode
showSMS?: boolean
onChange?: ChangeEventHandler<HTMLInputElement>
}

export const PhoneNumberField = ({
id: idProp,
name,
label,
showSMS = true,
onChange,
}: PhoneNumberFieldProps) => {
const { t } = useTranslation('components', {
keyPrefix: 'phoneNumberField',
})

const id = idProp || name

return (
<>
<TextField
id={`${id}.number`}
name={`${name}.number`}
label={label}
type="tel"
onChange={onChange}
/>
{showSMS && (
<YesNoQuestion
question={t('sms.label')}
hint={t('sms.help_text')}
name={`${name}.sms`}
/>
)}
</>
)
}
16 changes: 16 additions & 0 deletions src/components/form/fields/Address/Address.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.city {
width: 10rem !important;
margin-right: 2rem !important;
}

.city_error {
padding-right: 2rem;
}

.state {
width: 8rem !important;
}

.zipcode {
width: 8rem !important;
}
Comment on lines +1 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the !importants really needed? Its usually code smell, so definitely want to avoid that especially this early on a project where we have the luxury and time of doing things right, even if it means banging heads against scss (again 😢)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh I recall we added this back on DOL for a reason, but to be honest I don't remember what it is I just kept it the same. I would assume that we have less stuff here so probably the same conflict won't occur.

70 changes: 70 additions & 0 deletions src/components/form/fields/Address/Address.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { yupResolver } from '@hookform/resolvers/yup'
import { Meta, StoryFn } from '@storybook/react'
import { noop } from 'helpers/noop/noop'
import { FormProvider, useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import * as yup from 'yup'

import Address, { IAddressLabels } from './Address'

export default {
title: 'Components/Form/Address',
component: Address,
} as Meta<typeof Address>

const Template: StoryFn<typeof Address> = (args) => {
const { t: tCommon } = useTranslation('common')
type Address = {
address: IAddressLabels
}
const initialValues = {
address: {
address1: '',
address2: '',
address3: undefined,
city: '',
state: '',
zipcode: '',
},
}

const validationSchema = yup.object().shape({
address: yup.object().shape({
address1: yup.string().required(tCommon('validation.required')),
address2: yup.string().optional(),
address3: yup.string().optional(),
city: yup.string().required(tCommon('validation.required')),
state: yup.string().required(tCommon('validation.required')),

zipcode: yup
.string()
// eslint-disable-next-line security/detect-unsafe-regex
.matches(/^\d{5}(-\d{4})?$/, tCommon('validation.notZipCode'))
.required(tCommon('validation.required')),
}),
})

const hookFormMethods = useForm<Address>({
defaultValues: initialValues,
resolver: yupResolver(validationSchema),
})

return (
<FormProvider {...hookFormMethods}>
<form onSubmit={noop}>
<Address basename={args.basename} optAddress2={args.optAddress2} />
</form>
</FormProvider>
)
}

export const Default = Template.bind({})
Default.args = {
basename: 'address',
}

export const WithSecondAddress = Template.bind({})
WithSecondAddress.args = {
basename: 'address',
optAddress2: true,
}
105 changes: 105 additions & 0 deletions src/components/form/fields/Address/Address.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { FormGroup } from '@trussworks/react-uswds'
import { ChangeEventHandler } from 'react'
import { useTranslation } from 'react-i18next'

import { StateAbbrev, StatesDropdown } from '../StatesDropdown/StatesDropdown'
import TextField from '../TextField/TextField'
import styles from './Address.module.scss'

export interface IAddressLabels {
address: string
address2?: string
address3?: string
city: string
state: string
zipcode: string
}

export interface IAddressProps {
labels?: IAddressLabels
basename: string
optAddress2?: boolean
optAddress3?: boolean
stateSlice?: StateAbbrev[]
onChange?: ChangeEventHandler<HTMLInputElement | HTMLSelectElement>
}

export const Address = ({
labels,
basename,
stateSlice,
optAddress2,
optAddress3,
onChange,
}: IAddressProps) => {
const { t } = useTranslation('common')
const defaultLabels: IAddressLabels = {
address: t('address.address.label'),
address2: t('address.address2.label'),
address3: t('address.address3.label'),
city: t('address.city.label'),
state: t('address.state.label'),
zipcode: t('address.zipcode.label'),
}

return (
<FormGroup>
<TextField
name={`${basename}.address`}
label={labels ? labels.address : defaultLabels.address}
type="text"
data-testid={`${basename}.address`}
onChange={onChange}
/>
{optAddress2 && (
<TextField
name={`${basename}.address2`}
label={labels ? labels.address2 : defaultLabels.address2}
type="text"
data-testid={`${basename}.address2`}
onChange={onChange}
/>
)}
{optAddress3 && (
<TextField
name={`${basename}.address3`}
label={labels ? labels.address3 : defaultLabels.address3}
type="text"
data-testid={`${basename}.address3`}
onChange={onChange}
/>
)}
<div className="display-flex" data-testid={`${basename}.parent-div`}>
<TextField
name={`${basename}.city`}
label={labels ? labels.city : defaultLabels.city}
type="text"
data-testid={`${basename}.city`}
className={styles.city}
errorClassName={styles.city_error}
onChange={onChange}
/>
<StatesDropdown
name={`${basename}.state`}
label={labels ? labels.state : defaultLabels.state}
data-testid={`${basename}.state`}
startEmpty
stateSlice={stateSlice}
onChange={onChange}
className={styles.state}
/>
</div>
<TextField
name={`${basename}.zipcode`}
label={labels ? labels.zipcode : defaultLabels.zipcode}
type="text"
inputMode="numeric"
data-testid={`${basename}.zipcode`}
className={styles.zipcode}
onChange={onChange}
/>
</FormGroup>
)
}

export default Address
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import {
FormGroup,
Label,
} from '@trussworks/react-uswds'
import { EMPTY_DROPDOWN_OPTION } from 'constants/formOptions'
import React, { ChangeEventHandler, FocusEventHandler } from 'react'
import { useController } from 'react-hook-form'
import { useTranslation } from 'react-i18next'

export const EMPTY_DROPDOWN_OPTION = ''

const mapOptions = (options: DropdownOption[]) => {
return options.map(({ label, value }, index) => (
<option key={`${index}_${label}_${value}`} value={value}>
Expand Down
42 changes: 42 additions & 0 deletions src/components/form/fields/StatesDropdown/StatesDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import DropdownField from 'components/form/fields/DropdownField/DropdownField'
import { statesAndTerritories } from 'fixtures/states_and_territories'
import { ComponentProps } from 'react'

export type StateAbbrev = keyof typeof statesAndTerritories

type DropdownProps = ComponentProps<typeof DropdownField>

type StatesDropdownProps = {
stateSlice?: StateAbbrev[]
} & Omit<DropdownProps, 'options'>

const allStates = Object.entries(statesAndTerritories).map(([key, value]) => ({
label: value,
value: key,
}))

export const StatesDropdown = ({
label,
id,
name,
startEmpty,
stateSlice,
className,
...remainingProps
}: StatesDropdownProps) => (
<DropdownField
label={label}
id={id}
name={name}
startEmpty={startEmpty}
options={
stateSlice
? allStates.filter((opt) =>
stateSlice.includes(opt.value as StateAbbrev)
)
: allStates
}
className={className}
{...remainingProps}
/>
)
8 changes: 8 additions & 0 deletions src/constants/formOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const UNTOUCHED_RADIO_VALUE = null
brandonlenz marked this conversation as resolved.
Show resolved Hide resolved
export type UntouchedRadioValue = typeof UNTOUCHED_RADIO_VALUE

// export const UNTOUCHED_CHECKBOX_VALUE = '' as const
// export type UntouchedCheckboxValue = typeof UNTOUCHED_CHECKBOX_VALUE

export const EMPTY_DROPDOWN_OPTION = '' as const
export type EmptyOption = typeof EMPTY_DROPDOWN_OPTION
8 changes: 4 additions & 4 deletions src/dev/example-form.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { yupResolver } from '@hookform/resolvers/yup'
import { Meta, StoryObj } from '@storybook/react'
import DropdownField, {
EMPTY_DROPDOWN_OPTION,
} from 'components/form/fields/DropdownField/DropdownField'
import DropdownField from 'components/form/fields/DropdownField/DropdownField'
import { RadioField } from 'components/form/fields/RadioField/RadioField'
import TextField from 'components/form/fields/TextField/TextField'
import { YesNoQuestion } from 'components/form/fields/YesNoQuestion/YesNoQuestion'
import { ImportedField } from 'components/ImportedInputBox/ImportedField/ImportedField'
import { ImportedInputBox } from 'components/ImportedInputBox/ImportedInputBox'
import { EMPTY_DROPDOWN_OPTION } from 'constants/formOptions'
import { ChangeEventHandler } from 'react'
import {
FormProvider,
SubmitErrorHandler,
SubmitHandler,
useForm,
} from 'react-hook-form'
import { YesNoInput } from 'types/input'
import * as yup from 'yup'

const formLibraryPreferenceOptions = ['formik', 'reactHookForm'] as const
Expand All @@ -38,7 +38,7 @@ const schema = yup
.required()

type ExampleFieldValues = {
doYouLikeForms?: boolean
doYouLikeForms?: YesNoInput
formLibraryPreference?: FormLibraryPreferenceOption
whyIsFormikBad?: string
bestBeverage: string
Expand Down
12 changes: 12 additions & 0 deletions src/fixtures/provinces.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"AB": "Alberta",
"BC": "British Columbia",
"MB": "Manitoba",
"NB": "New Brunswick",
"NL": "Newfoundland and Labrador",
"NS": "Nova Scotia",
"ON": "Ontario",
"PE": "Prince Edward Island",
"QC": "Quebec",
"SK": "Saskatchewan"
}
Loading