diff --git a/docs/designers-developers/developers/data/data-core-nux.md b/docs/designers-developers/developers/data/data-core-nux.md
index e937601ec864b..66921755e5f07 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/package-lock.json b/package-lock.json
index 04093ff7f7b4f..8773c09370fe2 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/README.md b/packages/block-editor/README.md
index 63718286e9bb8..f9133889bf98b 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/package.json b/packages/block-editor/package.json
index 61c49f9d9a088..6b171bc3eb4da 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 e04dbcbd932be..d31c422316e67 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 { __experimentalInlineTip as InlineTip } from '@wordpress/nux';
/**
* Internal dependencies
@@ -27,6 +28,7 @@ const BlockInspector = ( {
selectedBlockClientId,
selectedBlockName,
showNoBlockSelectedMessage = true,
+ enableTips,
} ) => {
if ( count > 1 ) {
return ;
@@ -51,6 +53,11 @@ const BlockInspector = ( {
return (
<>
+ { enableTips && (
+
+ { __( 'The block tab contains additional settings for the selected block.' ) }
+
+ ) }
@@ -91,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/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss
index 2fdb6d04b44aa..1633e262da340 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 12d119ce4ab51..ba96236f7f22b 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 { __experimentalInlineTip as InlineTip } from '@wordpress/nux';
/**
* Internal dependencies
@@ -247,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,
@@ -290,6 +291,16 @@ export class InserterMenu extends Component {
aria-label={ __( 'Available block types' ) }
>
+ { 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/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss
index 87b9a79f12fa4..b8deee07553be 100644
--- a/packages/block-editor/src/components/inserter/style.scss
+++ b/packages/block-editor/src/components/inserter/style.scss
@@ -105,6 +105,10 @@ $block-inserter-search-height: 38px;
}
}
+.block-editor-inserter__tip {
+ margin: 0 0 $grid-size-large 0;
+}
+
.block-editor-inserter__popover .block-editor-block-types-list {
margin: 0 -8px;
}
diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js
index 1f91e29c7e2f5..f4f9a4815ab0d 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/components/src/notice/index.js b/packages/components/src/notice/index.js
index 99d5f9968f111..6a945cd8c7e50 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-test-utils/src/create-new-post.js b/packages/e2e-test-utils/src/create-new-post.js
index 91a95bf5216a4..8616f55143a12 100644
--- a/packages/e2e-test-utils/src/create-new-post.js
+++ b/packages/e2e-test-utils/src/create-new-post.js
@@ -32,8 +32,4 @@ export async function createNewPost( {
const action = _enableTips ? 'enableTips' : 'disableTips';
wp.data.dispatch( 'core/nux' )[ action ]();
}, enableTips );
-
- if ( enableTips ) {
- await page.reload();
- }
}
diff --git a/packages/e2e-tests/specs/nux.test.js b/packages/e2e-tests/specs/nux.test.js
index 40c980fac079c..f5f5c7e355aa2 100644
--- a/packages/e2e-tests/specs/nux.test.js
+++ b/packages/e2e-tests/specs/nux.test.js
@@ -2,179 +2,89 @@
* WordPress dependencies
*/
import {
- clickBlockAppender,
- clickOnMoreMenuItem,
createNewPost,
- saveDraft,
+ insertBlock,
+ openGlobalBlockInserter,
toggleScreenOption,
} from '@wordpress/e2e-test-utils';
-describe( 'New User Experience (NUX)', () => {
- 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();
+ it( 'should show a tip in the inserter', async () => {
+ // Open up the inserter.
+ await openGlobalBlockInserter();
- 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.' );
+ // Check there's a tip in the inserter.
+ const inserterTip = await page.$( '.block-editor-inserter__tip' );
+ expect( inserterTip ).not.toBeNull();
} );
- it( 'should show "Got it" once all tips have been displayed', async () => {
- await clickAllTips( page );
+ it( 'should show a tip in the block inspector', async () => {
+ // Insert any old block.
+ await insertBlock( 'Paragraph' );
- // 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 block inspector.
+ const blockInspectorTip = await page.$( '.block-editor-block-inspector__tip' );
+ expect( blockInspectorTip ).not.toBeNull();
} );
- it( 'should hide and disable tips if "disable tips" button is clicked', async () => {
- await page.click( '.nux-dot-tip__disable' );
+ it( 'should dismiss a single tip if X button is clicked and confirmation is dismissed', async () => {
+ // Open up the inserter.
+ await openGlobalBlockInserter();
- // Verify no more tips are visible on the page.
- let nuxTipElements = await page.$$( '.nux-dot-tip' );
- expect( nuxTipElements ).toHaveLength( 0 );
+ // Click the tip's X button.
+ await page.click( '.block-editor-inserter__tip button[aria-label="Dismiss this notice"]' );
- // We should be disabling the tips so they don't appear again.
- const areTipsEnabled = await getTipsEnabled( page );
- expect( areTipsEnabled ).toEqual( false );
+ // Dismiss the confirmation modal.
+ const [ noButton ] = await page.$x( '//*[contains(@class, "nux-hide-tips-confirmation")]//button[text()="No"]' );
+ await noButton.click();
- // Refresh the page; tips should not show because they were disabled.
- await page.reload();
+ // The tip should be gone.
+ const inserterTip = await page.$( '.block-editor-inserter__tip' );
+ expect( inserterTip ).toBeNull();
- nuxTipElements = await page.$$( '.nux-dot-tip' );
- expect( nuxTipElements ).toHaveLength( 0 );
+ // Tips should still be enabled.
+ expect( await areTipsEnabled() ).toBe( true );
} );
- 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 disable all tips if X button is clicked and confirmation is confirmed', async () => {
+ // Open up the inserter.
+ await openGlobalBlockInserter();
- // Toggle the 'Enable Tips' option to enable.
- await toggleScreenOption( 'Enable Tips' );
+ // Click the tip's X button.
+ await page.click( '.block-editor-inserter__tip button[aria-label="Dismiss this notice"]' );
- // Tips should once again appear.
- nuxTipElements = await page.$$( '.nux-dot-tip' );
- expect( nuxTipElements ).toHaveLength( 1 );
+ // Accept the confirmation modal.
+ const [ disableTipsButton ] = await page.$x( '//*[contains(@class, "nux-hide-tips-confirmation")]//button[text()="Disable Tips"]' );
+ await disableTipsButton.click();
- // Tips should be enabled in localStorage as well.
- areTipsEnabled = await getTipsEnabled( page );
- expect( areTipsEnabled ).toEqual( true );
- } );
-
- // 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 );
-
- // 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"]' );
+ // The tip should be gone.
+ const inserterTip = await page.$( '.block-editor-inserter__tip' );
+ expect( inserterTip ).toBeNull();
- expect( showTipsButton ).toHaveLength( 1 );
+ // Tips should now be disabled.
+ expect( await areTipsEnabled() ).toBe( false );
} );
- // 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' );
-
- // 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"]' );
-
- expect( showTipsButton ).toHaveLength( 1 );
-
- // 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 );
- } );
+ 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 );
- // 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 );
+ // 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 09cebd91d061b..08f46e37086fa 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 51cb90681f8ae..f7ab4d7f5b008 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 8ff5e2209594d..ca75a3fa3303b 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 dff5622593603..b6817580b48d6 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 2a0f84348d23b..0000000000000
--- 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 8771f8437ba53..928cfaaec9aff 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 a81252b32b95f..906cc1d08da94 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 88ddf540a926a..4b01678854583 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 b09f50b431269..b77bc4211f654 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 fc928b5e12cb9..587f30bf8a91b 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 ff3bedf5a79fb..604770d7157ad 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/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js
index cfa67ddbb615d..631fe2ead7e21 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,
};
}
diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md
index 546a22e7930b4..7ca31c33a7c6d 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 0000000000000..dea264ef68dc1
--- /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`](https://developer.wordpress.org/block-editor/components/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 0000000000000..35554ab9b73ca
--- /dev/null
+++ b/packages/nux/src/components/inline-tip/index.js
@@ -0,0 +1,94 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+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( {
+ children,
+ className,
+ hasDismissedAnyTips,
+ isTipVisible,
+ onDisableTips,
+ onDismissTip,
+} ) {
+ const [ isConfirmationVisible, setIsConfirmationVisible ] = useState( false );
+
+ const dismissNotice = () => {
+ if ( hasDismissedAnyTips ) {
+ onDismissTip();
+ } else {
+ setIsConfirmationVisible( true );
+ }
+ };
+
+ const closeConfirmation = () => {
+ setIsConfirmationVisible( false );
+ };
+
+ const dismissConfirmation = () => {
+ onDismissTip();
+ closeConfirmation();
+ };
+
+ const acceptConfirmation = () => {
+ onDisableTips();
+ closeConfirmation();
+ };
+
+ return (
+ <>
+ { isTipVisible && (
+
+ { children }
+
+ ) }
+
+ { isConfirmationVisible && (
+
+ { __( 'Would you like to disable tips like these in the future?' ) }
+
+
+
+
+
+ ) }
+ >
+ );
+}
+
+export default compose(
+ 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 {
+ 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 0000000000000..6911c53ce47fe
--- /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
new file mode 100644
index 0000000000000..3472d2dcd8877
--- /dev/null
+++ b/packages/nux/src/components/inline-tip/test/__snapshots__/index.js.snap
@@ -0,0 +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
new file mode 100644
index 0000000000000..0f7c2eb538670
--- /dev/null
+++ b/packages/nux/src/components/inline-tip/test/index.js
@@ -0,0 +1,64 @@
+/**
+ * External dependencies
+ */
+import { shallow, mount } from 'enzyme';
+
+/**
+ * Internal dependencies
+ */
+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?
+
+ );
+ expect( wrapper.children() ).toHaveLength( 0 );
+ } );
+
+ 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?
+
+ );
+ wrapper.find( 'button[aria-label="Dismiss this notice"]' ).simulate( 'click' );
+ wrapper.find( 'button' ).find( { children: 'No' } ).simulate( 'click' );
+ expect( onDismissTip ).toHaveBeenCalled();
+ } );
+
+ 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?
+
+ );
+ 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/index.js b/packages/nux/src/index.js
index a11d17bc96961..f57313de8e788 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 __experimentalInlineTip } from './components/inline-tip';
diff --git a/packages/nux/src/store/selectors.js b/packages/nux/src/store/selectors.js
index 9225bd97077ee..74a793c74c382 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 546d052958dd5..6a382cd37cd01 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 );
+ } );
+ } );
} );
diff --git a/packages/nux/src/style.scss b/packages/nux/src/style.scss
index 0df73ff851e9f..57e39958ceb7a 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";