Browse Source

Update file select box implementation

Improve UI of FileSelectBox.
pull/1/head
TheoryOfNekomata 1 year ago
parent
commit
6d4382ae9d
4 changed files with 240 additions and 175 deletions
  1. +2
    -1
      packages/web/categories/blob/react/package.json
  2. +224
    -10
      packages/web/categories/blob/react/src/components/FileSelectBox/index.tsx
  3. +4
    -0
      packages/web/categories/blob/react/yarn.lock
  4. +10
    -164
      packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx

+ 2
- 1
packages/web/categories/blob/react/package.json View File

@@ -13,7 +13,8 @@
"pridepack"
],
"dependencies": {
"@tesseract-design/web-base-blob": "link:../../../base/blob"
"@tesseract-design/web-base-blob": "link:../../../base/blob",
"@tesseract-design/web-base-button": "link:../../../base/button"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",


+ 224
- 10
packages/web/categories/blob/react/src/components/FileSelectBox/index.tsx View File

@@ -1,4 +1,9 @@
import * as React from 'react';
import * as ButtonBase from '@tesseract-design/web-base-button';

interface FileWithPreview extends File {
previewUrl?: string;
}

export interface FileButtonProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style' | 'label' | 'list'> {
/**
@@ -33,6 +38,9 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
block = false,
enhanced = false,
hiddenLabel = false,
multiple = false,
onChange,
disabled = false,
className: _className,
placeholder: _placeholder,
as: _as,
@@ -40,44 +48,250 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
}: FileButtonProps,
forwardedRef,
) => {
const [isEnhanced, setIsEnhanced] = React.useState(false);
const [selectedFiles, setSelectedFiles] = React.useState([] as Partial<FileWithPreview>[]);
const defaultRef = React.useRef<HTMLInputElement>(null);
const ref = forwardedRef ?? defaultRef;

const handleAddFile = async (fileList: FileList) => {
const files = Array.from(fileList);
const fileResult = await Promise.all(
files.map((f) => new Promise<Partial<FileWithPreview>>((resolve, reject) => {
if (f.type.startsWith('image/')) {
const reader = new FileReader();
reader.addEventListener('error', () => {
reject();
});

reader.addEventListener('load', (loadEvent) => {
const target = loadEvent.target as FileReader;
resolve({
...f,
name: f.name,
type: f.type,
size: f.size,
lastModified: f.lastModified,
previewUrl: target.result as string,
});
});

reader.readAsDataURL(f);
return;
}

resolve(f);
}))
);

setSelectedFiles(fileResult);
}

const addFile: React.ChangeEventHandler<HTMLInputElement> = async (e) => {
if (!enhanced) {
onChange?.(e);
return;
}

const currentTarget = e.currentTarget;
const fileList = currentTarget.files as FileList;
await handleAddFile(fileList);
onChange?.(e);
};

const deleteFiles: React.MouseEventHandler<HTMLButtonElement> = () => {
if (typeof ref === 'object' && ref.current) {
ref.current.value = '';
setSelectedFiles([]);
}
};

const cancelEvent = (e: React.DragEvent) => {
e.stopPropagation();
e.preventDefault();
}

const handleDropZone: React.DragEventHandler<HTMLDivElement> = async (e) => {
cancelEvent(e);
const { dataTransfer } = e;
if (typeof ref === 'object' && ref.current) {
const { files } = dataTransfer;
ref.current.files = files;
await handleAddFile(files);
ref.current.dispatchEvent(new Event('change'));
}
}

React.useEffect(() => {
setIsEnhanced(enhanced);
}, [enhanced]);

return (
<div>
<label>
<div
className="rounded relative min-h-[300px] overflow-hidden"
onDragEnter={cancelEvent}
onDragOver={cancelEvent}
onDrop={handleDropZone}
data-testid="root"
>
<label
className="block absolute top-0 left-0 w-full h-full cursor-pointer"
data-testid="clickArea"
>
<input
{...etcProps}
disabled={disabled}
ref={ref}
type="file"
className={`${enhanced ? 'sr-only' : ''}`}
onChange={addFile}
multiple={multiple}
data-testid="input"
/>
</label>
{
border && (
<span
data-testid="border"
className={
ButtonBase.Border({
size: ButtonBase.ButtonSize.MEDIUM,
border: true,
variant: ButtonBase.ButtonVariant.OUTLINE,
disabled,
compact: false,
menuItem: false,
block: true,
})
}
/>
)
}
{
label && !hiddenLabel && (
label
&& !hiddenLabel
&& (
<div
data-testid="label"
className="absolute top-0 left-0"
>
{label}
</div>
)
}
{hint && (
<div
data-testid="hint"
>
{
selectedFiles.length < 1
&& isEnhanced
&& hint
&& (
<div
data-testid="hint"
className="absolute top-0 left-0 w-full h-full pointer-events-none box-border overflow-hidden pt-4"
>
{hint}
<div className="flex items-center justify-center w-full h-full">
{hint}
</div>
</div>
</div>
)}
)
}
{
selectedFiles.length > 0
&& isEnhanced
&& (
<>
<div className={`absolute top-0 left-0 w-full h-full pointer-events-none pb-12 box-border overflow-hidden ${multiple ? 'pt-8' : 'pt-4'}`}>
<div className={`pointer-events-auto w-full h-full overflow-auto ${multiple ? '' : 'pt-4 box-border'}`}>
<div
className={`w-full px-4 box-border ${multiple ? 'grid gap-4 grid-cols-3 pb-4' : 'h-full'}`}
>
{selectedFiles.map((f, i) => (
<div data-testid="selectedFileItem" key={i} className={`${!multiple ? 'h-full flex gap-4 w-full' : 'w-full aspect-square'}`}>
<div className={`h-full ${multiple ? 'w-full' : 'w-1/3 flex-shrink-0'}`}>
{
f.previewUrl
&& (
<img
className="block w-full h-full object-center object-cover"
src={f.previewUrl}
alt={f.name}
data-testid="preview"
/>
)
}
</div>
{
!multiple
&& (
<dl className="w-2/3 flex-shrink-0 m-0" data-testid="infoBox">
<div className="w-full">
<dt className="sr-only">
Name
</dt>
<dd className="m-0 w-full text-ellipsis overflow-hidden">
{f.name}
</dd>
</div>
<div className="w-full">
<dt className="sr-only">
Size
</dt>
<dd className="m-0 w-full text-ellipsis overflow-hidden">
{f.size}
</dd>
</div>
<div className="w-full">
<dt className="sr-only">
Type
</dt>
<dd className="m-0 w-full text-ellipsis overflow-hidden">
{f.type}
</dd>
</div>
</dl>
)
}
</div>
))}
</div>
</div>
</div>
<div className="pointer-events-none absolute bottom-0 left-0 w-full text-center h-12 box-border flex">
<div className="w-0 flex-auto flex flex-col items-center justify-center h-full">
<span
className={ButtonBase.Button({
size: ButtonBase.ButtonSize.MEDIUM,
border: false,
block: true,
variant: ButtonBase.ButtonVariant.OUTLINE,
disabled,
compact: false,
menuItem: false,
})}
>
Reselect Files
</span>
</div>
<div className="pointer-events-auto w-0 flex-auto flex flex-col items-center justify-center h-full">
<button
data-testid="clear"
type="button"
onClick={deleteFiles}
className={ButtonBase.Button({
size: ButtonBase.ButtonSize.MEDIUM,
border: false,
block: true,
variant: ButtonBase.ButtonVariant.OUTLINE,
disabled,
compact: false,
menuItem: false,
})}
>
{multiple ? 'Clear Files' : 'Delete File'}
</button>
</div>
</div>
</>
)
}
</div>
);
}


+ 4
- 0
packages/web/categories/blob/react/yarn.lock View File

@@ -532,6 +532,10 @@
version "0.0.0"
uid ""
"@tesseract-design/web-base-button@link:../../../base/button":
version "0.0.0"
uid ""
"@testing-library/dom@^8.5.0":
version "8.20.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.0.tgz#914aa862cef0f5e89b98cc48e3445c4c921010f6"


+ 10
- 164
packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx View File

@@ -1,172 +1,18 @@
import {NextPage} from 'next';
import * as React from 'react';

interface FileWithPreview extends File {
previewUrl?: string;
}

const BlobReact = {
FileSelectBox: ({
border = false,
label = '',
hiddenLabel = false,
hint = '',
multiple = false,
block = false,
enhanced = false,
onChange = undefined as (React.ChangeEventHandler<HTMLInputElement> | undefined),
}) => {
const [isEnhanced, setIsEnhanced] = React.useState(false);
const [selectedFiles, setSelectedFiles] = React.useState([] as FileWithPreview[]);

const addFile: React.ChangeEventHandler<HTMLInputElement> = async (e) => {
if (!enhanced) {
onChange?.(e);
return;
}

const currentTarget = e.currentTarget;
const fileList = currentTarget.files as FileList;
const files = Array.from(fileList);
const fileResult = await Promise.all(
files.map((f) => new Promise<FileWithPreview>((resolve, reject) => {
if (f.type.startsWith('image/')) {
const reader = new FileReader();
reader.addEventListener('error', (e) => {
reject();
});

reader.addEventListener('load', (loadEvent) => {
const target = loadEvent.target as FileReader;
resolve({
...f,
name: f.name,
type: f.type,
size: f.size,
lastModified: f.lastModified,
previewUrl: target.result as string,
});
});

reader.readAsDataURL(f);
return;
}

resolve(f);
}))
);

setSelectedFiles(fileResult);
onChange?.(e);
};

React.useEffect(() => {
setIsEnhanced(enhanced);
}, [enhanced]);

return (
<div className="rounded relative min-h-[200px]">
<label className="block absolute top-0 left-0 w-full h-full">
<input
type="file"
className={`${enhanced ? 'sr-only' : ''}`}
onChange={addFile}
/>
</label>
{
border && (
<span
data-testid="border"
className="pointer-events-none border-solid box-border border-2 rounded-[inherit] absolute top-0 left-0 w-full h-full block"
/>
)
}
{
label && !hiddenLabel && (
<div
data-testid="label"
className="absolute top-0 left-0"
>
{label}
</div>
)
}
{
hint
&& (
<div
data-testid="hint"
>
<div
>
{hint}
</div>
</div>
)
}
{
selectedFiles.length > 0
&& (
<div className="relative w-full h-full pt-4 pointer-events-none">
<div className="pointer-events-auto w-full h-full">
<div
className={`grid gap-4 p-4 h-full ${multiple ? 'grid-cols-3' : ''}`}
>
{selectedFiles.map((f, i) => (
<div key={i} className={`${!multiple ? 'h-full flex gap-4' : ''}`}>
<div className="h-full w-1/3 flex-shrink-0">
{
f.previewUrl
&& (
<img className="w-full h-full object-center object-cover" src={f.previewUrl} alt={f.name} />
)
}
</div>
<dl className="w-2/3 flex-shrink-0">
<div>
<dt>
Name
</dt>
<dd>
{f.name}
</dd>
</div>
<div>
<dt>
Size
</dt>
<dd>
{f.size}
</dd>
</div>
<div>
<dt>
Type
</dt>
<dd>
{f.type}
</dd>
</div>
</dl>
</div>
))}
</div>
</div>
</div>
)
}
</div>
);
}
}
import * as BlobReact from '@tesseract-design/web-blob-react';

const BlobPage: NextPage = () => {
return (
<BlobReact.FileSelectBox
border
enhanced
label="vro"
/>
<div className="z-10 pb-12">
<BlobReact.FileSelectBox
border
enhanced
label="vro"
hint="Select any files here"
multiple
/>
</div>
)
}



Loading…
Cancel
Save