this repo has no description
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 openGallery={state.openGallery}
111 />
112 </View>
113 <EmojiPicker state={pickerState} close={onClosePicker} />
114 </DismissableLayer.DismissableLayer>
115 </FocusScope.FocusScope>
116 )
117}
118
119const styles = StyleSheet.create({
120 container: {
121 marginTop: 50,
122 maxWidth: 600,
123 width: '100%',
124 paddingVertical: 0,
125 borderRadius: 8,
126 marginBottom: 0,
127 borderWidth: 1,
128 // @ts-expect-error web only
129 maxHeight: 'calc(100% - (40px * 2))',
130 overflow: 'hidden',
131 },
132 containerMobile: {
133 borderRadius: 0,
134 marginBottom: BOTTOM_BAR_HEIGHT,
135 // @ts-expect-error web only
136 maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`,
137 },
138})