Skip to content

Commit

Permalink
a11y: fix August pass issues (#9388)
Browse files Browse the repository at this point in the history
* a11y: add label for Provision modal Instructions field

Fixes [75910](https://fuselabs.visualstudio.com/Composer/_queries/edit/75910)

* a11y: fix Source URLfield label aria for CreateQnAForm

Fixes [75891](https://fuselabs.visualstudio.com/Composer/_queries/edit/75891)

* a11y: mark Knowledge base name required in EditQnAFormModal

Fixes [75867](https://fuselabs.visualstudio.com/Composer/_queries/edit/75867)

* a11y: Make bot languages accessible

Fixes [75868](https://fuselabs.visualstudio.com/Composer/_queries/edit/75868)

List of changes:
- Add list and list-item roles for the Bot Languages list
- Add aria-labels for the list
- Add keyboard navigation support for the list
- Add focus state indication for list-items
- Show list-item action buttons on list item focus

* a11y: announce trigger creation on TriggerCreationModal submit action

Fixes [75735](https://fuselabs.visualstudio.com/Composer/_queries/edit/75735)

* a11y: align Add ProjectTreeHeader menu button announcement with visible content

Fixes [75711](https://fuselabs.visualstudio.com/Composer/_queries/edit/75711)

Make title to start with visible content, so the button is announced as `"Add actions menu button, collapsed"`

* a11y: remove redundant nested focusable elements from FileSelector table

Fixes [75696](https://fuselabs.visualstudio.com/Composer/_queries/edit/75696)

Removed hacks were previously needed in Fluent v7 to make the cell contents accessible via keyboard.

* a11y: add missing aria-label for GetAppInfoFromPublishProfileDialog profile field

Fixes [75812](https://fuselabs.visualstudio.com/Composer/_queries/edit/75812)

* a11y: rework keyboard navigation for AddLanguageModal

Fixes [75813](https://fuselabs.visualstudio.com/Composer/_queries/edit/75813)

In addition fixes `aria-labelledby` usage

* a11y: add aria-label for codeeditor open in popup button

Fixes [75783](https://fuselabs.visualstudio.com/Composer/_queries/edit/75783)

* a11y: announce empty search results in code editor toolbar menus

Fixes [75831](https://fuselabs.visualstudio.com/Composer/_queries/edit/75831)

* a11y: make dialog schema labels more descriptive

Fixes [75846](https://fuselabs.visualstudio.com/Composer/_queries/edit/75846)

* a11y: link intellisense input with label

Fixes [75853](https://fuselabs.visualstudio.com/Composer/_queries/edit/75853)

* Build extensions
  • Loading branch information
OEvgeny authored Sep 23, 2022
1 parent b7e8eb0 commit 7db4a77
Show file tree
Hide file tree
Showing 21 changed files with 145 additions and 56 deletions.
11 changes: 9 additions & 2 deletions Composer/packages/adaptive-form/src/components/AddButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ export const actionButtonStyles: IButtonStyles = {

type Props = {
disabled?: boolean;
ariaLabel?: string;
onClick: React.MouseEventHandler<unknown>;
};

export const AddButton = ({ children, onClick, disabled }: React.PropsWithChildren<Props>) => {
export const AddButton = ({ ariaLabel, children, onClick, disabled }: React.PropsWithChildren<Props>) => {
return (
<ButtonContainer>
<ActionButton data-testid="add-button" disabled={disabled} styles={actionButtonStyles} onClick={onClick}>
<ActionButton
ariaLabel={ariaLabel}
data-testid="add-button"
disabled={disabled}
styles={actionButtonStyles}
onClick={onClick}
>
{children ?? formatMessage('Add new')}
</ActionButton>
</ButtonContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const FieldLabel: React.FC<FieldLabelProps> = (props) => {
>
<Label
htmlFor={id}
id={`${id}-field-label`}
required={required}
styles={{
root: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { jsx } from '@emotion/react';
import React from 'react';
import { FieldProps } from '@bfc/extension-client';
import formatMessage from 'format-message';

import { FieldLabel } from '../../FieldLabel';
import { getPropertyItemProps, useObjectItems } from '../../../utils/objectUtils';
Expand Down Expand Up @@ -51,7 +52,12 @@ const OpenObjectField: React.FC<FieldProps<{
/>
);
})}
{additionalProperties && <AddButton onClick={onClick} />}
{additionalProperties && (
<AddButton
ariaLabel={label ? formatMessage('Add new value to the {label} field', { label }) : undefined}
onClick={onClick}
/>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export const FileSelector: React.FC<FileSelectorProps> = (props) => {
const renderNameColumn = (file: File, index: number | undefined) => {
const iconName = getFileIconName(file);
return (
<div data-is-focusable css={tableCell}>
<div css={tableCell}>
{index == selectedIndex && editMode !== EditMode.NONE ? (
<TextField
autoFocus
Expand Down Expand Up @@ -366,8 +366,8 @@ export const FileSelector: React.FC<FileSelectorProps> = (props) => {
!focusedStorageFolder.writable ||
item.type !== FileTypes.FOLDER ||
index !== selectedIndex ? null : (
<div data-is-focusable css={tableCell}>
<div css={content} tabIndex={-1}>
<div css={tableCell}>
<div css={content}>
<IconButton
ariaLabel={formatMessage('Edit')}
iconProps={{ iconName: 'Edit' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { DialogWrapper, DialogTypes } from '@bfc/ui-shared';
import { hiddenContentStyle, mergeStyles } from '@fluentui/react/lib/Styling';
import { Announced } from '@fluentui/react/lib/Announced';
import { useId } from '@fluentui/react-hooks';
import { FocusZoneDirection, FocusZone } from '@fluentui/react/lib/FocusZone';

import { MultiLanguagesDialog } from '../../constants';

Expand Down Expand Up @@ -166,7 +167,7 @@ const AddLanguageModal: React.FC<IAddLanguageModalProps> = (props) => {
onChange={onDefaultLanguageChange}
/>
</StackItem>
<StackItem aria-labelledBy={translationsLabelId} grow={0}>
<StackItem aria-labelledby={translationsLabelId} grow={0}>
<div id={translationsLabelId}>
<div className={screenReaderOnly}>{formatMessage('Bot language translations')}</div>
<Announced
Expand Down Expand Up @@ -196,21 +197,26 @@ const AddLanguageModal: React.FC<IAddLanguageModalProps> = (props) => {
{ languagesCount: formData.languages.length }
)}
/>
<div className={screenReaderOnly}>
{formatMessage('Press tab then up and down arrow keys to navigate the search results')}
</div>
</div>
<Label htmlFor={searchBoxId} id={searchBoxLabelId}>
{MultiLanguagesDialog.ADD_DIALOG.selectionTitle}
</Label>
<SearchBox
disableAnimation
aria-labelledBy={searchBoxLabelId}
aria-labelledby={searchBoxLabelId}
id={searchBoxId}
placeholder={MultiLanguagesDialog.ADD_DIALOG.searchPlaceHolder}
styles={{ root: { width: 300 } }}
onChange={onSearch}
/>
<ScrollablePane styles={scrollablePaneStyles}>
<div role="list">{languageCheckBoxList}</div>
</ScrollablePane>
<FocusZone direction={FocusZoneDirection.vertical}>
<ScrollablePane styles={scrollablePaneStyles}>
<div role="list">{languageCheckBoxList}</div>
</ScrollablePane>
</FocusZone>
</StackItem>
<StackItem>
<Checkbox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const ProjectTreeHeader: React.FC<ProjectTreeHeaderProps> = ({
const addCommandBtn = (
<CommandButton
data-is-focusable
ariaLabel={formatMessage('Actions')}
ariaLabel={formatMessage('Add actions')}
className="project-tree-header-more-btn"
css={buttonStyle}
data-testid="projectTreeHeaderMoreButton"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ export const CreateQnAFromUrl: React.FC<CreateQnAFromFormProps> = (props) => {
</p>
</Stack>
<Stack maxHeight={400} styles={urlStackStyle}>
<Text styles={knowledgeBaseStyle}>{formatMessage('Source URL')}</Text>
<div key={`add${formData.locales[0]}InCreateQnAFromUrlModal`} css={urlPairStyle}>
<TextField
data-testid={`add${formData.locales[0]}InCreateQnAFromUrlModal`}
errorMessage={formDataErrors.urls[0]}
label={formatMessage('Source URL')}
placeholder={formatMessage('Enter a URL')}
styles={textFieldUrl}
value={formData.urls[0]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const EditQnAFromScratchModal: React.FC<EditQnAFromScratchModalProps> = (
<div css={dialogWindow}>
<Stack>
<TextField
required
data-testid={`knowledgeLocationTextField-name`}
errorMessage={formErrors.name}
label={formatMessage('Knowledge base name')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const EditQnAFromUrlModal: React.FC<EditQnAFromUrlModalProps> = (props) =
<div css={dialogWindow}>
<Stack>
<TextField
required
data-testid={`knowledgeLocationTextField-name`}
errorMessage={formErrors.name}
label={formatMessage('Knowledge base name')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Dialog, DialogType, DialogFooter } from '@fluentui/react/lib/Dialog';
import { PrimaryButton, DefaultButton } from '@fluentui/react/lib/Button';
import { SDKKinds, RegexRecognizer } from '@bfc/shared';
import { useRecoilValue } from 'recoil';
import { useTriggerConfig, TriggerUISchema } from '@bfc/extension-client';
import { useTriggerConfig, TriggerUISchema, useShellApi } from '@bfc/extension-client';

import { TriggerFormData, TriggerFormDataErrors } from '../../utils/dialogUtil';
import { userSettingsState } from '../../recoilModel/atoms';
Expand Down Expand Up @@ -49,6 +49,9 @@ interface TriggerCreationModalProps {
}

export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props) => {
const {
shellApi: { announce },
} = useShellApi();
const { isOpen, onDismiss, onSubmit, dialogId, projectId } = props;
const dialogs = useRecoilValue(dialogsSelectorFamily(projectId));
const triggerUISchema = useTriggerConfig();
Expand Down Expand Up @@ -78,6 +81,7 @@ export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props)
}
onDismiss();
onSubmit(dialogId, { ...formData, $kind: selectedType, label: resolveSchemaLabel(selectedType, triggerUISchema) });
announce(formatMessage(`The trigger {triggerName} has been created`, { triggerName: formData.intent }));
TelemetryClient.track('AddNewTriggerCompleted', { kind: formData.$kind });
};

Expand Down
47 changes: 39 additions & 8 deletions Composer/packages/client/src/pages/botProject/BotLanguage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ import { useRecoilValue } from 'recoil';
import { ActionButton } from '@fluentui/react/lib/Button';
import formatMessage from 'format-message';
import cloneDeep from 'lodash/cloneDeep';
import { FontSizes, FontWeights } from '@fluentui/react/lib/Styling';
import {
getFocusStyle,
FontSizes,
FontWeights,
mergeStyles,
hiddenContentStyle,
getTheme,
} from '@fluentui/react/lib/Styling';
import { NeutralColors, SharedColors } from '@fluentui/theme';
import { useId } from '@fluentui/react-hooks';

import { dispatcherState, settingsState } from '../../recoilModel';
import { languageListTemplates } from '../../components/MultiLanguage';
Expand Down Expand Up @@ -56,15 +64,17 @@ const languageRowContainer = css`
display: flex;
height: 30px;
line-height: 30px;
&:hover .ms-Button,
&:focus .ms-Button,
&:focus-within .ms-Button {
visibility: visible;
}
`;

const languageItemContainer = css`
display: flex;
width: 100%;
justify-content: space-between;
&:hover .ms-Button {
visibility: visible;
}
`;

const languageButton = {
Expand Down Expand Up @@ -93,6 +103,10 @@ const languageButtonContainer = css`
width: 240px;
`;

const screenReaderOnlyClassName = mergeStyles(hiddenContentStyle);

const focusClassName = mergeStyles(getFocusStyle(getTheme(), { inset: -3 }));

// -------------------- BotLanguage -------------------- //

type BotLanguageProps = {
Expand Down Expand Up @@ -149,6 +163,8 @@ export const BotLanguage: React.FC<BotLanguageProps> = (props) => {
const dl = languageListOptions.splice(index, 1)[0];
languageListOptions.unshift(dl);

const languageRowAriaId = useId('language-row-aria-');

return (
<Fragment>
<div css={botLanguageContainerStyle}>
Expand All @@ -157,18 +173,33 @@ export const BotLanguage: React.FC<BotLanguageProps> = (props) => {
'List of languages that bot will be able to understand (User input) and respond to (Bot responses). To make this bot available in other languages, click ‘Manage languages’ to create a copy of the default language, and translate the content into the new language.'
)}
</div>
<div css={botLanguageFieldStyle}>
<div
aria-label={formatMessage('List of languages that bot will be able to understand and respond to')}
css={botLanguageFieldStyle}
role="list"
>
{languageListOptions.map((l) => (
<div key={l.key} css={languageRowContainer}>
<div
key={l.key}
aria-labelledby={`${languageRowAriaId}-${l.key}`}
className={focusClassName}
css={languageRowContainer}
role="listitem"
// Needed for keyboard navigation
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0}
>
{l.key === defaultLanguage && (
<div css={languageTextStyle} data-testid={'defaultLanguage'}>
<div css={languageTextStyle} data-testid={'defaultLanguage'} id={`${languageRowAriaId}-${l.key}`}>
{l.text}
<span css={defaultLanguageTextStyle}> {formatMessage('DEFAULT LANGUAGE')}</span>
</div>
)}
{l.key !== defaultLanguage && (
<div css={languageItemContainer}>
<div css={languageItem}>{l.text}</div>
<div css={languageItem} id={`${languageRowAriaId}-${l.key}`}>
{l.text} <span className={screenReaderOnlyClassName}>{formatMessage('Language')}</span>
</div>
<div css={languageButtonContainer}>
<ActionButton
data-testid={'setDefaultLanguage'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const GetAppInfoFromPublishProfileDialog: React.FC<Props> = (props) => {
>
<div css={{ height: '100px' }}>
<Dropdown
ariaLabel={formatMessage('Publishing profile')}
data-testid={'publishProfileDropdown'}
errorMessage={publishTargetsErrorMessage}
options={publishTargetOptions}
Expand Down
4 changes: 2 additions & 2 deletions Composer/packages/client/src/pages/home/CardWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const CardWidget: React.FC<CardWidgetProps> = ({
return (
<div ref={forwardedRef} css={home.cardWrapper} {...rest}>
<a
aria-labelledBy={labelId}
aria-labelledby={labelId}
css={[itemContainerWrapper(disabled), styles.container]}
href={href}
target={target}
Expand All @@ -93,7 +93,7 @@ export const CardWidget: React.FC<CardWidgetProps> = ({
<div css={styles.content}>{content}</div>
</div>
{moreLinkText && (
<Link aria-labelledBy={labelId} css={styles.moreLink} role="link">
<Link aria-labelledby={labelId} css={styles.moreLink} role="link">
{moreLinkText}
</Link>
)}
Expand Down
46 changes: 23 additions & 23 deletions Composer/packages/intellisense/src/components/Intellisense.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { Announced } from '@fluentui/react';
import React, { AriaAttributes, useCallback, useEffect, useState } from 'react';
import { Announced } from '@fluentui/react/lib/Announced';
import React, { AriaAttributes, useCallback, useMemo } from 'react';
import formatMessage from 'format-message';
import { useId } from '@fluentui/react-hooks';

import { useLanguageServer } from '../hooks/useLanguageServer';
import { checkIsOutside } from '../utils/uiUtils';

import { CompletionList } from './CompletionList';
import { useId } from '@fluentui/react-hooks';

type AriaState = Pick<AriaAttributes, 'aria-autocomplete' | 'aria-expanded' | 'aria-activedescendant' | 'aria-owns'> & {
type AriaState = Pick<
AriaAttributes,
'aria-autocomplete' | 'aria-activedescendant' | 'aria-expanded' | 'aria-owns' | 'aria-labelledby'
> & {
role: string;
'aria-description': string;
};
Expand Down Expand Up @@ -65,29 +68,26 @@ export const Intellisense = React.memo(
const itemIdPrefix = useId('suggestion-item');
const suggestionsContainerId = useId('suggestion-items');
const getItemId = useCallback((index: number) => `${itemIdPrefix}-${index}`, []);
const [aria, setAria] = useState<AriaState>(() => ({
role: 'combobox',
'aria-owns': suggestionsContainerId,
'aria-autocomplete': 'list',
'aria-expanded': 'false',
'aria-activedescendant': '',
'aria-description': formatMessage("Start typing to get suggestions or type '=' to write an expression"),
}));

useEffect(() => {
const aria = useMemo<AriaState>(() => {
const labelId = `${id.replace(/^intellisense-/, '')}-field-label`;
const initial = {
role: 'combobox',
'aria-owns': suggestionsContainerId,
'aria-autocomplete': 'list',
'aria-expanded': 'false',
'aria-activedescendant': undefined,
'aria-description': formatMessage("Start typing to get suggestions or type '=' to write an expression"),
'aria-labelledby': labelId,
} as const;
if (showCompletionList && selectedCompletionItem >= 0) {
setAria({
...aria,
return {
...initial,
'aria-expanded': 'true',
'aria-activedescendant': getItemId(selectedCompletionItem),
});
} else {
setAria({
...aria,
'aria-expanded': 'false',
'aria-activedescendant': '',
});
};
}

return initial;
}, [selectedCompletionItem, showCompletionList]);

const completionItems = useLanguageServer(url, scopes, id, textFieldValue, cursorPosition, projectId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const useEditorToolbarPopExpandItem = (
key: 'popExpandButton',
buttonStyles: itemButtonStyles,
className: options?.customClassName,
ariaLabel: formatMessage('Pop out editor: open editor'),
onRenderIcon: () => {
let PopExpandIcon = createSvgIcon({ svg: () => popExpandSvgIcon, displayName: 'PopExpandIcon' });
PopExpandIcon = withTooltip({ content: formatMessage('Pop out editor') }, PopExpandIcon);
Expand Down
Loading

0 comments on commit 7db4a77

Please sign in to comment.