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

DropZone: Convert to TypeScript #43962

Merged
merged 5 commits into from
Sep 12, 2022
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
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
- `RadioControl`: Clean up styles to use less custom CSS ([#43868](https://github.com/WordPress/gutenberg/pull/43868)).
- Remove unused `normalizeArrowKey` utility function ([#43640](https://github.com/WordPress/gutenberg/pull/43640/)).
- `SearchControl`: Convert to TypeScript ([#43871](https://github.com/WordPress/gutenberg/pull/43871)).
- `DropZone`: Convert to TypeScript ([#43962](https://github.com/WordPress/gutenberg/pull/43962)).
- `ToggleGroupControl`: Rename `__experimentalIsIconGroup` prop to `__experimentalIsBorderless` ([#43771](https://github.com/WordPress/gutenberg/pull/43771/)).
- Refactor `FocalPointPicker` to function component ([#39168](https://github.com/WordPress/gutenberg/pull/39168)).
- `Guide`: use `code` instead of `keyCode` for keyboard events ([#43604](https://github.com/WordPress/gutenberg/pull/43604/)).
Expand Down
6 changes: 3 additions & 3 deletions packages/components/src/drop-zone/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DropZone

`DropZone` is a Component creating a drop zone area taking the full size of its parent element. It supports dropping files, HTML content or any other HTML drop event.
`DropZone` is a component creating a drop zone area taking the full size of its parent element. It supports dropping files, HTML content or any other HTML drop event.

## Usage

Expand Down Expand Up @@ -30,7 +30,7 @@ The component accepts the following props:

### className

A CSS `class` to be _appended_ after the default `components-drop-zone` class.
A CSS `class` to give to the wrapper element.

- Type: `String`
- Default: `undefined`
Expand All @@ -52,7 +52,7 @@ The function is called when dropping a file into the `DropZone`. It receives an

### onHTMLDrop

The function is called when dropping a file into the `DropZone`. It receives the HTML being dropped as an argument.
The function is called when dropping HTML into the `DropZone`. It receives the HTML being dropped as an argument.

- Type: `Function`
- Required: No
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,51 @@ import {
__unstableMotion as motion,
__unstableAnimatePresence as AnimatePresence,
} from '../animation';
import type { DropType, DropZoneProps } from './types';
import type { WordPressComponentProps } from '../ui/context';

export default function DropZoneComponent( {
/**
* `DropZone` is a component creating a drop zone area taking the full size of its parent element. It supports dropping files, HTML content or any other HTML drop event.
*
* ```jsx
* import { DropZone } from '@wordpress/components';
* import { useState } from '@wordpress/element';
*
* const MyDropZone = () => {
* const [ hasDropped, setHasDropped ] = useState( false );
*
* return (
* <div>
* { hasDropped ? 'Dropped!' : 'Drop something here' }
* <DropZone
* onFilesDrop={ () => setHasDropped( true ) }
* onHTMLDrop={ () => setHasDropped( true ) }
* onDrop={ () => setHasDropped( true ) }
* />
* </div>
* );
* }
* ```
*/
export function DropZoneComponent( {
className,
label,
onFilesDrop,
onHTMLDrop,
onDrop,
} ) {
const [ isDraggingOverDocument, setIsDraggingOverDocument ] = useState();
const [ isDraggingOverElement, setIsDraggingOverElement ] = useState();
const [ type, setType ] = useState();
...restProps
}: WordPressComponentProps< DropZoneProps, 'div', false > ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this component be polymorphic?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be potentially, but not in the current form because the div used in the code is a raw div element and not a polymorphic one (like View or styled.div).

const [ isDraggingOverDocument, setIsDraggingOverDocument ] =
useState< boolean >();
const [ isDraggingOverElement, setIsDraggingOverElement ] =
useState< boolean >();
const [ type, setType ] = useState< DropType >();
const ref = useDropZone( {
onDrop( event ) {
const files = getFilesFromDataTransfer( event.dataTransfer );
const html = event.dataTransfer.getData( 'text/html' );
const files = event.dataTransfer
? getFilesFromDataTransfer( event.dataTransfer )
: [];
const html = event.dataTransfer?.getData( 'text/html' );

/**
* From Windows Chrome 96, the `event.dataTransfer` returns both file object and HTML.
Expand All @@ -53,19 +83,22 @@ export default function DropZoneComponent( {
onDragStart( event ) {
setIsDraggingOverDocument( true );

let _type = 'default';
let _type: DropType = 'default';

/**
* From Windows Chrome 96, the `event.dataTransfer` returns both file object and HTML.
* The order of the checks is important to recognise the HTML drop.
*/
if ( event.dataTransfer.types.includes( 'text/html' ) ) {
if ( event.dataTransfer?.types.includes( 'text/html' ) ) {
_type = 'html';
} else if (
// Check for the types because sometimes the files themselves
// are only available on drop.
event.dataTransfer.types.includes( 'Files' ) ||
getFilesFromDataTransfer( event.dataTransfer ).length > 0
event.dataTransfer?.types.includes( 'Files' ) ||
( event.dataTransfer
? getFilesFromDataTransfer( event.dataTransfer )
: []
).length > 0
) {
_type = 'file';
}
Expand All @@ -74,7 +107,7 @@ export default function DropZoneComponent( {
},
onDragEnd() {
setIsDraggingOverDocument( false );
setType();
setType( undefined );
},
onDragEnter() {
setIsDraggingOverElement( true );
Expand Down Expand Up @@ -149,7 +182,7 @@ export default function DropZoneComponent( {
} );

return (
<div ref={ ref } className={ classes }>
<div { ...restProps } ref={ ref } className={ classes }>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Runtime change: Pass through rest props.

{ disableMotion ? (
children
) : (
Expand All @@ -158,3 +191,5 @@ export default function DropZoneComponent( {
</div>
);
}

export default DropZoneComponent;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
*/
import deprecated from '@wordpress/deprecated';

export default function DropZoneProvider( { children } ) {
export default function DropZoneProvider( {
children,
}: {
children: React.ReactNode;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not going to extract this to the types.ts file because this is deprecated.

} ) {
deprecated( 'wp.components.DropZoneProvider', {
since: '5.8',
hint: 'wp.component.DropZone no longer needs a provider. wp.components.DropZoneProvider is safe to remove from your code.',
Expand Down
30 changes: 30 additions & 0 deletions packages/components/src/drop-zone/stories/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';
/**
* Internal dependencies
*/
import DropZone from '..';

const meta: ComponentMeta< typeof DropZone > = {
component: DropZone,
title: 'Components/DropZone',
parameters: {
actions: { argTypesRegex: '^on.*' },
controls: { expanded: true },
docs: { source: { state: 'open' } },
},
};
export default meta;

const Template: ComponentStory< typeof DropZone > = ( props ) => {
return (
<div style={ { background: 'lightgray', padding: 16 } }>
Drop something here
<DropZone { ...props } />
</div>
);
};

export const Default = Template.bind( {} );
29 changes: 29 additions & 0 deletions packages/components/src/drop-zone/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type DropType = 'file' | 'html' | 'default';

export type DropZoneProps = {
/**
* A CSS `class` to give to the wrapper element.
*/
className?: string;
/**
* A string to be shown within the drop zone area.
*
* @default `__( 'Drop files to upload' )`
*/
label?: string;
/**
* The function is generic drop handler called if the `onFilesDrop` or `onHTMLDrop` are not called.
* It receives the drop `event` object as an argument.
*/
onDrop?: ( event: DragEvent ) => void;
/**
* The function is called when dropping a file into the `DropZone`.
* It receives an array of dropped files as an argument.
*/
onFilesDrop?: ( files: File[] ) => void;
/**
* The function is called when dropping HTML into the `DropZone`.
* It receives the HTML being dropped as an argument.
*/
onHTMLDrop?: ( html: string ) => void;
};
1 change: 0 additions & 1 deletion packages/components/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"src/custom-gradient-picker",
"src/custom-select-control",
"src/dimension-control",
"src/drop-zone",
"src/duotone-picker",
"src/focal-point-picker",
"src/font-size-picker",
Expand Down
16 changes: 8 additions & 8 deletions packages/compose/src/hooks/use-drop-zone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ function useFreshRef( value ) {
/**
* A hook to facilitate drag and drop handling.
*
* @param {Object} props Named parameters.
* @param {boolean} props.isDisabled Whether or not to disable the drop zone.
* @param {(e: DragEvent) => void} props.onDragStart Called when dragging has started.
* @param {(e: DragEvent) => void} props.onDragEnter Called when the zone is entered.
* @param {(e: DragEvent) => void} props.onDragOver Called when the zone is moved within.
* @param {(e: DragEvent) => void} props.onDragLeave Called when the zone is left.
* @param {(e: MouseEvent) => void} props.onDragEnd Called when dragging has ended.
* @param {(e: DragEvent) => void} props.onDrop Called when dropping in the zone.
* @param {Object} props Named parameters.
* @param {boolean} [props.isDisabled] Whether or not to disable the drop zone.
* @param {(e: DragEvent) => void} [props.onDragStart] Called when dragging has started.
* @param {(e: DragEvent) => void} [props.onDragEnter] Called when the zone is entered.
* @param {(e: DragEvent) => void} [props.onDragOver] Called when the zone is moved within.
* @param {(e: DragEvent) => void} [props.onDragLeave] Called when the zone is left.
* @param {(e: MouseEvent) => void} [props.onDragEnd] Called when dragging has ended.
* @param {(e: DragEvent) => void} [props.onDrop] Called when dropping in the zone.
Comment on lines +36 to +43
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking these all as optional, based on actual use in the block-editor package like here and here.

Code looks safe as well.

*
* @return {import('react').RefCallback<HTMLElement>} Ref callback to be passed to the drop zone element.
*/
Expand Down