-
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 all 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,16 @@ | ||
.city { | ||
width: 10rem !important; | ||
margin-right: 2rem !important; | ||
} | ||
|
||
.city_error { | ||
padding-right: 2rem; | ||
} | ||
|
||
.state { | ||
width: 8rem !important; | ||
} | ||
|
||
.zipcode { | ||
width: 8rem !important; | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
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: '', | ||
city: '', | ||
state: '', | ||
zipcode: '', | ||
}, | ||
} | ||
|
||
const validationSchema = yup.object().shape({ | ||
address: yup.object().shape({ | ||
address1: yup.string().required(tCommon('validation.required')), | ||
address2: 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')), | ||
}), | ||
}) | ||
Comment on lines
+30
to
+43
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. Does this need to be defined again here when you have src/validations/address.ts? 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. They aren't the same schemas. I kept how we had it on DOL because I liked that the address only storybook had its own generic schema just for display and then when we used the object we made it more customized, min/max length. Any objection to keeping it that way? |
||
|
||
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,92 @@ | ||
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 | ||
city: string | ||
state: string | ||
zipcode: string | ||
} | ||
|
||
export interface IAddressProps { | ||
labels?: IAddressLabels | ||
basename: string | ||
optAddress2?: boolean | ||
stateSlice?: StateAbbrev[] | ||
onChange?: ChangeEventHandler<HTMLInputElement | HTMLSelectElement> | ||
} | ||
|
||
export const Address = ({ | ||
labels, | ||
basename, | ||
stateSlice, | ||
optAddress2, | ||
onChange, | ||
}: IAddressProps) => { | ||
const { t } = useTranslation('common') | ||
const defaultLabels: IAddressLabels = { | ||
address: t('address.address.label'), | ||
address2: t('address.address2.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} | ||
/> | ||
)} | ||
<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,45 @@ | ||||||||||||||||||
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 { yupPhone } 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: true, | ||||||||||||||||||
}, | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
const validationSchema = yup | ||||||||||||||||||
.object() | ||||||||||||||||||
.shape({ [args.name]: yupPhone(true, true) }) | ||||||||||||||||||
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:', | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+41
to
+45
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. So my note about the older syntax for stories can be seen in this example. The documentation now provides updated syntax (SB 7), while these examples are from storybook 6 and before. SB changes teh syntax every year it seems like, but lets go ahead and keep it all uniform with the latest style: https://storybook.js.org/docs/react/writing-stories/introduction StoryFn isn't used anymore, and the Template.bind is also not used. e.g this part would be something like
Suggested change
Yes, the name |
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 '../TextField/TextField' | ||
import { YesNoQuestion } from '../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,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,5 @@ | ||
export const UNTOUCHED_RADIO_VALUE = undefined | ||
export type UntouchedRadioValue = typeof UNTOUCHED_RADIO_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.
Are the
!important
s 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 😢)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.
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.