Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 991257c

Browse files
authored
Fix accessibility and consistency of MessageComposerButtons (#7679)
1 parent a17d585 commit 991257c

File tree

8 files changed

+93
-74
lines changed

8 files changed

+93
-74
lines changed

res/css/views/rooms/_MessageComposer.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,14 @@ limitations under the License.
192192
line-height: var(--size);
193193
width: auto;
194194
padding-left: var(--size);
195-
border-radius: 100%;
196-
margin-right: 6px;
197195

198-
&:last-child {
199-
margin-right: auto;
196+
&:not(.mx_CallContextMenu_item) {
197+
border-radius: 50%;
198+
margin-right: 6px;
199+
200+
&:last-child {
201+
margin-right: auto;
202+
}
200203
}
201204

202205
&::before {
@@ -407,6 +410,7 @@ limitations under the License.
407410
align-items: center;
408411
max-width: unset;
409412
width: 100%;
413+
margin: 7px 7px 7px 16px; // space out the buttons
410414
}
411415

412416
.mx_MessageComposer_Menu .mx_ContextualMenu {

src/components/structures/ContextMenu.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,13 @@ export const alwaysAboveRightOf = (elementRect: DOMRect, chevronFace = ChevronFa
453453
return menuOptions;
454454
};
455455

456-
type ContextMenuTuple<T> = [boolean, RefObject<T>, () => void, () => void, (val: boolean) => void];
456+
type ContextMenuTuple<T> = [
457+
boolean,
458+
RefObject<T>,
459+
(ev?: SyntheticEvent) => void,
460+
(ev?: SyntheticEvent) => void,
461+
(val: boolean) => void,
462+
];
457463
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
458464
export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<T> => {
459465
const button = useRef<T>(null);

src/components/views/location/LocationButton.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { ReactElement, useContext } from 'react';
17+
import React, { ReactElement, SyntheticEvent, useContext } from 'react';
1818
import classNames from 'classnames';
1919
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
2020
import { logger } from "matrix-js-sdk/src/logger";
@@ -23,39 +23,43 @@ import { makeLocationContent } from "matrix-js-sdk/src/content-helpers";
2323

2424
import { _t } from '../../../languageHandler';
2525
import LocationPicker from './LocationPicker';
26-
import { CollapsibleButton, ICollapsibleButtonProps } from '../rooms/CollapsibleButton';
26+
import { CollapsibleButton } from '../rooms/CollapsibleButton';
2727
import ContextMenu, { aboveLeftOf, useContextMenu, AboveLeftOf } from "../../structures/ContextMenu";
2828
import Modal from '../../../Modal';
2929
import QuestionDialog from '../dialogs/QuestionDialog';
3030
import MatrixClientContext from '../../../contexts/MatrixClientContext';
31+
import { OverflowMenuContext } from "../rooms/MessageComposerButtons";
3132

32-
interface IProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
33+
interface IProps {
3334
roomId: string;
3435
sender: RoomMember;
3536
menuPosition: AboveLeftOf;
36-
narrowMode: boolean;
3737
}
3838

39-
export const LocationButton: React.FC<IProps> = (
40-
{ roomId, sender, menuPosition, narrowMode },
41-
) => {
39+
export const LocationButton: React.FC<IProps> = ({ roomId, sender, menuPosition }) => {
40+
const overflowMenuCloser = useContext(OverflowMenuContext);
4241
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
4342
const matrixClient = useContext(MatrixClientContext);
4443

44+
const _onFinished = (ev?: SyntheticEvent) => {
45+
closeMenu(ev);
46+
overflowMenuCloser?.();
47+
};
48+
4549
let contextMenu: ReactElement;
4650
if (menuDisplayed) {
4751
const position = menuPosition ?? aboveLeftOf(
4852
button.current.getBoundingClientRect());
4953

5054
contextMenu = <ContextMenu
5155
{...position}
52-
onFinished={closeMenu}
56+
onFinished={_onFinished}
5357
managed={false}
5458
>
5559
<LocationPicker
5660
sender={sender}
5761
onChoose={shareLocation(matrixClient, roomId, openMenu)}
58-
onFinished={closeMenu}
62+
onFinished={_onFinished}
5963
/>
6064
</ContextMenu>;
6165
}
@@ -74,7 +78,6 @@ export const LocationButton: React.FC<IProps> = (
7478
<CollapsibleButton
7579
className={className}
7680
onClick={openMenu}
77-
narrowMode={narrowMode}
7881
title={_t("Share location")}
7982
/>
8083

src/components/views/rooms/CollapsibleButton.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,29 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React, { ComponentProps } from 'react';
17+
import React, { ComponentProps, useContext } from 'react';
18+
import classNames from 'classnames';
1819

1920
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
21+
import { MenuItem } from "../../structures/ContextMenu";
22+
import { OverflowMenuContext } from './MessageComposerButtons';
2023

21-
export interface ICollapsibleButtonProps
22-
extends ComponentProps<typeof AccessibleTooltipButton>
23-
{
24-
narrowMode: boolean;
24+
interface ICollapsibleButtonProps extends ComponentProps<typeof MenuItem> {
2525
title: string;
2626
}
2727

28-
export const CollapsibleButton = ({ narrowMode, title, ...props }: ICollapsibleButtonProps) => {
29-
return <AccessibleTooltipButton
30-
{...props}
31-
title={narrowMode ? undefined : title}
32-
label={narrowMode ? title : undefined}
33-
/>;
28+
export const CollapsibleButton = ({ title, className, ...props }: ICollapsibleButtonProps) => {
29+
const inOverflowMenu = !!useContext(OverflowMenuContext);
30+
if (inOverflowMenu) {
31+
return <MenuItem
32+
{...props}
33+
className={classNames("mx_CallContextMenu_item", className)}
34+
>
35+
{ title }
36+
</MenuItem>;
37+
}
38+
39+
return <AccessibleTooltipButton {...props} title={title} className={className} />;
3440
};
3541

3642
export default CollapsibleButton;

src/components/views/rooms/MessageComposer.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,10 @@ export default class MessageComposer extends React.Component<IProps, IState> {
330330
};
331331

332332
private setStickerPickerOpen = (isStickerPickerOpen: boolean) => {
333-
this.setState({ isStickerPickerOpen });
333+
this.setState({
334+
isStickerPickerOpen,
335+
isMenuOpen: false,
336+
});
334337
};
335338

336339
private toggleButtonMenu = (): void => {
@@ -453,7 +456,12 @@ export default class MessageComposer extends React.Component<IProps, IState> {
453456
menuPosition={menuPosition}
454457
narrowMode={this.state.narrowMode}
455458
relation={this.props.relation}
456-
onRecordStartEndClick={() => this.voiceRecordingButton.current?.onRecordStartEndClick()}
459+
onRecordStartEndClick={() => {
460+
this.voiceRecordingButton.current?.onRecordStartEndClick();
461+
if (this.state.narrowMode) {
462+
this.toggleButtonMenu();
463+
}
464+
}}
457465
setStickerPickerOpen={this.setStickerPickerOpen}
458466
showLocationButton={this.state.showLocationButton}
459467
showStickersButton={this.state.showStickersButton}

src/components/views/rooms/MessageComposerButtons.tsx

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ limitations under the License.
1717
import classNames from 'classnames';
1818
import { IEventRelation } from "matrix-js-sdk/src/models/event";
1919
import { M_POLL_START } from "matrix-events-sdk";
20-
import React, { ReactElement, useContext } from 'react';
20+
import React, { createContext, ReactElement, useContext } from 'react';
2121
import { Room } from 'matrix-js-sdk/src/models/room';
2222
import { MatrixClient } from 'matrix-js-sdk/src/client';
2323

2424
import { _t } from '../../../languageHandler';
2525
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
26-
import { CollapsibleButton, ICollapsibleButtonProps } from './CollapsibleButton';
27-
import ContextMenu, { aboveLeftOf, AboveLeftOf, MenuItem, useContextMenu } from '../../structures/ContextMenu';
26+
import { CollapsibleButton } from './CollapsibleButton';
27+
import ContextMenu, { aboveLeftOf, AboveLeftOf, useContextMenu } from '../../structures/ContextMenu';
2828
import dis from '../../../dispatcher/dispatcher';
2929
import EmojiPicker from '../emojipicker/EmojiPicker';
3030
import ErrorDialog from "../dialogs/ErrorDialog";
@@ -52,6 +52,9 @@ interface IProps {
5252
toggleButtonMenu: () => void;
5353
}
5454

55+
type OverflowMenuCloser = () => void;
56+
export const OverflowMenuContext = createContext<OverflowMenuCloser | null>(null);
57+
5558
const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
5659
const matrixClient: MatrixClient = useContext(MatrixClientContext);
5760
const { room, roomId } = useContext(RoomContext);
@@ -107,23 +110,16 @@ function narrowMode(
107110
className={moreOptionsClasses}
108111
onClick={props.toggleButtonMenu}
109112
title={_t("More options")}
110-
tooltip={false}
111113
/>
112114
{ props.isMenuOpen && (
113115
<ContextMenu
114116
onFinished={props.toggleButtonMenu}
115117
{...props.menuPosition}
116118
wrapperClassName="mx_MessageComposer_Menu"
117119
>
118-
{ moreButtons.map((button, index) => (
119-
<MenuItem
120-
className="mx_CallContextMenu_item"
121-
key={index}
122-
onClick={props.toggleButtonMenu}
123-
>
124-
{ button }
125-
</MenuItem>
126-
)) }
120+
<OverflowMenuContext.Provider value={props.toggleButtonMenu}>
121+
{ moreButtons }
122+
</OverflowMenuContext.Provider>
127123
</ContextMenu>
128124
) }
129125
</>;
@@ -134,18 +130,16 @@ function emojiButton(props: IProps): ReactElement {
134130
key="emoji_button"
135131
addEmoji={props.addEmoji}
136132
menuPosition={props.menuPosition}
137-
narrowMode={props.narrowMode}
138133
/>;
139134
}
140135

141-
interface IEmojiButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
136+
interface IEmojiButtonProps {
142137
addEmoji: (unicode: string) => boolean;
143138
menuPosition: AboveLeftOf;
144139
}
145140

146-
const EmojiButton: React.FC<IEmojiButtonProps> = (
147-
{ addEmoji, menuPosition, narrowMode },
148-
) => {
141+
const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition }) => {
142+
const overflowMenuCloser = useContext(OverflowMenuContext);
149143
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
150144

151145
let contextMenu: React.ReactElement | null = null;
@@ -156,7 +150,10 @@ const EmojiButton: React.FC<IEmojiButtonProps> = (
156150

157151
contextMenu = <ContextMenu
158152
{...position}
159-
onFinished={closeMenu}
153+
onFinished={() => {
154+
closeMenu();
155+
overflowMenuCloser?.();
156+
}}
160157
managed={false}
161158
>
162159
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
@@ -177,7 +174,6 @@ const EmojiButton: React.FC<IEmojiButtonProps> = (
177174
<CollapsibleButton
178175
className={className}
179176
onClick={openMenu}
180-
narrowMode={narrowMode}
181177
title={_t("Add emoji")}
182178
/>
183179

@@ -274,19 +270,18 @@ class UploadButton extends React.Component<IUploadButtonProps> {
274270
function showStickersButton(props: IProps): ReactElement {
275271
return (
276272
props.showStickersButton
277-
? <AccessibleTooltipButton
273+
? <CollapsibleButton
278274
id='stickersButton'
279275
key="controls_stickers"
280276
className="mx_MessageComposer_button mx_MessageComposer_stickers"
281277
onClick={() => props.setStickerPickerOpen(!props.isStickerPickerOpen)}
282278
title={
283279
props.narrowMode
284-
? null
280+
? _t("Send a sticker")
285281
: props.isStickerPickerOpen
286282
? _t("Hide Stickers")
287283
: _t("Show Stickers")
288284
}
289-
label={props.narrowMode ? _t("Send a sticker") : null}
290285
/>
291286
: null
292287
);
@@ -302,25 +297,23 @@ function voiceRecordingButton(props: IProps): ReactElement {
302297
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
303298
onClick={props.onRecordStartEndClick}
304299
title={_t("Send voice message")}
305-
narrowMode={props.narrowMode}
306300
/>
307301
);
308302
}
309303

310304
function pollButton(props: IProps, room: Room): ReactElement {
311-
return <PollButton
312-
key="polls"
313-
room={room}
314-
narrowMode={props.narrowMode}
315-
/>;
305+
return <PollButton key="polls" room={room} />;
316306
}
317307

318-
interface IPollButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
308+
interface IPollButtonProps {
319309
room: Room;
320310
}
321311

322312
class PollButton extends React.PureComponent<IPollButtonProps> {
313+
static contextType = OverflowMenuContext;
314+
323315
private onCreateClick = () => {
316+
this.context?.(); // close overflow menu
324317
const canSend = this.props.room.currentState.maySendEvent(
325318
M_POLL_START.name,
326319
MatrixClientPeg.get().getUserId(),
@@ -357,7 +350,6 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
357350
<CollapsibleButton
358351
className="mx_MessageComposer_button mx_MessageComposer_poll"
359352
onClick={this.onCreateClick}
360-
narrowMode={this.props.narrowMode}
361353
title={_t("Create poll")}
362354
/>
363355
);
@@ -377,7 +369,6 @@ function showLocationButton(
377369
roomId={roomId}
378370
sender={room.getMember(matrixClient.getUserId())}
379371
menuPosition={props.menuPosition}
380-
narrowMode={props.narrowMode}
381372
/>
382373
: null
383374
);

src/i18n/strings/en_EN.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1696,9 +1696,9 @@
16961696
"Send voice message": "Send voice message",
16971697
"Add emoji": "Add emoji",
16981698
"Upload file": "Upload file",
1699+
"Send a sticker": "Send a sticker",
16991700
"Hide Stickers": "Hide Stickers",
17001701
"Show Stickers": "Show Stickers",
1701-
"Send a sticker": "Send a sticker",
17021702
"You do not have permission to start polls in this room.": "You do not have permission to start polls in this room.",
17031703
"Create poll": "Create poll",
17041704
"Bold": "Bold",

0 commit comments

Comments
 (0)