@wordpress/edit-site 4.8.0 → 4.9.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 (119) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/add-new-template/add-custom-template-modal.js +185 -0
  3. package/build/components/add-new-template/add-custom-template-modal.js.map +1 -0
  4. package/build/components/add-new-template/new-template.js +79 -14
  5. package/build/components/add-new-template/new-template.js.map +1 -1
  6. package/build/components/add-new-template/utils.js +139 -0
  7. package/build/components/add-new-template/utils.js.map +1 -0
  8. package/build/components/global-styles/context-menu.js +6 -3
  9. package/build/components/global-styles/context-menu.js.map +1 -1
  10. package/build/components/global-styles/hooks.js +1 -1
  11. package/build/components/global-styles/hooks.js.map +1 -1
  12. package/build/components/global-styles/palette.js +2 -1
  13. package/build/components/global-styles/palette.js.map +1 -1
  14. package/build/components/global-styles/screen-block-list.js +4 -1
  15. package/build/components/global-styles/screen-block-list.js.map +1 -1
  16. package/build/components/global-styles/screen-button-color.js +80 -0
  17. package/build/components/global-styles/screen-button-color.js.map +1 -0
  18. package/build/components/global-styles/screen-colors.js +47 -7
  19. package/build/components/global-styles/screen-colors.js.map +1 -1
  20. package/build/components/global-styles/screen-root.js +4 -2
  21. package/build/components/global-styles/screen-root.js.map +1 -1
  22. package/build/components/global-styles/screen-typography-element.js +4 -0
  23. package/build/components/global-styles/screen-typography-element.js.map +1 -1
  24. package/build/components/global-styles/screen-typography.js +9 -1
  25. package/build/components/global-styles/screen-typography.js.map +1 -1
  26. package/build/components/global-styles/ui.js +11 -0
  27. package/build/components/global-styles/ui.js.map +1 -1
  28. package/build/components/global-styles/use-global-styles-output.js +40 -9
  29. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  30. package/build/components/global-styles/utils.js +3 -1
  31. package/build/components/global-styles/utils.js.map +1 -1
  32. package/build/components/header/index.js +28 -10
  33. package/build/components/header/index.js.map +1 -1
  34. package/build/components/header/more-menu/site-export.js +4 -1
  35. package/build/components/header/more-menu/site-export.js.map +1 -1
  36. package/build/components/header/undo-redo/redo.js +13 -4
  37. package/build/components/header/undo-redo/redo.js.map +1 -1
  38. package/build/components/header/undo-redo/undo.js +13 -4
  39. package/build/components/header/undo-redo/undo.js.map +1 -1
  40. package/build/components/keyboard-shortcut-help-modal/config.js +17 -0
  41. package/build/components/keyboard-shortcut-help-modal/config.js.map +1 -1
  42. package/build/components/sidebar/template-card/index.js +19 -7
  43. package/build/components/sidebar/template-card/index.js.map +1 -1
  44. package/build/components/sidebar/template-card/template-actions.js +64 -0
  45. package/build/components/sidebar/template-card/template-actions.js.map +1 -0
  46. package/build-module/components/add-new-template/add-custom-template-modal.js +170 -0
  47. package/build-module/components/add-new-template/add-custom-template-modal.js.map +1 -0
  48. package/build-module/components/add-new-template/new-template.js +79 -17
  49. package/build-module/components/add-new-template/new-template.js.map +1 -1
  50. package/build-module/components/add-new-template/utils.js +119 -0
  51. package/build-module/components/add-new-template/utils.js.map +1 -0
  52. package/build-module/components/global-styles/context-menu.js +6 -3
  53. package/build-module/components/global-styles/context-menu.js.map +1 -1
  54. package/build-module/components/global-styles/hooks.js +1 -1
  55. package/build-module/components/global-styles/hooks.js.map +1 -1
  56. package/build-module/components/global-styles/palette.js +2 -1
  57. package/build-module/components/global-styles/palette.js.map +1 -1
  58. package/build-module/components/global-styles/screen-block-list.js +4 -1
  59. package/build-module/components/global-styles/screen-block-list.js.map +1 -1
  60. package/build-module/components/global-styles/screen-button-color.js +67 -0
  61. package/build-module/components/global-styles/screen-button-color.js.map +1 -0
  62. package/build-module/components/global-styles/screen-colors.js +48 -8
  63. package/build-module/components/global-styles/screen-colors.js.map +1 -1
  64. package/build-module/components/global-styles/screen-root.js +4 -2
  65. package/build-module/components/global-styles/screen-root.js.map +1 -1
  66. package/build-module/components/global-styles/screen-typography-element.js +4 -0
  67. package/build-module/components/global-styles/screen-typography-element.js.map +1 -1
  68. package/build-module/components/global-styles/screen-typography.js +10 -2
  69. package/build-module/components/global-styles/screen-typography.js.map +1 -1
  70. package/build-module/components/global-styles/ui.js +10 -0
  71. package/build-module/components/global-styles/ui.js.map +1 -1
  72. package/build-module/components/global-styles/use-global-styles-output.js +39 -9
  73. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  74. package/build-module/components/global-styles/utils.js +3 -1
  75. package/build-module/components/global-styles/utils.js.map +1 -1
  76. package/build-module/components/header/index.js +29 -11
  77. package/build-module/components/header/index.js.map +1 -1
  78. package/build-module/components/header/more-menu/site-export.js +4 -1
  79. package/build-module/components/header/more-menu/site-export.js.map +1 -1
  80. package/build-module/components/header/undo-redo/redo.js +9 -3
  81. package/build-module/components/header/undo-redo/redo.js.map +1 -1
  82. package/build-module/components/header/undo-redo/undo.js +9 -3
  83. package/build-module/components/header/undo-redo/undo.js.map +1 -1
  84. package/build-module/components/keyboard-shortcut-help-modal/config.js +17 -0
  85. package/build-module/components/keyboard-shortcut-help-modal/config.js.map +1 -1
  86. package/build-module/components/sidebar/template-card/index.js +18 -7
  87. package/build-module/components/sidebar/template-card/index.js.map +1 -1
  88. package/build-module/components/sidebar/template-card/template-actions.js +49 -0
  89. package/build-module/components/sidebar/template-card/template-actions.js.map +1 -0
  90. package/build-style/style-rtl.css +167 -16
  91. package/build-style/style.css +167 -16
  92. package/package.json +29 -29
  93. package/src/components/add-new-template/add-custom-template-modal.js +231 -0
  94. package/src/components/add-new-template/new-template.js +124 -47
  95. package/src/components/add-new-template/style.scss +116 -0
  96. package/src/components/add-new-template/utils.js +125 -0
  97. package/src/components/global-styles/context-menu.js +3 -0
  98. package/src/components/global-styles/hooks.js +1 -0
  99. package/src/components/global-styles/palette.js +4 -1
  100. package/src/components/global-styles/screen-block-list.js +10 -1
  101. package/src/components/global-styles/screen-button-color.js +102 -0
  102. package/src/components/global-styles/screen-colors.js +49 -4
  103. package/src/components/global-styles/screen-root.js +8 -2
  104. package/src/components/global-styles/screen-typography-element.js +4 -0
  105. package/src/components/global-styles/screen-typography.js +17 -2
  106. package/src/components/global-styles/style.scss +10 -0
  107. package/src/components/global-styles/test/use-global-styles-output.js +82 -16
  108. package/src/components/global-styles/ui.js +13 -0
  109. package/src/components/global-styles/use-global-styles-output.js +43 -4
  110. package/src/components/global-styles/utils.js +3 -0
  111. package/src/components/header/index.js +36 -10
  112. package/src/components/header/more-menu/site-export.js +3 -0
  113. package/src/components/header/style.scss +53 -5
  114. package/src/components/header/undo-redo/redo.js +6 -1
  115. package/src/components/header/undo-redo/undo.js +6 -1
  116. package/src/components/keyboard-shortcut-help-modal/config.js +12 -0
  117. package/src/components/sidebar/template-card/index.js +15 -6
  118. package/src/components/sidebar/template-card/style.scss +49 -35
  119. package/src/components/sidebar/template-card/template-actions.js +43 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/edit-site",
3
- "version": "4.8.0",
3
+ "version": "4.9.0",
4
4
  "description": "Edit Site Page module for WordPress.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -27,33 +27,33 @@
27
27
  "react-native": "src/index",
28
28
  "dependencies": {
29
29
  "@babel/runtime": "^7.16.0",
30
- "@wordpress/a11y": "^3.11.0",
31
- "@wordpress/api-fetch": "^6.8.0",
32
- "@wordpress/block-editor": "^9.3.0",
33
- "@wordpress/block-library": "^7.8.0",
34
- "@wordpress/blocks": "^11.10.0",
35
- "@wordpress/components": "^19.13.0",
36
- "@wordpress/compose": "^5.9.0",
37
- "@wordpress/core-data": "^4.9.0",
38
- "@wordpress/data": "^6.11.0",
39
- "@wordpress/deprecated": "^3.11.0",
40
- "@wordpress/editor": "^12.10.0",
41
- "@wordpress/element": "^4.9.0",
42
- "@wordpress/hooks": "^3.11.0",
43
- "@wordpress/html-entities": "^3.11.0",
44
- "@wordpress/i18n": "^4.11.0",
45
- "@wordpress/icons": "^9.2.0",
46
- "@wordpress/interface": "^4.10.0",
47
- "@wordpress/keyboard-shortcuts": "^3.9.0",
48
- "@wordpress/keycodes": "^3.11.0",
49
- "@wordpress/media-utils": "^4.2.0",
50
- "@wordpress/notices": "^3.11.0",
51
- "@wordpress/plugins": "^4.9.0",
52
- "@wordpress/preferences": "^2.3.0",
53
- "@wordpress/reusable-blocks": "^3.9.0",
54
- "@wordpress/style-engine": "^0.10.0",
55
- "@wordpress/url": "^3.12.0",
56
- "@wordpress/viewport": "^4.9.0",
30
+ "@wordpress/a11y": "^3.12.0",
31
+ "@wordpress/api-fetch": "^6.9.0",
32
+ "@wordpress/block-editor": "^9.4.0",
33
+ "@wordpress/block-library": "^7.9.0",
34
+ "@wordpress/blocks": "^11.11.0",
35
+ "@wordpress/components": "^19.14.0",
36
+ "@wordpress/compose": "^5.10.0",
37
+ "@wordpress/core-data": "^4.10.0",
38
+ "@wordpress/data": "^6.12.0",
39
+ "@wordpress/deprecated": "^3.12.0",
40
+ "@wordpress/editor": "^12.11.0",
41
+ "@wordpress/element": "^4.10.0",
42
+ "@wordpress/hooks": "^3.12.0",
43
+ "@wordpress/html-entities": "^3.12.0",
44
+ "@wordpress/i18n": "^4.12.0",
45
+ "@wordpress/icons": "^9.3.0",
46
+ "@wordpress/interface": "^4.11.0",
47
+ "@wordpress/keyboard-shortcuts": "^3.10.0",
48
+ "@wordpress/keycodes": "^3.12.0",
49
+ "@wordpress/media-utils": "^4.3.0",
50
+ "@wordpress/notices": "^3.12.0",
51
+ "@wordpress/plugins": "^4.10.0",
52
+ "@wordpress/preferences": "^2.4.0",
53
+ "@wordpress/reusable-blocks": "^3.10.0",
54
+ "@wordpress/style-engine": "^0.11.0",
55
+ "@wordpress/url": "^3.13.0",
56
+ "@wordpress/viewport": "^4.10.0",
57
57
  "classnames": "^2.3.1",
58
58
  "downloadjs": "^1.4.7",
59
59
  "history": "^5.1.0",
@@ -68,5 +68,5 @@
68
68
  "publishConfig": {
69
69
  "access": "public"
70
70
  },
71
- "gitHead": "48d5f37dfb52d2e77c8eeb662f9874cf141b8c6b"
71
+ "gitHead": "a80eeb62ec7cb1418b9915c277e084a29d6665e3"
72
72
  }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState, useMemo, useEffect } from '@wordpress/element';
5
+ import { __, sprintf } from '@wordpress/i18n';
6
+ import {
7
+ Button,
8
+ Flex,
9
+ FlexItem,
10
+ Modal,
11
+ SearchControl,
12
+ TextHighlight,
13
+ __experimentalText as Text,
14
+ __experimentalHeading as Heading,
15
+ __unstableComposite as Composite,
16
+ __unstableUseCompositeState as useCompositeState,
17
+ __unstableCompositeItem as CompositeItem,
18
+ } from '@wordpress/components';
19
+ import { useDebounce } from '@wordpress/compose';
20
+ import { useEntityRecords } from '@wordpress/core-data';
21
+
22
+ /**
23
+ * Internal dependencies
24
+ */
25
+ import { mapToIHasNameAndId } from './utils';
26
+
27
+ const EMPTY_ARRAY = [];
28
+ const BASE_QUERY = {
29
+ order: 'asc',
30
+ _fields: 'id,title,slug,link',
31
+ context: 'view',
32
+ };
33
+
34
+ function SuggestionListItem( {
35
+ suggestion,
36
+ search,
37
+ onSelect,
38
+ entityForSuggestions,
39
+ composite,
40
+ } ) {
41
+ const baseCssClass =
42
+ 'edit-site-custom-template-modal__suggestions_list__list-item';
43
+ return (
44
+ <CompositeItem
45
+ role="option"
46
+ as={ Button }
47
+ { ...composite }
48
+ className={ baseCssClass }
49
+ onClick={ () => {
50
+ const title = sprintf(
51
+ // translators: Represents the title of a user's custom template in the Site Editor, where %1$s is the singular name of a post type and %2$s is the name of the post, e.g. "Post: Hello, WordPress"
52
+ __( '%1$s: %2$s' ),
53
+ entityForSuggestions.labels.singular_name,
54
+ suggestion.name
55
+ );
56
+ onSelect( {
57
+ title,
58
+ description: sprintf(
59
+ // translators: Represents the description of a user's custom template in the Site Editor, e.g. "Template for Post: Hello, WordPress"
60
+ __( 'Template for %1$s' ),
61
+ title
62
+ ),
63
+ slug: `single-${ entityForSuggestions.slug }-${ suggestion.slug }`,
64
+ } );
65
+ } }
66
+ >
67
+ <span className={ `${ baseCssClass }__title` }>
68
+ <TextHighlight text={ suggestion.name } highlight={ search } />
69
+ </span>
70
+ { suggestion.link && (
71
+ <span className={ `${ baseCssClass }__info` }>
72
+ { suggestion.link }
73
+ </span>
74
+ ) }
75
+ </CompositeItem>
76
+ );
77
+ }
78
+
79
+ function SuggestionList( { entityForSuggestions, onSelect } ) {
80
+ const composite = useCompositeState( { orientation: 'vertical' } );
81
+ const [ suggestions, setSuggestions ] = useState( EMPTY_ARRAY );
82
+ // We need to track two values, the search input's value(searchInputValue)
83
+ // and the one we want to debounce(search) and make REST API requests.
84
+ const [ searchInputValue, setSearchInputValue ] = useState( '' );
85
+ const [ search, setSearch ] = useState( '' );
86
+ const debouncedSearch = useDebounce( setSearch, 250 );
87
+ const query = {
88
+ ...BASE_QUERY,
89
+ search,
90
+ orderby: search ? 'relevance' : 'modified',
91
+ exclude: entityForSuggestions.postsToExclude,
92
+ per_page: search ? 20 : 10,
93
+ };
94
+ const { records: searchResults, hasResolved: searchHasResolved } =
95
+ useEntityRecords(
96
+ entityForSuggestions.type,
97
+ entityForSuggestions.slug,
98
+ query
99
+ );
100
+ useEffect( () => {
101
+ if ( search !== searchInputValue ) {
102
+ debouncedSearch( searchInputValue );
103
+ }
104
+ }, [ search, searchInputValue ] );
105
+ const entitiesInfo = useMemo( () => {
106
+ if ( ! searchResults?.length ) return EMPTY_ARRAY;
107
+ return mapToIHasNameAndId( searchResults, 'title.rendered' );
108
+ }, [ searchResults ] );
109
+ // Update suggestions only when the query has resolved.
110
+ useEffect( () => {
111
+ if ( ! searchHasResolved ) return;
112
+ setSuggestions( entitiesInfo );
113
+ }, [ entitiesInfo, searchHasResolved ] );
114
+ return (
115
+ <>
116
+ <SearchControl
117
+ onChange={ setSearchInputValue }
118
+ value={ searchInputValue }
119
+ label={ entityForSuggestions.labels.search_items }
120
+ placeholder={ entityForSuggestions.labels.search_items }
121
+ />
122
+ { !! suggestions?.length && (
123
+ <Composite
124
+ { ...composite }
125
+ role="listbox"
126
+ className="edit-site-custom-template-modal__suggestions_list"
127
+ >
128
+ { suggestions.map( ( suggestion ) => (
129
+ <SuggestionListItem
130
+ key={ suggestion.slug }
131
+ suggestion={ suggestion }
132
+ search={ search }
133
+ onSelect={ onSelect }
134
+ entityForSuggestions={ entityForSuggestions }
135
+ composite={ composite }
136
+ />
137
+ ) ) }
138
+ </Composite>
139
+ ) }
140
+ { search && ! suggestions?.length && (
141
+ <p className="edit-site-custom-template-modal__no-results">
142
+ { entityForSuggestions.labels.not_found }
143
+ </p>
144
+ ) }
145
+ </>
146
+ );
147
+ }
148
+
149
+ function AddCustomTemplateModal( { onClose, onSelect, entityForSuggestions } ) {
150
+ const [ showSearchEntities, setShowSearchEntities ] = useState(
151
+ entityForSuggestions.hasGeneralTemplate
152
+ );
153
+ const baseCssClass = 'edit-site-custom-template-modal';
154
+ return (
155
+ <Modal
156
+ title={ sprintf(
157
+ // translators: %s: Name of the post type e.g: "Post".
158
+ __( 'Add template: %s' ),
159
+ entityForSuggestions.labels.singular_name
160
+ ) }
161
+ className={ baseCssClass }
162
+ closeLabel={ __( 'Close' ) }
163
+ onRequestClose={ onClose }
164
+ >
165
+ { ! showSearchEntities && (
166
+ <>
167
+ <p>
168
+ { __(
169
+ 'Select whether to create a single template for all items or a specific one.'
170
+ ) }
171
+ </p>
172
+ <Flex
173
+ className={ `${ baseCssClass }__contents` }
174
+ gap="4"
175
+ align="initial"
176
+ >
177
+ <FlexItem
178
+ isBlock
179
+ onClick={ () => {
180
+ const { slug, title, description } =
181
+ entityForSuggestions.template;
182
+ onSelect( { slug, title, description } );
183
+ } }
184
+ >
185
+ <Heading level={ 5 }>
186
+ { entityForSuggestions.labels.all_items }
187
+ </Heading>
188
+ <Text as="span">
189
+ {
190
+ // translators: The user is given the choice to set up a template for all items of a post type, or just a specific one.
191
+ __( 'For all items' )
192
+ }
193
+ </Text>
194
+ </FlexItem>
195
+ <FlexItem
196
+ isBlock
197
+ onClick={ () => {
198
+ setShowSearchEntities( true );
199
+ } }
200
+ >
201
+ <Heading level={ 5 }>
202
+ { entityForSuggestions.labels.singular_name }
203
+ </Heading>
204
+ <Text as="span">
205
+ {
206
+ // translators: The user is given the choice to set up a template for all items of a post type, or just a specific one.
207
+ __( 'For a specific item' )
208
+ }
209
+ </Text>
210
+ </FlexItem>
211
+ </Flex>
212
+ </>
213
+ ) }
214
+ { showSearchEntities && (
215
+ <>
216
+ <p>
217
+ { __(
218
+ 'This template will be used only for the specific item chosen.'
219
+ ) }
220
+ </p>
221
+ <SuggestionList
222
+ entityForSuggestions={ entityForSuggestions }
223
+ onSelect={ onSelect }
224
+ />
225
+ </>
226
+ ) }
227
+ </Modal>
228
+ );
229
+ }
230
+
231
+ export default AddCustomTemplateModal;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { filter, includes, map } from 'lodash';
4
+ import { filter, includes } from 'lodash';
5
5
 
6
6
  /**
7
7
  * WordPress dependencies
@@ -12,6 +12,7 @@ import {
12
12
  MenuItem,
13
13
  NavigableMenu,
14
14
  } from '@wordpress/components';
15
+ import { useState } from '@wordpress/element';
15
16
  import { useSelect, useDispatch } from '@wordpress/data';
16
17
  import { store as coreStore } from '@wordpress/core-data';
17
18
  import { store as editorStore } from '@wordpress/editor';
@@ -30,12 +31,14 @@ import {
30
31
  search,
31
32
  tag,
32
33
  } from '@wordpress/icons';
33
- import { __ } from '@wordpress/i18n';
34
+ import { __, sprintf } from '@wordpress/i18n';
34
35
  import { store as noticesStore } from '@wordpress/notices';
35
36
 
36
37
  /**
37
38
  * Internal dependencies
38
39
  */
40
+ import AddCustomTemplateModal from './add-custom-template-modal';
41
+ import { usePostTypes, usePostTypesEntitiesInfo } from './utils';
39
42
  import { useHistory } from '../routes';
40
43
  import { store as editSiteStore } from '../../store';
41
44
 
@@ -72,9 +75,13 @@ const TEMPLATE_ICONS = {
72
75
 
73
76
  export default function NewTemplate( { postType } ) {
74
77
  const history = useHistory();
75
- const { templates, defaultTemplateTypes } = useSelect(
78
+ const postTypes = usePostTypes();
79
+ const [ showCustomTemplateModal, setShowCustomTemplateModal ] =
80
+ useState( false );
81
+ const [ entityForSuggestions, setEntityForSuggestions ] = useState( {} );
82
+ const { existingTemplates, defaultTemplateTypes } = useSelect(
76
83
  ( select ) => ( {
77
- templates: select( coreStore ).getEntityRecords(
84
+ existingTemplates: select( coreStore ).getEntityRecords(
78
85
  'postType',
79
86
  'wp_template',
80
87
  { per_page: -1 }
@@ -84,6 +91,7 @@ export default function NewTemplate( { postType } ) {
84
91
  } ),
85
92
  []
86
93
  );
94
+ const postTypesEntitiesInfo = usePostTypesEntitiesInfo( existingTemplates );
87
95
  const { saveEntityRecord } = useDispatch( coreStore );
88
96
  const { createErrorNotice } = useDispatch( noticesStore );
89
97
  const { setTemplate } = useDispatch( editSiteStore );
@@ -91,7 +99,6 @@ export default function NewTemplate( { postType } ) {
91
99
  async function createTemplate( template ) {
92
100
  try {
93
101
  const { title, description, slug } = template;
94
-
95
102
  const newTemplate = await saveEntityRecord(
96
103
  'postType',
97
104
  'wp_template',
@@ -128,9 +135,9 @@ export default function NewTemplate( { postType } ) {
128
135
  } );
129
136
  }
130
137
  }
131
-
132
- const existingTemplateSlugs = map( templates, 'slug' );
133
-
138
+ const existingTemplateSlugs = ( existingTemplates || [] ).map(
139
+ ( { slug } ) => slug
140
+ );
134
141
  const missingTemplates = filter(
135
142
  defaultTemplateTypes,
136
143
  ( template ) =>
@@ -138,54 +145,124 @@ export default function NewTemplate( { postType } ) {
138
145
  ! includes( existingTemplateSlugs, template.slug )
139
146
  );
140
147
 
141
- if ( ! missingTemplates.length ) {
148
+ const extraTemplates = ( postTypes || [] ).reduce(
149
+ ( accumulator, _postType ) => {
150
+ const { slug, labels, icon } = _postType;
151
+ const hasGeneralTemplate = existingTemplateSlugs?.includes(
152
+ `single-${ slug }`
153
+ );
154
+ const hasEntities = postTypesEntitiesInfo?.[ slug ]?.hasEntities;
155
+ const menuItem = {
156
+ slug: `single-${ slug }`,
157
+ title: sprintf(
158
+ // translators: %s: Name of the post type e.g: "Post".
159
+ __( 'Single item: %s' ),
160
+ labels.singular_name
161
+ ),
162
+ description: sprintf(
163
+ // translators: %s: Name of the post type e.g: "Post".
164
+ __( 'Displays a single item: %s.' ),
165
+ labels.singular_name
166
+ ),
167
+ // `icon` is the `menu_icon` property of a post type. We
168
+ // only handle `dashicons` for now, even if the `menu_icon`
169
+ // also supports urls and svg as values.
170
+ icon: icon?.startsWith( 'dashicons-' )
171
+ ? icon.slice( 10 )
172
+ : null,
173
+ };
174
+ // We have a different template creation flow only if they have entities.
175
+ if ( hasEntities ) {
176
+ menuItem.onClick = ( template ) => {
177
+ setShowCustomTemplateModal( true );
178
+ setEntityForSuggestions( {
179
+ type: 'postType',
180
+ slug,
181
+ labels,
182
+ hasGeneralTemplate,
183
+ template,
184
+ postsToExclude:
185
+ postTypesEntitiesInfo[ slug ].existingPosts,
186
+ } );
187
+ };
188
+ }
189
+ // We don't need to add the menu item if there are no
190
+ // entities and the general template exists.
191
+ if ( ! hasGeneralTemplate || hasEntities ) {
192
+ accumulator.push( menuItem );
193
+ }
194
+ return accumulator;
195
+ },
196
+ []
197
+ );
198
+ if ( ! missingTemplates.length && ! extraTemplates.length ) {
142
199
  return null;
143
200
  }
144
-
145
201
  // Update the sort order to match the DEFAULT_TEMPLATE_SLUGS order.
146
- missingTemplates.sort( ( template1, template2 ) => {
202
+ missingTemplates?.sort( ( template1, template2 ) => {
147
203
  return (
148
204
  DEFAULT_TEMPLATE_SLUGS.indexOf( template1.slug ) -
149
205
  DEFAULT_TEMPLATE_SLUGS.indexOf( template2.slug )
150
206
  );
151
207
  } );
152
-
208
+ // Append all extra templates at the end of the list for now.
209
+ missingTemplates.push( ...extraTemplates );
153
210
  return (
154
- <DropdownMenu
155
- className="edit-site-new-template-dropdown"
156
- icon={ null }
157
- text={ postType.labels.add_new }
158
- label={ postType.labels.add_new_item }
159
- popoverProps={ {
160
- noArrow: false,
161
- } }
162
- toggleProps={ {
163
- variant: 'primary',
164
- } }
165
- >
166
- { () => (
167
- <NavigableMenu className="edit-site-new-template-dropdown__popover">
168
- <MenuGroup label={ postType.labels.add_new_item }>
169
- { map( missingTemplates, ( template ) => {
170
- const { title, description, slug } = template;
171
- return (
172
- <MenuItem
173
- icon={ TEMPLATE_ICONS[ slug ] }
174
- iconPosition="left"
175
- info={ description }
176
- key={ slug }
177
- onClick={ () => {
178
- createTemplate( template );
179
- // We will be navigated way so no need to close the dropdown.
180
- } }
181
- >
182
- { title }
183
- </MenuItem>
184
- );
185
- } ) }
186
- </MenuGroup>
187
- </NavigableMenu>
211
+ <>
212
+ <DropdownMenu
213
+ className="edit-site-new-template-dropdown"
214
+ icon={ null }
215
+ text={ postType.labels.add_new }
216
+ label={ postType.labels.add_new_item }
217
+ popoverProps={ {
218
+ noArrow: false,
219
+ } }
220
+ toggleProps={ {
221
+ variant: 'primary',
222
+ } }
223
+ >
224
+ { () => (
225
+ <NavigableMenu className="edit-site-new-template-dropdown__popover">
226
+ <MenuGroup label={ postType.labels.add_new_item }>
227
+ { missingTemplates.map( ( template ) => {
228
+ const {
229
+ title,
230
+ description,
231
+ slug,
232
+ onClick,
233
+ icon,
234
+ } = template;
235
+ return (
236
+ <MenuItem
237
+ icon={
238
+ icon ||
239
+ TEMPLATE_ICONS[ slug ] ||
240
+ post
241
+ }
242
+ iconPosition="left"
243
+ info={ description }
244
+ key={ slug }
245
+ onClick={ () =>
246
+ onClick
247
+ ? onClick( template )
248
+ : createTemplate( template )
249
+ }
250
+ >
251
+ { title }
252
+ </MenuItem>
253
+ );
254
+ } ) }
255
+ </MenuGroup>
256
+ </NavigableMenu>
257
+ ) }
258
+ </DropdownMenu>
259
+ { showCustomTemplateModal && (
260
+ <AddCustomTemplateModal
261
+ onClose={ () => setShowCustomTemplateModal( false ) }
262
+ onSelect={ createTemplate }
263
+ entityForSuggestions={ entityForSuggestions }
264
+ />
188
265
  ) }
189
- </DropdownMenu>
266
+ </>
190
267
  );
191
268
  }
@@ -9,3 +9,119 @@
9
9
  }
10
10
  }
11
11
  }
12
+
13
+ .edit-site-custom-template-modal {
14
+ &__contents {
15
+ > div {
16
+ text-align: center;
17
+ cursor: pointer;
18
+ padding: $grid-unit-30;
19
+ border: 1px solid $gray-300;
20
+ border-radius: $radius-block-ui;
21
+ width: 256px;
22
+ display: flex;
23
+ flex-direction: column;
24
+ gap: $grid-unit;
25
+ align-items: center;
26
+ justify-content: center;
27
+
28
+ span {
29
+ color: $gray-700;
30
+ }
31
+
32
+ &:hover {
33
+ border-color: var(--wp-admin-theme-color);
34
+
35
+ h5 {
36
+ color: var(--wp-admin-theme-color);
37
+ }
38
+ }
39
+
40
+ &:focus {
41
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);
42
+ }
43
+ }
44
+ }
45
+
46
+ .components-search-control {
47
+ input[type="search"].components-search-control__input {
48
+ background: $white;
49
+ border: 1px solid $gray-300;
50
+
51
+ &:focus {
52
+ border-color: var(--wp-admin-theme-color);
53
+ box-shadow: 0 0 0 1px var(--wp-admin-theme-color);
54
+ }
55
+ }
56
+ }
57
+
58
+ @include break-medium() {
59
+ width: 456px;
60
+ }
61
+ }
62
+
63
+ .edit-site-custom-template-modal__suggestions_list {
64
+ margin-top: $grid-unit-20;
65
+
66
+ @include break-small() {
67
+ height: 232px;
68
+ overflow: scroll;
69
+ }
70
+
71
+ &__list-item {
72
+ display: block;
73
+ width: 100%;
74
+ text-align: left;
75
+ white-space: pre-wrap;
76
+ overflow-wrap: break-word;
77
+ height: auto;
78
+
79
+ mark {
80
+ font-weight: 700;
81
+ background: none;
82
+ }
83
+
84
+ &:hover {
85
+ background-color: $gray-100;
86
+
87
+ mark {
88
+ color: var(--wp-admin-theme-color);
89
+ }
90
+ }
91
+
92
+ &:focus {
93
+ background-color: $gray-100;
94
+ }
95
+
96
+ &:focus:not(:disabled) {
97
+ box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color) inset;
98
+ }
99
+
100
+ &__title,
101
+ &__info {
102
+ overflow: hidden;
103
+ text-overflow: ellipsis;
104
+ display: block;
105
+ }
106
+
107
+ &__title {
108
+ font-weight: 500;
109
+ margin-bottom: 0.2em;
110
+ }
111
+
112
+ &__info {
113
+ color: $gray-700;
114
+ font-size: 0.9em;
115
+ line-height: 1.3;
116
+ word-break: break-all;
117
+ }
118
+ }
119
+ }
120
+
121
+ .edit-site-custom-template-modal__no-results {
122
+ border: 1px solid $gray-400;
123
+ border-radius: $radius-block-ui;
124
+ padding: $grid-unit-20;
125
+ margin-bottom: 0;
126
+ margin-top: $grid-unit-20;
127
+ }