Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
at main 142 lines 3.8 kB view raw
1import React 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] = React.useState<EmojiPickerState>({ 45 isOpen: false, 46 pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null}, 47 }) 48 49 const onOpenPicker = React.useCallback( 50 (pos: EmojiPickerPosition | undefined) => { 51 if (!pos) return 52 setPickerState({ 53 isOpen: true, 54 pos, 55 }) 56 }, 57 [], 58 ) 59 60 const onClosePicker = React.useCallback(() => { 61 setPickerState(prev => ({ 62 ...prev, 63 isOpen: false, 64 })) 65 }, []) 66 67 FocusGuards.useFocusGuards() 68 69 return ( 70 <FocusScope.FocusScope loop trapped asChild> 71 <DismissableLayer.DismissableLayer 72 role="dialog" 73 aria-modal 74 style={flatten([ 75 {position: 'fixed'}, 76 a.inset_0, 77 {backgroundColor: '#000c'}, 78 a.flex, 79 a.flex_col, 80 a.align_center, 81 !reduceMotionEnabled && a.fade_in, 82 ])} 83 onFocusOutside={evt => evt.preventDefault()} 84 onInteractOutside={evt => evt.preventDefault()} 85 onDismiss={() => { 86 // TEMP: remove when all modals are ALF'd -sfn 87 if (!isModalActive) { 88 ref.current?.onPressCancel() 89 } 90 }}> 91 <View 92 style={[ 93 styles.container, 94 !gtMobile && styles.containerMobile, 95 t.atoms.bg, 96 t.atoms.border_contrast_medium, 97 !reduceMotionEnabled && [ 98 a.zoom_fade_in, 99 {animationDelay: 0.1}, 100 {animationFillMode: 'backwards'}, 101 ], 102 ]}> 103 <ComposePost 104 cancelRef={ref} 105 replyTo={state.replyTo} 106 quote={state.quote} 107 onPost={state.onPost} 108 onPostSuccess={state.onPostSuccess} 109 mention={state.mention} 110 openEmojiPicker={onOpenPicker} 111 text={state.text} 112 imageUris={state.imageUris} 113 videoUri={state.videoUri} 114 openGallery={state.openGallery} 115 /> 116 </View> 117 <EmojiPicker state={pickerState} close={onClosePicker} /> 118 </DismissableLayer.DismissableLayer> 119 </FocusScope.FocusScope> 120 ) 121} 122 123const styles = StyleSheet.create({ 124 container: { 125 marginTop: 50, 126 maxWidth: 600, 127 width: '100%', 128 paddingVertical: 0, 129 borderRadius: 8, 130 marginBottom: 0, 131 borderWidth: 1, 132 // @ts-expect-error web only 133 maxHeight: 'calc(100% - (40px * 2))', 134 overflow: 'hidden', 135 }, 136 containerMobile: { 137 borderRadius: 0, 138 marginBottom: BOTTOM_BAR_HEIGHT, 139 // @ts-expect-error web only 140 maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`, 141 }, 142})