@okta/odyssey-react-mui 1.8.0 → 1.8.2
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 +10 -0
- package/dist/@types/react-augment.d.js +2 -0
- package/dist/@types/react-augment.d.js.map +1 -0
- package/dist/Autocomplete.js +34 -23
- package/dist/Autocomplete.js.map +1 -1
- package/dist/Checkbox.js +16 -7
- package/dist/Checkbox.js.map +1 -1
- package/dist/Field.js +2 -0
- package/dist/Field.js.map +1 -1
- package/dist/FieldComponentProps.js.map +1 -1
- package/dist/NativeSelect.js +24 -8
- package/dist/NativeSelect.js.map +1 -1
- package/dist/PasswordField.js +21 -5
- package/dist/PasswordField.js.map +1 -1
- package/dist/RadioGroup.js +11 -8
- package/dist/RadioGroup.js.map +1 -1
- package/dist/SearchField.js +19 -16
- package/dist/SearchField.js.map +1 -1
- package/dist/Select.js +36 -18
- package/dist/Select.js.map +1 -1
- package/dist/TextField.js +22 -6
- package/dist/TextField.js.map +1 -1
- package/dist/inputUtils.js +46 -0
- package/dist/inputUtils.js.map +1 -0
- package/dist/labs/VirtualizedAutocomplete.js +29 -23
- package/dist/labs/VirtualizedAutocomplete.js.map +1 -1
- package/dist/src/Autocomplete.d.ts +2 -3
- package/dist/src/Autocomplete.d.ts.map +1 -1
- package/dist/src/Checkbox.d.ts.map +1 -1
- package/dist/src/Field.d.ts +5 -1
- package/dist/src/Field.d.ts.map +1 -1
- package/dist/src/FieldComponentProps.d.ts +4 -0
- package/dist/src/FieldComponentProps.d.ts.map +1 -1
- package/dist/src/NativeSelect.d.ts +19 -44
- package/dist/src/NativeSelect.d.ts.map +1 -1
- package/dist/src/PasswordField.d.ts +10 -2
- package/dist/src/PasswordField.d.ts.map +1 -1
- package/dist/src/RadioGroup.d.ts.map +1 -1
- package/dist/src/SearchField.d.ts +12 -4
- package/dist/src/SearchField.d.ts.map +1 -1
- package/dist/src/Select.d.ts +6 -2
- package/dist/src/Select.d.ts.map +1 -1
- package/dist/src/TextField.d.ts +8 -0
- package/dist/src/TextField.d.ts.map +1 -1
- package/dist/src/inputUtils.d.ts +47 -0
- package/dist/src/inputUtils.d.ts.map +1 -0
- package/dist/src/labs/VirtualizedAutocomplete.d.ts.map +1 -1
- package/dist/tsconfig.production.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/@types/react-augment.d.ts +19 -0
- package/src/Autocomplete.tsx +52 -44
- package/src/Checkbox.tsx +16 -9
- package/src/Field.tsx +6 -0
- package/src/FieldComponentProps.ts +4 -0
- package/src/NativeSelect.tsx +81 -26
- package/src/PasswordField.tsx +34 -4
- package/src/RadioGroup.tsx +12 -10
- package/src/SearchField.tsx +27 -19
- package/src/Select.tsx +52 -26
- package/src/TextField.tsx +35 -5
- package/src/inputUtils.ts +76 -0
- package/src/labs/VirtualizedAutocomplete.tsx +48 -42
- package/dist/src/useControlledState.d.ts +0 -28
- package/dist/src/useControlledState.d.ts.map +0 -1
- package/dist/useControlledState.js +0 -33
- package/dist/useControlledState.js.map +0 -1
- package/src/useControlledState.ts +0 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okta/odyssey-react-mui",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "React MUI components for Odyssey, Okta's design system",
|
|
5
5
|
"author": "Okta, Inc.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@mui/system": "^5.14.9",
|
|
52
52
|
"@mui/utils": "^5.11.2",
|
|
53
53
|
"@mui/x-date-pickers": "^5.0.15",
|
|
54
|
-
"@okta/odyssey-design-tokens": "1.8.
|
|
54
|
+
"@okta/odyssey-design-tokens": "1.8.2",
|
|
55
55
|
"date-fns": "^2.30.0",
|
|
56
56
|
"i18next": "^23.5.1",
|
|
57
57
|
"material-react-table": "^2.0.2",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"react": ">=17 <19",
|
|
64
64
|
"react-dom": ">=17 <19"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "0b0034e9fa97a5dd852f01249aa5093484040ec9"
|
|
67
67
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
|
|
3
|
+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
|
|
4
|
+
*
|
|
5
|
+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
|
6
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
7
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
8
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9
|
+
*
|
|
10
|
+
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { FC } from "react";
|
|
14
|
+
|
|
15
|
+
export interface ForwardRefWithType extends FC<WithForwardRefProps<Option>> {
|
|
16
|
+
<T extends Option>(props: WithForwardRefProps<T>): ReturnType<
|
|
17
|
+
FC<WithForwardRefProps<T>>
|
|
18
|
+
>;
|
|
19
|
+
}
|
package/src/Autocomplete.tsx
CHANGED
|
@@ -17,12 +17,16 @@ import {
|
|
|
17
17
|
UseAutocompleteProps,
|
|
18
18
|
AutocompleteValue,
|
|
19
19
|
} from "@mui/material";
|
|
20
|
-
import { memo, useCallback, useMemo } from "react";
|
|
20
|
+
import { memo, useCallback, useMemo, useRef } from "react";
|
|
21
21
|
|
|
22
22
|
import { Field } from "./Field";
|
|
23
23
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
24
24
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
ComponentControlledState,
|
|
27
|
+
useInputValues,
|
|
28
|
+
getControlState,
|
|
29
|
+
} from "./inputUtils";
|
|
26
30
|
|
|
27
31
|
export type AutocompleteProps<
|
|
28
32
|
OptionType,
|
|
@@ -31,7 +35,6 @@ export type AutocompleteProps<
|
|
|
31
35
|
> = {
|
|
32
36
|
/**
|
|
33
37
|
* The default value. Use when the component is not controlled.
|
|
34
|
-
* @default props.multiple ? [] : null
|
|
35
38
|
*/
|
|
36
39
|
defaultValue?: UseAutocompleteProps<
|
|
37
40
|
OptionType,
|
|
@@ -158,7 +161,7 @@ export type AutocompleteProps<
|
|
|
158
161
|
getIsOptionEqualToValue?: (option: OptionType, value: OptionType) => boolean;
|
|
159
162
|
} & Pick<
|
|
160
163
|
FieldComponentProps,
|
|
161
|
-
"errorMessage" | "hint" | "id" | "isOptional" | "name"
|
|
164
|
+
"errorMessage" | "hint" | "id" | "isOptional" | "name" | "isFullWidth"
|
|
162
165
|
> &
|
|
163
166
|
SeleniumProps;
|
|
164
167
|
|
|
@@ -174,6 +177,7 @@ const Autocomplete = <
|
|
|
174
177
|
inputValue,
|
|
175
178
|
isCustomValueAllowed,
|
|
176
179
|
isDisabled,
|
|
180
|
+
isFullWidth = false,
|
|
177
181
|
isLoading,
|
|
178
182
|
isOptional = false,
|
|
179
183
|
isReadOnly,
|
|
@@ -189,6 +193,45 @@ const Autocomplete = <
|
|
|
189
193
|
getIsOptionEqualToValue,
|
|
190
194
|
testId,
|
|
191
195
|
}: AutocompleteProps<OptionType, HasMultipleChoices, IsCustomValueAllowed>) => {
|
|
196
|
+
const controlledStateRef = useRef(
|
|
197
|
+
getControlState({ controlledValue: value, uncontrolledValue: defaultValue })
|
|
198
|
+
);
|
|
199
|
+
const defaultValueProp = useMemo<
|
|
200
|
+
| AutocompleteValue<
|
|
201
|
+
OptionType,
|
|
202
|
+
HasMultipleChoices,
|
|
203
|
+
undefined,
|
|
204
|
+
IsCustomValueAllowed
|
|
205
|
+
>
|
|
206
|
+
| undefined
|
|
207
|
+
>(() => {
|
|
208
|
+
if (hasMultipleChoices) {
|
|
209
|
+
if (value === undefined) {
|
|
210
|
+
return defaultValue;
|
|
211
|
+
}
|
|
212
|
+
return [] as AutocompleteValue<
|
|
213
|
+
OptionType,
|
|
214
|
+
HasMultipleChoices,
|
|
215
|
+
undefined,
|
|
216
|
+
IsCustomValueAllowed
|
|
217
|
+
>;
|
|
218
|
+
}
|
|
219
|
+
return value === undefined ? defaultValue : undefined;
|
|
220
|
+
}, [defaultValue, hasMultipleChoices, value]);
|
|
221
|
+
|
|
222
|
+
const valueProps = useInputValues({
|
|
223
|
+
defaultValue: defaultValueProp,
|
|
224
|
+
value: value,
|
|
225
|
+
controlState: controlledStateRef.current,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const inputValueProp = useMemo(() => {
|
|
229
|
+
if (controlledStateRef.current === ComponentControlledState.CONTROLLED) {
|
|
230
|
+
return { inputValue };
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
}, [inputValue]);
|
|
234
|
+
|
|
192
235
|
const renderInput = useCallback(
|
|
193
236
|
({ InputLabelProps, InputProps, ...params }) => (
|
|
194
237
|
<Field
|
|
@@ -223,39 +266,6 @@ const Autocomplete = <
|
|
|
223
266
|
),
|
|
224
267
|
[errorMessage, hint, isOptional, label, nameOverride]
|
|
225
268
|
);
|
|
226
|
-
|
|
227
|
-
const defaultValuesProp = useMemo<
|
|
228
|
-
| AutocompleteValue<
|
|
229
|
-
OptionType,
|
|
230
|
-
HasMultipleChoices,
|
|
231
|
-
undefined,
|
|
232
|
-
IsCustomValueAllowed
|
|
233
|
-
>
|
|
234
|
-
| undefined
|
|
235
|
-
>(() => {
|
|
236
|
-
if (hasMultipleChoices) {
|
|
237
|
-
return defaultValue === undefined
|
|
238
|
-
? ([] as AutocompleteValue<
|
|
239
|
-
OptionType,
|
|
240
|
-
HasMultipleChoices,
|
|
241
|
-
undefined,
|
|
242
|
-
IsCustomValueAllowed
|
|
243
|
-
>)
|
|
244
|
-
: defaultValue;
|
|
245
|
-
}
|
|
246
|
-
return defaultValue ?? undefined;
|
|
247
|
-
}, [defaultValue, hasMultipleChoices]);
|
|
248
|
-
|
|
249
|
-
const [localValue, setLocalValue] = useControlledState({
|
|
250
|
-
controlledValue: value,
|
|
251
|
-
uncontrolledValue: defaultValuesProp,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const [localInputValue, setLocalInputValue] = useControlledState({
|
|
255
|
-
controlledValue: inputValue,
|
|
256
|
-
uncontrolledValue: undefined,
|
|
257
|
-
});
|
|
258
|
-
|
|
259
269
|
const onChange = useCallback<
|
|
260
270
|
NonNullable<
|
|
261
271
|
UseAutocompleteProps<
|
|
@@ -267,10 +277,9 @@ const Autocomplete = <
|
|
|
267
277
|
>
|
|
268
278
|
>(
|
|
269
279
|
(event, value, reason, details) => {
|
|
270
|
-
setLocalValue(value);
|
|
271
280
|
onChangeProp?.(event, value, reason, details);
|
|
272
281
|
},
|
|
273
|
-
[onChangeProp
|
|
282
|
+
[onChangeProp]
|
|
274
283
|
);
|
|
275
284
|
|
|
276
285
|
const onInputChange = useCallback<
|
|
@@ -284,23 +293,24 @@ const Autocomplete = <
|
|
|
284
293
|
>
|
|
285
294
|
>(
|
|
286
295
|
(event, value, reason) => {
|
|
287
|
-
setLocalInputValue(value);
|
|
288
296
|
onInputChangeProp?.(event, value, reason);
|
|
289
297
|
},
|
|
290
|
-
[onInputChangeProp
|
|
298
|
+
[onInputChangeProp]
|
|
291
299
|
);
|
|
292
300
|
|
|
293
301
|
return (
|
|
294
302
|
<MuiAutocomplete
|
|
303
|
+
{...valueProps}
|
|
304
|
+
{...inputValueProp}
|
|
295
305
|
// AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div
|
|
296
306
|
aria-disabled={isDisabled}
|
|
297
307
|
data-se={testId}
|
|
298
|
-
defaultValue={defaultValuesProp}
|
|
299
308
|
disableCloseOnSelect={hasMultipleChoices}
|
|
300
309
|
disabled={isDisabled}
|
|
301
310
|
freeSolo={isCustomValueAllowed}
|
|
302
311
|
filterSelectedOptions={true}
|
|
303
312
|
id={idOverride}
|
|
313
|
+
fullWidth={isFullWidth}
|
|
304
314
|
loading={isLoading}
|
|
305
315
|
multiple={hasMultipleChoices}
|
|
306
316
|
onBlur={onBlur}
|
|
@@ -310,8 +320,6 @@ const Autocomplete = <
|
|
|
310
320
|
options={options}
|
|
311
321
|
readOnly={isReadOnly}
|
|
312
322
|
renderInput={renderInput}
|
|
313
|
-
value={localValue}
|
|
314
|
-
inputValue={localInputValue}
|
|
315
323
|
isOptionEqualToValue={getIsOptionEqualToValue}
|
|
316
324
|
/>
|
|
317
325
|
);
|
package/src/Checkbox.tsx
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { useTranslation } from "react-i18next";
|
|
14
|
-
import { memo, useCallback, useMemo } from "react";
|
|
14
|
+
import { memo, useCallback, useMemo, useRef } from "react";
|
|
15
15
|
import {
|
|
16
16
|
Checkbox as MuiCheckbox,
|
|
17
17
|
CheckboxProps as MuiCheckboxProps,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
23
23
|
import { Typography } from "./Typography";
|
|
24
24
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
25
|
-
import {
|
|
25
|
+
import { ComponentControlledState, getControlState } from "./inputUtils";
|
|
26
26
|
import { CheckedFieldProps } from "./FormCheckedProps";
|
|
27
27
|
|
|
28
28
|
export const checkboxValidityValues = ["valid", "invalid", "inherit"] as const;
|
|
@@ -90,10 +90,18 @@ const Checkbox = ({
|
|
|
90
90
|
value,
|
|
91
91
|
}: CheckboxProps) => {
|
|
92
92
|
const { t } = useTranslation();
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const controlledStateRef = useRef(
|
|
94
|
+
getControlState({
|
|
95
|
+
controlledValue: isChecked,
|
|
96
|
+
uncontrolledValue: isDefaultChecked,
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
const inputValues = useMemo(() => {
|
|
100
|
+
if (controlledStateRef.current === ComponentControlledState.CONTROLLED) {
|
|
101
|
+
return { checked: isChecked };
|
|
102
|
+
}
|
|
103
|
+
return { defaultChecked: isDefaultChecked };
|
|
104
|
+
}, [isDefaultChecked, isChecked]);
|
|
97
105
|
|
|
98
106
|
const label = useMemo(() => {
|
|
99
107
|
return (
|
|
@@ -114,10 +122,9 @@ const Checkbox = ({
|
|
|
114
122
|
|
|
115
123
|
const onChange = useCallback<NonNullable<MuiCheckboxProps["onChange"]>>(
|
|
116
124
|
(event, checked) => {
|
|
117
|
-
setIsLocalChecked(checked);
|
|
118
125
|
onChangeProp?.(event, checked);
|
|
119
126
|
},
|
|
120
|
-
[onChangeProp
|
|
127
|
+
[onChangeProp]
|
|
121
128
|
);
|
|
122
129
|
|
|
123
130
|
return (
|
|
@@ -134,7 +141,7 @@ const Checkbox = ({
|
|
|
134
141
|
}
|
|
135
142
|
control={
|
|
136
143
|
<MuiCheckbox
|
|
137
|
-
|
|
144
|
+
{...inputValues}
|
|
138
145
|
indeterminate={isIndeterminate}
|
|
139
146
|
onChange={onChange}
|
|
140
147
|
required={isRequired}
|
package/src/Field.tsx
CHANGED
|
@@ -59,6 +59,10 @@ export type FieldProps = {
|
|
|
59
59
|
* If `true`, the component is disabled.
|
|
60
60
|
*/
|
|
61
61
|
isDisabled?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* If `true`, the component can stretch to fill the width of the container.
|
|
64
|
+
*/
|
|
65
|
+
isFullWidth?: boolean;
|
|
62
66
|
/**
|
|
63
67
|
* If `true`, the `input` element is not required.
|
|
64
68
|
*/
|
|
@@ -96,6 +100,7 @@ const Field = ({
|
|
|
96
100
|
hint,
|
|
97
101
|
id: idOverride,
|
|
98
102
|
isDisabled: isDisabledProp = false,
|
|
103
|
+
isFullWidth = false,
|
|
99
104
|
isRadioGroup = false,
|
|
100
105
|
isOptional = false,
|
|
101
106
|
label,
|
|
@@ -126,6 +131,7 @@ const Field = ({
|
|
|
126
131
|
disabled={isDisabled}
|
|
127
132
|
error={Boolean(errorMessage)}
|
|
128
133
|
role={isRadioGroup ? "radiogroup" : undefined}
|
|
134
|
+
fullWidth={isFullWidth}
|
|
129
135
|
>
|
|
130
136
|
{fieldType === "group" ? (
|
|
131
137
|
<MuiFormLabel component="legend">
|
|
@@ -27,6 +27,10 @@ export type FieldComponentProps = {
|
|
|
27
27
|
* If `true`, the component is disabled.
|
|
28
28
|
*/
|
|
29
29
|
isDisabled?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* If `true`, the component can stretch to fill the width of the container.
|
|
32
|
+
*/
|
|
33
|
+
isFullWidth?: boolean;
|
|
30
34
|
/**
|
|
31
35
|
* If `true`, the `input` element is not required.
|
|
32
36
|
*/
|
package/src/NativeSelect.tsx
CHANGED
|
@@ -10,12 +10,23 @@
|
|
|
10
10
|
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
import React, {
|
|
14
|
+
ReactElement,
|
|
15
|
+
forwardRef,
|
|
16
|
+
memo,
|
|
17
|
+
useCallback,
|
|
18
|
+
useMemo,
|
|
19
|
+
useRef,
|
|
20
|
+
} from "react";
|
|
21
|
+
import {
|
|
22
|
+
Select as MuiSelect,
|
|
23
|
+
SelectProps as MuiSelectProps,
|
|
24
|
+
} from "@mui/material";
|
|
16
25
|
import { Field } from "./Field";
|
|
17
26
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
18
27
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
28
|
+
import { getControlState, useInputValues } from "./inputUtils";
|
|
29
|
+
import { ForwardRefWithType } from "./@types/react-augment";
|
|
19
30
|
|
|
20
31
|
export type NativeSelectOption = {
|
|
21
32
|
text: string;
|
|
@@ -23,19 +34,30 @@ export type NativeSelectOption = {
|
|
|
23
34
|
type?: "heading" | "option";
|
|
24
35
|
};
|
|
25
36
|
|
|
26
|
-
export type
|
|
37
|
+
export type NativeSelectValueType<HasMultipleChoices> =
|
|
38
|
+
HasMultipleChoices extends true ? string[] : string;
|
|
39
|
+
|
|
40
|
+
export type NativeSelectProps<
|
|
41
|
+
Value extends NativeSelectValueType<HasMultipleChoices>,
|
|
42
|
+
HasMultipleChoices extends boolean
|
|
43
|
+
> = {
|
|
27
44
|
/**
|
|
28
45
|
* The options or optgroup elements within the NativeSelect
|
|
29
46
|
*/
|
|
30
47
|
children?: ReactElement<"option"> | ReactElement<"optgroup">;
|
|
31
48
|
/**
|
|
32
|
-
* The default value of the NativeSelect.
|
|
49
|
+
* The default value of the NativeSelect. Use when component is uncontrolled
|
|
50
|
+
*/
|
|
51
|
+
defaultValue?: Value;
|
|
52
|
+
/**
|
|
53
|
+
* If `true`, the Select allows multiple selections
|
|
33
54
|
*/
|
|
34
|
-
|
|
55
|
+
hasMultipleChoices?: HasMultipleChoices;
|
|
35
56
|
/**
|
|
36
|
-
*
|
|
57
|
+
* @deprecated Use `hasMultipleChoices` instead
|
|
37
58
|
*/
|
|
38
|
-
|
|
59
|
+
/** **Deprecated:** use `hasMultipleChoices` */
|
|
60
|
+
isMultiSelect?: HasMultipleChoices;
|
|
39
61
|
/**
|
|
40
62
|
* The label text for the NativeSelect
|
|
41
63
|
*/
|
|
@@ -43,78 +65,110 @@ export type NativeSelectProps = {
|
|
|
43
65
|
/**
|
|
44
66
|
* Callback fired when the NativeSelect loses focus
|
|
45
67
|
*/
|
|
46
|
-
onBlur?: MuiSelectProps["onBlur"];
|
|
68
|
+
onBlur?: MuiSelectProps<Value>["onBlur"];
|
|
47
69
|
/**
|
|
48
70
|
* Callback fired when the value of the NativeSelect changes
|
|
49
71
|
*/
|
|
50
|
-
onChange?: MuiSelectProps["onChange"];
|
|
72
|
+
onChange?: MuiSelectProps<Value>["onChange"];
|
|
51
73
|
/**
|
|
52
74
|
* Callback fired when the NativeSelect gains focus
|
|
53
75
|
*/
|
|
54
|
-
onFocus?: MuiSelectProps["onFocus"];
|
|
76
|
+
onFocus?: MuiSelectProps<Value>["onFocus"];
|
|
77
|
+
options: Value;
|
|
55
78
|
/**
|
|
56
|
-
* The value or values selected in the NativeSelect
|
|
79
|
+
* The value or values selected in the NativeSelect. Use when component is controlled
|
|
57
80
|
*/
|
|
58
|
-
value?:
|
|
81
|
+
value?: Value;
|
|
59
82
|
} & Pick<
|
|
60
83
|
FieldComponentProps,
|
|
61
|
-
"errorMessage" | "hint" | "id" | "isDisabled" | "isOptional"
|
|
84
|
+
"errorMessage" | "hint" | "id" | "isDisabled" | "isOptional" | "isFullWidth"
|
|
62
85
|
> &
|
|
63
86
|
SeleniumProps;
|
|
64
87
|
|
|
65
|
-
const NativeSelect = forwardRef
|
|
66
|
-
|
|
88
|
+
const NativeSelect: ForwardRefWithType = forwardRef(
|
|
89
|
+
<
|
|
90
|
+
Value extends NativeSelectValueType<HasMultipleChoices>,
|
|
91
|
+
HasMultipleChoices extends boolean
|
|
92
|
+
>(
|
|
67
93
|
{
|
|
68
94
|
defaultValue,
|
|
69
95
|
errorMessage,
|
|
96
|
+
hasMultipleChoices: hasMultipleChoicesProp,
|
|
70
97
|
hint,
|
|
71
98
|
id: idOverride,
|
|
72
99
|
isDisabled = false,
|
|
73
|
-
|
|
100
|
+
isFullWidth = false,
|
|
101
|
+
isMultiSelect,
|
|
74
102
|
isOptional = false,
|
|
75
103
|
label,
|
|
76
104
|
onBlur,
|
|
77
|
-
onChange,
|
|
105
|
+
onChange: onChangeProp,
|
|
78
106
|
onFocus,
|
|
79
107
|
testId,
|
|
80
108
|
value,
|
|
81
109
|
children,
|
|
82
|
-
},
|
|
83
|
-
ref
|
|
110
|
+
}: NativeSelectProps<Value, HasMultipleChoices>,
|
|
111
|
+
ref?: React.Ref<ReactElement>
|
|
84
112
|
) => {
|
|
113
|
+
const controlledStateRef = useRef(
|
|
114
|
+
getControlState({
|
|
115
|
+
controlledValue: value,
|
|
116
|
+
uncontrolledValue: defaultValue,
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
const inputValues = useInputValues({
|
|
120
|
+
defaultValue,
|
|
121
|
+
value,
|
|
122
|
+
controlState: controlledStateRef.current,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const onChange = useCallback<
|
|
126
|
+
NonNullable<MuiSelectProps<Value>["onChange"]>
|
|
127
|
+
>(
|
|
128
|
+
(event, child) => {
|
|
129
|
+
onChangeProp?.(event, child);
|
|
130
|
+
},
|
|
131
|
+
[onChangeProp]
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const hasMultipleChoices = useMemo(
|
|
135
|
+
() =>
|
|
136
|
+
hasMultipleChoicesProp === undefined
|
|
137
|
+
? isMultiSelect
|
|
138
|
+
: hasMultipleChoicesProp,
|
|
139
|
+
[hasMultipleChoicesProp, isMultiSelect]
|
|
140
|
+
);
|
|
85
141
|
const renderFieldComponent = useCallback(
|
|
86
142
|
({ ariaDescribedBy, errorMessageElementId, labelElementId }) => (
|
|
87
143
|
<MuiSelect
|
|
144
|
+
{...inputValues}
|
|
88
145
|
aria-describedby={ariaDescribedBy}
|
|
89
146
|
children={children}
|
|
90
147
|
data-se={testId}
|
|
91
|
-
defaultValue={defaultValue}
|
|
92
148
|
id={idOverride}
|
|
93
149
|
inputProps={{
|
|
94
150
|
"aria-errormessage": errorMessageElementId,
|
|
95
151
|
"aria-labelledby": labelElementId,
|
|
96
152
|
}}
|
|
97
153
|
name={idOverride}
|
|
98
|
-
multiple={
|
|
154
|
+
multiple={hasMultipleChoices}
|
|
99
155
|
native={true}
|
|
100
156
|
onBlur={onBlur}
|
|
101
157
|
onChange={onChange}
|
|
102
158
|
onFocus={onFocus}
|
|
103
159
|
ref={ref}
|
|
104
|
-
value={value}
|
|
105
160
|
/>
|
|
106
161
|
),
|
|
107
162
|
[
|
|
108
163
|
children,
|
|
109
|
-
defaultValue,
|
|
110
164
|
idOverride,
|
|
111
|
-
|
|
165
|
+
inputValues,
|
|
166
|
+
hasMultipleChoices,
|
|
112
167
|
onBlur,
|
|
113
168
|
onChange,
|
|
114
169
|
onFocus,
|
|
115
170
|
ref,
|
|
116
171
|
testId,
|
|
117
|
-
value,
|
|
118
172
|
]
|
|
119
173
|
);
|
|
120
174
|
|
|
@@ -126,6 +180,7 @@ const NativeSelect = forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
|
126
180
|
hint={hint}
|
|
127
181
|
id={idOverride}
|
|
128
182
|
isDisabled={isDisabled}
|
|
183
|
+
isFullWidth={isFullWidth}
|
|
129
184
|
isOptional={isOptional}
|
|
130
185
|
label={label}
|
|
131
186
|
renderFieldComponent={renderFieldComponent}
|
package/src/PasswordField.tsx
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
forwardRef,
|
|
18
18
|
memo,
|
|
19
19
|
useCallback,
|
|
20
|
+
useRef,
|
|
20
21
|
useState,
|
|
21
22
|
} from "react";
|
|
22
23
|
|
|
@@ -25,6 +26,7 @@ import { Field } from "./Field";
|
|
|
25
26
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
26
27
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
27
28
|
import { useTranslation } from "react-i18next";
|
|
29
|
+
import { getControlState, useInputValues } from "./inputUtils";
|
|
28
30
|
|
|
29
31
|
export type PasswordFieldProps = {
|
|
30
32
|
/**
|
|
@@ -33,6 +35,10 @@ export type PasswordFieldProps = {
|
|
|
33
35
|
* You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
|
|
34
36
|
*/
|
|
35
37
|
autoCompleteType?: "current-password" | "new-password";
|
|
38
|
+
/**
|
|
39
|
+
* initial value for input. Use when component in uncontrolled.
|
|
40
|
+
*/
|
|
41
|
+
defaultValue?: string;
|
|
36
42
|
/**
|
|
37
43
|
* If `true`, the component will receive focus automatically.
|
|
38
44
|
*/
|
|
@@ -62,7 +68,7 @@ export type PasswordFieldProps = {
|
|
|
62
68
|
*/
|
|
63
69
|
placeholder?: string;
|
|
64
70
|
/**
|
|
65
|
-
* The value of the `input` element
|
|
71
|
+
* The value of the `input` element. Use when component is controlled.
|
|
66
72
|
*/
|
|
67
73
|
value?: string;
|
|
68
74
|
} & FieldComponentProps &
|
|
@@ -72,17 +78,19 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
72
78
|
(
|
|
73
79
|
{
|
|
74
80
|
autoCompleteType,
|
|
81
|
+
defaultValue,
|
|
75
82
|
errorMessage,
|
|
76
83
|
hasInitialFocus,
|
|
77
84
|
hint,
|
|
78
85
|
id: idOverride,
|
|
79
86
|
isDisabled = false,
|
|
87
|
+
isFullWidth = false,
|
|
80
88
|
isOptional = false,
|
|
81
89
|
hasShowPassword = true,
|
|
82
90
|
isReadOnly,
|
|
83
91
|
label,
|
|
84
92
|
name: nameOverride,
|
|
85
|
-
onChange,
|
|
93
|
+
onChange: onChangeProp,
|
|
86
94
|
onFocus,
|
|
87
95
|
onBlur,
|
|
88
96
|
placeholder,
|
|
@@ -100,9 +108,31 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
100
108
|
);
|
|
101
109
|
}, []);
|
|
102
110
|
|
|
111
|
+
const controlledStateRef = useRef(
|
|
112
|
+
getControlState({
|
|
113
|
+
controlledValue: value,
|
|
114
|
+
uncontrolledValue: defaultValue,
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
const inputValues = useInputValues({
|
|
118
|
+
defaultValue,
|
|
119
|
+
value,
|
|
120
|
+
controlState: controlledStateRef.current,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const onChange = useCallback<
|
|
124
|
+
ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>
|
|
125
|
+
>(
|
|
126
|
+
(event) => {
|
|
127
|
+
onChangeProp?.(event);
|
|
128
|
+
},
|
|
129
|
+
[onChangeProp]
|
|
130
|
+
);
|
|
131
|
+
|
|
103
132
|
const renderFieldComponent = useCallback(
|
|
104
133
|
({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
|
|
105
134
|
<InputBase
|
|
135
|
+
{...inputValues}
|
|
106
136
|
aria-describedby={ariaDescribedBy}
|
|
107
137
|
autoComplete={inputType === "password" ? autoCompleteType : "off"}
|
|
108
138
|
/* eslint-disable-next-line jsx-a11y/no-autofocus */
|
|
@@ -140,12 +170,12 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
140
170
|
ref={ref}
|
|
141
171
|
required={!isOptional}
|
|
142
172
|
type={inputType}
|
|
143
|
-
value={value}
|
|
144
173
|
/>
|
|
145
174
|
),
|
|
146
175
|
[
|
|
147
176
|
autoCompleteType,
|
|
148
177
|
hasInitialFocus,
|
|
178
|
+
inputValues,
|
|
149
179
|
t,
|
|
150
180
|
togglePasswordVisibility,
|
|
151
181
|
inputType,
|
|
@@ -159,7 +189,6 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
159
189
|
hasShowPassword,
|
|
160
190
|
ref,
|
|
161
191
|
testId,
|
|
162
|
-
value,
|
|
163
192
|
]
|
|
164
193
|
);
|
|
165
194
|
|
|
@@ -171,6 +200,7 @@ const PasswordField = forwardRef<HTMLInputElement, PasswordFieldProps>(
|
|
|
171
200
|
hint={hint}
|
|
172
201
|
id={idOverride}
|
|
173
202
|
isDisabled={isDisabled}
|
|
203
|
+
isFullWidth={isFullWidth}
|
|
174
204
|
isOptional={isOptional}
|
|
175
205
|
label={label}
|
|
176
206
|
renderFieldComponent={renderFieldComponent}
|
package/src/RadioGroup.tsx
CHANGED
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
RadioGroup as MuiRadioGroup,
|
|
15
15
|
type RadioGroupProps as MuiRadioGroupProps,
|
|
16
16
|
} from "@mui/material";
|
|
17
|
-
import { memo, ReactElement, useCallback } from "react";
|
|
17
|
+
import { memo, ReactElement, useCallback, useRef } from "react";
|
|
18
18
|
|
|
19
19
|
import { Radio, RadioProps } from "./Radio";
|
|
20
20
|
import { Field } from "./Field";
|
|
21
21
|
import { FieldComponentProps } from "./FieldComponentProps";
|
|
22
22
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
23
|
-
import {
|
|
23
|
+
import { getControlState, useInputValues } from "./inputUtils";
|
|
24
24
|
|
|
25
25
|
export type RadioGroupProps = {
|
|
26
26
|
/**
|
|
@@ -62,35 +62,37 @@ const RadioGroup = ({
|
|
|
62
62
|
testId,
|
|
63
63
|
value,
|
|
64
64
|
}: RadioGroupProps) => {
|
|
65
|
-
const
|
|
66
|
-
controlledValue: value,
|
|
67
|
-
|
|
65
|
+
const controlledStateRef = useRef(
|
|
66
|
+
getControlState({ controlledValue: value, uncontrolledValue: defaultValue })
|
|
67
|
+
);
|
|
68
|
+
const inputValues = useInputValues({
|
|
69
|
+
defaultValue,
|
|
70
|
+
value,
|
|
71
|
+
controlState: controlledStateRef.current,
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
const onChange = useCallback<NonNullable<MuiRadioGroupProps["onChange"]>>(
|
|
71
75
|
(event, value) => {
|
|
72
|
-
setLocalValue(value);
|
|
73
76
|
onChangeProp?.(event, value);
|
|
74
77
|
},
|
|
75
|
-
[onChangeProp
|
|
78
|
+
[onChangeProp]
|
|
76
79
|
);
|
|
77
80
|
const renderFieldComponent = useCallback(
|
|
78
81
|
({ ariaDescribedBy, errorMessageElementId, id, labelElementId }) => (
|
|
79
82
|
<MuiRadioGroup
|
|
83
|
+
{...inputValues}
|
|
80
84
|
aria-describedby={ariaDescribedBy}
|
|
81
85
|
aria-errormessage={errorMessageElementId}
|
|
82
86
|
aria-labelledby={labelElementId}
|
|
83
87
|
data-se={testId}
|
|
84
|
-
defaultValue={defaultValue}
|
|
85
88
|
id={id}
|
|
86
89
|
name={nameOverride ?? id}
|
|
87
90
|
onChange={onChange}
|
|
88
|
-
value={localValue}
|
|
89
91
|
>
|
|
90
92
|
{children}
|
|
91
93
|
</MuiRadioGroup>
|
|
92
94
|
),
|
|
93
|
-
[children,
|
|
95
|
+
[children, inputValues, nameOverride, onChange, testId]
|
|
94
96
|
);
|
|
95
97
|
|
|
96
98
|
return (
|