Skip to content

Commit bbb1ac1

Browse files
[Ezra bug] change behavior to tab to close and remove focus zone. (#5524)
* change behavior to tab to close and remove focus zone. * remove console * change * fix spelling * add callback to handle shift tabs * update to handle cwc behavior * fix cwc behavior * fix build
1 parent 4f445a7 commit bbb1ac1

9 files changed

Lines changed: 122 additions & 24 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "patch",
3+
"area": "fix",
4+
"workstream": "Ezra",
5+
"comment": "Update people button behavior to meet A11y requirements",
6+
"packageName": "@azure/communication-react",
7+
"email": "dmceachern@microsoft.com",
8+
"dependentChangeType": "patch"
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "patch",
3+
"area": "fix",
4+
"workstream": "Ezra",
5+
"comment": "Update people button behavior to meet A11y requirements",
6+
"packageName": "@azure/communication-react",
7+
"email": "dmceachern@microsoft.com",
8+
"dependentChangeType": "patch"
9+
}

packages/react-composites/src/composites/CallComposite/components/CallArrangement.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
162162
const controlBarRef = useRef<FocusableElement>(null);
163163
const peopleButtonRef = useRef<IButton>(null);
164164
const cameraButtonRef = useRef<IButton>(null);
165+
const sidePaneDismissButtonRef = useRef<IButton>(null);
165166

166167
const containerRef = useRef<HTMLDivElement>(null);
167168
const containerWidth = _useContainerWidth(containerRef);
@@ -204,7 +205,9 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
204205
onFetchParticipantMenuItems: props.callControlProps?.onFetchParticipantMenuItems,
205206
mobileView: props.mobileView,
206207
peopleButtonRef,
207-
setParticipantActioned
208+
setParticipantActioned,
209+
sidePaneDismissButtonRef,
210+
chatButtonPresent: !!props.onCloseChatPane
208211
}),
209212
[
210213
updateSidePaneRenderer,
@@ -213,7 +216,9 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
213216
props.onFetchAvatarPersonaData,
214217
props.mobileView,
215218
peopleButtonRef,
216-
setParticipantActioned
219+
setParticipantActioned,
220+
sidePaneDismissButtonRef,
221+
props.onCloseChatPane
217222
]
218223
);
219224

@@ -589,6 +594,7 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
589594
}
590595
onToggleTeamsMeetingConferenceModal={toggleTeamsMeetingConferenceModal}
591596
teamsMeetingConferenceModalPresent={showTeamsMeetingConferenceModal}
597+
sidePaneDismissButtonRef={sidePaneDismissButtonRef}
592598
/>
593599
)}
594600
</Stack>

packages/react-composites/src/composites/CallComposite/components/SidePane/usePeoplePane.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export const usePeoplePane = (props: {
5858
onPermitOthersVideo?: () => Promise<void>;
5959
/* @conditional-compile-remove(media-access) */
6060
meetingMediaAccess?: MediaAccess;
61+
sidePaneDismissButtonRef?: RefObject<IButton>;
62+
chatButtonPresent?: boolean;
6163
}): {
6264
openPeoplePane: () => void;
6365
closePeoplePane: () => void;
@@ -102,7 +104,9 @@ export const usePeoplePane = (props: {
102104
/* @conditional-compile-remove(media-access) */
103105
onPermitOthersVideo,
104106
/* @conditional-compile-remove(media-access) */
105-
meetingMediaAccess
107+
meetingMediaAccess,
108+
sidePaneDismissButtonRef,
109+
chatButtonPresent
106110
} = props;
107111

108112
const closePane = useCallback(() => {
@@ -325,12 +329,23 @@ export const usePeoplePane = (props: {
325329
() => (
326330
<SidePaneHeader
327331
onClose={closePane}
332+
paneOpenerButton={peopleButtonRef}
328333
headingText={localeStrings.peoplePaneTitle}
329334
dismissSidePaneButtonAriaLabel={localeStrings.dismissSidePaneButtonLabel}
330335
mobileView={mobileView ?? false}
336+
dismissButtonComponentRef={sidePaneDismissButtonRef}
337+
chatButtonPresent={chatButtonPresent}
331338
/>
332339
),
333-
[mobileView, closePane, localeStrings]
340+
[
341+
closePane,
342+
peopleButtonRef,
343+
localeStrings.peoplePaneTitle,
344+
localeStrings.dismissSidePaneButtonLabel,
345+
mobileView,
346+
sidePaneDismissButtonRef,
347+
chatButtonPresent
348+
]
334349
);
335350

336351
const onFetchParticipantMenuItemsForCallComposite = useCallback(

packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,9 +625,10 @@ const CallWithChatScreen = (props: CallWithChatScreenProps): JSX.Element => {
625625
onClose={closeChat}
626626
dismissSidePaneButtonAriaLabel={callWithChatStrings.dismissSidePaneButtonLabel ?? ''}
627627
mobileView={mobileView}
628+
chatButtonPresent={showChatButton}
628629
/>
629630
),
630-
[chatPaneTitle, callWithChatStrings.dismissSidePaneButtonLabel, closeChat, mobileView]
631+
[chatPaneTitle, closeChat, callWithChatStrings.dismissSidePaneButtonLabel, mobileView, showChatButton]
631632
);
632633

633634
const sidePaneContentRenderer = useMemo(

packages/react-composites/src/composites/common/ControlBar/CommonCallControlBar.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import React, { useMemo, useRef, useEffect, useState, useCallback, useImperativeHandle, forwardRef } from 'react';
4+
import React, {
5+
useMemo,
6+
useRef,
7+
useEffect,
8+
useState,
9+
useCallback,
10+
useImperativeHandle,
11+
forwardRef,
12+
RefObject
13+
} from 'react';
514
import { CallAdapterProvider } from '../../CallComposite/adapter/CallAdapterProvider';
615
import { CallAdapter } from '../../CallComposite';
716
import { PeopleButton } from './PeopleButton';
@@ -78,17 +87,16 @@ export interface CommonCallControlBarProps {
7887
onUserSetOverflowGalleryPositionChange?: (position: 'Responsive' | 'horizontalTop') => void;
7988
onUserSetGalleryLayout?: (layout: VideoGalleryLayout) => void;
8089
userSetGalleryLayout?: VideoGalleryLayout;
81-
peopleButtonRef?: React.RefObject<IButton>;
82-
cameraButtonRef?: React.RefObject<IButton>;
83-
videoBackgroundPickerRef?: React.RefObject<IButton>;
90+
peopleButtonRef?: RefObject<IButton>;
91+
cameraButtonRef?: RefObject<IButton>;
92+
videoBackgroundPickerRef?: RefObject<IButton>;
8493
onSetDialpadPage?: () => void;
8594
dtmfDialerPresent?: boolean;
8695
onStopLocalSpotlight?: () => void;
8796
useTeamsCaptions?: boolean;
88-
8997
onToggleTeamsMeetingConferenceModal?: () => void;
90-
9198
teamsMeetingConferenceModalPresent?: boolean;
99+
sidePaneDismissButtonRef?: RefObject<IButton>;
92100
}
93101

94102
const inferCommonCallControlOptions = (
@@ -580,6 +588,8 @@ export const CommonCallControlBar = forwardRef<FocusableElement, CommonCallContr
580588
strings={peopleButtonStrings}
581589
styles={commonButtonStyles}
582590
componentRef={props.peopleButtonRef}
591+
chatButtonPresent={isEnabled(options.chatButton)}
592+
peoplePaneDismissButtonRef={props.sidePaneDismissButtonRef}
583593
/>
584594
)}
585595
{customButtons['secondary']

packages/react-composites/src/composites/common/ControlBar/PeopleButton.tsx

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import React, { useMemo } from 'react';
4+
import React, { useCallback, useMemo, RefObject } from 'react';
55
import { ControlBarButton, ControlBarButtonProps, ControlBarButtonStyles, useTheme } from '@internal/react-components';
6-
import { concatStyleSets } from '@fluentui/react';
6+
import { concatStyleSets, IButton } from '@fluentui/react';
77
import { CallCompositeIcon } from '../icons';
88

99
const icon = (): JSX.Element => <CallCompositeIcon iconName={'ControlBarPeopleButton'} />;
1010

1111
/**
1212
* @private
13+
* props for the PeopleButton component
1314
*/
14-
export const PeopleButton = (props: ControlBarButtonProps): JSX.Element => {
15-
const { strings, onRenderOnIcon, onRenderOffIcon, onClick } = props;
15+
export interface PeopleButtonProps extends ControlBarButtonProps {
16+
peoplePaneDismissButtonRef?: RefObject<IButton>;
17+
chatButtonPresent?: boolean;
18+
}
19+
20+
/**
21+
* @private
22+
*/
23+
export const PeopleButton = (props: PeopleButtonProps): JSX.Element => {
24+
const { strings, onRenderOnIcon, onRenderOffIcon, onClick, peoplePaneDismissButtonRef, chatButtonPresent } = props;
1625
const theme = useTheme();
1726
const styles: ControlBarButtonStyles = useMemo(
1827
() =>
@@ -27,14 +36,37 @@ export const PeopleButton = (props: ControlBarButtonProps): JSX.Element => {
2736
[props.styles, theme.palette.neutralLight]
2837
);
2938

39+
const handleTab = useCallback(
40+
(event: React.KeyboardEvent<HTMLElement>) => {
41+
if (event.key === 'Tab' && !event.shiftKey && peoplePaneDismissButtonRef?.current && !chatButtonPresent) {
42+
peoplePaneDismissButtonRef.current.focus();
43+
event.preventDefault();
44+
}
45+
},
46+
[peoplePaneDismissButtonRef, chatButtonPresent]
47+
);
48+
49+
const handleClick = useCallback(
50+
(event: React.MouseEvent<HTMLElement>) => {
51+
onClick?.(event);
52+
if (chatButtonPresent) {
53+
console.log(peoplePaneDismissButtonRef);
54+
peoplePaneDismissButtonRef?.current?.focus();
55+
event.preventDefault();
56+
}
57+
},
58+
[chatButtonPresent, onClick, peoplePaneDismissButtonRef]
59+
);
60+
3061
return (
3162
<ControlBarButton
3263
{...props}
3364
strings={strings}
3465
labelKey={'peopleButtonLabelKey'}
3566
onRenderOnIcon={onRenderOnIcon ?? icon}
3667
onRenderOffIcon={onRenderOffIcon ?? icon}
37-
onClick={onClick}
68+
onClick={handleClick}
69+
onKeyDown={handleTab}
3870
styles={styles}
3971
/>
4072
);

packages/react-composites/src/composites/common/ParticipantContainer.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
headingMoreButtonStyles
1313
} from './styles/ParticipantContainer.styles';
1414
import { ParticipantList, ParticipantListProps, ParticipantMenuItemsCallback } from '@internal/react-components';
15-
import { FocusZone, Stack, Text, TooltipHost, TooltipOverflowMode, getId, useTheme } from '@fluentui/react';
15+
import { Stack, Text, TooltipHost, TooltipOverflowMode, getId, useTheme } from '@fluentui/react';
1616
import { DefaultButton, IContextualMenuProps } from '@fluentui/react';
1717
import { AvatarPersona, AvatarPersonaDataCallback } from './AvatarPersona';
1818
import { useId } from '@fluentui/react-hooks';
@@ -97,7 +97,6 @@ export const ParticipantListWithHeading = (props: {
9797
(headingMoreButtonMenuProps?.items && headingMoreButtonMenuProps.items.length > 0)) && (
9898
<Stack.Item>
9999
<DefaultButton
100-
autoFocus
101100
data-ui-id="people-pane-header-more-button"
102101
ariaLabel={headingMoreButtonAriaLabel}
103102
styles={headingMoreButtonStyles(theme)}
@@ -109,7 +108,7 @@ export const ParticipantListWithHeading = (props: {
109108
</Stack.Item>
110109
)}
111110
</Stack>
112-
<FocusZone className={participantListContainerStyle} shouldFocusOnMount={true}>
111+
<Stack className={participantListContainerStyle}>
113112
<ParticipantList
114113
{...participantListProps}
115114
pinnedParticipants={pinnedParticipants}
@@ -139,7 +138,7 @@ export const ParticipantListWithHeading = (props: {
139138
showParticipantOverflowTooltip={!props.isMobile}
140139
participantAriaLabelledBy={subheadingUniqueId}
141140
/>
142-
</FocusZone>
141+
</Stack>
143142
</Stack>
144143
);
145144
};

packages/react-composites/src/composites/common/SidePaneHeader.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { CommandBarButton, DefaultButton, Stack, concatStyleSets } from '@fluentui/react';
4+
import { CommandBarButton, DefaultButton, IButton, Stack, concatStyleSets } from '@fluentui/react';
55
import { useTheme } from '@internal/react-components';
6-
import React, { useMemo } from 'react';
6+
import React, { useMemo, RefObject, useCallback } from 'react';
77
import { sidePaneHeaderContainerStyles, sidePaneHeaderStyles } from '../common/styles/ParticipantContainer.styles';
88
import {
99
mobilePaneBackButtonStyles,
@@ -22,6 +22,9 @@ export const SidePaneHeader = (props: {
2222
dismissSidePaneButtonAriaDescription?: string;
2323
onClose: () => void;
2424
mobileView: boolean;
25+
paneOpenerButton?: RefObject<IButton>;
26+
dismissButtonComponentRef?: RefObject<IButton>;
27+
chatButtonPresent?: boolean;
2528
}): JSX.Element => {
2629
const theme = useTheme();
2730
const sidePaneCloseButtonStyles = useMemo(
@@ -43,6 +46,16 @@ export const SidePaneHeader = (props: {
4346
[theme.palette.neutralSecondary, theme.semanticColors.bodyBackground, theme.effects.roundedCorner4]
4447
);
4548

49+
const handleShiftTab = useCallback(
50+
(event: React.KeyboardEvent<HTMLElement>) => {
51+
if (event.key === 'Tab' && event.shiftKey && !props.chatButtonPresent) {
52+
props.paneOpenerButton?.current?.focus();
53+
event.preventDefault();
54+
}
55+
},
56+
[props.chatButtonPresent, props.paneOpenerButton]
57+
);
58+
4659
if (props.mobileView) {
4760
return <SidePaneMobileHeader {...props} />;
4861
}
@@ -57,8 +70,12 @@ export const SidePaneHeader = (props: {
5770
ariaLabel={props.dismissSidePaneButtonAriaLabel}
5871
styles={sidePaneCloseButtonStyles}
5972
iconProps={{ iconName: 'cancel' }}
60-
onClick={props.onClose}
61-
autoFocus
73+
onClick={() => {
74+
props.onClose();
75+
props.paneOpenerButton?.current?.focus();
76+
}}
77+
onKeyDown={handleShiftTab}
78+
componentRef={props.dismissButtonComponentRef}
6279
/>
6380
</Stack.Item>
6481
</Stack>

0 commit comments

Comments
 (0)