Skip to content

Commit

Permalink
feat: updated Radio and fixed types on TextField and Banner
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinGhadyani-Okta committed Jan 30, 2023
1 parent 25eb5d1 commit 9cfa050
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 246 deletions.
9 changes: 7 additions & 2 deletions packages/odyssey-react-mui/src/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { AlertColor, AlertProps } from "@mui/material";
import { memo } from "react";
import { Alert, Link } from "./";

export interface BannerProps {
Expand All @@ -23,7 +24,7 @@ export interface BannerProps {
* ("status" for something that dynamically updates, null for something
* unchanging)
*/
role?: "status" | undefined;
role?: "status";
/**
* The text content of the alert
*/
Expand All @@ -45,7 +46,7 @@ export interface BannerProps {
linkText?: string;
}

export const Banner = ({
const Banner = ({
onClose,
severity = "info",
role,
Expand All @@ -62,3 +63,7 @@ export const Banner = ({
)}
</Alert>
);

const MemoizedBanner = memo(Banner);

export { MemoizedBanner as Banner };
11 changes: 8 additions & 3 deletions packages/odyssey-react-mui/src/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@

import { FormControlLabel } from "./";
import { Radio as MuiRadio } from "@mui/material";
import { memo } from "react";

export interface RadioProps {
export type RadioProps = {
label: string;
value: string;
}
};

export const Radio = ({ label, value }: RadioProps) => (
const Radio = ({ label, value }: RadioProps) => (
<FormControlLabel control={<MuiRadio />} label={label} value={value} />
);

const MemoizedRadio = memo(Radio);

export { MemoizedRadio as Radio };
35 changes: 19 additions & 16 deletions packages/odyssey-react-mui/src/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
useUniqueId,
} from "./";
import { RadioGroup as MuiRadioGroup } from "@mui/material";
import { ReactElement, useMemo } from "react";
import { memo, ReactElement, useMemo } from "react";

export interface RadioGroupProps {
/**
Expand All @@ -33,7 +33,7 @@ export interface RadioGroupProps {
/**
* The error text for an invalid group
*/
error?: string;
errorMessage?: string;
/**
* Optional hint text
*/
Expand All @@ -42,10 +42,6 @@ export interface RadioGroupProps {
* Disables the whole radio group
*/
isDisabled?: boolean;
/**
* Declares the group invalid
*/
isInvalid?: boolean;
/**
* The text label for the radio group
*/
Expand All @@ -57,46 +53,53 @@ export interface RadioGroupProps {
name?: string;
}

export const RadioGroup = ({
const RadioGroup = ({
children,
defaultValue,
error,
errorMessage,
hint,
isDisabled,
isInvalid,
label,
name,
}: RadioGroupProps) => {
const ariaDescribedBy = useMemo(
() =>
error || hint
? [hint && `${name}-hint`, error && `${name}-error`]
errorMessage || hint
? [hint && `${name}-hint`, errorMessage && `${name}-error`]
.filter(Boolean)
.join(" ")
: undefined,
[error, hint, name]
[errorMessage, hint, name]
);

const uniqueName = useUniqueId(name);

return (
<FormControl component="fieldset" disabled={isDisabled} error={isInvalid}>
<FormControl
component="fieldset"
disabled={isDisabled}
error={Boolean(errorMessage)}
>
<FormLabel component="legend">{label}</FormLabel>
{hint && (
<FormHelperText id={`${uniqueName}-hint`}>{hint}</FormHelperText>
)}
<MuiRadioGroup
aria-describedby={ariaDescribedBy}
defaultValue={defaultValue}
name={`${uniqueName}-group`}
name={uniqueName}
>
{children}
</MuiRadioGroup>
{error && (
{errorMessage && (
<FormHelperText id={`${uniqueName}-error`} error>
<span style={visuallyHidden}>Error:</span> {error}
<span style={visuallyHidden}>Error:</span> {errorMessage}
</FormHelperText>
)}
</FormControl>
);
};

const MemoizedRadioGroup = memo(RadioGroup);

export { MemoizedRadioGroup as RadioGroup };
208 changes: 106 additions & 102 deletions packages/odyssey-react-mui/src/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
ChangeEventHandler,
FocusEventHandler,
forwardRef,
InputHTMLAttributes,
memo,
ReactNode,
useCallback,
useEffect,
Expand All @@ -35,13 +37,13 @@ import {
useUniqueId,
} from "./";

export interface TextFieldProps {
export type TextFieldProps = {
/**
* This prop helps users to fill forms faster, especially on mobile devices.
* The name can be confusing, as it's more like an autofill.
* You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
*/
autoCompleteType?: string;
autoCompleteType?: InputHTMLAttributes<HTMLInputElement>["autoComplete"];
/**
* If `true`, the component is disabled.
*/
Expand Down Expand Up @@ -106,111 +108,113 @@ export interface TextFieldProps {
* The value of the `input` element, required for a controlled component.
*/
value?: unknown;
}
};

export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
(props, ref) => {
const {
autoCompleteType,
isDisabled = false,
endAdornment,
errorMessage,
hint,
id: idOverride,
label,
isMultiline = false,
onChange,
onFocus,
optionalLabel,
placeholder,
isReadOnly,
isRequired = true,
startAdornment,
type = "text",
value,
} = props;
const TextField = forwardRef<HTMLInputElement, TextFieldProps>((props, ref) => {
const {
autoCompleteType,
isDisabled = false,
endAdornment,
errorMessage,
hint,
id: idOverride,
label,
isMultiline = false,
onChange,
onFocus,
optionalLabel,
placeholder,
isReadOnly,
isRequired = true,
startAdornment,
type = "text",
value,
} = props;

const [inputType, setInputType] = useState(type);
const [inputType, setInputType] = useState(type);

useEffect(() => {
setInputType(type);
}, [type]);
useEffect(() => {
setInputType(type);
}, [type]);

const togglePasswordVisibility = useCallback(() => {
setInputType((currentType) =>
currentType === "password" ? "text" : "password"
);
}, []);

const id = useUniqueId(idOverride);
const hintId = hint ? `${id}-hint` : undefined;
const errorId = errorMessage ? `${id}-error` : undefined;
const labelId = label ? `${id}-label` : undefined;

const inputProps = useMemo(
() =>
errorId || hintId
? {
"aria-describedby":
errorId && hintId ? `${hintId} ${errorId}` : errorId || hintId,
}
: undefined,
[errorId, hintId]
const togglePasswordVisibility = useCallback(() => {
setInputType((currentType) =>
currentType === "password" ? "text" : "password"
);
}, []);

return (
<FormControl disabled={isDisabled} error={Boolean(errorMessage)}>
<InputLabel htmlFor={id} id={labelId}>
{label}
{!isRequired && (
<Typography variant="subtitle1">{optionalLabel}</Typography>
)}
</InputLabel>
{hint && <FormHelperText id={hintId}>{hint}</FormHelperText>}
<InputBase
autoComplete={autoCompleteType}
endAdornment={
inputType === "password" ? (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
edge="end"
onClick={togglePasswordVisibility}
>
{inputType === "password" ? <EyeIcon /> : <EyeOffIcon />}
</IconButton>
</InputAdornment>
) : (
endAdornment
)
}
id={id}
inputProps={inputProps}
multiline={isMultiline}
onChange={onChange}
onFocus={onFocus}
placeholder={placeholder}
readOnly={isReadOnly}
ref={ref}
startAdornment={
inputType === "search" ? (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
) : (
startAdornment
)
const id = useUniqueId(idOverride);
const hintId = hint ? `${id}-hint` : undefined;
const errorId = errorMessage ? `${id}-error` : undefined;
const labelId = label ? `${id}-label` : undefined;

const inputProps = useMemo(
() =>
errorId || hintId
? {
"aria-describedby":
errorId && hintId ? `${hintId} ${errorId}` : errorId || hintId,
}
type={inputType}
value={value}
/>
{errorMessage && (
<FormHelperText error id={errorId}>
<span style={visuallyHidden}>Error:</span>
{errorMessage}
</FormHelperText>
: undefined,
[errorId, hintId]
);

return (
<FormControl disabled={isDisabled} error={Boolean(errorMessage)}>
<InputLabel htmlFor={id} id={labelId}>
{label}
{!isRequired && (
<Typography variant="subtitle1">{optionalLabel}</Typography>
)}
</FormControl>
);
}
);
</InputLabel>
{hint && <FormHelperText id={hintId}>{hint}</FormHelperText>}
<InputBase
autoComplete={autoCompleteType}
endAdornment={
inputType === "password" ? (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
edge="end"
onClick={togglePasswordVisibility}
>
{inputType === "password" ? <EyeIcon /> : <EyeOffIcon />}
</IconButton>
</InputAdornment>
) : (
endAdornment
)
}
id={id}
inputProps={inputProps}
multiline={isMultiline}
onChange={onChange}
onFocus={onFocus}
placeholder={placeholder}
readOnly={isReadOnly}
ref={ref}
startAdornment={
inputType === "search" ? (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
) : (
startAdornment
)
}
type={inputType}
value={value}
/>
{errorMessage && (
<FormHelperText error id={errorId}>
<span style={visuallyHidden}>Error:</span>
{errorMessage}
</FormHelperText>
)}
</FormControl>
);
});

const MemoizedTextField = memo(TextField);

export { MemoizedTextField as TextField };
Loading

0 comments on commit 9cfa050

Please sign in to comment.