Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
119
fork

Configure Feed

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

at a876aae44ea07494ebea9727350aa060b81f317b 142 lines 3.8 kB view raw
1import { 2 Children, 3 cloneElement, 4 isValidElement, 5 useCallback, 6 useEffect, 7 useMemo, 8 useRef, 9} from 'react' 10import { 11 AccessibilityInfo, 12 findNodeHandle, 13 Pressable, 14 Text, 15 View, 16} from 'react-native' 17import {msg} from '@lingui/core/macro' 18import {useLingui} from '@lingui/react' 19 20import {useA11y} from '#/state/a11y' 21 22/** 23 * Conditionally wraps children in a `FocusTrap` component based on whether 24 * screen reader support is enabled. THIS SHOULD BE USED SPARINGLY, only when 25 * no better option is available. 26 */ 27export function FocusScope({children}: {children: React.ReactNode}) { 28 const {screenReaderEnabled} = useA11y() 29 30 return screenReaderEnabled ? <FocusTrap>{children}</FocusTrap> : children 31} 32 33/** 34 * `FocusTrap` is intended as a last-ditch effort to ensure that users keep 35 * focus within a certain section of the app, like an overlay. 36 * 37 * It works by placing "guards" at the start and end of the active content. 38 * Then when the user reaches either of those guards, it will announce that 39 * they have reached the start or end of the content and tell them how to 40 * remain within the active content section. 41 */ 42function FocusTrap({children}: {children: React.ReactNode}) { 43 const {_} = useLingui() 44 const child = useRef<View>(null) 45 46 /* 47 * Here we add a ref to the first child of this component. This currently 48 * overrides any ref already on that first child, so we throw an error here 49 * to prevent us from ever accidentally doing this. 50 */ 51 const decoratedChildren = useMemo(() => { 52 return Children.toArray(children).map((node, i) => { 53 if (i === 0 && isValidElement(node)) { 54 const n = node as React.ReactElement<any> 55 if (n.props.ref !== undefined) { 56 throw new Error( 57 'FocusScope needs to override the ref on its first child.', 58 ) 59 } 60 return cloneElement(n, { 61 ...n.props, 62 ref: child, 63 }) 64 } 65 return node 66 }) 67 }, [children]) 68 69 const focusNode = useCallback((ref: View | null) => { 70 if (!ref) return 71 const node = findNodeHandle(ref) 72 if (node) { 73 AccessibilityInfo.setAccessibilityFocus(node) 74 } 75 }, []) 76 77 useEffect(() => { 78 setTimeout(() => { 79 focusNode(child.current) 80 }, 1e3) 81 }, [focusNode]) 82 83 return ( 84 <> 85 <Pressable 86 accessible 87 accessibilityLabel={_( 88 msg`You've reached the start of the active content.`, 89 )} 90 accessibilityHint={_( 91 msg`Please go back, or activate this element to return to the start of the active content.`, 92 )} 93 accessibilityActions={[{name: 'activate', label: 'activate'}]} 94 onAccessibilityAction={event => { 95 switch (event.nativeEvent.actionName) { 96 case 'activate': { 97 focusNode(child.current) 98 } 99 } 100 }}> 101 <Noop /> 102 </Pressable> 103 <View 104 /** 105 * This property traps focus effectively on iOS, but not on Android. 106 */ 107 accessibilityViewIsModal> 108 {decoratedChildren} 109 </View> 110 <Pressable 111 accessibilityLabel={_( 112 msg`You've reached the end of the active content.`, 113 )} 114 accessibilityHint={_( 115 msg`Please go back, or activate this element to return to the start of the active content.`, 116 )} 117 accessibilityActions={[{name: 'activate', label: 'activate'}]} 118 onAccessibilityAction={event => { 119 switch (event.nativeEvent.actionName) { 120 case 'activate': { 121 focusNode(child.current) 122 } 123 } 124 }}> 125 <Noop /> 126 </Pressable> 127 </> 128 ) 129} 130 131function Noop() { 132 return ( 133 <Text 134 accessible={false} 135 style={{ 136 height: 1, 137 opacity: 0, 138 }}> 139 {' '} 140 </Text> 141 ) 142}