about summary refs log tree commit diff
path: root/modules/bottom-sheet/src/BottomSheetNativeComponent.tsx
blob: fa2b163bf031c30ef9cf1af6fc2739c78c0f4d37 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import * as React from 'react'
import {
  Dimensions,
  NativeSyntheticEvent,
  Platform,
  StyleProp,
  View,
  ViewStyle,
} from 'react-native'
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'

import {BottomSheetState, BottomSheetViewProps} from './BottomSheet.types'
import {BottomSheetPortalProvider} from './BottomSheetPortal'

const screenHeight = Dimensions.get('screen').height

const NativeView: React.ComponentType<
  BottomSheetViewProps & {
    ref: React.RefObject<any>
    style: StyleProp<ViewStyle>
  }
> = requireNativeViewManager('BottomSheet')

const NativeModule = requireNativeModule('BottomSheet')

const isIOS15 = Platform.OS === 'ios' && Number(Platform.Version) < 16

export class BottomSheetNativeComponent extends React.Component<
  BottomSheetViewProps,
  {
    open: boolean
    viewHeight?: number
  }
> {
  ref = React.createRef<any>()

  constructor(props: BottomSheetViewProps) {
    super(props)
    this.state = {
      open: false,
    }
  }

  present() {
    this.setState({open: true})
  }

  dismiss() {
    this.ref.current?.dismiss()
  }

  private onStateChange = (
    event: NativeSyntheticEvent<{state: BottomSheetState}>,
  ) => {
    const {state} = event.nativeEvent
    const isOpen = state !== 'closed'
    this.setState({open: isOpen})
    this.props.onStateChange?.(event)
  }

  private updateLayout = () => {
    this.ref.current?.updateLayout()
  }

  static dismissAll = async () => {
    await NativeModule.dismissAll()
  }

  render() {
    const {children, backgroundColor, ...rest} = this.props
    const cornerRadius = rest.cornerRadius ?? 0

    let extraStyles
    if (isIOS15 && this.state.viewHeight) {
      const {viewHeight} = this.state
      if (viewHeight < screenHeight / 2) {
        extraStyles = {
          height: viewHeight,
          marginTop: screenHeight / 2 - viewHeight,
          borderTopLeftRadius: cornerRadius,
          borderTopRightRadius: cornerRadius,
        }
      }
    }

    if (!this.state.open) {
      return null
    }

    return (
      <NativeView
        {...rest}
        onStateChange={this.onStateChange}
        ref={this.ref}
        style={{
          position: 'absolute',
          height: screenHeight,
          width: '100%',
        }}
        containerBackgroundColor={backgroundColor}>
        <View
          style={[
            {
              flex: 1,
              backgroundColor,
            },
            Platform.OS === 'android' && {
              borderTopLeftRadius: cornerRadius,
              borderTopRightRadius: cornerRadius,
            },
            extraStyles,
          ]}>
          <View
            onLayout={e => {
              const {height} = e.nativeEvent.layout
              this.setState({viewHeight: height})
              this.updateLayout()
            }}>
            <BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
          </View>
        </View>
      </NativeView>
    )
  }
}