@wordpress/components 29.13.1-next.719a03cbe.0 → 30.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/CHANGELOG.md +34 -3
  2. package/build/autocomplete/index.js +4 -0
  3. package/build/autocomplete/index.js.map +1 -1
  4. package/build/calendar/date-calendar/index.js +13 -4
  5. package/build/calendar/date-calendar/index.js.map +1 -1
  6. package/build/calendar/date-range-calendar/index.js +8 -4
  7. package/build/calendar/date-range-calendar/index.js.map +1 -1
  8. package/build/calendar/types.js.map +1 -1
  9. package/build/color-picker/styles.js +7 -7
  10. package/build/color-picker/styles.js.map +1 -1
  11. package/build/icon/index.js +2 -0
  12. package/build/icon/index.js.map +1 -1
  13. package/build/index.js +0 -19
  14. package/build/index.js.map +1 -1
  15. package/build/palette-edit/index.js +1 -1
  16. package/build/palette-edit/index.js.map +1 -1
  17. package/build/private-apis.js +9 -1
  18. package/build/private-apis.js.map +1 -1
  19. package/build/range-control/index.js +1 -1
  20. package/build/range-control/index.js.map +1 -1
  21. package/build/range-control/styles/range-control-styles.js +35 -35
  22. package/build/range-control/styles/range-control-styles.js.map +1 -1
  23. package/build/range-control/tooltip.js +15 -15
  24. package/build/range-control/tooltip.js.map +1 -1
  25. package/build/range-control/types.js.map +1 -1
  26. package/build/select-control/index.js +1 -1
  27. package/build/select-control/index.js.map +1 -1
  28. package/build/textarea-control/index.js +7 -1
  29. package/build/textarea-control/index.js.map +1 -1
  30. package/build/toggle-group-control/toggle-group-control/as-button-group.js.map +1 -1
  31. package/build/utils/hooks/use-controlled-value.js +8 -4
  32. package/build/utils/hooks/use-controlled-value.js.map +1 -1
  33. package/build/validated-form-controls/components/checkbox-control.js +52 -0
  34. package/build/validated-form-controls/components/checkbox-control.js.map +1 -0
  35. package/build/validated-form-controls/components/combobox-control.js +64 -0
  36. package/build/validated-form-controls/components/combobox-control.js.map +1 -0
  37. package/build/validated-form-controls/components/custom-select-control.js +71 -0
  38. package/build/validated-form-controls/components/custom-select-control.js.map +1 -0
  39. package/build/validated-form-controls/components/index.js +138 -0
  40. package/build/validated-form-controls/components/index.js.map +1 -0
  41. package/build/validated-form-controls/components/input-control.js +50 -0
  42. package/build/validated-form-controls/components/input-control.js.map +1 -0
  43. package/build/validated-form-controls/components/number-control.js +53 -0
  44. package/build/validated-form-controls/components/number-control.js.map +1 -0
  45. package/build/validated-form-controls/components/radio-control.js +51 -0
  46. package/build/validated-form-controls/components/radio-control.js.map +1 -0
  47. package/build/validated-form-controls/components/range-control.js +51 -0
  48. package/build/validated-form-controls/components/range-control.js.map +1 -0
  49. package/build/validated-form-controls/components/select-control.js +53 -0
  50. package/build/validated-form-controls/components/select-control.js.map +1 -0
  51. package/build/validated-form-controls/components/text-control.js +51 -0
  52. package/build/validated-form-controls/components/text-control.js.map +1 -0
  53. package/build/validated-form-controls/components/textarea-control.js +50 -0
  54. package/build/validated-form-controls/components/textarea-control.js.map +1 -0
  55. package/build/validated-form-controls/components/toggle-control.js +60 -0
  56. package/build/validated-form-controls/components/toggle-control.js.map +1 -0
  57. package/build/validated-form-controls/components/toggle-group-control.js +69 -0
  58. package/build/validated-form-controls/components/toggle-group-control.js.map +1 -0
  59. package/build/validated-form-controls/components/types.js +6 -0
  60. package/build/validated-form-controls/components/types.js.map +1 -0
  61. package/build/validated-form-controls/control-with-error.js +137 -0
  62. package/build/validated-form-controls/control-with-error.js.map +1 -0
  63. package/build/validated-form-controls/index.js +28 -0
  64. package/build/validated-form-controls/index.js.map +1 -0
  65. package/build-module/autocomplete/index.js +4 -0
  66. package/build-module/autocomplete/index.js.map +1 -1
  67. package/build-module/calendar/date-calendar/index.js +11 -3
  68. package/build-module/calendar/date-calendar/index.js.map +1 -1
  69. package/build-module/calendar/date-range-calendar/index.js +8 -4
  70. package/build-module/calendar/date-range-calendar/index.js.map +1 -1
  71. package/build-module/calendar/types.js.map +1 -1
  72. package/build-module/color-picker/styles.js +7 -7
  73. package/build-module/color-picker/styles.js.map +1 -1
  74. package/build-module/icon/index.js +2 -0
  75. package/build-module/icon/index.js.map +1 -1
  76. package/build-module/index.js +0 -1
  77. package/build-module/index.js.map +1 -1
  78. package/build-module/palette-edit/index.js +2 -2
  79. package/build-module/palette-edit/index.js.map +1 -1
  80. package/build-module/private-apis.js +9 -1
  81. package/build-module/private-apis.js.map +1 -1
  82. package/build-module/range-control/index.js +1 -1
  83. package/build-module/range-control/index.js.map +1 -1
  84. package/build-module/range-control/styles/range-control-styles.js +35 -35
  85. package/build-module/range-control/styles/range-control-styles.js.map +1 -1
  86. package/build-module/range-control/tooltip.js +15 -15
  87. package/build-module/range-control/tooltip.js.map +1 -1
  88. package/build-module/range-control/types.js.map +1 -1
  89. package/build-module/select-control/index.js +1 -1
  90. package/build-module/select-control/index.js.map +1 -1
  91. package/build-module/textarea-control/index.js +7 -1
  92. package/build-module/textarea-control/index.js.map +1 -1
  93. package/build-module/toggle-group-control/toggle-group-control/as-button-group.js.map +1 -1
  94. package/build-module/utils/hooks/use-controlled-value.js +9 -5
  95. package/build-module/utils/hooks/use-controlled-value.js.map +1 -1
  96. package/build-module/validated-form-controls/components/checkbox-control.js +44 -0
  97. package/build-module/validated-form-controls/components/checkbox-control.js.map +1 -0
  98. package/build-module/validated-form-controls/components/combobox-control.js +56 -0
  99. package/build-module/validated-form-controls/components/combobox-control.js.map +1 -0
  100. package/build-module/validated-form-controls/components/custom-select-control.js +63 -0
  101. package/build-module/validated-form-controls/components/custom-select-control.js.map +1 -0
  102. package/build-module/validated-form-controls/components/index.js +13 -0
  103. package/build-module/validated-form-controls/components/index.js.map +1 -0
  104. package/build-module/validated-form-controls/components/input-control.js +42 -0
  105. package/build-module/validated-form-controls/components/input-control.js.map +1 -0
  106. package/build-module/validated-form-controls/components/number-control.js +45 -0
  107. package/build-module/validated-form-controls/components/number-control.js.map +1 -0
  108. package/build-module/validated-form-controls/components/radio-control.js +43 -0
  109. package/build-module/validated-form-controls/components/radio-control.js.map +1 -0
  110. package/build-module/validated-form-controls/components/range-control.js +43 -0
  111. package/build-module/validated-form-controls/components/range-control.js.map +1 -0
  112. package/build-module/validated-form-controls/components/select-control.js +45 -0
  113. package/build-module/validated-form-controls/components/select-control.js.map +1 -0
  114. package/build-module/validated-form-controls/components/text-control.js +43 -0
  115. package/build-module/validated-form-controls/components/text-control.js.map +1 -0
  116. package/build-module/validated-form-controls/components/textarea-control.js +42 -0
  117. package/build-module/validated-form-controls/components/textarea-control.js.map +1 -0
  118. package/build-module/validated-form-controls/components/toggle-control.js +52 -0
  119. package/build-module/validated-form-controls/components/toggle-control.js.map +1 -0
  120. package/build-module/validated-form-controls/components/toggle-group-control.js +62 -0
  121. package/build-module/validated-form-controls/components/toggle-group-control.js.map +1 -0
  122. package/build-module/validated-form-controls/components/types.js +2 -0
  123. package/build-module/validated-form-controls/components/types.js.map +1 -0
  124. package/build-module/validated-form-controls/control-with-error.js +129 -0
  125. package/build-module/validated-form-controls/control-with-error.js.map +1 -0
  126. package/build-module/validated-form-controls/index.js +3 -0
  127. package/build-module/validated-form-controls/index.js.map +1 -0
  128. package/build-style/style-rtl.css +81 -71
  129. package/build-style/style.css +81 -71
  130. package/build-types/autocomplete/index.d.ts.map +1 -1
  131. package/build-types/box-control/utils.d.ts +7 -7
  132. package/build-types/calendar/date-calendar/index.d.ts.map +1 -1
  133. package/build-types/calendar/date-range-calendar/index.d.ts.map +1 -1
  134. package/build-types/calendar/types.d.ts +2 -2
  135. package/build-types/calendar/types.d.ts.map +1 -1
  136. package/build-types/calendar/utils/use-localization-props.d.ts +3 -3
  137. package/build-types/color-picker/styles.d.ts.map +1 -1
  138. package/build-types/custom-gradient-picker/constants.d.ts +2 -2
  139. package/build-types/dimension-control/sizes.d.ts +5 -5
  140. package/build-types/font-size-picker/constants.d.ts +2 -2
  141. package/build-types/font-size-picker/constants.d.ts.map +1 -1
  142. package/build-types/icon/index.d.ts.map +1 -1
  143. package/build-types/index.d.ts +0 -1
  144. package/build-types/index.d.ts.map +1 -1
  145. package/build-types/private-apis.d.ts.map +1 -1
  146. package/build-types/range-control/types.d.ts +2 -2
  147. package/build-types/range-control/types.d.ts.map +1 -1
  148. package/build-types/select-control/stories/index.story.d.ts.map +1 -1
  149. package/build-types/text-control/stories/index.story.d.ts.map +1 -1
  150. package/build-types/textarea-control/index.d.ts.map +1 -1
  151. package/build-types/toggle-group-control/toggle-group-control/as-button-group.d.ts.map +1 -1
  152. package/build-types/utils/hooks/use-controlled-value.d.ts +2 -2
  153. package/build-types/utils/hooks/use-controlled-value.d.ts.map +1 -1
  154. package/build-types/validated-form-controls/components/checkbox-control.d.ts +9 -0
  155. package/build-types/validated-form-controls/components/checkbox-control.d.ts.map +1 -0
  156. package/build-types/validated-form-controls/components/combobox-control.d.ts +21 -0
  157. package/build-types/validated-form-controls/components/combobox-control.d.ts.map +1 -0
  158. package/build-types/validated-form-controls/components/custom-select-control.d.ts +4 -0
  159. package/build-types/validated-form-controls/components/custom-select-control.d.ts.map +1 -0
  160. package/build-types/validated-form-controls/components/index.d.ts +13 -0
  161. package/build-types/validated-form-controls/components/index.d.ts.map +1 -0
  162. package/build-types/validated-form-controls/components/input-control.d.ts +4 -0
  163. package/build-types/validated-form-controls/components/input-control.d.ts.map +1 -0
  164. package/build-types/validated-form-controls/components/number-control.d.ts +17 -0
  165. package/build-types/validated-form-controls/components/number-control.d.ts.map +1 -0
  166. package/build-types/validated-form-controls/components/radio-control.d.ts +11 -0
  167. package/build-types/validated-form-controls/components/radio-control.d.ts.map +1 -0
  168. package/build-types/validated-form-controls/components/range-control.d.ts +36 -0
  169. package/build-types/validated-form-controls/components/range-control.d.ts.map +1 -0
  170. package/build-types/validated-form-controls/components/select-control.d.ts +9 -0
  171. package/build-types/validated-form-controls/components/select-control.d.ts.map +1 -0
  172. package/build-types/validated-form-controls/components/stories/checkbox-control.story.d.ts +12 -0
  173. package/build-types/validated-form-controls/components/stories/checkbox-control.story.d.ts.map +1 -0
  174. package/build-types/validated-form-controls/components/stories/combobox-control.story.d.ts +12 -0
  175. package/build-types/validated-form-controls/components/stories/combobox-control.story.d.ts.map +1 -0
  176. package/build-types/validated-form-controls/components/stories/custom-select-control.story.d.ts +12 -0
  177. package/build-types/validated-form-controls/components/stories/custom-select-control.story.d.ts.map +1 -0
  178. package/build-types/validated-form-controls/components/stories/input-control.story.d.ts +18 -0
  179. package/build-types/validated-form-controls/components/stories/input-control.story.d.ts.map +1 -0
  180. package/build-types/validated-form-controls/components/stories/number-control.story.d.ts +12 -0
  181. package/build-types/validated-form-controls/components/stories/number-control.story.d.ts.map +1 -0
  182. package/build-types/validated-form-controls/components/stories/overview.story.d.ts +19 -0
  183. package/build-types/validated-form-controls/components/stories/overview.story.d.ts.map +1 -0
  184. package/build-types/validated-form-controls/components/stories/radio-control.story.d.ts +12 -0
  185. package/build-types/validated-form-controls/components/stories/radio-control.story.d.ts.map +1 -0
  186. package/build-types/validated-form-controls/components/stories/range-control.story.d.ts +9 -0
  187. package/build-types/validated-form-controls/components/stories/range-control.story.d.ts.map +1 -0
  188. package/build-types/validated-form-controls/components/stories/select-control.story.d.ts +12 -0
  189. package/build-types/validated-form-controls/components/stories/select-control.story.d.ts.map +1 -0
  190. package/build-types/validated-form-controls/components/stories/story-utils.d.ts +9 -0
  191. package/build-types/validated-form-controls/components/stories/story-utils.d.ts.map +1 -0
  192. package/build-types/validated-form-controls/components/stories/text-control.story.d.ts +9 -0
  193. package/build-types/validated-form-controls/components/stories/text-control.story.d.ts.map +1 -0
  194. package/build-types/validated-form-controls/components/stories/textarea-control.story.d.ts +9 -0
  195. package/build-types/validated-form-controls/components/stories/textarea-control.story.d.ts.map +1 -0
  196. package/build-types/validated-form-controls/components/stories/toggle-control.story.d.ts +9 -0
  197. package/build-types/validated-form-controls/components/stories/toggle-control.story.d.ts.map +1 -0
  198. package/build-types/validated-form-controls/components/stories/toggle-group-control.story.d.ts +9 -0
  199. package/build-types/validated-form-controls/components/stories/toggle-group-control.story.d.ts.map +1 -0
  200. package/build-types/validated-form-controls/components/text-control.d.ts +8 -0
  201. package/build-types/validated-form-controls/components/text-control.d.ts.map +1 -0
  202. package/build-types/validated-form-controls/components/textarea-control.d.ts +7 -0
  203. package/build-types/validated-form-controls/components/textarea-control.d.ts.map +1 -0
  204. package/build-types/validated-form-controls/components/toggle-control.d.ts +7 -0
  205. package/build-types/validated-form-controls/components/toggle-control.d.ts.map +1 -0
  206. package/build-types/validated-form-controls/components/toggle-group-control.d.ts +15 -0
  207. package/build-types/validated-form-controls/components/toggle-group-control.d.ts.map +1 -0
  208. package/build-types/validated-form-controls/components/types.d.ts +27 -0
  209. package/build-types/validated-form-controls/components/types.d.ts.map +1 -0
  210. package/build-types/validated-form-controls/control-with-error.d.ts +36 -0
  211. package/build-types/validated-form-controls/control-with-error.d.ts.map +1 -0
  212. package/build-types/validated-form-controls/index.d.ts +3 -0
  213. package/build-types/validated-form-controls/index.d.ts.map +1 -0
  214. package/package.json +19 -19
  215. package/src/autocomplete/index.tsx +4 -0
  216. package/src/calendar/date-calendar/README.md +57 -46
  217. package/src/calendar/date-calendar/index.tsx +22 -8
  218. package/src/calendar/date-range-calendar/README.md +63 -52
  219. package/src/calendar/date-range-calendar/index.tsx +23 -11
  220. package/src/calendar/types.ts +2 -2
  221. package/src/color-picker/styles.ts +10 -0
  222. package/src/dimension-control/test/__snapshots__/index.test.js.snap +8 -8
  223. package/src/icon/index.tsx +2 -0
  224. package/src/index.ts +0 -1
  225. package/src/modal/style.scss +2 -2
  226. package/src/palette-edit/index.tsx +3 -3
  227. package/src/private-apis.ts +13 -0
  228. package/src/range-control/index.tsx +1 -1
  229. package/src/range-control/styles/range-control-styles.ts +3 -3
  230. package/src/range-control/tooltip.tsx +13 -13
  231. package/src/range-control/types.ts +2 -2
  232. package/src/select-control/index.tsx +1 -1
  233. package/src/style.scss +2 -2
  234. package/src/text-control/stories/index.story.tsx +1 -0
  235. package/src/text-control/style.scss +6 -1
  236. package/src/textarea-control/index.tsx +8 -1
  237. package/src/toggle-group-control/toggle-group-control/as-button-group.tsx +3 -1
  238. package/src/utils/hooks/use-controlled-value.ts +16 -8
  239. package/src/utils/theme-variables.scss +3 -0
  240. package/src/validated-form-controls/components/checkbox-control.tsx +64 -0
  241. package/src/validated-form-controls/components/combobox-control.tsx +77 -0
  242. package/src/validated-form-controls/components/custom-select-control.tsx +86 -0
  243. package/src/validated-form-controls/components/index.ts +12 -0
  244. package/src/validated-form-controls/components/input-control.tsx +59 -0
  245. package/src/validated-form-controls/components/number-control.tsx +61 -0
  246. package/src/validated-form-controls/components/radio-control.tsx +60 -0
  247. package/src/validated-form-controls/components/range-control.tsx +60 -0
  248. package/src/validated-form-controls/components/select-control.tsx +75 -0
  249. package/src/validated-form-controls/components/stories/checkbox-control.story.tsx +57 -0
  250. package/src/validated-form-controls/components/stories/combobox-control.story.tsx +64 -0
  251. package/src/validated-form-controls/components/stories/custom-select-control.story.tsx +64 -0
  252. package/src/validated-form-controls/components/stories/input-control.story.tsx +132 -0
  253. package/src/validated-form-controls/components/stories/number-control.story.tsx +62 -0
  254. package/src/validated-form-controls/components/stories/overview.mdx +52 -0
  255. package/src/validated-form-controls/components/stories/overview.story.tsx +100 -0
  256. package/src/validated-form-controls/components/stories/radio-control.story.tsx +64 -0
  257. package/src/validated-form-controls/components/stories/range-control.story.tsx +60 -0
  258. package/src/validated-form-controls/components/stories/select-control.story.tsx +60 -0
  259. package/src/validated-form-controls/components/stories/story-utils.tsx +46 -0
  260. package/src/validated-form-controls/components/stories/text-control.story.tsx +55 -0
  261. package/src/validated-form-controls/components/stories/textarea-control.story.tsx +52 -0
  262. package/src/validated-form-controls/components/stories/toggle-control.story.tsx +55 -0
  263. package/src/validated-form-controls/components/stories/toggle-group-control.story.tsx +66 -0
  264. package/src/validated-form-controls/components/text-control.tsx +60 -0
  265. package/src/validated-form-controls/components/textarea-control.tsx +59 -0
  266. package/src/validated-form-controls/components/toggle-control.tsx +69 -0
  267. package/src/validated-form-controls/components/toggle-group-control.tsx +82 -0
  268. package/src/validated-form-controls/components/types.ts +28 -0
  269. package/src/validated-form-controls/control-with-error.tsx +198 -0
  270. package/src/validated-form-controls/index.ts +2 -0
  271. package/src/validated-form-controls/style.scss +75 -0
  272. package/tsconfig.tsbuildinfo +1 -1
  273. package/build/calendar/utils/use-controlled-value.js +0 -58
  274. package/build/calendar/utils/use-controlled-value.js.map +0 -1
  275. package/build-module/calendar/utils/use-controlled-value.js +0 -51
  276. package/build-module/calendar/utils/use-controlled-value.js.map +0 -1
  277. package/build-types/calendar/utils/use-controlled-value.d.ts +0 -27
  278. package/build-types/calendar/utils/use-controlled-value.d.ts.map +0 -1
  279. package/src/calendar/utils/use-controlled-value.ts +0 -61
  280. package/src/dimension-control/style.scss +0 -22
@@ -0,0 +1,60 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState } from '@wordpress/element';
5
+
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import type { StoryObj, Meta } from '@storybook/react';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { ValidatedSelectControl } from '../select-control';
15
+ import { formDecorator } from './story-utils';
16
+
17
+ const meta: Meta< typeof ValidatedSelectControl > = {
18
+ title: 'Components (Experimental)/Validated Form Controls/ValidatedSelectControl',
19
+ component: ValidatedSelectControl,
20
+ tags: [ 'status-private' ],
21
+ decorators: formDecorator,
22
+ args: { onChange: () => {} },
23
+ argTypes: {
24
+ value: { control: false },
25
+ },
26
+ };
27
+ export default meta;
28
+
29
+ export const Default: StoryObj< typeof ValidatedSelectControl > = {
30
+ render: function Template( { onChange, ...args } ) {
31
+ const [ value, setValue ] = useState( '' );
32
+
33
+ return (
34
+ <ValidatedSelectControl
35
+ { ...args }
36
+ value={ value }
37
+ onChange={ ( newValue ) => {
38
+ setValue( newValue );
39
+ onChange?.( newValue );
40
+ } }
41
+ />
42
+ );
43
+ },
44
+ };
45
+ Default.args = {
46
+ required: true,
47
+ label: 'Select',
48
+ help: 'Selecting option 1 will trigger an error.',
49
+ options: [
50
+ { value: '', label: 'Select an option' },
51
+ { value: '1', label: 'Option 1 (not allowed)' },
52
+ { value: '2', label: 'Option 2' },
53
+ ],
54
+ customValidator: ( value ) => {
55
+ if ( value === '1' ) {
56
+ return 'Option 1 is not allowed.';
57
+ }
58
+ return undefined;
59
+ },
60
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+
5
+ /**
6
+ * External dependencies
7
+ */
8
+ import type { Meta } from '@storybook/react';
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ import Button from '../../../button';
14
+
15
+ export const formDecorator: Meta[ 'decorators' ] = ( Story ) => (
16
+ <form
17
+ style={ {
18
+ fontFamily: 'sans-serif',
19
+ display: 'flex',
20
+ flexDirection: 'column',
21
+ alignItems: 'flex-start',
22
+ gap: 16,
23
+ } }
24
+ onSubmit={ ( e ) => {
25
+ e.preventDefault();
26
+ // eslint-disable-next-line no-alert
27
+ alert( 'Form submitted!' );
28
+ } }
29
+ >
30
+ <div
31
+ style={ {
32
+ display: 'flex',
33
+ flexDirection: 'column',
34
+ gap: 16,
35
+ alignItems: 'stretch',
36
+ width: 300,
37
+ } }
38
+ >
39
+ <Story />
40
+ </div>
41
+
42
+ <Button variant="primary" type="submit" __next40pxDefaultSize>
43
+ Submit
44
+ </Button>
45
+ </form>
46
+ );
@@ -0,0 +1,55 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState } from '@wordpress/element';
5
+
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import type { StoryObj, Meta } from '@storybook/react';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { formDecorator } from './story-utils';
15
+ import { ValidatedTextControl } from '../text-control';
16
+
17
+ const meta: Meta< typeof ValidatedTextControl > = {
18
+ title: 'Components (Experimental)/Validated Form Controls/ValidatedTextControl',
19
+ component: ValidatedTextControl,
20
+ tags: [ 'status-private' ],
21
+ decorators: formDecorator,
22
+ args: { onChange: () => {} },
23
+ argTypes: {
24
+ value: { control: false },
25
+ },
26
+ };
27
+ export default meta;
28
+
29
+ export const Default: StoryObj< typeof ValidatedTextControl > = {
30
+ render: function Template( { onChange, ...args } ) {
31
+ const [ value, setValue ] = useState( '' );
32
+
33
+ return (
34
+ <ValidatedTextControl
35
+ { ...args }
36
+ value={ value }
37
+ onChange={ ( newValue ) => {
38
+ setValue( newValue );
39
+ onChange?.( newValue );
40
+ } }
41
+ />
42
+ );
43
+ },
44
+ };
45
+ Default.args = {
46
+ required: true,
47
+ label: 'Text',
48
+ help: "The word 'error' will trigger an error.",
49
+ customValidator: ( value ) => {
50
+ if ( value?.toString().toLowerCase() === 'error' ) {
51
+ return 'The word "error" is not allowed.';
52
+ }
53
+ return undefined;
54
+ },
55
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState } from '@wordpress/element';
5
+
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import type { StoryObj, Meta } from '@storybook/react';
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ import { formDecorator } from './story-utils';
14
+ import { ValidatedTextareaControl } from '../textarea-control';
15
+
16
+ const meta: Meta< typeof ValidatedTextareaControl > = {
17
+ title: 'Components (Experimental)/Validated Form Controls/ValidatedTextareaControl',
18
+ component: ValidatedTextareaControl,
19
+ tags: [ 'status-private' ],
20
+ decorators: formDecorator,
21
+ args: { onChange: () => {} },
22
+ argTypes: { value: { control: false } },
23
+ };
24
+ export default meta;
25
+
26
+ export const Default: StoryObj< typeof ValidatedTextareaControl > = {
27
+ render: function Template( { onChange, ...args } ) {
28
+ const [ value, setValue ] = useState( '' );
29
+
30
+ return (
31
+ <ValidatedTextareaControl
32
+ { ...args }
33
+ onChange={ ( newValue ) => {
34
+ setValue( newValue );
35
+ onChange?.( newValue );
36
+ } }
37
+ value={ value }
38
+ />
39
+ );
40
+ },
41
+ };
42
+ Default.args = {
43
+ required: true,
44
+ label: 'Textarea',
45
+ help: 'The word "error" will trigger an error.',
46
+ customValidator: ( value ) => {
47
+ if ( value?.toLowerCase() === 'error' ) {
48
+ return 'The word "error" is not allowed.';
49
+ }
50
+ return undefined;
51
+ },
52
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState } from '@wordpress/element';
5
+
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import type { StoryObj, Meta } from '@storybook/react';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { formDecorator } from './story-utils';
15
+ import { ValidatedToggleControl } from '../toggle-control';
16
+
17
+ const meta: Meta< typeof ValidatedToggleControl > = {
18
+ title: 'Components (Experimental)/Validated Form Controls/ValidatedToggleControl',
19
+ component: ValidatedToggleControl,
20
+ tags: [ 'status-private' ],
21
+ decorators: formDecorator,
22
+ args: { onChange: () => {} },
23
+ argTypes: {
24
+ checked: { control: false },
25
+ },
26
+ };
27
+ export default meta;
28
+
29
+ export const Default: StoryObj< typeof ValidatedToggleControl > = {
30
+ render: function Template( { onChange, ...args } ) {
31
+ const [ checked, setChecked ] = useState( false );
32
+
33
+ return (
34
+ <ValidatedToggleControl
35
+ { ...args }
36
+ checked={ checked }
37
+ onChange={ ( value ) => {
38
+ setChecked( value );
39
+ onChange?.( value );
40
+ } }
41
+ />
42
+ );
43
+ },
44
+ };
45
+ Default.args = {
46
+ required: true,
47
+ label: 'Toggle',
48
+ help: 'This toggle may neither be enabled nor disabled.',
49
+ customValidator: ( value ) => {
50
+ if ( value ) {
51
+ return 'This toggle may not be enabled.';
52
+ }
53
+ return undefined;
54
+ },
55
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useState } from '@wordpress/element';
5
+
6
+ /**
7
+ * External dependencies
8
+ */
9
+ import type { StoryObj, Meta } from '@storybook/react';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { formDecorator } from './story-utils';
15
+ import { ValidatedToggleGroupControl } from '../toggle-group-control';
16
+ import { ToggleGroupControlOption } from '../../../toggle-group-control';
17
+
18
+ const meta: Meta< typeof ValidatedToggleGroupControl > = {
19
+ title: 'Components (Experimental)/Validated Form Controls/ValidatedToggleGroupControl',
20
+ component: ValidatedToggleGroupControl,
21
+ tags: [ 'status-private' ],
22
+ decorators: formDecorator,
23
+ args: { onChange: () => {} },
24
+ argTypes: {
25
+ value: { control: false },
26
+ },
27
+ };
28
+ export default meta;
29
+
30
+ export const Default: StoryObj< typeof ValidatedToggleGroupControl > = {
31
+ render: function Template( { onChange, ...args } ) {
32
+ const [ value, setValue ] =
33
+ useState<
34
+ React.ComponentProps<
35
+ typeof ValidatedToggleGroupControl
36
+ >[ 'value' ]
37
+ >( '1' );
38
+
39
+ return (
40
+ <ValidatedToggleGroupControl
41
+ { ...args }
42
+ value={ value }
43
+ onChange={ ( newValue ) => {
44
+ setValue( newValue );
45
+ onChange?.( newValue );
46
+ } }
47
+ />
48
+ );
49
+ },
50
+ };
51
+ Default.args = {
52
+ required: true,
53
+ label: 'Toggle Group',
54
+ isBlock: true,
55
+ children: [
56
+ <ToggleGroupControlOption value="1" key="1" label="Option 1" />,
57
+ <ToggleGroupControlOption value="2" key="2" label="Option 2" />,
58
+ ],
59
+ help: 'Selecting option 2 will trigger an error.',
60
+ customValidator: ( value ) => {
61
+ if ( value === '2' ) {
62
+ return 'Option 2 is not allowed.';
63
+ }
64
+ return undefined;
65
+ },
66
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { useMergeRefs } from '@wordpress/compose';
5
+ import { forwardRef, useRef } from '@wordpress/element';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { ControlWithError } from '../control-with-error';
11
+ import type { ValidatedControlProps } from './types';
12
+ import TextControl from '../../text-control';
13
+ import type { TextControlProps } from '../../text-control/types';
14
+
15
+ type Value = TextControlProps[ 'value' ];
16
+
17
+ const UnforwardedValidatedTextControl = (
18
+ {
19
+ required,
20
+ customValidator,
21
+ onChange,
22
+ markWhenOptional,
23
+ ...restProps
24
+ }: Omit<
25
+ React.ComponentProps< typeof TextControl >,
26
+ '__next40pxDefaultSize' | '__nextHasNoMarginBottom'
27
+ > &
28
+ ValidatedControlProps< Value >,
29
+ forwardedRef: React.ForwardedRef< HTMLInputElement >
30
+ ) => {
31
+ const validityTargetRef = useRef< HTMLInputElement >( null );
32
+ const mergedRefs = useMergeRefs( [ forwardedRef, validityTargetRef ] );
33
+ const valueRef = useRef< Value >( restProps.value );
34
+
35
+ return (
36
+ <ControlWithError
37
+ required={ required }
38
+ markWhenOptional={ markWhenOptional }
39
+ customValidator={ () => {
40
+ return customValidator?.( valueRef.current );
41
+ } }
42
+ getValidityTarget={ () => validityTargetRef.current }
43
+ >
44
+ <TextControl
45
+ __next40pxDefaultSize
46
+ __nextHasNoMarginBottom
47
+ ref={ mergedRefs }
48
+ onChange={ ( value ) => {
49
+ valueRef.current = value;
50
+ onChange?.( value );
51
+ } }
52
+ { ...restProps }
53
+ />
54
+ </ControlWithError>
55
+ );
56
+ };
57
+
58
+ export const ValidatedTextControl = forwardRef(
59
+ UnforwardedValidatedTextControl
60
+ );
@@ -0,0 +1,59 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { forwardRef, useRef } from '@wordpress/element';
5
+ import { useMergeRefs } from '@wordpress/compose';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { ControlWithError } from '../control-with-error';
11
+ import type { ValidatedControlProps } from './types';
12
+ import TextareaControl from '../../textarea-control';
13
+ import type { TextareaControlProps } from '../../textarea-control/types';
14
+
15
+ type Value = TextareaControlProps[ 'value' ];
16
+
17
+ const UnforwardedValidatedTextareaControl = (
18
+ {
19
+ required,
20
+ customValidator,
21
+ onChange,
22
+ markWhenOptional,
23
+ ...restProps
24
+ }: Omit<
25
+ React.ComponentProps< typeof TextareaControl >,
26
+ '__nextHasNoMarginBottom'
27
+ > &
28
+ ValidatedControlProps< Value >,
29
+ forwardedRef: React.ForwardedRef< HTMLTextAreaElement >
30
+ ) => {
31
+ const validityTargetRef = useRef< HTMLTextAreaElement >( null );
32
+ const mergedRefs = useMergeRefs( [ forwardedRef, validityTargetRef ] );
33
+ const valueRef = useRef< Value >( restProps.value );
34
+
35
+ return (
36
+ <ControlWithError
37
+ required={ required }
38
+ markWhenOptional={ markWhenOptional }
39
+ customValidator={ () => {
40
+ return customValidator?.( valueRef.current );
41
+ } }
42
+ getValidityTarget={ () => validityTargetRef.current }
43
+ >
44
+ <TextareaControl
45
+ __nextHasNoMarginBottom
46
+ ref={ mergedRefs }
47
+ onChange={ ( value ) => {
48
+ valueRef.current = value;
49
+ onChange?.( value );
50
+ } }
51
+ { ...restProps }
52
+ />
53
+ </ControlWithError>
54
+ );
55
+ };
56
+
57
+ export const ValidatedTextareaControl = forwardRef(
58
+ UnforwardedValidatedTextareaControl
59
+ );
@@ -0,0 +1,69 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { forwardRef, useRef, useEffect } from '@wordpress/element';
5
+ import { useMergeRefs } from '@wordpress/compose';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { ControlWithError } from '../control-with-error';
11
+ import type { ValidatedControlProps } from './types';
12
+ import ToggleControl from '../../toggle-control';
13
+ import type { ToggleControlProps } from '../../toggle-control/types';
14
+
15
+ type Value = ToggleControlProps[ 'checked' ];
16
+
17
+ // TODO: Should we customize the default `missingValue` message? It says to "check this box".
18
+
19
+ const UnforwardedValidatedToggleControl = (
20
+ {
21
+ required,
22
+ customValidator,
23
+ onChange,
24
+ markWhenOptional,
25
+ ...restProps
26
+ }: Omit<
27
+ React.ComponentProps< typeof ToggleControl >,
28
+ '__nextHasNoMarginBottom'
29
+ > &
30
+ ValidatedControlProps< Value >,
31
+ forwardedRef: React.ForwardedRef< HTMLInputElement >
32
+ ) => {
33
+ const validityTargetRef = useRef< HTMLInputElement >( null );
34
+ const mergedRefs = useMergeRefs( [ forwardedRef, validityTargetRef ] );
35
+ const valueRef = useRef< Value >( restProps.checked );
36
+
37
+ // TODO: Upstream limitation - The `required` attribute is not passed down to the input,
38
+ // so we need to set it manually.
39
+ useEffect( () => {
40
+ if ( validityTargetRef.current ) {
41
+ validityTargetRef.current.required = required ?? false;
42
+ }
43
+ }, [ required ] );
44
+
45
+ return (
46
+ <ControlWithError
47
+ required={ required }
48
+ markWhenOptional={ markWhenOptional }
49
+ customValidator={ () => {
50
+ return customValidator?.( valueRef.current );
51
+ } }
52
+ getValidityTarget={ () => validityTargetRef.current }
53
+ >
54
+ <ToggleControl
55
+ __nextHasNoMarginBottom
56
+ ref={ mergedRefs }
57
+ onChange={ ( value ) => {
58
+ valueRef.current = value;
59
+ onChange?.( value );
60
+ } }
61
+ { ...restProps }
62
+ />
63
+ </ControlWithError>
64
+ );
65
+ };
66
+
67
+ export const ValidatedToggleControl = forwardRef(
68
+ UnforwardedValidatedToggleControl
69
+ );
@@ -0,0 +1,82 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { forwardRef, useId, useRef } from '@wordpress/element';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { ControlWithError } from '../control-with-error';
10
+ import type { ValidatedControlProps } from './types';
11
+ import { ToggleGroupControl } from '../../toggle-group-control';
12
+ import type { ToggleGroupControlProps } from '../../toggle-group-control/types';
13
+
14
+ type Value = ToggleGroupControlProps[ 'value' ];
15
+
16
+ const UnforwardedValidatedToggleGroupControl = (
17
+ {
18
+ required,
19
+ customValidator,
20
+ onChange,
21
+ markWhenOptional,
22
+ ...restProps
23
+ }: Omit<
24
+ React.ComponentProps< typeof ToggleGroupControl >,
25
+ '__next40pxDefaultSize' | '__nextHasNoMarginBottom'
26
+ > &
27
+ ValidatedControlProps< Value >,
28
+ forwardedRef: React.ForwardedRef< HTMLInputElement >
29
+ ) => {
30
+ const validityTargetRef = useRef< HTMLInputElement >( null );
31
+ const valueRef = useRef< Value >( restProps.value );
32
+
33
+ const nameAttr = useId();
34
+
35
+ return (
36
+ <div className="components-validated-control__wrapper-with-error-delegate">
37
+ <ControlWithError
38
+ required={ required }
39
+ markWhenOptional={ markWhenOptional }
40
+ customValidator={ () => {
41
+ return customValidator?.( valueRef.current );
42
+ } }
43
+ getValidityTarget={ () => validityTargetRef.current }
44
+ >
45
+ <ToggleGroupControl
46
+ __nextHasNoMarginBottom
47
+ __next40pxDefaultSize
48
+ ref={ forwardedRef }
49
+ // TODO: Upstream limitation - In uncontrolled mode, starting from an undefined value then
50
+ // setting a value has a visual bug.
51
+ onChange={ ( value ) => {
52
+ valueRef.current = value;
53
+ onChange?.( value );
54
+ } }
55
+ { ...restProps }
56
+ />
57
+ </ControlWithError>
58
+ <input
59
+ className="components-validated-control__error-delegate"
60
+ type="radio"
61
+ ref={ validityTargetRef }
62
+ required={ required }
63
+ checked={ restProps.value !== null }
64
+ tabIndex={ -1 }
65
+ // A name attribute is needed for the `required` behavior to work.
66
+ name={ nameAttr }
67
+ onChange={ () => {} }
68
+ onFocus={ ( e ) => {
69
+ e.target.previousElementSibling
70
+ ?.querySelector< HTMLButtonElement | HTMLInputElement >(
71
+ '[data-active-item="true"]'
72
+ )
73
+ ?.focus();
74
+ } }
75
+ />
76
+ </div>
77
+ );
78
+ };
79
+
80
+ export const ValidatedToggleGroupControl = forwardRef(
81
+ UnforwardedValidatedToggleGroupControl
82
+ );
@@ -0,0 +1,28 @@
1
+ export type ValidatedControlProps< V > = {
2
+ /**
3
+ * Whether the control is required.
4
+ * @default false
5
+ */
6
+ required?: boolean;
7
+ /**
8
+ * Label the control as "optional" when _not_ `required`, instead of the inverse.
9
+ * @default false
10
+ */
11
+ markWhenOptional?: boolean;
12
+ /**
13
+ * A function that returns a custom validity message when applicable. This error message will be applied to the
14
+ * underlying element using the native [`setCustomValidity()` method](https://developer.mozilla.org/en-US/docs/Web/API/HTMLObjectElement/setCustomValidity).
15
+ * This means the custom validator will be run _in addition_ to any other HTML attribute-based validation, and
16
+ * will be prioritized over any existing validity messages dictated by the HTML attributes.
17
+ * An empty string or `undefined` return value will clear any existing custom validity message.
18
+ *
19
+ * Make sure you don't programatically pass a value (such as an initial value) to the control component
20
+ * that fails this validator, because the validator will only run for user-initiated changes.
21
+ *
22
+ * Always prefer using standard HTML attributes like `required` and `min`/`max` over custom validators
23
+ * when possible, as they are simpler and have localized error messages built in.
24
+ */
25
+ // TODO: Technically, we could add an optional `customValidity` string prop so the consumer can set
26
+ // an error message at any point in time. We should wait until we have a use case though.
27
+ customValidator?: ( currentValue: V ) => string | void;
28
+ };