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

feat: File Input component #900

Merged
merged 13 commits into from
Feb 26, 2021
49 changes: 46 additions & 3 deletions example/src/pages/Forms.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useRef } from 'react'
import { Field, Formik } from 'formik'
import * as Yup from 'yup'
import {
Expand All @@ -12,6 +12,7 @@ import {
ValidationChecklist,
ValidationItem,
DatePicker,
FileInput,
} from '@trussworks/react-uswds'

type FormValues = {
Expand Down Expand Up @@ -44,6 +45,8 @@ const fruitOptions = Object.entries(fruits).map(([value, key]) => ({
}))

const FormsPage = (): React.ReactElement => {
const fileInputRef = useRef<HTMLInputElement>(null)

return (
<>
<h1>Forms Examples</h1>
Expand All @@ -55,12 +58,14 @@ const FormsPage = (): React.ReactElement => {
password: '',
fruit: 'avocado',
appointmentDate: '1/20/2021',
file: '',
attachments: [],
}}
validationSchema={FormSchema}
onSubmit={(values, { setSubmitting }) => {
console.log('Submit form data:', values)
setTimeout(() => {
alert(JSON.stringify(values, null, 2))

console.log('Submit complete!')
setSubmitting(false)
}, 400)
}}>
Expand Down Expand Up @@ -151,6 +156,44 @@ const FormsPage = (): React.ReactElement => {
/>
</FormGroup>

<FormGroup>
<Label htmlFor="file">File input (single)</Label>
<FileInput
id="file"
name="file"
onChange={(e: React.ChangeEvent): void => {
const event = e as React.ChangeEvent<HTMLInputElement>

if (event.target.files?.length) {
setFieldValue('file', event.target.files[0])
}
}}
/>
</FormGroup>

<FormGroup>
<Label htmlFor="attachments">File input (multiple)</Label>
<FileInput
id="attachments"
name="attachments"
multiple
onChange={(e: React.ChangeEvent): void => {
const event = e as React.ChangeEvent<HTMLInputElement>
const files = []
if (event.target.files?.length) {
for (let i = 0; i < event.target.files?.length; i++) {
files.push(event.target.files[i])
}
}
setFieldValue('attachments', files)
}}
onDrop={(e: React.DragEvent): void => {
console.log('handle drop', e)
}}
inputRef={fileInputRef}
/>
</FormGroup>

<Button type="submit" disabled={isSubmitting}>
Submit
</Button>
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"peerDependencies": {
"react": "^16.x || ^17.x",
"react-dom": "^16.x || ^17.x",
"uswds": "2.8.0"
"uswds": "2.8.1"
},
"devDependencies": {
"@babel/core": "^7.10.5",
Expand Down Expand Up @@ -126,7 +126,7 @@
"ts-jest": "^26.1.2",
"typescript": "^3.8.0",
"url-loader": "^4.0.0",
"uswds": "2.8.0",
"uswds": "2.8.1",
"webpack": "^4.41.0",
"webpack-cli": "^4.0.0"
},
Expand Down
144 changes: 144 additions & 0 deletions src/components/forms/FileInput/FileInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { useState, useRef } from 'react'

import { FileInput } from './FileInput'
import { FormGroup } from '../FormGroup/FormGroup'
import { Label } from '../Label/Label'
import { ErrorMessage } from '../ErrorMessage/ErrorMessage'

export default {
title: 'Components/Form controls/File input',
component: FileInput,
argTypes: {
onChange: { action: 'changed' },
onDrop: { action: 'dropped' },
},
parameters: {
docs: {
description: {
component: `
### USWDS 2.0 FileInput component
Source: https://designsystem.digital.gov/components/form-controls/#file-input
`,
},
},
},
}

export const singleFileInput = (): React.ReactElement => (
<FormGroup>
<Label htmlFor="file-input-single">Input accepts a single file</Label>
<FileInput id="file-input-single" name="file-input-single" />
</FormGroup>
)

export const acceptTextAndPDF = (): React.ReactElement => (
<FormGroup>
<Label htmlFor="file-input-specific">
Input accepts only specific file types
</Label>
<span className="usa-hint" id="file-input-specific-hint">
Select PDF or TXT files
</span>
<FileInput
id="file-input-specific"
name="file-input-specific"
accept=".pdf,.txt"
aria-describedby="file-input-specific-hint"
multiple
/>
</FormGroup>
)

export const acceptImages = (): React.ReactElement => (
<FormGroup>
<Label htmlFor="file-input-wildcard">Input accepts any kind of image</Label>
<span className="usa-hint" id="file-input-wildcard-hint">
Select any type of image format
</span>
<FileInput
id="file-input-wildcard"
name="file-input-wildcard"
accept="image/*"
aria-describedby="file-input-wildcard-hint"
multiple
/>
</FormGroup>
)

export const multipleFilesInput = (): React.ReactElement => (
<FormGroup>
<Label htmlFor="file-input-multiple">Input accepts multiple files</Label>
<span className="usa-hint" id="file-input-multiple-hint">
Select one or more files
</span>
<FileInput
id="file-input-multiple"
name="file-input-multiple"
aria-describedby="file-input-multiple-hint"
multiple
/>
</FormGroup>
)

export const withError = (): React.ReactElement => (
<div style={{ marginLeft: '1.25em' }}>
<FormGroup error>
<Label htmlFor="file-input-multiple" error>
Input has an error
</Label>
<span className="usa-hint" id="file-input-error-hint">
Select any valid file
</span>
<ErrorMessage id="file-input-error-alert">
Display a helpful error message
</ErrorMessage>
<FileInput
id="file-input-error"
name="file-input-error"
aria-describedby="file-input-error-hint file-input-error-alert"
/>
</FormGroup>
</div>
)

export const disabled = (): React.ReactElement => (
<FormGroup>
<Label htmlFor="file-input-disabled">Input in a disabled state</Label>
<FileInput id="file-input-disabled" name="file-input-disabled" disabled />
</FormGroup>
)

export const withCustomHandlers = (argTypes): React.ReactElement => {
const [files, setFiles] = useState<FileList>()
const fileInputRef = useRef<HTMLInputElement>()

const handleChange = (e: React.ChangeEvent): void => {
argTypes.onChange(e)
setFiles(fileInputRef.current.files)
}

const fileList = []
for (let i = 0; i < files?.length; i++) {
fileList.push(<li key={`file_${i}`}>{files[i].name}</li>)
}

return (
<>
<FormGroup>
<Label htmlFor="file-input-async">
Input implements custom handlers
</Label>
<FileInput
id="file-input-async"
name="file-input-async"
multiple
onChange={handleChange}
onDrop={argTypes.onDrop}
inputRef={fileInputRef}
/>
</FormGroup>
<p>{files?.length || 0} files added:</p>
<ul>{fileList}</ul>
</>
)
}
Loading