@wordpress/components 28.4.0 → 28.5.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 (250) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/build/autocomplete/autocompleter-ui.js +2 -0
  3. package/build/autocomplete/autocompleter-ui.js.map +1 -1
  4. package/build/base-control/styles/base-control-styles.js +8 -8
  5. package/build/base-control/styles/base-control-styles.js.map +1 -1
  6. package/build/border-control/styles.js +18 -24
  7. package/build/border-control/styles.js.map +1 -1
  8. package/build/color-palette/index.js +1 -1
  9. package/build/color-palette/index.js.map +1 -1
  10. package/build/custom-select-control/index.js +37 -14
  11. package/build/custom-select-control/index.js.map +1 -1
  12. package/build/custom-select-control/types.js.map +1 -1
  13. package/build/custom-select-control-v2/styles.js +9 -9
  14. package/build/custom-select-control-v2/styles.js.map +1 -1
  15. package/build/date-time/index.js +0 -7
  16. package/build/date-time/index.js.map +1 -1
  17. package/build/date-time/time/index.js +66 -38
  18. package/build/date-time/time/index.js.map +1 -1
  19. package/build/date-time/time/styles.js +11 -11
  20. package/build/date-time/time/styles.js.map +1 -1
  21. package/build/date-time/{time-input → time/time-input}/index.js +7 -7
  22. package/build/date-time/time/time-input/index.js.map +1 -0
  23. package/build/dropdown-menu-v2/styles.js +14 -14
  24. package/build/dropdown-menu-v2/styles.js.map +1 -1
  25. package/build/form-toggle/index.js +24 -24
  26. package/build/form-toggle/index.js.map +1 -1
  27. package/build/guide/index.js +2 -0
  28. package/build/guide/index.js.map +1 -1
  29. package/build/heading/types.js.map +1 -1
  30. package/build/modal/index.js +18 -11
  31. package/build/modal/index.js.map +1 -1
  32. package/build/query-controls/index.js +1 -1
  33. package/build/query-controls/index.js.map +1 -1
  34. package/build/radio-control/index.js +35 -8
  35. package/build/radio-control/index.js.map +1 -1
  36. package/build/radio-control/types.js.map +1 -1
  37. package/build/select-control/index.js +20 -8
  38. package/build/select-control/index.js.map +1 -1
  39. package/build/select-control/types.js.map +1 -1
  40. package/build/text-control/index.js +1 -0
  41. package/build/text-control/index.js.map +1 -1
  42. package/build/toggle-control/index.js +27 -25
  43. package/build/toggle-control/index.js.map +1 -1
  44. package/build/toggle-group-control/toggle-group-control/component.js +6 -1
  45. package/build/toggle-group-control/toggle-group-control/component.js.map +1 -1
  46. package/build/toggle-group-control/toggle-group-control-option/component.js +6 -1
  47. package/build/toggle-group-control/toggle-group-control-option/component.js.map +1 -1
  48. package/build/toggle-group-control/toggle-group-control-option-icon/component.js +14 -14
  49. package/build/toggle-group-control/toggle-group-control-option-icon/component.js.map +1 -1
  50. package/build/tooltip/index.js +12 -1
  51. package/build/tooltip/index.js.map +1 -1
  52. package/build/tree-select/index.js +1 -2
  53. package/build/tree-select/index.js.map +1 -1
  54. package/build/utils/config-values.js +6 -0
  55. package/build/utils/config-values.js.map +1 -1
  56. package/build-module/autocomplete/autocompleter-ui.js +2 -0
  57. package/build-module/autocomplete/autocompleter-ui.js.map +1 -1
  58. package/build-module/base-control/styles/base-control-styles.js +8 -8
  59. package/build-module/base-control/styles/base-control-styles.js.map +1 -1
  60. package/build-module/border-control/styles.js +13 -23
  61. package/build-module/border-control/styles.js.map +1 -1
  62. package/build-module/color-palette/index.js +1 -1
  63. package/build-module/color-palette/index.js.map +1 -1
  64. package/build-module/custom-select-control/index.js +38 -14
  65. package/build-module/custom-select-control/index.js.map +1 -1
  66. package/build-module/custom-select-control/types.js.map +1 -1
  67. package/build-module/custom-select-control-v2/styles.js +9 -9
  68. package/build-module/custom-select-control-v2/styles.js.map +1 -1
  69. package/build-module/date-time/index.js +1 -2
  70. package/build-module/date-time/index.js.map +1 -1
  71. package/build-module/date-time/time/index.js +66 -38
  72. package/build-module/date-time/time/index.js.map +1 -1
  73. package/build-module/date-time/time/styles.js +11 -11
  74. package/build-module/date-time/time/styles.js.map +1 -1
  75. package/build-module/date-time/{time-input → time/time-input}/index.js +7 -7
  76. package/build-module/date-time/time/time-input/index.js.map +1 -0
  77. package/build-module/dropdown-menu-v2/styles.js +14 -14
  78. package/build-module/dropdown-menu-v2/styles.js.map +1 -1
  79. package/build-module/form-toggle/index.js +23 -22
  80. package/build-module/form-toggle/index.js.map +1 -1
  81. package/build-module/guide/index.js +2 -0
  82. package/build-module/guide/index.js.map +1 -1
  83. package/build-module/heading/types.js.map +1 -1
  84. package/build-module/modal/index.js +17 -11
  85. package/build-module/modal/index.js.map +1 -1
  86. package/build-module/query-controls/index.js +1 -1
  87. package/build-module/query-controls/index.js.map +1 -1
  88. package/build-module/radio-control/index.js +36 -10
  89. package/build-module/radio-control/index.js.map +1 -1
  90. package/build-module/radio-control/types.js.map +1 -1
  91. package/build-module/select-control/index.js +20 -8
  92. package/build-module/select-control/index.js.map +1 -1
  93. package/build-module/select-control/types.js.map +1 -1
  94. package/build-module/text-control/index.js +1 -0
  95. package/build-module/text-control/index.js.map +1 -1
  96. package/build-module/toggle-control/index.js +26 -24
  97. package/build-module/toggle-control/index.js.map +1 -1
  98. package/build-module/toggle-group-control/toggle-group-control/component.js +6 -1
  99. package/build-module/toggle-group-control/toggle-group-control/component.js.map +1 -1
  100. package/build-module/toggle-group-control/toggle-group-control-option/component.js +6 -1
  101. package/build-module/toggle-group-control/toggle-group-control-option/component.js.map +1 -1
  102. package/build-module/toggle-group-control/toggle-group-control-option-icon/component.js +14 -14
  103. package/build-module/toggle-group-control/toggle-group-control-option-icon/component.js.map +1 -1
  104. package/build-module/tooltip/index.js +13 -2
  105. package/build-module/tooltip/index.js.map +1 -1
  106. package/build-module/tree-select/index.js +1 -2
  107. package/build-module/tree-select/index.js.map +1 -1
  108. package/build-module/utils/config-values.js +6 -0
  109. package/build-module/utils/config-values.js.map +1 -1
  110. package/build-style/style-rtl.css +60 -24
  111. package/build-style/style.css +60 -24
  112. package/build-types/autocomplete/autocompleter-ui.d.ts.map +1 -1
  113. package/build-types/border-control/styles.d.ts.map +1 -1
  114. package/build-types/button/stories/e2e/index.story.d.ts.map +1 -1
  115. package/build-types/color-palette/index.d.ts.map +1 -1
  116. package/build-types/color-palette/styles.d.ts +2 -2
  117. package/build-types/color-picker/styles.d.ts +3 -1
  118. package/build-types/color-picker/styles.d.ts.map +1 -1
  119. package/build-types/custom-select-control/index.d.ts +2 -2
  120. package/build-types/custom-select-control/index.d.ts.map +1 -1
  121. package/build-types/custom-select-control/stories/index.story.d.ts +3 -3
  122. package/build-types/custom-select-control/stories/index.story.d.ts.map +1 -1
  123. package/build-types/custom-select-control/types.d.ts +7 -7
  124. package/build-types/custom-select-control/types.d.ts.map +1 -1
  125. package/build-types/custom-select-control-v2/styles.d.ts +16 -28
  126. package/build-types/custom-select-control-v2/styles.d.ts.map +1 -1
  127. package/build-types/date-time/date/styles.d.ts +2 -2
  128. package/build-types/date-time/index.d.ts +1 -2
  129. package/build-types/date-time/index.d.ts.map +1 -1
  130. package/build-types/date-time/stories/time.story.d.ts +5 -0
  131. package/build-types/date-time/stories/time.story.d.ts.map +1 -1
  132. package/build-types/date-time/time/index.d.ts +3 -0
  133. package/build-types/date-time/time/index.d.ts.map +1 -1
  134. package/build-types/date-time/time/styles.d.ts.map +1 -1
  135. package/build-types/date-time/{time-input → time/time-input}/index.d.ts +1 -1
  136. package/build-types/date-time/time/time-input/index.d.ts.map +1 -0
  137. package/build-types/date-time/time/time-input/test/index.d.ts.map +1 -0
  138. package/build-types/dropdown-menu-v2/styles.d.ts +24 -42
  139. package/build-types/dropdown-menu-v2/styles.d.ts.map +1 -1
  140. package/build-types/form-toggle/index.d.ts +2 -5
  141. package/build-types/form-toggle/index.d.ts.map +1 -1
  142. package/build-types/guide/index.d.ts.map +1 -1
  143. package/build-types/heading/component.d.ts +1 -1
  144. package/build-types/heading/types.d.ts +1 -1
  145. package/build-types/heading/types.d.ts.map +1 -1
  146. package/build-types/modal/index.d.ts.map +1 -1
  147. package/build-types/navigation/styles/navigation-styles.d.ts +2 -2
  148. package/build-types/palette-edit/styles.d.ts +2 -2
  149. package/build-types/query-controls/index.d.ts.map +1 -1
  150. package/build-types/radio-control/index.d.ts.map +1 -1
  151. package/build-types/radio-control/stories/index.story.d.ts +1 -0
  152. package/build-types/radio-control/stories/index.story.d.ts.map +1 -1
  153. package/build-types/radio-control/test/index.d.ts +2 -0
  154. package/build-types/radio-control/test/index.d.ts.map +1 -0
  155. package/build-types/radio-control/types.d.ts +4 -0
  156. package/build-types/radio-control/types.d.ts.map +1 -1
  157. package/build-types/select-control/index.d.ts +4 -1
  158. package/build-types/select-control/index.d.ts.map +1 -1
  159. package/build-types/select-control/stories/index.story.d.ts +9 -3
  160. package/build-types/select-control/stories/index.story.d.ts.map +1 -1
  161. package/build-types/select-control/types.d.ts +27 -27
  162. package/build-types/select-control/types.d.ts.map +1 -1
  163. package/build-types/tabs/styles.d.ts +8 -14
  164. package/build-types/tabs/styles.d.ts.map +1 -1
  165. package/build-types/text-control/index.d.ts +1 -0
  166. package/build-types/text-control/index.d.ts.map +1 -1
  167. package/build-types/toggle-control/index.d.ts +3 -9
  168. package/build-types/toggle-control/index.d.ts.map +1 -1
  169. package/build-types/toggle-group-control/toggle-group-control/component.d.ts +6 -1
  170. package/build-types/toggle-group-control/toggle-group-control/component.d.ts.map +1 -1
  171. package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts +6 -1
  172. package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts.map +1 -1
  173. package/build-types/toggle-group-control/toggle-group-control-option-icon/component.d.ts +14 -14
  174. package/build-types/tooltip/index.d.ts.map +1 -1
  175. package/build-types/tooltip/test/utils/index.d.ts +1 -5
  176. package/build-types/tooltip/test/utils/index.d.ts.map +1 -1
  177. package/build-types/tree-select/index.d.ts +1 -1
  178. package/build-types/tree-select/index.d.ts.map +1 -1
  179. package/build-types/utils/config-values.d.ts +6 -0
  180. package/package.json +20 -20
  181. package/src/alignment-matrix-control/test/index.tsx +1 -3
  182. package/src/autocomplete/autocompleter-ui.tsx +2 -0
  183. package/src/autocomplete/style.scss +0 -6
  184. package/src/base-control/styles/base-control-styles.ts +1 -1
  185. package/src/border-control/styles.ts +0 -5
  186. package/src/button/stories/e2e/index.story.tsx +103 -21
  187. package/src/button/style.scss +49 -21
  188. package/src/button/test/index.tsx +18 -40
  189. package/src/circular-option-picker/test/index.tsx +1 -4
  190. package/src/color-palette/index.tsx +22 -20
  191. package/src/composite/legacy/test/index.tsx +2 -18
  192. package/src/custom-select-control/index.tsx +55 -25
  193. package/src/custom-select-control/test/index.tsx +47 -30
  194. package/src/custom-select-control/types.ts +7 -7
  195. package/src/custom-select-control-v2/styles.ts +7 -6
  196. package/src/custom-select-control-v2/test/index.tsx +15 -19
  197. package/src/date-time/index.ts +1 -2
  198. package/src/date-time/stories/time.story.tsx +17 -0
  199. package/src/date-time/time/index.tsx +46 -16
  200. package/src/date-time/time/styles.ts +1 -0
  201. package/src/date-time/{time-input → time/time-input}/index.tsx +9 -9
  202. package/src/dropdown-menu-v2/styles.ts +18 -17
  203. package/src/dropdown-menu-v2/test/index.tsx +1 -4
  204. package/src/font-size-picker/test/index.tsx +50 -43
  205. package/src/form-toggle/index.tsx +23 -21
  206. package/src/guide/index.tsx +2 -0
  207. package/src/heading/types.ts +1 -4
  208. package/src/modal/index.tsx +21 -20
  209. package/src/placeholder/style.scss +11 -2
  210. package/src/query-controls/index.tsx +5 -1
  211. package/src/radio-control/index.tsx +48 -7
  212. package/src/radio-control/stories/index.story.tsx +23 -0
  213. package/src/radio-control/style.scss +26 -2
  214. package/src/radio-control/test/index.tsx +274 -0
  215. package/src/radio-control/types.ts +4 -0
  216. package/src/select-control/README.md +8 -1
  217. package/src/select-control/index.tsx +33 -22
  218. package/src/select-control/test/select-control.tsx +148 -4
  219. package/src/select-control/types.ts +70 -65
  220. package/src/tab-panel/test/index.tsx +1 -8
  221. package/src/tabs/test/index.tsx +68 -84
  222. package/src/text-control/README.md +1 -0
  223. package/src/text-control/index.tsx +1 -0
  224. package/src/text-control/style.scss +5 -0
  225. package/src/toggle-control/README.md +9 -0
  226. package/src/toggle-control/index.tsx +25 -22
  227. package/src/toggle-control/style.scss +2 -1
  228. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +6 -6
  229. package/src/toggle-group-control/test/index.tsx +0 -6
  230. package/src/toggle-group-control/toggle-group-control/README.md +13 -1
  231. package/src/toggle-group-control/toggle-group-control/component.tsx +6 -1
  232. package/src/toggle-group-control/toggle-group-control-option/README.md +6 -1
  233. package/src/toggle-group-control/toggle-group-control-option/component.tsx +6 -1
  234. package/src/toggle-group-control/toggle-group-control-option-icon/README.md +1 -1
  235. package/src/toggle-group-control/toggle-group-control-option-icon/component.tsx +14 -14
  236. package/src/tooltip/index.tsx +15 -2
  237. package/src/tooltip/test/index.tsx +0 -5
  238. package/src/tooltip/test/utils/index.tsx +5 -5
  239. package/src/tree-select/index.tsx +1 -2
  240. package/src/utils/config-values.js +6 -0
  241. package/tsconfig.tsbuildinfo +1 -1
  242. package/build/date-time/time-input/index.js.map +0 -1
  243. package/build-module/date-time/time-input/index.js.map +0 -1
  244. package/build-types/date-time/stories/time-input.story.d.ts +0 -12
  245. package/build-types/date-time/stories/time-input.story.d.ts.map +0 -1
  246. package/build-types/date-time/time-input/index.d.ts.map +0 -1
  247. package/build-types/date-time/time-input/test/index.d.ts.map +0 -1
  248. package/src/date-time/stories/time-input.story.tsx +0 -36
  249. /package/build-types/date-time/{time-input → time/time-input}/test/index.d.ts +0 -0
  250. /package/src/date-time/{time-input → time/time-input}/test/index.tsx +0 -0
@@ -4,18 +4,25 @@
4
4
  import * as Ariakit from '@ariakit/react';
5
5
  import clsx from 'clsx';
6
6
 
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { useInstanceId } from '@wordpress/compose';
11
+ import { __, sprintf } from '@wordpress/i18n';
12
+
7
13
  /**
8
14
  * Internal dependencies
9
15
  */
10
16
  import _CustomSelect from '../custom-select-control-v2/custom-select';
11
17
  import CustomSelectItem from '../custom-select-control-v2/item';
12
18
  import * as Styled from '../custom-select-control-v2/styles';
13
- import type { CustomSelectProps } from './types';
19
+ import type { CustomSelectProps, CustomSelectOption } from './types';
20
+ import { VisuallyHidden } from '../visually-hidden';
14
21
 
15
- function useDeprecatedProps( {
22
+ function useDeprecatedProps< T extends CustomSelectOption >( {
16
23
  __experimentalShowSelectedHint,
17
24
  ...otherProps
18
- }: CustomSelectProps ) {
25
+ }: CustomSelectProps< T > ) {
19
26
  return {
20
27
  showSelectedHint: __experimentalShowSelectedHint,
21
28
  ...otherProps,
@@ -28,14 +35,25 @@ function useDeprecatedProps( {
28
35
  function applyOptionDeprecations( {
29
36
  __experimentalHint,
30
37
  ...rest
31
- }: CustomSelectProps[ 'options' ][ number ] ) {
38
+ }: CustomSelectOption ) {
32
39
  return {
33
40
  hint: __experimentalHint,
34
41
  ...rest,
35
42
  };
36
43
  }
37
44
 
38
- function CustomSelectControl( props: CustomSelectProps ) {
45
+ function getDescribedBy( currentValue: string, describedBy?: string ) {
46
+ if ( describedBy ) {
47
+ return describedBy;
48
+ }
49
+
50
+ // translators: %s: The selected option.
51
+ return sprintf( __( 'Currently selected: %s' ), currentValue );
52
+ }
53
+
54
+ function CustomSelectControl< T extends CustomSelectOption >(
55
+ props: CustomSelectProps< T >
56
+ ) {
39
57
  const {
40
58
  __next40pxDefaultSize = false,
41
59
  describedBy,
@@ -48,8 +66,13 @@ function CustomSelectControl( props: CustomSelectProps ) {
48
66
  ...restProps
49
67
  } = useDeprecatedProps( props );
50
68
 
69
+ const descriptionId = useInstanceId(
70
+ CustomSelectControl,
71
+ 'custom-select-control__description'
72
+ );
73
+
51
74
  // Forward props + store from v2 implementation
52
- const store = Ariakit.useSelectStore( {
75
+ const store = Ariakit.useSelectStore< string >( {
53
76
  async setValue( nextValue ) {
54
77
  const nextOption = options.find(
55
78
  ( item ) => item.name === nextValue
@@ -117,9 +140,9 @@ function CustomSelectControl( props: CustomSelectProps ) {
117
140
  );
118
141
  } );
119
142
 
120
- const renderSelectedValueHint = () => {
121
- const { value: currentValue } = store.getState();
143
+ const { value: currentValue } = store.getState();
122
144
 
145
+ const renderSelectedValueHint = () => {
123
146
  const selectedOptionHint = options
124
147
  ?.map( applyOptionDeprecations )
125
148
  ?.find( ( { name } ) => currentValue === name )?.hint;
@@ -153,23 +176,30 @@ function CustomSelectControl( props: CustomSelectProps ) {
153
176
  } )();
154
177
 
155
178
  return (
156
- <_CustomSelect
157
- aria-describedby={ describedBy }
158
- renderSelectedValue={
159
- showSelectedHint ? renderSelectedValueHint : undefined
160
- }
161
- size={ translatedSize }
162
- store={ store }
163
- className={ clsx(
164
- // Keeping the classname for legacy reasons
165
- 'components-custom-select-control',
166
- classNameProp
167
- ) }
168
- isLegacy
169
- { ...restProps }
170
- >
171
- { children }
172
- </_CustomSelect>
179
+ <>
180
+ <_CustomSelect
181
+ aria-describedby={ descriptionId }
182
+ renderSelectedValue={
183
+ showSelectedHint ? renderSelectedValueHint : undefined
184
+ }
185
+ size={ translatedSize }
186
+ store={ store }
187
+ className={ clsx(
188
+ // Keeping the classname for legacy reasons
189
+ 'components-custom-select-control',
190
+ classNameProp
191
+ ) }
192
+ isLegacy
193
+ { ...restProps }
194
+ >
195
+ { children }
196
+ </_CustomSelect>
197
+ <VisuallyHidden>
198
+ <span id={ descriptionId }>
199
+ { getDescribedBy( currentValue, describedBy ) }
200
+ </span>
201
+ </VisuallyHidden>
202
+ </>
173
203
  );
174
204
  }
175
205
 
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { render, screen } from '@testing-library/react';
4
+ import { screen } from '@testing-library/react';
5
5
  import { click, press, sleep, type, waitFor } from '@ariakit/test';
6
+ import { render } from '@ariakit/test/react';
6
7
 
7
8
  /**
8
9
  * WordPress dependencies
@@ -85,7 +86,7 @@ const ControlledCustomSelectControl = ( {
85
86
 
86
87
  it( 'Should apply external controlled updates', async () => {
87
88
  const mockOnChange = jest.fn();
88
- const { rerender } = render(
89
+ const { rerender } = await render(
89
90
  <UncontrolledCustomSelectControl
90
91
  { ...props }
91
92
  value={ props.options[ 0 ] }
@@ -101,7 +102,7 @@ it( 'Should apply external controlled updates', async () => {
101
102
 
102
103
  expect( mockOnChange ).not.toHaveBeenCalled();
103
104
 
104
- rerender(
105
+ await rerender(
105
106
  <UncontrolledCustomSelectControl
106
107
  { ...props }
107
108
  value={ props.options[ 1 ] }
@@ -124,7 +125,7 @@ describe.each( [
124
125
 
125
126
  it( 'Should select the first option when no explicit initial value is passed without firing onChange', async () => {
126
127
  const mockOnChange = jest.fn();
127
- render( <Component { ...props } onChange={ mockOnChange } /> );
128
+ await render( <Component { ...props } onChange={ mockOnChange } /> );
128
129
 
129
130
  expect(
130
131
  screen.getByRole( 'combobox', {
@@ -140,7 +141,7 @@ describe.each( [
140
141
 
141
142
  it( 'Should pick the initially selected option if the value prop is passed without firing onChange', async () => {
142
143
  const mockOnChange = jest.fn();
143
- render(
144
+ await render(
144
145
  <Component
145
146
  { ...props }
146
147
  onChange={ mockOnChange }
@@ -161,7 +162,7 @@ describe.each( [
161
162
  } );
162
163
 
163
164
  it( 'Should replace the initial selection when a new item is selected', async () => {
164
- render( <Component { ...props } /> );
165
+ await render( <Component { ...props } /> );
165
166
 
166
167
  const currentSelectedItem = screen.getByRole( 'combobox', {
167
168
  expanded: false,
@@ -189,13 +190,12 @@ describe.each( [
189
190
  } );
190
191
 
191
192
  it( 'Should keep current selection if dropdown is closed without changing selection', async () => {
192
- render( <Component { ...props } /> );
193
+ await render( <Component { ...props } /> );
193
194
 
194
195
  const currentSelectedItem = screen.getByRole( 'combobox', {
195
196
  expanded: false,
196
197
  } );
197
198
 
198
- await sleep();
199
199
  await press.Tab();
200
200
  await press.Enter();
201
201
  expect(
@@ -217,7 +217,7 @@ describe.each( [
217
217
  } );
218
218
 
219
219
  it( 'Should apply class only to options that have a className defined', async () => {
220
- render( <Component { ...props } /> );
220
+ await render( <Component { ...props } /> );
221
221
 
222
222
  await click(
223
223
  screen.getByRole( 'combobox', {
@@ -251,7 +251,7 @@ describe.each( [
251
251
  } );
252
252
 
253
253
  it( 'Should apply styles only to options that have styles defined', async () => {
254
- render( <Component { ...props } /> );
254
+ await render( <Component { ...props } /> );
255
255
 
256
256
  await click(
257
257
  screen.getByRole( 'combobox', {
@@ -285,7 +285,7 @@ describe.each( [
285
285
  } );
286
286
 
287
287
  it( 'does not show selected hint by default', async () => {
288
- render(
288
+ await render(
289
289
  <Component
290
290
  { ...props }
291
291
  label="Custom select"
@@ -306,7 +306,7 @@ describe.each( [
306
306
  } );
307
307
 
308
308
  it( 'shows selected hint when showSelectedHint is set', async () => {
309
- render(
309
+ await render(
310
310
  <Component
311
311
  { ...props }
312
312
  label="Custom select"
@@ -331,7 +331,7 @@ describe.each( [
331
331
  } );
332
332
 
333
333
  it( 'shows selected hint in list of options when added, regardless of showSelectedHint prop', async () => {
334
- render(
334
+ await render(
335
335
  <Component
336
336
  { ...props }
337
337
  label="Custom select"
@@ -355,7 +355,7 @@ describe.each( [
355
355
  it( 'Should return object onChange', async () => {
356
356
  const mockOnChange = jest.fn();
357
357
 
358
- render( <Component { ...props } onChange={ mockOnChange } /> );
358
+ await render( <Component { ...props } onChange={ mockOnChange } /> );
359
359
 
360
360
  await click(
361
361
  screen.getByRole( 'combobox', {
@@ -385,9 +385,8 @@ describe.each( [
385
385
  it( 'Should return selectedItem object when specified onChange', async () => {
386
386
  const mockOnChange = jest.fn();
387
387
 
388
- render( <Component { ...props } onChange={ mockOnChange } /> );
388
+ await render( <Component { ...props } onChange={ mockOnChange } /> );
389
389
 
390
- await sleep();
391
390
  await press.Tab();
392
391
  expect(
393
392
  screen.getByRole( 'combobox', {
@@ -412,7 +411,7 @@ describe.each( [
412
411
  it( "Should pass arbitrary props to onChange's selectedItem, but apply only style and className to DOM elements", async () => {
413
412
  const onChangeMock = jest.fn();
414
413
 
415
- render( <Component { ...props } onChange={ onChangeMock } /> );
414
+ await render( <Component { ...props } onChange={ onChangeMock } /> );
416
415
 
417
416
  const currentSelectedItem = screen.getByRole( 'combobox', {
418
417
  expanded: false,
@@ -449,8 +448,8 @@ describe.each( [
449
448
  );
450
449
  } );
451
450
 
452
- it( 'Should label the component correctly even when the label is not visible', () => {
453
- render( <Component { ...props } hideLabelFromVision /> );
451
+ it( 'Should label the component correctly even when the label is not visible', async () => {
452
+ await render( <Component { ...props } hideLabelFromVision /> );
454
453
 
455
454
  expect(
456
455
  screen.getByRole( 'combobox', {
@@ -463,7 +462,7 @@ describe.each( [
463
462
  it( 'Captures the keypress event and does not let it propagate', async () => {
464
463
  const onKeyDown = jest.fn();
465
464
 
466
- render(
465
+ await render(
467
466
  <div
468
467
  // This role="none" is required to prevent an eslint warning about accessibility.
469
468
  role="none"
@@ -487,13 +486,12 @@ describe.each( [
487
486
  } );
488
487
 
489
488
  it( 'Should be able to change selection using keyboard', async () => {
490
- render( <Component { ...props } /> );
489
+ await render( <Component { ...props } /> );
491
490
 
492
491
  const currentSelectedItem = screen.getByRole( 'combobox', {
493
492
  expanded: false,
494
493
  } );
495
494
 
496
- await sleep();
497
495
  await press.Tab();
498
496
  expect( currentSelectedItem ).toHaveFocus();
499
497
 
@@ -513,13 +511,12 @@ describe.each( [
513
511
  } );
514
512
 
515
513
  it( 'Should be able to type characters to select matching options', async () => {
516
- render( <Component { ...props } /> );
514
+ await render( <Component { ...props } /> );
517
515
 
518
516
  const currentSelectedItem = screen.getByRole( 'combobox', {
519
517
  expanded: false,
520
518
  } );
521
519
 
522
- await sleep();
523
520
  await press.Tab();
524
521
  await press.Enter();
525
522
  expect(
@@ -534,13 +531,12 @@ describe.each( [
534
531
  } );
535
532
 
536
533
  it( 'Can change selection with a focused input and closed dropdown if typed characters match an option', async () => {
537
- render( <Component { ...props } /> );
534
+ await render( <Component { ...props } /> );
538
535
 
539
536
  const currentSelectedItem = screen.getByRole( 'combobox', {
540
537
  expanded: false,
541
538
  } );
542
539
 
543
- await sleep();
544
540
  await press.Tab();
545
541
  expect( currentSelectedItem ).toHaveFocus();
546
542
  expect( currentSelectedItem ).toHaveTextContent(
@@ -564,13 +560,12 @@ describe.each( [
564
560
  } );
565
561
 
566
562
  it( 'Can change selection with a focused input and closed dropdown while pressing arrow keys', async () => {
567
- render( <Component { ...props } /> );
563
+ await render( <Component { ...props } /> );
568
564
 
569
565
  const currentSelectedItem = screen.getByRole( 'combobox', {
570
566
  expanded: false,
571
567
  } );
572
568
 
573
- await sleep();
574
569
  await press.Tab();
575
570
  expect( currentSelectedItem ).toHaveFocus();
576
571
  expect( currentSelectedItem ).toHaveTextContent(
@@ -591,7 +586,7 @@ describe.each( [
591
586
  } );
592
587
 
593
588
  it( 'Should have correct aria-selected value for selections', async () => {
594
- render( <Component { ...props } /> );
589
+ await render( <Component { ...props } /> );
595
590
 
596
591
  const currentSelectedItem = screen.getByRole( 'combobox', {
597
592
  expanded: false,
@@ -646,7 +641,7 @@ describe.each( [
646
641
  const onFocusMock = jest.fn();
647
642
  const onBlurMock = jest.fn();
648
643
 
649
- render(
644
+ await render(
650
645
  <>
651
646
  <Component
652
647
  { ...props }
@@ -670,5 +665,27 @@ describe.each( [
670
665
  expect( currentSelectedItem ).not.toHaveFocus();
671
666
  expect( onBlurMock ).toHaveBeenCalledTimes( 1 );
672
667
  } );
668
+
669
+ it( 'should render the describedBy text when specified', async () => {
670
+ const describedByText = 'My description.';
671
+
672
+ await render(
673
+ <Component { ...props } describedBy={ describedByText } />
674
+ );
675
+
676
+ expect(
677
+ screen.getByRole( 'combobox' )
678
+ ).toHaveAccessibleDescription( describedByText );
679
+ } );
680
+
681
+ it( 'should render the default ARIA description when describedBy is not specified', async () => {
682
+ await render( <Component { ...props } /> );
683
+
684
+ expect(
685
+ screen.getByRole( 'combobox' )
686
+ ).toHaveAccessibleDescription(
687
+ `Currently selected: ${ props.options[ 0 ].name }`
688
+ );
689
+ } );
673
690
  } );
674
691
  } );
@@ -6,7 +6,7 @@ import type { FocusEventHandler, MouseEventHandler } from 'react';
6
6
  /**
7
7
  * The object structure for the options array.
8
8
  */
9
- type Option = {
9
+ export type CustomSelectOption = {
10
10
  key: string;
11
11
  name: string;
12
12
  style?: React.CSSProperties;
@@ -24,15 +24,15 @@ type Option = {
24
24
  /**
25
25
  * The object returned from the onChange event.
26
26
  */
27
- type ChangeObject = {
27
+ type CustomSelectChangeObject< T extends CustomSelectOption > = {
28
28
  highlightedIndex?: number;
29
29
  inputValue?: string;
30
30
  isOpen?: boolean;
31
31
  type?: string;
32
- selectedItem: Option;
32
+ selectedItem: T;
33
33
  };
34
34
 
35
- export type CustomSelectProps = {
35
+ export type CustomSelectProps< T extends CustomSelectOption > = {
36
36
  /**
37
37
  * Optional classname for the component.
38
38
  */
@@ -55,7 +55,7 @@ export type CustomSelectProps = {
55
55
  * Function called with the control's internal state changes. The `selectedItem`
56
56
  * property contains the next selected item.
57
57
  */
58
- onChange?: ( newValue: ChangeObject ) => void;
58
+ onChange?: ( newValue: CustomSelectChangeObject< T > ) => void;
59
59
  /**
60
60
  * A handler for `blur` events on the trigger button.
61
61
  *
@@ -83,7 +83,7 @@ export type CustomSelectProps = {
83
83
  /**
84
84
  * The list of options that can be chosen from.
85
85
  */
86
- options: Array< Option >;
86
+ options: Array< T >;
87
87
  /**
88
88
  * The size of the control.
89
89
  *
@@ -93,7 +93,7 @@ export type CustomSelectProps = {
93
93
  /**
94
94
  * Can be used to externally control the value of the control.
95
95
  */
96
- value?: Option;
96
+ value?: T;
97
97
  /**
98
98
  * Use the `showSelectedHint` property instead.
99
99
  * @deprecated
@@ -132,12 +132,13 @@ export const SelectPopover = styled( Ariakit.SelectPopover )`
132
132
  min-width: min-content;
133
133
 
134
134
  /* Animation */
135
- animation-duration: ${ ANIMATION_PARAMS.DURATION };
136
- animation-timing-function: ${ ANIMATION_PARAMS.EASING };
137
- animation-name: ${ slideDownAndFade };
138
- will-change: transform, opacity;
139
- @media ( prefers-reduced-motion ) {
140
- animation-duration: 0s;
135
+ &[data-open] {
136
+ @media not ( prefers-reduced-motion ) {
137
+ animation-duration: ${ ANIMATION_PARAMS.DURATION };
138
+ animation-timing-function: ${ ANIMATION_PARAMS.EASING };
139
+ animation-name: ${ slideDownAndFade };
140
+ will-change: transform, opacity;
141
+ }
141
142
  }
142
143
 
143
144
  &[data-focus-visible] {
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { render, screen } from '@testing-library/react';
5
- import { click, press, sleep, type } from '@ariakit/test';
4
+ import { screen } from '@testing-library/react';
5
+ import { click, press, type } from '@ariakit/test';
6
+ import { render } from '@ariakit/test/react';
6
7
 
7
8
  /**
8
9
  * WordPress dependencies
@@ -66,7 +67,7 @@ describe.each( [
66
67
  const [ , Component ] = modeAndComponent;
67
68
 
68
69
  it( 'Should replace the initial selection when a new item is selected', async () => {
69
- render( <Component { ...defaultProps } /> );
70
+ await render( <Component { ...defaultProps } /> );
70
71
 
71
72
  const currentSelectedItem = screen.getByRole( 'combobox', {
72
73
  expanded: false,
@@ -94,13 +95,12 @@ describe.each( [
94
95
  } );
95
96
 
96
97
  it( 'Should keep current selection if dropdown is closed without changing selection', async () => {
97
- render( <Component { ...defaultProps } /> );
98
+ await render( <Component { ...defaultProps } /> );
98
99
 
99
100
  const currentSelectedItem = screen.getByRole( 'combobox', {
100
101
  expanded: false,
101
102
  } );
102
103
 
103
- await sleep();
104
104
  await press.Tab();
105
105
  await press.Enter();
106
106
  expect(
@@ -121,13 +121,12 @@ describe.each( [
121
121
 
122
122
  describe( 'Keyboard behavior and accessibility', () => {
123
123
  it( 'Should be able to change selection using keyboard', async () => {
124
- render( <Component { ...defaultProps } /> );
124
+ await render( <Component { ...defaultProps } /> );
125
125
 
126
126
  const currentSelectedItem = screen.getByRole( 'combobox', {
127
127
  expanded: false,
128
128
  } );
129
129
 
130
- await sleep();
131
130
  await press.Tab();
132
131
  expect( currentSelectedItem ).toHaveFocus();
133
132
 
@@ -145,13 +144,12 @@ describe.each( [
145
144
  } );
146
145
 
147
146
  it( 'Should be able to type characters to select matching options', async () => {
148
- render( <Component { ...defaultProps } /> );
147
+ await render( <Component { ...defaultProps } /> );
149
148
 
150
149
  const currentSelectedItem = screen.getByRole( 'combobox', {
151
150
  expanded: false,
152
151
  } );
153
152
 
154
- await sleep();
155
153
  await press.Tab();
156
154
  await press.Enter();
157
155
  expect(
@@ -166,13 +164,12 @@ describe.each( [
166
164
  } );
167
165
 
168
166
  it( 'Can change selection with a focused input and closed dropdown if typed characters match an option', async () => {
169
- render( <Component { ...defaultProps } /> );
167
+ await render( <Component { ...defaultProps } /> );
170
168
 
171
169
  const currentSelectedItem = screen.getByRole( 'combobox', {
172
170
  expanded: false,
173
171
  } );
174
172
 
175
- await sleep();
176
173
  await press.Tab();
177
174
  expect( currentSelectedItem ).toHaveFocus();
178
175
  expect( currentSelectedItem ).toHaveTextContent( 'violets' );
@@ -194,7 +191,7 @@ describe.each( [
194
191
  } );
195
192
 
196
193
  it( 'Should have correct aria-selected value for selections', async () => {
197
- render( <Component { ...defaultProps } /> );
194
+ await render( <Component { ...defaultProps } /> );
198
195
 
199
196
  const currentSelectedItem = screen.getByRole( 'combobox', {
200
197
  expanded: false,
@@ -244,7 +241,7 @@ describe.each( [
244
241
  'ultraviolet morning light',
245
242
  ];
246
243
 
247
- render(
244
+ await render(
248
245
  <Component
249
246
  defaultValue={ defaultValues }
250
247
  onChange={ onChangeMock }
@@ -326,7 +323,7 @@ describe.each( [
326
323
  'ultraviolet morning light',
327
324
  ];
328
325
 
329
- render(
326
+ await render(
330
327
  <Component defaultValue={ defaultValues } label="Multi-select">
331
328
  { defaultValues.map( ( item ) => (
332
329
  <UncontrolledCustomSelectControlV2.Item
@@ -382,7 +379,7 @@ describe.each( [
382
379
  return <img src={ `${ value }.jpg` } alt={ value as string } />;
383
380
  };
384
381
 
385
- render(
382
+ await render(
386
383
  <Component label="Rendered" renderSelectedValue={ renderValue }>
387
384
  <UncontrolledCustomSelectControlV2.Item value="april-29">
388
385
  { renderValue( 'april-29' ) }
@@ -418,13 +415,12 @@ describe.each( [
418
415
  } );
419
416
 
420
417
  it( 'Should open the select popover when focussing the trigger button and pressing arrow down', async () => {
421
- render( <Component { ...defaultProps } /> );
418
+ await render( <Component { ...defaultProps } /> );
422
419
 
423
420
  const currentSelectedItem = screen.getByRole( 'combobox', {
424
421
  expanded: false,
425
422
  } );
426
423
 
427
- await sleep();
428
424
  await press.Tab();
429
425
  expect( currentSelectedItem ).toHaveFocus();
430
426
  expect( currentSelectedItem ).toHaveTextContent( items[ 0 ].value );
@@ -437,8 +433,8 @@ describe.each( [
437
433
  ).toBeVisible();
438
434
  } );
439
435
 
440
- it( 'Should label the component correctly even when the label is not visible', () => {
441
- render( <Component { ...defaultProps } hideLabelFromVision /> );
436
+ it( 'Should label the component correctly even when the label is not visible', async () => {
437
+ await render( <Component { ...defaultProps } hideLabelFromVision /> );
442
438
 
443
439
  expect(
444
440
  screen.getByRole( 'combobox', {
@@ -3,8 +3,7 @@
3
3
  */
4
4
  import { default as DatePicker } from './date';
5
5
  import { default as TimePicker } from './time';
6
- import { default as TimeInput } from './time-input';
7
6
  import { default as DateTimePicker } from './date-time';
8
7
 
9
- export { DatePicker, TimePicker, TimeInput };
8
+ export { DatePicker, TimePicker };
10
9
  export default DateTimePicker;
@@ -16,6 +16,8 @@ import TimePicker from '../time';
16
16
  const meta: Meta< typeof TimePicker > = {
17
17
  title: 'Components/TimePicker',
18
18
  component: TimePicker,
19
+ // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170
20
+ subcomponents: { 'TimePicker.TimeInput': TimePicker.TimeInput },
19
21
  argTypes: {
20
22
  currentTime: { control: 'date' },
21
23
  onChange: { action: 'onChange', control: { type: null } },
@@ -49,3 +51,18 @@ const Template: StoryFn< typeof TimePicker > = ( {
49
51
  };
50
52
 
51
53
  export const Default: StoryFn< typeof TimePicker > = Template.bind( {} );
54
+
55
+ const TimeInputTemplate: StoryFn< typeof TimePicker.TimeInput > = ( args ) => {
56
+ return <TimePicker.TimeInput { ...args } />;
57
+ };
58
+
59
+ /**
60
+ * The time input can be used in isolation as `<TimePicker.TimeInput />`. In this case, the `value` will be passed
61
+ * as an object in 24-hour format (`{ hours: number, minutes: number }`).
62
+ */
63
+ export const TimeInput = TimeInputTemplate.bind( {} );
64
+ TimeInput.args = {
65
+ label: 'Time',
66
+ };
67
+ // Hide TimePicker controls because they don't apply to TimeInput
68
+ TimeInput.parameters = { controls: { include: [] } };