Skip to content

Commit c314234

Browse files
brandonlenzgidjin
andauthored
feat: Add validationStatus prop to Dropdown (#2365)
Co-authored-by: John Gedeon <[email protected]>
1 parent 9de5b9d commit c314234

File tree

9 files changed

+112
-44
lines changed

9 files changed

+112
-44
lines changed

src/components/forms/DatePicker/DatePicker.stories.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@ import { Form } from '../Form/Form'
66
import { FormGroup } from '../FormGroup/FormGroup'
77
import { Label } from '../Label/Label'
88
import { TextInput } from '../TextInput/TextInput'
9+
import { ValidationStatus } from '../../../types/validationStatus'
910

1011
export default {
1112
title: 'Components/Date picker',
1213
component: DatePicker,
1314
argTypes: {
1415
onSubmit: { action: 'submitted' },
1516
disabled: { control: { type: 'boolean' } },
16-
validationStatus: {
17+
validationStatus: {
1718
control: {
1819
type: 'select',
1920
options: [undefined, 'success', 'error'],
2021
},
21-
defaultValue: undefined,
22-
}
22+
defaultValue: undefined,
23+
},
2324
},
2425
parameters: {
2526
docs: {
@@ -52,15 +53,18 @@ We may find that we want to expose props for custom event handlers or even a ref
5253
type StorybookArguments = {
5354
onSubmit: React.FormEventHandler<HTMLFormElement>
5455
disabled?: boolean
55-
validationStatus: 'success' | 'error' | undefined
56+
validationStatus?: ValidationStatus
5657
}
5758

5859
export const completeDatePicker = (
5960
argTypes: StorybookArguments
6061
): React.ReactElement => (
6162
<Form onSubmit={argTypes.onSubmit}>
6263
<FormGroup error={argTypes.validationStatus === 'error'}>
63-
<Label id="appointment-date-label" htmlFor="appointment-date" error={argTypes.validationStatus === 'error'}>
64+
<Label
65+
id="appointment-date-label"
66+
htmlFor="appointment-date"
67+
error={argTypes.validationStatus === 'error'}>
6468
Appointment date
6569
</Label>
6670
<div className="usa-hint" id="appointment-date-hint">

src/components/forms/DatePicker/DatePicker.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ import {
2424
addDays,
2525
} from './utils'
2626
import { Calendar } from './Calendar'
27+
import { ValidationStatus } from '../../../types/validationStatus'
2728

2829
type BaseDatePickerProps = {
2930
id: string
3031
name: string
3132
className?: string
32-
validationStatus?: 'error' | 'success'
33+
validationStatus?: ValidationStatus
3334
disabled?: boolean
3435
required?: boolean
3536
defaultValue?: string
@@ -252,7 +253,7 @@ export const DatePicker = ({
252253
{
253254
'usa-input--error': isError,
254255
'usa-input--success': isSuccess,
255-
},
256+
}
256257
)
257258

258259
const toggleCalendar = i18n.toggleCalendar

src/components/forms/Dropdown/Dropdown.stories.tsx

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22

33
import { Dropdown } from './Dropdown'
44
import { Label } from '../Label/Label'
5+
import { ComponentMeta, ComponentStory } from '@storybook/react'
56

67
export default {
78
title: 'Components/Dropdown',
@@ -17,43 +18,50 @@ Source: https://designsystem.digital.gov/components/select/
1718
},
1819
},
1920
},
20-
}
21+
argTypes: {
22+
validationStatus: {
23+
options: ['error', 'success'],
24+
control: 'radio',
25+
},
26+
disabled: { control: 'boolean' },
27+
},
28+
} as ComponentMeta<typeof Dropdown>
2129

22-
export const defaultDropdown = (): React.ReactElement => (
23-
<Dropdown id="input-dropdown" name="input-dropdown">
30+
const options = (
31+
<>
2432
<option>- Select - </option>
2533
<option value="value1">Option A</option>
2634
<option value="value2">Option B</option>
2735
<option value="value3">Option C</option>
28-
</Dropdown>
36+
</>
2937
)
3038

31-
export const withDefaultValue = (): React.ReactElement => (
32-
<Dropdown id="input-dropdown" name="input-dropdown" defaultValue="value2">
33-
<option>- Select - </option>
34-
<option value="value1">Option A</option>
35-
<option value="value2">Option B</option>
36-
<option value="value3">Option C</option>
37-
</Dropdown>
39+
const Template: ComponentStory<typeof Dropdown> = (args) => (
40+
<Dropdown {...args}>{options}</Dropdown>
3841
)
3942

40-
export const withLabel = (): React.ReactElement => (
43+
export const Default = Template.bind({})
44+
Default.args = { id: 'input-dropdown', name: 'input-dropdown' }
45+
46+
export const WithDefaultValue = Template.bind({})
47+
WithDefaultValue.args = {
48+
id: 'input-dropdown',
49+
name: 'input-dropdown',
50+
defaultValue: 'value2',
51+
}
52+
53+
export const Disabled = Template.bind({})
54+
Disabled.args = {
55+
id: 'input-dropdown',
56+
name: 'input-dropdown',
57+
disabled: true,
58+
}
59+
60+
export const WithLabel = () => (
4161
<>
42-
<Label htmlFor="options">Dropdown label</Label>
62+
<Label htmlFor="input-dropdown">Dropdown label</Label>
4363
<Dropdown id="input-dropdown" name="input-dropdown">
44-
<option>- Select - </option>
45-
<option value="value1">Option A</option>
46-
<option value="value2">Option B</option>
47-
<option value="value3">Option C</option>
64+
{options}
4865
</Dropdown>
4966
</>
5067
)
51-
52-
export const disabled = (): React.ReactElement => (
53-
<Dropdown id="input-dropdown" name="input-dropdown" disabled>
54-
<option>- Select - </option>
55-
<option value="value1">Option A</option>
56-
<option value="value2">Option B</option>
57-
<option value="value3">Option C</option>
58-
</Dropdown>
59-
)
Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,58 @@
1-
import React from 'react'
2-
import { render } from '@testing-library/react'
1+
import React, { ComponentProps } from 'react'
2+
import { render, screen } from '@testing-library/react'
33

44
import { Dropdown } from './Dropdown'
55

66
describe('Dropdown component', () => {
7-
it('renders without errors', () => {
8-
const { queryByTestId } = render(
9-
<Dropdown id="input-type-text" name="input-type-text">
7+
const renderDropdown = (
8+
props?: Omit<ComponentProps<typeof Dropdown>, 'id' | 'name' | 'children'>
9+
) => {
10+
render(
11+
<Dropdown id="input-type-dropdown" name="input-type-dropdown" {...props}>
1012
<option>- Select - </option>
1113
<option value="value1">Option A</option>
1214
<option value="value2">Option B</option>
1315
<option value="value3">Option C</option>
1416
</Dropdown>
1517
)
16-
expect(queryByTestId('dropdown')).toBeInTheDocument()
18+
19+
const queryForDropdown = () => screen.queryByRole('combobox')
20+
21+
return {
22+
queryForDropdown,
23+
}
24+
}
25+
26+
it('renders without errors', () => {
27+
const { queryForDropdown } = renderDropdown()
28+
29+
const dropdown = queryForDropdown()
30+
31+
expect(dropdown).toBeInTheDocument()
32+
expect(dropdown).toHaveClass('usa-select')
33+
})
34+
35+
describe('validationStatus', () => {
36+
it('renders with error styling', () => {
37+
const { queryForDropdown } = renderDropdown({ validationStatus: 'error' })
38+
39+
const dropdown = queryForDropdown()
40+
41+
expect(dropdown).toBeInTheDocument()
42+
expect(dropdown).toHaveClass('usa-select')
43+
expect(dropdown).toHaveClass('usa-input--error')
44+
})
45+
46+
it('renders with success styling', () => {
47+
const { queryForDropdown } = renderDropdown({
48+
validationStatus: 'success',
49+
})
50+
51+
const dropdown = queryForDropdown()
52+
53+
expect(dropdown).toBeInTheDocument()
54+
expect(dropdown).toHaveClass('usa-select')
55+
expect(dropdown).toHaveClass('usa-input--success')
56+
})
1757
})
1858
})

src/components/forms/Dropdown/Dropdown.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import React from 'react'
22
import classnames from 'classnames'
3+
import { ValidationStatus } from '../../../types/validationStatus'
34

45
type DropdownProps = {
56
id: string
67
name: string
78
className?: string
89
children: React.ReactNode
10+
validationStatus?: ValidationStatus
911
inputRef?:
1012
| string
1113
| ((instance: HTMLSelectElement | null) => void)
@@ -20,9 +22,19 @@ export const Dropdown = ({
2022
className,
2123
inputRef,
2224
children,
25+
validationStatus,
2326
...inputProps
2427
}: DropdownProps & JSX.IntrinsicElements['select']): React.ReactElement => {
25-
const classes = classnames('usa-select', className)
28+
const isError = validationStatus === 'error'
29+
const isSuccess = validationStatus === 'success'
30+
const classes = classnames(
31+
'usa-select',
32+
{
33+
'usa-input--error': isError,
34+
'usa-input--success': isSuccess,
35+
},
36+
className
37+
)
2638

2739
return (
2840
<select

src/components/forms/TextInput/TextInput.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react'
22
import { render } from '@testing-library/react'
33
import { TextInput } from './TextInput'
4+
import { ValidationStatus } from '../../../types/validationStatus'
45

56
describe('TextInput component', () => {
67
it('renders without errors', () => {
@@ -40,13 +41,12 @@ describe('TextInput component', () => {
4041
jest.clearAllMocks()
4142
})
4243

43-
it.each([
44+
it.each<[ValidationStatus, string]>([
4445
['error', 'usa-input--error'],
4546
['success', 'usa-input--success'],
4647
])(
4748
'when validationStatus is %s should include class %s',
48-
(validationString, uswdsClass) => {
49-
const validationStatus = validationString as 'error' | 'success'
49+
(validationStatus, uswdsClass) => {
5050
const { container } = render(
5151
<TextInput
5252
id="input-type-text"

src/components/forms/TextInput/TextInput.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react'
22
import classnames from 'classnames'
3+
import { ValidationStatus } from '../../../types/validationStatus'
34

45
type TextInputRef =
56
| string
@@ -16,7 +17,7 @@ type RequiredTextInputProps = {
1617

1718
type CustomTextInputProps = {
1819
className?: string
19-
validationStatus?: 'error' | 'success'
20+
validationStatus?: ValidationStatus
2021
inputSize?: 'small' | 'medium'
2122
inputRef?: TextInputRef
2223
inputProps?: JSX.IntrinsicElements['input']

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export { TextInput } from './components/forms/TextInput/TextInput'
6969
export { TimePicker } from './components/forms/TimePicker/TimePicker'
7070
export { ValidationChecklist } from './components/forms/Validation/ValidationChecklist'
7171
export { ValidationItem } from './components/forms/Validation/ValidationItem'
72+
export type { ValidationStatus } from './types/validationStatus'
7273

7374
/** Header Components */
7475
export { ExtendedNav } from './components/header/ExtendedNav/ExtendedNav'

src/types/validationStatus.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type ValidationStatus = 'error' | 'success'

0 commit comments

Comments
 (0)