Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at main 139 lines 3.8 kB view raw
1import {useCallback, useState} from 'react' 2import {StyleSheet, View} from 'react-native' 3import {DismissableLayer, FocusGuards, FocusScope} from 'radix-ui/internal' 4import {RemoveScrollBar} from 'react-remove-scroll-bar' 5 6import {useA11y} from '#/state/a11y' 7import {useModals} from '#/state/modals' 8import {type ComposerOpts, useComposerState} from '#/state/shell/composer' 9import { 10 EmojiPicker, 11 type EmojiPickerPosition, 12 type EmojiPickerState, 13} from '#/view/com/composer/text-input/web/EmojiPicker' 14import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf' 15import {ComposePost, useComposerCancelRef} from '../com/composer/Composer' 16 17const BOTTOM_BAR_HEIGHT = 61 18 19export function Composer({}: {winHeight: number}) { 20 const state = useComposerState() 21 const isActive = !!state 22 23 // rendering 24 // = 25 26 if (!isActive) { 27 return null 28 } 29 30 return ( 31 <> 32 <RemoveScrollBar /> 33 <Inner state={state} /> 34 </> 35 ) 36} 37 38function Inner({state}: {state: ComposerOpts}) { 39 const ref = useComposerCancelRef() 40 const {isModalActive} = useModals() 41 const t = useTheme() 42 const {gtMobile} = useBreakpoints() 43 const {reduceMotionEnabled} = useA11y() 44 const [pickerState, setPickerState] = useState<EmojiPickerState>({ 45 isOpen: false, 46 pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null}, 47 }) 48 49 const onOpenPicker = useCallback((pos: EmojiPickerPosition | undefined) => { 50 if (!pos) return 51 setPickerState({ 52 isOpen: true, 53 pos, 54 }) 55 }, []) 56 57 const onClosePicker = useCallback(() => { 58 setPickerState(prev => ({ 59 ...prev, 60 isOpen: false, 61 })) 62 }, []) 63 64 FocusGuards.useFocusGuards() 65 66 return ( 67 <FocusScope.FocusScope loop trapped asChild> 68 <DismissableLayer.DismissableLayer 69 role="dialog" 70 aria-modal 71 style={flatten([ 72 {position: 'fixed'}, 73 a.inset_0, 74 {backgroundColor: '#000c'}, 75 a.flex, 76 a.flex_col, 77 a.align_center, 78 !reduceMotionEnabled && a.fade_in, 79 ])} 80 onFocusOutside={evt => evt.preventDefault()} 81 onInteractOutside={evt => evt.preventDefault()} 82 onDismiss={() => { 83 // TEMP: remove when all modals are ALF'd -sfn 84 if (!isModalActive) { 85 ref.current?.onPressCancel() 86 } 87 }}> 88 <View 89 style={[ 90 styles.container, 91 !gtMobile && styles.containerMobile, 92 t.atoms.bg, 93 t.atoms.border_contrast_medium, 94 !reduceMotionEnabled && [ 95 a.zoom_fade_in, 96 {animationDelay: 0.1}, 97 {animationFillMode: 'backwards'}, 98 ], 99 ]}> 100 <ComposePost 101 cancelRef={ref} 102 replyTo={state.replyTo} 103 quote={state.quote} 104 onPost={state.onPost} 105 onPostSuccess={state.onPostSuccess} 106 mention={state.mention} 107 openEmojiPicker={onOpenPicker} 108 text={state.text} 109 imageUris={state.imageUris} 110 videoUri={state.videoUri} 111 openGallery={state.openGallery} 112 /> 113 </View> 114 <EmojiPicker state={pickerState} close={onClosePicker} /> 115 </DismissableLayer.DismissableLayer> 116 </FocusScope.FocusScope> 117 ) 118} 119 120const styles = StyleSheet.create({ 121 container: { 122 marginTop: 50, 123 maxWidth: 600, 124 width: '100%', 125 paddingVertical: 0, 126 borderRadius: 8, 127 marginBottom: 0, 128 borderWidth: 1, 129 // @ts-expect-error web only 130 maxHeight: 'calc(100% - (40px * 2))', 131 overflow: 'hidden', 132 }, 133 containerMobile: { 134 borderRadius: 0, 135 marginBottom: BOTTOM_BAR_HEIGHT, 136 // @ts-expect-error web only 137 maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`, 138 }, 139})