revamp template
This commit is contained in:
181
src/components/image-input/ImageInput.tsx
Normal file
181
src/components/image-input/ImageInput.tsx
Normal file
@ -0,0 +1,181 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { ChangeEvent, DragEvent, FC, useCallback, useRef, useState } from 'react';
|
||||
import { getAcceptTypeString, getListFiles, openFileDialog } from './utils';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface IImageInputFile {
|
||||
dataURL?: string;
|
||||
file?: File;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
type TImageInputFiles = IImageInputFile[];
|
||||
|
||||
interface IImageInputProps {
|
||||
value: TImageInputFiles;
|
||||
onChange: (value: TImageInputFiles, addUpdatedIndex?: number[]) => void;
|
||||
children?: (props: IImageInputExport) => React.ReactNode;
|
||||
multiple?: boolean;
|
||||
maxNumber?: number;
|
||||
acceptType?: string[];
|
||||
dataURLKey?: string;
|
||||
inputProps?: React.HTMLProps<HTMLInputElement>;
|
||||
}
|
||||
|
||||
interface IImageInputExport {
|
||||
fileList: TImageInputFiles;
|
||||
onImageUpload: () => void;
|
||||
onImageRemoveAll: () => void;
|
||||
onImageUpdate: (index: number) => void;
|
||||
onImageRemove: (index: number) => void;
|
||||
isDragging: boolean;
|
||||
dragProps: {
|
||||
onDrop: (e: any) => void;
|
||||
onDragEnter: (e: any) => void;
|
||||
onDragLeave: (e: any) => void;
|
||||
onDragOver: (e: any) => void;
|
||||
onDragStart: (e: any) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_NULL_INDEX = -1;
|
||||
export const DEFAULT_DATA_URL_KEY = 'dataURL';
|
||||
|
||||
const ImageInput: FC<IImageInputProps> = ({
|
||||
value,
|
||||
acceptType,
|
||||
inputProps,
|
||||
multiple,
|
||||
children,
|
||||
onChange
|
||||
}) => {
|
||||
const inValue = value || [];
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [keyUpdate, setKeyUpdate] = useState<number>(DEFAULT_NULL_INDEX);
|
||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||
|
||||
const onImageRemoveAll = useCallback((): void => {
|
||||
onChange?.([]);
|
||||
}, [onChange]);
|
||||
|
||||
const handleClickInput = useCallback(() => {
|
||||
openFileDialog(inputRef);
|
||||
}, [inputRef]);
|
||||
|
||||
const onImageUpload = useCallback((): void => {
|
||||
handleClickInput();
|
||||
}, [handleClickInput]);
|
||||
|
||||
const onInputChange = async (e: ChangeEvent<HTMLInputElement>): Promise<void> => {
|
||||
await handleChange(e.target.files);
|
||||
if (inputRef.current) inputRef.current.value = '';
|
||||
};
|
||||
|
||||
const handleChange = async (files: FileList | null) => {
|
||||
if (!files) return;
|
||||
const fileList = await getListFiles(files, DEFAULT_DATA_URL_KEY);
|
||||
if (!fileList.length) return;
|
||||
let updatedFileList: TImageInputFiles;
|
||||
const updatedIndexes: number[] = [];
|
||||
if (keyUpdate > DEFAULT_NULL_INDEX) {
|
||||
const [firstFile] = fileList;
|
||||
updatedFileList = [...inValue];
|
||||
updatedFileList[keyUpdate] = firstFile;
|
||||
updatedIndexes.push(keyUpdate);
|
||||
} else if (multiple) {
|
||||
updatedFileList = [...inValue, ...fileList];
|
||||
for (let i = inValue.length; i < updatedFileList.length; i += 1) {
|
||||
updatedIndexes.push(i);
|
||||
}
|
||||
} else {
|
||||
updatedFileList = [fileList[0]];
|
||||
updatedIndexes.push(0);
|
||||
}
|
||||
onChange?.(updatedFileList, updatedIndexes);
|
||||
};
|
||||
|
||||
const onImageRemove = (index: number | number[]): void => {
|
||||
const updatedList = [...inValue];
|
||||
if (Array.isArray(index)) {
|
||||
index.forEach((i) => {
|
||||
updatedList.splice(i, 1);
|
||||
});
|
||||
} else {
|
||||
updatedList.splice(index, 1);
|
||||
}
|
||||
onChange?.(updatedList);
|
||||
};
|
||||
|
||||
const onImageUpdate = (index: number): void => {
|
||||
setKeyUpdate(index);
|
||||
handleClickInput();
|
||||
};
|
||||
|
||||
const handleDrag = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragIn = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
|
||||
setIsDragging(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOut = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsDragging(false);
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
handleChange(e.dataTransfer.files);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragStart = (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.dataTransfer.clearData();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept={getAcceptTypeString(acceptType)}
|
||||
multiple={multiple}
|
||||
onChange={(e) => {
|
||||
onInputChange(e);
|
||||
}}
|
||||
style={{ display: 'none' }}
|
||||
{...inputProps}
|
||||
/>
|
||||
{children?.({
|
||||
fileList: inValue,
|
||||
onImageUpload,
|
||||
onImageRemove,
|
||||
onImageUpdate,
|
||||
onImageRemoveAll,
|
||||
dragProps: {
|
||||
onDrop: handleDrop,
|
||||
onDragEnter: handleDragIn,
|
||||
onDragLeave: handleDragOut,
|
||||
onDragOver: handleDrag,
|
||||
onDragStart: handleDragStart
|
||||
},
|
||||
isDragging
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export { ImageInput, type IImageInputProps, type TImageInputFiles, type IImageInputFile };
|
||||
2
src/components/image-input/index.ts
Normal file
2
src/components/image-input/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './ImageInput';
|
||||
export * from './utils';
|
||||
52
src/components/image-input/utils.ts
Normal file
52
src/components/image-input/utils.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { MutableRefObject } from 'react';
|
||||
import { TImageInputFiles } from './';
|
||||
|
||||
export const openFileDialog = (inputRef: MutableRefObject<HTMLInputElement | null>): void => {
|
||||
if (!inputRef.current) {
|
||||
return;
|
||||
}
|
||||
inputRef.current.click();
|
||||
};
|
||||
|
||||
export const getAcceptTypeString = (acceptType?: string[], allowNonImageType?: boolean) => {
|
||||
if (acceptType?.length) return acceptType.map((item) => `.${item}`).join(', ');
|
||||
if (allowNonImageType) return '';
|
||||
return 'image/*';
|
||||
};
|
||||
|
||||
export const getBase64 = async (file: File): Promise<string> => {
|
||||
const reader = new FileReader();
|
||||
return await new Promise((resolve) => {
|
||||
reader.addEventListener('load', () => {
|
||||
resolve(String(reader.result));
|
||||
});
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
export const getImage = async (file: File): Promise<HTMLImageElement> => {
|
||||
const image = new Image();
|
||||
return await new Promise((resolve) => {
|
||||
image.addEventListener('load', () => {
|
||||
resolve(image);
|
||||
});
|
||||
image.src = URL.createObjectURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
export const getListFiles = async (
|
||||
files: FileList,
|
||||
dataURLKey: string
|
||||
): Promise<TImageInputFiles> => {
|
||||
const promiseFiles: Array<Promise<string>> = [];
|
||||
for (let i = 0; i < files.length; i += 1) {
|
||||
promiseFiles.push(getBase64(files[i]));
|
||||
}
|
||||
return await Promise.all(promiseFiles).then((fileListBase64: string[]) => {
|
||||
const fileList: TImageInputFiles = fileListBase64.map((base64, index) => ({
|
||||
[dataURLKey]: base64,
|
||||
file: files[index]
|
||||
}));
|
||||
return fileList;
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user