diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/composer/Composer.tsx | 9 | ||||
-rw-r--r-- | src/view/com/composer/text-input/web/EmojiPicker.web.tsx | 87 | ||||
-rw-r--r-- | src/view/shell/Composer.web.tsx | 20 |
3 files changed, 71 insertions, 45 deletions
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index e4b09cf0f..c9e40530e 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -530,7 +530,14 @@ export const ComposePost = ({ } const onEmojiButtonPress = useCallback(() => { - openEmojiPicker?.(textInput.current?.getCursorPosition()) + const rect = textInput.current?.getCursorPosition() + if (rect) { + openEmojiPicker?.({ + ...rect, + nextFocusRef: + textInput as unknown as React.MutableRefObject<HTMLElement>, + }) + } }, [openEmojiPicker]) const scrollViewRef = useAnimatedRef<Animated.ScrollView>() 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> ) } diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index 47a86ed24..cfd9f6280 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -9,6 +9,7 @@ import {useModals} from '#/state/modals' import {ComposerOpts, useComposerState} from '#/state/shell/composer' import { EmojiPicker, + EmojiPickerPosition, EmojiPickerState, } from '#/view/com/composer/text-input/web/EmojiPicker.web' import {useBreakpoints, useTheme} from '#/alf' @@ -42,16 +43,19 @@ function Inner({state}: {state: ComposerOpts}) { const {gtMobile} = useBreakpoints() const [pickerState, setPickerState] = React.useState<EmojiPickerState>({ isOpen: false, - pos: {top: 0, left: 0, right: 0, bottom: 0}, + pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null}, }) - const onOpenPicker = React.useCallback((pos: DOMRect | undefined) => { - if (!pos) return - setPickerState({ - isOpen: true, - pos, - }) - }, []) + const onOpenPicker = React.useCallback( + (pos: EmojiPickerPosition | undefined) => { + if (!pos) return + setPickerState({ + isOpen: true, + pos, + }) + }, + [], + ) const onClosePicker = React.useCallback(() => { setPickerState(prev => ({ |