-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new @wordpress/preferences
package
#38873
Changes from all commits
a1dd183
a7dcf28
ce948ef
6fbe9f8
733246a
4bee2b7
87f4cfc
150fbe4
64ed509
dfbdf41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package-lock=false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. --> | ||
|
||
## Unreleased | ||
|
||
## 1.0.0 | ||
|
||
- Initial version of the package. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
# Preferences | ||
|
||
Utilities for storing WordPress preferences. | ||
|
||
## Installation | ||
|
||
Install the module | ||
|
||
```bash | ||
npm install @wordpress/preferences --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._ | ||
|
||
## Examples | ||
|
||
### Data store | ||
|
||
Preferences are persisted values of any kind. | ||
|
||
Set the default preferences for any features on initialization by dispatching an action: | ||
|
||
```js | ||
import { dispatch } from '@wordpress/data'; | ||
import { store as preferencesStore } from '@wordpress/preferences'; | ||
|
||
function initialize() { | ||
// ... | ||
|
||
dispatch( preferencesStore ).setDefaults( | ||
'namespace/editor-or-plugin-name', | ||
{ | ||
myBooleanFeature: true, | ||
} | ||
); | ||
|
||
// ... | ||
} | ||
``` | ||
|
||
Or the `get` selector to get a preference value, and the `set` action to update a preference to any value: | ||
|
||
```js | ||
wp.data | ||
.select( 'core/preferences' ) | ||
.get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // 1 | ||
wp.data | ||
.dispatch( 'core/preferences' ) | ||
.set( 'namespace/editor-or-plugin-name', 'myPreferenceName', 2 ); | ||
wp.data | ||
.select( 'core/preferences' ) | ||
.get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // 2 | ||
``` | ||
|
||
Use the `toggle` action to flip a boolean preference between `true` and `false`: | ||
|
||
```js | ||
wp.data | ||
.select( 'core/preferences' ) | ||
.get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // true | ||
wp.data | ||
.dispatch( 'core/preferences' ) | ||
.toggle( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); | ||
wp.data | ||
.select( 'core/preferences' ) | ||
.get( 'namespace/editor-or-plugin-name', 'myPreferenceName' ); // false | ||
``` | ||
|
||
### Components | ||
|
||
The `PreferenceToggleMenuItem` components can be used with a `DropdownMenu` to implement a menu for changing preferences. | ||
|
||
Also see the `MoreMenuDropdown` component from the `@wordpress/interface` package for implementing a more menu. | ||
|
||
```jsx | ||
function MyEditorMenu() { | ||
return ( | ||
<MoreMenuDropdown> | ||
{ () => ( | ||
<MenuGroup label={ __( 'Features' ) }> | ||
<PreferenceToggleMenuItem | ||
scope="namespace/editor-or-plugin-name" | ||
name="myPreferenceName" | ||
label={ __( 'My feature' ) } | ||
info={ __( 'A really awesome feature' ) } | ||
messageActivated={ __( 'My feature activated' ) } | ||
messageDeactivated={ __( 'My feature deactivated' ) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is not new to this PR but in other components that needed configurable labels, we had a single object prop like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can take a look at that in a follow-up. 👍 |
||
/> | ||
</MenuGroup> | ||
) } | ||
</MoreMenuDropdown> | ||
); | ||
} | ||
``` | ||
|
||
## API Reference | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we take this opportunity to simplify further. What I have in mind could be something like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, the 'feature' thing doesn't really fit for a generic preferences package. I'll work on the naming. 👍 I think it might good to have a On this subject, I've been wondering if we want any built-in type safety for these preferences. Or whether it should just be a freeform key/value store. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If we go the type safety road, it almost feels like we should "register" preferences. I'm not sure it's worth the added complexity. I understand that if we don't do it from the start it might harder to do later. Any use-cases that benefits from the type safety? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There definitely aren't many in the editor, so I think you're right that it's not worth the complexity. The only one really is the Code Editor/Visual Editor option. There is also the option of building more complex behavior around the existing actions and selectors. For example, if an editor needed a preference with some kind of validation, it could add that validation in its own action that calls through to |
||
|
||
### Actions | ||
|
||
The following set of dispatching action creators are available on the object returned by `wp.data.dispatch( 'core/preferences' )`: | ||
|
||
<!-- START TOKEN(Autogenerated actions|src/store/actions.js) --> | ||
|
||
#### set | ||
|
||
Returns an action object used in signalling that a preference should be set | ||
to a value | ||
|
||
_Parameters_ | ||
|
||
- _scope_ `string`: The preference scope (e.g. core/edit-post). | ||
- _name_ `string`: The preference name. | ||
- _value_ `*`: The value to set. | ||
|
||
_Returns_ | ||
|
||
- `Object`: Action object. | ||
|
||
#### setDefaults | ||
|
||
Returns an action object used in signalling that preference defaults should | ||
be set. | ||
|
||
_Parameters_ | ||
|
||
- _scope_ `string`: The preference scope (e.g. core/edit-post). | ||
- _defaults_ `Object<string, *>`: A key/value map of preference names to values. | ||
|
||
_Returns_ | ||
|
||
- `Object`: Action object. | ||
|
||
#### toggle | ||
|
||
Returns an action object used in signalling that a preference should be | ||
toggled. | ||
|
||
_Parameters_ | ||
|
||
- _scope_ `string`: The preference scope (e.g. core/edit-post). | ||
- _name_ `string`: The preference name. | ||
|
||
<!-- END TOKEN(Autogenerated actions|src/store/actions.js) --> | ||
|
||
### Selectors | ||
|
||
The following selectors are available on the object returned by `wp.data.select( 'core/preferences' )`: | ||
|
||
<!-- START TOKEN(Autogenerated selectors|src/store/selectors.js) --> | ||
|
||
#### get | ||
|
||
Returns a boolean indicating whether a prefer is active for a particular | ||
scope. | ||
|
||
_Parameters_ | ||
|
||
- _state_ `Object`: The store state. | ||
- _scope_ `string`: The scope of the feature (e.g. core/edit-post). | ||
- _name_ `string`: The name of the feature. | ||
|
||
_Returns_ | ||
|
||
- `*`: Is the feature enabled? | ||
|
||
<!-- END TOKEN(Autogenerated selectors|src/store/selectors.js) --> | ||
|
||
## 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). | ||
|
||
<br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
{ | ||
"name": "@wordpress/preferences", | ||
"version": "1.0.0-prerelease", | ||
"private": true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other public packages depend on this package so it shouldn't be private. See #39390. |
||
"description": "Utilities for managing WordPress preferences.", | ||
"author": "The WordPress Contributors", | ||
"license": "GPL-2.0-or-later", | ||
"keywords": [ | ||
"wordpress", | ||
"gutenberg", | ||
"preferences", | ||
"settings", | ||
"options" | ||
], | ||
"homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/preferences/README.md", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/WordPress/gutenberg.git", | ||
"directory": "packages/preferences" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/WordPress/gutenberg/issues" | ||
}, | ||
"engines": { | ||
"node": ">=12" | ||
}, | ||
"main": "build/index.js", | ||
"module": "build-module/index.js", | ||
"react-native": "src/index", | ||
"types": "build-types", | ||
"sideEffects": false, | ||
"dependencies": { | ||
"@babel/runtime": "^7.16.0", | ||
"@wordpress/a11y": "file:../a11y", | ||
"@wordpress/components": "file:../components", | ||
"@wordpress/data": "file:../data", | ||
"@wordpress/i18n": "file:../i18n", | ||
"@wordpress/icons": "file:../icons", | ||
"classnames": "^2.3.1" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as PreferenceToggleMenuItem } from './preference-toggle-menu-item'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# PreferenceToggleMenuItem | ||
|
||
`PreferenceToggleMenuItem` renders a menu item that is connected to the preference package's store, and will toggle the value of a 'preference' between true and false. | ||
|
||
This component implements a `MenuItem` component from the `@wordpress/components` package. | ||
|
||
## Props | ||
|
||
### scope | ||
|
||
The 'scope' of the feature. This is usually a namespaced string that represents the name of the editor (e.g. 'core/edit-post'), and often matches the name of the store for the editor. | ||
|
||
- Type: `String` | ||
- Required: Yes | ||
|
||
### name | ||
|
||
The name of the preference to toggle (e.g. 'fixedToolbar'). | ||
|
||
- Type: `String` | ||
- Required: Yes | ||
|
||
### label | ||
|
||
A human readable label for the feature. | ||
|
||
- Type: `String` | ||
- Required: Yes | ||
|
||
### info | ||
|
||
A human readable description of what this toggle does. | ||
|
||
- Type: `Object` | ||
- Required: No | ||
|
||
### messageActivated | ||
|
||
A message read by a screen reader when the feature is activated. (e.g. 'Fixed toolbar activated') | ||
|
||
- Type: `String` | ||
- Required: No | ||
|
||
### messageDeactivated | ||
|
||
A message read by a screen reader when the feature is deactivated. (e.g. 'Fixed toolbar deactivated') | ||
|
||
- Type: `String` | ||
- Required: No | ||
|
||
### shortcut | ||
|
||
A keyboard shortcut for the feature. This is just used for display purposes and the implementation of the shortcut should be handled separately. | ||
|
||
Consider using the `displayShortcut` helper from the `@wordpress/keycodes` package for this prop. | ||
|
||
- Type: `Array` | ||
- Required: No |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useSelect, useDispatch } from '@wordpress/data'; | ||
import { MenuItem } from '@wordpress/components'; | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { check } from '@wordpress/icons'; | ||
import { speak } from '@wordpress/a11y'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { store as preferencesStore } from '../../store'; | ||
|
||
export default function PreferenceToggleMenuItem( { | ||
scope, | ||
name, | ||
label, | ||
info, | ||
messageActivated, | ||
messageDeactivated, | ||
shortcut, | ||
} ) { | ||
const isActive = useSelect( | ||
( select ) => !! select( preferencesStore ).get( scope, name ), | ||
[ name ] | ||
); | ||
const { toggle } = useDispatch( preferencesStore ); | ||
const speakMessage = () => { | ||
if ( isActive ) { | ||
const message = | ||
messageDeactivated || | ||
sprintf( | ||
/* translators: %s: preference name, e.g. 'Fullscreen mode' */ | ||
__( 'Preference deactivated - %s' ), | ||
label | ||
); | ||
speak( message ); | ||
} else { | ||
const message = | ||
messageActivated || | ||
sprintf( | ||
/* translators: %s: preference name, e.g. 'Fullscreen mode' */ | ||
__( 'Preference activated - %s' ), | ||
label | ||
); | ||
speak( message ); | ||
} | ||
}; | ||
|
||
return ( | ||
<MenuItem | ||
icon={ isActive && check } | ||
isSelected={ isActive } | ||
onClick={ () => { | ||
toggle( scope, name ); | ||
speakMessage(); | ||
} } | ||
role="menuitemcheckbox" | ||
info={ info } | ||
shortcut={ shortcut } | ||
> | ||
{ label } | ||
</MenuItem> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './components'; | ||
export { store } from './store'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be "editor" instead of "WordPress" preferences?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should we limit this to "editor"? That's for sure the main use-case for us but does it prevent us to make this package ready for any client-side rendered WP-Admin page?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, but should we restrict it to WordPress? This package isn't inherently tied to WP, unless I'm missing something. It could potentially be used by non-WP block editors too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true. It may depend on how the persistence functionality is implemented in the future, as at some point we'll want to persist settings to the database via the REST API rather than localstorage.
If that can be done in a way where it's configurable or separate to the package (which I think it probably should be), it would be fine to drop the 'WordPress' from this, but for now I don't think it's harmful.