Skip to content

Commit

Permalink
feat: Add accessibility scenarios for Fluent UI v9 components #3 (mic…
Browse files Browse the repository at this point in the history
…rosoft#23334)

* Add accessibility scenarios for the Accordion, Button and Popover components

* Change scenario titles

* Use string directly as component prop value

Co-authored-by: Peter Varholak <peter.varholak@gmail.com>

* replace alert with status text and fix warnings

* Replace useCallback with ordinary function

* Revert the title for the scenarios list page

* Removed unnecessary fragment

* Use <Label> components

* Remove not used exports

* Change files

* Replace Calendar buttons scenario with Messenger buttons scenario, add Tooltip scenario and correct some wording

* Add ToggleButton component scenario and replace button element with Button component in Tooltip scenario

* Replace calendar scenario link with messenger scenario link and grammar changes

* Add Link component scenario, and small changes in Accordion

* Add beta version of SplitButton scenario

* Make SplitButton scenario working

* Change label for menu button within SplitButton

* Replace Button with MenuButton in Menu scenario

* Fix error in Menu scenario

* Refactor aria-label for menu button within split button

* Extract Postpone menu item into separate button

* Add tooltip button without triggerAriaAttribute prop

* Add other gender radio option

* Rename disabled links and add nav region

* Add heading and description for toggle buttons

* Add FAQ accordion scenario and change radios in personal form accordion scenario

* Add h1 for accordion scenarios

* Fix lint errors and change Tooltip scenario for more real life examples

* Add icon buttons for Tooltip scenario

* Add x of Y and Submit button to simple form accordion scenario

* Add menu with split item scenario

* Change menu items in menu with split item scenario

* Fix visible label in split button in menu secondary action

* Remove X of Y in Accordion scenario

* Enable dismiss of tooltip with Escape

* Move password hint button between password label and input field

* Reintroduce X of Y for personal form accordion scenario

* Add as='h2' for accordion headers

* Remove X of Y in personal form accordion scenario

* Fix Tooltip error regarding relationship attribute

* Add uncomplete Input scenario

* Add slider component scenario and fix TS error in Input scenario

* Change files

* Fix import errors

* Add screen reader supported validation to input scenario form

* Add lint disable next line for complex object

* Rename labels in slider scenario

* Remove errorneously inserted unused declaration and import

* Replace <input> with <Input> and add <form>

* Remove already not needed import error ignore comments

* Add onBlur validation for Input scenario and changed input requirements and hints

* Replace 'any' with new type

* Fix error messages flickering

* Add aria=invalid in Input scenario

* Fix nickname typo

* Replace h1 heading with h2

* Implement form validation using React Hook Form

* Ignore line max length lint error and add react-hook-form dependency

* Focus error element upon form submission and narrate when already focused

* Change form revalidate to onBlur

* Replace custom validation with React Hook Form library, but narration is not working properly

* toggle alert role to force re-reading of errors

* fix narration on submit

* Fix onblur error narration instead of first error narration on submit

* Remove pubsub dependancy which was moved to root

* Remove the password hint scenario for Tooltip component

* fix lint issues

* fix package

* fix

* cleanup

* Replace native checkbox with Checkbox Fluent UI component

* Fix typo in word 'reminder'

* Add basic Checkbox scenario and consistency fixes in other scenarios

* Add other accessibility relevant variants of Checkbox

* Add temporary version of RadioGroup scenario and replace native HTML elements with Fluent UI components in some other scenarios

* Replace native HTML checkbox elements with Fluent UI components

* Replace native HTML input with Fluent UI Input component

* Add disabled items variant for RadioGroup and some nit picking fixes in other scenarios

* Add disabled RadioGroup variant

* Replace tabindex values from 0 to -1

* Add Spinner, Switch and TabList components scenarios

* Add disabled tab for TabList scenario

* Add Notifications tab for TabList scenario

* Add Textarea scenarioand changes to TabList scenario

* Make checkboxes appear on a new line

* Make textareas appear on a new line

* Add vertical and overflow variants of TabList scenarios and small refactoring

* Fix broken page with scenarios

* Add Input with placeholder attribute

* Add disabled Input example and preparation for readonly Input

* Add lint ignore for long regex lines

* Make <input> readonly and small refactoring of security code generating function

* Proper setting of readOnly prpprop on Input component scenario

* Add validation for Textarea scenario, add more variants and fix Input validation behavior

* include accessibility scenario stories in the public docs

* fix imports

* fix imports

* chore: add index.stories.tsx

* Add FAQ accordion story

* Add first half of v9 accessibility scenarios

* Add remaining v9 accessibility scenarios

* Make Send button not disabledFocusable

* Make error message alerting more robust and remove 500ms setTimeout for first error field focus

* Make overflow button in TabList not selected

* Fix email validating birthDay instead

* Fix email validation and some code cleaning

* Fix first error field focus upon form submission problem

* Make Input and Textarea scenarios more like a real example

* Fix email validation when not required

* Fix Input and Textarea scenario form validation and text changes

* Fix invalid disabled textarea bug

* fix scenarios link, remove them from sidebar again

* Update apps/public-docsite-v9/.storybook/main.utils.js

Co-authored-by: Micah Godbolt <micahgodbolt@gmail.com>

* fix md file change

* temporarily add `as` to MenuItem usage

Co-authored-by: Peter Varholak <peter.varholak@gmail.com>
Co-authored-by: Juraj Kapsiar <jukapsia@microsoft.com>
Co-authored-by: Adam Samec <asamec@microsoft.com>
Co-authored-by: Bernardo Sunderhus <bernardo.sunderhus@gmail.com>
Co-authored-by: Juraj Kapsiar <jurokapsiar@gmail.com>
Co-authored-by: Micah Godbolt <micahgodbolt@gmail.com>
  • Loading branch information
7 people authored Oct 24, 2022
1 parent 9dd5a3b commit a560d5f
Show file tree
Hide file tree
Showing 26 changed files with 1,530 additions and 366 deletions.
2 changes: 1 addition & 1 deletion apps/public-docsite-v9/.storybook/manager-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
Usign suggested temporary workaround until storybook gets proper support
See https://github.com/microsoft/fluentui/blob/master/rfcs/convergence/authoring-stories.md#10-internal-stories-for-testing
*/
[id*='accessibility-scenario'] {
[id*='accessibility-stories'] {
display: none !important;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Add uncomplete Input scenario",
"packageName": "@fluentui/react-components",
"email": "adam.samec@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
Usign suggested temporary workaround until storybook gets proper support
See https://github.com/microsoft/fluentui/blob/master/rfcs/convergence/authoring-stories.md#10-internal-stories-for-testing
*/
[id*='accessibility-scenario'] {
[id*='accessibility-stories'] {
display: none !important;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,79 @@ import {
Button,
Input,
Label,
Radio,
RadioGroup,
Checkbox,
} from '@fluentui/react-components';

import { Scenario } from './utils';

export const PersonalFormAccordionAccessibilityScenario: React.FunctionComponent = () => {
export const PersonalFormAccordion = () => {
const [isSubmitted, setIsSubmitted] = React.useState(false);

React.useEffect(() => {
if (isSubmitted) {
document.getElementById('formSubmittedText')?.focus();
}
}, [isSubmitted]);

const onSubmit = (event: React.BaseSyntheticEvent) => {
event.preventDefault();
setIsSubmitted(true);
};

return (
<Scenario pageTitle="Personal form accordion">
<h1>Personal form</h1>
<form>
<Accordion defaultOpenItems="basicInfo">
<AccordionItem value="basicInfo">
<AccordionHeader as="h2">Basic information</AccordionHeader>
<AccordionPanel>
<Label htmlFor="name">Name:</Label>
<Input type="text" id="name" name="name" />
<Label htmlFor="email">Email:</Label>
<Input type="text" id="email" name="email" />
<fieldset>
<legend>Age</legend>
<input type="radio" id="ageClass1" name="age" value="ageClass1" />
<Label htmlFor="ageClass1">Below 18</Label>
<input type="radio" id="ageClass2" name="age" value="ageClass2" />
<Label htmlFor="ageClass2">Between 18 and 30</Label>
<input type="radio" id="ageClass3" name="age" value="ageClass3" />
<Label htmlFor="ageClass3">Over 30</Label>
</fieldset>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="residence">
<AccordionHeader as="h2">Residence</AccordionHeader>
<AccordionPanel>
<Label htmlFor="street">Street:</Label>
<Input type="text" id="street" name="street" />
<Label htmlFor="city">City:</Label>
<Input type="text" id="city" name="city" />
<Label htmlFor="country">Country:</Label>
<Input type="text" id="country" name="country" />
</AccordionPanel>
</AccordionItem>
<AccordionItem value="hobbies">
<AccordionHeader as="h2">Hobbies</AccordionHeader>
<AccordionPanel>
<div role="group" aria-label="Hobbies">
<input type="checkbox" id="books" name="hobbies" value="books" />
<Label htmlFor="books">books</Label>
<input type="checkbox" id="sports" name="hobbies" value="sports" />
<Label htmlFor="sports">sports</Label>
<input type="checkbox" id="music" name="hobbies" value="music" />
<Label htmlFor="music">music</Label>
<input type="checkbox" id="travelling" name="hobbies" value="travelling" />
<Label htmlFor="travelling">travelling</Label>
</div>
</AccordionPanel>
</AccordionItem>
</Accordion>
<Button>Submit</Button>
</form>
{!isSubmitted ? (
<form onSubmit={onSubmit}>
<Accordion defaultOpenItems="basicInfo">
<AccordionItem value="basicInfo">
<AccordionHeader as="h2">Basic information</AccordionHeader>
<AccordionPanel>
<Label htmlFor="name">Name:</Label>
<Input type="text" id="name" name="name" />
<Label htmlFor="email">Email:</Label>
<Input type="email" id="email" name="email" />
<Label id="ageLabel">Your age:</Label>
<RadioGroup defaultValue="ageClass1" aria-labelledby="ageLabel">
<Radio value="ageClass1" label="Under 16" />
<Radio value="ageClass2" label="Between 16 and 50" />
<Radio value="ageClass3" label="Over 50" />
</RadioGroup>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="residence">
<AccordionHeader as="h2">Residence</AccordionHeader>
<AccordionPanel>
<Label htmlFor="street">Street:</Label>
<Input type="text" id="street" name="street" />
<Label htmlFor="city">City:</Label>
<Input type="text" id="city" name="city" />
<Label htmlFor="country">Country:</Label>
<Input type="text" id="country" name="country" />
</AccordionPanel>
</AccordionItem>
<AccordionItem value="hobbies">
<AccordionHeader as="h2">Hobbies</AccordionHeader>
<AccordionPanel>
<div role="group" aria-labelledby="hobbiesLabel">
<Label id="hobbiesLabel">Please select your hobbies:</Label>
<Checkbox label="Books" />
<Checkbox label="Sports" />
<Checkbox label="Music" />
<Checkbox label="Travelling" />
</div>
</AccordionPanel>
</AccordionItem>
</Accordion>
<Button type="submit">Submit</Button>
</form>
) : (
<p id="formSubmittedText" tabIndex={-1}>
The form would have been submitted.
</p>
)}
</Scenario>
);
};

export default {
title: 'Accessibility Scenarios / Personal form accordion',
id: 'accordion-accessibility-scenario',
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Accordion, AccordionHeader, AccordionItem, AccordionPanel } from '@flue

import { Scenario } from './utils';

export const FAQAccordionAccessibilityScenario: React.FunctionComponent = () => {
export const FAQAccordion: React.FunctionComponent = () => {
return (
<Scenario pageTitle="FAQ accordion">
<h1>Frequently asked questions about Windows</h1>
Expand Down Expand Up @@ -53,8 +53,3 @@ export const FAQAccordionAccessibilityScenario: React.FunctionComponent = () =>
</Scenario>
);
};

export default {
title: 'Accessibility Scenarios / FAQ accordion',
id: 'accordion-faq-accessibility-scenario',
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { Button } from '@fluentui/react-components';

import { Scenario } from './utils';

export const MessengerButtonsAccessibilityScenario: React.FunctionComponent = () => {
const [sendButtonDisabled, setSendButtonDisabled] = React.useState<boolean | undefined>(true);
export const MessengerButtons: React.FunctionComponent = () => {
const [deleteButtonDisabled, setDeleteButtonDisabled] = React.useState<boolean | undefined>(true);
const [increaseFontButtonDisabled, setIncreaseFontButtonDisabled] = React.useState<boolean | undefined>(undefined);
const [decreaseFontButtonDisabled, setDecreaseFontButtonDisabled] = React.useState<boolean | undefined>(true);
Expand All @@ -23,13 +22,16 @@ export const MessengerButtonsAccessibilityScenario: React.FunctionComponent = ()

const resetMessage = () => {
setMessage('');
setSendButtonDisabled(true);
setDeleteButtonDisabled(true);
};

const onSendButtonClick = () => {
if (message.length > 0) {
setStatusText('Message has been sent.');
} else {
setStatusText('Please type a message.');
}
resetMessage();
setStatusText('Message has been sent.');
};
const onDeleteButtonClick = () => {
resetMessage();
Expand Down Expand Up @@ -59,50 +61,38 @@ export const MessengerButtonsAccessibilityScenario: React.FunctionComponent = ()
const onMessageTextareaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = event.target.value;
if (value.length > 0) {
setSendButtonDisabled(undefined);
setDeleteButtonDisabled(undefined);
setStatusText('');
} else {
setSendButtonDisabled(true);
setDeleteButtonDisabled(true);
}
setMessage(value);
};

return (
<Scenario pageTitle="Messenger buttons">
<Button disabledFocusable={sendButtonDisabled} onClick={onSendButtonClick}>
Send
</Button>
<Button disabledFocusable={deleteButtonDisabled} onClick={onDeleteButtonClick}>
Delete
</Button>
<Button ref={increaseFontButtonRef} disabled={increaseFontButtonDisabled} onClick={onIncreaseFontButtonClick}>
Increase font size
</Button>
<Button ref={decreaseFontButtonRef} disabled={decreaseFontButtonDisabled} onClick={onDecreaseFontButtonClick}>
Decrease font size
</Button>
<div>
<textarea
name="message"
rows={3}
cols={50}
placeholder="Enter message here...."
aria-label="Message"
onChange={onMessageTextareaChange}
value={message}
style={messageStyle}
/>
</div>
<p>
<span aria-live="polite">{statusText}</span>
</p>
<textarea
name="message"
rows={3}
cols={50}
placeholder="Enter message here...."
aria-label="Message"
onChange={onMessageTextareaChange}
value={message}
style={messageStyle}
/>
<Button onClick={onSendButtonClick}>Send</Button>
<Button disabledFocusable={deleteButtonDisabled} onClick={onDeleteButtonClick}>
Delete
</Button>

<p aria-live="polite">{statusText}</p>
</Scenario>
);
};

export default {
title: 'Accessibility Scenarios / Messenger buttons',
id: 'button-accessibility-scenario',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as React from 'react';

import { Button, Checkbox, CheckboxOnChangeData, Label } from '@fluentui/react-components';

import { Scenario } from './utils';

export const QuestionnaireAboutFoodCheckboxes: React.FunctionComponent = () => {
const [isAppleSelected, setIsAppleSelected] = React.useState(false);
const [isBananaSelected, setIsBananaSelected] = React.useState(false);
const [isOrangeSelected, setIsOrangeSelected] = React.useState(false);
const [isSpecialDietSelected, setIsSpecialDietSelected] = React.useState(false);
const [isSubmitted, setIsSubmitted] = React.useState(false);

const getSpecialDietDisabled = () => {
return isSpecialDietSelected ? false : true;
};

React.useEffect(() => {
if (isSubmitted) {
document.getElementById('formSubmittedText')?.focus();
}
}, [isSubmitted]);

const onSubmit = (event: React.BaseSyntheticEvent) => {
event.preventDefault();
setIsSubmitted(true);
};

return (
<Scenario pageTitle="Checkboxes in questionnaire about food">
<h1>Questionnaire about food</h1>
{!isSubmitted ? (
<form onSubmit={onSubmit}>
<div role="group" aria-labelledby="foodLabel">
<Label id="foodLabel">Select food that you like:</Label>

<Checkbox
checked={
isAppleSelected && isBananaSelected && isOrangeSelected
? true
: !(isAppleSelected || isBananaSelected || isOrangeSelected)
? false
: 'mixed'
}
onChange={(event: React.ChangeEvent, data: CheckboxOnChangeData) => {
setIsAppleSelected(!!data.checked);
setIsBananaSelected(!!data.checked);
setIsOrangeSelected(!!data.checked);
}}
label="All fruits"
/>
<Checkbox
checked={isAppleSelected}
onChange={() => setIsAppleSelected(checked => !checked)}
label="Apple"
/>
<Checkbox
checked={isBananaSelected}
onChange={() => setIsBananaSelected(checked => !checked)}
label="Banana"
/>
<Checkbox
checked={isOrangeSelected}
onChange={() => setIsOrangeSelected(checked => !checked)}
label="Orange"
/>
</div>

<Checkbox
checked={isSpecialDietSelected}
onChange={() => setIsSpecialDietSelected(checked => !checked)}
label="I am on special diet"
/>

<div role="group" aria-labelledby="cannotEatLabel">
<Label id="cannotEatLabel">I cannot eat the following:</Label>
<Checkbox disabled={getSpecialDietDisabled()} label="Sugar" />
<Checkbox disabled={getSpecialDietDisabled()} label="Meat" />
<Checkbox disabled={getSpecialDietDisabled()} label="Dairy products" />
</div>
<Checkbox
required
label={
<>
I accept the <a href="#">terms and conditions</a>.
</>
}
/>
<Button type="submit">Submit</Button>
</form>
) : (
<p id="formSubmittedText" tabIndex={-1}>
The form would have been submitted.
</p>
)}
</Scenario>
);
};
Loading

0 comments on commit a560d5f

Please sign in to comment.