Bottom Sheet
Bottom sheets are surfaces containing supplementary content, anchored to the bottom of the screen. They can provide users with quick access to contextual information, actions, or settings without interrupting their current workflow.
The Bottom Sheet Package is part of FlexNative for React Native, and is available under the @flexnative/bottom-sheet
NPM package.
A performant interactive bottom sheet with fully configurable options.
Dependencies
Installation
You can installing FlexNative Bottom Sheet packages using npm:
npm i @flexnative/bottom-sheet
API
import BottomSheet from "@flexnative/bottom-sheet";
Component
BottomSheet
Type: React.PureComponent<BottomSheetProps, BottomSheetStateProps>
Properties
Name | Type | Required | Default Value | Description |
---|---|---|---|---|
animationType | 'none' | 'slide' | 'fade' | false | fade | Animation type when BottomSheet it is open. |
height | number | false | 260 | BottomSheet height. |
minClosingHeight | number | false | 0 | Min height value to close the BottomSheet when drag down. minClosingHeight has effect only when closeOnDragDown it is true |
openDuration | number | false | 300 | Animation duration on open ButtonSheet. |
closeDuration | number | false | 300 | Animation duration on close ButtonSheet. |
closeOnDragDown | boolean | false | false | Boolean value indicating whether to close or not the BottomSheet on OnDragDown. |
closeOnPressMask | boolean | false | true | Boolean value indicating whether to close or not the BottomSheet on OnPressMask. |
dragFromTopOnly | boolean | false | false | Boolean value indicating whether to Drag FromTopOnly. |
closeOnPressBack | boolean | false | false | Boolean value indicating whether to close or not the BottomSheet on background overlay. |
maskMode | BlurTint | false | isDark ? dark : light | A tint mode which will be applied to background overlay. |
behavior | height , padding , position | false | padding | behavior for KeyboardAvoidingView. |
keyboardAvoidingViewEnabled | boolean | false | Default is true of iOS | Enables or disables the KeyboardAvoidingView. |
onClose | function of type void | false | undefined | function to call on close event. |
onOpen | function of type void | false | undefined | function to call on onOpen event. |
children | React.ReactNode | true | undefined | React.ReactNode to render on BottomSheet. |
wrapperStyle | StyleProp<ViewStyle> | false | undefined | ViewStyle to apply on BottomSheet wrapper. |
containerStyle | StyleProp<ViewStyle> | false | undefined | ViewStyle to apply on BottomSheet container. |
draggableIconStyle | StyleProp<ViewStyle> | false | undefined | ViewStyle to apply on draggable icon. |
Usecase Examples
The following example demonstrates the BottomSheet in action.
BottomSheet With action onOpen & onClose
- Preview
- Code
import React from "react";
import { Alert, Platform } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import MockContainer from "./MockContainer";
import { OPEN_TIME_OUT } from "./constants";
export default class extends React.PureComponent<{}, {}> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
private open() {
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
private showAlert(title: string, content: string) {
if (Platform.OS === 'web') {
alert(content)
} else {
Alert.alert(title, content)
}
}
public render() {
return (
<div className='example-block'>
<Button color='primary' text='With actions' onPress={this.open}/>
<BottomSheet ref={this.refBottomSheet}
onOpen={() => this.showAlert('Modal Open', 'Modal is opened.')}
onClose={() => this.showAlert('Modal Closed', 'Modal is closed.')} >
<MockContainer close={this.close} />
</BottomSheet>
</div>
);
}
}
Animation Type
- Preview
- Code
import React from "react";
import { StyleSheet } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import { OPEN_TIME_OUT } from "./constants";
import MockContainer from "./MockContainer";
type State = {
animationType: 'none' | 'fade' | 'slide'
}
export default class extends React.PureComponent<{}, State> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.state = {
animationType: 'fade'
};
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.animationTypeFade = this.animationTypeFade.bind(this);
this.animationTypeNone = this.animationTypeNone.bind(this);
this.animationTypeSlide = this.animationTypeSlide.bind(this);
}
private animationTypeNone() {
this.setState({animationType: 'none'})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private animationTypeFade() {
this.setState({animationType: 'fade'})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private animationTypeSlide() {
this.setState({animationType: 'slide'})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
public render() {
return (
<div className='example-block'>
<Button style={styles.button} color='primary' text='none (default)' onPress={this.animationTypeNone}/>
<Button style={styles.button} text='slide' color='primary' onPress={this.animationTypeSlide}/>
<Button style={styles.button} text='fade' color='primary' onPress={this.animationTypeFade}/>
<BottomSheet ref={this.refBottomSheet} animationType={this.state.animationType!} >
<MockContainer close={this.close} />
</BottomSheet>
</div>
);
}
}
const styles = StyleSheet.create({
button: {
flex: 1
},
});
Behavior
- Preview
- Code
import React from "react";
import { StyleSheet, TextInput } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import MockContainer from "./MockContainer";
import { OPEN_TIME_OUT } from "./constants";
type State = {
behavior?: "height" | "padding" | "position";
}
export default class extends React.PureComponent<{}, State> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.state = {
behavior: 'padding'
};
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
private open(behavior: "height" | "padding" | "position") {
this.setState({behavior})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
public render() {
return (
<div className='example-block'>
<Button style={styles.button} text='padding (default)' color='primary' onPress={() => this.open('padding')}/>
<Button style={styles.button} text='height' color='primary' onPress={() => this.open('height')}/>
<Button style={styles.button} text='position' color='primary' onPress={() => this.open('position')}/>
<BottomSheet ref={this.refBottomSheet} keyboardAvoidingViewEnabled={true} behavior={this.state.behavior!} >
<MockContainer close={this.close}>
<TextInput style={styles.input} />
</MockContainer>
</BottomSheet>
</div>
);
}
}
const styles = StyleSheet.create({
button: {
flex: 1
},
input: {
height: 40,
margin: 12,
borderWidth: 1,
padding: 10,
},
});
CloseOnDragDown
- Preview
- Code
import React from "react";
import { StyleSheet } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import MockContainer from "./MockContainer";
import { OPEN_TIME_OUT } from "./constants";
type State = {
closeOnDragDown: boolean;
}
export default class extends React.PureComponent<{}, State> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.state = {
closeOnDragDown: false
};
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
private open(closeOnDragDown: boolean) {
this.setState({closeOnDragDown})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
public render() {
return (
<div className='example-block'>
<Button style={styles.button} text='false (default)' color='primary' onPress={() => this.open(false)}/>
<Button style={styles.button} text='true' color='primary' onPress={() => this.open(true)}/>
<BottomSheet ref={this.refBottomSheet} closeOnDragDown={this.state.closeOnDragDown!} >
<MockContainer close={this.close} />
</BottomSheet>
</div>
);
}
}
const styles = StyleSheet.create({
button: {
flex: 1
},
});
Height
- Preview
- Code
import React from "react";
import { StyleSheet } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import MockContainer from "./MockContainer";
import { OPEN_TIME_OUT } from "./constants";
type State = {
height: number;
}
export default class extends React.PureComponent<{}, State> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.state = {
height: 260
};
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.setHeight = this.setHeight.bind(this);
}
private setHeight(height: number) {
this.setState({height})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
public render() {
return (
<div className='example-block'>
<Button style={styles.button} text='260 (default)' color='primary' onPress={() => this.setHeight(260)}/>
<Button style={styles.button} text='350' color='primary' onPress={() => this.setHeight(350)}/>
<Button style={styles.button} text='650' color='primary' onPress={() => this.setHeight(650)}/>
<BottomSheet ref={this.refBottomSheet} closeOnDragDown={true} height={this.state.height!} >
<MockContainer close={this.close} />
</BottomSheet>
</div>
);
}
}
const styles = StyleSheet.create({
button: {
flex: 1
},
});
MaskMode
- Preview
- Code
import React from "react";
import { BlurTint } from "expo-blur";
import { StyleSheet } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import MockContainer from "./MockContainer";
import { OPEN_TIME_OUT } from "./constants";
type State = {
maskMode?: BlurTint;
}
export default class extends React.PureComponent<{}, State> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.state = {
maskMode: 'light'
};
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
private open(maskMode: BlurTint) {
this.setState({maskMode})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
public render() {
return (
<div className='example-block'>
<Button style={styles.button} text='default' color='primary' onPress={() => this.open('default')}/>
<Button style={styles.button} text='light' color='primary' onPress={() => this.open('light')}/>
<Button style={styles.button} text='dark' color='primary' onPress={() => this.open('dark')}/>
<Button style={styles.button} text='regular' color='primary' onPress={() => this.open('regular')}/>
<Button style={styles.button} text='prominent' color='primary' onPress={() => this.open('prominent')}/>
<BottomSheet ref={this.refBottomSheet} maskMode={this.state.maskMode!} >
<MockContainer close={this.close} />
</BottomSheet>
</div>
);
}
}
const styles = StyleSheet.create({
button: {
flex: 1
}
});
MinClosingHeight
- Preview
- Code
import React from "react";
import { StyleSheet } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import MockContainer from "./MockContainer";
import { OPEN_TIME_OUT } from "./constants";
type State = {
minClosingHeight: number;
}
export default class extends React.PureComponent<{}, State> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.state = {
minClosingHeight: 0
};
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
private open(minClosingHeight: number) {
this.setState({minClosingHeight})
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
public render() {
return (
<div className='example-block'>
<Button style={styles.button} text='0 (default)' color='primary' onPress={() => this.open(0)}/>
<Button style={styles.button} text='200' color='primary' onPress={() => this.open(200)}/>
<Button style={styles.button} text='400' color='primary' onPress={() => this.open(400)}/>
<BottomSheet ref={this.refBottomSheet} minClosingHeight={this.state.minClosingHeight!} closeOnDragDown >
<MockContainer close={this.close} />
</BottomSheet>
</div>
);
}
}
const styles = StyleSheet.create({
button: {
flex: 1
},
});
Styles with StyleSheet
- Preview
- Code
import React from "react";
import { StyleSheet } from "react-native";
import Button from '@flexnative/buttons';
import BottomSheet from "@flexnative/bottom-sheet";
import MockContainer from "./MockContainer";
import { OPEN_TIME_OUT } from "./constants";
export default class extends React.PureComponent<{}, {}> {
private refBottomSheet: React.RefObject<BottomSheet>;
constructor(props: {}) {
super(props);
this.refBottomSheet = React.createRef();
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
private open() {
setTimeout(() => {
this.refBottomSheet?.current?.open();
}, OPEN_TIME_OUT);
}
private close() {
this.refBottomSheet?.current?.close();
};
public render() {
return (
<div className='example-block'>
<Button color='primary' text='Open' onPress={this.open}/>
<BottomSheet
ref={this.refBottomSheet}
closeOnDragDown={true}
wrapperStyle={styles.wrapperStyle}
containerStyle={styles.containerStyle}
draggableIconStyle={styles.draggableIconStyle}>
<MockContainer close={this.close} />
</BottomSheet>
</div>
);
}
}
const styles = StyleSheet.create({
wrapperStyle: {
padding: 16,
backgroundColor: '#4361EE',
},
containerStyle: {
backgroundColor: '#3A0CA3',
height: 250,
paddingLeft: 35,
paddingRight: 35,
paddingBottom: 35,
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
},
draggableIconStyle: {
backgroundColor: '#F72585',
height: 15,
width: '50%',
borderRadius: 3,
//@ts-ignore
cursor: 'pointer'
}
});
Playground
Live Editor
type Props = { children?: React.ReactNode; close?: () => void; } const MockContainer = (props: Props) => ( <div style={styles.container}> <p style={styles.text}>Bottom Sheet mock content</p> { props.children ?? props.children } <Button text='Close' color='error' onPress={props.close}/> </div> ); const styles = { container: { backgroundColor: '#f9c2ff', padding: 20, marginVertical: 8, marginHorizontal: 16, borderRadius: 4 }, text: { fontSize: 32, paddingVertical: 15, color: 'black' } }; const BottomSheetExample = () => { const refBottomSheet = React.useRef<BottomSheetModal>(null); const open = () => refBottomSheet.current.open(); const close = () => refBottomSheet?.current?.close(); const showAlert = (title: string, content: string) => console.log(title, '\n', content); console.log('refBottomSheet: ', refBottomSheet) return ( <> <Button color='primary' text='Test BottomSheet' onPress={open}/> {/*Play with BottomSheet props to see live changes;*/} <BottomSheet ref={refBottomSheet} onOpen={() => showAlert('Modal Open', 'Modal is opened.')} onClose={() => showAlert('Modal Closed', 'Modal is closed.')} > <MockContainer close={close} /> </BottomSheet> </> ) } render( <BottomSheetExample /> );
Result
Loading...