diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 7ba9e884259ea4..4008efa1b4d4e1 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -88,6 +88,7 @@
/packages/compose @ajitbohra
/packages/element @ajitbohra
/packages/notices @ajitbohra
+/packages/nux @ajitbohra @peterwilsoncc
/packages/viewport @ajitbohra
/packages/base-styles
/packages/icons
diff --git a/docs/contributors/code/scripts.md b/docs/contributors/code/scripts.md
index 5cd7efd2fffdad..1483a409a4d08f 100644
--- a/docs/contributors/code/scripts.md
+++ b/docs/contributors/code/scripts.md
@@ -31,6 +31,7 @@ The editor includes a number of packages to enable various pieces of functionali
| [Is Shallow Equal](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | A function for performing a shallow comparison between two objects or arrays |
| [Keycodes](/packages/keycodes/README.md) | wp-keycodes | Keycodes utilities for WordPress, used to check the key pressed in events like `onKeyDown` |
| [List Reusable blocks](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | Package used to add import/export links to the listing page of the reusable blocks |
+| [NUX](/packages/nux/README.md) | wp-nux | Components, and wp.data methods useful for onboarding a new user to the WordPress admin interface |
| [Plugins](/packages/plugins/README.md) | wp-plugins | Plugins module for WordPress |
| [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | Redux middleware for generator coroutines |
| [Rich Text](/packages/rich-text/README.md) | wp-rich-text | Helper functions to convert HTML or a DOM tree into a rich text value and back |
diff --git a/docs/manifest.json b/docs/manifest.json
index ec55132cd7d4b2..5f75e49924f355 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1757,6 +1757,12 @@
"markdown_source": "../packages/npm-package-json-lint-config/README.md",
"parent": "packages"
},
+ {
+ "title": "@wordpress/nux",
+ "slug": "packages-nux",
+ "markdown_source": "../packages/nux/README.md",
+ "parent": "packages"
+ },
{
"title": "@wordpress/plugins",
"slug": "packages-plugins",
@@ -2003,6 +2009,12 @@
"markdown_source": "../docs/reference-guides/data/data-core-notices.md",
"parent": "data"
},
+ {
+ "title": "The NUX (New User Experience) Data",
+ "slug": "data-core-nux",
+ "markdown_source": "../docs/reference-guides/data/data-core-nux.md",
+ "parent": "data"
+ },
{
"title": "Preferences",
"slug": "data-core-preferences",
diff --git a/docs/reference-guides/README.md b/docs/reference-guides/README.md
index f13c838697f2de..33fdd9aa602414 100644
--- a/docs/reference-guides/README.md
+++ b/docs/reference-guides/README.md
@@ -63,6 +63,7 @@
- [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md)
- [**core/keyboard-shortcuts**: The Keyboard Shortcuts Data](/docs/reference-guides/data/data-core-keyboard-shortcuts.md)
- [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md)
+ - [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md)
- [**core/preferences**: Preferences](/docs/reference-guides/data/data-core-preferences.md)
- [**core/reusable-blocks**: Reusable blocks](/docs/reference-guides/data/data-core-reusable-blocks.md)
- [**core/rich-text**: Rich Text](/docs/reference-guides/data/data-core-rich-text.md)
diff --git a/docs/reference-guides/data/README.md b/docs/reference-guides/data/README.md
index 5f4d8d92d4bd49..1134c1d5ddd307 100644
--- a/docs/reference-guides/data/README.md
+++ b/docs/reference-guides/data/README.md
@@ -12,6 +12,7 @@
- [**core/editor**: The Post Editor’s Data](/docs/reference-guides/data/data-core-editor.md)
- [**core/keyboard-shortcuts**: The Keyboard Shortcuts Data](/docs/reference-guides/data/data-core-keyboard-shortcuts.md)
- [**core/notices**: Notices Data](/docs/reference-guides/data/data-core-notices.md)
+- [**core/nux**: The NUX (New User Experience) Data](/docs/reference-guides/data/data-core-nux.md)
- [**core/preferences**: Preferences](/docs/reference-guides/data/data-core-preferences.md)
- [**core/reusable-blocks**: Reusable blocks](/docs/reference-guides/data/data-core-reusable-blocks.md)
- [**core/rich-text**: Rich Text](/docs/reference-guides/data/data-core-rich-text.md)
diff --git a/docs/reference-guides/data/data-core-nux.md b/docs/reference-guides/data/data-core-nux.md
new file mode 100644
index 00000000000000..eb6a1c3b5c9a5b
--- /dev/null
+++ b/docs/reference-guides/data/data-core-nux.md
@@ -0,0 +1,93 @@
+# The NUX (New User Experience) Data
+
+Namespace: `core/nux`.
+
+## Selectors
+
+
+
+### areTipsEnabled
+
+Returns whether or not tips are globally enabled.
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+
+_Returns_
+
+- `boolean`: Whether tips are globally enabled.
+
+### getAssociatedGuide
+
+Returns an object describing the guide, if any, that the given tip is a part of.
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+- _tipId_ `string`: The tip to query.
+
+_Returns_
+
+- `?NUXGuideInfo`: Information about the associated guide.
+
+### isTipVisible
+
+Determines whether or not the given tip is showing. Tips are hidden if they are disabled, have been dismissed, or are not the current tip in any guide that they have been added to.
+
+_Parameters_
+
+- _state_ `Object`: Global application state.
+- _tipId_ `string`: The tip to query.
+
+_Returns_
+
+- `boolean`: Whether or not the given tip is showing.
+
+
+
+## Actions
+
+
+
+### disableTips
+
+Returns an action object that, when dispatched, prevents all tips from showing again.
+
+_Returns_
+
+- `Object`: Action object.
+
+### dismissTip
+
+Returns an action object that, when dispatched, dismisses the given tip. A dismissed tip will not show again.
+
+_Parameters_
+
+- _id_ `string`: The tip to dismiss.
+
+_Returns_
+
+- `Object`: Action object.
+
+### enableTips
+
+Returns an action object that, when dispatched, makes all tips show again.
+
+_Returns_
+
+- `Object`: Action object.
+
+### triggerGuide
+
+Returns an action object that, when dispatched, presents a guide that takes the user through a series of tips step by step.
+
+_Parameters_
+
+- _tipIds_ `string[]`: Which tips to show in the guide.
+
+_Returns_
+
+- `Object`: Action object.
+
+
diff --git a/docs/toc.json b/docs/toc.json
index 085bbb536ece2b..1660afdcc29497 100644
--- a/docs/toc.json
+++ b/docs/toc.json
@@ -284,6 +284,7 @@
"docs/reference-guides/data/data-core-keyboard-shortcuts.md": []
},
{ "docs/reference-guides/data/data-core-notices.md": [] },
+ { "docs/reference-guides/data/data-core-nux.md": [] },
{
"docs/reference-guides/data/data-core-preferences.md": []
},
diff --git a/lib/client-assets.php b/lib/client-assets.php
index 99aa7f147ecbfc..891eb05eabdbeb 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -368,6 +368,15 @@ function gutenberg_register_packages_styles( $styles ) {
);
$styles->add_data( 'wp-edit-blocks', 'rtl', 'replace' );
+ gutenberg_override_style(
+ $styles,
+ 'wp-nux',
+ gutenberg_url( 'build/nux/style.css' ),
+ array( 'wp-components' ),
+ $version
+ );
+ $styles->add_data( 'wp-nux', 'rtl', 'replace' );
+
gutenberg_override_style(
$styles,
'wp-block-library-theme',
diff --git a/package-lock.json b/package-lock.json
index aec6b43f2e1e43..815a86ea8787f4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18259,6 +18259,20 @@
"version": "file:packages/npm-package-json-lint-config",
"dev": true
},
+ "@wordpress/nux": {
+ "version": "file:packages/nux",
+ "requires": {
+ "@babel/runtime": "^7.16.0",
+ "@wordpress/components": "file:packages/components",
+ "@wordpress/compose": "file:packages/compose",
+ "@wordpress/data": "file:packages/data",
+ "@wordpress/deprecated": "file:packages/deprecated",
+ "@wordpress/element": "file:packages/element",
+ "@wordpress/i18n": "file:packages/i18n",
+ "@wordpress/icons": "file:packages/icons",
+ "rememo": "^4.0.2"
+ }
+ },
"@wordpress/plugins": {
"version": "file:packages/plugins",
"requires": {
diff --git a/package.json b/package.json
index 5032f589b592fd..5bdef1e9e589e0 100644
--- a/package.json
+++ b/package.json
@@ -65,6 +65,7 @@
"@wordpress/list-reusable-blocks": "file:packages/list-reusable-blocks",
"@wordpress/media-utils": "file:packages/media-utils",
"@wordpress/notices": "file:packages/notices",
+ "@wordpress/nux": "file:packages/nux",
"@wordpress/plugins": "file:packages/plugins",
"@wordpress/preferences": "file:packages/preferences",
"@wordpress/preferences-persistence": "file:packages/preferences-persistence",
diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss
index cc4a42df98f0aa..7987e1b4b5b4dc 100644
--- a/packages/base-styles/_z-index.scss
+++ b/packages/base-styles/_z-index.scss
@@ -144,7 +144,10 @@ $z-layers: (
// The focus styles of the region navigation containers should be above their content.
".is-focusing-regions {region} :focus::after": 1000000,
- // Show tooltips above wp-admin menus, submenus, and sidebar:
+ // Show NUX tips above popovers, wp-admin menus, submenus, and sidebar:
+ ".nux-dot-tip": 1000001,
+
+ // Show tooltips above NUX tips, wp-admin menus, submenus, and sidebar:
".components-tooltip": 1000002,
// Keep template popover underneath 'Create custom template' modal overlay.
diff --git a/packages/nux/.npmrc b/packages/nux/.npmrc
new file mode 100644
index 00000000000000..43c97e719a5a82
--- /dev/null
+++ b/packages/nux/.npmrc
@@ -0,0 +1 @@
+package-lock=false
diff --git a/packages/nux/CHANGELOG.md b/packages/nux/CHANGELOG.md
new file mode 100644
index 00000000000000..edbf4c88a21f0f
--- /dev/null
+++ b/packages/nux/CHANGELOG.md
@@ -0,0 +1,124 @@
+
+
+## Unreleased
+
+### Breaking Changes
+
+- Updated dependencies to require React 18 ([45235](https://github.com/WordPress/gutenberg/pull/45235))
+
+## 5.20.0 (2022-11-16)
+
+## 5.19.0 (2022-11-02)
+
+## 5.18.0 (2022-10-19)
+
+## 5.17.0 (2022-10-05)
+
+## 5.16.0 (2022-09-21)
+
+## 5.15.0 (2022-09-13)
+
+## 5.14.0 (2022-08-24)
+
+## 5.13.0 (2022-08-10)
+
+## 5.12.0 (2022-07-27)
+
+## 5.11.0 (2022-07-13)
+
+## 5.10.0 (2022-06-29)
+
+## 5.9.0 (2022-06-15)
+
+## 5.8.0 (2022-06-01)
+
+## 5.7.0 (2022-05-18)
+
+## 5.6.0 (2022-05-04)
+
+## 5.5.0 (2022-04-21)
+
+## 5.4.0 (2022-04-08)
+
+## 5.3.0 (2022-03-23)
+
+## 5.2.0 (2022-03-11)
+
+## 5.1.0 (2022-01-27)
+
+## 5.0.0 (2021-07-29)
+
+### Breaking Change
+
+- Upgraded React components to work with v17.0 ([#29118](https://github.com/WordPress/gutenberg/pull/29118)). There are no new features in React v17.0 as explained in the [blog post](https://reactjs.org/blog/2020/10/20/react-v17.html).
+
+## 4.2.0 (2021-07-21)
+
+## 4.1.0 (2021-05-20)
+
+## 4.0.0 (2021-05-14)
+
+### Breaking Changes
+
+- Drop support for Internet Explorer 11 ([#31110](https://github.com/WordPress/gutenberg/pull/31110)). Learn more at https://make.wordpress.org/core/2021/04/22/ie-11-support-phase-out-plan/.
+- Increase the minimum Node.js version to v12 matching Long Term Support releases ([#31270](https://github.com/WordPress/gutenberg/pull/31270)). Learn more at https://nodejs.org/en/about/releases/.
+
+## 3.25.0 (2021-03-17)
+
+## 3.24.0 (2020-12-17)
+
+### New Feature
+
+- Added a store definition `store` for the core data namespace to use with `@wordpress/data` API ([#26655](https://github.com/WordPress/gutenberg/pull/26655)).
+
+# 3.1.0 (2019-06-03)
+
+- The `@wordpress/nux` package has been deprecated. Please use the `Guide` component in `@wordpress/components` to show a user guide.
+
+## 3.0.6 (2019-01-03)
+
+## 3.0.5 (2018-12-12)
+
+## 3.0.4 (2018-11-30)
+
+## 3.0.3 (2018-11-22)
+
+## 3.0.2 (2018-11-21)
+
+## 3.0.1 (2018-11-20)
+
+## 3.0.0 (2018-11-15)
+
+### Breaking Changes
+
+- The id prop of DotTip has been removed. Please use the tipId prop instead.
+
+## 2.0.13 (2018-11-12)
+
+## 2.0.12 (2018-11-12)
+
+## 2.0.11 (2018-11-09)
+
+## 2.0.10 (2018-11-09)
+
+## 2.0.9 (2018-11-03)
+
+## 2.0.8 (2018-10-30)
+
+## 2.0.7 (2018-10-29)
+
+### Deprecations
+
+- The id prop of DotTip has been deprecated. Please use the tipId prop instead.
+
+## 2.0.6 (2018-10-22)
+
+## 2.0.5 (2018-10-19)
+
+## 2.0.4 (2018-10-18)
+
+## 2.0.0 (2018-09-05)
+
+### Breaking Change
+
+- Change how required built-ins are polyfilled with Babel 7 ([#9171](https://github.com/WordPress/gutenberg/pull/9171)). If you're using an environment that has limited or no support for ES2015+ such as lower versions of IE then using [core-js](https://github.com/zloirock/core-js) or [@babel/polyfill](https://babeljs.io/docs/en/next/babel-polyfill) will add support for these methods.
diff --git a/packages/nux/README.md b/packages/nux/README.md
new file mode 100644
index 00000000000000..c0941ddd0c5f2a
--- /dev/null
+++ b/packages/nux/README.md
@@ -0,0 +1,114 @@
+# New User eXperience (NUX)
+
+The NUX module exposes components, and `wp.data` methods useful for onboarding a new user to the WordPress admin interface. Specifically, it exposes _tips_ and _guides_.
+
+A _tip_ is a component that points to an element in the UI and contains text that explains the element's functionality. The user can dismiss a tip, in which case it never shows again. The user can also disable tips entirely. Information about tips is persisted between sessions using `localStorage`.
+
+A _guide_ allows a series of tips to be presented to the user one by one. When a user dismisses a tip that is in a guide, the next tip in the guide is shown.
+
+## Installation
+
+Install the module
+
+```bash
+npm install @wordpress/nux --save
+```
+
+_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._
+
+## DotTip
+
+`DotTip` is a React component that renders a single _tip_ on the screen. The tip will point to the React element that `DotTip` is nested within. Each tip is uniquely identified by a string passed to `tipId`.
+
+See [the component's README][dot-tip-readme] for more information.
+
+[dot-tip-readme]: https://github.com/WordPress/gutenberg/tree/HEAD/packages/nux/src/components/dot-tip/README.md
+
+```jsx
+
+}
+```
+
+## Determining if a tip is visible
+
+You can programmatically determine if a tip is visible using the `isTipVisible` select method.
+
+```jsx
+const isVisible = select( 'core/nux' ).isTipVisible( 'acme/add-to-cart' );
+console.log( isVisible ); // true or false
+```
+
+## Manually dismissing a tip
+
+`dismissTip` is a dispatch method that allows you to programmatically dismiss a tip.
+
+```jsx
+
+```
+
+## Disabling and enabling tips
+
+Tips can be programatically disabled or enabled using the `disableTips` and `enableTips` dispatch methods. You can query the current setting by using the `areTipsEnabled` select method.
+
+Calling `enableTips` will also un-dismiss all previously dismissed tips.
+
+```jsx
+const areTipsEnabled = select( 'core/nux' ).areTipsEnabled();
+return (
+
+);
+```
+
+## Triggering a guide
+
+You can group a series of tips into a guide by calling the `triggerGuide` dispatch method. The given tips will then appear one by one.
+
+A tip cannot be added to more than one guide.
+
+```jsx
+dispatch( 'core/nux' ).triggerGuide( [
+ 'acme/product-info',
+ 'acme/add-to-cart',
+ 'acme/checkout',
+] );
+```
+
+## Getting information about a guide
+
+`getAssociatedGuide` is a select method that returns useful information about the state of the guide that a tip is associated with.
+
+```jsx
+const guide = select( 'core/nux' ).getAssociatedGuide( 'acme/add-to-cart' );
+console.log( 'Tips in this guide:', guide.tipIds );
+console.log( 'Currently showing:', guide.currentTipId );
+console.log( 'Next to show:', guide.nextTipId );
+```
+
+## Contributing to this package
+
+This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.
+
+To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md).
+
+
diff --git a/packages/nux/package.json b/packages/nux/package.json
new file mode 100644
index 00000000000000..6c820345d630c0
--- /dev/null
+++ b/packages/nux/package.json
@@ -0,0 +1,50 @@
+{
+ "name": "@wordpress/nux",
+ "version": "7.1.0",
+ "description": "NUX (New User eXperience) module for WordPress.",
+ "author": "The WordPress Contributors",
+ "license": "GPL-2.0-or-later",
+ "keywords": [
+ "wordpress",
+ "gutenberg",
+ "nux"
+ ],
+ "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/nux/README.md",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/WordPress/gutenberg.git",
+ "directory": "packages/nux"
+ },
+ "bugs": {
+ "url": "https://github.com/WordPress/gutenberg/issues"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "main": "build/index.js",
+ "module": "build-module/index.js",
+ "react-native": "src/index",
+ "sideEffects": [
+ "build-style/**",
+ "src/**/*.scss",
+ "{src,build,build-module}/{index.js,store/index.js}"
+ ],
+ "dependencies": {
+ "@babel/runtime": "^7.16.0",
+ "@wordpress/components": "file:../components",
+ "@wordpress/compose": "file:../compose",
+ "@wordpress/data": "file:../data",
+ "@wordpress/deprecated": "file:../deprecated",
+ "@wordpress/element": "file:../element",
+ "@wordpress/i18n": "file:../i18n",
+ "@wordpress/icons": "file:../icons",
+ "rememo": "^4.0.2"
+ },
+ "peerDependencies": {
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/nux/src/components/dot-tip/README.md b/packages/nux/src/components/dot-tip/README.md
new file mode 100644
index 00000000000000..f143a22a222588
--- /dev/null
+++ b/packages/nux/src/components/dot-tip/README.md
@@ -0,0 +1,38 @@
+# DotTip
+
+`DotTip` is a React component that renders a single _tip_ on the screen. The tip will point to the React element that `DotTip` is nested within. Each tip is uniquely identified by a string passed to `tipId`.
+
+## Usage
+
+```jsx
+
+}
+```
+
+## 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
+
+### position
+
+The direction in which the popover should open relative to its parent node. Specify y- and x-axis as a space-separated string. Supports `"top"`, `"middle"`, `"bottom"` y axis, and `"left"`, `"center"`, `"right"` x axis.
+
+- Type: `String`
+- Required: No
+- Default: `"middle right"`
+
+### children
+
+Any React element or elements can be passed as children. They will be rendered within the tip bubble.
diff --git a/packages/nux/src/components/dot-tip/index.js b/packages/nux/src/components/dot-tip/index.js
new file mode 100644
index 00000000000000..50de7ddb3be9df
--- /dev/null
+++ b/packages/nux/src/components/dot-tip/index.js
@@ -0,0 +1,93 @@
+/**
+ * WordPress dependencies
+ */
+import { compose } from '@wordpress/compose';
+import { Popover, Button } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { withSelect, withDispatch } from '@wordpress/data';
+import { useCallback, useRef } from '@wordpress/element';
+import { close } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+import { store as nuxStore } from '../../store';
+
+function onClick( event ) {
+ // Tips are often nested within buttons. We stop propagation so that clicking
+ // on a tip doesn't result in the button being clicked.
+ event.stopPropagation();
+}
+
+export function DotTip( {
+ position = 'middle right',
+ children,
+ isVisible,
+ hasNextTip,
+ onDismiss,
+ onDisable,
+} ) {
+ const anchorParent = useRef( null );
+ const onFocusOutsideCallback = useCallback(
+ ( event ) => {
+ if ( ! anchorParent.current ) {
+ return;
+ }
+ if ( anchorParent.current.contains( event.relatedTarget ) ) {
+ return;
+ }
+ onDisable();
+ },
+ [ onDisable, anchorParent ]
+ );
+ if ( ! isVisible ) {
+ return null;
+ }
+
+ return (
+
+