From 18ab2e9b6d2df242413d0a12ea0ac58f9c1c4b02 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 15 Jul 2019 13:39:40 +1000 Subject: [PATCH 1/7] NUX: Make tips appear inline Improve the usability of NUX tips by making them appear as inline notices rather than floating popovers. Adds a new InlineTip component to accomplish this. --- package-lock.json | 3 +- packages/block-editor/package.json | 1 + .../src/components/block-inspector/index.js | 4 + .../src/components/block-inspector/style.scss | 3 + .../src/components/inserter/menu.js | 11 +- .../src/components/inserter/style.scss | 4 + .../e2e-test-utils/src/create-new-post.js | 4 - packages/e2e-tests/specs/nux.test.js | 209 ++++++------------ packages/edit-post/package.json | 1 - .../components/editor-initialization/index.js | 15 -- .../components/header/header-toolbar/index.js | 8 +- .../edit-post/src/components/header/index.js | 22 +- .../options-modal/options/deferred.js | 36 --- .../options-modal/options/enable-tips.js | 9 +- .../test/__snapshots__/index.js.snap | 2 +- packages/editor/package.json | 1 - .../components/post-preview-button/index.js | 4 - .../test/__snapshots__/index.js.snap | 10 - .../components/post-publish-button/index.js | 4 - packages/nux/CHANGELOG.md | 4 + .../nux/src/components/inline-tip/README.md | 34 +++ .../nux/src/components/inline-tip/index.js | 40 ++++ .../test/__snapshots__/index.js.snap | 34 +++ .../src/components/inline-tip/test/index.js | 45 ++++ packages/nux/src/index.js | 1 + 25 files changed, 256 insertions(+), 253 deletions(-) delete mode 100644 packages/edit-post/src/components/options-modal/options/deferred.js create mode 100644 packages/nux/src/components/inline-tip/README.md create mode 100644 packages/nux/src/components/inline-tip/index.js create mode 100644 packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap create mode 100644 packages/nux/src/components/inline-tip/test/index.js diff --git a/package-lock.json b/package-lock.json index 04093ff7f7b4fa..8773c09370fe22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3741,6 +3741,7 @@ "@wordpress/i18n": "file:packages/i18n", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/nux": "file:packages/nux", "@wordpress/rich-text": "file:packages/rich-text", "@wordpress/token-list": "file:packages/token-list", "@wordpress/url": "file:packages/url", @@ -3997,7 +3998,6 @@ "@wordpress/hooks": "file:packages/hooks", "@wordpress/i18n": "file:packages/i18n", "@wordpress/keycodes": "file:packages/keycodes", - "@wordpress/nux": "file:packages/nux", "@wordpress/plugins": "file:packages/plugins", "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", @@ -4039,7 +4039,6 @@ "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", - "@wordpress/nux": "file:packages/nux", "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "@wordpress/wordcount": "file:packages/wordcount", diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json index 61c49f9d9a088e..6b171bc3eb4da4 100644 --- a/packages/block-editor/package.json +++ b/packages/block-editor/package.json @@ -37,6 +37,7 @@ "@wordpress/i18n": "file:../i18n", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", + "@wordpress/nux": "file:../nux", "@wordpress/rich-text": "file:../rich-text", "@wordpress/token-list": "file:../token-list", "@wordpress/url": "file:../url", diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index e04dbcbd932be1..62665978676a42 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -10,6 +10,7 @@ import { __ } from '@wordpress/i18n'; import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; import { PanelBody } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; +import { InlineTip } from '@wordpress/nux'; /** * Internal dependencies @@ -51,6 +52,9 @@ const BlockInspector = ( { return ( <> + + { __( 'The block tab contains additional settings for the selected block.' ) } +
diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss index 2fdb6d04b44aab..1633e262da3408 100644 --- a/packages/block-editor/src/components/block-inspector/style.scss +++ b/packages/block-editor/src/components/block-inspector/style.scss @@ -6,6 +6,9 @@ text-align: center; } +.block-editor-block-inspector__tip { + margin: 0 0 16px 0; +} .block-editor-block-inspector__card { display: flex; diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 12d119ce4ab519..6654f54c194281 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -22,7 +22,7 @@ import scrollIntoView from 'dom-scroll-into-view'; */ import { __, _n, _x, sprintf } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; -import { withSpokenMessages, PanelBody } from '@wordpress/components'; +import { withSpokenMessages, PanelBody, ExternalLink } from '@wordpress/components'; import { getCategories, isReusableBlock, @@ -33,6 +33,7 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { withInstanceId, compose, withSafeTimeout } from '@wordpress/compose'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { addQueryArgs } from '@wordpress/url'; +import { InlineTip } from '@wordpress/nux'; /** * Internal dependencies @@ -290,6 +291,14 @@ export class InserterMenu extends Component { aria-label={ __( 'Available block types' ) } > + + { __( 'There are Blocks for most types of content: text, headings, images, lists, and lots more!' ) } + { ' ' } + + { __( 'Learn more' ) } + + + { - async function clickAllTips( page ) { - // Click through all available tips. - const tips = await getTips( page ); - const numberOfTips = tips.tipIds.length; - - for ( let i = 1; i < numberOfTips; i++ ) { - await page.click( '.nux-dot-tip .components-button.is-link' ); - } - - return { numberOfTips, tips }; - } - - async function getTips( page ) { - return await page.evaluate( () => { - return wp.data.select( 'core/nux' ).getAssociatedGuide( 'core/editor.inserter' ); - } ); - } - - async function getTipsEnabled( page ) { - return await page.evaluate( () => { - return wp.data.select( 'core/nux' ).areTipsEnabled(); - } ); - } +/** + * Queries the data store and returns whether or not NUX tips are enabled. + * + * @return {boolean} Whether or not NUX tips are enabled. + */ +async function areTipsEnabled() { + return await page.evaluate( () => wp.data.select( 'core/nux' ).areTipsEnabled() ); +} +describe( 'New User Experience (NUX)', () => { beforeEach( async () => { await createNewPost( { enableTips: true } ); } ); - it( 'should show tips to a first-time user', async () => { - const firstTipText = await page.$eval( '.nux-dot-tip', ( element ) => element.innerText ); - expect( firstTipText ).toContain( 'Welcome to the wonderful world of blocks!' ); - - const [ nextTipButton ] = await page.$x( "//button[contains(text(), 'See next tip')]" ); - await nextTipButton.click(); - - const secondTipText = await page.$eval( '.nux-dot-tip', ( element ) => element.innerText ); - expect( secondTipText ).toContain( 'You’ll find more settings for your page and blocks in the sidebar.' ); - } ); - - it( 'should show "Got it" once all tips have been displayed', async () => { - await clickAllTips( page ); + it( 'should show a tip in the inserter', async () => { + // Open up the inserter. + await openGlobalBlockInserter(); - // Make sure "Got it" button appears on the last tip. - const gotItButton = await page.$x( "//button[contains(text(), 'Got it')]" ); - expect( gotItButton ).toHaveLength( 1 ); - - // Click the "Got it button". - await page.click( '.nux-dot-tip .components-button.is-link' ); - - // Verify no more tips are visible on the page. - const nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - - // Tips should not be marked as disabled, but when the user has seen all - // of the available tips, they will not appear. - const areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( true ); + // Check there's a tip in the inserter. + const inserterTip = await page.$( '.block-editor-inserter__tip' ); + expect( inserterTip ).not.toBeNull(); } ); - it( 'should hide and disable tips if "disable tips" button is clicked', async () => { - await page.click( '.nux-dot-tip__disable' ); - - // Verify no more tips are visible on the page. - let nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); + it( 'should show a tip in the block inspector', async () => { + // Insert any old block. + await insertBlock( 'Paragraph' ); - // We should be disabling the tips so they don't appear again. - const areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( false ); - - // Refresh the page; tips should not show because they were disabled. - await page.reload(); - - nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); + // Check there's a tip in the block inspector. + const blockInspectorTip = await page.$( '.block-editor-block-inspector__tip' ); + expect( blockInspectorTip ).not.toBeNull(); } ); - it( 'should enable tips when the "Enable tips" option is toggled on', async () => { - // Start by disabling tips. - await page.click( '.nux-dot-tip__disable' ); - - // Verify no more tips are visible on the page. - let nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - - // Tips should be disabled in localStorage as well. - let areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( false ); + it( 'should dismiss a single tip if X button is clicked and dialog is dismissed', async () => { + // We need to *dismiss* the upcoming confirm() dialog, so let's temporarily + // remove the listener that was added in by enablePageDialogAccept(). + const listeners = page.rawListeners( 'dialog' ); + page.removeAllListeners( 'dialog' ); - // Toggle the 'Enable Tips' option to enable. - await toggleScreenOption( 'Enable Tips' ); + // Open up the inserter. + await openGlobalBlockInserter(); - // Tips should once again appear. - nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 1 ); + // Dismiss the upcoming confirm() dialog. + page.once( 'dialog', async ( dialog ) => { + await dialog.dismiss(); + } ); - // Tips should be enabled in localStorage as well. - areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( true ); - } ); + // Click the tip's X button. + await page.click( '.block-editor-inserter__tip button[aria-label="Dismiss this notice"]' ); - // TODO: This test should be enabled once - // https://github.com/WordPress/gutenberg/issues/7458 is fixed. - it.skip( 'should show tips as disabled if all tips have been shown', async () => { - await clickAllTips( page ); + // The tip should be gone. + const inserterTip = await page.$( '.block-editor-inserter__tip' ); + expect( inserterTip ).toBeNull(); - // Open the "More" menu to check the "Show Tips" element. - await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' ); - const showTipsButton = await page.$x( '//button[contains(text(), "Show Tips")][@aria-pressed="false"]' ); + // Tips should still be enabled. + expect( await areTipsEnabled() ).toBe( true ); - expect( showTipsButton ).toHaveLength( 1 ); + // Restore the listeners that we removed above. + for ( const listener of listeners ) { + page.addListener( 'dialog', listener ); + } } ); - // TODO: This test should be enabled once - // https://github.com/WordPress/gutenberg/issues/7458 is fixed. - it.skip( 'should reset tips if all tips have been shown and show tips was unchecked', async () => { - const { numberOfTips } = await clickAllTips( page ); - - // Click again to re-enable tips; they should appear. - await clickOnMoreMenuItem( 'Show Tips' ); + it( 'should disable all tips if X button is clicked and dialog is confirmed', async () => { + // Open up the inserter. + await openGlobalBlockInserter(); - // Open the "More" menu to check the "Show Tips" element. - await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' ); - const showTipsButton = await page.$x( '//button[contains(text(), "Show Tips")][@aria-pressed="true"]' ); + // Dismiss the tip. (The confirm() dialog will automatically be accepted.) + await page.click( '.block-editor-inserter__tip button[aria-label="Dismiss this notice"]' ); - expect( showTipsButton ).toHaveLength( 1 ); + // The tip should be gone. + const inserterTip = await page.$( '.block-editor-inserter__tip' ); + expect( inserterTip ).toBeNull(); - // Tips should re-appear on the page. - const nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 1 ); - - // Tips should be enabled again. - const areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( true ); - - // Dismissed tips should be reset and ready to be shown again. - const resetTips = await getTips( page ); - const newNumberOfTips = resetTips.tipIds.length; - expect( newNumberOfTips ).toHaveLength( numberOfTips ); + // Tips should now be disabled. + expect( await areTipsEnabled() ).toBe( false ); } ); - // TODO: This test should be enabled once - // https://github.com/WordPress/gutenberg/issues/7753 is fixed. - // See: https://github.com/WordPress/gutenberg/issues/7753#issuecomment-403952816 - it.skip( 'should show tips if "Show tips" was disabled on a draft and then enabled', async () => { - // Click the "Show tips" button (enabled by default) to disable tips. - await clickOnMoreMenuItem( 'Show Tips' ); - - // Let's type something so there's content in this post. - await page.click( '.editor-post-title__input' ); - await page.keyboard.type( 'Post title' ); - await clickBlockAppender(); - await page.keyboard.type( 'Post content goes here.' ); - await saveDraft(); - - // Refresh the page; tips should be disabled. - await page.reload(); - let nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - - // Clicking should re-enable tips. - await clickOnMoreMenuItem( 'Show Tips' ); - - // Tips should re-appear on the page. - nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 1 ); + it( 'should enable and disable tips when option is toggled', async () => { + // Toggling the option off should disable tips. + await toggleScreenOption( 'Enable Tips', false ); + expect( await areTipsEnabled() ).toBe( false ); + + // Toggling the option on should enable tips. + await toggleScreenOption( 'Enable Tips', true ); + expect( await areTipsEnabled() ).toBe( true ); } ); } ); diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 09cebd91d061b5..08f46e37086fac 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -36,7 +36,6 @@ "@wordpress/hooks": "file:../hooks", "@wordpress/i18n": "file:../i18n", "@wordpress/keycodes": "file:../keycodes", - "@wordpress/nux": "file:../nux", "@wordpress/plugins": "file:../plugins", "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js index 51cb90681f8ae8..f7ab4d7f5b008d 100644 --- a/packages/edit-post/src/components/editor-initialization/index.js +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -1,9 +1,3 @@ -/** - * WordPress dependencies - */ -import { useEffect } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; - /** * Internal dependencies */ @@ -24,14 +18,5 @@ export default function( { postId } ) { useAdjustSidebarListener( postId ); useBlockSelectionListener( postId ); useUpdatePostLinkListener( postId ); - const { triggerGuide } = useDispatch( 'core/nux' ); - useEffect( () => { - triggerGuide( [ - 'core/editor.inserter', - 'core/editor.settings', - 'core/editor.preview', - 'core/editor.publish', - ] ); - }, [ triggerGuide ] ); return null; } diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 8ff5e2209594df..ca75a3fa3303b9 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -4,7 +4,6 @@ import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; -import { DotTip } from '@wordpress/nux'; import { __ } from '@wordpress/i18n'; import { Inserter, @@ -30,12 +29,7 @@ function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter, isText className="edit-post-header-toolbar" aria-label={ toolbarAriaLabel } > -
- - - { __( 'Welcome to the wonderful world of blocks! Click the “+” (“Add block”) button to add a new block. There are blocks available for all kinds of content: you can insert text, headings, images, lists, and lots more!' ) } - -
+ diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index dff5622593603a..b6817580b48d67 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -9,7 +9,6 @@ import { } from '@wordpress/editor'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { DotTip } from '@wordpress/nux'; /** * Internal dependencies @@ -63,19 +62,14 @@ function Header( { forceIsDirty={ hasActiveMetaboxes } forceIsSaving={ isSaving } /> -
- - - { __( 'You’ll find more settings for your page and blocks in the sidebar. Click the cog icon to toggle the sidebar open and closed.' ) } - -
+
diff --git a/packages/edit-post/src/components/options-modal/options/deferred.js b/packages/edit-post/src/components/options-modal/options/deferred.js deleted file mode 100644 index 2a0f84348d23bb..00000000000000 --- a/packages/edit-post/src/components/options-modal/options/deferred.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import BaseOption from './base'; - -class DeferredOption extends Component { - constructor( { isChecked } ) { - super( ...arguments ); - this.state = { - isChecked, - }; - } - - componentWillUnmount() { - if ( this.state.isChecked !== this.props.isChecked ) { - this.props.onChange( this.state.isChecked ); - } - } - - render() { - return ( - this.setState( { isChecked } ) } - /> - ); - } -} - -export default DeferredOption; diff --git a/packages/edit-post/src/components/options-modal/options/enable-tips.js b/packages/edit-post/src/components/options-modal/options/enable-tips.js index 8771f8437ba53a..928cfaaec9afff 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-tips.js +++ b/packages/edit-post/src/components/options-modal/options/enable-tips.js @@ -7,7 +7,7 @@ import { withSelect, withDispatch } from '@wordpress/data'; /** * Internal dependencies */ -import DeferredOption from './deferred'; +import BaseOption from './base'; export default compose( withSelect( ( select ) => ( { @@ -19,9 +19,4 @@ export default compose( onChange: ( isEnabled ) => ( isEnabled ? enableTips() : disableTips() ), }; } ) -)( - // Using DeferredOption here means enableTips() is called when the Options - // modal is dismissed. This stops the NUX guide from appearing above the - // Options modal, which looks totally weird. - DeferredOption -); +)( BaseOption ); diff --git a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap index a81252b32b95fb..906cc1d08da940 100644 --- a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap @@ -12,7 +12,7 @@ exports[`OptionsModal should match snapshot when the modal is active 1`] = ` - diff --git a/packages/editor/package.json b/packages/editor/package.json index 88ddf540a926aa..4b016788545833 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -40,7 +40,6 @@ "@wordpress/keycodes": "file:../keycodes", "@wordpress/media-utils": "file:../media-utils", "@wordpress/notices": "file:../notices", - "@wordpress/nux": "file:../nux", "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "@wordpress/wordcount": "file:../wordcount", diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index b09f50b431269d..b77bc4211f6543 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -10,7 +10,6 @@ import { Component, renderToString } from '@wordpress/element'; import { Button, Path, SVG } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; -import { DotTip } from '@wordpress/nux'; import { ifCondition, compose } from '@wordpress/compose'; import { applyFilters } from '@wordpress/hooks'; @@ -191,9 +190,6 @@ export class PostPreviewButton extends Component { __( '(opens in a new tab)' ) } - - { __( 'Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks.' ) } - ); } diff --git a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap index fc928b5e12cb9e..587f30bf8a91bb 100644 --- a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap @@ -15,11 +15,6 @@ exports[`PostPreviewButton render() should render currentPostLink otherwise 1`] > (opens in a new tab) - - Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks. - `; @@ -38,10 +33,5 @@ exports[`PostPreviewButton render() should render previewLink if provided 1`] = > (opens in a new tab) - - Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks. - `; diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index ff3bedf5a79fb5..604770d7157ad9 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -11,7 +11,6 @@ import { Component, createRef } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { DotTip } from '@wordpress/nux'; /** * Internal dependencies @@ -116,9 +115,6 @@ export class PostPublishButton extends Component { { ...componentProps } > { componentChildren } - - { __( 'Finished writing? That’s great, let’s get this published right now. Just click “Publish” and you’re good to go.' ) } - ); } diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md index 546a22e7930b46..7ca31c33a7c6d4 100644 --- a/packages/nux/CHANGELOG.md +++ b/packages/nux/CHANGELOG.md @@ -1,3 +1,7 @@ +# 3.1.0 (Unreleased) + +- Adds the `InlineTip` component. + ## 3.0.6 (2019-01-03) ## 3.0.5 (2018-12-12) diff --git a/packages/nux/src/components/inline-tip/README.md b/packages/nux/src/components/inline-tip/README.md new file mode 100644 index 00000000000000..27fe9364bd039a --- /dev/null +++ b/packages/nux/src/components/inline-tip/README.md @@ -0,0 +1,34 @@ +InlineTip +========= + +`InlineTip` is a React component that renders a single _tip_ on the screen. It has the same appearance as a `Notice`. Each tip is uniquely identified by a string passed to `tipId`. + +## Usage + +```jsx + + Add the product to your shopping cart by clicking ‘Add to Cart’. + +``` + +## Props + +The component accepts the following props: + +### tipId + +A string that uniquely identifies the tip. Identifiers should be prefixed with the name of the plugin, followed by a `/`. For example, `acme/add-to-cart`. + +- Type: `string` +- Required: Yes + +### className + +Class name added to the rendered tip. + +- Type: `string` +- Required: Yes + +### children + +Any React element or elements can be passed as children. They will be rendered within the tip. diff --git a/packages/nux/src/components/inline-tip/index.js b/packages/nux/src/components/inline-tip/index.js new file mode 100644 index 00000000000000..a1bec5742b21d3 --- /dev/null +++ b/packages/nux/src/components/inline-tip/index.js @@ -0,0 +1,40 @@ +/** + * WordPress dependencies + */ +import { Notice } from '@wordpress/components'; +import { compose } from '@wordpress/compose'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +export function InlineTip( { isVisible, className, onRemove, children } ) { + if ( ! isVisible ) { + return null; + } + + return ( + + { children } + + ); +} + +export default compose( + withSelect( ( select, { tipId } ) => ( { + isVisible: select( 'core/nux' ).isTipVisible( tipId ), + } ) ), + withDispatch( ( dispatch, { tipId } ) => { + const { disableTips, dismissTip } = dispatch( 'core/nux' ); + + return { + onRemove() { + // Disable reason: We don't yet have a component. One day! + // eslint-disable-next-line no-alert + if ( window.confirm( __( 'Would you like to disable tips like these in the future? ' ) ) ) { + disableTips(); + } else { + dismissTip( tipId ); + } + }, + }; + } ) +)( InlineTip ); diff --git a/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap b/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000000..fb596ab9500806 --- /dev/null +++ b/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InlineTip should render correctly 1`] = ` +
+
+ It looks like you’re writing a letter. Would you like help? +
+ +
+`; diff --git a/packages/nux/src/components/inline-tip/test/index.js b/packages/nux/src/components/inline-tip/test/index.js new file mode 100644 index 00000000000000..56914b414753b2 --- /dev/null +++ b/packages/nux/src/components/inline-tip/test/index.js @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import TestRenderer from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { Notice } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { InlineTip } from '../'; + +describe( 'InlineTip', () => { + it( 'should not render anything if invisible', () => { + const renderer = TestRenderer.create( + + It looks like you’re writing a letter. Would you like help? + + ); + expect( renderer.root.children ).toHaveLength( 0 ); + } ); + + it( 'should render correctly', () => { + const renderer = TestRenderer.create( + + It looks like you’re writing a letter. Would you like help? + + ); + expect( renderer ).toMatchSnapshot(); + } ); + + it( 'should call onRemove when the remove button is clicked', () => { + const onRemove = jest.fn(); + const renderer = TestRenderer.create( + + It looks like you’re writing a letter. Would you like help? + + ); + renderer.root.findByType( Notice ).props.onRemove(); + expect( onRemove ).toHaveBeenCalled(); + } ); +} ); diff --git a/packages/nux/src/index.js b/packages/nux/src/index.js index a11d17bc96961a..44cd97344d11f6 100644 --- a/packages/nux/src/index.js +++ b/packages/nux/src/index.js @@ -4,3 +4,4 @@ import './store'; export { default as DotTip } from './components/dot-tip'; +export { default as InlineTip } from './components/inline-tip'; From f45b0465f9aa72dc4cc4d93bcd74e7ce7829b135 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 17 Jul 2019 16:14:43 +1000 Subject: [PATCH 2/7] InlineTip: Link to the Notice component's documentation --- packages/nux/src/components/inline-tip/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nux/src/components/inline-tip/README.md b/packages/nux/src/components/inline-tip/README.md index 27fe9364bd039a..dea264ef68dc1f 100644 --- a/packages/nux/src/components/inline-tip/README.md +++ b/packages/nux/src/components/inline-tip/README.md @@ -1,7 +1,7 @@ InlineTip ========= -`InlineTip` is a React component that renders a single _tip_ on the screen. It has the same appearance as a `Notice`. Each tip is uniquely identified by a string passed to `tipId`. +`InlineTip` is a React component that renders a single _tip_ on the screen. It has the same appearance as a [`Notice`](https://developer.wordpress.org/block-editor/components/notice/). Each tip is uniquely identified by a string passed to `tipId`. ## Usage From 51baeadb6d46a8251cbf1c16567eeae633de50b5 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 18 Jul 2019 09:42:20 +1000 Subject: [PATCH 3/7] InlineTip: Mark as experimental --- packages/block-editor/src/components/block-inspector/index.js | 2 +- packages/block-editor/src/components/inserter/menu.js | 2 +- packages/nux/src/index.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 62665978676a42..bde298546e3728 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -10,7 +10,7 @@ import { __ } from '@wordpress/i18n'; import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; import { PanelBody } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; -import { InlineTip } from '@wordpress/nux'; +import { __experimentalInlineTip as InlineTip } from '@wordpress/nux'; /** * Internal dependencies diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 6654f54c194281..8379c10ef10e93 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -33,7 +33,7 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { withInstanceId, compose, withSafeTimeout } from '@wordpress/compose'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { addQueryArgs } from '@wordpress/url'; -import { InlineTip } from '@wordpress/nux'; +import { __experimentalInlineTip as InlineTip } from '@wordpress/nux'; /** * Internal dependencies diff --git a/packages/nux/src/index.js b/packages/nux/src/index.js index 44cd97344d11f6..f57313de8e788a 100644 --- a/packages/nux/src/index.js +++ b/packages/nux/src/index.js @@ -4,4 +4,4 @@ import './store'; export { default as DotTip } from './components/dot-tip'; -export { default as InlineTip } from './components/inline-tip'; +export { default as __experimentalInlineTip } from './components/inline-tip'; From bdd647311b89ceb14bba6b193a81ab0a791cd22b Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 18 Jul 2019 09:54:58 +1000 Subject: [PATCH 4/7] InlineTip: Use more accurate test description --- packages/nux/src/components/inline-tip/test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nux/src/components/inline-tip/test/index.js b/packages/nux/src/components/inline-tip/test/index.js index 56914b414753b2..ef66a95b848062 100644 --- a/packages/nux/src/components/inline-tip/test/index.js +++ b/packages/nux/src/components/inline-tip/test/index.js @@ -32,7 +32,7 @@ describe( 'InlineTip', () => { expect( renderer ).toMatchSnapshot(); } ); - it( 'should call onRemove when the remove button is clicked', () => { + it( 'calls `onRemove` when the rendered notice is dismissed', () => { const onRemove = jest.fn(); const renderer = TestRenderer.create( From 609a09cac5f2f6c051c485d0e56efa201f2efbeb Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 19 Jul 2019 15:14:58 +1000 Subject: [PATCH 5/7] BlockEditor: Do not show InlineTips by default --- packages/block-editor/README.md | 3 ++- .../src/components/block-inspector/index.js | 13 ++++++++---- .../src/components/inserter/menu.js | 21 ++++++++++++------- packages/block-editor/src/store/defaults.js | 6 ++++++ .../editor/src/components/provider/index.js | 1 + 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 63718286e9bb87..f9133889bf98bd 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -370,7 +370,8 @@ The default editor settings bodyPlaceholder string Empty post placeholder titlePlaceholder string Empty title placeholder codeEditingEnabled string Whether or not the user can switch to the code editor - \_\_experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. + **experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. + **experimentalEnableTips boolean Whether or not tips aimed at new users should appear in the UI. # **SkipToSelectedBlock** diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index bde298546e3728..d31c422316e677 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -28,6 +28,7 @@ const BlockInspector = ( { selectedBlockClientId, selectedBlockName, showNoBlockSelectedMessage = true, + enableTips, } ) => { if ( count > 1 ) { return ; @@ -52,9 +53,11 @@ const BlockInspector = ( { return ( <> - - { __( 'The block tab contains additional settings for the selected block.' ) } - + { enableTips && ( + + { __( 'The block tab contains additional settings for the selected block.' ) } + + ) }
@@ -95,18 +98,20 @@ const BlockInspector = ( { export default withSelect( ( select ) => { - const { getSelectedBlockClientId, getSelectedBlockCount, getBlockName } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getSelectedBlockCount, getBlockName, getSettings } = select( 'core/block-editor' ); const { getBlockStyles } = select( 'core/blocks' ); const selectedBlockClientId = getSelectedBlockClientId(); const selectedBlockName = selectedBlockClientId && getBlockName( selectedBlockClientId ); const blockType = selectedBlockClientId && getBlockType( selectedBlockName ); const blockStyles = selectedBlockClientId && getBlockStyles( selectedBlockName ); + const { __experimentalEnableTips: enableTips } = getSettings(); return { count: getSelectedBlockCount(), hasBlockStyles: blockStyles && blockStyles.length > 0, selectedBlockName, selectedBlockClientId, blockType, + enableTips, }; } )( BlockInspector ); diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 8379c10ef10e93..ba96236f7f22b0 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -248,7 +248,7 @@ export class InserterMenu extends Component { } render() { - const { instanceId, onSelect, rootClientId } = this.props; + const { instanceId, onSelect, rootClientId, enableTips } = this.props; const { childItems, hoveredItem, @@ -291,13 +291,15 @@ export class InserterMenu extends Component { aria-label={ __( 'Available block types' ) } > - - { __( 'There are Blocks for most types of content: text, headings, images, lists, and lots more!' ) } - { ' ' } - - { __( 'Learn more' ) } - - + { enableTips && ( + + { __( 'There are Blocks for most types of content: text, headings, images, lists, and lots more!' ) } + { ' ' } + + { __( 'Learn more' ) } + + + ) } { diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 1f91e29c7e2f50..f4f9a4815ab0d7 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -28,9 +28,11 @@ export const PREFERENCES_DEFAULTS = { * titlePlaceholder string Empty title placeholder * codeEditingEnabled string Whether or not the user can switch to the code editor * __experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. + * __experimentalEnableTips boolean Whether or not tips aimed at new users should appear in the UI. */ export const SETTINGS_DEFAULTS = { alignWide: false, + colors: [ { name: __( 'Pale pink' ), @@ -142,7 +144,11 @@ export const SETTINGS_DEFAULTS = { allowedMimeTypes: null, availableLegacyWidgets: {}, + hasPermissionsToManageWidgets: false, + __experimentalCanUserUseUnfilteredHTML: false, + + __experimentalEnableTips: false, }; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index cfa67ddbb615d7..631fe2ead7e216 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -104,6 +104,7 @@ class EditorProvider extends Component { __experimentalMediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalFetchLinkSuggestions: fetchLinkSuggestions, __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, + __experimentalEnableTips: true, }; } From b1270ca23f8231e58a905244d1fbca4d7a026caa Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 2 Aug 2019 13:53:19 +1000 Subject: [PATCH 6/7] InlineTip: Use Modal instead of window.confirm() --- packages/components/src/notice/index.js | 11 ++- packages/e2e-tests/specs/nux.test.js | 29 +++---- .../nux/src/components/inline-tip/index.js | 78 ++++++++++++++----- .../nux/src/components/inline-tip/style.scss | 13 ++++ .../test/__snapshots__/index.js.snap | 34 ++------ .../src/components/inline-tip/test/index.js | 44 ++++++----- packages/nux/src/style.scss | 1 + 7 files changed, 124 insertions(+), 86 deletions(-) create mode 100644 packages/nux/src/components/inline-tip/style.scss diff --git a/packages/components/src/notice/index.js b/packages/components/src/notice/index.js index 99d5f9968f1111..6a945cd8c7e509 100644 --- a/packages/components/src/notice/index.js +++ b/packages/components/src/notice/index.js @@ -24,9 +24,14 @@ function Notice( { actions = [], __unstableHTML, } ) { - const classes = classnames( className, 'components-notice', 'is-' + status, { - 'is-dismissible': isDismissible, - } ); + const classes = classnames( + className, + 'components-notice', + status ? 'is-' + status : undefined, + { + 'is-dismissible': isDismissible, + } + ); if ( __unstableHTML ) { children = { children }; diff --git a/packages/e2e-tests/specs/nux.test.js b/packages/e2e-tests/specs/nux.test.js index 4d1620648565c9..f5f5c7e355aa28 100644 --- a/packages/e2e-tests/specs/nux.test.js +++ b/packages/e2e-tests/specs/nux.test.js @@ -40,43 +40,36 @@ describe( 'New User Experience (NUX)', () => { expect( blockInspectorTip ).not.toBeNull(); } ); - it( 'should dismiss a single tip if X button is clicked and dialog is dismissed', async () => { - // We need to *dismiss* the upcoming confirm() dialog, so let's temporarily - // remove the listener that was added in by enablePageDialogAccept(). - const listeners = page.rawListeners( 'dialog' ); - page.removeAllListeners( 'dialog' ); - + it( 'should dismiss a single tip if X button is clicked and confirmation is dismissed', async () => { // Open up the inserter. await openGlobalBlockInserter(); - // Dismiss the upcoming confirm() dialog. - page.once( 'dialog', async ( dialog ) => { - await dialog.dismiss(); - } ); - // Click the tip's X button. await page.click( '.block-editor-inserter__tip button[aria-label="Dismiss this notice"]' ); + // Dismiss the confirmation modal. + const [ noButton ] = await page.$x( '//*[contains(@class, "nux-hide-tips-confirmation")]//button[text()="No"]' ); + await noButton.click(); + // The tip should be gone. const inserterTip = await page.$( '.block-editor-inserter__tip' ); expect( inserterTip ).toBeNull(); // Tips should still be enabled. expect( await areTipsEnabled() ).toBe( true ); - - // Restore the listeners that we removed above. - for ( const listener of listeners ) { - page.addListener( 'dialog', listener ); - } } ); - it( 'should disable all tips if X button is clicked and dialog is confirmed', async () => { + it( 'should disable all tips if X button is clicked and confirmation is confirmed', async () => { // Open up the inserter. await openGlobalBlockInserter(); - // Dismiss the tip. (The confirm() dialog will automatically be accepted.) + // Click the tip's X button. await page.click( '.block-editor-inserter__tip button[aria-label="Dismiss this notice"]' ); + // Accept the confirmation modal. + const [ disableTipsButton ] = await page.$x( '//*[contains(@class, "nux-hide-tips-confirmation")]//button[text()="Disable Tips"]' ); + await disableTipsButton.click(); + // The tip should be gone. const inserterTip = await page.$( '.block-editor-inserter__tip' ); expect( inserterTip ).toBeNull(); diff --git a/packages/nux/src/components/inline-tip/index.js b/packages/nux/src/components/inline-tip/index.js index a1bec5742b21d3..de1e8f4b05b9fe 100644 --- a/packages/nux/src/components/inline-tip/index.js +++ b/packages/nux/src/components/inline-tip/index.js @@ -1,40 +1,80 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { Notice } from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { Notice, Modal, Button } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -export function InlineTip( { isVisible, className, onRemove, children } ) { - if ( ! isVisible ) { - return null; - } +export function InlineTip( { + children, + className, + isTipVisible, + onDisableTips, + onDismissTip, +} ) { + const [ isConfirmationVisible, setIsConfirmationVisible ] = useState( false ); + + const openConfirmation = () => setIsConfirmationVisible( true ); + const closeConfirmation = () => setIsConfirmationVisible( false ); + + const dismissTip = () => { + onDismissTip(); + closeConfirmation(); + }; + + const disableTips = () => { + onDisableTips(); + closeConfirmation(); + }; return ( - - { children } - + <> + { isTipVisible && ( + + { children } + + ) } + + { isConfirmationVisible && ( + + { __( 'Would you like to disable tips like these in the future?' ) } +
+ + +
+
+ ) } + ); } export default compose( withSelect( ( select, { tipId } ) => ( { - isVisible: select( 'core/nux' ).isTipVisible( tipId ), + isTipVisible: select( 'core/nux' ).isTipVisible( tipId ), } ) ), withDispatch( ( dispatch, { tipId } ) => { const { disableTips, dismissTip } = dispatch( 'core/nux' ); - return { - onRemove() { - // Disable reason: We don't yet have a component. One day! - // eslint-disable-next-line no-alert - if ( window.confirm( __( 'Would you like to disable tips like these in the future? ' ) ) ) { - disableTips(); - } else { - dismissTip( tipId ); - } - }, + onDismissTip: () => dismissTip( tipId ), + onDisableTips: disableTips, }; } ) )( InlineTip ); diff --git a/packages/nux/src/components/inline-tip/style.scss b/packages/nux/src/components/inline-tip/style.scss new file mode 100644 index 00000000000000..6911c53ce47fef --- /dev/null +++ b/packages/nux/src/components/inline-tip/style.scss @@ -0,0 +1,13 @@ +.nux-hide-tips-confirmation .components-modal__header { + border-bottom: none; + margin-bottom: 0; +} + +.nux-hide-tips-confirmation__buttons { + margin-top: 2rem; + text-align: right; + + .components-button { + margin-left: 10px; + } +} diff --git a/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap b/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap index fb596ab9500806..3472d2dcd88776 100644 --- a/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap +++ b/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap @@ -1,34 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`InlineTip should render correctly 1`] = ` -
-
+ It looks like you’re writing a letter. Would you like help? -
- -
+ + `; diff --git a/packages/nux/src/components/inline-tip/test/index.js b/packages/nux/src/components/inline-tip/test/index.js index ef66a95b848062..8f9f5c3c4c7279 100644 --- a/packages/nux/src/components/inline-tip/test/index.js +++ b/packages/nux/src/components/inline-tip/test/index.js @@ -1,12 +1,7 @@ /** * External dependencies */ -import TestRenderer from 'react-test-renderer'; - -/** - * WordPress dependencies - */ -import { Notice } from '@wordpress/components'; +import { shallow, mount } from 'enzyme'; /** * Internal dependencies @@ -15,31 +10,44 @@ import { InlineTip } from '../'; describe( 'InlineTip', () => { it( 'should not render anything if invisible', () => { - const renderer = TestRenderer.create( - + const wrapper = shallow( + It looks like you’re writing a letter. Would you like help? ); - expect( renderer.root.children ).toHaveLength( 0 ); + expect( wrapper.children() ).toHaveLength( 0 ); } ); it( 'should render correctly', () => { - const renderer = TestRenderer.create( - + const wrapper = shallow( + + It looks like you’re writing a letter. Would you like help? + + ); + expect( wrapper ).toMatchSnapshot(); + } ); + + it( 'calls `onDismissTip` when the tip and confirmation are dismissed', () => { + const onDismissTip = jest.fn(); + const wrapper = mount( + It looks like you’re writing a letter. Would you like help? ); - expect( renderer ).toMatchSnapshot(); + wrapper.find( 'button[aria-label="Dismiss this notice"]' ).simulate( 'click' ); + wrapper.find( 'button' ).find( { children: 'No' } ).simulate( 'click' ); + expect( onDismissTip ).toHaveBeenCalled(); } ); - it( 'calls `onRemove` when the rendered notice is dismissed', () => { - const onRemove = jest.fn(); - const renderer = TestRenderer.create( - + it( 'calls `onDisableTips` when the tip is dismissed and the confirmation is accepted', () => { + const onDisableTips = jest.fn(); + const wrapper = mount( + It looks like you’re writing a letter. Would you like help? ); - renderer.root.findByType( Notice ).props.onRemove(); - expect( onRemove ).toHaveBeenCalled(); + wrapper.find( 'button[aria-label="Dismiss this notice"]' ).simulate( 'click' ); + wrapper.find( 'button' ).find( { children: 'Disable Tips' } ).simulate( 'click' ); + expect( onDisableTips ).toHaveBeenCalled(); } ); } ); diff --git a/packages/nux/src/style.scss b/packages/nux/src/style.scss index 0df73ff851e9f9..57e39958ceb7a2 100644 --- a/packages/nux/src/style.scss +++ b/packages/nux/src/style.scss @@ -1 +1,2 @@ @import "./components/dot-tip/style.scss"; +@import "./components/inline-tip/style.scss"; From 7333838fc00c75fd3cefc03109984964c35bf9be Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 2 Aug 2019 14:25:29 +1000 Subject: [PATCH 7/7] InlineTip: Don't show confirmation if a tip has already been dismissed --- .../developers/data/data-core-nux.md | 12 +++++++ .../nux/src/components/inline-tip/index.js | 34 +++++++++++++------ .../src/components/inline-tip/test/index.js | 19 ++++++++--- packages/nux/src/store/selectors.js | 13 ++++++- packages/nux/src/store/test/selectors.js | 26 +++++++++++++- 5 files changed, 88 insertions(+), 16 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-nux.md b/docs/designers-developers/developers/data/data-core-nux.md index e937601ec864b6..66921755e5f074 100644 --- a/docs/designers-developers/developers/data/data-core-nux.md +++ b/docs/designers-developers/developers/data/data-core-nux.md @@ -32,6 +32,18 @@ _Returns_ - `?NUX.GuideInfo`: Information about the associated guide. +# **hasDismissedAnyTips** + +Returns whether or not any tips have been dismissed. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: Whether any tips have been dismissed. + # **isTipVisible** Determines whether or not the given tip is showing. Tips are hidden if they diff --git a/packages/nux/src/components/inline-tip/index.js b/packages/nux/src/components/inline-tip/index.js index de1e8f4b05b9fe..35554ab9b73cad 100644 --- a/packages/nux/src/components/inline-tip/index.js +++ b/packages/nux/src/components/inline-tip/index.js @@ -15,21 +15,31 @@ import { __ } from '@wordpress/i18n'; export function InlineTip( { children, className, + hasDismissedAnyTips, isTipVisible, onDisableTips, onDismissTip, } ) { const [ isConfirmationVisible, setIsConfirmationVisible ] = useState( false ); - const openConfirmation = () => setIsConfirmationVisible( true ); - const closeConfirmation = () => setIsConfirmationVisible( false ); + const dismissNotice = () => { + if ( hasDismissedAnyTips ) { + onDismissTip(); + } else { + setIsConfirmationVisible( true ); + } + }; + + const closeConfirmation = () => { + setIsConfirmationVisible( false ); + }; - const dismissTip = () => { + const dismissConfirmation = () => { onDismissTip(); closeConfirmation(); }; - const disableTips = () => { + const acceptConfirmation = () => { onDisableTips(); closeConfirmation(); }; @@ -39,7 +49,7 @@ export function InlineTip( { { isTipVisible && ( { children } @@ -53,10 +63,10 @@ export function InlineTip( { > { __( 'Would you like to disable tips like these in the future?' ) }
- -
@@ -67,9 +77,13 @@ export function InlineTip( { } export default compose( - withSelect( ( select, { tipId } ) => ( { - isTipVisible: select( 'core/nux' ).isTipVisible( tipId ), - } ) ), + withSelect( ( select, { tipId } ) => { + const { isTipVisible, hasDismissedAnyTips } = select( 'core/nux' ); + return { + isTipVisible: isTipVisible( tipId ), + hasDismissedAnyTips: hasDismissedAnyTips(), + }; + } ), withDispatch( ( dispatch, { tipId } ) => { const { disableTips, dismissTip } = dispatch( 'core/nux' ); return { diff --git a/packages/nux/src/components/inline-tip/test/index.js b/packages/nux/src/components/inline-tip/test/index.js index 8f9f5c3c4c7279..0f7c2eb5386705 100644 --- a/packages/nux/src/components/inline-tip/test/index.js +++ b/packages/nux/src/components/inline-tip/test/index.js @@ -11,7 +11,7 @@ import { InlineTip } from '../'; describe( 'InlineTip', () => { it( 'should not render anything if invisible', () => { const wrapper = shallow( - + It looks like you’re writing a letter. Would you like help? ); @@ -20,17 +20,28 @@ describe( 'InlineTip', () => { it( 'should render correctly', () => { const wrapper = shallow( - + It looks like you’re writing a letter. Would you like help? ); expect( wrapper ).toMatchSnapshot(); } ); + it( 'calls `onDismissTip` when the tip is dismissed and a tip has already been dismissed', () => { + const onDismissTip = jest.fn(); + const wrapper = mount( + + It looks like you’re writing a letter. Would you like help? + + ); + wrapper.find( 'button[aria-label="Dismiss this notice"]' ).simulate( 'click' ); + expect( onDismissTip ).toHaveBeenCalled(); + } ); + it( 'calls `onDismissTip` when the tip and confirmation are dismissed', () => { const onDismissTip = jest.fn(); const wrapper = mount( - + It looks like you’re writing a letter. Would you like help? ); @@ -42,7 +53,7 @@ describe( 'InlineTip', () => { it( 'calls `onDisableTips` when the tip is dismissed and the confirmation is accepted', () => { const onDisableTips = jest.fn(); const wrapper = mount( - + It looks like you’re writing a letter. Would you like help? ); diff --git a/packages/nux/src/store/selectors.js b/packages/nux/src/store/selectors.js index 9225bd97077eee..74a793c74c382f 100644 --- a/packages/nux/src/store/selectors.js +++ b/packages/nux/src/store/selectors.js @@ -2,7 +2,7 @@ * External dependencies */ import createSelector from 'rememo'; -import { includes, difference, keys, has } from 'lodash'; +import { includes, difference, keys, has, isEmpty } from 'lodash'; /** * An object containing information about a guide. @@ -77,3 +77,14 @@ export function isTipVisible( state, tipId ) { export function areTipsEnabled( state ) { return state.preferences.areTipsEnabled; } + +/** + * Returns whether or not any tips have been dismissed. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether any tips have been dismissed. + */ +export function hasDismissedAnyTips( state ) { + return ! isEmpty( state.preferences.dismissedTips ); +} diff --git a/packages/nux/src/store/test/selectors.js b/packages/nux/src/store/test/selectors.js index 546d052958dd5e..6a382cd37cd01f 100644 --- a/packages/nux/src/store/test/selectors.js +++ b/packages/nux/src/store/test/selectors.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { getAssociatedGuide, isTipVisible, areTipsEnabled } from '../selectors'; +import { getAssociatedGuide, isTipVisible, areTipsEnabled, hasDismissedAnyTips } from '../selectors'; describe( 'selectors', () => { describe( 'getAssociatedGuide', () => { @@ -145,4 +145,28 @@ describe( 'selectors', () => { expect( areTipsEnabled( state ) ).toBe( false ); } ); } ); + + describe( 'hasDismissedAnyTips', () => { + it( 'should return true if a tip has been dismissed', () => { + const state = { + guides: [], + preferences: { + dismissedTips: { + 'test/tip': true, + }, + }, + }; + expect( hasDismissedAnyTips( state ) ).toBe( true ); + } ); + + it( 'should return false if tips are disabled', () => { + const state = { + guides: [], + preferences: { + dismissedTips: {}, + }, + }; + expect( hasDismissedAnyTips( state ) ).toBe( false ); + } ); + } ); } );