Bluesky app fork with some witchin' additions 馃挮
at main 4.9 kB view raw
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 if (isIOS15) { 116 const {height} = e.nativeEvent.layout 117 this.setState({viewHeight: height}) 118 } 119 if (Platform.OS === 'android') { 120 // TEMP HACKFIX: I had to timebox this, but this is Bad. 121 // On Android, if you run updateLayout() immediately, 122 // it will take ages to actually run on the native side. 123 // However, adding literally any delay will fix this, including 124 // a console.log() - just sending the log to the CLI is enough. 125 // TODO: Get to the bottom of this and fix it properly! -sfn 126 setTimeout(() => this.updateLayout()) 127 } else { 128 this.updateLayout() 129 } 130 }} 131 /> 132 </Portal> 133 ) 134 } 135} 136 137function BottomSheetNativeComponentInner({ 138 children, 139 backgroundColor, 140 onLayout, 141 onStateChange, 142 nativeViewRef, 143 extraStyles, 144 ...rest 145}: BottomSheetViewProps & { 146 extraStyles?: StyleProp<ViewStyle> 147 onStateChange: ( 148 event: NativeSyntheticEvent<{state: BottomSheetState}>, 149 ) => void 150 nativeViewRef: React.RefObject<View> 151 onLayout: (event: LayoutChangeEvent) => void 152}) { 153 const insets = useSafeAreaInsets() 154 const cornerRadius = rest.cornerRadius ?? 0 155 156 const sheetHeight = isIOS ? screenHeight - insets.top : screenHeight 157 158 return ( 159 <NativeView 160 {...rest} 161 onStateChange={onStateChange} 162 ref={nativeViewRef} 163 style={{ 164 position: 'absolute', 165 height: sheetHeight, 166 width: '100%', 167 }} 168 containerBackgroundColor={backgroundColor}> 169 <View 170 style={[ 171 { 172 flex: 1, 173 backgroundColor, 174 }, 175 Platform.OS === 'android' && { 176 borderTopLeftRadius: cornerRadius, 177 borderTopRightRadius: cornerRadius, 178 }, 179 extraStyles, 180 ]}> 181 <View onLayout={onLayout}> 182 <BottomSheetPortalProvider>{children}</BottomSheetPortalProvider> 183 </View> 184 </View> 185 </NativeView> 186 ) 187}