From cffb89ebaac1af09cdf296a7236af0059ed2ca60 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Mon, 22 Jan 2024 09:56:43 -0500 Subject: [PATCH] Updated examples and tests --- .../components/HelperText/HelperTextItem.tsx | 37 +++++++++----- .../__tests__/HelperTextItem.test.tsx | 51 ++++++++++--------- .../HelperText/examples/HelperTextBasic.tsx | 8 +-- .../examples/HelperTextWithCustomIcon.tsx | 8 +-- .../examples/MultipleFileUploadBasic.tsx | 4 +- packages/react-core/src/demos/HelperText.md | 25 +++++---- ...tDynamicText.tsx => HelperTextDynamic.tsx} | 4 +- ...antStaticText.tsx => HelperTextStatic.tsx} | 4 +- ...=> HelperTextStaticTextDynamicVariant.tsx} | 20 ++++---- .../PasswordStrength/PasswordStrengthDemo.tsx | 6 +-- 10 files changed, 93 insertions(+), 74 deletions(-) rename packages/react-core/src/demos/examples/HelperText/{HelperTextStaticVariantDynamicText.tsx => HelperTextDynamic.tsx} (88%) rename packages/react-core/src/demos/examples/HelperText/{HelperTextStaticVariantStaticText.tsx => HelperTextStatic.tsx} (80%) rename packages/react-core/src/demos/examples/HelperText/{HelperTextDynamicVariantStaticText.tsx => HelperTextStaticTextDynamicVariant.tsx} (78%) diff --git a/packages/react-core/src/components/HelperText/HelperTextItem.tsx b/packages/react-core/src/components/HelperText/HelperTextItem.tsx index 13f62dc3717..25812cc9ee1 100644 --- a/packages/react-core/src/components/HelperText/HelperTextItem.tsx +++ b/packages/react-core/src/components/HelperText/HelperTextItem.tsx @@ -6,6 +6,13 @@ import ExclamationTriangleIcon from '@patternfly/react-icons/dist/esm/icons/excl import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/esm/icons/exclamation-circle-icon'; +export enum HelperTextItemVariant { + default = 'default', + warning = 'warning', + error = 'error', + success = 'success' +} + export interface HelperTextItemProps extends React.HTMLProps { /** Content rendered inside the helper text item. */ children?: React.ReactNode; @@ -13,24 +20,25 @@ export interface HelperTextItemProps extends React.HTMLProps, indeterminate: , warning: , success: , @@ -41,24 +49,29 @@ export const HelperTextItem: React.FunctionComponent = ({ children, className, component = 'div', - status, + variant = 'default', icon, id, - screenReaderText = `${status} status`, + screenReaderText = `${variant} status`, ...props }: HelperTextItemProps) => { const Component = component as any; + const isNotDefaultVariant = variant !== 'default'; return ( - - {(status || icon) && ( + + {(isNotDefaultVariant || icon) && ( - {icon || defaultStatusIcons[status]} + {icon || defaultVariantIcons[variant]} )} {children} - {status && : {screenReaderText};} + {isNotDefaultVariant && : {screenReaderText};} ); diff --git a/packages/react-core/src/components/HelperText/__tests__/HelperTextItem.test.tsx b/packages/react-core/src/components/HelperText/__tests__/HelperTextItem.test.tsx index 158f88c7d68..4ef5fb26b7a 100644 --- a/packages/react-core/src/components/HelperText/__tests__/HelperTextItem.test.tsx +++ b/packages/react-core/src/components/HelperText/__tests__/HelperTextItem.test.tsx @@ -30,6 +30,12 @@ test('Renders custom className', () => { expect(screen.getByText('help test text 1').parentElement).toHaveClass('custom'); }); +test('Does not render screen reader text by default', () => { + render(help test text 1); + + expect(screen.queryByText('help test text 1')?.querySelector('.pf-v5-screen-reader')).not.toBeInTheDocument(); +}); + Object.values(['indeterminate', 'warning', 'success', 'error']).forEach((variant) => { test(`Renders with class ${styles.modifiers[variant]} when variant = ${variant}`, () => { render( @@ -39,6 +45,15 @@ Object.values(['indeterminate', 'warning', 'success', 'error']).forEach((variant ); expect(screen.getByText('text').parentElement).toHaveClass(styles.modifiers[variant]); }); + + test(`Renders default screenreader text when variant = ${variant}`, () => { + render( + + text + + ); + expect(screen.getByText('text').querySelector('span')).toHaveTextContent(`: ${variant} status;`); + }); }); test('Renders id when id is passed', () => { @@ -56,38 +71,26 @@ test('Renders with element passed to component prop', () => { expect(screen.getByText('help test text 1').parentElement?.tagName).toBe('LI'); }); -test('Renders custom icon', () => { - render(test}>help test text); - expect(screen.getByText('test').parentElement).toHaveClass(styles.helperTextItemIcon); -}); - -test('Renders default icon when hasIcon = true and icon is not passed', () => { - render(help test text); - expect(screen.getByText('help test text').parentElement?.querySelector('span')).toHaveClass( - styles.helperTextItemIcon - ); +test('Does not render an icon by default', () => { + render(help test text); + expect(screen.queryByText('help test text')?.previousSibling).not.toBeInTheDocument(); }); -test('Renders custom icon when icon is passed and hasIcon = true', () => { - render( - test}> - help test text - - ); - expect(screen.getByText('test').parentElement).toHaveClass(styles.helperTextItemIcon); +test('Renders a default icon when variant is passed and icon is not passed', () => { + render(help test text); + expect(screen.getByText('help test text').previousSibling).toHaveClass(styles.helperTextItemIcon); }); -test('Renders dynamic helper text', () => { - render(help test text); - expect(screen.getByText('help test text').parentElement).toHaveClass(styles.modifiers.dynamic); - expect(screen.getByText('help test text').querySelector('span')).toHaveClass('pf-v5-screen-reader'); +test('Renders custom icon when icon prop is passed', () => { + render(icon content}>help test text); + expect(screen.getByText('icon content').parentElement).toHaveClass(styles.helperTextItemIcon); }); -test('Renders custom screenreader text when isDynamic = true and screenReaderText is passed', () => { +test('Renders custom icon instead of variant icon when icon and variant are passed', () => { render( - + icon content} variant="success"> help test text ); - expect(screen.getByText('help test text').querySelector('span')).toHaveTextContent('sr test'); + expect(screen.getByText('icon content').parentElement).toHaveClass(styles.helperTextItemIcon); }); diff --git a/packages/react-core/src/components/HelperText/examples/HelperTextBasic.tsx b/packages/react-core/src/components/HelperText/examples/HelperTextBasic.tsx index 5ee386ee82d..0ef67cced25 100644 --- a/packages/react-core/src/components/HelperText/examples/HelperTextBasic.tsx +++ b/packages/react-core/src/components/HelperText/examples/HelperTextBasic.tsx @@ -7,16 +7,16 @@ export const HelperTextBasic: React.FunctionComponent = () => ( This is default helper text - This is indeterminate helper text + This is indeterminate helper text - This is warning helper text + This is warning helper text - This is success helper text + This is success helper text - This is error helper text + This is error helper text ); diff --git a/packages/react-core/src/components/HelperText/examples/HelperTextWithCustomIcon.tsx b/packages/react-core/src/components/HelperText/examples/HelperTextWithCustomIcon.tsx index d2d81de7ffb..162f573a655 100644 --- a/packages/react-core/src/components/HelperText/examples/HelperTextWithCustomIcon.tsx +++ b/packages/react-core/src/components/HelperText/examples/HelperTextWithCustomIcon.tsx @@ -12,22 +12,22 @@ export const HelperTextWithCustomIcon: React.FunctionComponent = () => ( }>This is default helper text - }> + }> This is indeterminate helper text - }> + }> This is warning helper text - }> + }> This is success helper text - }> + }> This is error helper text diff --git a/packages/react-core/src/components/MultipleFileUpload/examples/MultipleFileUploadBasic.tsx b/packages/react-core/src/components/MultipleFileUpload/examples/MultipleFileUploadBasic.tsx index a55c4f73bb5..ab3f92982f9 100644 --- a/packages/react-core/src/components/MultipleFileUpload/examples/MultipleFileUploadBasic.tsx +++ b/packages/react-core/src/components/MultipleFileUpload/examples/MultipleFileUploadBasic.tsx @@ -63,7 +63,7 @@ export const MultipleFileUploadBasic: React.FunctionComponent = () => { if (fileUploadShouldFail) { const corruptedFiles = files.map((file) => ({ ...file, lastModified: 'foo' as unknown as number })); // eslint-disable-next-line - setCurrentFiles((prevFiles) => [...prevFiles, ...corruptedFiles as any]); + setCurrentFiles((prevFiles) => [...prevFiles, ...(corruptedFiles as any)]); } else { setCurrentFiles((prevFiles) => [...prevFiles, ...files]); } @@ -101,7 +101,7 @@ export const MultipleFileUploadBasic: React.FunctionComponent = () => { if (fileResult?.loadError) { return ( - {fileResult.loadError.toString()} + {fileResult.loadError.toString()} ); } diff --git a/packages/react-core/src/demos/HelperText.md b/packages/react-core/src/demos/HelperText.md index 7c339cdc3e0..5e2823e067e 100644 --- a/packages/react-core/src/demos/HelperText.md +++ b/packages/react-core/src/demos/HelperText.md @@ -12,20 +12,21 @@ import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon'; ## Demos -### Static variant with static text +### Static helper text -In this demo, the static variant of the helper text item component (the default) is used, and the text itself will always be visible to users and will never change. +In this demo the text content of the helper text item will always be visible to users and will never change. The `aria-describedby` attribute is passed into the text input component and is linked to the `id` of the helper text component. This allows assistive technologies to notify users of the helper text content when the input receives focus, which can be helpful if a user navigates away from and then back to the input. Note that this demo does not validate the text input component. When it would need to be validated, there are other steps that would need to be taken to make it accessible, such as passing in `aria-invalid` and `aria-live` attributes to the appropriate components. -```ts file='./examples/HelperText/HelperTextStaticVariantStaticText.tsx' +```ts file='./examples/HelperText/HelperTextStatic.tsx' + ``` -### Static variant with dynamic text +### Dynamic helper text -In this demo, the static variant of the helper text item component (the default) is used with the `hasIcon` prop passed in when there is an error, and the text itself dynamically updates based on the input value. When the input has a value of `johndoe`, an error is rendered to simulate a username already being taken, while an empty input renders other helper text. When the input is valid, no helper text is rendered. +In this demo the text content of the helper text item dynamically updates based on the input value. When the input has a value of `johndoe`, an error is rendered to simulate a username already being taken, while an empty input renders default text. When the input is valid, no helper text is rendered. The `aria-describedby` attribute is passed into the text input component and is linked to the `id` of the helper text component. Similar to the static variant with static text demo, this allows assistive technologies to notify users of the helper text content when the navigating to the input. @@ -33,16 +34,18 @@ An `aria-live` region is passed into the helper text component, which allows ass The `aria-invalid` attribute is also passed into the text input, which allows assistive technologies to notify users that an input is invalid. When this attribute is true, it's important that users are notified of what is causing the input to be invalid; in this case, `aria-describedby` and `aria-live` help accomplish this. -```ts file='./examples/HelperText/HelperTextStaticVariantDynamicText.tsx' +```ts file='./examples/HelperText/HelperTextDynamic.tsx' + ``` -### Dynamic variant with static text +### Static text and dynamic status + +In this demo the text content of the helper text item remains static and never changes, but the icons and styling will change as the validation of the input changes. -In this demo, the helper text item components have the `isDynamic` prop passed in. While the text content of the components is static, the icons and styling will change as the validation of the input changes. +The `aria-describedby` attribute is passed into the text input component and is linked to the `id` attribute of a helper text item that results in an invalid input. This will allow assistive technologies to only be notified of any outstanding criteria that has not been met when the input receives focus. -The `aria-describedby` attribute is passed into the text input component and is linked to the id attribute of a helper text item that results in an invalid input. This will allow assistive technologies to only be notified of any outstanding criteria that has not been met when the input receives focus. +Similar to the [with dynamic text example](/components/helper-text/react-demos#with-dynamic-text), the `aria-invalid` attribute is passed in, allowing assistive technologies to announce to users when at least 1 item is causing the input to be invalid. -Similar to the static variant with dynamic text example, the `aria-invalid` attribute is passed in, allowing assistive technologies to announce to users when at least 1 item is causing the input to be invalid. +```ts file='./examples/HelperText/HelperTextStaticTextDynamicVariant.tsx' -```ts file='./examples/HelperText/HelperTextDynamicVariantStaticText.tsx' ``` diff --git a/packages/react-core/src/demos/examples/HelperText/HelperTextStaticVariantDynamicText.tsx b/packages/react-core/src/demos/examples/HelperText/HelperTextDynamic.tsx similarity index 88% rename from packages/react-core/src/demos/examples/HelperText/HelperTextStaticVariantDynamicText.tsx rename to packages/react-core/src/demos/examples/HelperText/HelperTextDynamic.tsx index d191a20b43a..9b7489d8b5d 100644 --- a/packages/react-core/src/demos/examples/HelperText/HelperTextStaticVariantDynamicText.tsx +++ b/packages/react-core/src/demos/examples/HelperText/HelperTextDynamic.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Form, FormGroup, FormHelperText, TextInput, HelperText, HelperTextItem } from '@patternfly/react-core'; -export const HelperTextStaticVariantDynamicText: React.FunctionComponent = () => { +export const HelperTextDynamic: React.FunctionComponent = () => { const [value, setValue] = React.useState(''); const [inputValidation, setInputValidation] = React.useState('default'); @@ -35,7 +35,7 @@ export const HelperTextStaticVariantDynamicText: React.FunctionComponent = () => {inputValidation !== 'success' && ( - + {inputValidation === 'default' ? 'Please enter a username' : 'Username already exists'} )} diff --git a/packages/react-core/src/demos/examples/HelperText/HelperTextStaticVariantStaticText.tsx b/packages/react-core/src/demos/examples/HelperText/HelperTextStatic.tsx similarity index 80% rename from packages/react-core/src/demos/examples/HelperText/HelperTextStaticVariantStaticText.tsx rename to packages/react-core/src/demos/examples/HelperText/HelperTextStatic.tsx index a351be55e54..b3ad325fcf9 100644 --- a/packages/react-core/src/demos/examples/HelperText/HelperTextStaticVariantStaticText.tsx +++ b/packages/react-core/src/demos/examples/HelperText/HelperTextStatic.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Form, FormGroup, FormHelperText, TextInput, HelperText, HelperTextItem } from '@patternfly/react-core'; -export const HelperTextStaticVariantStaticText: React.FunctionComponent = () => { +export const HelperTextStaticText: React.FunctionComponent = () => { const [value, setValue] = React.useState(''); const handleInputChange = (_event, inputValue: string) => { @@ -21,7 +21,7 @@ export const HelperTextStaticVariantStaticText: React.FunctionComponent = () => /> - Enter your middle name or your middle initial + Enter your middle name or your middle initial diff --git a/packages/react-core/src/demos/examples/HelperText/HelperTextDynamicVariantStaticText.tsx b/packages/react-core/src/demos/examples/HelperText/HelperTextStaticTextDynamicVariant.tsx similarity index 78% rename from packages/react-core/src/demos/examples/HelperText/HelperTextDynamicVariantStaticText.tsx rename to packages/react-core/src/demos/examples/HelperText/HelperTextStaticTextDynamicVariant.tsx index aa332c7bfa4..084534ead40 100644 --- a/packages/react-core/src/demos/examples/HelperText/HelperTextDynamicVariantStaticText.tsx +++ b/packages/react-core/src/demos/examples/HelperText/HelperTextStaticTextDynamicVariant.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Form, FormGroup, FormHelperText, TextInput, HelperText, HelperTextItem } from '@patternfly/react-core'; -export const HelperTextDynamicVariantDynamicText: React.FunctionComponent = () => { +export const HelperTextStaticTextDynamicVariant: React.FunctionComponent = () => { const [value, setValue] = React.useState(''); const [inputValidation, setInputValidation] = React.useState({ ruleLength: 'indeterminate', @@ -10,8 +10,8 @@ export const HelperTextDynamicVariantDynamicText: React.FunctionComponent = () = const { ruleLength, ruleCharacterTypes } = inputValidation; React.useEffect(() => { - let lengthStatus = ruleLength; - let typeStatus = ruleCharacterTypes; + let lengthVariant = ruleLength; + let typeVariant = ruleCharacterTypes; if (value === '') { setInputValidation({ @@ -22,18 +22,18 @@ export const HelperTextDynamicVariantDynamicText: React.FunctionComponent = () = } if (!/\d/g.test(value)) { - typeStatus = 'error'; + typeVariant = 'error'; } else { - typeStatus = 'success'; + typeVariant = 'success'; } if (value.length < 5) { - lengthStatus = 'error'; + lengthVariant = 'error'; } else { - lengthStatus = 'success'; + lengthVariant = 'success'; } - setInputValidation({ ruleLength: lengthStatus, ruleCharacterTypes: typeStatus }); + setInputValidation({ ruleLength: lengthVariant, ruleCharacterTypes: typeVariant }); }, [value, ruleLength, ruleCharacterTypes]); const handleInputChange = (_event, inputValue: string) => { @@ -57,10 +57,10 @@ export const HelperTextDynamicVariantDynamicText: React.FunctionComponent = () = /> - + Must be at least 5 characters in length - + Must include at least 1 number diff --git a/packages/react-core/src/demos/examples/PasswordStrength/PasswordStrengthDemo.tsx b/packages/react-core/src/demos/examples/PasswordStrength/PasswordStrengthDemo.tsx index 53e9ea1a1d1..4cbf30cb6c8 100644 --- a/packages/react-core/src/demos/examples/PasswordStrength/PasswordStrengthDemo.tsx +++ b/packages/react-core/src/demos/examples/PasswordStrength/PasswordStrengthDemo.tsx @@ -136,13 +136,13 @@ export const PasswordStrengthDemo: React.FunctionComponent = () => { /> - + Must be at least 14 characters - + Cannot contain the word "redhat" - + Must include at least 3 of the following: lowercase letter, uppercase letters, numbers, symbols