@wordpress/patterns 1.5.0 → 1.6.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.6.0 (2023-11-02)
6
+
5
7
  ## 1.5.0 (2023-10-18)
6
8
 
7
9
  ## 1.4.0 (2023-10-05)
@@ -12,6 +12,7 @@ var _element = require("@wordpress/element");
12
12
  var _htmlEntities = require("@wordpress/html-entities");
13
13
  var _i18n = require("@wordpress/i18n");
14
14
  var _notices = require("@wordpress/notices");
15
+ var _a11y = require("@wordpress/a11y");
15
16
  var _categorySelector = require("./category-selector");
16
17
  /**
17
18
  * WordPress dependencies
@@ -23,13 +24,18 @@ var _categorySelector = require("./category-selector");
23
24
 
24
25
  function RenamePatternCategoryModal({
25
26
  category,
27
+ existingCategories,
26
28
  onClose,
27
29
  onError,
28
30
  onSuccess,
29
31
  ...props
30
32
  }) {
33
+ const id = (0, _element.useId)();
34
+ const textControlRef = (0, _element.useRef)();
31
35
  const [name, setName] = (0, _element.useState)((0, _htmlEntities.decodeEntities)(category.name));
32
36
  const [isSaving, setIsSaving] = (0, _element.useState)(false);
37
+ const [validationMessage, setValidationMessage] = (0, _element.useState)(false);
38
+ const validationMessageId = validationMessage ? `patterns-rename-pattern-category-modal__validation-message-${id}` : undefined;
33
39
  const {
34
40
  saveEntityRecord,
35
41
  invalidateResolution
@@ -38,9 +44,35 @@ function RenamePatternCategoryModal({
38
44
  createErrorNotice,
39
45
  createSuccessNotice
40
46
  } = (0, _data.useDispatch)(_notices.store);
41
- const onRename = async event => {
47
+ const onChange = newName => {
48
+ if (validationMessage) {
49
+ setValidationMessage(undefined);
50
+ }
51
+ setName(newName);
52
+ };
53
+ const onSave = async event => {
42
54
  event.preventDefault();
43
- if (!name || name === category.name || isSaving) {
55
+ if (isSaving) {
56
+ return;
57
+ }
58
+ if (!name || name === category.name) {
59
+ const message = (0, _i18n.__)('Please enter a new name for this category.');
60
+ (0, _a11y.speak)(message, 'assertive');
61
+ setValidationMessage(message);
62
+ textControlRef.current?.focus();
63
+ return;
64
+ }
65
+
66
+ // Check existing categories to avoid creating duplicates.
67
+ if (existingCategories.patternCategories.find(existingCategory => {
68
+ // Compare the id so that the we don't disallow the user changing the case of their current category
69
+ // (i.e. renaming 'test' to 'Test').
70
+ return existingCategory.id !== category.id && existingCategory.label.toLowerCase() === name.toLowerCase();
71
+ })) {
72
+ const message = (0, _i18n.__)('This category already exists. Please use a different name.');
73
+ (0, _a11y.speak)(message, 'assertive');
74
+ setValidationMessage(message);
75
+ textControlRef.current?.focus();
44
76
  return;
45
77
  }
46
78
  try {
@@ -82,16 +114,23 @@ function RenamePatternCategoryModal({
82
114
  onRequestClose: onRequestClose,
83
115
  ...props
84
116
  }, (0, _react.createElement)("form", {
85
- onSubmit: onRename
117
+ onSubmit: onSave
86
118
  }, (0, _react.createElement)(_components.__experimentalVStack, {
87
119
  spacing: "5"
120
+ }, (0, _react.createElement)(_components.__experimentalVStack, {
121
+ spacing: "2"
88
122
  }, (0, _react.createElement)(_components.TextControl, {
123
+ ref: textControlRef,
89
124
  __nextHasNoMarginBottom: true,
90
125
  label: (0, _i18n.__)('Name'),
91
126
  value: name,
92
- onChange: setName,
127
+ onChange: onChange,
128
+ "aria-describedby": validationMessageId,
93
129
  required: true
94
- }), (0, _react.createElement)(_components.__experimentalHStack, {
130
+ }), validationMessage && (0, _react.createElement)("span", {
131
+ className: "patterns-rename-pattern-category-modal__validation-message",
132
+ id: validationMessageId
133
+ }, validationMessage)), (0, _react.createElement)(_components.__experimentalHStack, {
95
134
  justify: "right"
96
135
  }, (0, _react.createElement)(_components.Button, {
97
136
  variant: "tertiary",
@@ -1 +1 @@
1
- {"version":3,"names":["_components","require","_coreData","_data","_element","_htmlEntities","_i18n","_notices","_categorySelector","RenamePatternCategoryModal","category","onClose","onError","onSuccess","props","name","setName","useState","decodeEntities","isSaving","setIsSaving","saveEntityRecord","invalidateResolution","useDispatch","coreStore","createErrorNotice","createSuccessNotice","noticesStore","onRename","event","preventDefault","savedRecord","CATEGORY_SLUG","id","slug","__","type","error","message","onRequestClose","_react","createElement","Modal","title","onSubmit","__experimentalVStack","spacing","TextControl","__nextHasNoMarginBottom","label","value","onChange","required","__experimentalHStack","justify","Button","variant","onClick","isBusy"],"sources":["@wordpress/patterns/src/components/rename-pattern-category-modal.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport {\n\tModal,\n\tButton,\n\tTextControl,\n\t__experimentalHStack as HStack,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useDispatch } from '@wordpress/data';\nimport { useState } from '@wordpress/element';\nimport { decodeEntities } from '@wordpress/html-entities';\nimport { __ } from '@wordpress/i18n';\nimport { store as noticesStore } from '@wordpress/notices';\n\n/**\n * Internal dependencies\n */\nimport { CATEGORY_SLUG } from './category-selector';\n\nexport default function RenamePatternCategoryModal( {\n\tcategory,\n\tonClose,\n\tonError,\n\tonSuccess,\n\t...props\n} ) {\n\tconst [ name, setName ] = useState( decodeEntities( category.name ) );\n\tconst [ isSaving, setIsSaving ] = useState( false );\n\n\tconst { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );\n\n\tconst { createErrorNotice, createSuccessNotice } =\n\t\tuseDispatch( noticesStore );\n\n\tconst onRename = async ( event ) => {\n\t\tevent.preventDefault();\n\n\t\tif ( ! name || name === category.name || isSaving ) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tsetIsSaving( true );\n\n\t\t\t// User pattern category properties may differ as they can be\n\t\t\t// normalized for use alongside template part areas, core pattern\n\t\t\t// categories etc. As a result we won't just destructure the passed\n\t\t\t// category object.\n\t\t\tconst savedRecord = await saveEntityRecord(\n\t\t\t\t'taxonomy',\n\t\t\t\tCATEGORY_SLUG,\n\t\t\t\t{\n\t\t\t\t\tid: category.id,\n\t\t\t\t\tslug: category.slug,\n\t\t\t\t\tname,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tinvalidateResolution( 'getUserPatternCategories' );\n\t\t\tonSuccess?.( savedRecord );\n\t\t\tonClose();\n\n\t\t\tcreateSuccessNotice( __( 'Pattern category renamed.' ), {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} catch ( error ) {\n\t\t\tonError?.();\n\t\t\tcreateErrorNotice( error.message, {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} finally {\n\t\t\tsetIsSaving( false );\n\t\t\tsetName( '' );\n\t\t}\n\t};\n\n\tconst onRequestClose = () => {\n\t\tonClose();\n\t\tsetName( '' );\n\t};\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ __( 'Rename' ) }\n\t\t\tonRequestClose={ onRequestClose }\n\t\t\t{ ...props }\n\t\t>\n\t\t\t<form onSubmit={ onRename }>\n\t\t\t\t<VStack spacing=\"5\">\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\t__nextHasNoMarginBottom\n\t\t\t\t\t\tlabel={ __( 'Name' ) }\n\t\t\t\t\t\tvalue={ name }\n\t\t\t\t\t\tonChange={ setName }\n\t\t\t\t\t\trequired\n\t\t\t\t\t/>\n\t\t\t\t\t<HStack justify=\"right\">\n\t\t\t\t\t\t<Button variant=\"tertiary\" onClick={ onRequestClose }>\n\t\t\t\t\t\t\t{ __( 'Cancel' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\taria-disabled={\n\t\t\t\t\t\t\t\t! name || name === category.name || isSaving\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tisBusy={ isSaving }\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Save' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</HStack>\n\t\t\t\t</VStack>\n\t\t\t</form>\n\t\t</Modal>\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"names":["_components","require","_coreData","_data","_element","_htmlEntities","_i18n","_notices","_a11y","_categorySelector","RenamePatternCategoryModal","category","existingCategories","onClose","onError","onSuccess","props","id","useId","textControlRef","useRef","name","setName","useState","decodeEntities","isSaving","setIsSaving","validationMessage","setValidationMessage","validationMessageId","undefined","saveEntityRecord","invalidateResolution","useDispatch","coreStore","createErrorNotice","createSuccessNotice","noticesStore","onChange","newName","onSave","event","preventDefault","message","__","speak","current","focus","patternCategories","find","existingCategory","label","toLowerCase","savedRecord","CATEGORY_SLUG","slug","type","error","onRequestClose","_react","createElement","Modal","title","onSubmit","__experimentalVStack","spacing","TextControl","ref","__nextHasNoMarginBottom","value","required","className","__experimentalHStack","justify","Button","variant","onClick","isBusy"],"sources":["@wordpress/patterns/src/components/rename-pattern-category-modal.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport {\n\tModal,\n\tButton,\n\tTextControl,\n\t__experimentalHStack as HStack,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useDispatch } from '@wordpress/data';\nimport { useId, useRef, useState } from '@wordpress/element';\nimport { decodeEntities } from '@wordpress/html-entities';\nimport { __ } from '@wordpress/i18n';\nimport { store as noticesStore } from '@wordpress/notices';\nimport { speak } from '@wordpress/a11y';\n\n/**\n * Internal dependencies\n */\nimport { CATEGORY_SLUG } from './category-selector';\n\nexport default function RenamePatternCategoryModal( {\n\tcategory,\n\texistingCategories,\n\tonClose,\n\tonError,\n\tonSuccess,\n\t...props\n} ) {\n\tconst id = useId();\n\tconst textControlRef = useRef();\n\tconst [ name, setName ] = useState( decodeEntities( category.name ) );\n\tconst [ isSaving, setIsSaving ] = useState( false );\n\tconst [ validationMessage, setValidationMessage ] = useState( false );\n\tconst validationMessageId = validationMessage\n\t\t? `patterns-rename-pattern-category-modal__validation-message-${ id }`\n\t\t: undefined;\n\n\tconst { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );\n\tconst { createErrorNotice, createSuccessNotice } =\n\t\tuseDispatch( noticesStore );\n\n\tconst onChange = ( newName ) => {\n\t\tif ( validationMessage ) {\n\t\t\tsetValidationMessage( undefined );\n\t\t}\n\t\tsetName( newName );\n\t};\n\n\tconst onSave = async ( event ) => {\n\t\tevent.preventDefault();\n\n\t\tif ( isSaving ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( ! name || name === category.name ) {\n\t\t\tconst message = __( 'Please enter a new name for this category.' );\n\t\t\tspeak( message, 'assertive' );\n\t\t\tsetValidationMessage( message );\n\t\t\ttextControlRef.current?.focus();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check existing categories to avoid creating duplicates.\n\t\tif (\n\t\t\texistingCategories.patternCategories.find( ( existingCategory ) => {\n\t\t\t\t// Compare the id so that the we don't disallow the user changing the case of their current category\n\t\t\t\t// (i.e. renaming 'test' to 'Test').\n\t\t\t\treturn (\n\t\t\t\t\texistingCategory.id !== category.id &&\n\t\t\t\t\texistingCategory.label.toLowerCase() === name.toLowerCase()\n\t\t\t\t);\n\t\t\t} )\n\t\t) {\n\t\t\tconst message = __(\n\t\t\t\t'This category already exists. Please use a different name.'\n\t\t\t);\n\t\t\tspeak( message, 'assertive' );\n\t\t\tsetValidationMessage( message );\n\t\t\ttextControlRef.current?.focus();\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tsetIsSaving( true );\n\n\t\t\t// User pattern category properties may differ as they can be\n\t\t\t// normalized for use alongside template part areas, core pattern\n\t\t\t// categories etc. As a result we won't just destructure the passed\n\t\t\t// category object.\n\t\t\tconst savedRecord = await saveEntityRecord(\n\t\t\t\t'taxonomy',\n\t\t\t\tCATEGORY_SLUG,\n\t\t\t\t{\n\t\t\t\t\tid: category.id,\n\t\t\t\t\tslug: category.slug,\n\t\t\t\t\tname,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tinvalidateResolution( 'getUserPatternCategories' );\n\t\t\tonSuccess?.( savedRecord );\n\t\t\tonClose();\n\n\t\t\tcreateSuccessNotice( __( 'Pattern category renamed.' ), {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} catch ( error ) {\n\t\t\tonError?.();\n\t\t\tcreateErrorNotice( error.message, {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} finally {\n\t\t\tsetIsSaving( false );\n\t\t\tsetName( '' );\n\t\t}\n\t};\n\n\tconst onRequestClose = () => {\n\t\tonClose();\n\t\tsetName( '' );\n\t};\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ __( 'Rename' ) }\n\t\t\tonRequestClose={ onRequestClose }\n\t\t\t{ ...props }\n\t\t>\n\t\t\t<form onSubmit={ onSave }>\n\t\t\t\t<VStack spacing=\"5\">\n\t\t\t\t\t<VStack spacing=\"2\">\n\t\t\t\t\t\t<TextControl\n\t\t\t\t\t\t\tref={ textControlRef }\n\t\t\t\t\t\t\t__nextHasNoMarginBottom\n\t\t\t\t\t\t\tlabel={ __( 'Name' ) }\n\t\t\t\t\t\t\tvalue={ name }\n\t\t\t\t\t\t\tonChange={ onChange }\n\t\t\t\t\t\t\taria-describedby={ validationMessageId }\n\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{ validationMessage && (\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"patterns-rename-pattern-category-modal__validation-message\"\n\t\t\t\t\t\t\t\tid={ validationMessageId }\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ validationMessage }\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t) }\n\t\t\t\t\t</VStack>\n\t\t\t\t\t<HStack justify=\"right\">\n\t\t\t\t\t\t<Button variant=\"tertiary\" onClick={ onRequestClose }>\n\t\t\t\t\t\t\t{ __( 'Cancel' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\taria-disabled={\n\t\t\t\t\t\t\t\t! name || name === category.name || isSaving\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tisBusy={ isSaving }\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Save' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</HStack>\n\t\t\t\t</VStack>\n\t\t\t</form>\n\t\t</Modal>\n\t);\n}\n"],"mappings":"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"}
@@ -5,10 +5,11 @@ import { createElement } from "react";
5
5
  import { Modal, Button, TextControl, __experimentalHStack as HStack, __experimentalVStack as VStack } from '@wordpress/components';
6
6
  import { store as coreStore } from '@wordpress/core-data';
7
7
  import { useDispatch } from '@wordpress/data';
8
- import { useState } from '@wordpress/element';
8
+ import { useId, useRef, useState } from '@wordpress/element';
9
9
  import { decodeEntities } from '@wordpress/html-entities';
10
10
  import { __ } from '@wordpress/i18n';
11
11
  import { store as noticesStore } from '@wordpress/notices';
12
+ import { speak } from '@wordpress/a11y';
12
13
 
13
14
  /**
14
15
  * Internal dependencies
@@ -16,13 +17,18 @@ import { store as noticesStore } from '@wordpress/notices';
16
17
  import { CATEGORY_SLUG } from './category-selector';
17
18
  export default function RenamePatternCategoryModal({
18
19
  category,
20
+ existingCategories,
19
21
  onClose,
20
22
  onError,
21
23
  onSuccess,
22
24
  ...props
23
25
  }) {
26
+ const id = useId();
27
+ const textControlRef = useRef();
24
28
  const [name, setName] = useState(decodeEntities(category.name));
25
29
  const [isSaving, setIsSaving] = useState(false);
30
+ const [validationMessage, setValidationMessage] = useState(false);
31
+ const validationMessageId = validationMessage ? `patterns-rename-pattern-category-modal__validation-message-${id}` : undefined;
26
32
  const {
27
33
  saveEntityRecord,
28
34
  invalidateResolution
@@ -31,9 +37,35 @@ export default function RenamePatternCategoryModal({
31
37
  createErrorNotice,
32
38
  createSuccessNotice
33
39
  } = useDispatch(noticesStore);
34
- const onRename = async event => {
40
+ const onChange = newName => {
41
+ if (validationMessage) {
42
+ setValidationMessage(undefined);
43
+ }
44
+ setName(newName);
45
+ };
46
+ const onSave = async event => {
35
47
  event.preventDefault();
36
- if (!name || name === category.name || isSaving) {
48
+ if (isSaving) {
49
+ return;
50
+ }
51
+ if (!name || name === category.name) {
52
+ const message = __('Please enter a new name for this category.');
53
+ speak(message, 'assertive');
54
+ setValidationMessage(message);
55
+ textControlRef.current?.focus();
56
+ return;
57
+ }
58
+
59
+ // Check existing categories to avoid creating duplicates.
60
+ if (existingCategories.patternCategories.find(existingCategory => {
61
+ // Compare the id so that the we don't disallow the user changing the case of their current category
62
+ // (i.e. renaming 'test' to 'Test').
63
+ return existingCategory.id !== category.id && existingCategory.label.toLowerCase() === name.toLowerCase();
64
+ })) {
65
+ const message = __('This category already exists. Please use a different name.');
66
+ speak(message, 'assertive');
67
+ setValidationMessage(message);
68
+ textControlRef.current?.focus();
37
69
  return;
38
70
  }
39
71
  try {
@@ -75,16 +107,23 @@ export default function RenamePatternCategoryModal({
75
107
  onRequestClose: onRequestClose,
76
108
  ...props
77
109
  }, createElement("form", {
78
- onSubmit: onRename
110
+ onSubmit: onSave
79
111
  }, createElement(VStack, {
80
112
  spacing: "5"
113
+ }, createElement(VStack, {
114
+ spacing: "2"
81
115
  }, createElement(TextControl, {
116
+ ref: textControlRef,
82
117
  __nextHasNoMarginBottom: true,
83
118
  label: __('Name'),
84
119
  value: name,
85
- onChange: setName,
120
+ onChange: onChange,
121
+ "aria-describedby": validationMessageId,
86
122
  required: true
87
- }), createElement(HStack, {
123
+ }), validationMessage && createElement("span", {
124
+ className: "patterns-rename-pattern-category-modal__validation-message",
125
+ id: validationMessageId
126
+ }, validationMessage)), createElement(HStack, {
88
127
  justify: "right"
89
128
  }, createElement(Button, {
90
129
  variant: "tertiary",
@@ -1 +1 @@
1
- {"version":3,"names":["Modal","Button","TextControl","__experimentalHStack","HStack","__experimentalVStack","VStack","store","coreStore","useDispatch","useState","decodeEntities","__","noticesStore","CATEGORY_SLUG","RenamePatternCategoryModal","category","onClose","onError","onSuccess","props","name","setName","isSaving","setIsSaving","saveEntityRecord","invalidateResolution","createErrorNotice","createSuccessNotice","onRename","event","preventDefault","savedRecord","id","slug","type","error","message","onRequestClose","createElement","title","onSubmit","spacing","__nextHasNoMarginBottom","label","value","onChange","required","justify","variant","onClick","isBusy"],"sources":["@wordpress/patterns/src/components/rename-pattern-category-modal.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport {\n\tModal,\n\tButton,\n\tTextControl,\n\t__experimentalHStack as HStack,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useDispatch } from '@wordpress/data';\nimport { useState } from '@wordpress/element';\nimport { decodeEntities } from '@wordpress/html-entities';\nimport { __ } from '@wordpress/i18n';\nimport { store as noticesStore } from '@wordpress/notices';\n\n/**\n * Internal dependencies\n */\nimport { CATEGORY_SLUG } from './category-selector';\n\nexport default function RenamePatternCategoryModal( {\n\tcategory,\n\tonClose,\n\tonError,\n\tonSuccess,\n\t...props\n} ) {\n\tconst [ name, setName ] = useState( decodeEntities( category.name ) );\n\tconst [ isSaving, setIsSaving ] = useState( false );\n\n\tconst { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );\n\n\tconst { createErrorNotice, createSuccessNotice } =\n\t\tuseDispatch( noticesStore );\n\n\tconst onRename = async ( event ) => {\n\t\tevent.preventDefault();\n\n\t\tif ( ! name || name === category.name || isSaving ) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tsetIsSaving( true );\n\n\t\t\t// User pattern category properties may differ as they can be\n\t\t\t// normalized for use alongside template part areas, core pattern\n\t\t\t// categories etc. As a result we won't just destructure the passed\n\t\t\t// category object.\n\t\t\tconst savedRecord = await saveEntityRecord(\n\t\t\t\t'taxonomy',\n\t\t\t\tCATEGORY_SLUG,\n\t\t\t\t{\n\t\t\t\t\tid: category.id,\n\t\t\t\t\tslug: category.slug,\n\t\t\t\t\tname,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tinvalidateResolution( 'getUserPatternCategories' );\n\t\t\tonSuccess?.( savedRecord );\n\t\t\tonClose();\n\n\t\t\tcreateSuccessNotice( __( 'Pattern category renamed.' ), {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} catch ( error ) {\n\t\t\tonError?.();\n\t\t\tcreateErrorNotice( error.message, {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} finally {\n\t\t\tsetIsSaving( false );\n\t\t\tsetName( '' );\n\t\t}\n\t};\n\n\tconst onRequestClose = () => {\n\t\tonClose();\n\t\tsetName( '' );\n\t};\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ __( 'Rename' ) }\n\t\t\tonRequestClose={ onRequestClose }\n\t\t\t{ ...props }\n\t\t>\n\t\t\t<form onSubmit={ onRename }>\n\t\t\t\t<VStack spacing=\"5\">\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\t__nextHasNoMarginBottom\n\t\t\t\t\t\tlabel={ __( 'Name' ) }\n\t\t\t\t\t\tvalue={ name }\n\t\t\t\t\t\tonChange={ setName }\n\t\t\t\t\t\trequired\n\t\t\t\t\t/>\n\t\t\t\t\t<HStack justify=\"right\">\n\t\t\t\t\t\t<Button variant=\"tertiary\" onClick={ onRequestClose }>\n\t\t\t\t\t\t\t{ __( 'Cancel' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\taria-disabled={\n\t\t\t\t\t\t\t\t! name || name === category.name || isSaving\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tisBusy={ isSaving }\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Save' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</HStack>\n\t\t\t\t</VStack>\n\t\t\t</form>\n\t\t</Modal>\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"names":["Modal","Button","TextControl","__experimentalHStack","HStack","__experimentalVStack","VStack","store","coreStore","useDispatch","useId","useRef","useState","decodeEntities","__","noticesStore","speak","CATEGORY_SLUG","RenamePatternCategoryModal","category","existingCategories","onClose","onError","onSuccess","props","id","textControlRef","name","setName","isSaving","setIsSaving","validationMessage","setValidationMessage","validationMessageId","undefined","saveEntityRecord","invalidateResolution","createErrorNotice","createSuccessNotice","onChange","newName","onSave","event","preventDefault","message","current","focus","patternCategories","find","existingCategory","label","toLowerCase","savedRecord","slug","type","error","onRequestClose","createElement","title","onSubmit","spacing","ref","__nextHasNoMarginBottom","value","required","className","justify","variant","onClick","isBusy"],"sources":["@wordpress/patterns/src/components/rename-pattern-category-modal.js"],"sourcesContent":["/**\n * WordPress dependencies\n */\nimport {\n\tModal,\n\tButton,\n\tTextControl,\n\t__experimentalHStack as HStack,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useDispatch } from '@wordpress/data';\nimport { useId, useRef, useState } from '@wordpress/element';\nimport { decodeEntities } from '@wordpress/html-entities';\nimport { __ } from '@wordpress/i18n';\nimport { store as noticesStore } from '@wordpress/notices';\nimport { speak } from '@wordpress/a11y';\n\n/**\n * Internal dependencies\n */\nimport { CATEGORY_SLUG } from './category-selector';\n\nexport default function RenamePatternCategoryModal( {\n\tcategory,\n\texistingCategories,\n\tonClose,\n\tonError,\n\tonSuccess,\n\t...props\n} ) {\n\tconst id = useId();\n\tconst textControlRef = useRef();\n\tconst [ name, setName ] = useState( decodeEntities( category.name ) );\n\tconst [ isSaving, setIsSaving ] = useState( false );\n\tconst [ validationMessage, setValidationMessage ] = useState( false );\n\tconst validationMessageId = validationMessage\n\t\t? `patterns-rename-pattern-category-modal__validation-message-${ id }`\n\t\t: undefined;\n\n\tconst { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );\n\tconst { createErrorNotice, createSuccessNotice } =\n\t\tuseDispatch( noticesStore );\n\n\tconst onChange = ( newName ) => {\n\t\tif ( validationMessage ) {\n\t\t\tsetValidationMessage( undefined );\n\t\t}\n\t\tsetName( newName );\n\t};\n\n\tconst onSave = async ( event ) => {\n\t\tevent.preventDefault();\n\n\t\tif ( isSaving ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( ! name || name === category.name ) {\n\t\t\tconst message = __( 'Please enter a new name for this category.' );\n\t\t\tspeak( message, 'assertive' );\n\t\t\tsetValidationMessage( message );\n\t\t\ttextControlRef.current?.focus();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check existing categories to avoid creating duplicates.\n\t\tif (\n\t\t\texistingCategories.patternCategories.find( ( existingCategory ) => {\n\t\t\t\t// Compare the id so that the we don't disallow the user changing the case of their current category\n\t\t\t\t// (i.e. renaming 'test' to 'Test').\n\t\t\t\treturn (\n\t\t\t\t\texistingCategory.id !== category.id &&\n\t\t\t\t\texistingCategory.label.toLowerCase() === name.toLowerCase()\n\t\t\t\t);\n\t\t\t} )\n\t\t) {\n\t\t\tconst message = __(\n\t\t\t\t'This category already exists. Please use a different name.'\n\t\t\t);\n\t\t\tspeak( message, 'assertive' );\n\t\t\tsetValidationMessage( message );\n\t\t\ttextControlRef.current?.focus();\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tsetIsSaving( true );\n\n\t\t\t// User pattern category properties may differ as they can be\n\t\t\t// normalized for use alongside template part areas, core pattern\n\t\t\t// categories etc. As a result we won't just destructure the passed\n\t\t\t// category object.\n\t\t\tconst savedRecord = await saveEntityRecord(\n\t\t\t\t'taxonomy',\n\t\t\t\tCATEGORY_SLUG,\n\t\t\t\t{\n\t\t\t\t\tid: category.id,\n\t\t\t\t\tslug: category.slug,\n\t\t\t\t\tname,\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tinvalidateResolution( 'getUserPatternCategories' );\n\t\t\tonSuccess?.( savedRecord );\n\t\t\tonClose();\n\n\t\t\tcreateSuccessNotice( __( 'Pattern category renamed.' ), {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} catch ( error ) {\n\t\t\tonError?.();\n\t\t\tcreateErrorNotice( error.message, {\n\t\t\t\ttype: 'snackbar',\n\t\t\t\tid: 'pattern-category-update',\n\t\t\t} );\n\t\t} finally {\n\t\t\tsetIsSaving( false );\n\t\t\tsetName( '' );\n\t\t}\n\t};\n\n\tconst onRequestClose = () => {\n\t\tonClose();\n\t\tsetName( '' );\n\t};\n\n\treturn (\n\t\t<Modal\n\t\t\ttitle={ __( 'Rename' ) }\n\t\t\tonRequestClose={ onRequestClose }\n\t\t\t{ ...props }\n\t\t>\n\t\t\t<form onSubmit={ onSave }>\n\t\t\t\t<VStack spacing=\"5\">\n\t\t\t\t\t<VStack spacing=\"2\">\n\t\t\t\t\t\t<TextControl\n\t\t\t\t\t\t\tref={ textControlRef }\n\t\t\t\t\t\t\t__nextHasNoMarginBottom\n\t\t\t\t\t\t\tlabel={ __( 'Name' ) }\n\t\t\t\t\t\t\tvalue={ name }\n\t\t\t\t\t\t\tonChange={ onChange }\n\t\t\t\t\t\t\taria-describedby={ validationMessageId }\n\t\t\t\t\t\t\trequired\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{ validationMessage && (\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"patterns-rename-pattern-category-modal__validation-message\"\n\t\t\t\t\t\t\t\tid={ validationMessageId }\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ validationMessage }\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t) }\n\t\t\t\t\t</VStack>\n\t\t\t\t\t<HStack justify=\"right\">\n\t\t\t\t\t\t<Button variant=\"tertiary\" onClick={ onRequestClose }>\n\t\t\t\t\t\t\t{ __( 'Cancel' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t\ttype=\"submit\"\n\t\t\t\t\t\t\taria-disabled={\n\t\t\t\t\t\t\t\t! name || name === category.name || isSaving\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tisBusy={ isSaving }\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Save' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</HStack>\n\t\t\t\t</VStack>\n\t\t\t</form>\n\t\t</Modal>\n\t);\n}\n"],"mappings":";AAAA;AACA;AACA;AACA,SACCA,KAAK,EACLC,MAAM,EACNC,WAAW,EACXC,oBAAoB,IAAIC,MAAM,EAC9BC,oBAAoB,IAAIC,MAAM,QACxB,uBAAuB;AAC9B,SAASC,KAAK,IAAIC,SAAS,QAAQ,sBAAsB;AACzD,SAASC,WAAW,QAAQ,iBAAiB;AAC7C,SAASC,KAAK,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,oBAAoB;AAC5D,SAASC,cAAc,QAAQ,0BAA0B;AACzD,SAASC,EAAE,QAAQ,iBAAiB;AACpC,SAASP,KAAK,IAAIQ,YAAY,QAAQ,oBAAoB;AAC1D,SAASC,KAAK,QAAQ,iBAAiB;;AAEvC;AACA;AACA;AACA,SAASC,aAAa,QAAQ,qBAAqB;AAEnD,eAAe,SAASC,0BAA0BA,CAAE;EACnDC,QAAQ;EACRC,kBAAkB;EAClBC,OAAO;EACPC,OAAO;EACPC,SAAS;EACT,GAAGC;AACJ,CAAC,EAAG;EACH,MAAMC,EAAE,GAAGf,KAAK,CAAC,CAAC;EAClB,MAAMgB,cAAc,GAAGf,MAAM,CAAC,CAAC;EAC/B,MAAM,CAAEgB,IAAI,EAAEC,OAAO,CAAE,GAAGhB,QAAQ,CAAEC,cAAc,CAAEM,QAAQ,CAACQ,IAAK,CAAE,CAAC;EACrE,MAAM,CAAEE,QAAQ,EAAEC,WAAW,CAAE,GAAGlB,QAAQ,CAAE,KAAM,CAAC;EACnD,MAAM,CAAEmB,iBAAiB,EAAEC,oBAAoB,CAAE,GAAGpB,QAAQ,CAAE,KAAM,CAAC;EACrE,MAAMqB,mBAAmB,GAAGF,iBAAiB,GACzC,8DAA8DN,EAAI,EAAC,GACpES,SAAS;EAEZ,MAAM;IAAEC,gBAAgB;IAAEC;EAAqB,CAAC,GAAG3B,WAAW,CAAED,SAAU,CAAC;EAC3E,MAAM;IAAE6B,iBAAiB;IAAEC;EAAoB,CAAC,GAC/C7B,WAAW,CAAEM,YAAa,CAAC;EAE5B,MAAMwB,QAAQ,GAAKC,OAAO,IAAM;IAC/B,IAAKT,iBAAiB,EAAG;MACxBC,oBAAoB,CAAEE,SAAU,CAAC;IAClC;IACAN,OAAO,CAAEY,OAAQ,CAAC;EACnB,CAAC;EAED,MAAMC,MAAM,GAAG,MAAQC,KAAK,IAAM;IACjCA,KAAK,CAACC,cAAc,CAAC,CAAC;IAEtB,IAAKd,QAAQ,EAAG;MACf;IACD;IAEA,IAAK,CAAEF,IAAI,IAAIA,IAAI,KAAKR,QAAQ,CAACQ,IAAI,EAAG;MACvC,MAAMiB,OAAO,GAAG9B,EAAE,CAAE,4CAA6C,CAAC;MAClEE,KAAK,CAAE4B,OAAO,EAAE,WAAY,CAAC;MAC7BZ,oBAAoB,CAAEY,OAAQ,CAAC;MAC/BlB,cAAc,CAACmB,OAAO,EAAEC,KAAK,CAAC,CAAC;MAC/B;IACD;;IAEA;IACA,IACC1B,kBAAkB,CAAC2B,iBAAiB,CAACC,IAAI,CAAIC,gBAAgB,IAAM;MAClE;MACA;MACA,OACCA,gBAAgB,CAACxB,EAAE,KAAKN,QAAQ,CAACM,EAAE,IACnCwB,gBAAgB,CAACC,KAAK,CAACC,WAAW,CAAC,CAAC,KAAKxB,IAAI,CAACwB,WAAW,CAAC,CAAC;IAE7D,CAAE,CAAC,EACF;MACD,MAAMP,OAAO,GAAG9B,EAAE,CACjB,4DACD,CAAC;MACDE,KAAK,CAAE4B,OAAO,EAAE,WAAY,CAAC;MAC7BZ,oBAAoB,CAAEY,OAAQ,CAAC;MAC/BlB,cAAc,CAACmB,OAAO,EAAEC,KAAK,CAAC,CAAC;MAC/B;IACD;IAEA,IAAI;MACHhB,WAAW,CAAE,IAAK,CAAC;;MAEnB;MACA;MACA;MACA;MACA,MAAMsB,WAAW,GAAG,MAAMjB,gBAAgB,CACzC,UAAU,EACVlB,aAAa,EACb;QACCQ,EAAE,EAAEN,QAAQ,CAACM,EAAE;QACf4B,IAAI,EAAElC,QAAQ,CAACkC,IAAI;QACnB1B;MACD,CACD,CAAC;MAEDS,oBAAoB,CAAE,0BAA2B,CAAC;MAClDb,SAAS,GAAI6B,WAAY,CAAC;MAC1B/B,OAAO,CAAC,CAAC;MAETiB,mBAAmB,CAAExB,EAAE,CAAE,2BAA4B,CAAC,EAAE;QACvDwC,IAAI,EAAE,UAAU;QAChB7B,EAAE,EAAE;MACL,CAAE,CAAC;IACJ,CAAC,CAAC,OAAQ8B,KAAK,EAAG;MACjBjC,OAAO,GAAG,CAAC;MACXe,iBAAiB,CAAEkB,KAAK,CAACX,OAAO,EAAE;QACjCU,IAAI,EAAE,UAAU;QAChB7B,EAAE,EAAE;MACL,CAAE,CAAC;IACJ,CAAC,SAAS;MACTK,WAAW,CAAE,KAAM,CAAC;MACpBF,OAAO,CAAE,EAAG,CAAC;IACd;EACD,CAAC;EAED,MAAM4B,cAAc,GAAGA,CAAA,KAAM;IAC5BnC,OAAO,CAAC,CAAC;IACTO,OAAO,CAAE,EAAG,CAAC;EACd,CAAC;EAED,OACC6B,aAAA,CAACzD,KAAK;IACL0D,KAAK,EAAG5C,EAAE,CAAE,QAAS,CAAG;IACxB0C,cAAc,EAAGA,cAAgB;IAAA,GAC5BhC;EAAK,GAEViC,aAAA;IAAME,QAAQ,EAAGlB;EAAQ,GACxBgB,aAAA,CAACnD,MAAM;IAACsD,OAAO,EAAC;EAAG,GAClBH,aAAA,CAACnD,MAAM;IAACsD,OAAO,EAAC;EAAG,GAClBH,aAAA,CAACvD,WAAW;IACX2D,GAAG,EAAGnC,cAAgB;IACtBoC,uBAAuB;IACvBZ,KAAK,EAAGpC,EAAE,CAAE,MAAO,CAAG;IACtBiD,KAAK,EAAGpC,IAAM;IACdY,QAAQ,EAAGA,QAAU;IACrB,oBAAmBN,mBAAqB;IACxC+B,QAAQ;EAAA,CACR,CAAC,EACAjC,iBAAiB,IAClB0B,aAAA;IACCQ,SAAS,EAAC,4DAA4D;IACtExC,EAAE,EAAGQ;EAAqB,GAExBF,iBACG,CAEA,CAAC,EACT0B,aAAA,CAACrD,MAAM;IAAC8D,OAAO,EAAC;EAAO,GACtBT,aAAA,CAACxD,MAAM;IAACkE,OAAO,EAAC,UAAU;IAACC,OAAO,EAAGZ;EAAgB,GAClD1C,EAAE,CAAE,QAAS,CACR,CAAC,EACT2C,aAAA,CAACxD,MAAM;IACNkE,OAAO,EAAC,SAAS;IACjBb,IAAI,EAAC,QAAQ;IACb,iBACC,CAAE3B,IAAI,IAAIA,IAAI,KAAKR,QAAQ,CAACQ,IAAI,IAAIE,QACpC;IACDwC,MAAM,EAAGxC;EAAU,GAEjBf,EAAE,CAAE,MAAO,CACN,CACD,CACD,CACH,CACA,CAAC;AAEV"}
@@ -132,4 +132,13 @@
132
132
  .patterns-create-modal__name-input input[type=text] {
133
133
  min-height: 40px;
134
134
  margin: 0;
135
+ }
136
+
137
+ .patterns-rename-pattern-category-modal__validation-message {
138
+ color: #cc1818;
139
+ }
140
+ @media (min-width: 782px) {
141
+ .patterns-rename-pattern-category-modal__validation-message {
142
+ width: 320px;
143
+ }
135
144
  }
@@ -132,4 +132,13 @@
132
132
  .patterns-create-modal__name-input input[type=text] {
133
133
  min-height: 40px;
134
134
  margin: 0;
135
+ }
136
+
137
+ .patterns-rename-pattern-category-modal__validation-message {
138
+ color: #cc1818;
139
+ }
140
+ @media (min-width: 782px) {
141
+ .patterns-rename-pattern-category-modal__validation-message {
142
+ width: 320px;
143
+ }
135
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/patterns",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Management of user pattern editing.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -31,19 +31,20 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "@babel/runtime": "^7.16.0",
34
- "@wordpress/block-editor": "^12.12.0",
35
- "@wordpress/blocks": "^12.21.0",
36
- "@wordpress/components": "^25.10.0",
37
- "@wordpress/compose": "^6.21.0",
38
- "@wordpress/core-data": "^6.21.0",
39
- "@wordpress/data": "^9.14.0",
40
- "@wordpress/element": "^5.21.0",
41
- "@wordpress/html-entities": "^3.44.0",
42
- "@wordpress/i18n": "^4.44.0",
43
- "@wordpress/icons": "^9.35.0",
44
- "@wordpress/notices": "^4.12.0",
45
- "@wordpress/private-apis": "^0.26.0",
46
- "@wordpress/url": "^3.45.0"
34
+ "@wordpress/a11y": "^3.45.0",
35
+ "@wordpress/block-editor": "^12.13.0",
36
+ "@wordpress/blocks": "^12.22.0",
37
+ "@wordpress/components": "^25.11.0",
38
+ "@wordpress/compose": "^6.22.0",
39
+ "@wordpress/core-data": "^6.22.0",
40
+ "@wordpress/data": "^9.15.0",
41
+ "@wordpress/element": "^5.22.0",
42
+ "@wordpress/html-entities": "^3.45.0",
43
+ "@wordpress/i18n": "^4.45.0",
44
+ "@wordpress/icons": "^9.36.0",
45
+ "@wordpress/notices": "^4.13.0",
46
+ "@wordpress/private-apis": "^0.27.0",
47
+ "@wordpress/url": "^3.46.0"
47
48
  },
48
49
  "peerDependencies": {
49
50
  "react": "^18.0.0",
@@ -52,5 +53,5 @@
52
53
  "publishConfig": {
53
54
  "access": "public"
54
55
  },
55
- "gitHead": "f83bb1a71e8fa416131b81a9f282a72a1dc6c694"
56
+ "gitHead": "2a00e87b57b9c27ed2b9b0fd8d423ef3cada72c1"
56
57
  }
@@ -10,10 +10,11 @@ import {
10
10
  } from '@wordpress/components';
11
11
  import { store as coreStore } from '@wordpress/core-data';
12
12
  import { useDispatch } from '@wordpress/data';
13
- import { useState } from '@wordpress/element';
13
+ import { useId, useRef, useState } from '@wordpress/element';
14
14
  import { decodeEntities } from '@wordpress/html-entities';
15
15
  import { __ } from '@wordpress/i18n';
16
16
  import { store as noticesStore } from '@wordpress/notices';
17
+ import { speak } from '@wordpress/a11y';
17
18
 
18
19
  /**
19
20
  * Internal dependencies
@@ -22,23 +23,64 @@ import { CATEGORY_SLUG } from './category-selector';
22
23
 
23
24
  export default function RenamePatternCategoryModal( {
24
25
  category,
26
+ existingCategories,
25
27
  onClose,
26
28
  onError,
27
29
  onSuccess,
28
30
  ...props
29
31
  } ) {
32
+ const id = useId();
33
+ const textControlRef = useRef();
30
34
  const [ name, setName ] = useState( decodeEntities( category.name ) );
31
35
  const [ isSaving, setIsSaving ] = useState( false );
36
+ const [ validationMessage, setValidationMessage ] = useState( false );
37
+ const validationMessageId = validationMessage
38
+ ? `patterns-rename-pattern-category-modal__validation-message-${ id }`
39
+ : undefined;
32
40
 
33
41
  const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );
34
-
35
42
  const { createErrorNotice, createSuccessNotice } =
36
43
  useDispatch( noticesStore );
37
44
 
38
- const onRename = async ( event ) => {
45
+ const onChange = ( newName ) => {
46
+ if ( validationMessage ) {
47
+ setValidationMessage( undefined );
48
+ }
49
+ setName( newName );
50
+ };
51
+
52
+ const onSave = async ( event ) => {
39
53
  event.preventDefault();
40
54
 
41
- if ( ! name || name === category.name || isSaving ) {
55
+ if ( isSaving ) {
56
+ return;
57
+ }
58
+
59
+ if ( ! name || name === category.name ) {
60
+ const message = __( 'Please enter a new name for this category.' );
61
+ speak( message, 'assertive' );
62
+ setValidationMessage( message );
63
+ textControlRef.current?.focus();
64
+ return;
65
+ }
66
+
67
+ // Check existing categories to avoid creating duplicates.
68
+ if (
69
+ existingCategories.patternCategories.find( ( existingCategory ) => {
70
+ // Compare the id so that the we don't disallow the user changing the case of their current category
71
+ // (i.e. renaming 'test' to 'Test').
72
+ return (
73
+ existingCategory.id !== category.id &&
74
+ existingCategory.label.toLowerCase() === name.toLowerCase()
75
+ );
76
+ } )
77
+ ) {
78
+ const message = __(
79
+ 'This category already exists. Please use a different name.'
80
+ );
81
+ speak( message, 'assertive' );
82
+ setValidationMessage( message );
83
+ textControlRef.current?.focus();
42
84
  return;
43
85
  }
44
86
 
@@ -90,15 +132,27 @@ export default function RenamePatternCategoryModal( {
90
132
  onRequestClose={ onRequestClose }
91
133
  { ...props }
92
134
  >
93
- <form onSubmit={ onRename }>
135
+ <form onSubmit={ onSave }>
94
136
  <VStack spacing="5">
95
- <TextControl
96
- __nextHasNoMarginBottom
97
- label={ __( 'Name' ) }
98
- value={ name }
99
- onChange={ setName }
100
- required
101
- />
137
+ <VStack spacing="2">
138
+ <TextControl
139
+ ref={ textControlRef }
140
+ __nextHasNoMarginBottom
141
+ label={ __( 'Name' ) }
142
+ value={ name }
143
+ onChange={ onChange }
144
+ aria-describedby={ validationMessageId }
145
+ required
146
+ />
147
+ { validationMessage && (
148
+ <span
149
+ className="patterns-rename-pattern-category-modal__validation-message"
150
+ id={ validationMessageId }
151
+ >
152
+ { validationMessage }
153
+ </span>
154
+ ) }
155
+ </VStack>
102
156
  <HStack justify="right">
103
157
  <Button variant="tertiary" onClick={ onRequestClose }>
104
158
  { __( 'Cancel' ) }
@@ -34,3 +34,12 @@
34
34
  // Override the default 1px margin-x.
35
35
  margin: 0;
36
36
  }
37
+
38
+ .patterns-rename-pattern-category-modal__validation-message {
39
+ color: $alert-red;
40
+
41
+ // Match the input size.
42
+ @include break-medium() {
43
+ width: $grid-unit * 40;
44
+ }
45
+ }