@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.
- package/CHANGELOG.md +31 -1
- package/build/autocomplete/autocompleter-ui.js +2 -0
- package/build/autocomplete/autocompleter-ui.js.map +1 -1
- package/build/base-control/styles/base-control-styles.js +8 -8
- package/build/base-control/styles/base-control-styles.js.map +1 -1
- package/build/border-control/styles.js +18 -24
- package/build/border-control/styles.js.map +1 -1
- package/build/color-palette/index.js +1 -1
- package/build/color-palette/index.js.map +1 -1
- package/build/custom-select-control/index.js +37 -14
- package/build/custom-select-control/index.js.map +1 -1
- package/build/custom-select-control/types.js.map +1 -1
- package/build/custom-select-control-v2/styles.js +9 -9
- package/build/custom-select-control-v2/styles.js.map +1 -1
- package/build/date-time/index.js +0 -7
- package/build/date-time/index.js.map +1 -1
- package/build/date-time/time/index.js +66 -38
- package/build/date-time/time/index.js.map +1 -1
- package/build/date-time/time/styles.js +11 -11
- package/build/date-time/time/styles.js.map +1 -1
- package/build/date-time/{time-input → time/time-input}/index.js +7 -7
- package/build/date-time/time/time-input/index.js.map +1 -0
- package/build/dropdown-menu-v2/styles.js +14 -14
- package/build/dropdown-menu-v2/styles.js.map +1 -1
- package/build/form-toggle/index.js +24 -24
- package/build/form-toggle/index.js.map +1 -1
- package/build/guide/index.js +2 -0
- package/build/guide/index.js.map +1 -1
- package/build/heading/types.js.map +1 -1
- package/build/modal/index.js +18 -11
- package/build/modal/index.js.map +1 -1
- package/build/query-controls/index.js +1 -1
- package/build/query-controls/index.js.map +1 -1
- package/build/radio-control/index.js +35 -8
- package/build/radio-control/index.js.map +1 -1
- package/build/radio-control/types.js.map +1 -1
- package/build/select-control/index.js +20 -8
- package/build/select-control/index.js.map +1 -1
- package/build/select-control/types.js.map +1 -1
- package/build/text-control/index.js +1 -0
- package/build/text-control/index.js.map +1 -1
- package/build/toggle-control/index.js +27 -25
- package/build/toggle-control/index.js.map +1 -1
- package/build/toggle-group-control/toggle-group-control/component.js +6 -1
- package/build/toggle-group-control/toggle-group-control/component.js.map +1 -1
- package/build/toggle-group-control/toggle-group-control-option/component.js +6 -1
- package/build/toggle-group-control/toggle-group-control-option/component.js.map +1 -1
- package/build/toggle-group-control/toggle-group-control-option-icon/component.js +14 -14
- package/build/toggle-group-control/toggle-group-control-option-icon/component.js.map +1 -1
- package/build/tooltip/index.js +12 -1
- package/build/tooltip/index.js.map +1 -1
- package/build/tree-select/index.js +1 -2
- package/build/tree-select/index.js.map +1 -1
- package/build/utils/config-values.js +6 -0
- package/build/utils/config-values.js.map +1 -1
- package/build-module/autocomplete/autocompleter-ui.js +2 -0
- package/build-module/autocomplete/autocompleter-ui.js.map +1 -1
- package/build-module/base-control/styles/base-control-styles.js +8 -8
- package/build-module/base-control/styles/base-control-styles.js.map +1 -1
- package/build-module/border-control/styles.js +13 -23
- package/build-module/border-control/styles.js.map +1 -1
- package/build-module/color-palette/index.js +1 -1
- package/build-module/color-palette/index.js.map +1 -1
- package/build-module/custom-select-control/index.js +38 -14
- package/build-module/custom-select-control/index.js.map +1 -1
- package/build-module/custom-select-control/types.js.map +1 -1
- package/build-module/custom-select-control-v2/styles.js +9 -9
- package/build-module/custom-select-control-v2/styles.js.map +1 -1
- package/build-module/date-time/index.js +1 -2
- package/build-module/date-time/index.js.map +1 -1
- package/build-module/date-time/time/index.js +66 -38
- package/build-module/date-time/time/index.js.map +1 -1
- package/build-module/date-time/time/styles.js +11 -11
- package/build-module/date-time/time/styles.js.map +1 -1
- package/build-module/date-time/{time-input → time/time-input}/index.js +7 -7
- package/build-module/date-time/time/time-input/index.js.map +1 -0
- package/build-module/dropdown-menu-v2/styles.js +14 -14
- package/build-module/dropdown-menu-v2/styles.js.map +1 -1
- package/build-module/form-toggle/index.js +23 -22
- package/build-module/form-toggle/index.js.map +1 -1
- package/build-module/guide/index.js +2 -0
- package/build-module/guide/index.js.map +1 -1
- package/build-module/heading/types.js.map +1 -1
- package/build-module/modal/index.js +17 -11
- package/build-module/modal/index.js.map +1 -1
- package/build-module/query-controls/index.js +1 -1
- package/build-module/query-controls/index.js.map +1 -1
- package/build-module/radio-control/index.js +36 -10
- package/build-module/radio-control/index.js.map +1 -1
- package/build-module/radio-control/types.js.map +1 -1
- package/build-module/select-control/index.js +20 -8
- package/build-module/select-control/index.js.map +1 -1
- package/build-module/select-control/types.js.map +1 -1
- package/build-module/text-control/index.js +1 -0
- package/build-module/text-control/index.js.map +1 -1
- package/build-module/toggle-control/index.js +26 -24
- package/build-module/toggle-control/index.js.map +1 -1
- package/build-module/toggle-group-control/toggle-group-control/component.js +6 -1
- package/build-module/toggle-group-control/toggle-group-control/component.js.map +1 -1
- package/build-module/toggle-group-control/toggle-group-control-option/component.js +6 -1
- package/build-module/toggle-group-control/toggle-group-control-option/component.js.map +1 -1
- package/build-module/toggle-group-control/toggle-group-control-option-icon/component.js +14 -14
- package/build-module/toggle-group-control/toggle-group-control-option-icon/component.js.map +1 -1
- package/build-module/tooltip/index.js +13 -2
- package/build-module/tooltip/index.js.map +1 -1
- package/build-module/tree-select/index.js +1 -2
- package/build-module/tree-select/index.js.map +1 -1
- package/build-module/utils/config-values.js +6 -0
- package/build-module/utils/config-values.js.map +1 -1
- package/build-style/style-rtl.css +60 -24
- package/build-style/style.css +60 -24
- package/build-types/autocomplete/autocompleter-ui.d.ts.map +1 -1
- package/build-types/border-control/styles.d.ts.map +1 -1
- package/build-types/button/stories/e2e/index.story.d.ts.map +1 -1
- package/build-types/color-palette/index.d.ts.map +1 -1
- package/build-types/color-palette/styles.d.ts +2 -2
- package/build-types/color-picker/styles.d.ts +3 -1
- package/build-types/color-picker/styles.d.ts.map +1 -1
- package/build-types/custom-select-control/index.d.ts +2 -2
- package/build-types/custom-select-control/index.d.ts.map +1 -1
- package/build-types/custom-select-control/stories/index.story.d.ts +3 -3
- package/build-types/custom-select-control/stories/index.story.d.ts.map +1 -1
- package/build-types/custom-select-control/types.d.ts +7 -7
- package/build-types/custom-select-control/types.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/styles.d.ts +16 -28
- package/build-types/custom-select-control-v2/styles.d.ts.map +1 -1
- package/build-types/date-time/date/styles.d.ts +2 -2
- package/build-types/date-time/index.d.ts +1 -2
- package/build-types/date-time/index.d.ts.map +1 -1
- package/build-types/date-time/stories/time.story.d.ts +5 -0
- package/build-types/date-time/stories/time.story.d.ts.map +1 -1
- package/build-types/date-time/time/index.d.ts +3 -0
- package/build-types/date-time/time/index.d.ts.map +1 -1
- package/build-types/date-time/time/styles.d.ts.map +1 -1
- package/build-types/date-time/{time-input → time/time-input}/index.d.ts +1 -1
- package/build-types/date-time/time/time-input/index.d.ts.map +1 -0
- package/build-types/date-time/time/time-input/test/index.d.ts.map +1 -0
- package/build-types/dropdown-menu-v2/styles.d.ts +24 -42
- package/build-types/dropdown-menu-v2/styles.d.ts.map +1 -1
- package/build-types/form-toggle/index.d.ts +2 -5
- package/build-types/form-toggle/index.d.ts.map +1 -1
- package/build-types/guide/index.d.ts.map +1 -1
- package/build-types/heading/component.d.ts +1 -1
- package/build-types/heading/types.d.ts +1 -1
- package/build-types/heading/types.d.ts.map +1 -1
- package/build-types/modal/index.d.ts.map +1 -1
- package/build-types/navigation/styles/navigation-styles.d.ts +2 -2
- package/build-types/palette-edit/styles.d.ts +2 -2
- package/build-types/query-controls/index.d.ts.map +1 -1
- package/build-types/radio-control/index.d.ts.map +1 -1
- package/build-types/radio-control/stories/index.story.d.ts +1 -0
- package/build-types/radio-control/stories/index.story.d.ts.map +1 -1
- package/build-types/radio-control/test/index.d.ts +2 -0
- package/build-types/radio-control/test/index.d.ts.map +1 -0
- package/build-types/radio-control/types.d.ts +4 -0
- package/build-types/radio-control/types.d.ts.map +1 -1
- package/build-types/select-control/index.d.ts +4 -1
- package/build-types/select-control/index.d.ts.map +1 -1
- package/build-types/select-control/stories/index.story.d.ts +9 -3
- package/build-types/select-control/stories/index.story.d.ts.map +1 -1
- package/build-types/select-control/types.d.ts +27 -27
- package/build-types/select-control/types.d.ts.map +1 -1
- package/build-types/tabs/styles.d.ts +8 -14
- package/build-types/tabs/styles.d.ts.map +1 -1
- package/build-types/text-control/index.d.ts +1 -0
- package/build-types/text-control/index.d.ts.map +1 -1
- package/build-types/toggle-control/index.d.ts +3 -9
- package/build-types/toggle-control/index.d.ts.map +1 -1
- package/build-types/toggle-group-control/toggle-group-control/component.d.ts +6 -1
- package/build-types/toggle-group-control/toggle-group-control/component.d.ts.map +1 -1
- package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts +6 -1
- package/build-types/toggle-group-control/toggle-group-control-option/component.d.ts.map +1 -1
- package/build-types/toggle-group-control/toggle-group-control-option-icon/component.d.ts +14 -14
- package/build-types/tooltip/index.d.ts.map +1 -1
- package/build-types/tooltip/test/utils/index.d.ts +1 -5
- package/build-types/tooltip/test/utils/index.d.ts.map +1 -1
- package/build-types/tree-select/index.d.ts +1 -1
- package/build-types/tree-select/index.d.ts.map +1 -1
- package/build-types/utils/config-values.d.ts +6 -0
- package/package.json +20 -20
- package/src/alignment-matrix-control/test/index.tsx +1 -3
- package/src/autocomplete/autocompleter-ui.tsx +2 -0
- package/src/autocomplete/style.scss +0 -6
- package/src/base-control/styles/base-control-styles.ts +1 -1
- package/src/border-control/styles.ts +0 -5
- package/src/button/stories/e2e/index.story.tsx +103 -21
- package/src/button/style.scss +49 -21
- package/src/button/test/index.tsx +18 -40
- package/src/circular-option-picker/test/index.tsx +1 -4
- package/src/color-palette/index.tsx +22 -20
- package/src/composite/legacy/test/index.tsx +2 -18
- package/src/custom-select-control/index.tsx +55 -25
- package/src/custom-select-control/test/index.tsx +47 -30
- package/src/custom-select-control/types.ts +7 -7
- package/src/custom-select-control-v2/styles.ts +7 -6
- package/src/custom-select-control-v2/test/index.tsx +15 -19
- package/src/date-time/index.ts +1 -2
- package/src/date-time/stories/time.story.tsx +17 -0
- package/src/date-time/time/index.tsx +46 -16
- package/src/date-time/time/styles.ts +1 -0
- package/src/date-time/{time-input → time/time-input}/index.tsx +9 -9
- package/src/dropdown-menu-v2/styles.ts +18 -17
- package/src/dropdown-menu-v2/test/index.tsx +1 -4
- package/src/font-size-picker/test/index.tsx +50 -43
- package/src/form-toggle/index.tsx +23 -21
- package/src/guide/index.tsx +2 -0
- package/src/heading/types.ts +1 -4
- package/src/modal/index.tsx +21 -20
- package/src/placeholder/style.scss +11 -2
- package/src/query-controls/index.tsx +5 -1
- package/src/radio-control/index.tsx +48 -7
- package/src/radio-control/stories/index.story.tsx +23 -0
- package/src/radio-control/style.scss +26 -2
- package/src/radio-control/test/index.tsx +274 -0
- package/src/radio-control/types.ts +4 -0
- package/src/select-control/README.md +8 -1
- package/src/select-control/index.tsx +33 -22
- package/src/select-control/test/select-control.tsx +148 -4
- package/src/select-control/types.ts +70 -65
- package/src/tab-panel/test/index.tsx +1 -8
- package/src/tabs/test/index.tsx +68 -84
- package/src/text-control/README.md +1 -0
- package/src/text-control/index.tsx +1 -0
- package/src/text-control/style.scss +5 -0
- package/src/toggle-control/README.md +9 -0
- package/src/toggle-control/index.tsx +25 -22
- package/src/toggle-control/style.scss +2 -1
- package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +6 -6
- package/src/toggle-group-control/test/index.tsx +0 -6
- package/src/toggle-group-control/toggle-group-control/README.md +13 -1
- package/src/toggle-group-control/toggle-group-control/component.tsx +6 -1
- package/src/toggle-group-control/toggle-group-control-option/README.md +6 -1
- package/src/toggle-group-control/toggle-group-control-option/component.tsx +6 -1
- package/src/toggle-group-control/toggle-group-control-option-icon/README.md +1 -1
- package/src/toggle-group-control/toggle-group-control-option-icon/component.tsx +14 -14
- package/src/tooltip/index.tsx +15 -2
- package/src/tooltip/test/index.tsx +0 -5
- package/src/tooltip/test/utils/index.tsx +5 -5
- package/src/tree-select/index.tsx +1 -2
- package/src/utils/config-values.js +6 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/build/date-time/time-input/index.js.map +0 -1
- package/build-module/date-time/time-input/index.js.map +0 -1
- package/build-types/date-time/stories/time-input.story.d.ts +0 -12
- package/build-types/date-time/stories/time-input.story.d.ts.map +0 -1
- package/build-types/date-time/time-input/index.d.ts.map +0 -1
- package/build-types/date-time/time-input/test/index.d.ts.map +0 -1
- package/src/date-time/stories/time-input.story.tsx +0 -36
- /package/build-types/date-time/{time-input → time/time-input}/test/index.d.ts +0 -0
- /package/src/date-time/{time-input → time/time-input}/test/index.tsx +0 -0
|
@@ -183,8 +183,17 @@
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
&::before {
|
|
187
|
+
content: "";
|
|
188
|
+
position: absolute;
|
|
189
|
+
top: 0;
|
|
190
|
+
right: 0;
|
|
191
|
+
bottom: 0;
|
|
192
|
+
left: 0;
|
|
193
|
+
pointer-events: none;
|
|
194
|
+
background: currentColor;
|
|
195
|
+
opacity: 0.1;
|
|
196
|
+
}
|
|
188
197
|
|
|
189
198
|
overflow: hidden;
|
|
190
199
|
.is-selected & {
|
|
@@ -85,7 +85,11 @@ export function QueryControls( {
|
|
|
85
85
|
__next40pxDefaultSize={ __next40pxDefaultSize }
|
|
86
86
|
key="query-controls-order-select"
|
|
87
87
|
label={ __( 'Order by' ) }
|
|
88
|
-
value={
|
|
88
|
+
value={
|
|
89
|
+
orderBy === undefined || order === undefined
|
|
90
|
+
? undefined
|
|
91
|
+
: `${ orderBy }/${ order }`
|
|
92
|
+
}
|
|
89
93
|
options={ [
|
|
90
94
|
{
|
|
91
95
|
label: __( 'Newest to oldest' ),
|
|
@@ -16,6 +16,16 @@ import BaseControl from '../base-control';
|
|
|
16
16
|
import type { WordPressComponentProps } from '../context';
|
|
17
17
|
import type { RadioControlProps } from './types';
|
|
18
18
|
import { VStack } from '../v-stack';
|
|
19
|
+
import { useBaseControlProps } from '../base-control/hooks';
|
|
20
|
+
import { StyledHelp } from '../base-control/styles/base-control-styles';
|
|
21
|
+
|
|
22
|
+
function generateOptionDescriptionId( radioGroupId: string, index: number ) {
|
|
23
|
+
return `${ radioGroupId }-${ index }-option-description`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function generateOptionId( radioGroupId: string, index: number ) {
|
|
27
|
+
return `${ radioGroupId }-${ index }`;
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
/**
|
|
21
31
|
* Render a user interface to select the user type using radio inputs.
|
|
@@ -53,13 +63,23 @@ export function RadioControl(
|
|
|
53
63
|
onChange,
|
|
54
64
|
hideLabelFromVision,
|
|
55
65
|
options = [],
|
|
66
|
+
id: preferredId,
|
|
56
67
|
...additionalProps
|
|
57
68
|
} = props;
|
|
58
|
-
const
|
|
59
|
-
|
|
69
|
+
const id = useInstanceId(
|
|
70
|
+
RadioControl,
|
|
71
|
+
'inspector-radio-control',
|
|
72
|
+
preferredId
|
|
73
|
+
);
|
|
74
|
+
|
|
60
75
|
const onChangeValue = ( event: ChangeEvent< HTMLInputElement > ) =>
|
|
61
76
|
onChange( event.target.value );
|
|
62
77
|
|
|
78
|
+
// Use `useBaseControlProps` to get the id of the help text.
|
|
79
|
+
const {
|
|
80
|
+
controlProps: { 'aria-describedby': helpTextId },
|
|
81
|
+
} = useBaseControlProps( { id, help } );
|
|
82
|
+
|
|
63
83
|
if ( ! options?.length ) {
|
|
64
84
|
return null;
|
|
65
85
|
}
|
|
@@ -73,14 +93,19 @@ export function RadioControl(
|
|
|
73
93
|
help={ help }
|
|
74
94
|
className={ clsx( className, 'components-radio-control' ) }
|
|
75
95
|
>
|
|
76
|
-
<VStack
|
|
96
|
+
<VStack
|
|
97
|
+
spacing={ 3 }
|
|
98
|
+
className={ clsx( 'components-radio-control__group-wrapper', {
|
|
99
|
+
'has-help': !! help,
|
|
100
|
+
} ) }
|
|
101
|
+
>
|
|
77
102
|
{ options.map( ( option, index ) => (
|
|
78
103
|
<div
|
|
79
|
-
key={
|
|
104
|
+
key={ generateOptionId( id, index ) }
|
|
80
105
|
className="components-radio-control__option"
|
|
81
106
|
>
|
|
82
107
|
<input
|
|
83
|
-
id={
|
|
108
|
+
id={ generateOptionId( id, index ) }
|
|
84
109
|
className="components-radio-control__input"
|
|
85
110
|
type="radio"
|
|
86
111
|
name={ id }
|
|
@@ -88,16 +113,32 @@ export function RadioControl(
|
|
|
88
113
|
onChange={ onChangeValue }
|
|
89
114
|
checked={ option.value === selected }
|
|
90
115
|
aria-describedby={
|
|
91
|
-
|
|
116
|
+
clsx( [
|
|
117
|
+
!! option.description &&
|
|
118
|
+
generateOptionDescriptionId(
|
|
119
|
+
id,
|
|
120
|
+
index
|
|
121
|
+
),
|
|
122
|
+
helpTextId,
|
|
123
|
+
] ) || undefined
|
|
92
124
|
}
|
|
93
125
|
{ ...additionalProps }
|
|
94
126
|
/>
|
|
95
127
|
<label
|
|
96
128
|
className="components-radio-control__label"
|
|
97
|
-
htmlFor={
|
|
129
|
+
htmlFor={ generateOptionId( id, index ) }
|
|
98
130
|
>
|
|
99
131
|
{ option.label }
|
|
100
132
|
</label>
|
|
133
|
+
{ !! option.description ? (
|
|
134
|
+
<StyledHelp
|
|
135
|
+
__nextHasNoMarginBottom
|
|
136
|
+
id={ generateOptionDescriptionId( id, index ) }
|
|
137
|
+
className="components-radio-control__option-description"
|
|
138
|
+
>
|
|
139
|
+
{ option.description }
|
|
140
|
+
</StyledHelp>
|
|
141
|
+
) : null }
|
|
101
142
|
</div>
|
|
102
143
|
) ) }
|
|
103
144
|
</VStack>
|
|
@@ -68,3 +68,26 @@ Default.args = {
|
|
|
68
68
|
{ label: 'Password Protected', value: 'password' },
|
|
69
69
|
],
|
|
70
70
|
};
|
|
71
|
+
|
|
72
|
+
export const WithOptionDescriptions: StoryFn< typeof RadioControl > =
|
|
73
|
+
Template.bind( {} );
|
|
74
|
+
WithOptionDescriptions.args = {
|
|
75
|
+
...Default.args,
|
|
76
|
+
options: [
|
|
77
|
+
{
|
|
78
|
+
label: 'Public',
|
|
79
|
+
value: 'public',
|
|
80
|
+
description: 'Visible to everyone',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
label: 'Private',
|
|
84
|
+
value: 'private',
|
|
85
|
+
description: 'Only visible to you',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
label: 'Password Protected',
|
|
89
|
+
value: 'password',
|
|
90
|
+
description: 'Protected by a password',
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
.components-radio-control__group-wrapper.has-help {
|
|
2
|
+
margin-block-end: $grid-unit-15;
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
.components-radio-control__option {
|
|
2
|
-
display:
|
|
6
|
+
display: grid;
|
|
7
|
+
grid-template-columns: auto 1fr;
|
|
8
|
+
grid-template-rows: auto minmax(0, max-content);
|
|
9
|
+
column-gap: $grid-unit-10;
|
|
3
10
|
align-items: center;
|
|
4
11
|
}
|
|
5
12
|
|
|
6
13
|
.components-radio-control__input[type="radio"] {
|
|
14
|
+
grid-column: 1;
|
|
15
|
+
grid-row: 1;
|
|
16
|
+
|
|
7
17
|
@include radio-control;
|
|
8
18
|
|
|
9
19
|
display: inline-flex;
|
|
10
|
-
margin: 0
|
|
20
|
+
margin: 0;
|
|
11
21
|
padding: 0;
|
|
12
22
|
appearance: none;
|
|
13
23
|
cursor: pointer;
|
|
@@ -28,6 +38,9 @@
|
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
.components-radio-control__label {
|
|
41
|
+
grid-column: 2;
|
|
42
|
+
grid-row: 1;
|
|
43
|
+
|
|
31
44
|
cursor: pointer;
|
|
32
45
|
line-height: $radio-input-size-sm;
|
|
33
46
|
|
|
@@ -35,3 +48,14 @@
|
|
|
35
48
|
line-height: $radio-input-size;
|
|
36
49
|
}
|
|
37
50
|
}
|
|
51
|
+
|
|
52
|
+
.components-radio-control__option-description {
|
|
53
|
+
grid-column: 2;
|
|
54
|
+
grid-row: 2;
|
|
55
|
+
|
|
56
|
+
padding-block-start: $grid-unit-05;
|
|
57
|
+
|
|
58
|
+
// Override the top margin of the StyledHelp component from BaseControl.
|
|
59
|
+
// TODO: we should tweak the StyledHelp component to not have a top margin.
|
|
60
|
+
margin-top: 0;
|
|
61
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { useState } from '@wordpress/element';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import RadioControl from '../';
|
|
16
|
+
|
|
17
|
+
const ControlledRadioControl = ( {
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps< typeof RadioControl > ) => {
|
|
20
|
+
const [ option, setOption ] = useState( props.selected );
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<RadioControl
|
|
24
|
+
{ ...props }
|
|
25
|
+
onChange={ ( newValue ) => {
|
|
26
|
+
setOption( newValue );
|
|
27
|
+
props.onChange?.( newValue );
|
|
28
|
+
} }
|
|
29
|
+
selected={ option }
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const defaultProps = {
|
|
35
|
+
options: [
|
|
36
|
+
{ label: 'Mouse', value: 'mouse' },
|
|
37
|
+
{ label: 'Cat', value: 'cat' },
|
|
38
|
+
{ label: 'Dog', value: 'dog' },
|
|
39
|
+
],
|
|
40
|
+
label: 'Animal',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const defaultPropsWithDescriptions = {
|
|
44
|
+
...defaultProps,
|
|
45
|
+
options: defaultProps.options.map( ( option, index ) => ( {
|
|
46
|
+
...option,
|
|
47
|
+
description: `This is the option number ${ index + 1 }.`,
|
|
48
|
+
} ) ),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe.each( [
|
|
52
|
+
// TODO: `RadioControl` doesn't currently support uncontrolled mode.
|
|
53
|
+
// [ 'uncontrolled', RadioControl ],
|
|
54
|
+
[ 'controlled', ControlledRadioControl ],
|
|
55
|
+
] )( 'RadioControl %s', ( ...modeAndComponent ) => {
|
|
56
|
+
const [ , Component ] = modeAndComponent;
|
|
57
|
+
|
|
58
|
+
describe( 'semantics and labelling', () => {
|
|
59
|
+
it( 'should render radio inputs with accessible labels', () => {
|
|
60
|
+
const onChangeSpy = jest.fn();
|
|
61
|
+
render(
|
|
62
|
+
<Component { ...defaultProps } onChange={ onChangeSpy } />
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
for ( const option of defaultProps.options ) {
|
|
66
|
+
const optionEl = screen.getByRole( 'radio', {
|
|
67
|
+
name: option.label,
|
|
68
|
+
} );
|
|
69
|
+
expect( optionEl ).toBeVisible();
|
|
70
|
+
expect( optionEl ).not.toBeChecked();
|
|
71
|
+
}
|
|
72
|
+
} );
|
|
73
|
+
|
|
74
|
+
it( 'should not select have a selected value when the `selected` prop does not match any available options', () => {
|
|
75
|
+
const onChangeSpy = jest.fn();
|
|
76
|
+
render(
|
|
77
|
+
<Component { ...defaultProps } onChange={ onChangeSpy } />
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(
|
|
81
|
+
screen.queryByRole( 'radio', {
|
|
82
|
+
checked: true,
|
|
83
|
+
} )
|
|
84
|
+
).not.toBeInTheDocument();
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
it( 'should render mutually exclusive radio inputs', () => {
|
|
88
|
+
const onChangeSpy = jest.fn();
|
|
89
|
+
render(
|
|
90
|
+
<Component
|
|
91
|
+
{ ...defaultProps }
|
|
92
|
+
onChange={ onChangeSpy }
|
|
93
|
+
selected={ defaultProps.options[ 1 ].value }
|
|
94
|
+
/>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(
|
|
98
|
+
screen.getByRole( 'radio', {
|
|
99
|
+
checked: true,
|
|
100
|
+
} )
|
|
101
|
+
).toHaveAccessibleName( defaultProps.options[ 1 ].label );
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
it( 'should use the group help text to describe individual options', () => {
|
|
105
|
+
const onChangeSpy = jest.fn();
|
|
106
|
+
render(
|
|
107
|
+
<Component
|
|
108
|
+
{ ...defaultProps }
|
|
109
|
+
onChange={ onChangeSpy }
|
|
110
|
+
selected={ defaultProps.options[ 1 ].value }
|
|
111
|
+
help="Select your favorite animal."
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
for ( const option of defaultProps.options ) {
|
|
116
|
+
expect(
|
|
117
|
+
screen.getByRole( 'radio', { name: option.label } )
|
|
118
|
+
).toHaveAccessibleDescription( 'Select your favorite animal.' );
|
|
119
|
+
}
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
it( 'should use the option description text to describe individual options', () => {
|
|
123
|
+
const onChangeSpy = jest.fn();
|
|
124
|
+
render(
|
|
125
|
+
<Component
|
|
126
|
+
{ ...defaultPropsWithDescriptions }
|
|
127
|
+
onChange={ onChangeSpy }
|
|
128
|
+
selected={ defaultProps.options[ 1 ].value }
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
let index = 1;
|
|
133
|
+
for ( const option of defaultProps.options ) {
|
|
134
|
+
expect(
|
|
135
|
+
screen.getByRole( 'radio', { name: option.label } )
|
|
136
|
+
).toHaveAccessibleDescription(
|
|
137
|
+
`This is the option number ${ index }.`
|
|
138
|
+
);
|
|
139
|
+
index += 1;
|
|
140
|
+
}
|
|
141
|
+
} );
|
|
142
|
+
|
|
143
|
+
it( 'should use both the option description text and the group help text to describe individual options', () => {
|
|
144
|
+
const onChangeSpy = jest.fn();
|
|
145
|
+
render(
|
|
146
|
+
<Component
|
|
147
|
+
{ ...defaultPropsWithDescriptions }
|
|
148
|
+
onChange={ onChangeSpy }
|
|
149
|
+
selected={ defaultProps.options[ 1 ].value }
|
|
150
|
+
help="Select your favorite animal"
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
let index = 1;
|
|
155
|
+
for ( const option of defaultProps.options ) {
|
|
156
|
+
expect(
|
|
157
|
+
screen.getByRole( 'radio', { name: option.label } )
|
|
158
|
+
).toHaveAccessibleDescription(
|
|
159
|
+
`This is the option number ${ index }. Select your favorite animal`
|
|
160
|
+
);
|
|
161
|
+
index += 1;
|
|
162
|
+
}
|
|
163
|
+
} );
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
describe( 'interaction', () => {
|
|
167
|
+
it( 'should select a new value when clicking on the radio input', async () => {
|
|
168
|
+
const user = userEvent.setup();
|
|
169
|
+
const onChangeSpy = jest.fn();
|
|
170
|
+
render(
|
|
171
|
+
<Component { ...defaultProps } onChange={ onChangeSpy } />
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Click on the third radio, make sure it's selected.
|
|
175
|
+
await user.click(
|
|
176
|
+
screen.getByRole( 'radio', {
|
|
177
|
+
name: defaultProps.options[ 2 ].label,
|
|
178
|
+
} )
|
|
179
|
+
);
|
|
180
|
+
expect(
|
|
181
|
+
screen.getByRole( 'radio', {
|
|
182
|
+
checked: true,
|
|
183
|
+
} )
|
|
184
|
+
).toHaveAccessibleName( defaultProps.options[ 2 ].label );
|
|
185
|
+
|
|
186
|
+
expect( onChangeSpy ).toHaveBeenCalledTimes( 1 );
|
|
187
|
+
expect( onChangeSpy ).toHaveBeenLastCalledWith(
|
|
188
|
+
defaultProps.options[ 2 ].value
|
|
189
|
+
);
|
|
190
|
+
} );
|
|
191
|
+
|
|
192
|
+
it( 'should select a new value when clicking on the radio label', async () => {
|
|
193
|
+
const user = userEvent.setup();
|
|
194
|
+
const onChangeSpy = jest.fn();
|
|
195
|
+
render(
|
|
196
|
+
<Component { ...defaultProps } onChange={ onChangeSpy } />
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Click on the second radio's label, make sure it selects the associated radio.
|
|
200
|
+
await user.click(
|
|
201
|
+
screen.getByText( defaultProps.options[ 1 ].label )
|
|
202
|
+
);
|
|
203
|
+
expect(
|
|
204
|
+
screen.getByRole( 'radio', {
|
|
205
|
+
checked: true,
|
|
206
|
+
} )
|
|
207
|
+
).toHaveAccessibleName( defaultProps.options[ 1 ].label );
|
|
208
|
+
|
|
209
|
+
expect( onChangeSpy ).toHaveBeenCalledTimes( 1 );
|
|
210
|
+
expect( onChangeSpy ).toHaveBeenLastCalledWith(
|
|
211
|
+
defaultProps.options[ 1 ].value
|
|
212
|
+
);
|
|
213
|
+
} );
|
|
214
|
+
|
|
215
|
+
it( 'should select a new value when using the arrow keys', async () => {
|
|
216
|
+
const user = userEvent.setup();
|
|
217
|
+
const onChangeSpy = jest.fn();
|
|
218
|
+
render(
|
|
219
|
+
<Component { ...defaultProps } onChange={ onChangeSpy } />
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
await user.tab();
|
|
223
|
+
|
|
224
|
+
expect(
|
|
225
|
+
screen.getByRole( 'radio', {
|
|
226
|
+
name: defaultProps.options[ 0 ].label,
|
|
227
|
+
} )
|
|
228
|
+
).toHaveFocus();
|
|
229
|
+
|
|
230
|
+
await user.keyboard( '{ArrowDown}' );
|
|
231
|
+
|
|
232
|
+
expect(
|
|
233
|
+
screen.getByRole( 'radio', {
|
|
234
|
+
checked: true,
|
|
235
|
+
name: defaultProps.options[ 1 ].label,
|
|
236
|
+
} )
|
|
237
|
+
).toHaveFocus();
|
|
238
|
+
expect( onChangeSpy ).toHaveBeenCalledTimes( 2 );
|
|
239
|
+
expect( onChangeSpy ).toHaveBeenLastCalledWith(
|
|
240
|
+
defaultProps.options[ 1 ].value
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
await user.keyboard( '{ArrowDown}' );
|
|
244
|
+
await user.keyboard( '{ArrowDown}' );
|
|
245
|
+
|
|
246
|
+
// The selection wrap around.
|
|
247
|
+
expect(
|
|
248
|
+
screen.getByRole( 'radio', {
|
|
249
|
+
checked: true,
|
|
250
|
+
name: defaultProps.options[ 0 ].label,
|
|
251
|
+
} )
|
|
252
|
+
).toHaveFocus();
|
|
253
|
+
// TODO: why called twice for every interaction?
|
|
254
|
+
expect( onChangeSpy ).toHaveBeenCalledTimes( 6 );
|
|
255
|
+
expect( onChangeSpy ).toHaveBeenLastCalledWith(
|
|
256
|
+
defaultProps.options[ 0 ].value
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
await user.keyboard( '{ArrowUp}' );
|
|
260
|
+
|
|
261
|
+
expect(
|
|
262
|
+
screen.getByRole( 'radio', {
|
|
263
|
+
checked: true,
|
|
264
|
+
name: defaultProps.options[ 2 ].label,
|
|
265
|
+
} )
|
|
266
|
+
).toHaveFocus();
|
|
267
|
+
|
|
268
|
+
expect( onChangeSpy ).toHaveBeenCalledTimes( 8 );
|
|
269
|
+
expect( onChangeSpy ).toHaveBeenLastCalledWith(
|
|
270
|
+
defaultProps.options[ 2 ].value
|
|
271
|
+
);
|
|
272
|
+
} );
|
|
273
|
+
} );
|
|
274
|
+
} );
|
|
@@ -24,6 +24,10 @@ export type RadioControlProps = Pick<
|
|
|
24
24
|
* The internal value compared against select and passed to onChange
|
|
25
25
|
*/
|
|
26
26
|
value: string;
|
|
27
|
+
/**
|
|
28
|
+
* Optional help text to be shown in addition the label.
|
|
29
|
+
*/
|
|
30
|
+
description?: string;
|
|
27
31
|
}[];
|
|
28
32
|
/**
|
|
29
33
|
* The value property of the currently selected option.
|
|
@@ -190,7 +190,7 @@ In most cases, it is preferable to use the `FormTokenField` or `CheckboxControl`
|
|
|
190
190
|
|
|
191
191
|
#### options
|
|
192
192
|
|
|
193
|
-
An array of objects containing the following properties:
|
|
193
|
+
An array of objects containing the following properties, as well as any other `option` element attributes:
|
|
194
194
|
|
|
195
195
|
- `label`: (string) The label to be shown to the user.
|
|
196
196
|
- `value`: (string) The internal value used to choose the selected value. This is also the value passed to onChange when the option is selected.
|
|
@@ -214,6 +214,13 @@ If multiple is false the value received is a single value with the new selected
|
|
|
214
214
|
- Type: `function`
|
|
215
215
|
- Required: Yes
|
|
216
216
|
|
|
217
|
+
#### value
|
|
218
|
+
|
|
219
|
+
The value of the selected option. If `multiple` is true, the `value` should be an array with the values of the selected options.
|
|
220
|
+
|
|
221
|
+
- Type: `String|String[]`
|
|
222
|
+
- Required: No
|
|
223
|
+
|
|
217
224
|
#### variant
|
|
218
225
|
|
|
219
226
|
The style variant of the control.
|
|
@@ -26,8 +26,24 @@ function useUniqueId( idProp?: string ) {
|
|
|
26
26
|
return idProp || id;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function
|
|
30
|
-
|
|
29
|
+
function SelectOptions( {
|
|
30
|
+
options,
|
|
31
|
+
}: {
|
|
32
|
+
options: NonNullable< SelectControlProps[ 'options' ] >;
|
|
33
|
+
} ) {
|
|
34
|
+
return options.map( ( { id, label, value, ...optionProps }, index ) => {
|
|
35
|
+
const key = id || `${ label }-${ value }-${ index }`;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<option key={ key } value={ value } { ...optionProps }>
|
|
39
|
+
{ label }
|
|
40
|
+
</option>
|
|
41
|
+
);
|
|
42
|
+
} );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function UnforwardedSelectControl< V extends string >(
|
|
46
|
+
props: WordPressComponentProps< SelectControlProps< V >, 'select', false >,
|
|
31
47
|
ref: React.ForwardedRef< HTMLSelectElement >
|
|
32
48
|
) {
|
|
33
49
|
const {
|
|
@@ -66,12 +82,14 @@ function UnforwardedSelectControl(
|
|
|
66
82
|
const selectedOptions = Array.from( event.target.options ).filter(
|
|
67
83
|
( { selected } ) => selected
|
|
68
84
|
);
|
|
69
|
-
const newValues = selectedOptions.map(
|
|
85
|
+
const newValues = selectedOptions.map(
|
|
86
|
+
( { value } ) => value as V
|
|
87
|
+
);
|
|
70
88
|
props.onChange?.( newValues, { event } );
|
|
71
89
|
return;
|
|
72
90
|
}
|
|
73
91
|
|
|
74
|
-
props.onChange?.( event.target.value, { event } );
|
|
92
|
+
props.onChange?.( event.target.value as V, { event } );
|
|
75
93
|
};
|
|
76
94
|
|
|
77
95
|
const classes = clsx( 'components-select-control', className );
|
|
@@ -115,23 +133,7 @@ function UnforwardedSelectControl(
|
|
|
115
133
|
value={ valueProp }
|
|
116
134
|
variant={ variant }
|
|
117
135
|
>
|
|
118
|
-
{ children ||
|
|
119
|
-
options.map( ( option, index ) => {
|
|
120
|
-
const key =
|
|
121
|
-
option.id ||
|
|
122
|
-
`${ option.label }-${ option.value }-${ index }`;
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<option
|
|
126
|
-
key={ key }
|
|
127
|
-
value={ option.value }
|
|
128
|
-
disabled={ option.disabled }
|
|
129
|
-
hidden={ option.hidden }
|
|
130
|
-
>
|
|
131
|
-
{ option.label }
|
|
132
|
-
</option>
|
|
133
|
-
);
|
|
134
|
-
} ) }
|
|
136
|
+
{ children || <SelectOptions options={ options } /> }
|
|
135
137
|
</Select>
|
|
136
138
|
</StyledInputBase>
|
|
137
139
|
</BaseControl>
|
|
@@ -151,6 +153,7 @@ function UnforwardedSelectControl(
|
|
|
151
153
|
*
|
|
152
154
|
* return (
|
|
153
155
|
* <SelectControl
|
|
156
|
+
* __nextHasNoMarginBottom
|
|
154
157
|
* label="Size"
|
|
155
158
|
* value={ size }
|
|
156
159
|
* options={ [
|
|
@@ -164,6 +167,14 @@ function UnforwardedSelectControl(
|
|
|
164
167
|
* };
|
|
165
168
|
* ```
|
|
166
169
|
*/
|
|
167
|
-
export const SelectControl = forwardRef( UnforwardedSelectControl )
|
|
170
|
+
export const SelectControl = forwardRef( UnforwardedSelectControl ) as <
|
|
171
|
+
V extends string,
|
|
172
|
+
>(
|
|
173
|
+
props: WordPressComponentProps<
|
|
174
|
+
SelectControlProps< V >,
|
|
175
|
+
'select',
|
|
176
|
+
false
|
|
177
|
+
> & { ref?: React.Ref< HTMLSelectElement > }
|
|
178
|
+
) => React.JSX.Element | null;
|
|
168
179
|
|
|
169
180
|
export default SelectControl;
|