diff --git a/ts/components/Button.tsx b/ts/components/Button.tsx index 4281964c9f..36fd5e7997 100644 --- a/ts/components/Button.tsx +++ b/ts/components/Button.tsx @@ -1,7 +1,12 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { CSSProperties, MouseEventHandler, ReactNode } from 'react'; +import type { + CSSProperties, + KeyboardEventHandler, + MouseEventHandler, + ReactNode, +} from 'react'; import React from 'react'; import classNames from 'classnames'; @@ -48,6 +53,8 @@ type PropsType = { } & ( | { onClick: MouseEventHandler; + // TODO: DESKTOP-4121 + onKeyDown?: KeyboardEventHandler; } | { type: 'submit'; diff --git a/ts/components/CustomizingPreferredReactionsModal.tsx b/ts/components/CustomizingPreferredReactionsModal.tsx index 246e59aba2..d0f80eb855 100644 --- a/ts/components/CustomizingPreferredReactionsModal.tsx +++ b/ts/components/CustomizingPreferredReactionsModal.tsx @@ -173,6 +173,11 @@ export function CustomizingPreferredReactionsModal({ onClick={() => { resetDraftEmoji(); }} + onKeyDown={event => { + if (event.key === 'Enter' || event.key === 'Space') { + resetDraftEmoji(); + } + }} variant={ButtonVariant.SecondaryAffirmative} > {i18n('reset')} @@ -182,6 +187,11 @@ export function CustomizingPreferredReactionsModal({ onClick={() => { savePreferredReactions(); }} + onKeyDown={event => { + if (event.key === 'Enter' || event.key === 'Space') { + savePreferredReactions(); + } + }} > {i18n('save')} diff --git a/ts/components/ReactionPickerPicker.tsx b/ts/components/ReactionPickerPicker.tsx index 31f310ca95..e7da8acf77 100644 --- a/ts/components/ReactionPickerPicker.tsx +++ b/ts/components/ReactionPickerPicker.tsx @@ -35,6 +35,13 @@ export const ReactionPickerPickerEmojiButton = React.forwardRef< event.stopPropagation(); onClick(); }} + onKeyDown={event => { + if (event.key === 'Enter' || event.key === 'Space') { + event.stopPropagation(); + event.preventDefault(); + onClick(); + } + }} > @@ -54,6 +61,13 @@ export const ReactionPickerPickerMoreButton = ({ event.stopPropagation(); onClick(); }} + onKeyDown={event => { + if (event.key === 'Enter' || event.key === 'Space') { + event.stopPropagation(); + event.preventDefault(); + onClick(); + } + }} tabIndex={0} title={i18n('Reactions--more')} type="button" diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 84e28f2a20..48ef2b0dcc 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -495,9 +495,9 @@ export class Message extends React.PureComponent { }; public handleFocus = (): void => { - const { interactionMode } = this.props; + const { interactionMode, isSelected } = this.props; - if (interactionMode === 'keyboard') { + if (interactionMode === 'keyboard' && !isSelected) { this.setSelected(); } }; @@ -1979,32 +1979,30 @@ export class Message extends React.PureComponent { {reactionPickerRoot && createPortal( - - - {({ ref, style }) => - renderReactionPicker({ - ref, - style, - selected: selectedReaction, - onClose: this.toggleReactionPicker, - onPick: emoji => { - this.toggleReactionPicker(true); - reactToMessage(id, { - emoji, - remove: emoji === selectedReaction, - }); - }, - renderEmojiPicker, - }) - } - - , + + {({ ref, style }) => + renderReactionPicker({ + ref, + style, + selected: selectedReaction, + onClose: this.toggleReactionPicker, + onPick: emoji => { + this.toggleReactionPicker(true); + reactToMessage(id, { + emoji, + remove: emoji === selectedReaction, + }); + }, + renderEmojiPicker, + }) + } + , reactionPickerRoot )} @@ -2613,28 +2611,26 @@ export class Message extends React.PureComponent { {reactionViewerRoot && createPortal( - - - {({ ref, style }) => ( - - )} - - , + + {({ ref, style }) => ( + + )} + , reactionViewerRoot )} diff --git a/ts/components/conversation/ReactionPicker.tsx b/ts/components/conversation/ReactionPicker.tsx index dadc508b24..82b5ba0136 100644 --- a/ts/components/conversation/ReactionPicker.tsx +++ b/ts/components/conversation/ReactionPicker.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { convertShortName } from '../emoji/lib'; import type { Props as EmojiPickerProps } from '../emoji/EmojiPicker'; -import { useRestoreFocus } from '../../hooks/useRestoreFocus'; +import { useDelayedRestoreFocus } from '../../hooks/useRestoreFocus'; import type { LocalizerType } from '../../types/Util'; import { ReactionPickerPicker, @@ -75,7 +75,7 @@ export const ReactionPicker = React.forwardRef( ); // Focus first button and restore focus on unmount - const [focusRef] = useRestoreFocus(); + const [focusRef] = useDelayedRestoreFocus(); if (pickingOther) { return renderEmojiPicker({ diff --git a/ts/components/conversation/ReactionViewer.tsx b/ts/components/conversation/ReactionViewer.tsx index 59644b8815..fe3d4729c1 100644 --- a/ts/components/conversation/ReactionViewer.tsx +++ b/ts/components/conversation/ReactionViewer.tsx @@ -193,6 +193,13 @@ export const ReactionViewer = React.forwardRef( event.stopPropagation(); setSelectedReactionCategory(id); }} + onKeyDown={event => { + if (event.key === 'Enter' || event.key === 'Space') { + event.stopPropagation(); + event.preventDefault(); + setSelectedReactionCategory(id); + } + }} > {isAll ? ( diff --git a/ts/components/emoji/EmojiPicker.tsx b/ts/components/emoji/EmojiPicker.tsx index 12f9838db8..c5bdb31958 100644 --- a/ts/components/emoji/EmojiPicker.tsx +++ b/ts/components/emoji/EmojiPicker.tsx @@ -89,8 +89,14 @@ export const EmojiPicker = React.memo( const [selectedTone, setSelectedTone] = React.useState(skinTone); const handleToggleSearch = React.useCallback( - (e: React.MouseEvent) => { + ( + e: + | React.MouseEvent + | React.KeyboardEvent + ) => { e.stopPropagation(); + e.preventDefault(); + setSearchText(''); setSelectedCategory(categories[0]); setSearchMode(m => !m); @@ -115,7 +121,11 @@ export const EmojiPicker = React.memo( ); const handlePickTone = React.useCallback( - (e: React.MouseEvent) => { + ( + e: + | React.MouseEvent + | React.KeyboardEvent + ) => { e.preventDefault(); e.stopPropagation(); @@ -135,19 +145,24 @@ export const EmojiPicker = React.memo( | React.MouseEvent | React.KeyboardEvent ) => { + const { shortName } = e.currentTarget.dataset; if ('key' in e) { - if (e.key === 'Enter' && doSend) { - e.stopPropagation(); - e.preventDefault(); - doSend(); - } - } else { - const { shortName } = e.currentTarget.dataset; - if (shortName) { - e.stopPropagation(); - e.preventDefault(); - onPickEmoji({ skinTone: selectedTone, shortName }); + if (e.key === 'Enter') { + if (doSend) { + doSend(); + e.stopPropagation(); + e.preventDefault(); + } + if (shortName) { + onPickEmoji({ skinTone: selectedTone, shortName }); + e.stopPropagation(); + e.preventDefault(); + } } + } else if (shortName) { + e.stopPropagation(); + e.preventDefault(); + onPickEmoji({ skinTone: selectedTone, shortName }); } }, [doSend, onPickEmoji, selectedTone] @@ -158,14 +173,16 @@ export const EmojiPicker = React.memo( const handler = (event: KeyboardEvent) => { if (event.key === 'Escape') { if (searchMode) { + event.preventDefault(); + event.stopPropagation(); setScrollToRow(0); setSearchText(''); setSearchMode(false); - } else { - onClose?.(); + } else if (onClose) { + event.preventDefault(); + event.stopPropagation(); + onClose(); } - event.preventDefault(); - event.stopPropagation(); } else if (!searchMode && !event.ctrlKey && !event.metaKey) { if ( [ @@ -251,8 +268,14 @@ export const EmojiPicker = React.memo( ); const handleSelectCategory = React.useCallback( - (e: React.MouseEvent) => { + ( + e: + | React.MouseEvent + | React.KeyboardEvent + ) => { e.stopPropagation(); + e.preventDefault(); + const { category } = e.currentTarget.dataset; if (category) { setSelectedCategory(category); @@ -326,6 +349,11 @@ export const EmojiPicker = React.memo(