-
Notifications
You must be signed in to change notification settings - Fork 1
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
base: main
Are you sure you want to change the base?
Changes from 7 commits
cee970b
317f3d2
f46ea74
d9d857b
a2b124f
069bea6
de7e6e7
7d38ba3
bd58b66
9526b42
e370ac4
7e7c2f0
7cc2e27
ba42032
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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:', | ||
} |
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`} | ||
/> | ||
)} | ||
</> | ||
) | ||
} |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
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, | ||
} |
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 |
---|---|---|
@@ -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} | ||
/> | ||
) |
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 |
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" | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.