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

FormikFileDropInput #1073

Merged
merged 3 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/small-coins-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@navikt/sif-common-formik-ds': minor
---

Legge til ny komponent for filopplasting med drag and drop - FormikFileDropInput
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ interface Props extends TypedFormInputValidationProps<any, ValidationError> {
legend?: string;
buttonLabel: string;
apiEndpoint: ApiEndpoint;

onFileInputClick?: () => void;
onErrorUploadingAttachments: (files: File[]) => void;
onUnauthorizedOrForbiddenUpload: () => void;
Expand Down
1 change: 1 addition & 0 deletions packages/sif-common-formik-ds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"classnames": "2.3.2",
"i18n-iso-countries": "7.6.0",
"lodash": "4.17.21",
"react-dropzone": "14.2.3",
"react-fast-compare": "3.2.2",
"uuid": "9.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { TextFieldProps } from '@navikt/ds-react';
import React from 'react';
import { Accept } from 'react-dropzone';
import { ArrayHelpers, Field, FieldArray, FieldProps } from 'formik';
import { FormError, TypedFormInputValidationProps } from '../../types';
import { getErrorPropForFormikInput } from '../../utils/typedFormErrorUtils';
import { TypedFormikFormContext } from '../typed-formik-form/TypedFormikForm';
import FileDropInput from './file-drop-input/FileDropInput';

interface OwnProps<FieldName> {
name: FieldName;
legend: string;
description?: React.ReactNode;
buttonLabel: string;
acceptLabel?: string;
rejectLabel?: string;
accept?: Accept;
multiple?: boolean;
error?: FormError;
onFilesSelect: (files: File[], arrayHelpers: ArrayHelpers) => void;
onClick?: () => void;
}

export type FormikFileDropInputProps<FieldName> = OwnProps<FieldName> & Omit<TextFieldProps, 'label' | 'accept'>;

function FormikFileDropInput<FieldName, ErrorType>({
name,
legend,
description,
buttonLabel,
acceptLabel,
rejectLabel,
accept,
multiple = true,
validate,
onFilesSelect,
error,
onClick,
}: FormikFileDropInputProps<FieldName> & TypedFormInputValidationProps<FieldName, ErrorType>) {
const context = React.useContext(TypedFormikFormContext);

return (
<FieldArray
name={`${name}`}
render={(arrayHelpers) => (
<Field validate={validate ? (value: any) => validate(value, name) : undefined} name={name}>
{({ field, form }: FieldProps) => {
return (
<FileDropInput
id={field.name}
name={field.name}
legend={legend}
description={description}
buttonLabel={buttonLabel}
acceptLabel={acceptLabel}
rejectLabel={rejectLabel}
onClick={onClick}
onFilesSelect={(files) => onFilesSelect(files, arrayHelpers)}
multiple={multiple}
accept={accept}
error={getErrorPropForFormikInput({ field, form, context, error })}
/>
);
}}
</Field>
)}
/>
);
}

export default FormikFileDropInput;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { BodyShort } from '@navikt/ds-react';
import React, { useMemo } from 'react';
import { Accept, useDropzone } from 'react-dropzone';
import { UploadIcon } from '@navikt/aksel-icons';
import { FormError } from '../../../types';
import bemUtils from '../../../utils/bemUtils';
import SkjemagruppeQuestion from '../../helpers/skjemagruppe-question/SkjemagruppeQuestion';
import './fileDropInput.scss';

interface Props {
id: string;
legend: string;
description?: React.ReactNode;
buttonLabel: string;
acceptLabel?: string;
rejectLabel?: string;
name: string;
multiple?: boolean;
accept?: Accept;
error?: FormError;
onFilesSelect: (files: File[]) => void;
onClick?: () => void;
}

const bem = bemUtils('fileDropInput');

const FileDropInput: React.FunctionComponent<Props> = (props) => {
const { id, name, buttonLabel, error, description, multiple, legend, rejectLabel, acceptLabel, accept } = props;
const inputId = `${id}-input`;

const onDrop = (files: File[]) => {
props.onFilesSelect(files);
};

const { getRootProps, getInputProps, isFocused, isDragActive, isDragAccept, isDragReject } = useDropzone({
accept,
multiple,
onDrop,
});

const className = useMemo(
() => `${bem.block}
${bem.modifierConditional('withError', error !== undefined) || ''}
${bem.modifierConditional('dragActive', isDragActive) || ''}
${bem.modifierConditional('dragAccept', isDragAccept) || ''}
${bem.modifierConditional('dragReject', isDragReject) || ''}
${bem.modifierConditional('focused', isFocused) || ''}`,
[isDragActive, isDragAccept, isDragReject, error, isFocused]
);

const getLabel = () => {
if (rejectLabel && isDragActive && isDragReject) {
return rejectLabel;
}
if (acceptLabel && isDragActive && isDragAccept) {
return acceptLabel;
}

return buttonLabel;
};
return (
<SkjemagruppeQuestion error={error} legend={legend} description={description}>
<div {...getRootProps({ className })}>
<div className={bem.element('icon')}>
<UploadIcon />
</div>
<BodyShort as="div" className={bem.element('label')}>
{getLabel()}
</BodyShort>
<input id={inputId} name={name} {...getInputProps()} />
</div>
</SkjemagruppeQuestion>
);
};

export default FileDropInput;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.fileDropInput {
cursor: pointer;
display: flex;
flex-direction: row;
padding: 1rem 2rem;
background-color: white;
width: 100%;
min-height: 4rem;
justify-content: center;
align-items: center;
padding-left: 0;
border-style: dashed;
border-width: 2px;
border-radius: 8px;
border-color: #c6c2bf;
&--withError {
border: 2px dashed #ba3a26 !important;
}
&--focused {
border-color: transparent;
box-shadow: 0 0 0 3px;
}
&--dragAccept {
border-color: #06893a;
}
&--dragReject {
border-color: #ba3a26;
}
&:hover {
border-color: #0067c5;
}
&__icon {
line-height: 1rem;
display: inline-block;
margin-right: 0.5rem;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { getErrorPropForFormikInput } from '../../utils/typedFormErrorUtils';
import { TypedFormikFormContext } from '../typed-formik-form/TypedFormikForm';
import FileInput from './file-input/FileInput';

/**
* Denne er deprecated.
* Bruk heller FileDropInput - denne ligger er fortsatt
* her pga. bakoverkompabilitet på accept prop.
* */

interface OwnProps<FieldName> {
name: FieldName;
legend: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import FormikTimeInput, { FormikTimeInputProps } from './formik-time-input/Formi
import FormikYesOrNoQuestion, { FormikYesOrNoQuestionProps } from './formik-yes-or-no-question/FormikYesOrNoQuestion';
import TypedFormikForm, { TypedFormikFormProps } from './typed-formik-form/TypedFormikForm';
import TypedFormikWrapper, { TypedFormikWrapperProps } from './typed-formik-wrapper/TypedFormikWrapper';
import FormikFileDropInput, { FormikFileDropInputProps } from './formik-file-drop-input/FormikFileDropInput';

export interface TypedFormComponents<FieldName, FormValues, ErrorType> {
Checkbox: (props: FormikCheckboxProps<FieldName, ErrorType>) => JSX.Element;
Expand All @@ -31,6 +32,7 @@ export interface TypedFormComponents<FieldName, FormValues, ErrorType> {
DateIntervalPicker: (props: DateIntervalPickerProps<FieldName, ErrorType>) => JSX.Element;
DateRangePicker: (props: FormikDateRangePickerProps<FieldName, ErrorType>) => JSX.Element;
FileInput: (props: FormikFileInputProps<FieldName>) => JSX.Element;
FileDropInput: (props: FormikFileDropInputProps<FieldName>) => JSX.Element;
Form: (props: TypedFormikFormProps<FormValues, ErrorType>) => JSX.Element;
FormikWrapper: (props: TypedFormikWrapperProps<FormValues>) => JSX.Element;
TextField: (props: FormikTextFieldProps<FieldName, ErrorType>) => JSX.Element;
Expand Down Expand Up @@ -70,6 +72,9 @@ export function getTypedFormComponents<FieldName, FormValues, ErrorType = string
DateRangePicker: (props: FormikDateRangePickerProps<FieldName, ErrorType>) => (
<FormikDateRangePicker<FieldName, ErrorType> {...props} />
),
FileDropInput: (props: FormikFileDropInputProps<FieldName>) => (
<FormikFileDropInput<FieldName, ErrorType> {...props} />
),
FileInput: (props: FormikFileInputProps<FieldName>) => <FormikFileInput<FieldName, ErrorType> {...props} />,
Form: (props: TypedFormikFormProps<FormValues, ErrorType>) => <TypedFormikForm {...props} />,
FormikWrapper: (props: TypedFormikWrapperProps<FormValues>) => <TypedFormikWrapper {...props} />,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Meta, StoryFn } from '@storybook/react';
import { withFormikWrapper } from '../../decorators/StoryFormikWrapper';
import FormikFileDropInput from '../../../src/components/formik-file-drop-input/FormikFileDropInput';

const meta: Meta<typeof FormikFileDropInput> = {
title: 'Component/FormikFileDropInput',
component: FormikFileDropInput,
decorators: [withFormikWrapper],
};

export default meta;

const Template: StoryFn<typeof FormikFileDropInput> = (args) => (
<>
<FormikFileDropInput {...args} buttonLabel="Last opp fil" />
</>
);

export const Default = Template.bind({});
Default.args = {
label: 'FormikFileDropInput',
name: 'formikCheckbox',
value: 'abc',
onFilesSelect: (files: any) => {
// eslint-disable-next-line no-console
console.log(files);
},
};
Default.parameters = {
formik: {
initialValues: {
formikCheckbox: true,
},
},
};
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
/* eslint-disable no-console */
import { Heading, Panel } from '@navikt/ds-react';
import * as React from 'react';
import '@navikt/ds-datepicker/lib/index.css';
import { useIntl } from 'react-intl';
import { ISODateString } from '@navikt/ds-datepicker/lib/types';
import FormikValidationErrorSummary from '../../../src/components/formik-validation-error-summary/FormikValidationErrorSummary';
import { getTypedFormComponents } from '../../../src/components/getTypedFormComponents';
import { YesOrNo } from '../../../src/types';
import { ValidationError } from '../../../src/validation/types';
import { mockAnimalOptions, MockAnimals } from '../../mock-data';
import { getCheckedValidator, getRequiredFieldValidator } from '../../../src/validation';
import FormikValidationErrorSummary from '../../../src/components/formik-validation-error-summary/FormikValidationErrorSummary';
import getIntlFormErrorHandler from '../../../src/validation/intlFormErrorHandler';
import { useIntl } from 'react-intl';
import ExampleListAndDialog from './ExampleListAndDialog';
import { ValidationError } from '../../../src/validation/types';
import FormBlock from '../../components/form-block/FormBlock';
import { mockAnimalOptions, MockAnimals } from '../../mock-data';
import ExampleListAndDialog from './ExampleListAndDialog';
import '@navikt/ds-datepicker/lib/index.css';
import { Accept } from 'react-dropzone';

enum Fields {
checked = 'checked',
Expand Down Expand Up @@ -53,6 +54,10 @@ const Form = getTypedFormComponents<Fields, FieldValues, ValidationError>();

const ExampleForm: React.FunctionComponent = () => {
const intl = useIntl();
const accept: Accept = {
'image/png': ['.png'],
'text/html': ['.html', '.htm'],
};
return (
<Panel border={true} style={{ margin: '1rem' }}>
<Heading size="medium">Example form</Heading>
Expand Down Expand Up @@ -105,7 +110,17 @@ const ExampleForm: React.FunctionComponent = () => {
name={Fields.attachments}
buttonLabel="Choose picture"
onFilesSelect={(evt) => console.log(evt)}
accept={'.png'}
accept={'.pdf'}
description="Dette er en liten tekst som trengs her"
/>
</FormBlock>
<FormBlock>
<Form.FileDropInput
legend="Velg filer"
name={Fields.attachments}
buttonLabel="Choose picture"
onFilesSelect={(evt) => console.log(evt)}
accept={accept}
description="Dette er en liten tekst som trengs her"
/>
</FormBlock>
Expand Down
30 changes: 30 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4139,6 +4139,7 @@ __metadata:
react: 18.2.0
react-day-picker: 8.8.0
react-dom: 18.2.0
react-dropzone: 14.2.3
react-fast-compare: 3.2.2
react-intl: 6.4.4
react-responsive: 9.0.2
Expand Down Expand Up @@ -9854,6 +9855,13 @@ __metadata:
languageName: node
linkType: hard

"attr-accept@npm:^2.2.2":
version: 2.2.2
resolution: "attr-accept@npm:2.2.2"
checksum: 496f7249354ab53e522510c1dc8f67a1887382187adde4dc205507d2f014836a247073b05e9d9ea51e2e9c7f71b0d2aa21730af80efa9af2d68303e5f0565c4d
languageName: node
linkType: hard

"autoprefixer@npm:10.4.15, autoprefixer@npm:^10.4.13":
version: 10.4.15
resolution: "autoprefixer@npm:10.4.15"
Expand Down Expand Up @@ -14719,6 +14727,15 @@ __metadata:
languageName: node
linkType: hard

"file-selector@npm:^0.6.0":
version: 0.6.0
resolution: "file-selector@npm:0.6.0"
dependencies:
tslib: ^2.4.0
checksum: 7d051b6e5d793f3c6e2ab287ba5e7c2c6a0971bccc9d56e044c8047ba483e18f60fc0b5771c951dc707c0d15f4f36ccb4f1f1aaf385d21ec8f7700dadf8325ba
languageName: node
linkType: hard

"file-system-cache@npm:2.3.0":
version: 2.3.0
resolution: "file-system-cache@npm:2.3.0"
Expand Down Expand Up @@ -23122,6 +23139,19 @@ __metadata:
languageName: node
linkType: hard

"react-dropzone@npm:14.2.3":
version: 14.2.3
resolution: "react-dropzone@npm:14.2.3"
dependencies:
attr-accept: ^2.2.2
file-selector: ^0.6.0
prop-types: ^15.8.1
peerDependencies:
react: ">= 16.8 || 18.0.0"
checksum: 174b744d5ca898cf3d84ec1aeb6cef5211c446697e45dc8ece8287a03d291f8d07253206d5a1247ef156fd385d65e7de666d4d5c2986020b8543b8f2434e8b40
languageName: node
linkType: hard

"react-element-to-jsx-string@npm:^15.0.0":
version: 15.0.0
resolution: "react-element-to-jsx-string@npm:15.0.0"
Expand Down
Loading