Skip to content

Commit

Permalink
feat: Adds two versions of dropzone
Browse files Browse the repository at this point in the history
  • Loading branch information
Katie George committed Oct 15, 2024
1 parent 7ac1398 commit 02361cc
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 80 deletions.
2 changes: 1 addition & 1 deletion pages/file-token-group/permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import PermutationsView from '../utils/permutations-view';
import ScreenshotArea from '../utils/screenshot-area';

const file1 = new File([new Blob(['demo content 1'])], 'demo file 1', { type: 'image/*' });
const file2 = new File([new Blob(['demo content 2'])], 'demo file 2', { type: 'image/*' });
const file2 = new File([new Blob(['demo content 2'])], 'demo file 2 long name here test', { type: 'image/*' });

const permutations = createPermutations<Omit<FileTokenGroupProps, 'onDismiss' | 'i18nStrings'>>([
{
Expand Down
139 changes: 95 additions & 44 deletions pages/file-upload/deconstructed.page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useEffect, useRef, useState } from 'react';
import clsx from 'clsx';

import { Box, Checkbox, FileInput, FileTokenGroup, FileUploadProps, FormField, Header, PromptInput } from '~components';
import SpaceBetween from '~components/space-between';
Expand All @@ -10,6 +11,53 @@ import { i18nStrings } from './shared';
import { useDropzoneVisible } from './use-dropzone-visible';
import { validateContractFiles } from './validations';

import styles from './styles.scss';

interface DropzoneProps {
onChange: (files: File[]) => void;
children: React.ReactNode;
}

export function Dropzone({ onChange, children }: DropzoneProps) {
const [isDropzoneHovered, setDropzoneHovered] = useState(false);

const onDragOver = (event: React.DragEvent) => {
event.preventDefault();

if (event.dataTransfer) {
setDropzoneHovered(true);
event.dataTransfer.dropEffect = 'copy';
}
};

const onDragLeave = (event: React.DragEvent) => {
event.preventDefault();
setDropzoneHovered(false);

if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'none';
}
};

const onDrop = (event: React.DragEvent) => {
event.preventDefault();
setDropzoneHovered(false);

onChange(Array.from(event.dataTransfer.files));
};

return (
<div
className={clsx(styles.dropzone, isDropzoneHovered && styles['dropzone-hovered'])}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
onDrop={onDrop}
>
<span>{children}</span>
</div>
);
}

export default function FileUploadScenarioStandalone() {
const contractsRef = useRef<FileUploadProps.Ref>(null);
const [textareaValue, setTextareaValue] = useState('');
Expand Down Expand Up @@ -44,27 +92,30 @@ export default function FileUploadScenarioStandalone() {

return (
<Box margin="xl">
{isDropzoneVisible ? (
'dropping files'
) : (
<SpaceBetween size="xl">
<Header variant="h1">File upload: deconstructed</Header>

<Checkbox checked={acceptMultiple} onChange={(event: any) => setAcceptMultiple(event.detail.checked)}>
Accept multiple files
</Checkbox>
<Checkbox checked={verticalAlign} onChange={(event: any) => setVerticalAlign(event.detail.checked)}>
Vertical alignment
</Checkbox>

<FormField>
<SpaceBetween size="xl">
<Header variant="h1">File upload: deconstructed</Header>

<Checkbox checked={acceptMultiple} onChange={(event: any) => setAcceptMultiple(event.detail.checked)}>
Accept multiple files
</Checkbox>
<Checkbox checked={verticalAlign} onChange={(event: any) => setVerticalAlign(event.detail.checked)}>
Vertical alignment
</Checkbox>

<FormField>
{isDropzoneVisible ? (
<Dropzone onChange={handleFilesChange}>dropping files</Dropzone>
) : (
<PromptInput
ariaLabel="Chat input"
actionButtonIconName="send"
actionButtonAriaLabel="Submit prompt"
value={textareaValue}
onChange={(event: any) => setTextareaValue(event.detail.value)}
onAction={(event: any) => window.alert(`Submitted the following: ${event.detail.value}`)}
// onFilesChange={(event: any) => handleFilesChange(event.detail.value)}
// files={formState.files}
// multiple={acceptMultiple}
placeholder="Ask a question"
maxRows={4}
disableSecondaryActionsPaddings={true}
Expand Down Expand Up @@ -99,38 +150,38 @@ export default function FileUploadScenarioStandalone() {
) : undefined
}
/>
</FormField>

<FormField
label={acceptMultiple ? 'Contracts' : 'Contract'}
description={acceptMultiple ? 'Upload your contract with all amendments' : 'Upload your contract'}
>
<FileInput
variant="icon"
ref={contractsRef}
multiple={acceptMultiple}
value={formState.files}
onChange={(event: any) => handleFilesChange(event.detail.value)}
// accept="application/pdf, image/*"
i18nStrings={i18nStrings}
/>
</FormField>

<FileTokenGroup
alignment={verticalAlign ? 'vertical' : 'horizontal'}
items={formState.files.map(file => ({
file,
loading: formState.status === 'uploading',
errorText: file.size > 5000000 ? 'File size cannot exceed 5MB' : undefined,
}))}
showFileLastModified={true}
showFileSize={true}
showFileThumbnail={true}
)}
</FormField>

<FormField
label={acceptMultiple ? 'Contracts' : 'Contract'}
description={acceptMultiple ? 'Upload your contract with all amendments' : 'Upload your contract'}
>
<FileInput
variant="icon"
ref={contractsRef}
multiple={acceptMultiple}
value={formState.files}
onChange={(event: any) => handleFilesChange(event.detail.value)}
// accept="application/pdf, image/*"
i18nStrings={i18nStrings}
onDismiss={onDismiss}
/>
</SpaceBetween>
)}
</FormField>

<FileTokenGroup
alignment={verticalAlign ? 'vertical' : 'horizontal'}
items={formState.files.map(file => ({
file,
loading: formState.status === 'uploading',
errorText: file.size > 5000000 ? 'File size cannot exceed 5MB' : undefined,
}))}
// showFileLastModified={true}
// showFileSize={true}
showFileThumbnail={true}
i18nStrings={i18nStrings}
onDismiss={onDismiss}
/>
</SpaceBetween>
</Box>
);
}
27 changes: 27 additions & 0 deletions pages/file-upload/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

@use '~design-tokens' as awsui;

.dropzone {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: awsui.$space-static-xxs;
padding-block: awsui.$space-static-l;
padding-inline: awsui.$space-static-l;
border-start-start-radius: 2px;
border-start-end-radius: 2px;
border-end-start-radius: 2px;
border-end-end-radius: 2px;
font-weight: awsui.$font-weight-heading-m;
color: awsui.$color-text-button-normal-active;
background-color: awsui.$color-background-cell-shaded;
&-hovered {
color: awsui.$color-text-button-normal-active;
background-color: awsui.$color-background-button-normal-hover;
}
}
5 changes: 3 additions & 2 deletions src/file-token-group/file-token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function InternalFileToken({

const containerRef = React.useRef<HTMLDivElement>(null);
const [showTooltip, setShowTooltip] = useState(false);
const [imageError, setImageError] = useState(false);

return (
<div
Expand All @@ -94,11 +95,11 @@ function InternalFileToken({
warningIconAriaLabel={i18nStrings.warningIconAriaLabel}
disabled={disabled}
alignment={alignment}
groupContainsImage={groupContainsImage && showFileThumbnail && alignment === 'horizontal'}
groupContainsImage={groupContainsImage && showFileThumbnail && alignment === 'horizontal' && !imageError}
data-index={index}
>
<InternalBox className={styles['file-option']}>
{showFileThumbnail && isImage && <FileOptionThumbnail file={file} />}
{showFileThumbnail && isImage && <FileOptionThumbnail file={file} setHasError={setImageError} />}

<div
className={clsx(styles['file-option-metadata'], {
Expand Down
13 changes: 11 additions & 2 deletions src/file-token-group/thumbnail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import styles from './styles.css.js';

interface FileOptionThumbnailProps {
file: File;
setHasError: (hasError: boolean) => void;
}

export function FileOptionThumbnail({ file }: FileOptionThumbnailProps) {
export function FileOptionThumbnail({ file, setHasError }: FileOptionThumbnailProps) {
const [imageSrc, setImageSrc] = useState('');

useEffect(() => {
Expand All @@ -26,7 +27,15 @@ export function FileOptionThumbnail({ file }: FileOptionThumbnailProps) {

return (
<div className={styles['file-option-thumbnail']} aria-hidden={true}>
<img className={styles['file-option-thumbnail-image']} alt={file.name} src={imageSrc} />
<img
className={styles['file-option-thumbnail-image']}
alt={file.name}
src={imageSrc}
onError={({ currentTarget }) => {
setHasError(true);
currentTarget.onerror = null; // prevents looping

Check warning on line 36 in src/file-token-group/thumbnail.tsx

View check run for this annotation

Codecov / codecov/patch

src/file-token-group/thumbnail.tsx#L34-L36

Added lines #L34 - L36 were not covered by tests
}}
/>
</div>
);
}
2 changes: 1 addition & 1 deletion src/file-upload/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import InternalBox from '../box/internal';
import { ButtonProps } from '../button/interfaces';
import { useFormFieldContext } from '../contexts/form-field';
import InternalFileInput from '../file-input/internal';
import * as defaultFormatters from '../file-token-group/default-formatters';
import InternalFileToken from '../file-token-group/file-token';
import InternalFileTokenGroup from '../file-token-group/internal';
import { ConstraintText, FormFieldError, FormFieldWarning } from '../form-field/internal';
Expand All @@ -22,7 +23,6 @@ import { useMergeRefs } from '../internal/hooks/use-merge-refs';
import { useUniqueId } from '../internal/hooks/use-unique-id';
import { joinStrings } from '../internal/utils/strings';
import InternalSpaceBetween from '../space-between/internal';
import * as defaultFormatters from './default-formatters.js';
import { Dropzone, useDropzoneVisible } from './dropzone';
import { FileUploadProps } from './interfaces';

Expand Down
17 changes: 17 additions & 0 deletions src/prompt-input/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { FileUploadProps } from '../file-upload/interfaces';
import { IconProps } from '../icon/interfaces';
import {
BaseChangeDetail,
Expand Down Expand Up @@ -103,6 +104,22 @@ export interface PromptInputProps
* Determines whether the secondary content area of the input has padding. If true, removes the default padding from the secondary content area.
*/
disableSecondaryContentPaddings?: boolean;

/**
* Called when the user selects new file(s), or removes a file.
* The event `detail` contains the current value of the component.
*/
onFilesChange?: NonCancelableEventHandler<FileUploadProps.ChangeDetail>;

/**
* Files.
*/
files?: File[];

/**
* Specifies the native file input `multiple` attribute to allow users entering more than one file.
*/
multiple?: boolean;
}

export namespace PromptInputProps {
Expand Down
Loading

0 comments on commit 02361cc

Please sign in to comment.