gorhom / react-native-bottom-sheet Goto Github PK
View Code? Open in Web Editor NEWA performant interactive bottom sheet with fully configurable options ๐
Home Page: https://ui.gorhom.dev/components/bottom-sheet
License: MIT License
A performant interactive bottom sheet with fully configurable options ๐
Home Page: https://ui.gorhom.dev/components/bottom-sheet
License: MIT License
Hi!
First of al; thanks for making this awesome package. It works very well.
Today I tried out the <BottomSheetScrollView />
component to enhance the scroll behavior in the bottom sheet.
I have 3 snap points, and if I scroll between snap points 1 and 2 everything works fine, but when I scroll to snap point 0 and then scroll back, the animation is not smooth anymore. It just snaps in to place. I made a video to clarify. You can see the animation is working properly untill I swipe the sheet to the bottom.
Library | Version |
---|---|
"@gorhom/bottom-sheet": "^1.2.0", | |
"react-native": "0.63.0", | |
"react-native-reanimated": "^1.13.0", | |
"react-native-gesture-handler": "^1.7.0", |
This is the code for the bottom sheet
const bottomSheetRef = useRef(null);
const snapPoints = useMemo(() => ['5%', '65%', '80%'], []);
...
<BottomSheet
ref={bottomSheetRef}
initialSnapIndex={1}
snapPoints={snapPoints}
handleComponent={() => <SheetHandle />}
>
<BottomSheetScrollView
style={{ flex: 1, backgroundColor: 'white' }}
>
<View style={{ padding: 20, paddingTop: 0 }}>
<Text>Lorem...</Text>
</View>
</BottomSheetScrollView>
</BottomSheet>
I'm able to get it working on iOS, but Android it isn't selectable or usable. Curious if there is an example somewhere I could look at if it has been done before. Thanks for the awesome library!
First Device Pixel 3a, Second Device 4xl.
Hi @gorhom ,
It would be possible to implement two props to be able to change the color of both the handle and the container that contains the handle.
I tried to create my own handle in order to do this but I realized that on two devices both android their display changes as you can see in the image above.
Instead using the normal non-customized one remains the same.
I don't know if I did something wrong, below is the code of the handle I use.
indicator: {
alignSelf: 'center',
width: (8 * SCREEN_WIDTH) / 100,
height: 5,
borderRadius: 4,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
}
const handle = () => {
return (
<View
style={{
paddingHorizontal: 16,
paddingVertical: 5,
backgroundColor,
}}>
<View
style={[
styles.indicator,
{
backgroundColor:
settings.item.colorScheme === 'light'
? 'rgba(0, 0, 0, 0.25)'
: 'rgba(255, 255, 255, 0.25)',
},
]}
/>
</View>
);
};
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.63.3 |
react-native-reanimated | 1.13.1 |
react-native-gesture-handler | 1.8.0 |
I have created a context for filter options (filtering is selected in bottom sheet)
import React from 'react';
export type FilterContextValue = {
filter?: 'DATE' | 'NAME';
setFilter: (filter: FilterContextValue['filter']) => void;
};
export default React.createContext<FilterContextValue>({
setFilter: () => {},
});
const FilterProvider = ({children}: {children: React.ReactNode}) => {
const [filter, setFilter] = useState<FilterContextValue['filter']>();
const value = useMemo(() => ({filter, setFilter}), [filter, setFilter]);
return <FilterContext.Provider value={value}>{children}</FilterContext.Provider>;
};
export default FilterProvider;
import {TouchableWithoutFeedback} from '@gorhom/bottom-sheet';
...
const {setFilter} = useContext(FilterCtx);
<BottomSheetView>
<TouchableWithoutFeedback
onPress={() => {
setFilter('NAME');
}}>
<Text>By name</Text>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={() => setFilter('NAME')}>
<Text>By date</Text>
</TouchableWithoutFeedback>
</BottomSheetView>
Describe what you expected to happen:
I expect that pressing By name
and By date
, the setFilter
method created in FilterProvider would be called. In fact, it is the default method (in React.createContext which is called)
I have tried to use setFilter elsewhere and it works fine (that's why I think it is related to bottom-sheets)
I will create one later
Hi @gorhom. I can't find any props relate with gestureStateChange. Can we have one?
My case I have some conflict relate with React Native TabView. So I might need to disable the tabView while the bottom-sheet start drag up/down.
It's good to have Handle State
https://docs.swmansion.com/react-native-gesture-handler/docs/state
There are six possible states for the handler:
UNDETERMINED
FAILED
BEGAN
CANCELLED
ACTIVE
END
Thanks
Through these styles:
src/components/BottomSheet/styles.ts
container: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
}
it happens that when opening the Keyboard on android (e.g. having a text input in the bottom sheet's content) the content gets pushed up:
This is the default behaviour on android. Interestingly, this doesn't happen when changing the styles from bottom
-> top
.
container: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
}
However, this breaks the BottomSheet, as all calculations are thought to be run from the bottom edge.
I am aware that it is possible to change on android the windowSoftInputMode, however, this is quite invasive and doesn't work with projects using react native navigation.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.63.3 |
react-native-reanimated | 2.0.0-alpha.8 |
react-native-gesture-handler | 1.8.0 |
Please tell me whether reproduction is necessary.
I've tried to add the library two times and follow all the necessary instructions to make it run but I always got Cannot convert undefined value to object
What I did:
Hey!
I just started using this, and so far it's great! Seems like the best bottom drawer out there
I'm currently trying to use this library with an Expo / React Native Web app. In order to get it working, I used patch-package
to add a Touchables.web.js
file and a ContentWrapper.web.js
file that are exact replicas of their respective ios files. I was wondering if it would be possible to add these two files so patching isn't necessary to support web?
Thanks!
Hi, @gorhom.
Is it possible to set zIndex for main bottom-sheet container?
Thanks
First of all thanks for creating this awesome library!
I have noticed that on iOS <BottomSheet/>
's onChange
prop is being called when the component is mounted. I suspect this behavior also exists in useBottomSheetModal
.
On Android, it is working as expected though. I am not sure, maybe I am doing something wrong.
Library | Version |
---|---|
@gorhom/bottom-sheet | ^1.4.1 |
react-native | ~0.62.2 |
react-native-reanimated | ~1.9.0 |
react-native-gesture-handler | ~1.6.0 |
I wanted to have 2 BottomSheet
s which I could "navigate" between them. When the second one is closed, the first one should be opened. But when I open the app on iPhone it expands the first BottomSheet
immediately.
Initially, I used <BottomSheet/>
(first one) and useBottomSheetModal
(for the second one), but it didn't work as I expected, probably because of the same reason.
export function HomeScreen() {
const [isButtonVisible, setIsButtonVisible] = useState(true);
const bottomSheetRef1 = useRef<BottomSheet>(null);
const bottomSheetRef2 = useRef<BottomSheet>(null);
return (
<>
<BottomSheet
ref={bottomSheetRef1}
snapPoints={["20%", "60%"]}
onChange={() => {
// For checking
console.log("onChange is called in first BottomSheet");
}}
>
<MyComponent1 />
</BottomSheet>
<BottomSheet
ref={bottomSheetRef2}
snapPoints={[0, "20%", "60%"]}
onChange={(index) => {
// For checking
console.log("onChange is called in second BottomSheet");
if (index === 0) {
bottomSheetRef1.current?.expand();
setIsButtonVisible(true);
}
}}
>
<MyComponent2 />
</BottomSheet>
{isButtonVisible && (
<View>
<Button
onPress={() => {
bottomSheetRef1.current?.close();
bottomSheetRef2.current?.expand();
setIsButtonVisible(false);
}}
>
Open Second
</Button>
</View>
)}
</>
);
}
/* console output:
onChange is called in second BottomSheet
onChange is called in second BottomSheet
onChange is called in first BottomSheet
*/
Per the docs it should be possible to import Modal Provider like this:
import { BottomSheetModalProvider, useBottomSheetModal } from "@gorhom/bottom-sheet";
however this errors to:
Module '"../../node_modules/@gorhom/bottom-sheet/lib/typescript"' has no exported member 'BottomSheetModalProvider'. Did you mean to use 'import BottomSheetModalProvider from "../../node_modules/@gorhom/bottom-sheet/lib/typescript"' instead?
Library | Version |
---|---|
@gorhom/bottom-sheet | 2.0.0-alpha.0 |
react-native | 0.63.3 |
react-native-reanimated | 2.0.0-alpha.8 |
react-native-gesture-handler | 1.8.0 |
yarn add @gorhom/[email protected]
Describe what you expected to happen:
import { BottomSheetModalProvider, useBottomSheetModal } from "@gorhom/bottom-sheet";
I have a TextInput on top of the BottomSheet.
On Android, when the focus is on the TextInput, the keyboard is open and the top of the BottomSheet is no longer visible.
I try to solve the bug by removing the keyboard.height to the max snapPoint but then, the gestures on the BottomSheet are buggy.
When I look at the Map example, i can see in the AndroidManifest.xml android:windowSoftInputMode="adjustPan"
.
In my project I set windowSoftInputMode
to adjustResize
.
Any idea how can I prevent the BottomSheet to not exceed the maximum height ?
Without focus | focus |
---|---|
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.62.2 |
react-native-reanimated | 1.13.1 |
react-native-gesture-handler | 1.8.0 |
Describe what you expected to happen:
import * as React from 'react';
import BottomSheet, { BottomSheetOverlay, BottomSheetSectionList } from '@gorhom/bottom-sheet';
import { useFocusEffect } from '@react-navigation/native';
import { StackScreenProps, useHeaderHeight } from '@react-navigation/stack';
import { Dimensions, InteractionManager } from 'react-native';
import { Platform, StatusBar } from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import { Easing, Extrapolate, interpolate } from 'react-native-reanimated';
import { useValue } from 'react-native-redash/lib/module/v1';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import styled from 'styled-components/native';
import { MainStackParamList } from 'navigation/MainNavigator';
import BlurredBackground from './BlurredBackground';
import HomeMap from './HomeMap/HomeMap';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const STATUS_BAR_HEIGHT = Platform.select({
android: StatusBar.currentHeight || (Platform.Version < 23 ? 25 : 24),
default: 0,
});
const Home: React.FC<StackScreenProps<MainStackParamList, 'Home'>> = ({}) => {
const bottomSheetRef = React.useRef<BottomSheet>(null);
const { top: topSafeArea, bottom: bottomSafeArea } = useSafeAreaInsets();
const headerHeight = useHeaderHeight();
const snapPoints = React.useMemo(() => [bottomSafeArea + 194, SCREEN_HEIGHT / 2, SCREEN_HEIGHT - STATUS_BAR_HEIGHT - headerHeight - topSafeArea], [
bottomSafeArea,
headerHeight,
topSafeArea,
]);
const animatedPosition = useValue<number>(0);
const animatedPositionIndex = useValue<number>(0);
const animatedOverlayOpacity = React.useMemo(
() =>
interpolate(animatedPosition, {
inputRange: [snapPoints[1], snapPoints[2]],
outputRange: [0, 0.25],
extrapolate: Extrapolate.CLAMP,
}),
[animatedPosition, snapPoints],
);
const handleSheetChanges = React.useCallback((index: number) => {
console.log('handleSheetChanges', index);
}, []);
const initBottomSheet = React.useCallback(() => {
const task = InteractionManager.runAfterInteractions(() => {
bottomSheetRef.current?.snapTo(1);
});
return () => task.cancel();
}, []);
useFocusEffect(initBottomSheet);
return (
<Container>
<HomeMap />
<BottomSheetOverlay pointerEvents="none" animatedOpacity={animatedOverlayOpacity} />
<BottomSheet
ref={bottomSheetRef}
snapPoints={snapPoints}
initialSnapIndex={0}
topInset={topSafeArea}
animatedPosition={animatedPosition}
animatedPositionIndex={animatedPositionIndex}
animationDuration={500}
animationEasing={Easing.out(Easing.exp)}
backgroundComponent={BlurredBackground}
onChange={handleSheetChanges}>
<BottomSheetScreen>
<TextInput />
<BottomSheetSectionList
sections={data}
keyExtractor={(item, index) => item.id + index}
renderItem={renderItem}
renderSectionHeader={renderSectionHeader}
stickySectionHeadersEnabled={false}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
/>
</BottomSheetScreen>
</BottomSheet>
</Container>
);
};
const Container = styled.View`
position: relative;
flex: 1;
`;
const BottomSheetScreen = styled.View`
flex: 1;
`;
export default Home;
The ability to pull the bottom sheet down when it is in its default position and trigger reload of Flatlist/Sectionlist.
Very common action to refresh a list by pulling the list down.
I have tempted something like having three snap points, the lowest for the data fetching.
const handleSheetChange = useCallback(async index => {
if (index === 0) {
// shows the ActivityIndicator and sets a state that is used to locking the bottom sheet
setFetchingScreen(true);
await fetchData()
setFetchingScreen(false);
// Move the sheet to original place when data has been fetched
sheetRef.current?.snapTo(1);
}
}, []);
But it is slow and pretty buggy. Any suggestions of how it would be best to achieve this?
Best regards and thank you for a great library!
Reanimated V2 just released 2.0.0-alpha.8
. It offers lots of fixes over 2.0.0-alpha.7
and no longer requires turbo modules to be globally enables (no more AppDelegate setup). There is a breaking change in that you now must explicitly specify which thread you're running a function, either runOnJS or runOnUI. Functions must either runOnUI if they are to be sync or runOnJS if they are to be async. See here.
In iOS using the bottom sheet results in this error:
Library | Version |
---|---|
@gorhom/bottom-sheet | 2.0.0-alpha.0 |
react-native | 0.63.2 |
react-native-reanimated | 2.0.0-alpha.8 |
react-native-gesture-handler | 1.7.0 |
Use bottom-sheet with Reanimated V2 2.0.0-alpha.8
The current Expo version (38) only supports react-native-reanimated 1.9. It looks this package is using useValue which was introduced in reanimated 1.10.
I'm not sure if there are other methods that this package depends that makes it incompatible with Expo 38.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.1.1 |
react-native | 0.62 (Expo 38) |
react-native-reanimated | ~1.9 |
react-native-gesture-handler | ~1.6.0 |
Render the component and get the following error:
(0, _reactNativeReanimated.useValue) is not a function. (In '(0, _reactNativeReanimated.useValue)(0)', '(0, _reactNativeReanimated.useValue)' is undefined)
The bug appears for more than 1 spnap points.
Suppose you have a button on the sheet with a onPress event handler. The onPress event works only for the last snap point.
otherwise, it wont.
Version V1
<BottomSheet> ... <Button onPress={() => { alert();} }></Button> ... <BottomSheet/>
Great library! thanks a lot. Does handle shadow comes out of the box? I am not seeing shadow on top of sheet. it would be great if you could provide some example that has shadow and if you have then apologies.
Library | Version |
---|---|
@gorhom/bottom-sheet | ^1.4.1 |
react-native | 0.62.2 |
react-native-reanimated | ^1.8.0 |
react-native-gesture-handler | ^1.6.1 |
Describe what you expected to happen:
Hey, I've been loving this library, great work! I've been missing an option to disable gestures on the bottomsheet so the user wouldn't be able to swipe it away
In case you want to put something important in the bottomsheet, and want the user to not accidentally close it by swipining/scrolling around
I have some state updates I need to do in my component when I want my bottom sheet to be opened, however when I do this my call to bottomSheetRef.current?.snapTo(1)
no longer works. If I comment out the state update then it starts working again. The order in which the state update and calling the snapTo method makes no difference either. I have also tried using bottomSheetRef.current?.expand()
and it has the same issue.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.2.0 |
react-native | 0.63.2 |
react-native-reanimated | 1.13.0 |
react-native-gesture-handler | 1.8.0 |
Describe what you expected to happen:
import BottomSheet from '@gorhom/bottom-sheet';
import React, {useMemo, useRef, useState} from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';
const App = () => {
const [counter, setCounter] = useState(0);
const bottomSheetRef = useRef<BottomSheet>(null);
const snapPoints = useMemo(() => [0, '90%'], []);
const openBottomSheet = () => {
// comment out setCounter and the bottom sheet opens as expected
setCounter((prev) => prev + 1);
bottomSheetRef.current?.snapTo(1);
};
return (
<View style={styles.container}>
<Button onPress={openBottomSheet} title="Open bottom sheet" />
<BottomSheet ref={bottomSheetRef} snapPoints={snapPoints}>
<Text>Hello</Text>
</BottomSheet>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
},
});
export default App;
Is it possible to implement something like what reddit does? main screen of stack navigator will be a bottom sheet which will subsequently call other stack screens. Could you please point me to as how we can achieve this?
Library | Version |
---|---|
@gorhom/bottom-sheet | ^1.4.1 |
react-native | 0.62.2 |
react-native-reanimated | ^1.8.0 |
react-native-gesture-handler | ^1.6.1 |
Describe what you expected to happen:
Hi @gorhom ,
as you can see from the images when I open the dialog bottom sheet, that effect happens.
I could see that it happens both with short times and with long times as in the example in the gif.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.63.3 |
react-native-reanimated | 1.13.2 |
react-native-gesture-handler | 1.8.0 |
const bottomSheetLang = useCallback((newValue, id) => {
present(
<View style={{ flex: 1, backgroundColor }}>
<Text>{settings.item.lang}</Text>
<RadioButton.Group
onValueChange={(newValue) => changeButtonLang(newValue)}
value={settings.item.lang}>
<View style={(Layout.row, Layout.rowHCenter)}>
<RadioButton value="en" />
<Text>En</Text>
</View>
<View style={(Layout.row, Layout.rowHCenter)}>
<RadioButton value="fr" />
<Text>Fr</Text>
</View>
<View style={(Layout.row, Layout.rowHCenter)}>
<RadioButton value="it" />
<Text>It</Text>
</View>
</RadioButton.Group>
</View>,
{
snapPoints: ['50%'],
animationDuration: 5000,
overlayComponent: BottomSheetOverlay,
overlayOpacity: 0.5,
dismissOnOverlayPress: true,
//handleComponent: handle,
//backgroundComponent: BlurredBackground,
//onChange: handleChange,
}
);
}, [settings]);
Button / Pressable rendered inside Bottom Sheet are barely clickable on Android device.
It behaves differently than a normal Button/Preesable outside of bottom sheet.
Library | Version |
---|---|
@gorhom/bottom-sheet | 2.0.0-alpha.0 |
react-native | 0.63.2 |
react-native-reanimated | 2.0.0-alpha.6 |
react-native-gesture-handler | 1.7.0 |
Describe what you expected to happen:
<View style={styles.container}>
<BottomSheet
ref={bottomSheetRef}
initialSnapIndex={1}
snapPoints={snapPoints}
onChange={handleSheetChanges}
>
<Pressable onPress={() => console.log('xxxxx')} style={{ backgroundColor: 'cyan', height: 100 }}>
<Text>Press me</Text>
</Pressable>
</BottomSheet>
</View>
App crashes with the example usage from docs
Not to crash
Run a simple example of the library using Reanimated 2
The reason I guess since I had faced it before is that it uses interpolate instead of interpolateNode
since in reanimated 2 it should me imported as interpolateNode
one place I found that is using interpolate
It would be awesome if you add dynamic height feature based on the children height (and still able to define max height of the bottom sheet). Also the snapPoints prop should be relative to the total Bottom Sheet height, not the screen height.
Sometimes we only need to render only a few items inside the bottom sheet. So there's no need to define the bottom sheet height. It should adjust itself according to the children height. And the snapPoints should be relative to the height of the bottom sheet, not the screen height like in the example.
When switching between BottomSheetScrollViews, if the sheet is in an expanded view then it gets stuck and the content won't scroll until you minimize the sheet and then expand it again. I'm using the focus hook from react navigation, and it's still not working.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.2.2 |
react-native | 0.63.2 |
react-native-reanimated | 1.13.0 |
react-native-gesture-handler | 1.8.0 |
Describe what you expected to happen:
In progress
Could this package be released on https://www.npmjs.com?
Linting in my editor isn't handling this type of package well...
onChange index is NaN when snapPoints array is length 1 i.e const snapPoints = useMemo(() => ['80%'], []);
then console.log('handleSheetChange', index);
outputs handleSheetChange NaN
I am trying to implement an overlay behind my bottom sheet which displays when the bottom sheet is open but currently my onChange is only outputting NaN so I have no way of knowing whether the bottom sheet is open or closed.
Library | Version |
---|---|
@gorhom/bottom-sheet | "2.0.0-alpha.0" |
react-native | "0.63.2" |
react-native-reanimated | "2.0.0-alpha.6" |
react-native-gesture-handler | "1.8.0" |
bsRef.current?.snapTo(0)
to open and bsRef.current?.close()
to open and closecurrentPositionIndexRef.current
is NaNDescribe what you expected to happen:
bsRef.current?.snapTo(0)
is called. I would expect index = 0
bsRef.current?.close() is called. I would expect
index = -1`Here is my component. Currently it sits in a private repo which I am not allowed to share but I can recreate the situation in isolation if you are having trouble recreating the bug
import React, {useCallback, useMemo, RefObject} from 'react';
import {TouchableOpacity, StyleSheet, ViewProps} from 'react-native';
import {Box, Text} from '@private-space/ui';
import BottomSheet, {BottomSheetView} from '@gorhom/bottom-sheet';
import Animated, {
Easing,
useAnimatedProps,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated';
interface Props {
bsRef: RefObject<BottomSheet>;
onClickItem: () => void;
}
export const WorkoutBottomSheet = ({
bsRef,
onClickItem,
}: Props) => {
const snapPoints = useMemo(() => ['80%'], []);
const [isOpen, setIsOpen] = React.useState(false);
const handleSheetChange = useCallback((index: number) => {
console.log('handleSheetChange', index);
if (index > 0) {
setIsOpen(true);
}
}, []);
const handleClosePress = useCallback(() => {
setIsOpen(false);
bsRef.current?.close();
}, []);
const overlayStyle = useAnimatedStyle(() => {
const toValue = isOpen ? 0.3 : 0;
return {
opacity: withTiming(toValue, {duration: 100, easing: Easing.linear}),
};
});
const animatedProps = useAnimatedProps<ViewProps>(() => {
return {
pointerEvents: isOpen ? 'auto' : 'none',
};
});
return (
<>
<Animated.View
animatedProps={animatedProps}
style={[
{
...StyleSheet.absoluteFillObject,
backgroundColor: '#000',
},
overlayStyle,
]}
/>
<Box position="absolute" height={20} bottom={0} left={0} right={0}>
<BottomSheet
ref={bsRef}
initialSnapIndex={-1}
snapPoints={snapPoints}
onChange={handleSheetChange}>
<BottomSheetView>
<Box
justifyContent="flex-start"
alignItems="center"
backgroundColor="white"
padding="m"
flex={1}
height={650}>
<Box
alignSelf="stretch"
flexDirection="row"
justifyContent="space-evenly"
marginBottom="l">
<Text variant="textPreset10" color="primary70">
Choose a workout
</Text>
<Box position="absolute" right={0}>
<TouchableOpacity
onPress={() => {
handleClosePress();
setIsOpen(false);
}}>
<Text color="primary100">Close</Text>
</TouchableOpacity>
</Box>
</Box>
<Box alignSelf="stretch">
{/*workoutTypes.map((type, index) => (
<WorkoutTypeItem
key={index}
type={type}
onPress={() => {
onClickItem(type);
handleClosePress();
setIsOpen(false);
}}
checked={type === selectedWorkoutType}
/>
))*/}
</Box>
</Box>
</BottomSheetView>
</BottomSheet>
</Box>
</>
);
};
How to fix this?
This will the library to behave like a Modal View.
We could introduce:
BottomSheetContainerProvider
: this will be responsible of:
BottomSheet
view.BottomSheet
when user trigger the modal.useBottomSheetModal
: this will expose:
present
a method that will mount the bottom sheet with initial close position, then snap to a provided snap point.dismiss
a method that will close the bottom sheet then unmount it.snapTo
a method to snap to a certain point.BottomSheetContainerProvider
# to be add at the root
<BottomSheetContainerProvider>
<App />
</BottomSheetContainerProvider>
#
const { present } = useBottomSheetModal()
...
const handlePresetView = () => {
present({
snapPoints: [100, '50%', '90%'],
children: /** BOTTOM SHEET CONTENT */
})
}
useBottomSheetModal
interface BottomSheetModalContext {
present: (configs: BottomSheetProps) => void
dismiss: () => void
snapTo: (index: number) => void
}
Library | Version |
---|---|
@gorhom/bottom-sheet | ^1.1.0 |
react-native | 0.63.1 |
react-native-reanimated | ^1.12.0 |
react-native-gesture-handler | ^1.6.1 |
<BottomSheet2
// ref={bottomSheetRef}
initialSnapIndex={0}
snapPoints={[300, contentHeight]}
// onChange={handleSheetChanges}
>
<BottomSheetFlatList
data={this.state.dataProvider.length ? this.state.dataProvider : NOT_FOUND}
keyExtractor={item => item.id}
initialNumToRender={10}
windowSize={20}
maxToRenderPerBatch={5}
renderItem={this._rowRenderer}
// {...(header && {
// stickyHeaderIndices: [0],
// ListHeaderComponent: header,
// })}
contentContainerStyle={styles.listContainer}
// focusHook={useFocusEffect}
/>
</BottomSheet2>
in FlatList.tsx
change disableIntervalMomentum={disableIntervalMomentum}
to disableIntervalMomentum={false}
and it working
i try fix disableIntervalMomentum: Animated.Node<number>;
to disableIntervalMomentum: Animated.Node<boolean>;
but it not working
When using BottomSheetFlatList with an empty list you get the following error:
scrollToIndex out of range: requested index 0 but maximum is -1
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.1.1 |
react-native | 0.63.2 |
react-native-reanimated | 1.12.0 |
react-native-gesture-handler | 1.7.0 |
import { AppRegistry, LogBox } from 'react-native';
import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet';
const App = function App() {
return (
<BottomSheet snapPoints={[100, 600]}>
<BottomSheetFlatList
contentContainerStyle={{ flexGrow: 1, backgroundColor: '#fff' }}
data={[]}
renderItem={() => null}
/>
</BottomSheet>
);
};
AppRegistry.registerComponent('App', () => App);
Provide a default backdrop/background component with a translucent background color.
To distinguish that the bottom sheet is visible, or it pops out when the UI behind is mostly color white.
backgroundComponent
should have a default implementation, I'm thinking it is also useful to export that component as BottomSheetBackdrop
so that users can provide custom styling.
noBackdrop
prop will also help to disable the default backdrop component.
import BottomSheet, {BottomSheetBackdrop} from '@gorhom/react-native-bottom-sheet`;
const MyBackdrop = () => <BottomSheetBackdrop style={{
backgroundColor: 'red',
}}/>
<BottomSheet />
<BottomSheet noBackdrop/>
<BottomSheet backgroundComponent={MyBackdrop}/>
Is having a bottom sheet completely hidden/off the screen against design guidelines, or simply missed?
Library | Version |
---|---|
@gorhom/bottom-sheet | 2.0.0-alpha.0 |
react-native | 0.63.3 |
react-native-reanimated | 2.0.0-alpha.7 |
react-native-gesture-handler | 1.8.0 |
['0%', '80%']
)Describe what you expected to happen:
<BottomSheet
ref={bsRef}
initialSnapIndex={-1}
onChange={onBottomSheetChanged}
animatedPositionIndex={position}
snapPoints={['0%', '80%']}>
...
</BottomSheet>
I'm wondering if there is a way to close the sheet (basic) when pressing outside of it? Is there an event handler that I could utilize?
Thanks for your great work, by the way!
These are the planned improvements and features for v2.
BottomSheetModal
into a separate repository and package, or refactor repository to become a monorepo.animateOnMount
prop to snap to the initial snap point index when the sheet is mounted. (#78)enableContentPanningGesture
prop to allow panning sheet by its content view or scrollables. #55BottomSheetModal
to make it declarative component, using @gorhom/portal
.yarn add @gorhom/bottom-sheet
also check out the new documents website ( still in progress ) ๐
These are the planned improvements and features for v3.
BottomSheetModal
๐ฅanimateOnMount
prop to snap to the initial snap point index when the sheet is mounted.onAnimate
callbackenablePanningGesture
prop to enable panning gesture of the sheet.enableContentPanningGesture
prop to allow panning sheet by its content view or scrollables.yarn add @gorhom/bottom-sheet@3.0.0
also check out the new documents website ๐
Hi @gorhom , i am having the following problem, i am using present by useBottomSheetModal, as you can see from the image through the buttons i should change the theme.
The status is not updated internally, only when you reopen the BottomSheetModal the status is updated.
How can I solve?
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.63.3 |
react-native-reanimated | 1.13.2 |
react-native-gesture-handler | 1.8.0 |
import React, {
useState,
useEffect,
useCallback,
useMemo,
useRef,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { View, StyleSheet, Image, Dimensions } from 'react-native';
import { Text, Button, RadioButton } from 'react-native-paper';
import {
Common,
Fonts,
Gutters,
Layout,
Images,
Colors,
ColorsDarkMode,
} from '@/Theme';
import { useTranslation } from 'react-i18next';
import Settings from '@/Store/Settings/Init';
const { width: SCREEN_WIDTH } = Dimensions.get('screen');
import AppIntroSlider from 'react-native-app-intro-slider';
import {
BottomSheetModalProvider,
useBottomSheetModal,
BottomSheetOverlay,
} from '@gorhom/bottom-sheet';
import withModalProvider from './withModalProvider';
import BlurredBackground from './BlurredBackground';
const styles = StyleSheet.create({
slide: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
image: {
width: 320,
height: 320,
marginVertical: 32,
resizeMode: 'stretch',
},
text: {
color: 'rgba(255, 255, 255, 0.8)',
textAlign: 'center',
},
title: {
fontSize: 22,
color: 'white',
textAlign: 'center',
},
blurView: {
...StyleSheet.absoluteFillObject,
},
container: {
...StyleSheet.absoluteFillObject,
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
overflow: 'hidden',
},
androidContainer: {
backgroundColor: 'rgba(255,255,255, 0.95)',
},
indicator: {
alignSelf: 'center',
width: (8 * SCREEN_WIDTH) / 100,
height: 5,
borderRadius: 4,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
});
const IndexInstallationContainer = (props) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const settings = useSelector((state) => state.settings);
const { present } = useBottomSheetModal();
const handlePresentPress = useCallback(() => {
present(
<View style={{ flex: 1, backgroundColor }}>
<Text>{settings.item.colorScheme}</Text>
<RadioButton.Group
onValueChange={(newValue) => changeButtonTheme(newValue)}
value={settings.item.colorScheme}>
<View style={(Layout.row, Layout.rowHCenter)}>
<RadioButton value="light" />
<Text>Light</Text>
</View>
<View style={(Layout.row, Layout.rowHCenter)}>
<RadioButton value="dark" />
<Text>Dark</Text>
</View>
</RadioButton.Group>
</View>,
{
snapPoints: ['20%'],
animationDuration: 300,
overlayComponent: BottomSheetOverlay,
overlayOpacity: 0.5,
dismissOnOverlayPress: true,
handleComponent: handle,
//backgroundComponent: BlurredBackground,
//onChange: handleChange,
}
);
}, [present, dispatch, settings]);
const slides = [
{
key: 'one',
title: t('welcome'),
text: '',
image: Images.logo,
backgroundColor: '#2196f3',
},
{
key: 'two',
title: t('change.language'),
text: '',
backgroundColor: '#ffc107',
},
{
key: 'three',
title: t('change.mode'),
text: '',
backgroundColor: '#4caf50',
},
];
const changeTheme = () => {
dispatch(
Settings.action({
colorScheme: settings.item.colorScheme === 'dark' ? 'ligth' : 'dark',
})
);
};
const changeButtonTheme = (colorScheme) => {
dispatch(Settings.action({ colorScheme }));
};
const openBottom = () => setState((prev) => ({ ...prev, open: !prev.open }));
const b = (
<Button
style={[Gutters.largeHMargin, Gutters.largeBMargin]}
raised
mode="contained"
onPress={handlePresentPress}>
{t('actions.change')}
</Button>
);
const _renderItem = ({ item }) => {
return (
<View style={[styles.slide, { backgroundColor: item.backgroundColor }]}>
{item.image && <Image source={item.image} style={styles.image} />}
<Text style={styles.title}>{item.title}</Text>
{item.key === 'three' && b}
<Text style={styles.text}>{item.text}</Text>
</View>
);
};
const _onDone = () => {
//console.log(props)
props.navigation.navigate('Login');
};
const [state, setState] = useState({
open: false,
});
const { open } = state;
const backgroundColor =
settings.item.colorScheme === 'dark'
? ColorsDarkMode.backgroundPrimary
: Colors.backgroundPrimary;
const handle = () => {
return (
<View
style={{
paddingHorizontal: 16,
paddingVertical: 5,
backgroundColor,
}}>
<View
style={[
styles.indicator,
{
backgroundColor:
settings.item.colorScheme === 'light'
? 'rgba(0, 0, 0, 0.25)'
: 'rgba(255, 255, 255, 0.25)',
},
]}
/>
</View>
);
};
return (
<>
<AppIntroSlider
renderItem={_renderItem}
data={slides}
onDone={_onDone}
nextLabel={t('next')}
doneLabel={t('done')}
/>
</>
);
};
export default withModalProvider(IndexInstallationContainer);
Apologies for the rapid bug reports, loving the library!
When using a BottomSheetFlatList sandwiched between two views, the two view becomes unresponsive to gestures after the BottomSheetFlatList has been scrolled down and then up.
I've attached a gif to demonstrate, it shows 3 scenarios:
As you can see, anything outside of the BottomSheetFlatList becomes unresponsive to gestures.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.1.1 |
react-native | 0.63.2 |
react-native-reanimated | 1.12.0 |
react-native-gesture-handler | 1.7.0 |
import { AppRegistry, LogBox } from 'react-native';
import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet';
const App = function App() {
return (
<BottomSheet snapPoints={[100, 600]} handleComponent={() => null}>
<View style={{ height: 100, backgroundColor: 'blue' }} />
<BottomSheetFlatList
contentContainerStyle={{ flexGrow: 1, backgroundColor: '#fff' }}
data={[0, 1, 2, 3, 4, 5, 6, 7, 8]}
renderItem={() => (
<View style={{ backgroundColor: 'red', height: 100, marginBottom: 20 }} />
)}
/>
<View style={{ height: 100, backgroundColor: 'green' }} />
</BottomSheet>
);
};
AppRegistry.registerComponent('App', () => App);
There is an issue when using the focusHook
prop on a BottomSheetFlatList. Opening the bottom sheet works fine and the list is scrollable, however when the BottomSheet component re-renders it locks the scrolling of the list.
Some investigation reveals that the re-rendering of BottomSheet
causes ContentWrapper
to also re-render, re-setting maxDeltaY
.
I can see that without focusHook
, the function refreshUIElements
is usually called which sets the maxDeltaY
to 0 and enables scrolling. For some reason this is not happening with focusHook.
Both iOS and Android
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.63.3 |
react-native-reanimated | 2.0.0-alpha.7 |
react-native-gesture-handler | 1.8.0 |
import React from 'react';
import BottomSheet, { BottomSheetFlatList, TouchableOpacity } from '@gorhom/bottom-sheet';
import { useFocusEffect, NavigationContainer } from '@react-navigation/native';
import { View, AppRegistry, LogBox } from 'react-native';
import { useState } from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
function Test() {
const [, reRender] = useState(-1);
return (
<SafeAreaProvider>
<BottomSheet snapPoints={[100, 600]}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="List">
{(props) => (
<BottomSheetFlatList
focusHook={useFocusEffect}
data={[...new Array(20)]}
keyExtractor={(_, i) => i.toString()}
renderItem={({ index }) => (
<TouchableOpacity onPress={() => reRender(index)}>
<View
style={{
backgroundColor: 'red',
height: 50,
marginTop: 10
}}
/>
</TouchableOpacity>
)}
/>
)}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
</BottomSheet>
</SafeAreaProvider>
);
}
AppRegistry.registerComponent('Test', () => Test);
Thanks!
To utilise the power of Reanimated v2, this library will need to be partially re-written.
useTransition.ts
measure
& scrollTo
Wondering if there is a way to close the keyboard in sync with the BottomSheet being dismissed via drag. I have it working nicely when you press the close button:
const handleDismissPress = useCallback(() => {
Keyboard.dismiss()
dismiss()
}, [dismiss])
Seems janky to watch the BottomSheet dismiss fully and then watch the keyboard dismiss.
I poked around for a couple min to see if I could do this easily on a ListView in the lib. Should be possible.
Getting the following error when trying to use this package:
Tried to synchronously call function {assign} from a diffrent thread.
Solution is:
a) if you want to synchronously execute this method, mark it as a worklet
b) if you want to execute this method on the JS thread, wrap it using runOnJS
I've tracked the error message back to node_modules/react-native-reanimated/Common/cpp/SharedItems/ShareableValue.cpp
line 15. And there is a thread on the issue here: software-mansion/react-native-reanimated#1347
Library | Version |
---|---|
@gorhom/bottom-sheet | "2.0.0-alpha.0" |
react-native | "0.63.3" |
react-native-reanimated | "^2.0.0-alpha.8" |
react-native-gesture-handler | "^1.8.0" |
"@gorhom/bottom-sheet": "2.0.0-alpha.0"
, "react-native-reanimated": "^2.0.0-alpha.8"
(with all changes needed for the library) and "react-native-gesture-handler": "^1.8.0"`.Describe what you expected to happen:
Library | Version |
---|---|
@gorhom/bottom-sheet | ^1.4.1 |
react-native | "https://github.com/expo/react-native/archive/sdk-39.0.0.tar.gz" |
react-native-gesture-handler | ~1.7.0 |
1.scrolling up the bottomSheet
2.scroll horizontally images
3. scroll down 50% the bottomSheet
4.scroll horizontally images -> cannot.
Describe what you expected to happen:
1.i would like to be able to scroll the images horizontally also when the bottomSheet is not on the top level.
import { BottomSheetFlatList } from '@gorhom/bottom-sheet'
import React from 'react'
import {View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native'
import { ImagesSliderProps } from './ImagesSlider.d'
const ImagesSlider: React.FC<ImagesSliderProps> = ({title, images}) => {
const renderImage = (itemData:any) => {
return (
<TouchableOpacity activeOpacity={0.8} onPress={()=> console.log(itemData.item)}>
<Image style={styles.image} source={{uri:itemData.item.key}}/>
</TouchableOpacity>
)
}
return (
<View style={{flex:1}}>
<Text>{title}</Text>
<BottomSheetFlatList
showsHorizontalScrollIndicator={true}
initialNumToRender={2}
keyExtractor={(item, index) => index.toString()}
horizontal={true}
data={images}
renderItem={renderImage}/>
</View>
)
}
const styles = StyleSheet.create({
image: {
marginHorizontal: 5,
width:200,
height:150,
resizeMode: 'stretch'
}
})
export default ImagesSlider;
Hey, love the project. You up for adding some tests? I could start that for you.
jest
+ native-testing-library
+ react-hooks-testing-library
?
100%
Hi @gorhom ,
I am using your component on different devices, I tried to put snapPoints = "90%".
I noticed that it differs a lot between the two, as can be seen from the image.
I tried with higher values (100%), it seems that on android it works fine but on ios it gives problems, it goes further.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.63.3 |
react-native-reanimated | 1.13.2 |
react-native-gesture-handler | 1.8.0 |
{
snapPoints: ['90%'],
animationDuration: 10,
overlayComponent: BottomSheetOverlay,
overlayOpacity: 0.5,
dismissOnOverlayPress: true,
}
I used react-native-raw-bottom-sheet
before and it has a prop dragFromTopOnly
in which the bottom-sheet can only be close (when being drag) by the handle not by the entire bottom-sheet itself. I like this library compare to that because of performance. I hope you can add this, thanks
Hi, I don't seem to be able to pull on/move the sheet on Android at all, even with a very minimal example.
On iOS, it works just fine (excuse the crude example):
But on Android, it doesn't respond to interaction at all:
I've tried this both in the simulator and on-device, with the same results.
Library | Version |
---|---|
@gorhom/bottom-sheet | 1.4.1 |
react-native | 0.63.3 |
react-native-reanimated | 1.13.1 |
react-native-gesture-handler | 1.8.0 |
Describe what you expected to happen:
import React from 'react'
import { View, Text } from 'react-native'
import BottomSheet from '@gorhom/bottom-sheet'
const MinimalExample = () => (
<View style={{ flex: 1, backgroundColor: 'grey' }}>
<BottomSheet snapPoints={[50, 200]}>
<Text>Hello, world!</Text>
</BottomSheet>
</View>
)
export default MinimalExample
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.