From 04c5a8ba95031e9637cb085e70c238d86af4ce9d Mon Sep 17 00:00:00 2001 From: Trevor Ing Date: Mon, 22 Jan 2024 14:40:45 -0500 Subject: [PATCH 1/5] fix: move testId to inputProps for all input components --- .../src/@types/react-augment.d.ts | 14 +++++++++----- packages/odyssey-react-mui/src/Autocomplete.tsx | 3 ++- packages/odyssey-react-mui/src/Button.tsx | 2 +- packages/odyssey-react-mui/src/Checkbox.tsx | 11 ++++++++--- packages/odyssey-react-mui/src/Link.tsx | 2 +- packages/odyssey-react-mui/src/NativeSelect.tsx | 2 +- packages/odyssey-react-mui/src/PasswordField.tsx | 5 ++--- packages/odyssey-react-mui/src/Radio.tsx | 13 ++++++++++--- packages/odyssey-react-mui/src/Select.tsx | 4 ++-- packages/odyssey-react-mui/src/TextField.tsx | 5 ++--- packages/odyssey-react-mui/src/Typography.tsx | 2 +- packages/odyssey-react-mui/src/inputUtils.ts | 4 ++++ packages/odyssey-react-mui/src/labs/Switch.tsx | 3 ++- .../src/labs/VirtualizedAutocomplete.tsx | 4 ++-- 14 files changed, 47 insertions(+), 27 deletions(-) diff --git a/packages/odyssey-react-mui/src/@types/react-augment.d.ts b/packages/odyssey-react-mui/src/@types/react-augment.d.ts index 1dc10d7a59..7afa4f58fc 100644 --- a/packages/odyssey-react-mui/src/@types/react-augment.d.ts +++ b/packages/odyssey-react-mui/src/@types/react-augment.d.ts @@ -10,14 +10,18 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { FC } from "react"; - +import "react"; export interface ForwardRefWithType extends FC> { (props: WithForwardRefProps): ReturnType< FC> >; } -export type FocusHandle = { - focus: () => void; -}; +type DataAttributeKey = `data-${string}`; +declare module "react" { + interface HTMLAttributes { + // Allows data-* props to be passed to inputProps in nested MUI components + // see: https://github.com/mui/material-ui/issues/20160 + [dataAttribute: DataAttributeKey]: string | undefined; + } +} diff --git a/packages/odyssey-react-mui/src/Autocomplete.tsx b/packages/odyssey-react-mui/src/Autocomplete.tsx index 5a936a699a..0c2d1b5a23 100644 --- a/packages/odyssey-react-mui/src/Autocomplete.tsx +++ b/packages/odyssey-react-mui/src/Autocomplete.tsx @@ -267,6 +267,7 @@ const Autocomplete = < ...params.inputProps, "aria-errormessage": errorMessageElementId, "aria-labelledby": labelElementId, + "data-se": testId, }} aria-describedby={ariaDescribedBy} id={id} @@ -284,6 +285,7 @@ const Autocomplete = < isOptional, label, nameOverride, + testId, ] ); const onChange = useCallback< @@ -324,7 +326,6 @@ const Autocomplete = < {...inputValueProp} // AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div aria-disabled={isDisabled} - data-se={testId} disableCloseOnSelect={hasMultipleChoices} disabled={isDisabled} freeSolo={isCustomValueAllowed} diff --git a/packages/odyssey-react-mui/src/Button.tsx b/packages/odyssey-react-mui/src/Button.tsx index fa2978037a..a0cf63b65b 100644 --- a/packages/odyssey-react-mui/src/Button.tsx +++ b/packages/odyssey-react-mui/src/Button.tsx @@ -23,7 +23,7 @@ import { import { MuiPropsContext, useMuiProps } from "./MuiPropsContext"; import { Tooltip } from "./Tooltip"; import type { AllowedProps } from "./AllowedProps"; -import { FocusHandle } from "./@types/react-augment"; +import { FocusHandle } from "./inputUtils"; export const buttonSizeValues = ["small", "medium", "large"] as const; export const buttonTypeValues = ["button", "submit", "reset"] as const; diff --git a/packages/odyssey-react-mui/src/Checkbox.tsx b/packages/odyssey-react-mui/src/Checkbox.tsx index e76a342a78..078f2f2e4c 100644 --- a/packages/odyssey-react-mui/src/Checkbox.tsx +++ b/packages/odyssey-react-mui/src/Checkbox.tsx @@ -23,9 +23,12 @@ import { import { FieldComponentProps } from "./FieldComponentProps"; import { Typography } from "./Typography"; import type { AllowedProps } from "./AllowedProps"; -import { ComponentControlledState, getControlState } from "./inputUtils"; +import { + ComponentControlledState, + FocusHandle, + getControlState, +} from "./inputUtils"; import { CheckedFieldProps } from "./FormCheckedProps"; -import { FocusHandle } from "./@types/react-augment"; export const checkboxValidityValues = ["valid", "invalid", "inherit"] as const; @@ -179,13 +182,15 @@ const Checkbox = ({ indeterminate={isIndeterminate} onChange={onChange} required={isRequired} + inputProps={{ + "data-se": testId, + }} inputRef={inputRef} sx={() => ({ marginBlockStart: "2px", })} /> } - data-se={testId} disabled={isDisabled} id={idOverride} label={label} diff --git a/packages/odyssey-react-mui/src/Link.tsx b/packages/odyssey-react-mui/src/Link.tsx index 5f8b22403a..cf33616cf2 100644 --- a/packages/odyssey-react-mui/src/Link.tsx +++ b/packages/odyssey-react-mui/src/Link.tsx @@ -13,9 +13,9 @@ import { memo, ReactElement, useImperativeHandle, useRef } from "react"; import { ExternalLinkIcon } from "./icons.generated"; import type { AllowedProps } from "./AllowedProps"; +import { FocusHandle } from "./inputUtils"; import { Link as MuiLink, LinkProps as MuiLinkProps } from "@mui/material"; -import { FocusHandle } from "./@types/react-augment"; export const linkVariantValues = ["default", "monochrome"] as const; diff --git a/packages/odyssey-react-mui/src/NativeSelect.tsx b/packages/odyssey-react-mui/src/NativeSelect.tsx index 78251fb52d..fda5c62e98 100644 --- a/packages/odyssey-react-mui/src/NativeSelect.tsx +++ b/packages/odyssey-react-mui/src/NativeSelect.tsx @@ -154,11 +154,11 @@ const NativeSelect: ForwardRefWithType = forwardRef( {...inputValues} aria-describedby={ariaDescribedBy} children={children} - data-se={testId} id={idOverride} inputProps={{ "aria-errormessage": errorMessageElementId, "aria-labelledby": labelElementId, + "data-se": testId, }} name={idOverride} multiple={hasMultipleChoices} diff --git a/packages/odyssey-react-mui/src/PasswordField.tsx b/packages/odyssey-react-mui/src/PasswordField.tsx index b459a838ca..b010ee58ef 100644 --- a/packages/odyssey-react-mui/src/PasswordField.tsx +++ b/packages/odyssey-react-mui/src/PasswordField.tsx @@ -27,8 +27,7 @@ import { Field } from "./Field"; import { FieldComponentProps } from "./FieldComponentProps"; import type { AllowedProps } from "./AllowedProps"; import { useTranslation } from "react-i18next"; -import { getControlState, useInputValues } from "./inputUtils"; -import { FocusHandle } from "./@types/react-augment"; +import { FocusHandle, getControlState, useInputValues } from "./inputUtils"; export type PasswordFieldProps = { /** @@ -160,7 +159,6 @@ const PasswordField = forwardRef( autoComplete={inputType === "password" ? autoCompleteType : "off"} /* eslint-disable-next-line jsx-a11y/no-autofocus */ autoFocus={hasInitialFocus} - data-se={testId} endAdornment={ hasShowPassword && ( @@ -181,6 +179,7 @@ const PasswordField = forwardRef( inputProps={{ "aria-errormessage": errorMessageElementId, "aria-labelledby": labelElementId, + "data-se": testId, // role: "textbox" Added because password inputs don't have an implicit role assigned. This causes problems with element selection. role: "textbox", }} diff --git a/packages/odyssey-react-mui/src/Radio.tsx b/packages/odyssey-react-mui/src/Radio.tsx index 4dc7994821..1f32f041a4 100644 --- a/packages/odyssey-react-mui/src/Radio.tsx +++ b/packages/odyssey-react-mui/src/Radio.tsx @@ -20,7 +20,7 @@ import { memo, useCallback, useRef, useImperativeHandle } from "react"; import { FieldComponentProps } from "./FieldComponentProps"; import type { AllowedProps } from "./AllowedProps"; -import { FocusHandle } from "./@types/react-augment"; +import { FocusHandle } from "./inputUtils"; export type RadioProps = { /** @@ -99,8 +99,15 @@ const Radio = ({ } - data-se={testId} + control={ + + } disabled={isDisabled} label={label} name={name} diff --git a/packages/odyssey-react-mui/src/Select.tsx b/packages/odyssey-react-mui/src/Select.tsx index 98d9ddce97..36736a3c24 100644 --- a/packages/odyssey-react-mui/src/Select.tsx +++ b/packages/odyssey-react-mui/src/Select.tsx @@ -36,10 +36,10 @@ import { CheckIcon } from "./icons.generated"; import type { AllowedProps } from "./AllowedProps"; import { ComponentControlledState, + FocusHandle, useInputValues, getControlState, } from "./inputUtils"; -import { FocusHandle } from "./@types/react-augment"; export type SelectOption = { text: string; @@ -290,8 +290,8 @@ const Select = < aria-describedby={ariaDescribedBy} aria-errormessage={errorMessageElementId} children={children} - data-se={testId} id={id} + inputProps={{ "data-se": testId }} inputRef={inputRef} labelId={labelElementId} multiple={hasMultipleChoices} diff --git a/packages/odyssey-react-mui/src/TextField.tsx b/packages/odyssey-react-mui/src/TextField.tsx index 65c0e70db2..4476272c02 100644 --- a/packages/odyssey-react-mui/src/TextField.tsx +++ b/packages/odyssey-react-mui/src/TextField.tsx @@ -26,8 +26,7 @@ import { InputAdornment, InputBase } from "@mui/material"; import { FieldComponentProps } from "./FieldComponentProps"; import { Field } from "./Field"; import { AllowedProps } from "./AllowedProps"; -import { useInputValues, getControlState } from "./inputUtils"; -import { FocusHandle } from "./@types/react-augment"; +import { FocusHandle, useInputValues, getControlState } from "./inputUtils"; export const textFieldTypeValues = [ "email", @@ -180,7 +179,6 @@ const TextField = forwardRef( autoComplete={autoCompleteType} /* eslint-disable-next-line jsx-a11y/no-autofocus */ autoFocus={hasInitialFocus} - data-se={testId} endAdornment={ endAdornment && ( @@ -192,6 +190,7 @@ const TextField = forwardRef( inputProps={{ "aria-errormessage": errorMessageElementId, "aria-labelledby": labelElementId, + "data-se": testId, inputmode: inputMode, }} inputRef={inputRef} diff --git a/packages/odyssey-react-mui/src/Typography.tsx b/packages/odyssey-react-mui/src/Typography.tsx index d174d39664..7f2a427b1e 100644 --- a/packages/odyssey-react-mui/src/Typography.tsx +++ b/packages/odyssey-react-mui/src/Typography.tsx @@ -23,7 +23,7 @@ import { useImperativeHandle, } from "react"; import { AllowedProps } from "./AllowedProps"; -import { FocusHandle } from "./@types/react-augment"; +import { FocusHandle } from "./inputUtils"; export type TypographyVariantValue = | "h1" diff --git a/packages/odyssey-react-mui/src/inputUtils.ts b/packages/odyssey-react-mui/src/inputUtils.ts index fc30b3539d..c336b78178 100644 --- a/packages/odyssey-react-mui/src/inputUtils.ts +++ b/packages/odyssey-react-mui/src/inputUtils.ts @@ -12,6 +12,10 @@ import { useMemo } from "react"; +export type FocusHandle = { + focus: () => void; +}; + type UseControlledStateProps = { controlledValue?: Value; uncontrolledValue?: Value; diff --git a/packages/odyssey-react-mui/src/labs/Switch.tsx b/packages/odyssey-react-mui/src/labs/Switch.tsx index b3aced16e1..7f4817655c 100644 --- a/packages/odyssey-react-mui/src/labs/Switch.tsx +++ b/packages/odyssey-react-mui/src/labs/Switch.tsx @@ -186,6 +186,7 @@ const Switch = ({ "aria-describedby": hintId, "aria-label": label, "aria-labelledby": labelElementId, + "data-se": testId, }} name={_name ?? id} onChange={handleOnChange} @@ -201,6 +202,7 @@ const Switch = ({ label, labelElementId, _name, + testId, ] ); @@ -213,7 +215,6 @@ const Switch = ({ ), - [errorMessage, errorMessageList, hint, isOptional, label, nameOverride] + [errorMessage, errorMessageList, hint, isOptional, label, nameOverride, testId] ); const onChange = useCallback< NonNullable< @@ -313,7 +314,6 @@ const VirtualizedAutocomplete = < {...inputValueProp} // AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div aria-disabled={isDisabled} - data-se={testId} disableCloseOnSelect={hasMultipleChoices} disabled={isDisabled} freeSolo={isCustomValueAllowed} From eaba3bfedf7bc3dfc3647da938f16dbdb50b2c82 Mon Sep 17 00:00:00 2001 From: Trevor Ing Date: Fri, 26 Jan 2024 00:12:57 -0500 Subject: [PATCH 2/5] fix: build issue --- packages/odyssey-react-mui/src/@types/react-augment.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/odyssey-react-mui/src/@types/react-augment.d.ts b/packages/odyssey-react-mui/src/@types/react-augment.d.ts index 7afa4f58fc..6f02babc51 100644 --- a/packages/odyssey-react-mui/src/@types/react-augment.d.ts +++ b/packages/odyssey-react-mui/src/@types/react-augment.d.ts @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import "react"; +import { FC } from "react"; export interface ForwardRefWithType extends FC> { (props: WithForwardRefProps): ReturnType< FC> @@ -19,7 +19,7 @@ export interface ForwardRefWithType extends FC> { type DataAttributeKey = `data-${string}`; declare module "react" { - interface HTMLAttributes { + interface InputHTMLAttributes extends HTMLAttributes { // Allows data-* props to be passed to inputProps in nested MUI components // see: https://github.com/mui/material-ui/issues/20160 [dataAttribute: DataAttributeKey]: string | undefined; From f954b6cec900f10b120868ccd8beaa3e6e5eae59 Mon Sep 17 00:00:00 2001 From: Trevor Ing Date: Sat, 27 Jan 2024 01:40:37 -0500 Subject: [PATCH 3/5] fix: add support for autocomplete and inputref in NativeSelect --- .../odyssey-react-mui/src/NativeSelect.tsx | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/odyssey-react-mui/src/NativeSelect.tsx b/packages/odyssey-react-mui/src/NativeSelect.tsx index fda5c62e98..da00b65819 100644 --- a/packages/odyssey-react-mui/src/NativeSelect.tsx +++ b/packages/odyssey-react-mui/src/NativeSelect.tsx @@ -11,10 +11,12 @@ */ import React, { + InputHTMLAttributes, ReactElement, forwardRef, memo, useCallback, + useImperativeHandle, useMemo, useRef, } from "react"; @@ -25,7 +27,7 @@ import { import { Field } from "./Field"; import { FieldComponentProps } from "./FieldComponentProps"; import type { AllowedProps } from "./AllowedProps"; -import { getControlState, useInputValues } from "./inputUtils"; +import { FocusHandle, getControlState, useInputValues } from "./inputUtils"; import { ForwardRefWithType } from "./@types/react-augment"; export type NativeSelectOption = { @@ -41,6 +43,12 @@ export type NativeSelectProps< Value extends NativeSelectValueType, HasMultipleChoices extends boolean > = { + /** + * This prop helps users to fill forms faster, especially on mobile devices. + * The name can be confusing, as it's more like an autofill. + * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill + */ + autoCompleteType?: InputHTMLAttributes["autoComplete"]; /** * The options or optgroup elements within the NativeSelect */ @@ -53,6 +61,10 @@ export type NativeSelectProps< * If `true`, the Select allows multiple selections */ hasMultipleChoices?: HasMultipleChoices; + /** + * The ref forwarded to the NativeSelect to expose focus() + */ + inputFocusRef?: React.RefObject; /** * @deprecated Use `hasMultipleChoices` instead */ @@ -98,6 +110,7 @@ const NativeSelect: ForwardRefWithType = forwardRef( HasMultipleChoices extends boolean >( { + autoCompleteType, defaultValue, errorMessage, errorMessageList, @@ -105,6 +118,7 @@ const NativeSelect: ForwardRefWithType = forwardRef( hint, HintLinkComponent, id: idOverride, + inputFocusRef, isDisabled = false, isFullWidth = false, isMultiSelect, @@ -126,6 +140,21 @@ const NativeSelect: ForwardRefWithType = forwardRef( uncontrolledValue: defaultValue, }) ); + const inputRef = useRef(null); + + useImperativeHandle( + inputFocusRef, + () => { + const element = inputRef.current; + return { + focus: () => { + element && element.focus(); + }, + }; + }, + [] + ); + const inputValues = useInputValues({ defaultValue, value, @@ -153,6 +182,7 @@ const NativeSelect: ForwardRefWithType = forwardRef( ), [ + autoCompleteType, children, idOverride, inputValues, From cb1f482452de5aa3dd873e7e66efa8121cad983e Mon Sep 17 00:00:00 2001 From: Trevor Ing Date: Mon, 29 Jan 2024 12:01:29 -0500 Subject: [PATCH 4/5] fix: lint fix --- .../src/labs/VirtualizedAutocomplete.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/odyssey-react-mui/src/labs/VirtualizedAutocomplete.tsx b/packages/odyssey-react-mui/src/labs/VirtualizedAutocomplete.tsx index a6753d44c0..5ec1fec97f 100644 --- a/packages/odyssey-react-mui/src/labs/VirtualizedAutocomplete.tsx +++ b/packages/odyssey-react-mui/src/labs/VirtualizedAutocomplete.tsx @@ -274,7 +274,15 @@ const VirtualizedAutocomplete = < )} /> ), - [errorMessage, errorMessageList, hint, isOptional, label, nameOverride, testId] + [ + errorMessage, + errorMessageList, + hint, + isOptional, + label, + nameOverride, + testId, + ] ); const onChange = useCallback< NonNullable< From b3ec07a7b9129753cc9520f227bf86985813f236 Mon Sep 17 00:00:00 2001 From: Trevor Ing Date: Mon, 29 Jan 2024 17:26:34 -0500 Subject: [PATCH 5/5] fix: address PR comments --- .../src/@types/react-augment.d.ts | 2 +- packages/odyssey-react-mui/src/Button.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/Checkbox.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/Link.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/NativeSelect.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/PasswordField.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/Radio.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/Select.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/TextField.tsx | 15 +++++++-------- packages/odyssey-react-mui/src/Typography.tsx | 15 +++++++-------- 10 files changed, 64 insertions(+), 73 deletions(-) diff --git a/packages/odyssey-react-mui/src/@types/react-augment.d.ts b/packages/odyssey-react-mui/src/@types/react-augment.d.ts index 6f02babc51..f1501f50df 100644 --- a/packages/odyssey-react-mui/src/@types/react-augment.d.ts +++ b/packages/odyssey-react-mui/src/@types/react-augment.d.ts @@ -17,8 +17,8 @@ export interface ForwardRefWithType extends FC> { >; } -type DataAttributeKey = `data-${string}`; declare module "react" { + type DataAttributeKey = `data-${string}`; interface InputHTMLAttributes extends HTMLAttributes { // Allows data-* props to be passed to inputProps in nested MUI components // see: https://github.com/mui/material-ui/issues/20160 diff --git a/packages/odyssey-react-mui/src/Button.tsx b/packages/odyssey-react-mui/src/Button.tsx index a0cf63b65b..0baa0e9dc1 100644 --- a/packages/odyssey-react-mui/src/Button.tsx +++ b/packages/odyssey-react-mui/src/Button.tsx @@ -49,9 +49,9 @@ export type ButtonProps = { */ ariaDescribedBy?: string; /** - * The ref forwarded to the Button to expose focus() + * The ref forwarded to the Button */ - buttonFocusRef?: React.RefObject; + buttonRef?: React.RefObject; /** * The icon element to display at the end of the Button */ @@ -119,7 +119,7 @@ const Button = ({ ariaDescribedBy, ariaLabel, ariaLabelledBy, - buttonFocusRef, + buttonRef, endIcon, id, isDisabled, @@ -136,14 +136,13 @@ const Button = ({ }: ButtonProps) => { const muiProps = useMuiProps(); - const ref = useRef(null); + const localButtonRef = useRef(null); useImperativeHandle( - buttonFocusRef, + buttonRef, () => { - const element = ref.current; return { focus: () => { - element && element.focus(); + localButtonRef.current?.focus(); }, }; }, @@ -163,7 +162,7 @@ const Button = ({ fullWidth={isFullWidth} id={id} onClick={onClick} - ref={ref} + ref={localButtonRef} size={size} startIcon={startIcon} translate={translate} diff --git a/packages/odyssey-react-mui/src/Checkbox.tsx b/packages/odyssey-react-mui/src/Checkbox.tsx index 078f2f2e4c..2d99fc9a5e 100644 --- a/packages/odyssey-react-mui/src/Checkbox.tsx +++ b/packages/odyssey-react-mui/src/Checkbox.tsx @@ -46,9 +46,9 @@ export type CheckboxProps = { */ id?: string; /** - * The ref forwarded to the Checkbox to expose focus() + * The ref forwarded to the Checkbox */ - inputFocusRef?: React.RefObject; + inputRef?: React.RefObject; /** * Determines whether the Checkbox is disabled */ @@ -89,7 +89,7 @@ const Checkbox = ({ ariaLabel, ariaLabelledBy, id: idOverride, - inputFocusRef, + inputRef, isChecked, isDefaultChecked, isDisabled, @@ -119,14 +119,13 @@ const Checkbox = ({ return { defaultChecked: isDefaultChecked }; }, [isDefaultChecked, isChecked]); - const inputRef = useRef(null); + const localInputRef = useRef(null); useImperativeHandle( - inputFocusRef, + inputRef, () => { - const element = inputRef.current; return { focus: () => { - element && element.focus(); + localInputRef.current?.focus(); }, }; }, @@ -185,7 +184,7 @@ const Checkbox = ({ inputProps={{ "data-se": testId, }} - inputRef={inputRef} + inputRef={localInputRef} sx={() => ({ marginBlockStart: "2px", })} diff --git a/packages/odyssey-react-mui/src/Link.tsx b/packages/odyssey-react-mui/src/Link.tsx index cf33616cf2..28a9cdea76 100644 --- a/packages/odyssey-react-mui/src/Link.tsx +++ b/packages/odyssey-react-mui/src/Link.tsx @@ -33,9 +33,9 @@ export type LinkProps = { */ icon?: ReactElement; /** - * The ref forwarded to the TextField to expose focus() + * The ref forwarded to the TextField */ - linkFocusRef?: React.RefObject; + linkRef?: React.RefObject; /** * The click event handler for the Link */ @@ -63,7 +63,7 @@ const Link = ({ children, href, icon, - linkFocusRef, + linkRef, rel, target, testId, @@ -71,14 +71,13 @@ const Link = ({ variant, onClick, }: LinkProps) => { - const ref = useRef(null); + const localLinkRef = useRef(null); useImperativeHandle( - linkFocusRef, + linkRef, () => { - const element = ref.current; return { focus: () => { - element && element.focus(); + localLinkRef.current?.focus(); }, }; }, @@ -89,7 +88,7 @@ const Link = ({ ; + inputRef?: React.RefObject; /** * @deprecated Use `hasMultipleChoices` instead */ @@ -118,7 +118,7 @@ const NativeSelect: ForwardRefWithType = forwardRef( hint, HintLinkComponent, id: idOverride, - inputFocusRef, + inputRef, isDisabled = false, isFullWidth = false, isMultiSelect, @@ -140,15 +140,14 @@ const NativeSelect: ForwardRefWithType = forwardRef( uncontrolledValue: defaultValue, }) ); - const inputRef = useRef(null); + const localInputRef = useRef(null); useImperativeHandle( - inputFocusRef, + inputRef, () => { - const element = inputRef.current; return { focus: () => { - element && element.focus(); + localInputRef.current?.focus(); }, }; }, @@ -190,7 +189,7 @@ const NativeSelect: ForwardRefWithType = forwardRef( "aria-labelledby": labelElementId, "data-se": testId, }} - inputRef={inputRef} + inputRef={localInputRef} name={idOverride} multiple={hasMultipleChoices} native={true} diff --git a/packages/odyssey-react-mui/src/PasswordField.tsx b/packages/odyssey-react-mui/src/PasswordField.tsx index b010ee58ef..15b0c5c208 100644 --- a/packages/odyssey-react-mui/src/PasswordField.tsx +++ b/packages/odyssey-react-mui/src/PasswordField.tsx @@ -49,9 +49,9 @@ export type PasswordFieldProps = { */ hasShowPassword?: boolean; /** - * The ref forwarded to the TextField to expose focus() + * The ref forwarded to the TextField */ - inputFocusRef?: React.RefObject; + inputRef?: React.RefObject; /** * The label for the `input` element. */ @@ -89,7 +89,7 @@ const PasswordField = forwardRef( hasInitialFocus, hint, id: idOverride, - inputFocusRef, + inputRef, isDisabled = false, isFullWidth = false, isOptional = false, @@ -128,14 +128,13 @@ const PasswordField = forwardRef( controlState: controlledStateRef.current, }); - const inputRef = useRef(null); + const localInputRef = useRef(null); useImperativeHandle( - inputFocusRef, + inputRef, () => { - const element = inputRef.current; return { focus: () => { - element && element.focus(); + localInputRef.current?.focus(); }, }; }, @@ -183,7 +182,7 @@ const PasswordField = forwardRef( // role: "textbox" Added because password inputs don't have an implicit role assigned. This causes problems with element selection. role: "textbox", }} - inputRef={inputRef} + inputRef={localInputRef} name={nameOverride ?? id} onChange={onChange} onFocus={onFocus} diff --git a/packages/odyssey-react-mui/src/Radio.tsx b/packages/odyssey-react-mui/src/Radio.tsx index 1f32f041a4..f6c64487c6 100644 --- a/packages/odyssey-react-mui/src/Radio.tsx +++ b/packages/odyssey-react-mui/src/Radio.tsx @@ -24,9 +24,9 @@ import { FocusHandle } from "./inputUtils"; export type RadioProps = { /** - * The ref forwarded to the Radio to expose focus() + * The ref forwarded to the Radio */ - inputFocusRef?: React.RefObject; + inputRef?: React.RefObject; /** * If `true`, the Radio is selected */ @@ -55,7 +55,7 @@ export type RadioProps = { AllowedProps; const Radio = ({ - inputFocusRef, + inputRef, isChecked, isDisabled, isInvalid, @@ -67,14 +67,13 @@ const Radio = ({ onChange: onChangeProp, onBlur: onBlurProp, }: RadioProps) => { - const ref = useRef(null); + const localInputRef = useRef(null); useImperativeHandle( - inputFocusRef, + inputRef, () => { - const element = ref.current; return { focus: () => { - element && element.focus(); + localInputRef.current?.focus(); }, }; }, @@ -104,7 +103,7 @@ const Radio = ({ inputProps={{ "data-se": testId, }} - inputRef={ref} + inputRef={localInputRef} onChange={onChange} /> } diff --git a/packages/odyssey-react-mui/src/Select.tsx b/packages/odyssey-react-mui/src/Select.tsx index 36736a3c24..3ca7bf3100 100644 --- a/packages/odyssey-react-mui/src/Select.tsx +++ b/packages/odyssey-react-mui/src/Select.tsx @@ -63,9 +63,9 @@ export type SelectProps< */ hasMultipleChoices?: HasMultipleChoices; /** - * The ref forwarded to the Select to expose focus() + * The ref forwarded to the Select */ - inputFocusRef?: React.RefObject; + inputRef?: React.RefObject; /** * @deprecated Use `hasMultipleChoices` instead. */ @@ -136,7 +136,7 @@ const Select = < hint, HintLinkComponent, id: idOverride, - inputFocusRef, + inputRef, isDisabled = false, isFullWidth = false, isMultiSelect, @@ -164,15 +164,14 @@ const Select = < const [internalSelectedValues, setInternalSelectedValues] = useState( controlledStateRef.current === CONTROLLED ? value : defaultValue ); - const inputRef = useRef(null); + const localInputRef = useRef(null); useImperativeHandle( - inputFocusRef, + inputRef, () => { - const element = inputRef.current; return { focus: () => { - element && element.focus(); + localInputRef.current?.focus(); }, }; }, @@ -292,7 +291,7 @@ const Select = < children={children} id={id} inputProps={{ "data-se": testId }} - inputRef={inputRef} + inputRef={localInputRef} labelId={labelElementId} multiple={hasMultipleChoices} name={nameOverride ?? id} diff --git a/packages/odyssey-react-mui/src/TextField.tsx b/packages/odyssey-react-mui/src/TextField.tsx index 4476272c02..937ede49d5 100644 --- a/packages/odyssey-react-mui/src/TextField.tsx +++ b/packages/odyssey-react-mui/src/TextField.tsx @@ -56,9 +56,9 @@ export type TextFieldProps = { */ hasInitialFocus?: boolean; /** - * The ref forwarded to the TextField to expose focus() + * The ref forwarded to the TextField */ - inputFocusRef?: React.RefObject; + inputRef?: React.RefObject; /** * Hints at the type of data that might be entered by the user while editing the element or its contents * @see https://html.spec.whatwg.org/multipage/interaction.html#input-modalities:-the-inputmode-attribute @@ -115,7 +115,7 @@ const TextField = forwardRef( hint, HintLinkComponent, id: idOverride, - inputFocusRef, + inputRef, inputMode, isDisabled = false, isFullWidth = false, @@ -148,14 +148,13 @@ const TextField = forwardRef( controlState: controlledStateRef.current, }); - const inputRef = useRef(null); + const localInputRef = useRef(null); useImperativeHandle( - inputFocusRef, + inputRef, () => { - const element = inputRef.current; return { focus: () => { - element && element.focus(); + localInputRef.current?.focus(); }, }; }, @@ -193,7 +192,7 @@ const TextField = forwardRef( "data-se": testId, inputmode: inputMode, }} - inputRef={inputRef} + inputRef={localInputRef} multiline={isMultiline} name={nameOverride ?? id} onBlur={onBlur} diff --git a/packages/odyssey-react-mui/src/Typography.tsx b/packages/odyssey-react-mui/src/Typography.tsx index 7f2a427b1e..f991b79efb 100644 --- a/packages/odyssey-react-mui/src/Typography.tsx +++ b/packages/odyssey-react-mui/src/Typography.tsx @@ -87,9 +87,9 @@ export type TypographyProps = { */ component?: ElementType; /** - * The ref forwarded to the Typography to expose focus() + * The ref forwarded to the Typography */ - typographyFocusRef?: React.RefObject; + typographyRef?: React.RefObject; /** * The variant of Typography to render. */ @@ -105,7 +105,7 @@ const Typography = ({ component: componentProp, testId, translate, - typographyFocusRef, + typographyRef, variant = "body", }: TypographyProps) => { const component = useMemo(() => { @@ -121,14 +121,13 @@ const Typography = ({ return componentProp; }, [componentProp, variant]); - const ref = useRef(null); + const localTypographyRef = useRef(null); useImperativeHandle( - typographyFocusRef, + typographyRef, () => { - const element = ref.current; return { focus: () => { - element && element.focus(); + localTypographyRef.current?.focus(); }, }; }, @@ -144,7 +143,7 @@ const Typography = ({ color={color} component={component} data-se={testId} - ref={ref} + ref={localTypographyRef} tabIndex={-1} translate={translate} variant={typographyVariantMapping[variant]}