Commit b1f8e17a authored by tom's avatar tom

simple drag-and-drop

parent 2c43d825
import { Text, Button, Box, chakra } from '@chakra-ui/react'; import { Text, Button, Box, chakra, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { ControllerRenderProps } from 'react-hook-form'; import type { ControllerRenderProps } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import type { FormFields } from '../types'; import type { FormFields } from '../types';
import DragAndDropArea from 'ui/shared/forms/DragAndDropArea';
import FileInput from 'ui/shared/forms/FileInput'; import FileInput from 'ui/shared/forms/FileInput';
import FileSnippet from 'ui/shared/forms/FileSnippet'; import FileSnippet from 'ui/shared/forms/FileSnippet';
...@@ -55,14 +56,25 @@ const ContractVerificationFieldSources = ({ accept, multiple, title, className, ...@@ -55,14 +56,25 @@ const ContractVerificationFieldSources = ({ accept, multiple, title, className,
const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'sources'>}) => ( const renderControl = React.useCallback(({ field }: {field: ControllerRenderProps<FormFields, 'sources'>}) => (
<> <>
<FileInput<FormFields, 'sources'> accept={ accept } multiple={ multiple } field={ field }> <FileInput<FormFields, 'sources'> accept={ accept } multiple={ multiple } field={ field }>
<Button { ({ onChange }) => (
variant="outline" <Flex
size="sm" flexDir="column"
display={ field.value && field.value.length > 0 && !multiple ? 'none' : 'block' } alignItems="flex-start"
mb={ field.value && field.value.length > 0 ? 2 : 0 } rowGap={ 2 }
> w="100%"
Upload file{ multiple ? 's' : '' } display={ field.value && field.value.length > 0 && !multiple ? 'none' : 'block' }
</Button> mb={ field.value && field.value.length > 0 ? 2 : 0 }
>
<Button
variant="outline"
size="sm"
mb={ 2 }
>
Upload file{ multiple ? 's' : '' }
</Button>
<DragAndDropArea onDrop={ onChange }/>
</Flex>
) }
</FileInput> </FileInput>
{ field.value && field.value.length > 0 && renderFiles(field.value) } { field.value && field.value.length > 0 && renderFiles(field.value) }
{ error && ( { error && (
......
import { Box } from '@chakra-ui/react';
import type { DragEvent } from 'react';
import React from 'react';
interface Props {
onDrop: (files: Array<File>) => void;
}
const DragAndDropArea = ({ onDrop }: Props) => {
const [ isDragOver, setIsDragOver ] = React.useState(false);
const handleDrop = React.useCallback(async(event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
const files = Array.from(event.dataTransfer.files);
onDrop(files);
setIsDragOver(false);
}, [ onDrop ]);
const handleDragOver = React.useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
}, []);
const handleDragEnter = React.useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
setIsDragOver(true);
}, []);
const handleDragLeave = React.useCallback((event: DragEvent<HTMLDivElement>) => {
event.preventDefault();
setIsDragOver(false);
}, []);
return (
<Box
w="100%"
h="200px"
bgColor="lightpink"
opacity={ isDragOver ? 0.8 : 1 }
onDrop={ handleDrop }
onDragOver={ handleDragOver }
onDragEnter={ handleDragEnter }
onDragLeave={ handleDragLeave }
/>
);
};
export default React.memo(DragAndDropArea);
...@@ -3,8 +3,12 @@ import type { ChangeEvent } from 'react'; ...@@ -3,8 +3,12 @@ import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form'; import type { ControllerRenderProps, FieldValues, Path } from 'react-hook-form';
interface InjectedProps {
onChange: (files: Array<File>) => void;
}
interface Props<V extends FieldValues, N extends Path<V>> { interface Props<V extends FieldValues, N extends Path<V>> {
children: React.ReactNode; children: React.ReactNode | ((props: InjectedProps) => React.ReactNode);
field: ControllerRenderProps<V, N>; field: ControllerRenderProps<V, N>;
accept?: string; accept?: string;
multiple?: boolean; multiple?: boolean;
...@@ -13,6 +17,10 @@ interface Props<V extends FieldValues, N extends Path<V>> { ...@@ -13,6 +17,10 @@ interface Props<V extends FieldValues, N extends Path<V>> {
const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ children, accept, multiple, field }: Props<Values, Names>) => { const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ children, accept, multiple, field }: Props<Values, Names>) => {
const ref = React.useRef<HTMLInputElement>(null); const ref = React.useRef<HTMLInputElement>(null);
const onChange = React.useCallback((files: Array<File>) => {
field.onChange([ ...(field.value || []), ...files ]);
}, [ field ]);
const handleInputChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleInputChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
const fileList = event.target.files; const fileList = event.target.files;
if (!fileList) { if (!fileList) {
...@@ -20,9 +28,9 @@ const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ chi ...@@ -20,9 +28,9 @@ const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ chi
} }
const files = Array.from(fileList); const files = Array.from(fileList);
field.onChange([ ...(field.value || []), ...files ]); onChange(files);
field.onBlur(); field.onBlur();
}, [ field ]); }, [ onChange, field ]);
const handleClick = React.useCallback(() => { const handleClick = React.useCallback(() => {
ref.current?.click(); ref.current?.click();
...@@ -32,6 +40,12 @@ const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ chi ...@@ -32,6 +40,12 @@ const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ chi
field.onBlur(); field.onBlur();
}, [ field ]); }, [ field ]);
const injectedProps = React.useMemo(() => ({
onChange,
}), [ onChange ]);
const content = typeof children === 'function' ? children(injectedProps) : children;
return ( return (
<InputGroup onClick={ handleClick } onBlur={ handleInputBlur }> <InputGroup onClick={ handleClick } onBlur={ handleInputBlur }>
<VisuallyHiddenInput <VisuallyHiddenInput
...@@ -42,7 +56,7 @@ const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ chi ...@@ -42,7 +56,7 @@ const FileInput = <Values extends FieldValues, Names extends Path<Values>>({ chi
multiple={ multiple } multiple={ multiple }
name={ field.name } name={ field.name }
/> />
{ children } { content }
</InputGroup> </InputGroup>
); );
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment