Skip to content

Commit

Permalink
fix: pulled out TextField into separate components (#1744)
Browse files Browse the repository at this point in the history
* fix: pulled out TextField into separate components

* fix: updated docs for custom text fields

* fix: restricted password to only allow specific auto-complete types

* fix: removed errorMessage and hint from SearchField

* fix: password field has label

* fix: internal FieldLabel uses ScreenReaderText

* fix(odyssey-storybook): args for SearchField stories match the component
  • Loading branch information
KevinGhadyani-Okta authored Apr 14, 2023
1 parent d494fe3 commit 0b7a412
Show file tree
Hide file tree
Showing 19 changed files with 816 additions and 337 deletions.
112 changes: 112 additions & 0 deletions packages/odyssey-react-mui/src/Field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*!
* Copyright (c) 2022-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

import { memo, ReactElement, useMemo } from "react";

import {
FieldError,
FieldHint,
FieldLabel,
FormControl,
useUniqueId,
} from "./";

export type FieldProps = {
/**
* If `error` is not undefined, the `input` will indicate an error.
*/
errorMessage?: string;
hasVisibleLabel: boolean;
/**
* The helper text content.
*/
hint?: string;
/**
* The id of the `input` element.
*/
id?: string;
/**
* If `true`, the component is disabled.
*/
isDisabled?: boolean;
/**
* If `true`, the `input` element is required.
*/
isRequired?: boolean;
/**
* The label for the `input` element.
*/
label: string;
/**
* The label for the `input` element if the it's not optional
*/
optionalLabel?: string;
/**
* The short hint displayed in the `input` before the user enters a value.
*/
placeholder?: string;
renderFieldComponent: ({
ariaDescribedBy,
id,
}: {
ariaDescribedBy?: string;
id: string;
}) => ReactElement;
};

const Field = ({
errorMessage,
hasVisibleLabel,
hint,
id: idOverride,
isDisabled = false,
isRequired = true,
label,
optionalLabel,
renderFieldComponent,
}: FieldProps) => {
const id = useUniqueId(idOverride);
const hintId = hint ? `${id}-hint` : undefined;
const errorId = errorMessage ? `${id}-error` : undefined;
const labelId = `${id}-label`;

const ariaDescribedBy = useMemo(
() => [hintId, errorId].join(" ").trim() || undefined,
[errorId, hintId]
);

return (
<FormControl disabled={isDisabled} error={Boolean(errorMessage)}>
<FieldLabel
hasVisibleLabel={hasVisibleLabel}
id={labelId}
inputId={id}
isRequired={isRequired}
optionalText={optionalLabel}
text={label}
/>

{hint && <FieldHint id={hintId} text={hint} />}

{renderFieldComponent({
ariaDescribedBy,
id,
})}

{errorMessage && <FieldError id={errorId} text={errorMessage} />}
</FormControl>
);
};

const MemoizedField = memo(Field);

export { MemoizedField as Field };
34 changes: 34 additions & 0 deletions packages/odyssey-react-mui/src/FieldError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*!
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

import { memo } from "react";

import { FormHelperText } from ".";
import { ScreenReaderText } from "./ScreenReaderText";

export type FieldErrorProps = {
id?: string;
text: string;
};

const FieldError = ({ id, text }: FieldErrorProps) => {
return (
<FormHelperText error id={id}>
<ScreenReaderText>Error:</ScreenReaderText>
{text}
</FormHelperText>
);
};

const MemoizedFieldError = memo(FieldError);

export { MemoizedFieldError as FieldError };
28 changes: 28 additions & 0 deletions packages/odyssey-react-mui/src/FieldHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*!
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

import { memo } from "react";

import { FormHelperText } from "./";

export type FieldHintProps = {
id?: string;
text: string;
};

const FieldHint = ({ id, text }: FieldHintProps) => {
return <FormHelperText id={id}>{text}</FormHelperText>;
};

const MemoizedFieldHint = memo(FieldHint);

export { MemoizedFieldHint as FieldHint };
57 changes: 57 additions & 0 deletions packages/odyssey-react-mui/src/FieldLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*!
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

import { InputLabel } from "@mui/material";
import { memo, useMemo } from "react";

import { ScreenReaderText } from "./ScreenReaderText";
import { Typography } from ".";

export type FieldLabelProps = {
hasVisibleLabel: boolean;
id: string;
inputId: string;
isRequired: boolean;
optionalText?: string;
text: string;
};

const FieldLabel = ({
hasVisibleLabel,
id,
inputId,
isRequired,
optionalText,
text,
}: FieldLabelProps) => {
const inputLabel = useMemo(
() => (
<InputLabel htmlFor={inputId} id={id}>
{text}
{!isRequired && (
<Typography variant="subtitle1">{optionalText}</Typography>
)}
</InputLabel>
),
[id, inputId, isRequired, optionalText, text]
);

return hasVisibleLabel ? (
inputLabel
) : (
<ScreenReaderText>{inputLabel}</ScreenReaderText>
);
};

const MemoizedFieldLabel = memo(FieldLabel);

export { MemoizedFieldLabel as FieldLabel };
Loading

0 comments on commit 0b7a412

Please sign in to comment.