Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix keyboard support on the dropdown (#1073)

* Fix: dropdown now supports accessibility labels and keyboard controls

* Fix event propagation around the post dropdown

authored by

Paul Frazee and committed by
GitHub
1195f289 45da8a86

+109 -39
+22
src/view/com/util/EventStopper.tsx
··· 1 + import React from 'react' 2 + import {View} from 'react-native' 3 + 4 + /** 5 + * This utility function captures events and stops 6 + * them from propagating upwards. 7 + */ 8 + export function EventStopper({children}: React.PropsWithChildren<{}>) { 9 + const stop = (e: any) => { 10 + e.stopPropagation() 11 + } 12 + return ( 13 + <View 14 + onStartShouldSetResponder={_ => true} 15 + onTouchEnd={stop} 16 + // @ts-ignore web only -prf 17 + onClick={stop} 18 + onKeyDown={stop}> 19 + {children} 20 + </View> 21 + ) 22 + }
+78 -35
src/view/com/util/forms/NativeDropdown.tsx
··· 1 1 import React from 'react' 2 2 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 3 3 import * as DropdownMenu from 'zeego/dropdown-menu' 4 - import { 5 - Pressable, 6 - StyleSheet, 7 - Platform, 8 - StyleProp, 9 - ViewStyle, 10 - } from 'react-native' 4 + import {Pressable, StyleSheet, Platform, View} from 'react-native' 11 5 import {IconProp} from '@fortawesome/fontawesome-svg-core' 12 6 import {MenuItemCommonProps} from 'zeego/lib/typescript/menu' 13 7 import {usePalette} from 'lib/hooks/usePalette' ··· 18 12 // Custom Dropdown Menu Components 19 13 // == 20 14 export const DropdownMenuRoot = DropdownMenu.Root 21 - export const DropdownMenuTrigger = DropdownMenu.Trigger 15 + // export const DropdownMenuTrigger = DropdownMenu.Trigger 22 16 export const DropdownMenuContent = DropdownMenu.Content 17 + 18 + type TriggerProps = Omit< 19 + React.ComponentProps<(typeof DropdownMenu)['Trigger']>, 20 + 'children' 21 + > & 22 + React.PropsWithChildren<{ 23 + testID?: string 24 + accessibilityLabel?: string 25 + accessibilityHint?: string 26 + }> 27 + export const DropdownMenuTrigger = DropdownMenu.create( 28 + (props: TriggerProps) => { 29 + const theme = useTheme() 30 + const defaultCtrlColor = theme.palette.default.postCtrl 31 + const ref = React.useRef<View>(null) 32 + 33 + // HACK 34 + // fire a click event on the keyboard press to trigger the dropdown 35 + // -prf 36 + const onPress = isWeb 37 + ? (evt: any) => { 38 + if (evt instanceof KeyboardEvent) { 39 + // @ts-ignore web only -prf 40 + ref.current?.click() 41 + } 42 + } 43 + : undefined 44 + 45 + return ( 46 + <Pressable 47 + testID={props.testID} 48 + accessibilityRole="button" 49 + accessibilityLabel={props.accessibilityLabel} 50 + accessibilityHint={props.accessibilityHint} 51 + style={({pressed}) => [{opacity: pressed ? 0.5 : 1}]} 52 + hitSlop={HITSLOP_10} 53 + onPress={onPress}> 54 + <DropdownMenu.Trigger action="press"> 55 + <View ref={ref}> 56 + {props.children ? ( 57 + props.children 58 + ) : ( 59 + <FontAwesomeIcon 60 + icon="ellipsis" 61 + size={20} 62 + color={defaultCtrlColor} 63 + style={styles.ellipsis} 64 + /> 65 + )} 66 + </View> 67 + </DropdownMenu.Trigger> 68 + </Pressable> 69 + ) 70 + }, 71 + 'Trigger', 72 + ) 73 + 23 74 type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']> 24 75 export const DropdownMenuItem = DropdownMenu.create( 25 76 (props: ItemProps & {testID?: string}) => { 26 - const pal = usePalette('default') 27 77 const theme = useTheme() 28 78 const [focused, setFocused] = React.useState(false) 29 - const {borderColor: backgroundColor} = 30 - theme.colorScheme === 'dark' ? pal.borderDark : pal.border 79 + const backgroundColor = theme.colorScheme === 'dark' ? '#fff1' : '#0001' 31 80 32 81 return ( 33 82 <DropdownMenu.Item ··· 46 95 }, 47 96 'Item', 48 97 ) 98 + 49 99 type TitleProps = React.ComponentProps<(typeof DropdownMenu)['ItemTitle']> 50 100 export const DropdownMenuItemTitle = DropdownMenu.create( 51 101 (props: TitleProps) => { ··· 59 109 }, 60 110 'ItemTitle', 61 111 ) 112 + 62 113 type IconProps = React.ComponentProps<(typeof DropdownMenu)['ItemIcon']> 63 114 export const DropdownMenuItemIcon = DropdownMenu.create((props: IconProps) => { 64 115 return <DropdownMenu.ItemIcon {...props} /> 65 116 }, 'ItemIcon') 117 + 66 118 type SeparatorProps = React.ComponentProps<(typeof DropdownMenu)['Separator']> 67 119 export const DropdownMenuSeparator = DropdownMenu.create( 68 120 (props: SeparatorProps) => { ··· 97 149 } 98 150 type Props = { 99 151 items: DropdownItem[] 100 - children?: React.ReactNode 101 152 testID?: string 153 + accessibilityLabel?: string 154 + accessibilityHint?: string 102 155 } 103 156 104 157 /* The `NativeDropdown` function uses native iOS and Android dropdown menus. ··· 107 160 * @prop {DropdownItem[]} items - An array of dropdown items 108 161 * @prop {React.ReactNode} children - A custom dropdown trigger 109 162 */ 110 - export function NativeDropdown({items, children, testID}: Props) { 163 + export function NativeDropdown({ 164 + items, 165 + children, 166 + testID, 167 + accessibilityLabel, 168 + accessibilityHint, 169 + }: React.PropsWithChildren<Props>) { 111 170 const pal = usePalette('default') 112 171 const theme = useTheme() 113 172 const dropDownBackgroundColor = 114 173 theme.colorScheme === 'dark' ? pal.btn : pal.viewLight 115 - const defaultCtrlColor = React.useMemo( 116 - () => ({ 117 - color: theme.palette.default.postCtrl, 118 - }), 119 - [theme], 120 - ) as StyleProp<ViewStyle> 121 174 122 175 return ( 123 176 <DropdownMenuRoot> 124 - <DropdownMenuTrigger action="press"> 125 - <Pressable 126 - testID={testID} 127 - accessibilityRole="button" 128 - style={({pressed}) => [{opacity: pressed ? 0.5 : 1}]} 129 - hitSlop={HITSLOP_10}> 130 - {children ? ( 131 - children 132 - ) : ( 133 - <FontAwesomeIcon 134 - icon="ellipsis" 135 - size={20} 136 - style={[defaultCtrlColor, styles.ellipsis]} 137 - /> 138 - )} 139 - </Pressable> 177 + <DropdownMenuTrigger 178 + action="press" 179 + testID={testID} 180 + accessibilityLabel={accessibilityLabel} 181 + accessibilityHint={accessibilityHint}> 182 + {children} 140 183 </DropdownMenuTrigger> 141 184 <DropdownMenuContent 142 185 style={[styles.content, dropDownBackgroundColor]}
+9 -4
src/view/com/util/forms/PostDropdownBtn.tsx
··· 6 6 NativeDropdown, 7 7 DropdownItem as NativeDropdownItem, 8 8 } from './NativeDropdown' 9 - import {Pressable} from 'react-native' 9 + import {EventStopper} from '../EventStopper' 10 10 11 11 export function PostDropdownBtn({ 12 12 testID, ··· 141 141 ].filter(Boolean) as NativeDropdownItem[] 142 142 143 143 return ( 144 - <Pressable testID={testID} accessibilityRole="button"> 145 - <NativeDropdown items={dropdownItems} /> 146 - </Pressable> 144 + <EventStopper> 145 + <NativeDropdown 146 + testID={testID} 147 + items={dropdownItems} 148 + accessibilityLabel="More post options" 149 + accessibilityHint="" 150 + /> 151 + </EventStopper> 147 152 ) 148 153 }