@wordpress/components 32.6.0 → 33.0.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 (208) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/build/autocomplete/get-autocomplete-match.cjs +11 -2
  3. package/build/autocomplete/get-autocomplete-match.cjs.map +2 -2
  4. package/build/autocomplete/index.cjs +42 -11
  5. package/build/autocomplete/index.cjs.map +2 -2
  6. package/build/external-link/index.cjs +1 -1
  7. package/build/external-link/index.cjs.map +2 -2
  8. package/build/form-token-field/index.cjs +22 -6
  9. package/build/form-token-field/index.cjs.map +3 -3
  10. package/build/form-token-field/token-input.cjs +1 -1
  11. package/build/form-token-field/token-input.cjs.map +2 -2
  12. package/build/menu/popover.cjs +7 -3
  13. package/build/menu/popover.cjs.map +2 -2
  14. package/build/menu/styles.cjs +39 -16
  15. package/build/menu/styles.cjs.map +2 -2
  16. package/build/navigable-container/container.cjs +72 -110
  17. package/build/navigable-container/container.cjs.map +2 -2
  18. package/build/utils/breakpoint.cjs.map +1 -1
  19. package/build/utils/font.cjs.map +1 -1
  20. package/build/visually-hidden/component.cjs +1 -0
  21. package/build/visually-hidden/component.cjs.map +2 -2
  22. package/build-module/autocomplete/get-autocomplete-match.mjs +11 -2
  23. package/build-module/autocomplete/get-autocomplete-match.mjs.map +2 -2
  24. package/build-module/autocomplete/index.mjs +42 -11
  25. package/build-module/autocomplete/index.mjs.map +2 -2
  26. package/build-module/external-link/index.mjs +1 -1
  27. package/build-module/external-link/index.mjs.map +2 -2
  28. package/build-module/form-token-field/index.mjs +22 -6
  29. package/build-module/form-token-field/index.mjs.map +2 -2
  30. package/build-module/form-token-field/token-input.mjs +1 -1
  31. package/build-module/form-token-field/token-input.mjs.map +2 -2
  32. package/build-module/menu/popover.mjs +7 -3
  33. package/build-module/menu/popover.mjs.map +2 -2
  34. package/build-module/menu/styles.mjs +37 -16
  35. package/build-module/menu/styles.mjs.map +2 -2
  36. package/build-module/navigable-container/container.mjs +73 -111
  37. package/build-module/navigable-container/container.mjs.map +2 -2
  38. package/build-module/utils/breakpoint.mjs.map +1 -1
  39. package/build-module/utils/font.mjs.map +1 -1
  40. package/build-module/visually-hidden/component.mjs +1 -0
  41. package/build-module/visually-hidden/component.mjs.map +2 -2
  42. package/build-style/style-rtl.css +26 -2
  43. package/build-style/style.css +26 -2
  44. package/build-types/autocomplete/get-autocomplete-match.d.ts +10 -1
  45. package/build-types/autocomplete/get-autocomplete-match.d.ts.map +1 -1
  46. package/build-types/autocomplete/index.d.ts.map +1 -1
  47. package/build-types/base-control/stories/index.story.d.ts.map +1 -1
  48. package/build-types/button/stories/index.story.d.ts.map +1 -1
  49. package/build-types/card/stories/index.story.d.ts +0 -6
  50. package/build-types/card/stories/index.story.d.ts.map +1 -1
  51. package/build-types/checkbox-control/stories/index.story.d.ts.map +1 -1
  52. package/build-types/color-indicator/stories/index.story.d.ts.map +1 -1
  53. package/build-types/color-palette/stories/index.story.d.ts.map +1 -1
  54. package/build-types/color-picker/stories/index.story.d.ts.map +1 -1
  55. package/build-types/combobox-control/stories/index.story.d.ts.map +1 -1
  56. package/build-types/composite/stories/index.story.d.ts.map +1 -1
  57. package/build-types/custom-select-control/stories/index.story.d.ts.map +1 -1
  58. package/build-types/disabled/stories/index.story.d.ts.map +1 -1
  59. package/build-types/drop-zone/stories/index.story.d.ts.map +1 -1
  60. package/build-types/dropdown/stories/index.story.d.ts.map +1 -1
  61. package/build-types/external-link/index.d.ts.map +1 -1
  62. package/build-types/external-link/stories/index.story.d.ts.map +1 -1
  63. package/build-types/form-file-upload/stories/index.story.d.ts.map +1 -1
  64. package/build-types/form-toggle/stories/index.story.d.ts.map +1 -1
  65. package/build-types/form-token-field/index.d.ts.map +1 -1
  66. package/build-types/form-token-field/stories/index.story.d.ts.map +1 -1
  67. package/build-types/form-token-field/token-input.d.ts.map +1 -1
  68. package/build-types/form-token-field/types.d.ts +16 -2
  69. package/build-types/form-token-field/types.d.ts.map +1 -1
  70. package/build-types/gradient-picker/stories/index.story.d.ts.map +1 -1
  71. package/build-types/icon/stories/index.story.d.ts.map +1 -1
  72. package/build-types/keyboard-shortcuts/stories/index.story.d.ts.map +1 -1
  73. package/build-types/menu/popover.d.ts.map +1 -1
  74. package/build-types/menu/styles.d.ts +16 -1
  75. package/build-types/menu/styles.d.ts.map +1 -1
  76. package/build-types/menu-group/stories/index.story.d.ts.map +1 -1
  77. package/build-types/menu-item/stories/index.story.d.ts.map +1 -1
  78. package/build-types/menu-items-choice/stories/index.story.d.ts.map +1 -1
  79. package/build-types/modal/stories/index.story.d.ts.map +1 -1
  80. package/build-types/navigable-container/container.d.ts +3 -8
  81. package/build-types/navigable-container/container.d.ts.map +1 -1
  82. package/build-types/navigable-container/types.d.ts +1 -5
  83. package/build-types/navigable-container/types.d.ts.map +1 -1
  84. package/build-types/navigation/stories/utils/more-examples.d.ts.map +1 -1
  85. package/build-types/navigator/stories/index.story.d.ts.map +1 -1
  86. package/build-types/notice/stories/index.story.d.ts.map +1 -1
  87. package/build-types/panel/stories/index.story.d.ts.map +1 -1
  88. package/build-types/popover/stories/index.story.d.ts.map +1 -1
  89. package/build-types/progress-bar/stories/index.story.d.ts.map +1 -1
  90. package/build-types/radio-control/stories/index.story.d.ts.map +1 -1
  91. package/build-types/range-control/stories/index.story.d.ts.map +1 -1
  92. package/build-types/resizable-box/stories/index.story.d.ts.map +1 -1
  93. package/build-types/sandbox/stories/index.story.d.ts.map +1 -1
  94. package/build-types/scroll-lock/stories/index.story.d.ts.map +1 -1
  95. package/build-types/search-control/stories/index.story.d.ts.map +1 -1
  96. package/build-types/select-control/stories/index.story.d.ts.map +1 -1
  97. package/build-types/shortcut/stories/index.story.d.ts.map +1 -1
  98. package/build-types/slot-fill/stories/index.story.d.ts.map +1 -1
  99. package/build-types/snackbar/stories/index.story.d.ts.map +1 -1
  100. package/build-types/spinner/stories/index.story.d.ts.map +1 -1
  101. package/build-types/text-control/stories/index.story.d.ts.map +1 -1
  102. package/build-types/text-highlight/stories/index.story.d.ts.map +1 -1
  103. package/build-types/textarea-control/stories/index.story.d.ts.map +1 -1
  104. package/build-types/toggle-control/stories/index.story.d.ts.map +1 -1
  105. package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
  106. package/build-types/tree-select/stories/index.story.d.ts.map +1 -1
  107. package/build-types/utils/breakpoint.d.ts +2 -1
  108. package/build-types/utils/breakpoint.d.ts.map +1 -1
  109. package/build-types/utils/font.d.ts +3 -2
  110. package/build-types/utils/font.d.ts.map +1 -1
  111. package/build-types/visually-hidden/component.d.ts.map +1 -1
  112. package/build-types/visually-hidden/stories/index.story.d.ts +0 -6
  113. package/build-types/visually-hidden/stories/index.story.d.ts.map +1 -1
  114. package/package.json +21 -21
  115. package/src/autocomplete/get-autocomplete-match.ts +25 -4
  116. package/src/autocomplete/index.tsx +69 -21
  117. package/src/autocomplete/test/get-autocomplete-match.ts +97 -75
  118. package/src/base-control/stories/index.story.tsx +1 -0
  119. package/src/button/stories/index.story.tsx +1 -0
  120. package/src/button-group/style.scss +1 -2
  121. package/src/card/stories/index.story.tsx +2 -9
  122. package/src/checkbox-control/stories/index.story.tsx +1 -0
  123. package/src/circular-option-picker/style.scss +8 -6
  124. package/src/color-indicator/stories/index.story.tsx +1 -0
  125. package/src/color-palette/stories/index.story.tsx +1 -0
  126. package/src/color-picker/stories/index.story.tsx +1 -0
  127. package/src/combobox-control/stories/index.story.tsx +1 -0
  128. package/src/composite/stories/index.story.tsx +1 -0
  129. package/src/confirm-dialog/stories/index.story.tsx +1 -1
  130. package/src/custom-select-control/stories/index.story.tsx +1 -0
  131. package/src/disabled/stories/index.story.tsx +1 -0
  132. package/src/drop-zone/stories/index.story.tsx +1 -0
  133. package/src/dropdown/stories/index.story.tsx +1 -0
  134. package/src/external-link/index.tsx +1 -6
  135. package/src/external-link/stories/index.story.tsx +2 -1
  136. package/src/external-link/style.scss +30 -2
  137. package/src/form-file-upload/stories/index.story.tsx +1 -0
  138. package/src/form-toggle/stories/index.story.tsx +1 -0
  139. package/src/form-toggle/style.scss +3 -2
  140. package/src/form-token-field/README.md +2 -1
  141. package/src/form-token-field/index.tsx +39 -9
  142. package/src/form-token-field/stories/index.story.tsx +2 -0
  143. package/src/form-token-field/test/index.tsx +70 -10
  144. package/src/form-token-field/token-input.tsx +1 -6
  145. package/src/form-token-field/types.ts +16 -2
  146. package/src/gradient-picker/stories/index.story.tsx +1 -0
  147. package/src/icon/stories/index.story.tsx +1 -0
  148. package/src/input-control/stories/index.story.tsx +1 -1
  149. package/src/item-group/stories/index.story.tsx +1 -1
  150. package/src/keyboard-shortcuts/stories/index.story.tsx +1 -0
  151. package/src/menu/popover.tsx +15 -8
  152. package/src/menu/styles.ts +26 -16
  153. package/src/menu/test/index.tsx +24 -34
  154. package/src/menu-group/stories/index.story.tsx +1 -0
  155. package/src/menu-item/stories/index.story.tsx +1 -0
  156. package/src/menu-items-choice/stories/index.story.tsx +1 -0
  157. package/src/mobile/link-settings/index.native.js +1 -1
  158. package/src/modal/stories/index.story.tsx +1 -0
  159. package/src/navigable-container/container.tsx +120 -141
  160. package/src/navigable-container/test/navigable-menu.tsx +24 -0
  161. package/src/navigable-container/types.ts +1 -5
  162. package/src/navigation/stories/utils/more-examples.tsx +2 -1
  163. package/src/navigator/stories/index.story.tsx +1 -0
  164. package/src/notice/stories/index.story.tsx +1 -0
  165. package/src/notice/test/__snapshots__/index.tsx.snap +1 -0
  166. package/src/number-control/stories/index.story.tsx +1 -1
  167. package/src/panel/stories/index.story.tsx +1 -0
  168. package/src/popover/stories/index.story.tsx +1 -0
  169. package/src/progress-bar/stories/index.story.tsx +1 -0
  170. package/src/radio-control/stories/index.story.tsx +1 -0
  171. package/src/range-control/stories/index.story.tsx +1 -0
  172. package/src/resizable-box/stories/index.story.tsx +1 -0
  173. package/src/resizable-box/style.scss +4 -5
  174. package/src/sandbox/stories/index.story.tsx +1 -0
  175. package/src/scroll-lock/stories/index.story.tsx +1 -0
  176. package/src/search-control/stories/index.story.tsx +1 -0
  177. package/src/select-control/stories/index.story.tsx +1 -0
  178. package/src/shortcut/stories/index.story.tsx +1 -0
  179. package/src/slot-fill/stories/index.story.tsx +1 -0
  180. package/src/snackbar/stories/index.story.tsx +1 -0
  181. package/src/spinner/stories/index.story.tsx +1 -0
  182. package/src/text-control/stories/index.story.tsx +1 -0
  183. package/src/text-highlight/stories/index.story.tsx +1 -0
  184. package/src/textarea-control/stories/index.story.tsx +1 -0
  185. package/src/toggle-control/stories/index.story.tsx +1 -0
  186. package/src/toggle-group-control/stories/index.story.tsx +1 -1
  187. package/src/toolbar/toolbar-group/index.tsx +2 -2
  188. package/src/tooltip/stories/index.story.tsx +1 -0
  189. package/src/tooltip/test/index.tsx +3 -2
  190. package/src/tree-grid/stories/index.story.tsx +1 -1
  191. package/src/tree-select/stories/index.story.tsx +1 -0
  192. package/src/truncate/stories/index.story.tsx +1 -1
  193. package/src/unit-control/stories/index.story.tsx +1 -1
  194. package/src/utils/breakpoint.js +1 -1
  195. package/src/utils/font.js +1 -1
  196. package/src/visually-hidden/component.tsx +1 -0
  197. package/src/visually-hidden/stories/index.story.tsx +2 -8
  198. package/build/card/context.cjs +0 -36
  199. package/build/card/context.cjs.map +0 -7
  200. package/build-module/card/context.mjs +0 -10
  201. package/build-module/card/context.mjs.map +0 -7
  202. package/build-types/card/context.d.ts +0 -3
  203. package/build-types/card/context.d.ts.map +0 -1
  204. package/build-types/visually-hidden/test/index.d.ts +0 -2
  205. package/build-types/visually-hidden/test/index.d.ts.map +0 -1
  206. package/src/card/context.ts +0 -9
  207. package/src/visually-hidden/test/__snapshots__/index.tsx.snap +0 -12
  208. package/src/visually-hidden/test/index.tsx +0 -17
@@ -14,6 +14,7 @@ import MenuItem from '../../menu-item';
14
14
  import { DropdownContentWrapper } from '../dropdown-content-wrapper';
15
15
 
16
16
  const meta: Meta< typeof Dropdown > = {
17
+ tags: [ 'manifest' ],
17
18
  title: 'Components/Overlays/Dropdown',
18
19
  id: 'components-dropdown',
19
20
  component: Dropdown,
@@ -26,12 +26,7 @@ function UnforwardedExternalLink(
26
26
  const { href, children, className, rel = '', ...additionalProps } = props;
27
27
  const optimizedRel = [
28
28
  ...new Set(
29
- [
30
- ...rel.split( ' ' ),
31
- 'external',
32
- 'noreferrer',
33
- 'noopener',
34
- ].filter( Boolean )
29
+ [ ...rel.split( ' ' ), 'external', 'noopener' ].filter( Boolean )
35
30
  ),
36
31
  ].join( ' ' );
37
32
  const classes = clsx( 'components-external-link', className );
@@ -21,8 +21,9 @@ const meta: Meta< typeof ExternalLink > = {
21
21
  },
22
22
  docs: { canvas: { sourceState: 'shown' } },
23
23
  componentStatus: {
24
- status: 'stable',
24
+ status: 'not-recommended',
25
25
  whereUsed: 'global',
26
+ notes: 'Use `Link` from `@wordpress/ui` instead, with the `openInNewTab` prop set.',
26
27
  },
27
28
  },
28
29
  };
@@ -1,12 +1,40 @@
1
1
  .components-external-link {
2
+ color: var(--wpds-color-fg-interactive-brand);
2
3
  text-decoration: none;
4
+
5
+ // Match the `outset-ring--focus` utility used by `Link` in `@wordpress/ui`.
6
+ @media not ( prefers-reduced-motion ) {
7
+ transition: outline 0.1s ease-out;
8
+ }
9
+ // Outline width must be kept at 0 even with a transparent color,
10
+ // or else the outline will be visible in forced-colors mode.
11
+ outline: 0 solid transparent;
12
+ outline-offset: 1px;
13
+
14
+ &:visited {
15
+ color: var(--wpds-color-fg-interactive-brand);
16
+ }
17
+
18
+ &:hover,
19
+ &:active {
20
+ color: var(--wpds-color-fg-interactive-brand-active);
21
+ }
22
+
23
+ &:focus {
24
+ outline:
25
+ var(--wpds-border-width-focus) solid
26
+ var(--wpds-color-stroke-focus-brand);
27
+ }
3
28
  }
4
29
 
5
30
  .components-external-link__contents {
6
31
  text-decoration: underline;
32
+ text-underline-offset: 0.2em;
33
+ text-decoration-thickness: from-font;
7
34
  }
8
35
 
9
36
  .components-external-link__icon {
10
- margin-left: 0.5ch;
11
- font-weight: 400;
37
+ display: inline-block;
38
+ margin-inline-start: var(--wpds-dimension-padding-xs);
39
+ font-weight: var(--wpds-typography-font-weight-regular);
12
40
  }
@@ -14,6 +14,7 @@ import { upload as uploadIcon } from '@wordpress/icons';
14
14
  import FormFileUpload from '..';
15
15
 
16
16
  const meta: Meta< typeof FormFileUpload > = {
17
+ tags: [ 'manifest' ],
17
18
  title: 'Components/Selection & Input/File Upload/FormFileUpload',
18
19
  id: 'components-formfileupload',
19
20
  component: FormFileUpload,
@@ -14,6 +14,7 @@ import { useState } from '@wordpress/element';
14
14
  import FormToggle from '..';
15
15
 
16
16
  const meta: Meta< typeof FormToggle > = {
17
+ tags: [ 'manifest' ],
17
18
  component: FormToggle,
18
19
  title: 'Components/FormToggle',
19
20
  argTypes: {
@@ -2,7 +2,6 @@
2
2
  @use "@wordpress/base-styles/colors" as *;
3
3
  @use "@wordpress/base-styles/mixins" as *;
4
4
  @use "@wordpress/base-styles/variables" as *;
5
- @use "@wordpress/base-styles/z-index" as *;
6
5
  @use "../utils/theme-variables" as *;
7
6
 
8
7
  $toggle-width: $grid-unit-40;
@@ -14,6 +13,7 @@ $transition-duration: 0.2s;
14
13
 
15
14
  .components-form-toggle {
16
15
  position: relative;
16
+ isolation: isolate;
17
17
  display: inline-block;
18
18
  height: $toggle-height;
19
19
 
@@ -151,7 +151,8 @@ $transition-duration: 0.2s;
151
151
  opacity: 0;
152
152
  margin: 0;
153
153
  padding: 0;
154
- z-index: z-index(".components-form-toggle__input");
154
+ // Sit above the visual track and thumb so the invisible input receives pointer events.
155
+ z-index: 1;
155
156
 
156
157
  // This overrides a border style that is inherited from parent checkbox styles.
157
158
  border: none;
@@ -51,6 +51,7 @@ The `value` property is handled in a manner similar to controlled form component
51
51
  - `maxLength` - If passed, `TokenField` will disable ability to add new tokens once number of tokens is greater than or equal to `maxLength`.
52
52
  - `disabled` - When true, tokens are not able to be added or removed.
53
53
  - `placeholder` - If passed, the `TokenField` input will show a placeholder string if no value tokens are present.
54
+ - `help` - Additional description for the control. Only use for meaningful description or instructions for the control. An element containing the description will be programmatically associated to the `FormTokenField` via `aria-describedby`. Defaults to a how-to message (e.g. _Separate with commas or the Enter key._); pass an empty string to hide it.
54
55
  - `messages` - Allows customizing the messages presented by screen readers in different occasions:
55
56
  - `added` - The user added a new token.
56
57
  - `removed` - The user removed an existing token.
@@ -58,7 +59,7 @@ The `value` property is handled in a manner similar to controlled form component
58
59
  - `__experimentalInvalid` - The user tried to add a token that didn't pass the validation.
59
60
  - `__experimentalRenderItem` - Custom renderer invoked for each option in the suggestion list. The render prop receives as its argument an object containing, under the `item` key, the single option's data (directly from the array of data passed to the `options` prop).
60
61
  - `__experimentalExpandOnFocus` - If true, the suggestions list will be always expanded when the input field has the focus.
61
- - `__experimentalShowHowTo` - If false, the text on how to use the select (ie: _Separate with commas or the Enter key._) will be hidden.
62
+ - `__experimentalShowHowTo` - **Deprecated.** Use the `help` prop instead. The `help` prop now defaults to the previous how-to text; if you were passing `__experimentalShowHowTo={ false }` to hide it, pass an empty string to `help` instead.
62
63
  - `__experimentalValidateInput` - If passed, all introduced values will be validated before being added as tokens.
63
64
  - `__experimentalAutoSelectFirstMatch` - If true, the select the first matching suggestion when the user presses the Enter key (or space when tokenizeOnSpace is true).
64
65
  - `__next40pxDefaultSize` - Start opting into the larger default height that will become the default size in a future version.
@@ -2,7 +2,13 @@
2
2
  * External dependencies
3
3
  */
4
4
  import clsx from 'clsx';
5
- import type { KeyboardEvent, MouseEvent, TouchEvent, FocusEvent } from 'react';
5
+ import type {
6
+ KeyboardEvent,
7
+ MouseEvent,
8
+ TouchEvent,
9
+ FocusEvent,
10
+ ReactNode,
11
+ } from 'react';
6
12
 
7
13
  /**
8
14
  * WordPress dependencies
@@ -12,6 +18,7 @@ import { __, _n, sprintf } from '@wordpress/i18n';
12
18
  import { useDebounce, useInstanceId, usePrevious } from '@wordpress/compose';
13
19
  import { speak } from '@wordpress/a11y';
14
20
  import { isShallowEqual } from '@wordpress/is-shallow-equal';
21
+ import deprecated from '@wordpress/deprecated';
15
22
 
16
23
  /**
17
24
  * Internal dependencies
@@ -70,10 +77,11 @@ export function FormTokenField( props: FormTokenFieldProps ) {
70
77
  __experimentalRenderItem,
71
78
  __experimentalExpandOnFocus = false,
72
79
  __experimentalValidateInput = () => true,
73
- __experimentalShowHowTo = true,
80
+ __experimentalShowHowTo,
74
81
  __next40pxDefaultSize = false,
75
82
  __experimentalAutoSelectFirstMatch = false,
76
83
  tokenizeOnBlur = false,
84
+ help,
77
85
  } = useDeprecated36pxDefaultSizeProp< FormTokenFieldProps >( props );
78
86
 
79
87
  maybeWarnDeprecated36pxSize( {
@@ -82,6 +90,27 @@ export function FormTokenField( props: FormTokenFieldProps ) {
82
90
  __next40pxDefaultSize,
83
91
  } );
84
92
 
93
+ const defaultHelp = tokenizeOnSpace
94
+ ? __( 'Separate with commas, spaces, or the Enter key.' )
95
+ : __( 'Separate with commas or the Enter key.' );
96
+
97
+ let computedHelp: ReactNode = help !== undefined ? help : defaultHelp;
98
+
99
+ if ( typeof __experimentalShowHowTo === 'boolean' ) {
100
+ deprecated(
101
+ '`__experimentalShowHowTo` prop in wp.components.FormTokenField',
102
+ {
103
+ since: '7.1',
104
+ alternative: '`help` prop',
105
+ hint: 'The `help` prop now defaults to the previous how-to text. Pass an empty string to hide it.',
106
+ }
107
+ );
108
+
109
+ if ( __experimentalShowHowTo === false && help === undefined ) {
110
+ computedHelp = '';
111
+ }
112
+ }
113
+
85
114
  const instanceId = useInstanceId( FormTokenField );
86
115
 
87
116
  // We reset to these initial values again in the onBlur
@@ -655,6 +684,10 @@ export function FormTokenField( props: FormTokenFieldProps ) {
655
684
  }
656
685
 
657
686
  function renderInput() {
687
+ const describedById = computedHelp
688
+ ? `components-form-token-input-${ instanceId }__help`
689
+ : undefined;
690
+
658
691
  const inputProps = {
659
692
  instanceId,
660
693
  autoCapitalize,
@@ -665,6 +698,7 @@ export function FormTokenField( props: FormTokenFieldProps ) {
665
698
  onBlur,
666
699
  isExpanded,
667
700
  selectedSuggestionIndex,
701
+ 'aria-describedby': describedById,
668
702
  };
669
703
 
670
704
  return (
@@ -749,16 +783,12 @@ export function FormTokenField( props: FormTokenFieldProps ) {
749
783
  />
750
784
  ) }
751
785
  </div>
752
- { __experimentalShowHowTo && (
786
+ { computedHelp && (
753
787
  <StyledHelp
754
- id={ `components-form-token-suggestions-howto-${ instanceId }` }
788
+ id={ `components-form-token-input-${ instanceId }__help` }
755
789
  className="components-form-token-field__help"
756
790
  >
757
- { tokenizeOnSpace
758
- ? __(
759
- 'Separate with commas, spaces, or the Enter key.'
760
- )
761
- : __( 'Separate with commas or the Enter key.' ) }
791
+ { computedHelp }
762
792
  </StyledHelp>
763
793
  ) }
764
794
  </div>
@@ -14,6 +14,7 @@ import { useState } from '@wordpress/element';
14
14
  import FormTokenField from '../';
15
15
 
16
16
  const meta: Meta< typeof FormTokenField > = {
17
+ tags: [ 'manifest' ],
17
18
  component: FormTokenField,
18
19
  title: 'Components/Selection & Input/Common/FormTokenField',
19
20
  id: 'components-formtokenfield',
@@ -24,6 +25,7 @@ const meta: Meta< typeof FormTokenField > = {
24
25
  __experimentalValidateInput: {
25
26
  control: false,
26
27
  },
28
+ help: { control: 'text' },
27
29
  },
28
30
  parameters: {
29
31
  controls: {
@@ -17,6 +17,7 @@ import type { ComponentProps } from 'react';
17
17
  * WordPress dependencies
18
18
  */
19
19
  import { useState } from '@wordpress/element';
20
+ import { logged } from '@wordpress/deprecated';
20
21
 
21
22
  /**
22
23
  * Internal dependencies
@@ -122,6 +123,14 @@ function unescapeAndFormatSpaces( str: string ) {
122
123
  }
123
124
 
124
125
  describe( 'FormTokenField', () => {
126
+ afterEach( () => {
127
+ // `@wordpress/deprecated` caches each warning message after the first
128
+ // log; reset it so multiple tests can assert the same deprecation.
129
+ for ( const key in logged ) {
130
+ delete logged[ key ];
131
+ }
132
+ } );
133
+
125
134
  describe( 'basic usage', () => {
126
135
  it( "should add tokens with the input's value when pressing the enter key", async () => {
127
136
  const user = userEvent.setup();
@@ -582,18 +591,17 @@ describe( 'FormTokenField', () => {
582
591
  );
583
592
  } );
584
593
 
585
- it( 'should show extra instructions when the `__experimentalShowHowTo` prop is set to `true`', () => {
594
+ it( 'should show the default how-to text via the `help` prop by default', () => {
586
595
  const instructionsTokenizeSpace =
587
596
  'Separate with commas, spaces, or the Enter key.';
588
597
  const instructionsDefault =
589
598
  'Separate with commas or the Enter key.';
590
599
 
591
- // The __experimentalShowHowTo prop is `true` by default
592
600
  const { rerender } = render( <FormTokenFieldWithState /> );
593
601
 
594
602
  expect( screen.getByText( instructionsDefault ) ).toBeVisible();
595
603
 
596
- // The "show how to" text is used to aria-describedby the input
604
+ // The default how-to text is used to aria-describedby the input.
597
605
  expect(
598
606
  screen.getByRole( 'combobox' )
599
607
  ).toHaveAccessibleDescription( instructionsDefault );
@@ -604,27 +612,79 @@ describe( 'FormTokenField', () => {
604
612
  screen.getByText( instructionsTokenizeSpace )
605
613
  ).toBeVisible();
606
614
 
607
- // The "show how to" text is used to aria-describedby the input
608
615
  expect(
609
616
  screen.getByRole( 'combobox' )
610
617
  ).toHaveAccessibleDescription( instructionsTokenizeSpace );
618
+ } );
611
619
 
612
- rerender(
620
+ it( 'should allow hiding the help text by passing an empty string', () => {
621
+ render( <FormTokenFieldWithState help="" /> );
622
+
623
+ expect(
624
+ screen.queryByText( 'Separate with commas or the Enter key.' )
625
+ ).not.toBeInTheDocument();
626
+ expect(
627
+ screen.getByRole( 'combobox' )
628
+ ).not.toHaveAccessibleDescription();
629
+ } );
630
+
631
+ it( 'should associate the `help` text with the input accessibly', () => {
632
+ render( <FormTokenFieldWithState help="Help text" /> );
633
+ expect(
634
+ screen.getByRole( 'combobox' )
635
+ ).toHaveAccessibleDescription( 'Help text' );
636
+ // The default how-to text should no longer be rendered.
637
+ expect(
638
+ screen.queryByText( 'Separate with commas or the Enter key.' )
639
+ ).not.toBeInTheDocument();
640
+ } );
641
+
642
+ it( 'should warn and hide the default text when `__experimentalShowHowTo` is `false`', () => {
643
+ render(
644
+ <FormTokenFieldWithState __experimentalShowHowTo={ false } />
645
+ );
646
+
647
+ expect( console ).toHaveWarnedWith(
648
+ '`__experimentalShowHowTo` prop in wp.components.FormTokenField is deprecated since version 7.1. Please use `help` prop instead. Note: The `help` prop now defaults to the previous how-to text. Pass an empty string to hide it.'
649
+ );
650
+
651
+ expect(
652
+ screen.queryByText( 'Separate with commas or the Enter key.' )
653
+ ).not.toBeInTheDocument();
654
+ expect(
655
+ screen.getByRole( 'combobox' )
656
+ ).not.toHaveAccessibleDescription();
657
+ } );
658
+
659
+ it( 'should warn and prefer `help` over `__experimentalShowHowTo` when both are provided', () => {
660
+ const { rerender } = render(
613
661
  <FormTokenFieldWithState
614
- tokenizeOnSpace
615
662
  __experimentalShowHowTo={ false }
663
+ help="Help text"
616
664
  />
617
665
  );
618
666
 
667
+ expect( console ).toHaveWarned();
619
668
  expect(
620
- screen.queryByText( instructionsDefault )
621
- ).not.toBeInTheDocument();
669
+ screen.getByRole( 'combobox' )
670
+ ).toHaveAccessibleDescription( 'Help text' );
622
671
  expect(
623
- screen.queryByText( instructionsTokenizeSpace )
672
+ screen.queryByText( 'Separate with commas or the Enter key.' )
624
673
  ).not.toBeInTheDocument();
674
+
675
+ rerender(
676
+ <FormTokenFieldWithState
677
+ __experimentalShowHowTo
678
+ help="Help text"
679
+ />
680
+ );
681
+
625
682
  expect(
626
683
  screen.getByRole( 'combobox' )
627
- ).not.toHaveAccessibleDescription();
684
+ ).toHaveAccessibleDescription( 'Help text' );
685
+ expect(
686
+ screen.queryByText( 'Separate with commas or the Enter key.' )
687
+ ).not.toBeInTheDocument();
628
688
  } );
629
689
 
630
690
  it( "should use the value of the `placeholder` prop as the input's placeholder only when there are no tokens", async () => {
@@ -87,12 +87,7 @@ export function UnForwardedTokenInput(
87
87
  ? `components-form-token-suggestions-${ instanceId }-${ selectedSuggestionIndex }`
88
88
  : undefined
89
89
  }
90
- aria-describedby={ [
91
- `components-form-token-suggestions-howto-${ instanceId }`,
92
- ariaDescribedBy,
93
- ]
94
- .filter( Boolean )
95
- .join( ' ' ) }
90
+ aria-describedby={ ariaDescribedBy }
96
91
  />
97
92
  );
98
93
  }
@@ -147,9 +147,12 @@ export interface FormTokenFieldProps
147
147
  */
148
148
  __experimentalValidateInput?: ( token: string ) => boolean;
149
149
  /**
150
- * If false, the text on how to use the select (ie: _Separate with commas or the Enter key._) will be hidden.
150
+ * Use the `help` prop instead. The `help` prop now defaults to the previous
151
+ * how-to text; if you were passing `__experimentalShowHowTo={ false }` to
152
+ * hide it, pass an empty string to `help` instead.
151
153
  *
152
- * @default true
154
+ * @deprecated Use the `help` prop instead.
155
+ * @ignore
153
156
  */
154
157
  __experimentalShowHowTo?: boolean;
155
158
  /**
@@ -193,6 +196,17 @@ export interface FormTokenFieldProps
193
196
  * @default false
194
197
  */
195
198
  tokenizeOnBlur?: boolean;
199
+ /**
200
+ * Additional description for the control.
201
+ *
202
+ * Only use for meaningful description or instructions for the control. An
203
+ * element containing the description will be programmatically associated to
204
+ * the `FormTokenField` via `aria-describedby`.
205
+ *
206
+ * Defaults to a how-to message (e.g. _Separate with commas or the Enter key._);
207
+ * pass an empty string to hide it.
208
+ */
209
+ help?: ReactNode;
196
210
  }
197
211
 
198
212
  /**
@@ -15,6 +15,7 @@ import { useState } from '@wordpress/element';
15
15
  import GradientPicker from '..';
16
16
 
17
17
  const meta: Meta< typeof GradientPicker > = {
18
+ tags: [ 'manifest' ],
18
19
  title: 'Components/Selection & Input/Color/GradientPicker',
19
20
  id: 'components-gradientpicker',
20
21
  component: GradientPicker,
@@ -16,6 +16,7 @@ import Icon from '..';
16
16
  import { VStack } from '../../v-stack';
17
17
 
18
18
  const meta: Meta< typeof Icon > = {
19
+ tags: [ 'manifest' ],
19
20
  title: 'Components/Icon',
20
21
  component: Icon,
21
22
  parameters: {
@@ -31,7 +31,7 @@ const meta: Meta< typeof InputControl > = {
31
31
  type: { control: { type: 'text' } },
32
32
  value: { control: { disable: true } },
33
33
  },
34
- tags: [ 'status-experimental' ],
34
+ tags: [ 'status-experimental', 'manifest' ],
35
35
  args: {
36
36
  onChange: fn(),
37
37
  onValidate: fn(),
@@ -19,7 +19,7 @@ const meta: Meta< typeof ItemGroup > = {
19
19
  as: { control: false },
20
20
  children: { control: false },
21
21
  },
22
- tags: [ 'status-experimental' ],
22
+ tags: [ 'status-experimental', 'manifest' ],
23
23
  parameters: {
24
24
  controls: { expanded: true },
25
25
  docs: { canvas: { sourceState: 'shown' } },
@@ -9,6 +9,7 @@ import type { Meta, StoryFn } from '@storybook/react-vite';
9
9
  import KeyboardShortcuts from '..';
10
10
 
11
11
  const meta: Meta< typeof KeyboardShortcuts > = {
12
+ tags: [ 'manifest' ],
12
13
  component: KeyboardShortcuts,
13
14
  title: 'Components/Utilities/KeyboardShortcuts',
14
15
  id: 'components-keyboardshortcuts',
@@ -24,10 +24,7 @@ import { Context } from './context';
24
24
  export const Popover = forwardRef<
25
25
  HTMLDivElement,
26
26
  WordPressComponentProps< PopoverProps, 'div', false >
27
- >( function Popover(
28
- { gutter, children, shift, modal = true, ...otherProps },
29
- ref
30
- ) {
27
+ >( function Popover( { gutter, shift, modal = true, ...otherProps }, ref ) {
31
28
  const menuContext = useContext( Context );
32
29
 
33
30
  // Extract the side from the applied placement — useful for animations.
@@ -70,6 +67,18 @@ export const Popover = forwardRef<
70
67
  );
71
68
  }
72
69
 
70
+ const renderMenu = useCallback(
71
+ ( htmlProps: React.ComponentPropsWithRef< 'div' > ) => (
72
+ <Styled.MenuMotionRoot>
73
+ <Styled.MenuSurface
74
+ { ...htmlProps }
75
+ variant={ menuContext.variant }
76
+ />
77
+ </Styled.MenuMotionRoot>
78
+ ),
79
+ [ menuContext.variant ]
80
+ );
81
+
73
82
  return (
74
83
  <Styled.Menu
75
84
  { ...otherProps }
@@ -88,9 +97,7 @@ export const Popover = forwardRef<
88
97
  wrapperProps={ wrapperProps }
89
98
  hideOnEscape={ hideOnEscape }
90
99
  unmountOnHide
91
- variant={ menuContext.variant }
92
- >
93
- { children }
94
- </Styled.Menu>
100
+ render={ renderMenu }
101
+ />
95
102
  );
96
103
  } );
@@ -32,12 +32,17 @@ const TOOLBAR_VARIANT_BOX_SHADOW = `0 0 0 ${ CONFIG.borderWidth } ${ TOOLBAR_VAR
32
32
 
33
33
  const GRID_TEMPLATE_COLS = 'minmax( 0, max-content ) 1fr';
34
34
 
35
- export const Menu = styled( Ariakit.Menu )< Pick< ContextProps, 'variant' > >`
35
+ export const Menu = styled( Ariakit.Menu )`
36
36
  position: relative;
37
37
  /* Same as popover component */
38
38
  /* TODO: is there a way to read the sass variable? */
39
39
  z-index: 1000000;
40
40
 
41
+ /* Only visible in Windows High Contrast mode */
42
+ outline: 2px solid transparent !important;
43
+ `;
44
+
45
+ export const MenuSurface = styled.div< Pick< ContextProps, 'variant' > >`
41
46
  display: grid;
42
47
  grid-template-columns: ${ GRID_TEMPLATE_COLS };
43
48
  grid-template-rows: auto;
@@ -59,11 +64,16 @@ export const Menu = styled( Ariakit.Menu )< Pick< ContextProps, 'variant' > >`
59
64
  ? TOOLBAR_VARIANT_BOX_SHADOW
60
65
  : DEFAULT_BOX_SHADOW };
61
66
  ` }
67
+ `;
62
68
 
63
- /* Only visible in Windows High Contrast mode */
64
- outline: 2px solid transparent !important;
65
-
66
- /* Open/close animation */
69
+ /**
70
+ * Outer wrapper for menu motion. `Menu.Popover` uses Ariakit’s `render` prop so
71
+ * this element wraps the inner surface that receives all merged menu props
72
+ * (ref, role, `data-*`, children). Transitions mirror the pre-refactor `Menu`
73
+ * styles from `trunk`, driven by `data-enter` / `data-side` on the inner
74
+ * surface via `:has(> …)`.
75
+ */
76
+ export const MenuMotionRoot = styled.div`
67
77
  @media not ( prefers-reduced-motion ) {
68
78
  transition-property: transform, opacity;
69
79
  transition-duration: ${ DROPDOWN_MOTION_CSS.SLIDE_DURATION },
@@ -72,40 +82,40 @@ export const Menu = styled( Ariakit.Menu )< Pick< ContextProps, 'variant' > >`
72
82
  ${ DROPDOWN_MOTION_CSS.FADE_EASING };
73
83
  will-change: transform, opacity;
74
84
 
75
- &:not( [data-submenu] ) {
85
+ &:not( :has( > ${ MenuSurface }[data-submenu] ) ) {
76
86
  /* Regardless of the side, fade in and out. */
77
87
  opacity: 0;
78
- &[data-enter] {
88
+ &:has( > ${ MenuSurface }[data-enter] ) {
79
89
  opacity: 1;
80
90
  }
81
91
 
82
92
  /* Slide in the direction the menu is opening. */
83
- &[data-side='bottom'] {
93
+ &:has( > ${ MenuSurface }[data-side='bottom'] ) {
84
94
  transform: translateY(
85
95
  -${ DROPDOWN_MOTION_CSS.SLIDE_DISTANCE }
86
96
  );
87
97
  }
88
- &[data-side='top'] {
98
+ &:has( > ${ MenuSurface }[data-side='top'] ) {
89
99
  transform: translateY(
90
100
  ${ DROPDOWN_MOTION_CSS.SLIDE_DISTANCE }
91
101
  );
92
102
  }
93
- &[data-side='left'] {
103
+ &:has( > ${ MenuSurface }[data-side='left'] ) {
94
104
  transform: translateX(
95
105
  ${ DROPDOWN_MOTION_CSS.SLIDE_DISTANCE }
96
106
  );
97
107
  }
98
- &[data-side='right'] {
108
+ &:has( > ${ MenuSurface }[data-side='right'] ) {
99
109
  transform: translateX(
100
110
  -${ DROPDOWN_MOTION_CSS.SLIDE_DISTANCE }
101
111
  );
102
112
  }
103
- &[data-enter][data-side='bottom'],
104
- &[data-enter][data-side='top'] {
113
+ &:has( > ${ MenuSurface }[data-enter][data-side='bottom'] ),
114
+ &:has( > ${ MenuSurface }[data-enter][data-side='top'] ) {
105
115
  transform: translateY( 0 );
106
116
  }
107
- &[data-enter][data-side='left'],
108
- &[data-enter][data-side='right'] {
117
+ &:has( > ${ MenuSurface }[data-enter][data-side='left'] ),
118
+ &:has( > ${ MenuSurface }[data-enter][data-side='right'] ) {
109
119
  transform: translateX( 0 );
110
120
  }
111
121
  }
@@ -114,6 +124,7 @@ export const Menu = styled( Ariakit.Menu )< Pick< ContextProps, 'variant' > >`
114
124
 
115
125
  const baseItem = css`
116
126
  all: unset;
127
+ cursor: pointer;
117
128
 
118
129
  position: relative;
119
130
  min-height: ${ space( 8 ) };
@@ -157,7 +168,6 @@ const baseItem = css`
157
168
 
158
169
  &[aria-disabled='true'] {
159
170
  color: ${ COLORS.ui.textDisabled };
160
- cursor: not-allowed;
161
171
  }
162
172
 
163
173
  /* Active item (including hover) */