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

Compose: Add new useCopyToClipboard hook #29643

Merged
merged 2 commits into from
Mar 17, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@ import { castArray, flow, noop } from 'lodash';
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
DropdownMenu,
MenuGroup,
MenuItem,
ClipboardButton,
} from '@wordpress/components';
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { moreVertical } from '@wordpress/icons';

import { Children, cloneElement, useCallback } from '@wordpress/element';
import { serialize } from '@wordpress/blocks';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { useCopyToClipboard } from '@wordpress/compose';

/**
* Internal dependencies
Expand All @@ -35,6 +31,11 @@ const POPOVER_PROPS = {
isAlternate: true,
};

function CopyMenuItem( { blocks, onCopy } ) {
const ref = useCopyToClipboard( () => serialize( blocks ), onCopy );
return <MenuItem ref={ ref }>{ __( 'Copy' ) }</MenuItem>;
}

export function BlockSettingsDropdown( {
clientIds,
__experimentalSelectBlock,
Expand Down Expand Up @@ -112,14 +113,10 @@ export function BlockSettingsDropdown( {
clientId={ firstBlockClientId }
/>
) }
<ClipboardButton
text={ () => serialize( blocks ) }
role="menuitem"
className="components-menu-item__button"
Copy link
Member Author

Choose a reason for hiding this comment

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

Notice how we can use the MenuItem here instead of emulating it.

<CopyMenuItem
blocks={ blocks }
onCopy={ onCopy }
>
{ __( 'Copy' ) }
</ClipboardButton>
/>
{ canDuplicate && (
<MenuItem
onClick={ flow(
Expand Down
18 changes: 12 additions & 6 deletions packages/block-library/src/file/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
ToolbarGroup,
ToolbarButton,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import {
BlockControls,
BlockIcon,
Expand All @@ -23,28 +23,34 @@ import {
useBlockProps,
store as blockEditorStore,
} from '@wordpress/block-editor';
import { useEffect, useState, useRef } from '@wordpress/element';
import { useCopyOnClick } from '@wordpress/compose';
import { useEffect, useState } from '@wordpress/element';
import { useCopyToClipboard } from '@wordpress/compose';
import { __, _x } from '@wordpress/i18n';
import { file as icon } from '@wordpress/icons';
import { store as coreStore } from '@wordpress/core-data';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
*/
import FileBlockInspector from './inspector';

function ClipboardToolbarButton( { text, disabled } ) {
const ref = useRef();
const hasCopied = useCopyOnClick( ref, text );
const { createNotice } = useDispatch( noticesStore );
const ref = useCopyToClipboard( text, () => {
createNotice( 'info', __( 'Copied URL to clipboard.' ), {
isDismissible: true,
type: 'snackbar',
} );
} );

return (
<ToolbarButton
className="components-clipboard-toolbar-button"
ref={ ref }
disabled={ disabled }
>
{ hasCopied ? __( 'Copied!' ) : __( 'Copy URL' ) }
Copy link
Contributor

Choose a reason for hiding this comment

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

This changes the behavior from the button label change to snacker, I just want to make sure that this is what we want :)

{ __( 'Copy URL' ) }
</ToolbarButton>
);
}
Expand Down
31 changes: 17 additions & 14 deletions packages/components/src/clipboard-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import classnames from 'classnames';
* WordPress dependencies
*/
import { useRef, useEffect } from '@wordpress/element';
import { useCopyOnClick } from '@wordpress/compose';
import { useCopyToClipboard } from '@wordpress/compose';
import deprecated from '@wordpress/deprecated';

/**
* Internal dependencies
*/
import Button from '../button';

const TIMEOUT = 4000;

export default function ClipboardButton( {
className,
children,
Expand All @@ -22,23 +25,23 @@ export default function ClipboardButton( {
text,
...buttonProps
} ) {
const ref = useRef();
const hasCopied = useCopyOnClick( ref, text );
const lastHasCopied = useRef( hasCopied );
deprecated( 'wp.components.ClipboardButton', {
alternative: 'wp.compose.useCopyToClipboard',
} );

useEffect( () => {
if ( lastHasCopied.current === hasCopied ) {
return;
}
const timeoutId = useRef();
const ref = useCopyToClipboard( text, () => {
onCopy();
clearTimeout( timeoutId.current );

if ( hasCopied ) {
onCopy();
} else if ( onFinishCopy ) {
onFinishCopy();
if ( onFinishCopy ) {
timeoutId.current = setTimeout( () => onFinishCopy(), TIMEOUT );
}
} );

lastHasCopied.current = hasCopied;
}, [ onCopy, onFinishCopy, hasCopied ] );
useEffect( () => {
clearTimeout( timeoutId.current );
}, [] );

const classes = classnames( 'components-clipboard-button', className );

Expand Down
42 changes: 0 additions & 42 deletions packages/components/src/clipboard-button/stories/index.js

This file was deleted.

15 changes: 15 additions & 0 deletions packages/compose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ _Returns_

<a name="useCopyOnClick" href="#useCopyOnClick">#</a> **useCopyOnClick**

> **Deprecated**

Copies the text to the clipboard when the element is clicked.

_Parameters_
Expand All @@ -171,6 +173,19 @@ _Returns_

- `boolean`: Whether or not the text has been copied. Resets after the timeout.

<a name="useCopyToClipboard" href="#useCopyToClipboard">#</a> **useCopyToClipboard**

Copies the given text to the clipboard when the element is clicked.

_Parameters_

- _text_ `text|Function`: The text to copy. Use a function if not already available and expensive to compute.
- _onSuccess_ `Function`: Called when to text is copied.

_Returns_

- `RefObject`: A ref to assign to the target element.

<a name="useDebounce" href="#useDebounce">#</a> **useDebounce**

Debounces a function with Lodash's `debounce`. A new debounced function will
Expand Down
7 changes: 7 additions & 0 deletions packages/compose/src/hooks/use-copy-on-click/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import Clipboard from 'clipboard';
* WordPress dependencies
*/
import { useRef, useEffect, useState } from '@wordpress/element';
import deprecated from '@wordpress/deprecated';

/**
* Copies the text to the clipboard when the element is clicked.
*
* @deprecated
*
* @param {Object} ref Reference with the element.
* @param {string|Function} text The text to copy.
* @param {number} timeout Optional timeout to reset the returned
Expand All @@ -20,6 +23,10 @@ import { useRef, useEffect, useState } from '@wordpress/element';
* timeout.
*/
export default function useCopyOnClick( ref, text, timeout = 4000 ) {
deprecated( 'wp.compose.useCopyOnClick', {
alternative: 'wp.compose.useCopyToClipboard',
} );

const clipboard = useRef();
const [ hasCopied, setHasCopied ] = useState( false );

Expand Down
66 changes: 66 additions & 0 deletions packages/compose/src/hooks/use-copy-to-clipboard/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* External dependencies
*/
import Clipboard from 'clipboard';

/**
* WordPress dependencies
*/
import { useRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import useRefEffect from '../use-ref-effect';

/** @typedef {import('@wordpress/element').RefObject} RefObject */

function useUpdatedRef( value ) {
const ref = useRef( value );
ref.current = value;
return ref;
}

/**
* Copies the given text to the clipboard when the element is clicked.
*
* @param {text|Function} text The text to copy. Use a function if not
* already available and expensive to compute.
* @param {Function} onSuccess Called when to text is copied.
*
* @return {RefObject} A ref to assign to the target element.
*/
export default function useCopyToClipboard( text, onSuccess ) {
// Store the dependencies as refs and continuesly update them so they're
// fresh when the callback is called.
const textRef = useUpdatedRef( text );
const onSuccesRef = useUpdatedRef( onSuccess );
return useRefEffect( ( node ) => {
// Clipboard listens to click events.
const clipboard = new Clipboard( node, {
text() {
return typeof textRef.current === 'function'
? textRef.current()
: textRef.current;
},
} );

clipboard.on( 'success', ( { clearSelection } ) => {
// Clearing selection will move focus back to the triggering
// button, ensuring that it is not reset to the body, and
// further that it is kept within the rendered node.
clearSelection();
// Handle ClipboardJS focus bug, see
// https://github.com/zenorocha/clipboard.js/issues/680
node.focus();

if ( onSuccesRef.current ) {
onSuccesRef.current();
}
} );

return () => {
clipboard.destroy();
};
}, [] );
}
1 change: 1 addition & 0 deletions packages/compose/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { default as withState } from './higher-order/with-state';
// Hooks
export { default as useConstrainedTabbing } from './hooks/use-constrained-tabbing';
export { default as useCopyOnClick } from './hooks/use-copy-on-click';
export { default as useCopyToClipboard } from './hooks/use-copy-to-clipboard';
export { default as __experimentalUseDialog } from './hooks/use-dialog';
export { default as __experimentalUseDragging } from './hooks/use-dragging';
export { default as useFocusOnMount } from './hooks/use-focus-on-mount';
Expand Down
48 changes: 15 additions & 33 deletions packages/edit-post/src/plugins/copy-content-menu-item/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,28 @@
* WordPress dependencies
*/
import { MenuItem } from '@wordpress/components';
import { withDispatch, withSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { useCopyOnClick, compose, ifCondition } from '@wordpress/compose';
import { useRef, useEffect } from '@wordpress/element';
import { useCopyToClipboard } from '@wordpress/compose';
import { store as noticesStore } from '@wordpress/notices';
import { store as editorStore } from '@wordpress/editor';

function CopyContentMenuItem( { createNotice, editedPostContent } ) {
const ref = useRef();
const hasCopied = useCopyOnClick( ref, editedPostContent );

useEffect( () => {
if ( ! hasCopied ) {
return;
}
export default function CopyContentMenuItem() {
const { createNotice } = useDispatch( noticesStore );
const getText = useSelect(
( select ) => () =>
select( editorStore ).getEditedPostAttribute( 'content' ),
[]
);

function onSuccess() {
createNotice( 'info', __( 'All content copied.' ), {
isDismissible: true,
type: 'snackbar',
} );
}, [ hasCopied ] );

return (
<MenuItem ref={ ref }>
{ hasCopied ? __( 'Copied!' ) : __( 'Copy all content' ) }
</MenuItem>
);
}
}

export default compose(
withSelect( ( select ) => ( {
editedPostContent: select( 'core/editor' ).getEditedPostAttribute(
'content'
),
} ) ),
withDispatch( ( dispatch ) => {
const { createNotice } = dispatch( noticesStore );
const ref = useCopyToClipboard( getText, onSuccess );

return {
createNotice,
};
} ),
ifCondition( ( { editedPostContent } ) => editedPostContent.length > 0 )
)( CopyContentMenuItem );
return <MenuItem ref={ ref }>{ __( 'Copy all content' ) }</MenuItem>;
}
Loading