diff --git a/package.json b/package.json index bcc3a36744..bd27b7c3de 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "node": ">=16 <17" }, "scripts": { - "build": "lerna run build --ignore @okta/odyssey-storybook", + "build": "lerna run build --ignore @okta/odyssey-storybook --ignore @okta/odyssey-icons --ignore @okta/odyssey-svgr", "dev:source": "lerna run dev:source", "eslint": "eslint . --ext .js,.jsx,.ts,.tsx", "generate:icons": "yarn workspace @okta/odyssey-icons build && yarn workspace @okta/odyssey-svgr build && yarn workspace @okta/odyssey-react-mui build:icons", diff --git a/packages/odyssey-react-mui/src/Autocomplete.tsx b/packages/odyssey-react-mui/src/Autocomplete.tsx index 166f27314a..a649920b38 100644 --- a/packages/odyssey-react-mui/src/Autocomplete.tsx +++ b/packages/odyssey-react-mui/src/Autocomplete.tsx @@ -102,8 +102,9 @@ const Autocomplete = < const renderInput = useCallback( ({ InputLabelProps, InputProps, ...params }) => ( ( diff --git a/packages/odyssey-react-mui/src/CheckboxGroup.tsx b/packages/odyssey-react-mui/src/CheckboxGroup.tsx index f00f24c522..a4f5d1ef2d 100644 --- a/packages/odyssey-react-mui/src/CheckboxGroup.tsx +++ b/packages/odyssey-react-mui/src/CheckboxGroup.tsx @@ -10,16 +10,11 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { memo, ReactElement, useMemo } from "react"; +import { FormGroup as MuiFormGroup } from "@mui/material"; +import { memo, ReactElement, useCallback } from "react"; -import { - Checkbox, - FormControl, - FormGroup, - FormHelperText, - FormLabel, - ScreenReaderText, -} from "."; +import { Checkbox } from "./Checkbox"; +import { Field } from "./Field"; export type CheckboxGroupProps = { children: @@ -27,9 +22,12 @@ export type CheckboxGroupProps = { | Array>; errorMessage?: string; hint?: string; + /** + * The id of the `input` element. This will also be the input's `name` field. + */ + id?: string; isDisabled?: boolean; - label?: string; - name?: string; + label: string; }; const CheckboxGroup = ({ @@ -37,35 +35,29 @@ const CheckboxGroup = ({ isDisabled, errorMessage, hint, + id: idOverride, label, - name, }: CheckboxGroupProps) => { - const ariaDescribedBy = useMemo( - () => - errorMessage || hint - ? [hint && `${name}-hint`, errorMessage && `${name}-error`] - .filter(Boolean) - .join(" ") - : undefined, - [errorMessage, hint, name] + const renderFieldComponent = useCallback( + ({ ariaDescribedBy, id }) => ( + + {children} + + ), + [children] ); return ( - - {label && {label}} - {hint && {hint}} - {children} - {errorMessage && ( - - Error: {errorMessage} - - )} - + ); }; diff --git a/packages/odyssey-react-mui/src/Field.tsx b/packages/odyssey-react-mui/src/Field.tsx index d6874d468a..85117f6e6c 100644 --- a/packages/odyssey-react-mui/src/Field.tsx +++ b/packages/odyssey-react-mui/src/Field.tsx @@ -13,18 +13,23 @@ import { memo, ReactElement, useMemo } from "react"; import { - FieldError, - FieldHint, - FieldLabel, - FormControl, - useUniqueId, -} from "./"; + FormControl as MuiFormControl, + FormLabel as MuiFormLabel, +} from "@mui/material"; +import { FieldError } from "./FieldError"; +import { FieldHint } from "./FieldHint"; +import { FieldLabel } from "./FieldLabel"; +import { useUniqueId } from "./useUniqueId"; export type FieldProps = { /** * If `error` is not undefined, the `input` will indicate an error. */ errorMessage?: string; + /** + * The field type determines how ARIA components are setup. It's important to use this to denote if you expect only one component (like a text field) or multiple (like a radio group). + */ + fieldType: "single" | "group"; hasVisibleLabel: boolean; /** * The helper text content. @@ -34,6 +39,10 @@ export type FieldProps = { * The id of the `input` element. */ id?: string; + /** + * Important for narrowing down the `fieldset` role to "radiogroup". + */ + isRadioGroup?: boolean; /** * If `true`, the component is disabled. */ @@ -54,6 +63,9 @@ export type FieldProps = { * The short hint displayed in the `input` before the user enters a value. */ placeholder?: string; + /** + * Render-props function that sends back ARIA props to your field component. + */ renderFieldComponent: ({ ariaDescribedBy, id, @@ -65,10 +77,12 @@ export type FieldProps = { const Field = ({ errorMessage, + fieldType, hasVisibleLabel, hint, id: idOverride, isDisabled = false, + isRadioGroup = false, isOptional = false, label, optionalLabel, @@ -85,15 +99,24 @@ const Field = ({ ); return ( - - + + {fieldType === "group" ? ( + {label} + ) : ( + + )} {hint && } @@ -103,7 +126,7 @@ const Field = ({ })} {errorMessage && } - + ); }; diff --git a/packages/odyssey-react-mui/src/FieldLabel.tsx b/packages/odyssey-react-mui/src/FieldLabel.tsx index 24d75b09f3..62f358c32b 100644 --- a/packages/odyssey-react-mui/src/FieldLabel.tsx +++ b/packages/odyssey-react-mui/src/FieldLabel.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and limitations under the License. */ -import { InputLabel } from "@mui/material"; +import { InputLabel as MuiInputLabel } from "@mui/material"; import { memo, useMemo } from "react"; import { ScreenReaderText } from "./ScreenReaderText"; @@ -35,12 +35,12 @@ const FieldLabel = ({ }: FieldLabelProps) => { const inputLabel = useMemo( () => ( - + {text} {isOptional && ( {optionalText} )} - + ), [id, inputId, isOptional, optionalText, text] ); diff --git a/packages/odyssey-react-mui/src/PasswordField.tsx b/packages/odyssey-react-mui/src/PasswordField.tsx index 35cc97a50a..75e6a6b014 100644 --- a/packages/odyssey-react-mui/src/PasswordField.tsx +++ b/packages/odyssey-react-mui/src/PasswordField.tsx @@ -162,6 +162,7 @@ const PasswordField = forwardRef( return ( { - const ariaDescribedBy = useMemo( - () => - errorMessage || hint - ? [hint && `${name}-hint`, errorMessage && `${name}-error`] - .filter(Boolean) - .join(" ") - : undefined, - [errorMessage, hint, name] - ); - - const uniqueName = useUniqueId(name); - - return ( - - {label} - {hint && ( - {hint} - )} + const renderFieldComponent = useCallback( + ({ ariaDescribedBy, id }) => ( {children} - {errorMessage && ( - - Error: {errorMessage} - - )} - + ), + [children, defaultValue, onChange, value] + ); + + return ( + ); }; diff --git a/packages/odyssey-react-mui/src/SearchField.tsx b/packages/odyssey-react-mui/src/SearchField.tsx index 75e056c728..a3313eb6d4 100644 --- a/packages/odyssey-react-mui/src/SearchField.tsx +++ b/packages/odyssey-react-mui/src/SearchField.tsx @@ -121,6 +121,7 @@ const SearchField = forwardRef( return ( ( return ( { Destination diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/RadioGroup/RadioGroup.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/RadioGroup/RadioGroup.stories.tsx index 86fc30dca9..a2a15216d8 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/RadioGroup/RadioGroup.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/RadioGroup/RadioGroup.stories.tsx @@ -37,6 +37,10 @@ const storybookMeta: ComponentMeta = { hint: { control: "text", }, + id: { + control: "text", + defaultValue: "storybook-radio", + }, isDisabled: { control: "boolean", }, @@ -44,10 +48,6 @@ const storybookMeta: ComponentMeta = { control: "text", defaultValue: "Speed", }, - name: { - control: "text", - defaultValue: "storybook-radio", - }, onChange: { control: "function", }, diff --git a/packages/odyssey-storybook/src/contributing/CustomTheme.stories.tsx b/packages/odyssey-storybook/src/contributing/CustomTheme.stories.tsx index e7290ed642..16bb1dfa37 100644 --- a/packages/odyssey-storybook/src/contributing/CustomTheme.stories.tsx +++ b/packages/odyssey-storybook/src/contributing/CustomTheme.stories.tsx @@ -95,7 +95,7 @@ export const RadioGroupStory: StoryFn = () => {