diff options
author | Eric Bailey <git@esb.lol> | 2024-12-20 13:59:33 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-20 19:59:33 +0000 |
commit | 8116d12c15495fa192e92f5bfb75cb561bb16402 (patch) | |
tree | d8291bc888d6423ccb7f242877c9293283156e83 /src/view/com/composer/text-input | |
parent | 8a3dfcb9d0860eb8f8112a84dcf32ae522f77069 (diff) | |
download | voidsky-8116d12c15495fa192e92f5bfb75cb561bb16402.tar.zst |
Fix Emoji picker focus (#7217)
* Only portal the emoji picker where needed * Add optional portal prop to emoji picker * Use FocusScope to our advantage * Pare back, add guards, fix focus trap * Don't return focus to emoji button * Set DM input position on emoji insert * Let the caller determine next focus node --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Diffstat (limited to 'src/view/com/composer/text-input')
-rw-r--r-- | src/view/com/composer/text-input/web/EmojiPicker.web.tsx | 87 |
1 files changed, 51 insertions, 36 deletions
diff --git a/src/view/com/composer/text-input/web/EmojiPicker.web.tsx b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx index c72172902..f5e6a987c 100644 --- a/src/view/com/composer/text-input/web/EmojiPicker.web.tsx +++ b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx @@ -1,15 +1,13 @@ import React from 'react' -import { - GestureResponderEvent, - TouchableWithoutFeedback, - useWindowDimensions, - View, -} from 'react-native' +import {Pressable, useWindowDimensions, View} from 'react-native' import Picker from '@emoji-mart/react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {DismissableLayer} from '@radix-ui/react-dismissable-layer' +import {FocusScope} from '@radix-ui/react-focus-scope' import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' -import {atoms as a} from '#/alf' +import {atoms as a, flatten} from '#/alf' import {Portal} from '#/components/Portal' const HEIGHT_OFFSET = 40 @@ -33,6 +31,7 @@ export interface EmojiPickerPosition { left: number right: number bottom: number + nextFocusRef: React.MutableRefObject<HTMLElement> | null } export interface EmojiPickerState { @@ -51,6 +50,7 @@ interface IProps { } export function EmojiPicker({state, close, pinToTop}: IProps) { + const {_} = useLingui() const {height, width} = useWindowDimensions() const isShiftDown = React.useRef(false) @@ -119,48 +119,63 @@ export function EmojiPicker({state, close, pinToTop}: IProps) { if (!state.isOpen) return null - const onPressBackdrop = (e: GestureResponderEvent) => { - // @ts-ignore web only - if (e.nativeEvent?.pointerId === -1) return - close() - } - return ( <Portal> - <TouchableWithoutFeedback - accessibilityRole="button" - onPress={onPressBackdrop} - accessibilityViewIsModal> + <FocusScope + loop + trapped + onUnmountAutoFocus={e => { + const nextFocusRef = state.pos.nextFocusRef + const node = nextFocusRef?.current + if (node) { + e.preventDefault() + node.focus() + } + }}> + <Pressable + accessible + accessibilityLabel={_(msg`Close emoji picker`)} + accessibilityHint={_(msg`Tap to close the emoji picker`)} + onPress={close} + style={[a.fixed, a.inset_0]} + /> + <View - style={[ + style={flatten([ a.fixed, a.w_full, a.h_full, a.align_center, + a.z_10, { top: 0, left: 0, right: 0, }, - ]}> - {/* eslint-disable-next-line react-native-a11y/has-valid-accessibility-descriptors */} - <TouchableWithoutFeedback onPress={e => e.stopPropagation()}> - <View style={[{position: 'absolute'}, position]}> - <DismissableLayer - onFocusOutside={evt => evt.preventDefault()} - onDismiss={close}> - <Picker - data={async () => { - return (await import('./EmojiPickerData.json')).default - }} - onEmojiSelect={onInsert} - autoFocus={true} - /> - </DismissableLayer> - </View> - </TouchableWithoutFeedback> + ])}> + <View style={[{position: 'absolute'}, position]}> + <DismissableLayer + onFocusOutside={evt => evt.preventDefault()} + onDismiss={close}> + <Picker + data={async () => { + return (await import('./EmojiPickerData.json')).default + }} + onEmojiSelect={onInsert} + autoFocus={true} + /> + </DismissableLayer> + </View> </View> - </TouchableWithoutFeedback> + + <Pressable + accessible + accessibilityLabel={_(msg`Close emoji picker`)} + accessibilityHint={_(msg`Tap to close the emoji picker`)} + onPress={close} + style={[a.fixed, a.inset_0]} + /> + </FocusScope> </Portal> ) } |