diff --git a/src/Components/Inputs/FileUploadInput/FileUploadInput.stories.tsx b/src/Components/Inputs/FileUploadInput/FileUploadInput.stories.tsx index ba0cf5f..2e5b4dd 100644 --- a/src/Components/Inputs/FileUploadInput/FileUploadInput.stories.tsx +++ b/src/Components/Inputs/FileUploadInput/FileUploadInput.stories.tsx @@ -3,7 +3,7 @@ import { ComponentStory, ComponentMeta } from '@storybook/react'; import FileUploadInput from './FileUploadInput'; export default { - title: 'Shared/Inputs/File Upload Input', + title: 'Shared/Inputs/Files Inputs/Basic', component: FileUploadInput, } as ComponentMeta; diff --git a/src/Components/Inputs/FileUploadInput/FileUploadInput.tsx b/src/Components/Inputs/FileUploadInput/FileUploadInput.tsx index be68418..241a317 100644 --- a/src/Components/Inputs/FileUploadInput/FileUploadInput.tsx +++ b/src/Components/Inputs/FileUploadInput/FileUploadInput.tsx @@ -1,4 +1,4 @@ -import Uploady, { useUploady, useRequestPreSend, UPLOADER_EVENTS } from "@rpldy/uploady"; +import Uploady, { useUploady, useRequestPreSend, UPLOADER_EVENTS, } from "@rpldy/uploady"; import { asUploadButton } from "@rpldy/upload-button"; import Button from "src/Components/Button/Button"; import { fetchUploadUrl } from "./fetch-upload-img-url"; @@ -66,7 +66,6 @@ const UploadBtn = asUploadButton((props: any) => { const DropZone = forwardRef((props, ref) => { const { onClick, ...buttonProps } = props; - useRequestPreSend(async (data) => { const filename = data.items?.[0].file.name ?? '' @@ -95,10 +94,10 @@ const DropZone = forwardRef((props, ref) => { ref={ref} onDragOverClassName={styles.active} extraProps={{ onClick: onZoneClick }} - className={`${styles.zone} border-2 w-full min-h-[200px] rounded-16 flex flex-col justify-center items-center text text-body3`} + className={`${styles.zone} border-2 w-full min-h-[200px] max-w-[600px] rounded-16 flex flex-col justify-center items-center text text-body3 border-dashed`} >
- Drop your files here or + Drop your IMAGES here or
; - -const Template: ComponentStory = (args) => - - -export const DefaultButton = Template.bind({}); -DefaultButton.args = { -} - -export const CustomizedButton = Template.bind({}); -CustomizedButton.args = { - multiple: true, - uploadBtn: -} - -const DropTemplate: ComponentStory = (args) =>
-export const DropZoneInput = DropTemplate.bind({}); -DropZoneInput.args = { - onChange: console.log, -} \ No newline at end of file diff --git a/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.stories.tsx b/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.stories.tsx index 63fda1d..1575707 100644 --- a/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.stories.tsx +++ b/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.stories.tsx @@ -4,7 +4,7 @@ import ScreenshotsInput, { ScreenshotType } from './ScreenshotsInput'; import { WrapForm, WrapFormController } from 'src/utils/storybook/decorators'; export default { - title: 'Shared/Inputs/Screenshots Input', + title: 'Shared/Inputs/Files Inputs/Screenshots', component: ScreenshotsInput, decorators: [ WrapFormController<{ screenshots: Array }>({ diff --git a/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.tsx b/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.tsx index 422f29b..b5bf6c0 100644 --- a/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.tsx +++ b/src/Components/Inputs/ScreenshotsInput/ScreenshotsInput.tsx @@ -39,9 +39,6 @@ export default function ScreenshotsInput(props: Props) { const [uploadingCount, setUploadingCount] = useState(0) - if (!Array.isArray(uploadedFiles)) - throw new Error("screenshots field should be an array"); - const canUploadMore = uploadingCount + uploadedFiles.length < MAX_UPLOAD_COUNT; const placeholdersCount = (MAX_UPLOAD_COUNT - (uploadingCount + uploadedFiles.length + 1)); diff --git a/src/Components/Inputs/SingleImageUploadInput/ImagePreviews.tsx b/src/Components/Inputs/SingleImageUploadInput/ImagePreviews.tsx new file mode 100644 index 0000000..74d4d64 --- /dev/null +++ b/src/Components/Inputs/SingleImageUploadInput/ImagePreviews.tsx @@ -0,0 +1,97 @@ +import UploadPreview, { PreviewComponentProps, PreviewMethods } from '@rpldy/upload-preview' +import { useAbortItem, useItemAbortListener, useItemCancelListener, useItemErrorListener, useItemProgressListener } from '@rpldy/uploady'; +import { useState } from 'react' +import ScreenShotsThumbnail from './ScreenshotThumbnail' + +export default function ImagePreviews() { + return ( + + ) +} + +function CustomImagePreview({ id, url }: PreviewComponentProps) { + + const [progress, setProgress] = useState(0); + const [itemState, setItemState] = useState(STATES.PROGRESS); + + const abortItem = useAbortItem(); + + + useItemProgressListener(item => { + if (item.completed > progress) { + setProgress(() => item.completed); + + if (item.completed === 100) { + setItemState(STATES.DONE) + } else { + setItemState(STATES.PROGRESS) + } + } + }, id); + + + + useItemAbortListener(item => { + setItemState(STATES.CANCELLED); + }, id); + + + useItemCancelListener(item => { + setItemState(STATES.CANCELLED); + }, id); + + useItemErrorListener(item => { + setItemState(STATES.ERROR); + }, id); + + if (itemState === STATES.DONE || itemState === STATES.CANCELLED) + return null + + return { + abortItem(id) + }} + /> + + // return
+ // + //
+ //
+ // {itemState === STATES.PROGRESS && + //
+ // + //
} + // {itemState === STATES.ERROR && + //
+ // Failed... + //
} + // {itemState === STATES.CANCELLED && + //
+ // Cancelled + //
} + //
; +}; + +const STATES = { + PROGRESS: "PROGRESS", + DONE: "DONE", + CANCELLED: "CANCELLED", + ERROR: "ERROR" +}; diff --git a/src/Components/Inputs/SingleImageUploadInput/ScreenshotThumbnail.tsx b/src/Components/Inputs/SingleImageUploadInput/ScreenshotThumbnail.tsx new file mode 100644 index 0000000..0003c43 --- /dev/null +++ b/src/Components/Inputs/SingleImageUploadInput/ScreenshotThumbnail.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import { FaTimes } from 'react-icons/fa'; +import { RotatingLines } from 'react-loader-spinner'; + +interface Props { + url?: string, + isLoading?: boolean; + isError?: boolean; + onCancel?: () => void; + +} + +export default function ScreenshotThumbnail({ url, isLoading, isError, onCancel }: Props) { + + const isEmpty = !url; + + return ( +
+ {!isEmpty && } +
+
+ {isLoading && +
+ +
} + {isError && +
+ Failed... +
} + {!isEmpty && + + } +
+ ) +} diff --git a/src/Components/Inputs/SingleImageUploadInput/SingleImageUploadInput.stories.tsx b/src/Components/Inputs/SingleImageUploadInput/SingleImageUploadInput.stories.tsx new file mode 100644 index 0000000..e2d9061 --- /dev/null +++ b/src/Components/Inputs/SingleImageUploadInput/SingleImageUploadInput.stories.tsx @@ -0,0 +1,118 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import SingleImageUploadInput, { ImageType } from './SingleImageUploadInput'; +import { WrapFormController } from 'src/utils/storybook/decorators'; +import { RotatingLines } from 'react-loader-spinner'; +import { FiCamera, } from 'react-icons/fi'; +import { FaExchangeAlt, FaImage } from 'react-icons/fa'; + +export default { + title: 'Shared/Inputs/Files Inputs/Single Image Upload ', + component: SingleImageUploadInput, + decorators: [ + WrapFormController<{ avatar: ImageType | null }>({ + logValues: true, + name: "avatar", + defaultValues: { + avatar: null + } + })] +} as ComponentMeta; + +const Template: ComponentStory = (args, context) => { + + return + +} + + +export const Avatar = Template.bind({}); +Avatar.args = { + wrapperClass: "inline-block cursor-pointer ", + render: ({ img, isUploading }) =>
+ {img && } + {!img && + <> +

+
+ Add Image +
+ } + {isUploading && +
+ +
+ } +
+} + +export const Thumbnail = Template.bind({}); +Thumbnail.args = { + wrapperClass: "inline-block cursor-pointer ", + render: ({ img, isUploading }) =>
+ {img && } + {!img && + <> +

+
+ Add Image +
+ } + {isUploading && +
+ +
+ } +
+} + +export const Cover = Template.bind({}); +Cover.args = { + wrapperClass: "block cursor-pointer ", + render: ({ img, isUploading }) =>
+ {img && <> + + {!isUploading && + } + } + {!img && + <> +

+
+ Drop a COVER IMAGE here or
Click to browse +
+ } + {isUploading && +
+ +
+ } +
+} + diff --git a/src/Components/Inputs/SingleImageUploadInput/SingleImageUploadInput.tsx b/src/Components/Inputs/SingleImageUploadInput/SingleImageUploadInput.tsx new file mode 100644 index 0000000..f95cd31 --- /dev/null +++ b/src/Components/Inputs/SingleImageUploadInput/SingleImageUploadInput.tsx @@ -0,0 +1,139 @@ +import Uploady, { useRequestPreSend, UPLOADER_EVENTS, useAbortAll } from "@rpldy/uploady"; +import { asUploadButton } from "@rpldy/upload-button"; +// import { fetchUploadUrl } from "./fetch-upload-img-url"; +import UploadDropZone from "@rpldy/upload-drop-zone"; +import { forwardRef, ReactElement, useCallback, useState } from "react"; +import styles from './styles.module.scss' +import { getMockSenderEnhancer } from "@rpldy/mock-sender"; + + + +const mockSenderEnhancer = getMockSenderEnhancer({ + delay: 1500, +}); + + +export interface ImageType { + id: string, + name: string, + url: string; +} + +type RenderPropArgs = { + isUploading?: boolean; + img: ImageType | null, + onAbort: () => void +} + +interface Props { + value: ImageType, + onChange: (new_value: ImageType | null) => void; + wrapperClass?: string; + render: (args: RenderPropArgs) => ReactElement; +} + + +export default function ScreenshotsInput(props: Props) { + + const { value, onChange, render } = props; + + + const [currentlyUploadingItem, setCurrentlyUploadingItem] = useState(null) + + + return ( + { + onChange(null) + + setCurrentlyUploadingItem({ + id: item.id, + url: URL.createObjectURL(item.file), + name: item.file.name, + }) + }, + [UPLOADER_EVENTS.ITEM_FINALIZE]: () => setCurrentlyUploadingItem(null), + [UPLOADER_EVENTS.ITEM_FINISH]: (item) => { + + // Just for mocking purposes + const dataUrl = URL.createObjectURL(item.file); + + const { id, filename, variants } = item?.uploadResponse?.data?.result ?? { + id: Math.random().toString(), + filename: item.file.name, + variants: [ + "", + dataUrl + ] + } + if (id) { + onChange({ id, name: filename, url: variants[1] }) + } + } + }} + > + + + ) +} + + +const DropZone = forwardRef((props, ref) => { + const { onClick, children, renderProps, ...buttonProps } = props; + + + + useRequestPreSend(async (data) => { + + const filename = data.items?.[0].file.name ?? '' + + // const url = await fetchUploadUrl({ filename }); + return { + options: { + destination: { + url: "URL" + } + } + } + }) + + const onZoneClick = useCallback( + (e: any) => { + if (onClick) { + onClick(e); + } + }, + [onClick] + ); + + return + + {renderProps.render({ + img: renderProps.img, + isUploading: renderProps.isUploading, + + })} + +}) + +const DropZoneButton = asUploadButton(DropZone); diff --git a/src/Components/Inputs/SingleImageUploadInput/styles.module.scss b/src/Components/Inputs/SingleImageUploadInput/styles.module.scss new file mode 100644 index 0000000..217ca39 --- /dev/null +++ b/src/Components/Inputs/SingleImageUploadInput/styles.module.scss @@ -0,0 +1,25 @@ +.zone { + background-color: #f2f4f7; + border-color: #e4e7ec; + + .active_content { + display: none; + } + + .idle_content { + display: block; + } + + &.active { + background-color: #b3a0ff; + border-color: #9e88ff; + + .active_content { + display: block; + } + + .idle_content { + display: none; + } + } +}