@wordpress/interface 4.0.1 → 4.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +66 -1
  3. package/build/components/index.js +16 -0
  4. package/build/components/index.js.map +1 -1
  5. package/build/components/interface-skeleton/index.js +7 -6
  6. package/build/components/interface-skeleton/index.js.map +1 -1
  7. package/build/components/more-menu-dropdown/index.js +52 -0
  8. package/build/components/more-menu-dropdown/index.js.map +1 -0
  9. package/build/components/more-menu-feature-toggle/index.js +63 -0
  10. package/build/components/more-menu-feature-toggle/index.js.map +1 -0
  11. package/build/index.js +2 -2
  12. package/build/index.js.map +1 -1
  13. package/build/store/actions.js +57 -0
  14. package/build/store/actions.js.map +1 -1
  15. package/build/store/index.js +4 -2
  16. package/build/store/index.js.map +1 -1
  17. package/build/store/reducer.js +64 -2
  18. package/build/store/reducer.js.map +1 -1
  19. package/build/store/selectors.js +20 -0
  20. package/build/store/selectors.js.map +1 -1
  21. package/build-module/components/index.js +2 -0
  22. package/build-module/components/index.js.map +1 -1
  23. package/build-module/components/interface-skeleton/index.js +7 -7
  24. package/build-module/components/interface-skeleton/index.js.map +1 -1
  25. package/build-module/components/more-menu-dropdown/index.js +39 -0
  26. package/build-module/components/more-menu-dropdown/index.js.map +1 -0
  27. package/build-module/components/more-menu-feature-toggle/index.js +50 -0
  28. package/build-module/components/more-menu-feature-toggle/index.js.map +1 -0
  29. package/build-module/index.js +1 -1
  30. package/build-module/index.js.map +1 -1
  31. package/build-module/store/actions.js +51 -0
  32. package/build-module/store/actions.js.map +1 -1
  33. package/build-module/store/index.js +4 -2
  34. package/build-module/store/index.js.map +1 -1
  35. package/build-module/store/reducer.js +61 -1
  36. package/build-module/store/reducer.js.map +1 -1
  37. package/build-module/store/selectors.js +18 -0
  38. package/build-module/store/selectors.js.map +1 -1
  39. package/build-style/style-rtl.css +47 -2
  40. package/build-style/style.css +47 -2
  41. package/package.json +12 -11
  42. package/src/components/complementary-area/style.scss +10 -2
  43. package/src/components/complementary-area-header/style.scss +8 -0
  44. package/src/components/index.js +2 -0
  45. package/src/components/interface-skeleton/index.js +5 -5
  46. package/src/components/more-menu-dropdown/README.md +78 -0
  47. package/src/components/more-menu-dropdown/index.js +46 -0
  48. package/src/components/more-menu-dropdown/style.scss +35 -0
  49. package/src/components/more-menu-feature-toggle/README.md +59 -0
  50. package/src/components/more-menu-feature-toggle/index.js +53 -0
  51. package/src/index.js +1 -1
  52. package/src/store/actions.js +48 -0
  53. package/src/store/index.js +4 -2
  54. package/src/store/reducer.js +55 -0
  55. package/src/store/selectors.js +20 -0
  56. package/src/store/test/selectors.js +107 -0
  57. package/src/style.scss +1 -0
@@ -122,6 +122,12 @@
122
122
  }
123
123
  }
124
124
 
125
+ @media (min-width: 782px) {
126
+ .components-panel__header + .interface-complementary-area-header {
127
+ margin-top: 0;
128
+ }
129
+ }
130
+
125
131
  .interface-complementary-area {
126
132
  background: #fff;
127
133
  color: #1e1e1e;
@@ -157,12 +163,18 @@
157
163
  .interface-complementary-area p {
158
164
  margin-top: 0;
159
165
  }
160
- .interface-complementary-area h2,
161
- .interface-complementary-area h3 {
166
+ .interface-complementary-area h2 {
162
167
  font-size: 13px;
163
168
  color: #1e1e1e;
164
169
  margin-bottom: 1.5em;
165
170
  }
171
+ .interface-complementary-area h3 {
172
+ font-size: 11px;
173
+ text-transform: uppercase;
174
+ font-weight: 500;
175
+ color: #1e1e1e;
176
+ margin-bottom: 1.5em;
177
+ }
166
178
  .interface-complementary-area hr {
167
179
  border-top: none;
168
180
  border-bottom: 1px solid #f0f0f0;
@@ -383,6 +395,39 @@ body.is-fullscreen-mode .interface-interface-skeleton {
383
395
  bottom: 0;
384
396
  }
385
397
 
398
+ .interface-more-menu-dropdown {
399
+ margin-left: -4px;
400
+ }
401
+ .interface-more-menu-dropdown .components-button {
402
+ width: auto;
403
+ padding: 0 2px;
404
+ }
405
+ @media (min-width: 600px) {
406
+ .interface-more-menu-dropdown {
407
+ margin-left: 0;
408
+ }
409
+ .interface-more-menu-dropdown .components-button {
410
+ padding: 0 4px;
411
+ }
412
+ }
413
+
414
+ .interface-more-menu-dropdown__content .components-popover__content {
415
+ min-width: 280px;
416
+ }
417
+ @media (min-width: 480px) {
418
+ .interface-more-menu-dropdown__content .components-popover__content {
419
+ width: auto;
420
+ max-width: 480px;
421
+ }
422
+ }
423
+ .interface-more-menu-dropdown__content .components-popover__content .components-dropdown-menu__menu {
424
+ padding: 0;
425
+ }
426
+
427
+ .components-popover.interface-more-menu-dropdown__content {
428
+ z-index: 99998;
429
+ }
430
+
386
431
  .interface-pinned-items {
387
432
  display: flex;
388
433
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/interface",
3
- "version": "4.0.1",
3
+ "version": "4.1.3",
4
4
  "description": "Interface module for WordPress. The package contains shared functionality across the modern JavaScript-based WordPress screens.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -32,20 +32,21 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@babel/runtime": "^7.13.10",
35
- "@wordpress/components": "^16.0.0",
36
- "@wordpress/compose": "^5.0.1",
37
- "@wordpress/data": "^6.0.1",
38
- "@wordpress/deprecated": "^3.2.1",
39
- "@wordpress/element": "^4.0.0",
40
- "@wordpress/i18n": "^4.2.1",
41
- "@wordpress/icons": "^5.0.1",
42
- "@wordpress/plugins": "^4.0.1",
43
- "@wordpress/viewport": "^4.0.1",
35
+ "@wordpress/a11y": "^3.2.3",
36
+ "@wordpress/components": "^19.0.1",
37
+ "@wordpress/compose": "^5.0.5",
38
+ "@wordpress/data": "^6.1.3",
39
+ "@wordpress/deprecated": "^3.2.2",
40
+ "@wordpress/element": "^4.0.3",
41
+ "@wordpress/i18n": "^4.2.3",
42
+ "@wordpress/icons": "^6.1.0",
43
+ "@wordpress/plugins": "^4.0.5",
44
+ "@wordpress/viewport": "^4.0.5",
44
45
  "classnames": "^2.3.1",
45
46
  "lodash": "^4.17.21"
46
47
  },
47
48
  "publishConfig": {
48
49
  "access": "public"
49
50
  },
50
- "gitHead": "8b67fbd424bc67f81c2d825877f0fae726d6363a"
51
+ "gitHead": "393c2b5533837fd637e998d23f0124c081a10df0"
51
52
  }
@@ -35,13 +35,21 @@
35
35
  margin-top: 0;
36
36
  }
37
37
 
38
- h2,
39
- h3 {
38
+ h2 {
40
39
  font-size: $default-font-size;
41
40
  color: $gray-900;
42
41
  margin-bottom: 1.5em;
43
42
  }
44
43
 
44
+ // Subheading style.
45
+ h3 {
46
+ font-size: 11px;
47
+ text-transform: uppercase;
48
+ font-weight: 500;
49
+ color: $gray-900;
50
+ margin-bottom: 1.5em;
51
+ }
52
+
45
53
  hr {
46
54
  border-top: none;
47
55
  border-bottom: 1px solid $gray-100;
@@ -31,3 +31,11 @@
31
31
  }
32
32
  }
33
33
  }
34
+
35
+ // This overrides the negative margins between two consecutives panels.
36
+ // since the first panel is hidden.
37
+ .components-panel__header + .interface-complementary-area-header {
38
+ @include break-medium() {
39
+ margin-top: 0;
40
+ }
41
+ }
@@ -3,4 +3,6 @@ export { default as ComplementaryAreaMoreMenuItem } from './complementary-area-m
3
3
  export { default as FullscreenMode } from './fullscreen-mode';
4
4
  export { default as InterfaceSkeleton } from './interface-skeleton';
5
5
  export { default as PinnedItems } from './pinned-items';
6
+ export { default as MoreMenuDropdown } from './more-menu-dropdown';
7
+ export { default as MoreMenuFeatureToggle } from './more-menu-feature-toggle';
6
8
  export { default as ActionItem } from './action-item';
@@ -9,7 +9,7 @@ import classnames from 'classnames';
9
9
  /**
10
10
  * WordPress dependencies
11
11
  */
12
- import { forwardRef, useEffect, useRef } from '@wordpress/element';
12
+ import { forwardRef, useEffect } from '@wordpress/element';
13
13
  import { __unstableUseNavigateRegions as useNavigateRegions } from '@wordpress/components';
14
14
  import { __ } from '@wordpress/i18n';
15
15
  import { useMergeRefs } from '@wordpress/compose';
@@ -44,8 +44,7 @@ function InterfaceSkeleton(
44
44
  },
45
45
  ref
46
46
  ) {
47
- const fallbackRef = useRef();
48
- const regionsClassName = useNavigateRegions( fallbackRef, shortcuts );
47
+ const navigateRegionsProps = useNavigateRegions( shortcuts );
49
48
 
50
49
  useHTMLClass( 'interface-interface-skeleton__html-container' );
51
50
 
@@ -70,11 +69,12 @@ function InterfaceSkeleton(
70
69
 
71
70
  return (
72
71
  <div
73
- ref={ useMergeRefs( [ ref, fallbackRef ] ) }
72
+ { ...navigateRegionsProps }
73
+ ref={ useMergeRefs( [ ref, navigateRegionsProps.ref ] ) }
74
74
  className={ classnames(
75
75
  className,
76
76
  'interface-interface-skeleton',
77
- regionsClassName,
77
+ navigateRegionsProps.className,
78
78
  !! footer && 'has-footer'
79
79
  ) }
80
80
  >
@@ -0,0 +1,78 @@
1
+ # MoreMenuDropdown
2
+
3
+ `MoreMenuDropdown` is a convenient component for rendering an editor 'more' menu. This is typically a menu that provides:
4
+
5
+ - menu items for quick toggling editor preferences.
6
+ - a way to open dialogs for keyboard shortcuts and editor preferences.
7
+ - links to help.
8
+
9
+ This component implements a `DropdownMenu` component from the `@wordpress/components` package.
10
+
11
+ See also the `MoreMenuFeatureToggle` component in the `@wordpress/interface` package, which provides an easy way to implement a feature toggle as a child of this component. Use with the `MenuGroup`, `MenuItem`, `MenuItemsChoice` components from the `@wordpress/components` package to implement more advanced behaviors.
12
+
13
+ Note that just like the `DropdownMenu` component, this component accepts a render callback, which child elements should be returned from.
14
+
15
+ ## Example
16
+
17
+ ```jsx
18
+ function MyEditorMenu() {
19
+ return (
20
+ <MoreMenuDropdown>
21
+ { () => (
22
+ <MenuGroup label={ __( 'Features' ) }>
23
+ <MoreMenuFeatureToggle
24
+ scope="namespace/editor-or-plugin-name"
25
+ feature="myFeatureName"
26
+ label={ __( 'My feature' ) }
27
+ info={ __( 'A really awesome feature' ) }
28
+ messageActivated={ __( 'My feature activated' )}
29
+ messageDeactivated={ __( 'My feature deactivated' )}
30
+ />
31
+ </MenuGroup>
32
+ ) }
33
+ </MoreMenuDropdown>
34
+ );
35
+ }
36
+ ```
37
+
38
+ ## Props
39
+
40
+ ### as
41
+
42
+ Provide a component that the dropdown should render as. This may be useful if you need `MoreMenuDropdown` to render a `ToolbarDropdownMenu` instead of a `DropdownMenu`. Defaults to `DropdownMenu`.
43
+
44
+ - Type: `Component`
45
+ - Required: No.
46
+
47
+ ### className
48
+
49
+ Provide an additional class name to the dropdown component.
50
+
51
+ - Type: `String`
52
+ - Required: No
53
+
54
+ ### label
55
+
56
+ Change the label of the button that opens the dropdown.
57
+
58
+ - Default: 'Options'
59
+ - Type: `String`
60
+ - Required: No
61
+
62
+ ### popoverProps
63
+
64
+ Override or extend the dropdown's popover props.
65
+
66
+ See the documentation for the `DropdownMenu` and `Popover` components in the `@wordpress/components` package for more information.
67
+
68
+ - Type: `Object`
69
+ - Required: No
70
+
71
+ ### toggleProps
72
+
73
+ Override or extend the dropdown's toggle props.
74
+
75
+ See the documentation for the `DropdownMenu` and `Button` components in the `@wordpress/components` package for more information.
76
+
77
+ - Type: `Object`
78
+ - Required: No
@@ -0,0 +1,46 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { DropdownMenu } from '@wordpress/components';
10
+ import { __ } from '@wordpress/i18n';
11
+ import { moreVertical } from '@wordpress/icons';
12
+
13
+ export default function MoreMenuDropdown( {
14
+ as: DropdownComponent = DropdownMenu,
15
+ className,
16
+ /* translators: button label text should, if possible, be under 16 characters. */
17
+ label = __( 'Options' ),
18
+ popoverProps,
19
+ toggleProps,
20
+ children,
21
+ } ) {
22
+ return (
23
+ <DropdownComponent
24
+ className={ classnames(
25
+ 'interface-more-menu-dropdown',
26
+ className
27
+ ) }
28
+ icon={ moreVertical }
29
+ label={ label }
30
+ popoverProps={ {
31
+ position: 'bottom left',
32
+ ...popoverProps,
33
+ className: classnames(
34
+ 'interface-more-menu-dropdown__content',
35
+ popoverProps?.className
36
+ ),
37
+ } }
38
+ toggleProps={ {
39
+ tooltipPosition: 'bottom',
40
+ ...toggleProps,
41
+ } }
42
+ >
43
+ { ( onClose ) => children( onClose ) }
44
+ </DropdownComponent>
45
+ );
46
+ }
@@ -0,0 +1,35 @@
1
+ .interface-more-menu-dropdown {
2
+ margin-left: -4px;
3
+
4
+ // the padding and margin of the more menu is intentionally non-standard
5
+ .components-button {
6
+ width: auto;
7
+ padding: 0 2px;
8
+ }
9
+
10
+ @include break-small() {
11
+ margin-left: 0;
12
+
13
+ .components-button {
14
+ padding: 0 4px;
15
+ }
16
+ }
17
+ }
18
+
19
+ .interface-more-menu-dropdown__content .components-popover__content {
20
+ min-width: 280px;
21
+
22
+ // Let the menu scale to fit items.
23
+ @include break-mobile() {
24
+ width: auto;
25
+ max-width: $break-mobile;
26
+ }
27
+
28
+ .components-dropdown-menu__menu {
29
+ padding: 0;
30
+ }
31
+ }
32
+
33
+ .components-popover.interface-more-menu-dropdown__content {
34
+ z-index: z-index(".components-popover.edit-widgets-more-menu__content");
35
+ }
@@ -0,0 +1,59 @@
1
+ # MoreMenuFeatureToggle
2
+
3
+ `MoreMenuFeatureToggle` renders a menu item that can be used as a child of the `MoreMenuDropdown` component. The component
4
+ is connected to the interface package's store, and will toggle the value of a 'feature' between true and false.
5
+
6
+ This component implements a `MenuItem` component from the `@wordpress/components` package.
7
+
8
+ ## Props
9
+
10
+ ### scope
11
+
12
+ 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.
13
+
14
+ - Type: `String`
15
+ - Required: Yes
16
+
17
+ ### feature
18
+
19
+ The name of the feature to toggle (e.g. 'fixedToolbar').
20
+
21
+ - Type: `String`
22
+ - Required: Yes
23
+
24
+ ### label
25
+
26
+ A human readable label for the feature.
27
+
28
+ - Type: `String`
29
+ - Required: Yes
30
+
31
+ ### info
32
+
33
+ A human readable description of what this toggle does.
34
+
35
+ - Type: `Object`
36
+ - Required: No
37
+
38
+ ### messageActivated
39
+
40
+ A message read by a screen reader when the feature is activated. (e.g. 'Fixed toolbar activated')
41
+
42
+ - Type: `String`
43
+ - Required: No
44
+
45
+ ### messageDeactivated
46
+
47
+ A message read by a screen reader when the feature is deactivated. (e.g. 'Fixed toolbar deactivated')
48
+
49
+ - Type: `String`
50
+ - Required: No
51
+
52
+ ### shortcut
53
+
54
+ A keyboard shortcut for the feature. This is just used for display purposes and the implementation of the shortcut should be handled separately.
55
+
56
+ Consider using the `displayShortcut` helper from the `@wordpress/keycodes` package for this prop.
57
+
58
+ - Type: `Array`
59
+ - Required: No
@@ -0,0 +1,53 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useSelect, useDispatch } from '@wordpress/data';
5
+ import { MenuItem } from '@wordpress/components';
6
+ import { __ } from '@wordpress/i18n';
7
+ import { check } from '@wordpress/icons';
8
+ import { speak } from '@wordpress/a11y';
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ import { store as interfaceStore } from '../../store';
14
+
15
+ export default function MoreMenuFeatureToggle( {
16
+ scope,
17
+ label,
18
+ info,
19
+ messageActivated,
20
+ messageDeactivated,
21
+ shortcut,
22
+ feature,
23
+ } ) {
24
+ const isActive = useSelect(
25
+ ( select ) =>
26
+ select( interfaceStore ).isFeatureActive( scope, feature ),
27
+ [ feature ]
28
+ );
29
+ const { toggleFeature } = useDispatch( interfaceStore );
30
+ const speakMessage = () => {
31
+ if ( isActive ) {
32
+ speak( messageDeactivated || __( 'Feature deactivated' ) );
33
+ } else {
34
+ speak( messageActivated || __( 'Feature activated' ) );
35
+ }
36
+ };
37
+
38
+ return (
39
+ <MenuItem
40
+ icon={ isActive && check }
41
+ isSelected={ isActive }
42
+ onClick={ () => {
43
+ toggleFeature( scope, feature );
44
+ speakMessage();
45
+ } }
46
+ role="menuitemcheckbox"
47
+ info={ info }
48
+ shortcut={ shortcut }
49
+ >
50
+ { label }
51
+ </MenuItem>
52
+ );
53
+ }
package/src/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { store } from './store';
2
1
  export * from './components';
2
+ export { store } from './store';
@@ -82,3 +82,51 @@ export function pinItem( scope, itemId ) {
82
82
  export function unpinItem( scope, itemId ) {
83
83
  return setMultipleEnableItem( 'pinnedItems', scope, itemId, false );
84
84
  }
85
+
86
+ /**
87
+ * Returns an action object used in signalling that a feature should be toggled.
88
+ *
89
+ * @param {string} scope The feature scope (e.g. core/edit-post).
90
+ * @param {string} featureName The feature name.
91
+ */
92
+ export function toggleFeature( scope, featureName ) {
93
+ return function ( { select, dispatch } ) {
94
+ const currentValue = select.isFeatureActive( scope, featureName );
95
+ dispatch.setFeatureValue( scope, featureName, ! currentValue );
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Returns an action object used in signalling that a feature should be set to
101
+ * a true or false value
102
+ *
103
+ * @param {string} scope The feature scope (e.g. core/edit-post).
104
+ * @param {string} featureName The feature name.
105
+ * @param {boolean} value The value to set.
106
+ *
107
+ * @return {Object} Action object.
108
+ */
109
+ export function setFeatureValue( scope, featureName, value ) {
110
+ return {
111
+ type: 'SET_FEATURE_VALUE',
112
+ scope,
113
+ featureName,
114
+ value: !! value,
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Returns an action object used in signalling that defaults should be set for features.
120
+ *
121
+ * @param {string} scope The feature scope (e.g. core/edit-post).
122
+ * @param {Object<string, boolean>} defaults A key/value map of feature names to values.
123
+ *
124
+ * @return {Object} Action object.
125
+ */
126
+ export function setFeatureDefaults( scope, defaults ) {
127
+ return {
128
+ type: 'SET_FEATURE_DEFAULTS',
129
+ scope,
130
+ defaults,
131
+ };
132
+ }
@@ -22,7 +22,8 @@ export const store = createReduxStore( STORE_NAME, {
22
22
  reducer,
23
23
  actions,
24
24
  selectors,
25
- persist: [ 'enableItems' ],
25
+ persist: [ 'enableItems', 'preferences' ],
26
+ __experimentalUseThunks: true,
26
27
  } );
27
28
 
28
29
  // Once we build a more generic persistence plugin that works across types of stores
@@ -31,5 +32,6 @@ registerStore( STORE_NAME, {
31
32
  reducer,
32
33
  actions,
33
34
  selectors,
34
- persist: [ 'enableItems' ],
35
+ persist: [ 'enableItems', 'preferences' ],
36
+ __experimentalUseThunks: true,
35
37
  } );
@@ -78,6 +78,59 @@ export function multipleEnableItems(
78
78
  };
79
79
  }
80
80
 
81
+ /**
82
+ * Reducer returning the defaults for user preferences.
83
+ *
84
+ * This is kept intentionally separate from the preferences
85
+ * themselves so that defaults are not persisted.
86
+ *
87
+ * @param {Object} state Current state.
88
+ * @param {Object} action Dispatched action.
89
+ *
90
+ * @return {Object} Updated state.
91
+ */
92
+ export const preferenceDefaults = combineReducers( {
93
+ features( state = {}, action ) {
94
+ if ( action.type === 'SET_FEATURE_DEFAULTS' ) {
95
+ const { scope, defaults } = action;
96
+ return {
97
+ ...state,
98
+ [ scope ]: {
99
+ ...state[ scope ],
100
+ ...defaults,
101
+ },
102
+ };
103
+ }
104
+
105
+ return state;
106
+ },
107
+ } );
108
+
109
+ /**
110
+ * Reducer returning the user preferences.
111
+ *
112
+ * @param {Object} state Current state.
113
+ * @param {Object} action Dispatched action.
114
+ *
115
+ * @return {Object} Updated state.
116
+ */
117
+ export const preferences = combineReducers( {
118
+ features( state = {}, action ) {
119
+ if ( action.type === 'SET_FEATURE_VALUE' ) {
120
+ const { scope, featureName, value } = action;
121
+ return {
122
+ ...state,
123
+ [ scope ]: {
124
+ ...state[ scope ],
125
+ [ featureName ]: value,
126
+ },
127
+ };
128
+ }
129
+
130
+ return state;
131
+ },
132
+ } );
133
+
81
134
  const enableItems = combineReducers( {
82
135
  singleEnableItems,
83
136
  multipleEnableItems,
@@ -85,4 +138,6 @@ const enableItems = combineReducers( {
85
138
 
86
139
  export default combineReducers( {
87
140
  enableItems,
141
+ preferenceDefaults,
142
+ preferences,
88
143
  } );
@@ -61,3 +61,23 @@ export function isItemPinned( state, scope, item ) {
61
61
  false
62
62
  );
63
63
  }
64
+
65
+ /**
66
+ * Returns a boolean indicating whether a feature is active for a particular
67
+ * scope.
68
+ *
69
+ * @param {Object} state The store state.
70
+ * @param {string} scope The scope of the feature (e.g. core/edit-post).
71
+ * @param {string} featureName The name of the feature.
72
+ *
73
+ * @return {boolean} Is the feature enabled?
74
+ */
75
+ export function isFeatureActive( state, scope, featureName ) {
76
+ const featureValue = state.preferences.features[ scope ]?.[ featureName ];
77
+ const defaultedFeatureValue =
78
+ featureValue !== undefined
79
+ ? featureValue
80
+ : state.preferenceDefaults.features[ scope ]?.[ featureName ];
81
+
82
+ return !! defaultedFeatureValue;
83
+ }