@wordpress/patterns 1.1.0

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 (73) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.md +788 -0
  3. package/README.md +26 -0
  4. package/build/components/create-pattern-modal.js +100 -0
  5. package/build/components/create-pattern-modal.js.map +1 -0
  6. package/build/components/index.js +32 -0
  7. package/build/components/index.js.map +1 -0
  8. package/build/components/pattern-convert-button.js +102 -0
  9. package/build/components/pattern-convert-button.js.map +1 -0
  10. package/build/components/patterns-manage-button.js +72 -0
  11. package/build/components/patterns-manage-button.js.map +1 -0
  12. package/build/index.js +18 -0
  13. package/build/index.js.map +1 -0
  14. package/build/index.native.js +19 -0
  15. package/build/index.native.js.map +1 -0
  16. package/build/lock-unlock.js +18 -0
  17. package/build/lock-unlock.js.map +1 -0
  18. package/build/private-apis.js +21 -0
  19. package/build/private-apis.js.map +1 -0
  20. package/build/store/actions.js +77 -0
  21. package/build/store/actions.js.map +1 -0
  22. package/build/store/constants.js +12 -0
  23. package/build/store/constants.js.map +1 -0
  24. package/build/store/index.js +49 -0
  25. package/build/store/index.js.map +1 -0
  26. package/build/store/reducer.js +26 -0
  27. package/build/store/reducer.js.map +1 -0
  28. package/build/store/selectors.js +17 -0
  29. package/build/store/selectors.js.map +1 -0
  30. package/build-module/components/create-pattern-modal.js +91 -0
  31. package/build-module/components/create-pattern-modal.js.map +1 -0
  32. package/build-module/components/index.js +24 -0
  33. package/build-module/components/index.js.map +1 -0
  34. package/build-module/components/pattern-convert-button.js +95 -0
  35. package/build-module/components/pattern-convert-button.js.map +1 -0
  36. package/build-module/components/patterns-manage-button.js +64 -0
  37. package/build-module/components/patterns-manage-button.js.map +1 -0
  38. package/build-module/index.js +6 -0
  39. package/build-module/index.js.map +1 -0
  40. package/build-module/index.native.js +11 -0
  41. package/build-module/index.native.js.map +1 -0
  42. package/build-module/lock-unlock.js +9 -0
  43. package/build-module/lock-unlock.js.map +1 -0
  44. package/build-module/private-apis.js +12 -0
  45. package/build-module/private-apis.js.map +1 -0
  46. package/build-module/store/actions.js +69 -0
  47. package/build-module/store/actions.js.map +1 -0
  48. package/build-module/store/constants.js +5 -0
  49. package/build-module/store/constants.js.map +1 -0
  50. package/build-module/store/index.js +38 -0
  51. package/build-module/store/index.js.map +1 -0
  52. package/build-module/store/reducer.js +17 -0
  53. package/build-module/store/reducer.js.map +1 -0
  54. package/build-module/store/selectors.js +11 -0
  55. package/build-module/store/selectors.js.map +1 -0
  56. package/build-style/style-rtl.css +108 -0
  57. package/build-style/style.css +108 -0
  58. package/package.json +55 -0
  59. package/src/components/create-pattern-modal.js +121 -0
  60. package/src/components/index.js +30 -0
  61. package/src/components/pattern-convert-button.js +123 -0
  62. package/src/components/patterns-manage-button.js +77 -0
  63. package/src/components/style.scss +3 -0
  64. package/src/index.js +6 -0
  65. package/src/index.native.js +11 -0
  66. package/src/lock-unlock.js +9 -0
  67. package/src/private-apis.js +12 -0
  68. package/src/store/actions.js +99 -0
  69. package/src/store/constants.js +4 -0
  70. package/src/store/index.js +38 -0
  71. package/src/store/reducer.js +19 -0
  72. package/src/store/selectors.js +10 -0
  73. package/src/style.scss +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"names":["createReduxStore","register","reducer","actions","STORE_NAME","selectors","storeConfig","store"],"sources":["@wordpress/patterns/src/store/index.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { createReduxStore, register } from '@wordpress/data';\n\n/**\n * Internal dependencies\n */\nimport reducer from './reducer';\nimport * as actions from './actions';\nimport { STORE_NAME } from './constants';\nimport * as selectors from './selectors';\n\n/**\n * Post editor data store configuration.\n *\n * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#registerStore\n *\n * @type {Object}\n */\nexport const storeConfig = {\n\treducer,\n\tselectors,\n\tactions,\n};\n\n/**\n * Store definition for the editor namespace.\n *\n * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore\n *\n * @type {Object}\n */\nexport const store = createReduxStore( STORE_NAME, {\n\t...storeConfig,\n} );\n\nregister( store );\n"],"mappings":"AAAA;AACA;AACA;AACA,SAASA,gBAAgB,EAAEC,QAAQ,QAAQ,iBAAiB;;AAE5D;AACA;AACA;AACA,OAAOC,OAAO,MAAM,WAAW;AAC/B,OAAO,KAAKC,OAAO,MAAM,WAAW;AACpC,SAASC,UAAU,QAAQ,aAAa;AACxC,OAAO,KAAKC,SAAS,MAAM,aAAa;;AAExC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,WAAW,GAAG;EAC1BJ,OAAO;EACPG,SAAS;EACTF;AACD,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMI,KAAK,GAAGP,gBAAgB,CAAEI,UAAU,EAAE;EAClD,GAAGE;AACJ,CAAE,CAAC;AAEHL,QAAQ,CAAEM,KAAM,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { combineReducers } from '@wordpress/data';
5
+ export function isEditingPattern(state = {}, action) {
6
+ if (action?.type === 'SET_EDITING_PATTERN') {
7
+ return {
8
+ ...state,
9
+ [action.clientId]: action.isEditing
10
+ };
11
+ }
12
+ return state;
13
+ }
14
+ export default combineReducers({
15
+ isEditingPattern
16
+ });
17
+ //# sourceMappingURL=reducer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["combineReducers","isEditingPattern","state","action","type","clientId","isEditing"],"sources":["@wordpress/patterns/src/store/reducer.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport { combineReducers } from '@wordpress/data';\n\nexport function isEditingPattern( state = {}, action ) {\n\tif ( action?.type === 'SET_EDITING_PATTERN' ) {\n\t\treturn {\n\t\t\t...state,\n\t\t\t[ action.clientId ]: action.isEditing,\n\t\t};\n\t}\n\n\treturn state;\n}\n\nexport default combineReducers( {\n\tisEditingPattern,\n} );\n"],"mappings":"AAAA;AACA;AACA;AACA,SAASA,eAAe,QAAQ,iBAAiB;AAEjD,OAAO,SAASC,gBAAgBA,CAAEC,KAAK,GAAG,CAAC,CAAC,EAAEC,MAAM,EAAG;EACtD,IAAKA,MAAM,EAAEC,IAAI,KAAK,qBAAqB,EAAG;IAC7C,OAAO;MACN,GAAGF,KAAK;MACR,CAAEC,MAAM,CAACE,QAAQ,GAAIF,MAAM,CAACG;IAC7B,CAAC;EACF;EAEA,OAAOJ,KAAK;AACb;AAEA,eAAeF,eAAe,CAAE;EAC/BC;AACD,CAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Returns true if pattern is in the editing state.
3
+ *
4
+ * @param {Object} state Global application state.
5
+ * @param {number} clientId the clientID of the block.
6
+ * @return {boolean} Whether the pattern is in the editing state.
7
+ */
8
+ export function __experimentalIsEditingPattern(state, clientId) {
9
+ return state.isEditingPattern[clientId];
10
+ }
11
+ //# sourceMappingURL=selectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["__experimentalIsEditingPattern","state","clientId","isEditingPattern"],"sources":["@wordpress/patterns/src/store/selectors.js"],"sourcesContent":["/**\n * Returns true if pattern is in the editing state.\n *\n * @param {Object} state Global application state.\n * @param {number} clientId the clientID of the block.\n * @return {boolean} Whether the pattern is in the editing state.\n */\nexport function __experimentalIsEditingPattern( state, clientId ) {\n\treturn state.isEditingPattern[ clientId ];\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASA,8BAA8BA,CAAEC,KAAK,EAAEC,QAAQ,EAAG;EACjE,OAAOD,KAAK,CAACE,gBAAgB,CAAED,QAAQ,CAAE;AAC1C"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Converts a hex value into the rgb equivalent.
3
+ *
4
+ * @param {string} hex - the hexadecimal value to convert
5
+ * @return {string} comma separated rgb values
6
+ */
7
+ /**
8
+ * Colors
9
+ */
10
+ /**
11
+ * Breakpoints & Media Queries
12
+ */
13
+ /**
14
+ * SCSS Variables.
15
+ *
16
+ * Please use variables from this sheet to ensure consistency across the UI.
17
+ * Don't add to this sheet unless you're pretty sure the value will be reused in many places.
18
+ * For example, don't add rules to this sheet that affect block visuals. It's purely for UI.
19
+ */
20
+ /**
21
+ * Converts a hex value into the rgb equivalent.
22
+ *
23
+ * @param {string} hex - the hexadecimal value to convert
24
+ * @return {string} comma separated rgb values
25
+ */
26
+ /**
27
+ * Colors
28
+ */
29
+ /**
30
+ * Fonts & basic variables.
31
+ */
32
+ /**
33
+ * Grid System.
34
+ * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/
35
+ */
36
+ /**
37
+ * Dimensions.
38
+ */
39
+ /**
40
+ * Shadows.
41
+ */
42
+ /**
43
+ * Editor widths.
44
+ */
45
+ /**
46
+ * Block & Editor UI.
47
+ */
48
+ /**
49
+ * Block paddings.
50
+ */
51
+ /**
52
+ * React Native specific.
53
+ * These variables do not appear to be used anywhere else.
54
+ */
55
+ /**
56
+ * Converts a hex value into the rgb equivalent.
57
+ *
58
+ * @param {string} hex - the hexadecimal value to convert
59
+ * @return {string} comma separated rgb values
60
+ */
61
+ /**
62
+ * Long content fade mixin
63
+ *
64
+ * Creates a fading overlay to signify that the content is longer
65
+ * than the space allows.
66
+ */
67
+ /**
68
+ * Breakpoint mixins
69
+ */
70
+ /**
71
+ * Focus styles.
72
+ */
73
+ /**
74
+ * Applies editor left position to the selector passed as argument
75
+ */
76
+ /**
77
+ * Styles that are reused verbatim in a few places
78
+ */
79
+ /**
80
+ * Allows users to opt-out of animations via OS-level preferences.
81
+ */
82
+ /**
83
+ * Reset default styles for JavaScript UI based pages.
84
+ * This is a WP-admin agnostic reset
85
+ */
86
+ /**
87
+ * Reset the WP Admin page styles for Gutenberg-like pages.
88
+ */
89
+ :root {
90
+ --wp-admin-theme-color: #007cba;
91
+ --wp-admin-theme-color--rgb: 0, 124, 186;
92
+ --wp-admin-theme-color-darker-10: #006ba1;
93
+ --wp-admin-theme-color-darker-10--rgb: 0, 107, 161;
94
+ --wp-admin-theme-color-darker-20: #005a87;
95
+ --wp-admin-theme-color-darker-20--rgb: 0, 90, 135;
96
+ --wp-admin-border-width-focus: 2px;
97
+ --wp-block-synced-color: #7a00df;
98
+ --wp-block-synced-color--rgb: 122, 0, 223;
99
+ }
100
+ @media (min-resolution: 192dpi) {
101
+ :root {
102
+ --wp-admin-border-width-focus: 1.5px;
103
+ }
104
+ }
105
+
106
+ .patterns-menu-items__convert-modal {
107
+ z-index: 1000001;
108
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Converts a hex value into the rgb equivalent.
3
+ *
4
+ * @param {string} hex - the hexadecimal value to convert
5
+ * @return {string} comma separated rgb values
6
+ */
7
+ /**
8
+ * Colors
9
+ */
10
+ /**
11
+ * Breakpoints & Media Queries
12
+ */
13
+ /**
14
+ * SCSS Variables.
15
+ *
16
+ * Please use variables from this sheet to ensure consistency across the UI.
17
+ * Don't add to this sheet unless you're pretty sure the value will be reused in many places.
18
+ * For example, don't add rules to this sheet that affect block visuals. It's purely for UI.
19
+ */
20
+ /**
21
+ * Converts a hex value into the rgb equivalent.
22
+ *
23
+ * @param {string} hex - the hexadecimal value to convert
24
+ * @return {string} comma separated rgb values
25
+ */
26
+ /**
27
+ * Colors
28
+ */
29
+ /**
30
+ * Fonts & basic variables.
31
+ */
32
+ /**
33
+ * Grid System.
34
+ * https://make.wordpress.org/design/2019/10/31/proposal-a-consistent-spacing-system-for-wordpress/
35
+ */
36
+ /**
37
+ * Dimensions.
38
+ */
39
+ /**
40
+ * Shadows.
41
+ */
42
+ /**
43
+ * Editor widths.
44
+ */
45
+ /**
46
+ * Block & Editor UI.
47
+ */
48
+ /**
49
+ * Block paddings.
50
+ */
51
+ /**
52
+ * React Native specific.
53
+ * These variables do not appear to be used anywhere else.
54
+ */
55
+ /**
56
+ * Converts a hex value into the rgb equivalent.
57
+ *
58
+ * @param {string} hex - the hexadecimal value to convert
59
+ * @return {string} comma separated rgb values
60
+ */
61
+ /**
62
+ * Long content fade mixin
63
+ *
64
+ * Creates a fading overlay to signify that the content is longer
65
+ * than the space allows.
66
+ */
67
+ /**
68
+ * Breakpoint mixins
69
+ */
70
+ /**
71
+ * Focus styles.
72
+ */
73
+ /**
74
+ * Applies editor left position to the selector passed as argument
75
+ */
76
+ /**
77
+ * Styles that are reused verbatim in a few places
78
+ */
79
+ /**
80
+ * Allows users to opt-out of animations via OS-level preferences.
81
+ */
82
+ /**
83
+ * Reset default styles for JavaScript UI based pages.
84
+ * This is a WP-admin agnostic reset
85
+ */
86
+ /**
87
+ * Reset the WP Admin page styles for Gutenberg-like pages.
88
+ */
89
+ :root {
90
+ --wp-admin-theme-color: #007cba;
91
+ --wp-admin-theme-color--rgb: 0, 124, 186;
92
+ --wp-admin-theme-color-darker-10: #006ba1;
93
+ --wp-admin-theme-color-darker-10--rgb: 0, 107, 161;
94
+ --wp-admin-theme-color-darker-20: #005a87;
95
+ --wp-admin-theme-color-darker-20--rgb: 0, 90, 135;
96
+ --wp-admin-border-width-focus: 2px;
97
+ --wp-block-synced-color: #7a00df;
98
+ --wp-block-synced-color--rgb: 122, 0, 223;
99
+ }
100
+ @media (min-resolution: 192dpi) {
101
+ :root {
102
+ --wp-admin-border-width-focus: 1.5px;
103
+ }
104
+ }
105
+
106
+ .patterns-menu-items__convert-modal {
107
+ z-index: 1000001;
108
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@wordpress/patterns",
3
+ "version": "1.1.0",
4
+ "description": "Management of user pattern editing.",
5
+ "author": "The WordPress Contributors",
6
+ "license": "GPL-2.0-or-later",
7
+ "keywords": [
8
+ "wordpress",
9
+ "gutenberg",
10
+ "patterns"
11
+ ],
12
+ "homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/patterns/README.md",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/WordPress/gutenberg.git",
16
+ "directory": "packages/patterns"
17
+ },
18
+ "bugs": {
19
+ "url": "https://github.com/WordPress/gutenberg/issues"
20
+ },
21
+ "engines": {
22
+ "node": ">=16.0.0",
23
+ "npm": ">=8 <9"
24
+ },
25
+ "main": "build/index.js",
26
+ "module": "build-module/index.js",
27
+ "react-native": "src/index",
28
+ "sideEffects": [
29
+ "build-style/**",
30
+ "src/**/*.scss",
31
+ "{src,build,build-module}/{index.js,store/index.js,hooks/**}"
32
+ ],
33
+ "dependencies": {
34
+ "@wordpress/block-editor": "^12.8.0",
35
+ "@wordpress/blocks": "^12.17.0",
36
+ "@wordpress/components": "^25.6.0",
37
+ "@wordpress/compose": "^6.17.0",
38
+ "@wordpress/core-data": "^6.17.0",
39
+ "@wordpress/data": "^9.10.0",
40
+ "@wordpress/element": "^5.17.0",
41
+ "@wordpress/i18n": "^4.40.0",
42
+ "@wordpress/icons": "^9.31.0",
43
+ "@wordpress/notices": "^4.8.0",
44
+ "@wordpress/private-apis": "^0.22.0",
45
+ "@wordpress/url": "^3.41.0"
46
+ },
47
+ "peerDependencies": {
48
+ "react": "^18.0.0",
49
+ "react-dom": "^18.0.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "gitHead": "78a288d55b83a713b2f7d98d5a855c0771a2afc6"
55
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ Modal,
6
+ Button,
7
+ TextControl,
8
+ __experimentalHStack as HStack,
9
+ __experimentalVStack as VStack,
10
+ ToggleControl,
11
+ } from '@wordpress/components';
12
+ import { __ } from '@wordpress/i18n';
13
+ import { useState, useCallback } from '@wordpress/element';
14
+ import { useDispatch } from '@wordpress/data';
15
+ import { store as noticesStore } from '@wordpress/notices';
16
+
17
+ export const USER_PATTERN_CATEGORY = 'my-patterns';
18
+
19
+ export const SYNC_TYPES = {
20
+ full: undefined,
21
+ unsynced: 'unsynced',
22
+ };
23
+
24
+ /**
25
+ * Internal dependencies
26
+ */
27
+ import { store } from '../store';
28
+
29
+ export default function CreatePatternModal( {
30
+ onSuccess,
31
+ onError,
32
+ clientIds,
33
+ onClose,
34
+ className = 'patterns-menu-items__convert-modal',
35
+ } ) {
36
+ const [ syncType, setSyncType ] = useState( SYNC_TYPES.full );
37
+ const [ title, setTitle ] = useState( '' );
38
+ const { __experimentalCreatePattern: createPattern } = useDispatch( store );
39
+
40
+ const { createErrorNotice } = useDispatch( noticesStore );
41
+ const onCreate = useCallback(
42
+ async function ( patternTitle, sync ) {
43
+ try {
44
+ const newPattern = await createPattern(
45
+ patternTitle,
46
+ sync,
47
+ clientIds
48
+ );
49
+ onSuccess( {
50
+ pattern: newPattern,
51
+ categoryId: USER_PATTERN_CATEGORY,
52
+ } );
53
+ } catch ( error ) {
54
+ createErrorNotice( error.message, {
55
+ type: 'snackbar',
56
+ id: 'convert-to-pattern-error',
57
+ } );
58
+ onError();
59
+ }
60
+ },
61
+ [ createPattern, clientIds, onSuccess, createErrorNotice, onError ]
62
+ );
63
+ return (
64
+ <Modal
65
+ title={ __( 'Create pattern' ) }
66
+ onRequestClose={ () => {
67
+ onClose();
68
+ setTitle( '' );
69
+ } }
70
+ overlayClassName={ className }
71
+ >
72
+ <form
73
+ onSubmit={ ( event ) => {
74
+ event.preventDefault();
75
+ onCreate( title, syncType );
76
+ setTitle( '' );
77
+ } }
78
+ >
79
+ <VStack spacing="5">
80
+ <TextControl
81
+ __nextHasNoMarginBottom
82
+ label={ __( 'Name' ) }
83
+ value={ title }
84
+ onChange={ setTitle }
85
+ placeholder={ __( 'My pattern' ) }
86
+ />
87
+
88
+ <ToggleControl
89
+ label={ __( 'Synced' ) }
90
+ help={ __(
91
+ 'Editing the pattern will update it anywhere it is used.'
92
+ ) }
93
+ checked={ ! syncType }
94
+ onChange={ () => {
95
+ setSyncType(
96
+ syncType === SYNC_TYPES.full
97
+ ? SYNC_TYPES.unsynced
98
+ : SYNC_TYPES.full
99
+ );
100
+ } }
101
+ />
102
+ <HStack justify="right">
103
+ <Button
104
+ variant="tertiary"
105
+ onClick={ () => {
106
+ onClose();
107
+ setTitle( '' );
108
+ } }
109
+ >
110
+ { __( 'Cancel' ) }
111
+ </Button>
112
+
113
+ <Button variant="primary" type="submit">
114
+ { __( 'Create' ) }
115
+ </Button>
116
+ </HStack>
117
+ </VStack>
118
+ </form>
119
+ </Modal>
120
+ );
121
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { BlockSettingsMenuControls } from '@wordpress/block-editor';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import PatternConvertButton from './pattern-convert-button';
10
+ import PatternsManageButton from './patterns-manage-button';
11
+
12
+ export default function PatternsMenuItems( { rootClientId } ) {
13
+ return (
14
+ <BlockSettingsMenuControls>
15
+ { ( { selectedClientIds } ) => (
16
+ <>
17
+ <PatternConvertButton
18
+ clientIds={ selectedClientIds }
19
+ rootClientId={ rootClientId }
20
+ />
21
+ { selectedClientIds.length === 1 && (
22
+ <PatternsManageButton
23
+ clientId={ selectedClientIds[ 0 ] }
24
+ />
25
+ ) }
26
+ </>
27
+ ) }
28
+ </BlockSettingsMenuControls>
29
+ );
30
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { hasBlockSupport, isReusableBlock } from '@wordpress/blocks';
5
+ import { store as blockEditorStore } from '@wordpress/block-editor';
6
+ import { useState } from '@wordpress/element';
7
+ import { MenuItem } from '@wordpress/components';
8
+ import { symbol } from '@wordpress/icons';
9
+ import { useSelect, useDispatch } from '@wordpress/data';
10
+ import { store as coreStore } from '@wordpress/core-data';
11
+ import { __, sprintf } from '@wordpress/i18n';
12
+ import { store as noticesStore } from '@wordpress/notices';
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ import CreatePatternModal from './create-pattern-modal';
17
+
18
+ /**
19
+ * Menu control to convert block(s) to a pattern block.
20
+ *
21
+ * @param {Object} props Component props.
22
+ * @param {string[]} props.clientIds Client ids of selected blocks.
23
+ * @param {string} props.rootClientId ID of the currently selected top-level block.
24
+ * @return {import('@wordpress/element').WPComponent} The menu control or null.
25
+ */
26
+ export default function PatternConvertButton( { clientIds, rootClientId } ) {
27
+ const { createSuccessNotice } = useDispatch( noticesStore );
28
+ const [ isModalOpen, setIsModalOpen ] = useState( false );
29
+ const canConvert = useSelect(
30
+ ( select ) => {
31
+ const { canUser } = select( coreStore );
32
+ const {
33
+ getBlocksByClientId,
34
+ canInsertBlockType,
35
+ getBlockRootClientId,
36
+ } = select( blockEditorStore );
37
+
38
+ const rootId =
39
+ rootClientId ||
40
+ ( clientIds.length > 0
41
+ ? getBlockRootClientId( clientIds[ 0 ] )
42
+ : undefined );
43
+
44
+ const blocks = getBlocksByClientId( clientIds ) ?? [];
45
+
46
+ const isReusable =
47
+ blocks.length === 1 &&
48
+ blocks[ 0 ] &&
49
+ isReusableBlock( blocks[ 0 ] ) &&
50
+ !! select( coreStore ).getEntityRecord(
51
+ 'postType',
52
+ 'wp_block',
53
+ blocks[ 0 ].attributes.ref
54
+ );
55
+
56
+ const _canConvert =
57
+ // Hide when this is already a synced pattern.
58
+ ! isReusable &&
59
+ // Hide when patterns are disabled.
60
+ canInsertBlockType( 'core/block', rootId ) &&
61
+ blocks.every(
62
+ ( block ) =>
63
+ // Guard against the case where a regular block has *just* been converted.
64
+ !! block &&
65
+ // Hide on invalid blocks.
66
+ block.isValid &&
67
+ // Hide when block doesn't support being made into a pattern.
68
+ hasBlockSupport( block.name, 'reusable', true )
69
+ ) &&
70
+ // Hide when current doesn't have permission to do that.
71
+ !! canUser( 'create', 'blocks' );
72
+
73
+ return _canConvert;
74
+ },
75
+ [ clientIds, rootClientId ]
76
+ );
77
+
78
+ if ( ! canConvert ) {
79
+ return null;
80
+ }
81
+
82
+ const handleSuccess = ( { pattern } ) => {
83
+ createSuccessNotice(
84
+ pattern.wp_pattern_sync_status === 'unsynced'
85
+ ? sprintf(
86
+ // translators: %s: the name the user has given to the pattern.
87
+ __( 'Unsynced Pattern created: %s' ),
88
+ pattern.title.raw
89
+ )
90
+ : sprintf(
91
+ // translators: %s: the name the user has given to the pattern.
92
+ __( 'Synced Pattern created: %s' ),
93
+ pattern.title.raw
94
+ ),
95
+ {
96
+ type: 'snackbar',
97
+ id: 'convert-to-pattern-success',
98
+ }
99
+ );
100
+ setIsModalOpen( false );
101
+ };
102
+ return (
103
+ <>
104
+ <MenuItem icon={ symbol } onClick={ () => setIsModalOpen( true ) }>
105
+ { __( 'Create pattern' ) }
106
+ </MenuItem>
107
+ { isModalOpen && (
108
+ <CreatePatternModal
109
+ clientIds={ clientIds }
110
+ onSuccess={ ( pattern ) => {
111
+ handleSuccess( pattern );
112
+ } }
113
+ onError={ () => {
114
+ setIsModalOpen( false );
115
+ } }
116
+ onClose={ () => {
117
+ setIsModalOpen( false );
118
+ } }
119
+ />
120
+ ) }
121
+ </>
122
+ );
123
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { MenuItem } from '@wordpress/components';
5
+ import { __ } from '@wordpress/i18n';
6
+ import { isReusableBlock } from '@wordpress/blocks';
7
+ import { useSelect, useDispatch } from '@wordpress/data';
8
+ import { store as blockEditorStore } from '@wordpress/block-editor';
9
+ import { addQueryArgs } from '@wordpress/url';
10
+ import { store as coreStore } from '@wordpress/core-data';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import { store as editorStore } from '../store';
16
+
17
+ function PatternsManageButton( { clientId } ) {
18
+ const { canRemove, isVisible, innerBlockCount, managePatternsUrl } =
19
+ useSelect(
20
+ ( select ) => {
21
+ const { getBlock, canRemoveBlock, getBlockCount, getSettings } =
22
+ select( blockEditorStore );
23
+ const { canUser } = select( coreStore );
24
+ const reusableBlock = getBlock( clientId );
25
+ const isBlockTheme = getSettings().__unstableIsBlockBasedTheme;
26
+
27
+ return {
28
+ canRemove: canRemoveBlock( clientId ),
29
+ isVisible:
30
+ !! reusableBlock &&
31
+ isReusableBlock( reusableBlock ) &&
32
+ !! canUser(
33
+ 'update',
34
+ 'blocks',
35
+ reusableBlock.attributes.ref
36
+ ),
37
+ innerBlockCount: getBlockCount( clientId ),
38
+ // The site editor and templates both check whether the user
39
+ // has edit_theme_options capabilities. We can leverage that here
40
+ // and omit the manage patterns link if the user can't access it.
41
+ managePatternsUrl:
42
+ isBlockTheme && canUser( 'read', 'templates' )
43
+ ? addQueryArgs( 'site-editor.php', {
44
+ path: '/patterns',
45
+ } )
46
+ : addQueryArgs( 'edit.php', {
47
+ post_type: 'wp_block',
48
+ } ),
49
+ };
50
+ },
51
+ [ clientId ]
52
+ );
53
+
54
+ const { __experimentalConvertSyncedPatternToStatic: convertBlockToStatic } =
55
+ useDispatch( editorStore );
56
+
57
+ if ( ! isVisible ) {
58
+ return null;
59
+ }
60
+
61
+ return (
62
+ <>
63
+ <MenuItem href={ managePatternsUrl }>
64
+ { __( 'Manage patterns' ) }
65
+ </MenuItem>
66
+ { canRemove && (
67
+ <MenuItem onClick={ () => convertBlockToStatic( clientId ) }>
68
+ { innerBlockCount > 1
69
+ ? __( 'Detach patterns' )
70
+ : __( 'Detach pattern' ) }
71
+ </MenuItem>
72
+ ) }
73
+ </>
74
+ );
75
+ }
76
+
77
+ export default PatternsManageButton;
@@ -0,0 +1,3 @@
1
+ .patterns-menu-items__convert-modal {
2
+ z-index: z-index(".patterns-menu-items__convert-modal");
3
+ }
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import './store';
5
+
6
+ export * from './private-apis';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import './store';
5
+ import { lock } from './lock-unlock';
6
+
7
+ export const privateApis = {};
8
+ lock( privateApis, {
9
+ CreatePatternModal: () => null,
10
+ PatternsMenuItems: () => null,
11
+ } );