From 36790cab5e0c9e4d461ad37c33ec027b5d0c2dd0 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Wed, 11 May 2022 18:35:53 +0300 Subject: [PATCH] feat: file drag input component --- package-lock.json | 17 ++++ package.json | 1 + .../Inputs/FilesInput/DropInput.jsx | 60 +++++++++++++ .../Inputs/FilesInput/FileInput.stories.tsx | 15 +++- .../Inputs/FilesInput/FilesDropInput.tsx | 84 +++++++++++++++++++ .../Posts/Components/Comments/types.ts | 3 +- 6 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 src/Components/Inputs/FilesInput/DropInput.jsx create mode 100644 src/Components/Inputs/FilesInput/FilesDropInput.tsx diff --git a/package-lock.json b/package-lock.json index f0bf095..fe26739 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-datepicker": "^4.7.0", "react-dom": "^18.0.0", + "react-file-drop": "^3.1.4", "react-hook-form": "^7.30.0", "react-icons": "^4.3.1", "react-loader-spinner": "^6.0.0-0", @@ -58899,6 +58900,14 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "node_modules/react-file-drop": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-file-drop/-/react-file-drop-3.1.4.tgz", + "integrity": "sha512-83633H9CK/IoBXBl77qBS36K0pGb5sQn6c1rc54nxWYkAbM/zo+xuxFnwUDib4Yh+xVmWBZeTAnY5ZSN2PmUCQ==", + "dependencies": { + "prop-types": "^15.7.2" + } + }, "node_modules/react-helmet-async": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", @@ -111144,6 +111153,14 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "react-file-drop": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-file-drop/-/react-file-drop-3.1.4.tgz", + "integrity": "sha512-83633H9CK/IoBXBl77qBS36K0pGb5sQn6c1rc54nxWYkAbM/zo+xuxFnwUDib4Yh+xVmWBZeTAnY5ZSN2PmUCQ==", + "requires": { + "prop-types": "^15.7.2" + } + }, "react-helmet-async": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", diff --git a/package.json b/package.json index cf9094f..76bfc50 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "react-copy-to-clipboard": "^5.1.0", "react-datepicker": "^4.7.0", "react-dom": "^18.0.0", + "react-file-drop": "^3.1.4", "react-hook-form": "^7.30.0", "react-icons": "^4.3.1", "react-loader-spinner": "^6.0.0-0", diff --git a/src/Components/Inputs/FilesInput/DropInput.jsx b/src/Components/Inputs/FilesInput/DropInput.jsx new file mode 100644 index 0000000..64ac918 --- /dev/null +++ b/src/Components/Inputs/FilesInput/DropInput.jsx @@ -0,0 +1,60 @@ +import { useToggle } from "@react-hookz/web"; +import React from "react"; +import { FileDrop } from "react-file-drop"; + +export default function DropInput({ + value: files, + onChange, + emptyContent, + draggingContent, + hasFilesContent, + height, + multiple = false, + allowedType = "*", + classes = {}, +}) { + const [isDragging, toggleDrag] = useToggle(false); + const fileInputRef = React.useRef(null); + + const onAddFiles = (_files) => { + onChange(_files); + // do something with your files... + }; + + const uploadClick = () => { + fileInputRef.current.click(); + }; + + const status = isDragging ? "dragging" : files ? "has-files" : "empty"; + + return ( +
+ onAddFiles(files)} + onTargetClick={uploadClick} + onFrameDragEnter={() => toggleDrag(true)} + onFrameDragLeave={() => toggleDrag(false)} + onFrameDrop={() => toggleDrag(false)} + className={`h-full cursor-pointer`} + targetClassName={`h-full ${classes.wrapper}`} + draggingOverFrameClassName={`${classes.dragging}`} + > + {status === "dragging" && draggingContent} + {status === "empty" && emptyContent} + {status === "has-files" && hasFilesContent} + + onAddFiles(e.target.files)} + ref={fileInputRef} + type="file" + className="hidden" + multiple={multiple} + accept={allowedType} + /> +
+ ); +} diff --git a/src/Components/Inputs/FilesInput/FileInput.stories.tsx b/src/Components/Inputs/FilesInput/FileInput.stories.tsx index 97ad927..e56b62c 100644 --- a/src/Components/Inputs/FilesInput/FileInput.stories.tsx +++ b/src/Components/Inputs/FilesInput/FileInput.stories.tsx @@ -3,6 +3,7 @@ import { BsImages } from 'react-icons/bs'; import Button from 'src/Components/Button/Button'; import FilesInput from './FilesInput'; +import FileDropInput from './FilesDropInput'; export default { title: 'Shared/Files Input', @@ -13,12 +14,18 @@ export default { const Template: ComponentStory = (args) => -export const Default = Template.bind({}); -Default.args = { +export const DefaultButton = Template.bind({}); +DefaultButton.args = { } -export const CustomButton = Template.bind({}); -CustomButton.args = { +export const CustomizedButton = Template.bind({}); +CustomizedButton.args = { multiple: true, uploadBtn: +} + +const DropTemplate: ComponentStory = (args) =>
+export const DropZoneInput = DropTemplate.bind({}); +DropZoneInput.args = { + onChange: () => { }, } \ No newline at end of file diff --git a/src/Components/Inputs/FilesInput/FilesDropInput.tsx b/src/Components/Inputs/FilesInput/FilesDropInput.tsx new file mode 100644 index 0000000..1972c67 --- /dev/null +++ b/src/Components/Inputs/FilesInput/FilesDropInput.tsx @@ -0,0 +1,84 @@ +import { FaImage } from "react-icons/fa"; +import { UnionToObjectKeys } from "src/utils/types/utils"; +import DropInput from "./DropInput"; + + +type Props = { + height?: number + multiple?: boolean; + value?: File[] | string[] | string; + max?: number; + onBlur?: () => void; + onChange?: (files: (File | string)[] | null) => void + uploadBtn?: JSX.Element + uploadText?: string; + allowedType?: 'images'; + classes?: Partial<{ + wrapper: string, + dragging: string + }> +} + +const fileAccept: UnionToObjectKeys = { + images: ".png, .jpg, .jpeg" +} as const; + +const fileUrlToObject = async (url: string, fileName: string = 'filename') => { + const res = await fetch(url); + const contentType = res.headers.get('content-type') as string; + const blob = await res.blob() + const file = new File([blob], fileName, { contentType } as any) + return file +} + +export default function FilesInput({ + height = 200, + multiple, + value, + max = 3, + onBlur, + onChange, + allowedType = 'images', + classes, + ...props +}: Props) { + + + const wrapperClasses = classes?.wrapper ?? 'bg-primary-50 p-32 border border-primary-500 rounded-8 text-center flex flex-col justify-center items-center' + const draggingClasses = classes?.dragging ?? '!bg-primary-500 !text-white' + + return ( + + ) +} + +const defaultEmptyContent = ( + <> +
+ {" "} + Drop your files here +
+

+ or {" "} +

+ +); + +const defaultDraggingContent =

Drop your files here ⬇⬇⬇

; + +const defaultHasFilesContent = ( +

Files Uploaded Successfully!!

+); \ No newline at end of file diff --git a/src/features/Posts/Components/Comments/types.ts b/src/features/Posts/Components/Comments/types.ts index b6e84cb..1d563f1 100644 --- a/src/features/Posts/Components/Comments/types.ts +++ b/src/features/Posts/Components/Comments/types.ts @@ -12,4 +12,5 @@ export interface Comment { export interface CommentWithReplies extends Comment { replies: CommentWithReplies[] -} \ No newline at end of file +} +