mirror of https://github.com/tooot-app/app
115 lines
2.8 KiB
TypeScript
115 lines
2.8 KiB
TypeScript
import React, { useEffect, useRef } from 'react'
|
|
import {
|
|
Animated,
|
|
Dimensions,
|
|
Modal,
|
|
PanResponder,
|
|
StyleSheet,
|
|
View
|
|
} from 'react-native'
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
|
import { StyleConstants } from 'src/utils/styles/constants'
|
|
import { ButtonRow } from './Button'
|
|
|
|
export interface Props {
|
|
children: React.ReactNode
|
|
visible: boolean
|
|
handleDismiss: () => void
|
|
}
|
|
|
|
const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
|
const { theme } = useTheme()
|
|
const insets = useSafeAreaInsets()
|
|
|
|
const panY = useRef(new Animated.Value(Dimensions.get('screen').height))
|
|
.current
|
|
const top = panY.interpolate({
|
|
inputRange: [-1, 0, 1],
|
|
outputRange: [0, 0, 1]
|
|
})
|
|
const resetModal = Animated.timing(panY, {
|
|
toValue: 0,
|
|
duration: 300,
|
|
useNativeDriver: false
|
|
})
|
|
|
|
const closeModal = Animated.timing(panY, {
|
|
toValue: Dimensions.get('screen').height,
|
|
duration: 350,
|
|
useNativeDriver: false
|
|
})
|
|
|
|
const panResponder = useRef(
|
|
PanResponder.create({
|
|
onStartShouldSetPanResponder: () => true,
|
|
onMoveShouldSetPanResponder: () => true,
|
|
onPanResponderMove: Animated.event([null, { dy: panY }], {
|
|
useNativeDriver: false
|
|
}),
|
|
onPanResponderRelease: (e, gs) => {
|
|
if (gs.dy > 0 && gs.vy > 1) {
|
|
return closeModal.start(() => handleDismiss())
|
|
} else if (gs.dy === 0 && gs.vy === 0) {
|
|
return closeModal.start(() => handleDismiss())
|
|
}
|
|
return resetModal.start()
|
|
}
|
|
})
|
|
).current
|
|
|
|
useEffect(() => {
|
|
if (visible) {
|
|
resetModal.start()
|
|
}
|
|
}, [visible])
|
|
|
|
return (
|
|
<Modal animated animationType='fade' visible={visible} transparent>
|
|
<View
|
|
style={[styles.overlay, { backgroundColor: theme.border }]}
|
|
{...panResponder.panHandlers}
|
|
>
|
|
<Animated.View
|
|
style={[
|
|
styles.container,
|
|
{
|
|
top,
|
|
backgroundColor: theme.background,
|
|
paddingBottom: insets.bottom || StyleConstants.Spacing.L
|
|
}
|
|
]}
|
|
>
|
|
<View
|
|
style={[styles.handle, { backgroundColor: theme.background }]}
|
|
/>
|
|
{children}
|
|
<ButtonRow
|
|
onPress={() => closeModal.start(() => handleDismiss())}
|
|
text='取消'
|
|
/>
|
|
</Animated.View>
|
|
</View>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
overlay: {
|
|
flex: 1,
|
|
justifyContent: 'flex-end'
|
|
},
|
|
container: {
|
|
paddingTop: StyleConstants.Spacing.M
|
|
},
|
|
handle: {
|
|
alignSelf: 'center',
|
|
width: StyleConstants.Spacing.S * 8,
|
|
height: StyleConstants.Spacing.S / 2,
|
|
borderRadius: 100,
|
|
top: -StyleConstants.Spacing.M * 2
|
|
}
|
|
})
|
|
|
|
export default BottomSheet
|