-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read-only support for Checkbox, Radio, Switch [OKTA-731946] #2305
Changes from all commits
30e97ef
c306ca3
d8d70d9
7e2da86
30ee68b
d68131f
c30533c
c38b805
9e7a575
dacf3f7
2cf8f3c
5fc601c
f66a53a
7014df6
f8e01fe
5691113
9e5c9ad
9c2b32b
37efc4f
2485d7f
8a99902
760edcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,7 +61,10 @@ export type CheckboxProps = { | |
* Callback fired when the blur event happens. Provides event value. | ||
*/ | ||
onBlur?: MuiFormControlLabelProps["onBlur"]; | ||
} & Pick<FieldComponentProps, "hint" | "id" | "isDisabled" | "name"> & | ||
} & Pick< | ||
FieldComponentProps, | ||
"hint" | "id" | "isDisabled" | "isReadOnly" | "name" | ||
> & | ||
CheckedFieldProps<MuiCheckboxProps> & | ||
Pick<HtmlProps, "ariaLabel" | "ariaLabelledBy" | "testId" | "translate">; | ||
|
||
|
@@ -74,6 +77,7 @@ const Checkbox = ({ | |
isDefaultChecked, | ||
isDisabled, | ||
isIndeterminate, | ||
isReadOnly = false, | ||
isRequired, | ||
label: labelProp, | ||
hint, | ||
|
@@ -102,13 +106,11 @@ const Checkbox = ({ | |
const localInputRef = useRef<HTMLInputElement>(null); | ||
useImperativeHandle( | ||
inputRef, | ||
() => { | ||
return { | ||
focus: () => { | ||
localInputRef.current?.focus(); | ||
}, | ||
}; | ||
}, | ||
() => ({ | ||
focus: () => { | ||
localInputRef.current?.focus(); | ||
}, | ||
}), | ||
[], | ||
); | ||
|
||
|
@@ -136,16 +138,39 @@ const Checkbox = ({ | |
[onChangeProp], | ||
); | ||
|
||
const onClick = useCallback<NonNullable<MuiCheckboxProps["onClick"]>>( | ||
(event) => { | ||
if (isReadOnly) { | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
} | ||
}, | ||
[isReadOnly], | ||
); | ||
|
||
const onBlur = useCallback<NonNullable<MuiFormControlLabelProps["onBlur"]>>( | ||
(event) => { | ||
onBlurProp?.(event); | ||
}, | ||
[onBlurProp], | ||
); | ||
|
||
const checkboxStyles = useMemo( | ||
() => ({ | ||
alignItems: "flex-start", | ||
...(isReadOnly && { | ||
cursor: "default", | ||
"& .MuiTypography-root": { | ||
cursor: "default", | ||
}, | ||
}), | ||
}), | ||
[isReadOnly], | ||
); | ||
|
||
return ( | ||
<FormControlLabel | ||
sx={{ alignItems: "flex-start" }} | ||
sx={checkboxStyles} | ||
aria-label={ariaLabel} | ||
aria-labelledby={ariaLabelledBy} | ||
className={ | ||
|
@@ -160,9 +185,14 @@ const Checkbox = ({ | |
{...inputValues} | ||
indeterminate={isIndeterminate} | ||
onChange={onChange} | ||
onClick={ | ||
onClick as unknown as React.MouseEventHandler<HTMLButtonElement> | ||
} | ||
required={isRequired} | ||
inputProps={{ | ||
"data-se": testId, | ||
"aria-readonly": isReadOnly, | ||
readOnly: isReadOnly, | ||
}} | ||
Comment on lines
192
to
196
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we memoize these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could memoize them, but since |
||
disabled={isDisabled} | ||
inputRef={localInputRef} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,9 +17,7 @@ import { | |
Radio as MuiRadio, | ||
RadioProps as MuiRadioProps, | ||
} from "@mui/material"; | ||
|
||
import { memo, useCallback, useMemo, useRef, useImperativeHandle } from "react"; | ||
|
||
import { FieldComponentProps } from "./FieldComponentProps"; | ||
import type { HtmlProps } from "./HtmlProps"; | ||
import { FocusHandle } from "./inputUtils"; | ||
|
@@ -31,11 +29,11 @@ export type RadioProps = { | |
*/ | ||
inputRef?: React.RefObject<FocusHandle>; | ||
/** | ||
* If `true`, the Radio is selected | ||
* Determines whether the Radio button is checked | ||
*/ | ||
isChecked?: boolean; | ||
/** | ||
* If `true`, the Radio has an invalid value | ||
* If `true`, the radio button has an invalid value | ||
*/ | ||
isInvalid?: boolean; | ||
/** | ||
|
@@ -46,30 +44,33 @@ export type RadioProps = { | |
* The value attribute of the Radio | ||
*/ | ||
value: string; | ||
/** | ||
* Callback fired when the state is changed. Provides event and checked value. | ||
*/ | ||
onChange?: MuiRadioProps["onChange"]; | ||
/** | ||
* Callback fired when the blur event happens. Provides event value. | ||
*/ | ||
onChange?: MuiRadioProps["onChange"]; | ||
onBlur?: MuiFormControlLabelProps["onBlur"]; | ||
} & Pick<FieldComponentProps, "hint" | "id" | "isDisabled" | "name"> & | ||
onClick?: React.MouseEventHandler<HTMLSpanElement>; | ||
} & Pick< | ||
FieldComponentProps, | ||
"hint" | "id" | "isDisabled" | "isReadOnly" | "name" | ||
> & | ||
Pick<HtmlProps, "testId" | "translate">; | ||
|
||
const Radio = ({ | ||
hint, | ||
inputRef, | ||
isChecked, | ||
isDisabled, | ||
isDisabled = false, | ||
isInvalid, | ||
label: labelProp, | ||
name, | ||
testId, | ||
translate, | ||
value, | ||
isReadOnly = false, | ||
onChange: onChangeProp, | ||
onBlur: onBlurProp, | ||
onClick, | ||
}: RadioProps) => { | ||
const localInputRef = useRef<HTMLInputElement>(null); | ||
useImperativeHandle( | ||
|
@@ -95,10 +96,25 @@ const Radio = ({ | |
); | ||
|
||
const onChange = useCallback<NonNullable<MuiRadioProps["onChange"]>>( | ||
(event, checked) => onChangeProp?.(event, checked), | ||
[onChangeProp], | ||
(event, checked) => { | ||
if (isReadOnly) { | ||
event.preventDefault(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can add |
||
} else { | ||
onChangeProp?.(event, checked); | ||
} | ||
}, | ||
[onChangeProp, isReadOnly], | ||
); | ||
|
||
const handleClick = useCallback<React.MouseEventHandler<HTMLSpanElement>>( | ||
(event) => { | ||
if (isReadOnly) { | ||
event.stopPropagation(); | ||
event.preventDefault(); | ||
} | ||
}, | ||
[isReadOnly], | ||
); | ||
const onBlur = useCallback<NonNullable<MuiFormControlLabelProps["onBlur"]>>( | ||
(event) => { | ||
onBlurProp?.(event); | ||
|
@@ -114,17 +130,30 @@ const Radio = ({ | |
<MuiRadio | ||
inputProps={{ | ||
"data-se": testId, | ||
"aria-disabled": isDisabled || isReadOnly, | ||
readOnly: isReadOnly, | ||
tabIndex: isReadOnly ? 0 : undefined, | ||
}} | ||
inputRef={localInputRef} | ||
onChange={onChange} | ||
onClick={onClick || handleClick} | ||
disabled={isDisabled} | ||
/> | ||
} | ||
disabled={isDisabled} | ||
label={label} | ||
name={name} | ||
translate={translate} | ||
value={value} | ||
onBlur={onBlur} | ||
disabled={isDisabled} | ||
sx={{ | ||
...(isReadOnly && { | ||
cursor: "default", | ||
"& .MuiTypography-root": { | ||
cursor: "default", | ||
}, | ||
}), | ||
}} | ||
Comment on lines
+149
to
+156
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually, these styles are in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This style only applies to the label, not the form elements. I wanted this to live in |
||
/> | ||
); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird. Any way around this syntax like setting our
useCallback
toMuiCheckboxProps["onClick"]
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this. I think this was part of something I was debugging that didn't get removed. I updated the onClick to match the existing onChange: