Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 19 additions & 23 deletions cypress/component/TextArea.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {Basic} from '../../modules/react/text-area/stories/examples/Basic';
import {Caution} from '../../modules/react/text-area/stories/examples/Caution';
import {Error} from '../../modules/react/text-area/stories/examples/Error';
import {Disabled} from '../../modules/react/text-area/stories/examples/Disabled';
import {Placeholder} from '../../modules/react/text-area/stories/examples/Placeholder';

Expand All @@ -9,34 +7,32 @@ const getTextArea = () => {
};

describe('Text Area', () => {
[Basic, Caution, Error].forEach(Example => {
context(`given the '${Example.name}' story is rendered`, () => {
context(`given the 'Basic' story is rendered`, () => {
beforeEach(() => {
cy.mount(<Basic />);
});

it('should not have any axe errors', () => {
cy.checkA11y();
});

context('when clicked', () => {
beforeEach(() => {
cy.mount(<Example />);
getTextArea().click();
});

it('should not have any axe errors', () => {
cy.checkA11y();
it('should be focused', () => {
getTextArea().should('be.focused');
});
});

context('when clicked', () => {
beforeEach(() => {
getTextArea().click();
});

it('should be focused', () => {
getTextArea().should('be.focused');
});
context('when text is entered', () => {
beforeEach(() => {
getTextArea().clear().type('Test');
});

context('when text is entered', () => {
beforeEach(() => {
getTextArea().clear().type('Test');
});

it('should reflect the text typed', () => {
getTextArea().should('have.value', 'Test');
});
it('should reflect the text typed', () => {
getTextArea().should('have.value', 'Test');
});
});
});
Expand Down
42 changes: 19 additions & 23 deletions cypress/component/TextInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {Basic} from '../../modules/react/text-input/stories/examples/Basic';
import {Caution} from '../../modules/react/text-input/stories/examples/Caution';
import {Error} from '../../modules/react/text-input/stories/examples/Error';
import {Disabled} from '../../modules/react/text-input/stories/examples/Disabled';
import {Placeholder} from '../../modules/react/text-input/stories/examples/Placeholder';

Expand All @@ -9,34 +7,32 @@ const getTextInput = () => {
};

describe('TextInput', () => {
[Basic, Caution, Error].forEach(Example => {
context(`given the '${Example.name}' story is rendered`, () => {
context(`given the 'Basic' story is rendered`, () => {
beforeEach(() => {
cy.mount(<Basic />);
});

it('should not have any axe errors', () => {
cy.checkA11y();
});

context('when clicked', () => {
beforeEach(() => {
cy.mount(<Example />);
getTextInput().click();
});

it('should not have any axe errors', () => {
cy.checkA11y();
it('should be focused', () => {
getTextInput().should('be.focused');
});
});

context('when clicked', () => {
beforeEach(() => {
getTextInput().click();
});

it('should be focused', () => {
getTextInput().should('be.focused');
});
context('when text is entered', () => {
beforeEach(() => {
getTextInput().clear().type('Test');
});

context('when text is entered', () => {
beforeEach(() => {
getTextInput().clear().type('Test');
});

it('should reflect the text typed', () => {
getTextInput().should('have.value', 'Test');
});
it('should reflect the text typed', () => {
getTextInput().should('have.value', 'Test');
});
});
});
Expand Down
29 changes: 14 additions & 15 deletions modules/docs/mdx/accessibility/AriaLiveRegions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {AriaLiveRegion} from '@workday/canvas-kit-react/common';
import {FilterListWithLiveStatus} from './examples/AriaLiveRegions/FilterListWithLiveStatus';
import {VisibleLiveRegion} from './examples/AriaLiveRegions/VisibleLiveRegion';
import {HiddenLiveRegion} from './examples/AriaLiveRegions/HiddenLiveRegion';
import {TextInputWithLiveError} from './examples/AriaLiveRegions/TextInputWithLiveError';
import {CommentBoxWithCharLimit} from './examples/AriaLiveRegions/CommentBoxWithCharLimit';

<Meta title="Guides/Accessibility/ARIA Live Regions" component={AriaLiveRegion} />

Expand Down Expand Up @@ -61,21 +61,20 @@ describe how many items in the list are shown.

<ExampleCodeBlock code={FilterListWithLiveStatus} />

## Text input with live inline error
## Debouncing an `AriaLiveRegion`: `TextArea` with character limit

In this example, a live region is applied to the inline error message that will appear below the
text input. Listen for the screen reader to automatically describe the error message as you leave
the input field blank.
Using a live region to announce the character count of a text area can help screen reader users
track their progress. However, announcing on every keystroke would be extremely disruptive—imagine
hearing "5 of 200 characters... 6 of 200 characters... 7 of 200 characters" for each letter typed!
In this example, we've implemented debouncing to wait 2 seconds after the user stops typing before
announcing the count.

**Note:** Use this example with discretion. Using live regions for automatically announcing form
errors to screen reader users can be a nice experience for simple forms with a very limited number
of error conditions. As forms increase in complexity, live regions on each error message can become
increasingly distracting and disruptive to the experience, especially if users are trying to first
understand the information that is required of them to complete the task.
**Note:** Turn on a screen reader for this experience.

**Note:** The `<AriaLiveRegion>` component is used inside of the `Hint` to ensure the live region
remains in the browser DOM at all times. The `Hint` is only rendered in the DOM when it contains
content, so it will not work reliably as a live region for screen readers using the
`as={AriaLiveRegion}` prop.
- Used the `as={AccessibleHide}` prop to hide the live region from view with CSS
- The live region will only update when a 2 second timer expires after the last keystroke
- If users have reached the maximum number of characters, the live region will update immediately to
inform users that they have reached the limit
- The live region will be cleared on blur events when users leave the field

<ExampleCodeBlock code={TextInputWithLiveError} />
<ExampleCodeBlock code={CommentBoxWithCharLimit} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextArea} from '@workday/canvas-kit-react/text-area';
import {AriaLiveRegion, AccessibleHide} from '@workday/canvas-kit-react/common';

const MAX_CHARACTERS = 200;
const DEBOUNCE_DELAY = 2000; // 2 seconds after user stops typing

export const CommentBoxWithCharLimit = () => {
const [value, setValue] = React.useState('');
const [liveUpdateStr, setLiveUpdateStr] = React.useState('');
const hintTextStr = `${value.length} of ${MAX_CHARACTERS} characters`;

const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setValue(event.target.value);
};

const handleBlur = () => {
setLiveUpdateStr('');
};

React.useEffect(() => {
// Immediately announce when limit is reached (bypass debounce)
if (value.length === MAX_CHARACTERS) {
setLiveUpdateStr(`Character limit reached. ${value.length} of ${MAX_CHARACTERS} characters`);
return;
}

// Otherwise, debounce the updates
const timer = setTimeout(() => {
setLiveUpdateStr(hintTextStr);
}, DEBOUNCE_DELAY);
return () => clearTimeout(timer);
}, [hintTextStr]);

return (
<FormField>
<FormField.Label>Comments</FormField.Label>
<FormField.Field>
<FormField.Input
as={TextArea}
onChange={handleChange}
onBlur={handleBlur}
value={value}
maxLength={MAX_CHARACTERS}
/>
<FormField.Hint>{hintTextStr}</FormField.Hint>
<AriaLiveRegion as={AccessibleHide}>{liveUpdateStr}</AriaLiveRegion>
</FormField.Field>
</FormField>
);
};
Loading
Loading