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

Use NonIdealState for empty DropZone #586

Merged
merged 4 commits into from
Dec 11, 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
67 changes: 42 additions & 25 deletions src/components/drop-zone/DropZone.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { NonIdealState, Button, Colors } from '@blueprintjs/core';
import type { IconName } from '@blueprintjs/icons';
import styled from '@emotion/styled';
import { rgba } from 'polished';
import { CSSProperties, MouseEventHandler, useCallback, useMemo } from 'react';
import { FileError, FileRejection, useDropzone } from 'react-dropzone';
import { FaCloudUploadAlt } from 'react-icons/fa';

export interface DropZoneProps {
color?: string;
borderColor?: string;
onDrop?: <T extends File>(
acceptedFiles: T[],
rejectedFiles?: FileRejection[],
) => void;
fileValidator?: <T extends File>(file: T) => FileError | FileError[] | null;
emptyText?: string;
emptyIcon?: IconName;
emptyTitle?: string;
emptyDescription?: string;
emptyButtonText?: string;
emptyButtonIcon?: IconName;
}

const DropzoneRoot = styled.div`
Expand All @@ -22,43 +27,36 @@ const DropzoneRoot = styled.div`

interface DropzoneColorProps {
borderColor: CSSProperties['borderColor'];
color: CSSProperties['color'];
}

const DropzoneDragActive = styled.div<DropzoneColorProps>`
font-size: 1.5em;
font-weight: 600;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
opacity: 0.7;
background-color: white;
background-color: rgb(255, 255, 255, 0.7);
border: 5px dashed;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;

border-color: ${({ borderColor }) => borderColor};
color: ${({ color }) => color};
border-color: ${({ borderColor }) =>
borderColor ? rgba(borderColor, 0.7) : ''};
`;

const DropzoneEmpty = styled.div<DropzoneColorProps>`
font-size: 1.5em;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
:hover .dropzone-button {
background-color: ${rgba(Colors.BLUE3, 0.15)};
}
width: 100%;
height: 100%;
padding: 1em;
border: 5px dashed;
cursor: pointer;

border-color: ${({ borderColor }) => borderColor};
color: ${({ color }) => color};
`;

export function DropZone(props: DropZoneProps) {
Expand All @@ -77,17 +75,24 @@ function DropZoneContent(
props: DropZoneProps & {
children?: JSX.Element | null;
onClick?: MouseEventHandler<HTMLDivElement>;
emptyText?: string;
emptyIcon?: IconName;
emptyTitle?: string;
emptyDescription?: string;
emptyButtonText?: string;
emptyButtonIcon?: IconName;
},
) {
const {
color = 'black',
borderColor = 'gray',
borderColor = Colors.GRAY3,
children = null,
onDrop,
emptyText = 'Click or drag and drop to add data.',
onClick,
fileValidator,
emptyIcon = 'import',
emptyTitle = 'No data loaded',
emptyDescription = 'You can load data by drag-and-dropping files here',
emptyButtonText = 'Select files',
emptyButtonIcon = 'plus',
} = props;

const hasChildren = children !== null;
Expand Down Expand Up @@ -115,13 +120,25 @@ function DropZoneContent(
<DropzoneRoot {...getRootProps(getPropsOptions)}>
{children}
{isDragActive ? (
<DropzoneDragActive borderColor={borderColor} color={color}>
<FaCloudUploadAlt size={70} />
<p>Drop the files here.</p>
<DropzoneDragActive borderColor={borderColor}>
<NonIdealState icon="cloud-upload" title="Drop the files here" />
</DropzoneDragActive>
) : !hasChildren ? (
<DropzoneEmpty borderColor={borderColor} color={color}>
{emptyText}
<DropzoneEmpty borderColor={borderColor}>
<NonIdealState
icon={emptyIcon}
title={emptyTitle}
description={emptyDescription}
action={
<Button
className="dropzone-button"
outlined
text={emptyButtonText}
icon={emptyButtonIcon}
intent="primary"
/>
}
/>
</DropzoneEmpty>
) : null}
<input {...getInputProps()} />
Expand Down
35 changes: 23 additions & 12 deletions stories/components/drop-zone.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { IconName } from '@blueprintjs/icons';
import { useState } from 'react';
import type { FileWithPath } from 'react-dropzone';

Expand All @@ -13,7 +14,11 @@ export default {
};

interface DropZoneStoryProps {
color: string;
emptyIcon?: IconName;
emptyTitle?: string;
emptyDescription?: string;
emptyButtonText?: string;
emptyButtonIcon?: IconName;
}

function fileValidator(file: File) {
Expand All @@ -27,11 +32,12 @@ function fileValidator(file: File) {
}

export function Control({
color,
emptyText,
}: DropZoneStoryProps & {
emptyText?: string;
}) {
emptyIcon,
emptyTitle,
emptyDescription,
emptyButtonText,
emptyButtonIcon,
}: DropZoneStoryProps) {
const [files, setFiles] = useState<FileWithPath[]>([]);
return (
<div
Expand All @@ -48,11 +54,14 @@ export function Control({
>
<DropZone
fileValidator={fileValidator}
color={color}
emptyText={emptyText}
onDrop={(files: FileWithPath[]) => {
setFiles(files);
}}
emptyIcon={emptyIcon}
emptyTitle={emptyTitle}
emptyDescription={emptyDescription}
emptyButtonText={emptyButtonText}
emptyButtonIcon={emptyButtonIcon}
/>
</div>
{files.length > 0 && (
Expand All @@ -74,17 +83,19 @@ export function Control({
}

Control.args = {
color: 'black',
emptyText: 'Click or drag and drop to add data.',
emptyIcon: 'import',
emptyTitle: 'No data loaded',
emptyDescription: 'You can load data by drag-and-dropping files here',
emptyButtonText: 'Select files',
emptyButtonIcon: 'plus',
};

export function DropZoneContainerControl({ color }: DropZoneStoryProps) {
export function DropZoneContainerControl() {
const [files, setFiles] = useState<FileWithPath[]>([]);
return (
<div style={{ height: 500 }}>
<DropZoneContainer
fileValidator={fileValidator}
color={color}
onDrop={(files: FileWithPath[]) => {
setFiles(files);
}}
Expand Down
Loading