mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import * as React from 'react'
2import {
3 Dimensions,
4 type LayoutChangeEvent,
5 type NativeSyntheticEvent,
6 Platform,
7 type StyleProp,
8 View,
9 type ViewStyle,
10} from 'react-native'
11import {useSafeAreaInsets} from 'react-native-safe-area-context'
12import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
13
14import {isIOS} from '#/platform/detection'
15import {
16 type BottomSheetState,
17 type BottomSheetViewProps,
18} from './BottomSheet.types'
19import {BottomSheetPortalProvider} from './BottomSheetPortal'
20import {Context as PortalContext} from './BottomSheetPortal'
21
22const screenHeight = Dimensions.get('screen').height
23
24const NativeView: React.ComponentType<
25 BottomSheetViewProps & {
26 ref: React.RefObject<any>
27 style: StyleProp<ViewStyle>
28 }
29> = requireNativeViewManager('BottomSheet')
30
31const NativeModule = requireNativeModule('BottomSheet')
32
33const isIOS15 =
34 Platform.OS === 'ios' &&
35 // semvar - can be 3 segments, so can't use Number(Platform.Version)
36 Number(Platform.Version.split('.').at(0)) < 16
37
38export class BottomSheetNativeComponent extends React.Component<
39 BottomSheetViewProps,
40 {
41 open: boolean
42 viewHeight?: number
43 }
44> {
45 ref = React.createRef<any>()
46
47 static contextType = PortalContext
48
49 constructor(props: BottomSheetViewProps) {
50 super(props)
51 this.state = {
52 open: false,
53 }
54 }
55
56 present() {
57 this.setState({open: true})
58 }
59
60 dismiss() {
61 this.ref.current?.dismiss()
62 }
63
64 private onStateChange = (
65 event: NativeSyntheticEvent<{state: BottomSheetState}>,
66 ) => {
67 const {state} = event.nativeEvent
68 const isOpen = state !== 'closed'
69 this.setState({open: isOpen})
70 this.props.onStateChange?.(event)
71 }
72
73 private updateLayout = () => {
74 this.ref.current?.updateLayout()
75 }
76
77 static dismissAll = async () => {
78 await NativeModule.dismissAll()
79 }
80
81 render() {
82 const Portal = this.context as React.ContextType<typeof PortalContext>
83 if (!Portal) {
84 throw new Error(
85 'BottomSheet: You need to wrap your component tree with a <BottomSheetPortalProvider> to use the bottom sheet.',
86 )
87 }
88
89 if (!this.state.open) {
90 return null
91 }
92
93 let extraStyles
94 if (isIOS15 && this.state.viewHeight) {
95 const {viewHeight} = this.state
96 const cornerRadius = this.props.cornerRadius ?? 0
97 if (viewHeight < screenHeight / 2) {
98 extraStyles = {
99 height: viewHeight,
100 marginTop: screenHeight / 2 - viewHeight,
101 borderTopLeftRadius: cornerRadius,
102 borderTopRightRadius: cornerRadius,
103 }
104 }
105 }
106
107 return (
108 <Portal>
109 <BottomSheetNativeComponentInner
110 {...this.props}
111 nativeViewRef={this.ref}
112 onStateChange={this.onStateChange}
113 extraStyles={extraStyles}
114 onLayout={e => {
115 const {height} = e.nativeEvent.layout
116 this.setState({viewHeight: height})
117 this.updateLayout()
118 }}
119 />
120 </Portal>
121 )
122 }
123}
124
125function BottomSheetNativeComponentInner({
126 children,
127 backgroundColor,
128 onLayout,
129 onStateChange,
130 nativeViewRef,
131 extraStyles,
132 ...rest
133}: BottomSheetViewProps & {
134 extraStyles?: StyleProp<ViewStyle>
135 onStateChange: (
136 event: NativeSyntheticEvent<{state: BottomSheetState}>,
137 ) => void
138 nativeViewRef: React.RefObject<View>
139 onLayout: (event: LayoutChangeEvent) => void
140}) {
141 const insets = useSafeAreaInsets()
142 const cornerRadius = rest.cornerRadius ?? 0
143
144 const sheetHeight = isIOS ? screenHeight - insets.top : screenHeight
145
146 return (
147 <NativeView
148 {...rest}
149 onStateChange={onStateChange}
150 ref={nativeViewRef}
151 style={{
152 position: 'absolute',
153 height: sheetHeight,
154 width: '100%',
155 }}
156 containerBackgroundColor={backgroundColor}>
157 <View
158 style={[
159 {
160 flex: 1,
161 backgroundColor,
162 },
163 Platform.OS === 'android' && {
164 borderTopLeftRadius: cornerRadius,
165 borderTopRightRadius: cornerRadius,
166 },
167 extraStyles,
168 ]}>
169 <View onLayout={onLayout}>
170 <BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
171 </View>
172 </View>
173 </NativeView>
174 )
175}