react-native-raw-bottom-sheet
Advanced tools
Comparing version 2.2.1 to 3.0.0-rc.1
@@ -1,150 +0,28 @@ | ||
import React from "react"; | ||
import { View, Text, Modal, TouchableOpacity, Animated } from "react-native"; | ||
import Enzyme, { shallow } from "enzyme"; | ||
import Adapter from "enzyme-adapter-react-16"; | ||
import RBSheet from "../src"; | ||
import React from 'react'; | ||
import {render, act} from '@testing-library/react-native'; | ||
import RBSheet from '../src'; | ||
Enzyme.configure({ adapter: new Adapter() }); | ||
describe('<RBSheet />', () => { | ||
let ref; | ||
let getByTestId; | ||
describe("React Native Raw Bottom Sheet", () => { | ||
describe("Render", () => { | ||
it("should render correctly with no props", () => { | ||
const wrapper = shallow(<RBSheet />); | ||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
it("should render correctly with given props", () => { | ||
const wrapper = shallow( | ||
<RBSheet | ||
height={300} | ||
minClosingHeight={100} | ||
openDuration={350} | ||
closeOnSwipeDown={false} | ||
closeOnPressMask={false} | ||
customStyles={{ | ||
wrapper: { | ||
backgroundColor: "#00000066" | ||
}, | ||
container: { | ||
justifyContent: "center", | ||
alignItems: "center" | ||
} | ||
}} | ||
onClose={() => {}} | ||
/> | ||
); | ||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
it("should render correctly with any children", () => { | ||
const wrapper = shallow( | ||
<RBSheet> | ||
<View> | ||
<Text>React Native Raw Bottom Sheet</Text> | ||
</View> | ||
</RBSheet> | ||
); | ||
expect(wrapper).toMatchSnapshot(); | ||
}); | ||
describe("Mask", () => { | ||
it("should render mask", () => { | ||
const wrapper = shallow(<RBSheet />); | ||
expect(wrapper.find(TouchableOpacity).length).toEqual(1); | ||
}); | ||
it("should closeOnPressMask when given prop true", () => { | ||
const wrapper = shallow(<RBSheet closeOnPressMask />); | ||
wrapper.instance().close = jest.fn(); | ||
wrapper.find(TouchableOpacity).simulate("Press"); | ||
expect(wrapper.instance().close).toHaveBeenCalled(); | ||
}); | ||
it("should not closeOnPressMask when given prop false", () => { | ||
const wrapper = shallow(<RBSheet closeOnPressMask={false} />); | ||
wrapper.instance().close = jest.fn(); | ||
wrapper.find(TouchableOpacity).simulate("Press"); | ||
expect(wrapper.instance().close).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
describe("Modal", () => { | ||
it("should render modal", () => { | ||
const wrapper = shallow(<RBSheet />); | ||
expect(wrapper.find(Modal).length).toEqual(1); | ||
}); | ||
}); | ||
describe("DraggableArea", () => { | ||
it("should not render draggable area", () => { | ||
const wrapper = shallow(<RBSheet />); | ||
expect(wrapper.find(View).length).toEqual(1); | ||
}); | ||
it("should render draggable area", () => { | ||
const wrapper = shallow(<RBSheet closeOnDragDown />); | ||
expect(wrapper.find(View).length).toEqual(3); | ||
}); | ||
}); | ||
beforeEach(() => { | ||
ref = React.createRef(); | ||
getByTestId = render(<RBSheet ref={ref} />).getByTestId; | ||
}); | ||
describe("Method", () => { | ||
let wrapper; | ||
let setModalVisible; | ||
beforeEach(() => { | ||
wrapper = shallow(<RBSheet />); | ||
setModalVisible = jest.spyOn(RBSheet.prototype, "setModalVisible"); | ||
Animated.timing = (value, config) => { | ||
return { | ||
start: callback => { | ||
value.setValue(config.toValue); | ||
if (typeof callback === "function") callback(); | ||
} | ||
}; | ||
}; | ||
}); | ||
it('should render correctly', () => { | ||
getByTestId = render(<RBSheet ref={ref} draggable />).getByTestId; | ||
it("should createPanResponder called", () => { | ||
wrapper = shallow(<RBSheet />); | ||
const createPanResponder = jest.spyOn(RBSheet.prototype, "createPanResponder"); | ||
wrapper.instance().createPanResponder({ closeOnSwipeDown: true, height: 300 }); | ||
expect(createPanResponder).toHaveBeenCalledTimes(1); | ||
act(() => { | ||
ref.current.open(); | ||
}); | ||
it("should method open called", () => { | ||
wrapper.instance().open(); | ||
expect(setModalVisible).toHaveBeenCalled(); | ||
expect(wrapper.state().modalVisible).toBe(true); | ||
}); | ||
it("should onOpen callback function called", () => { | ||
const onOpen = jest.fn(); | ||
wrapper = shallow(<RBSheet onOpen={onOpen} />); | ||
wrapper.instance().open(); | ||
expect(onOpen).toHaveBeenCalled(); | ||
}); | ||
it("should method close called", () => { | ||
wrapper.instance().close(); | ||
expect(setModalVisible).toHaveBeenCalled(); | ||
expect(wrapper.state().modalVisible).toBe(false); | ||
}); | ||
it("should onClose callback function called", () => { | ||
const onClose = jest.fn(); | ||
wrapper = shallow(<RBSheet onClose={onClose} />); | ||
wrapper.instance().close(); | ||
expect(onClose).toHaveBeenCalled(); | ||
}); | ||
it("should onRequestClose called", () => { | ||
const mockFn = jest.fn(); | ||
RBSheet.prototype.setModalVisible = mockFn; | ||
wrapper | ||
.find(Modal) | ||
.props() | ||
.onRequestClose(); | ||
expect(mockFn).toHaveBeenCalled(); | ||
}); | ||
expect(getByTestId('Modal')).toBeTruthy(); | ||
expect(getByTestId('KeyboardAvoidingView')).toBeTruthy(); | ||
expect(getByTestId('TouchableOpacity')).toBeTruthy(); | ||
expect(getByTestId('AnimatedView')).toBeTruthy(); | ||
expect(getByTestId('DraggableView')).toBeTruthy(); | ||
expect(getByTestId('DraggableIcon')).toBeTruthy(); | ||
}); | ||
}); |
@@ -1,30 +0,39 @@ | ||
import { Component } from "react"; | ||
import { StyleProp, ViewStyle } from "react-native"; | ||
import React from 'react'; | ||
import { | ||
StyleProp, | ||
ViewStyle, | ||
ModalProps, | ||
KeyboardAvoidingViewProps, | ||
} from 'react-native'; | ||
declare module "react-native-raw-bottom-sheet" { | ||
export type RBSheetProps = { | ||
animationType?: "none" | "fade" | "slide"; | ||
height?: number; | ||
minClosingHeight?: number; | ||
openDuration?: number; | ||
closeDuration?: number; | ||
closeOnDragDown?: boolean; | ||
dragFromTopOnly?: boolean; | ||
closeOnPressMask?: boolean; | ||
closeOnPressBack?: boolean; | ||
onClose?: (params?: any) => void; | ||
onOpen?: (params?: any) => void; | ||
customStyles?: { | ||
wrapper?: StyleProp<ViewStyle>; | ||
container?: StyleProp<ViewStyle>; | ||
draggableIcon?: StyleProp<ViewStyle>; | ||
}; | ||
keyboardAvoidingViewEnabled?: boolean; | ||
children?: React.ReactNode; | ||
interface RBSheetProps { | ||
height?: number; | ||
openDuration?: number; | ||
closeDuration?: number; | ||
closeOnPressMask?: boolean; | ||
closeOnPressBack?: boolean; | ||
draggable?: boolean; | ||
dragOnContent?: boolean; | ||
useNativeDriver?: boolean; | ||
customStyles?: { | ||
wrapper?: StyleProp<ViewStyle>; | ||
container?: StyleProp<ViewStyle>; | ||
draggableIcon?: StyleProp<ViewStyle>; | ||
}; | ||
customModalProps?: ModalProps; | ||
customAvoidingViewProps?: KeyboardAvoidingViewProps; | ||
onOpen?: () => void; | ||
onClose?: () => void; | ||
children?: React.ReactNode; | ||
} | ||
export default class RBSheet extends Component<RBSheetProps> { | ||
open(params?: any): void; | ||
close(params?: any): void; | ||
} | ||
interface RBSheetRef { | ||
open: () => void; | ||
close: () => void; | ||
} | ||
declare const RBSheet: React.ForwardRefExoticComponent< | ||
RBSheetProps & React.RefAttributes<RBSheetRef> | ||
>; | ||
export default RBSheet; |
@@ -1,3 +0,3 @@ | ||
import RBSheet from "./src"; | ||
import RBSheet from './src'; | ||
export default RBSheet; |
{ | ||
"name": "react-native-raw-bottom-sheet", | ||
"version": "2.2.1", | ||
"version": "3.0.0-rc.1", | ||
"description": "Add Your Own Component To Bottom Sheet Whatever You Want (Android & iOS)", | ||
@@ -8,3 +8,4 @@ "main": "index.js", | ||
"scripts": { | ||
"test": "jest --coverage" | ||
"lint": "eslint .", | ||
"test": "jest" | ||
}, | ||
@@ -34,25 +35,20 @@ "repository": { | ||
"devDependencies": { | ||
"enzyme": "^3.9.0", | ||
"enzyme-adapter-react-16": "^1.10.0", | ||
"eslint": "^5.14.1", | ||
"eslint-config-airbnb": "^17.1.0", | ||
"eslint-config-prettier": "^4.0.0", | ||
"eslint-plugin-import": "^2.16.0", | ||
"eslint-plugin-jsx-a11y": "^6.2.1", | ||
"eslint-plugin-prettier": "^3.0.1", | ||
"eslint-plugin-react": "^7.12.4", | ||
"jest": "^24.9.0", | ||
"metro-react-native-babel-preset": "^0.52.0", | ||
"prettier": "^1.16.4", | ||
"prop-types": "^15.7.2", | ||
"react": "^16.8.6", | ||
"react-dom": "^16.8.3", | ||
"react-native": "^0.62.2" | ||
"@babel/core": "^7.24.0", | ||
"@babel/preset-env": "^7.24.0", | ||
"@react-native/eslint-config": "0.73.2", | ||
"@react-native/typescript-config": "0.73.1", | ||
"@testing-library/react-native": "^12.4.3", | ||
"babel-jest": "^29.7.0", | ||
"eslint": "8.19.0", | ||
"jest": "29.6.3", | ||
"metro-react-native-babel-preset": "^0.77.0", | ||
"prettier": "2.8.8", | ||
"react": "18.2.0", | ||
"react-native": "0.73.2", | ||
"react-test-renderer": "^18.2.0", | ||
"typescript": "5.0.4" | ||
}, | ||
"jest": { | ||
"preset": "react-native", | ||
"transform": { | ||
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js" | ||
} | ||
"preset": "react-native" | ||
} | ||
} |
200
README.md
# react-native-raw-bottom-sheet | ||
> ## Motivation | ||
> | ||
> Thank you for using the `react-native-raw-bottom-sheet` library. | ||
> | ||
> This library has been published for over 5 years and I've noticed that new libraries are being published frequently, and I hope that those newer libraries will replace this small library with time, that's why I have stopped maintaining this project for the past few years. | ||
> | ||
> However, I was pleasantly surprised to see that the number of installations has remained high over the past year. Therefore, I have decided to continue to maintain this project. | ||
> | ||
> I will ensure that this project remains simple and lightweight, without requiring any configuration or external dependencies. | ||
> | ||
> I would also like to express my gratitude to all the contributors who have made pull requests. Thank you! | ||
## Hooray! The new version 3 has been released. | ||
Please pay close attention if you are upgrading the RBSheet from version 2 to version 3. | ||
- Functional Components: Starting from v3.0.0, RBSheet has been completely rewritten using Functional Components. This improves performance and aligns with modern React practices. | ||
- Prop Removal & Renaming: Several props have been removed and renamed for improved clarity and maintainability. Please refer to the updated documentation for a complete list of available props and their intended behavior. | ||
<hr> | ||
[![npm version](https://badge.fury.io/js/react-native-raw-bottom-sheet.svg)](//npmjs.com/package/react-native-raw-bottom-sheet) | ||
[![npm downloads](https://img.shields.io/npm/dm/react-native-raw-bottom-sheet.svg) | ||
](//npmjs.com/package/react-native-raw-bottom-sheet) | ||
[![Build Status](https://travis-ci.org/nysamnang/react-native-raw-bottom-sheet.svg?branch=master)](https://travis-ci.org/nysamnang/react-native-raw-bottom-sheet) | ||
[![codecov](https://codecov.io/gh/nysamnang/react-native-raw-bottom-sheet/graph/badge.svg?token=tJuJsd1V8e)](https://codecov.io/gh/nysamnang/react-native-raw-bottom-sheet) | ||
- Super Lightweight Component | ||
- Add Your own Component To Bottom Sheet | ||
- Add Your Own Component To Bottom Sheet | ||
- Customize Whatever You Like | ||
@@ -29,3 +26,3 @@ - Support Drag Down Gesture | ||
- Zero dependency | ||
- Top Search Ranking (react native bottom sheet) at [npms.io](https://npms.io/search?q=react%20native%20bottom%20sheet) | ||
- Millions of Downloads | ||
@@ -50,67 +47,38 @@ | Showcase iOS | Showcase Android | | ||
#### Class component | ||
Please check the [example](https://github.com/nysamnang/react-native-raw-bottom-sheet/tree/master/example) folder to explore more example codes. | ||
```jsx | ||
import React, { Component } from "react"; | ||
import { View, Button } from "react-native"; | ||
import RBSheet from "react-native-raw-bottom-sheet"; | ||
#### Single Bottom Sheet | ||
export default class Example extends Component { | ||
render() { | ||
return ( | ||
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> | ||
<Button title="OPEN BOTTOM SHEET" onPress={() => this.RBSheet.open()} /> | ||
<RBSheet | ||
ref={ref => { | ||
this.RBSheet = ref; | ||
}} | ||
height={300} | ||
openDuration={250} | ||
customStyles={{ | ||
container: { | ||
justifyContent: "center", | ||
alignItems: "center" | ||
} | ||
}} | ||
> | ||
<YourOwnComponent /> | ||
</RBSheet> | ||
</View> | ||
); | ||
} | ||
} | ||
``` | ||
#### Functional component | ||
```jsx | ||
import React, { useRef } from "react"; | ||
import { View, Button } from "react-native"; | ||
import RBSheet from "react-native-raw-bottom-sheet"; | ||
import React, {useRef} from 'react'; | ||
import {View, Button} from 'react-native'; | ||
import RBSheet from 'react-native-raw-bottom-sheet'; | ||
export default function Example() { | ||
const refRBSheet = useRef(); | ||
return ( | ||
<View | ||
style={{ | ||
flex: 1, | ||
justifyContent: "center", | ||
alignItems: "center", | ||
backgroundColor: "#000" | ||
}} | ||
> | ||
<Button title="OPEN BOTTOM SHEET" onPress={() => refRBSheet.current.open()} /> | ||
<View style={{flex: 1}}> | ||
<Button | ||
title="OPEN BOTTOM SHEET" | ||
onPress={() => refRBSheet.current.open()} | ||
/> | ||
<RBSheet | ||
ref={refRBSheet} | ||
closeOnDragDown={true} | ||
closeOnPressMask={false} | ||
useNativeDriver={true} | ||
customStyles={{ | ||
wrapper: { | ||
backgroundColor: "transparent" | ||
backgroundColor: 'transparent', | ||
}, | ||
draggableIcon: { | ||
backgroundColor: "#000" | ||
} | ||
backgroundColor: '#000', | ||
}, | ||
}} | ||
> | ||
customModalProps={{ | ||
animationType: 'slide', | ||
statusBarTranslucent: true, | ||
}} | ||
customAvoidingViewProps={{ | ||
enabled: false, | ||
}}> | ||
<YourOwnComponent /> | ||
@@ -123,17 +91,24 @@ </RBSheet> | ||
#### Dynamic Bottom Sheet | ||
#### Multiple Bottom Sheet | ||
```jsx | ||
renderItem = (item, index) => ( | ||
<View> | ||
<Button title={`OPEN BOTTOM SHEET-${index}`} onPress={() => this[RBSheet + index].open()} /> | ||
<RBSheet | ||
ref={ref => { | ||
this[RBSheet + index] = ref; | ||
}} | ||
> | ||
<YourOwnComponent onPress={() => this[RBSheet + index].close()} /> | ||
</RBSheet> | ||
</View> | ||
); | ||
const refRBSheet = useRef([]); | ||
const renderItem = ({item, index}) => { | ||
return ( | ||
<View> | ||
<TouchableOpacity | ||
style={styles.button} | ||
onPress={() => refRBSheet.current[index].open()}> | ||
<Text style={styles.buttonText}>ITEM {item + 1}</Text> | ||
</TouchableOpacity> | ||
<RBSheet ref={ref => (refRBSheet.current[index] = ref)}> | ||
<View style={styles.bottomSheetContainer}> | ||
<Text style={styles.bottomSheetText}>I AM ITEM {item + 1}</Text> | ||
</View> | ||
</RBSheet> | ||
</View> | ||
); | ||
}; | ||
``` | ||
@@ -143,27 +118,25 @@ | ||
| Props | Type | Description | Default | | ||
| ---------------------------| -------- | ------------------------------------------------------- | -------- | | ||
| animationType | string | Background animation ("none", "fade", "slide") | "none" | | ||
| height | number | Height of Bottom Sheet | 260 | | ||
| minClosingHeight | number | Minimum height of Bottom Sheet before close | 0 | | ||
| openDuration | number | Open Bottom Sheet animation duration | 300 (ms) | | ||
| closeDuration | number | Close Bottom Sheet animation duration | 200 (ms) | | ||
| closeOnDragDown | boolean | Use gesture drag down to close Bottom Sheet | false | | ||
| closeOnTouchablesDragDown | boolean | Use gesture drag down on touchable components to close Bottom Sheet<br/> (Doesn't work for touchable components inside a scrollView) <br/> (closeOnDragDown must be enabled for this to work) | false | | ||
| dragFromTopOnly | boolean | Drag only the top area of the draggableIcon to close Bottom Sheet instead of the whole content | false | | ||
| closeOnPressMask | boolean | Press the area outside to close Bottom Sheet | true | | ||
| closeOnPressBack | boolean | Press back android to close Bottom Sheet (Android only) | true | | ||
| onClose | function | Callback function when Bottom Sheet has closed | null | | ||
| onOpen | function | Callback function when Bottom Sheet has opened | null | | ||
| customStyles | object | Custom style to Bottom Sheet | {} | | ||
| keyboardAvoidingViewEnabled | boolean | Enable KeyboardAvoidingView | true (ios) | | ||
| customModalProps | object | Custom props to Bottom Sheet Modal | {} | | ||
| Props | Type | Description | Default | | ||
| ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | ||
| height | number | The height of bottom sheet | 260 | | ||
| openDuration | number | Duration of the animation when opening bottom sheet | 300 (ms) | | ||
| closeDuration | number | Duration of the animation when closing bottom sheet | 200 (ms) | | ||
| closeOnPressMask | boolean | Press the outside area (mask) to close bottom sheet | true | | ||
| closeOnPressBack | boolean | Press hardware back android to close bottom sheet (Android only) | false | | ||
| draggable | boolean | Enable the drag-down gesture to close the bottom sheet | false | | ||
| dragOnContent | boolean | The draggable is only worked on the draggable icon. Set this to `true`<br />if you want to drag on the content as well (doesn't work with ScrollView) | false | | ||
| useNativeDriver | boolean | Use the native driver to run smoother animation | false | | ||
| customStyles | object | Add [custom styles](#available-custom-style) to bottom sheet | {} | | ||
| customModalProps | object | Add [custom props](https://reactnative.dev/docs/modal#props) to modal | {} | | ||
| customAvoidingViewProps | object | Add [custom props](https://reactnative.dev/docs/keyboardavoidingview#props) to KeyboardAvoidingView | {} | | ||
| onOpen | function | Callback function that will be called after the bottom sheet has been opened | null | | ||
| onClose | function | Callback function that will be called after the bottom sheet has been closed | null | | ||
### Available Custom Style | ||
``` | ||
```js | ||
customStyles: { | ||
wrapper: {...}, // The Root of Component (You can change the `backgroundColor` or any styles) | ||
container: {...}, // The Container of Bottom Sheet | ||
draggableIcon: {...} // The Draggable Icon (If you set closeOnDragDown to true) | ||
wrapper: {...}, // The Root of component (Change the mask's background color here). | ||
container: {...}, // The Container of bottom sheet (The animated view that contains your component). | ||
draggableIcon: {...} // The style of Draggable Icon (If you set `draggable` to `true`). | ||
} | ||
@@ -174,15 +147,28 @@ ``` | ||
| Method Name | Description | | ||
| ----------- | ------------------ | | ||
| open | Open Bottom Sheet | | ||
| close | Close Bottom Sheet | | ||
| Method Name | Description | Usage | | ||
| ----------- | ---------------------- | ---------------------------- | | ||
| open | Open the bottom sheet | `refRBSheet.current.open()` | | ||
| close | Close the bottom sheet | `refRBSheet.current.close()` | | ||
## Note | ||
## CONTRIBUTING | ||
- If you combind `RBSheet` with <a href="https://github.com/kmagiera/react-native-gesture-handler" target="_blank">react-native-gesture-handler</a>, the components inside RBSheet will not fire onPress event on Android [#37](https://github.com/nysamnang/react-native-raw-bottom-sheet/issues/37). | ||
- The demo source codes are in `example folder`. | ||
I'm really glad you're reading this, because we need volunteer developers to help bring this project to life. | ||
#### How to contribute: | ||
1. Clone this repository | ||
2. Open project, then run `yarn` to install devDependencies | ||
3. Add your magic code for contribution | ||
4. Test your code | ||
- Navigate to `example` folder | ||
- Run `yarn` & `yarn start` to run the example project | ||
- Test your code in `example/App.js` | ||
5. Update `README.md` to update documentation (Optional) | ||
6. Write unit testing in `__tests__` folder (Optional) | ||
7. Update `index.d.ts` to update typing (Optional) | ||
8. Make a pull request, Genius! | ||
## License | ||
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/nysamnang/react-native-raw-bottom-sheet/blob/master/LICENSE) file for details | ||
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/nysamnang/react-native-raw-bottom-sheet/blob/master/LICENSE) file for details. | ||
@@ -189,0 +175,0 @@ ## Author |
322
src/index.js
@@ -1,200 +0,164 @@ | ||
import React, { Component } from "react"; | ||
import PropTypes from "prop-types"; | ||
// Importing necessary packages and components | ||
import React, {useState, useRef, forwardRef, useImperativeHandle} from 'react'; | ||
import { | ||
View, | ||
KeyboardAvoidingView, | ||
Modal, | ||
TouchableOpacity, | ||
Animated, | ||
PanResponder, | ||
Platform | ||
} from "react-native"; | ||
import styles from "./style"; | ||
TouchableOpacity, | ||
Modal, | ||
KeyboardAvoidingView, | ||
Platform, | ||
View, | ||
} from 'react-native'; | ||
import styles from './style'; | ||
const SUPPORTED_ORIENTATIONS = [ | ||
"portrait", | ||
"portrait-upside-down", | ||
"landscape", | ||
"landscape-left", | ||
"landscape-right" | ||
]; | ||
// Creating the RBSheet component | ||
const RBSheet = forwardRef((props, ref) => { | ||
// Props destructuring | ||
const { | ||
height = 260, | ||
openDuration = 300, | ||
closeDuration = 200, | ||
closeOnPressMask = true, | ||
closeOnPressBack = false, | ||
draggable = false, | ||
dragOnContent = false, | ||
useNativeDriver = false, | ||
customStyles = {}, | ||
customModalProps = {}, | ||
customAvoidingViewProps = {}, | ||
onOpen = null, | ||
onClose = null, | ||
children = <View />, | ||
} = props; | ||
class RBSheet extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
modalVisible: false, | ||
animatedHeight: new Animated.Value(0), | ||
pan: new Animated.ValueXY() | ||
}; | ||
// Using useState hook to manage modal visibility | ||
const [modalVisible, setModalVisible] = useState(false); | ||
this.createPanResponder(props); | ||
} | ||
// Using useRef hook to reference animated values | ||
const animatedHeight = useRef(new Animated.Value(0)).current; | ||
const pan = useRef(new Animated.ValueXY()).current; | ||
setModalVisible(visible, props) { | ||
const { height, minClosingHeight, openDuration, closeDuration, onClose, onOpen } = this.props; | ||
const { animatedHeight, pan } = this.state; | ||
if (visible) { | ||
this.setState({ modalVisible: visible }); | ||
if (typeof onOpen === "function") onOpen(props); | ||
Animated.timing(animatedHeight, { | ||
useNativeDriver: false, | ||
toValue: height, | ||
duration: openDuration | ||
}).start(); | ||
} else { | ||
Animated.timing(animatedHeight, { | ||
useNativeDriver: false, | ||
toValue: minClosingHeight, | ||
duration: closeDuration | ||
}).start(() => { | ||
pan.setValue({ x: 0, y: 0 }); | ||
this.setState({ | ||
modalVisible: visible, | ||
animatedHeight: new Animated.Value(0) | ||
}); | ||
// Exposing component methods to parent via useImperativeHandle hook | ||
useImperativeHandle(ref, () => ({ | ||
open: () => handleSetVisible(true), | ||
close: () => handleSetVisible(false), | ||
})); | ||
if (typeof onClose === "function") onClose(props); | ||
}); | ||
} | ||
} | ||
// Function to create PanResponder | ||
const createPanResponder = () => { | ||
return PanResponder.create({ | ||
// Respond only if draggable is true | ||
onStartShouldSetPanResponder: () => draggable, | ||
createPanResponder(props) { | ||
const { closeOnDragDown, closeOnTouchablesDragDown, height } = props; | ||
const { pan } = this.state; | ||
this.panResponder = PanResponder.create({ | ||
onStartShouldSetPanResponder: () => closeOnDragDown, | ||
onMoveShouldSetPanResponder: (e, gestureState) => ( | ||
(closeOnTouchablesDragDown && closeOnDragDown) | ||
&& (Math.abs(gestureState.dx) >= 5 | ||
|| Math.abs(gestureState.dy) >= 5) | ||
), | ||
// Respond only if draggable, dragOnContent is true, and vertical movement is positive | ||
onMoveShouldSetPanResponder: (e, gestureState) => | ||
draggable && dragOnContent && gestureState.dy > 0, | ||
// Update pan.y value on vertical move if gestureState.dy is positive | ||
onPanResponderMove: (e, gestureState) => { | ||
if (gestureState.dy > 0) { | ||
Animated.event([null, { dy: pan.y }], { useNativeDriver: false })(e, gestureState); | ||
} | ||
gestureState.dy > 0 && | ||
Animated.event([null, {dy: pan.y}], {useNativeDriver})( | ||
e, | ||
gestureState, | ||
); | ||
}, | ||
// Handle when the user has released the touche | ||
onPanResponderRelease: (e, gestureState) => { | ||
if (height / 4 - gestureState.dy < 0) { | ||
this.setModalVisible(false); | ||
// Close modal if swipe down distance is more than 100 | ||
if (gestureState.dy > 100) { | ||
handleSetVisible(false); | ||
} else { | ||
Animated.spring(pan, { toValue: { x: 0, y: 0 }, useNativeDriver: false }).start(); | ||
// Reset pan to original position on release | ||
Animated.spring(pan, { | ||
toValue: {x: 0, y: 0}, | ||
useNativeDriver, | ||
}).start(); | ||
} | ||
} | ||
}, | ||
}); | ||
} | ||
}; | ||
open(props) { | ||
this.setModalVisible(true, props); | ||
} | ||
// Referencing the panResponder | ||
const panResponder = useRef(createPanResponder()).current; | ||
close(props) { | ||
this.setModalVisible(false, props); | ||
} | ||
setHeight(newHeight) { | ||
const { animatedHeight } = this.state; | ||
const { duration } = this.props; | ||
Animated.timing(animatedHeight, { | ||
toValue: newHeight, | ||
duration | ||
}).start(); | ||
} | ||
// Function to handle the visibility of the modal | ||
const handleSetVisible = visible => { | ||
if (visible) { | ||
setModalVisible(visible); | ||
// Call onOpen callback if provided | ||
if (typeof onOpen === 'function') { | ||
onOpen(); | ||
} | ||
// Animate height on open | ||
Animated.timing(animatedHeight, { | ||
useNativeDriver, | ||
toValue: height, | ||
duration: openDuration, | ||
}).start(); | ||
} else { | ||
// Animate height on close | ||
Animated.timing(animatedHeight, { | ||
useNativeDriver, | ||
toValue: 0, | ||
duration: closeDuration, | ||
}).start(() => { | ||
setModalVisible(visible); | ||
// Reset pan value | ||
pan.setValue({x: 0, y: 0}); | ||
// Call onClose callback if provided | ||
if (typeof onClose === 'function') { | ||
onClose(); | ||
} | ||
}); | ||
} | ||
}; | ||
render() { | ||
const { | ||
animationType, | ||
closeOnDragDown, | ||
dragFromTopOnly, | ||
closeOnPressMask, | ||
closeOnPressBack, | ||
children, | ||
customStyles, | ||
keyboardAvoidingViewEnabled, | ||
customModalProps | ||
} = this.props; | ||
const { animatedHeight, pan, modalVisible } = this.state; | ||
const panStyle = { | ||
transform: pan.getTranslateTransform() | ||
}; | ||
return ( | ||
<Modal | ||
transparent | ||
animationType={animationType} | ||
visible={modalVisible} | ||
supportedOrientations={SUPPORTED_ORIENTATIONS} | ||
onRequestClose={() => { | ||
if (closeOnPressBack) this.setModalVisible(false); | ||
}} | ||
{...customModalProps} | ||
> | ||
<KeyboardAvoidingView | ||
enabled={keyboardAvoidingViewEnabled} | ||
behavior="padding" | ||
style={[styles.wrapper, customStyles.wrapper]} | ||
> | ||
<TouchableOpacity | ||
style={styles.mask} | ||
activeOpacity={1} | ||
onPress={() => (closeOnPressMask ? this.close() : null)} | ||
/> | ||
<Animated.View | ||
{...(!dragFromTopOnly && this.panResponder.panHandlers)} | ||
style={[panStyle, styles.container, { height: animatedHeight }, customStyles.container]} | ||
> | ||
{closeOnDragDown && ( | ||
// Returning the RBSheet component | ||
return ( | ||
<Modal | ||
testID="Modal" | ||
transparent | ||
visible={modalVisible} | ||
onRequestClose={closeOnPressBack ? () => handleSetVisible(false) : null} // Close on hardware button press (Android) if enabled | ||
{...customModalProps}> | ||
<KeyboardAvoidingView | ||
testID="KeyboardAvoidingView" | ||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'} | ||
style={[styles.wrapper, customStyles.wrapper]} | ||
{...customAvoidingViewProps}> | ||
<TouchableOpacity | ||
testID="TouchableOpacity" | ||
style={styles.mask} | ||
activeOpacity={1} | ||
onPress={closeOnPressMask ? () => handleSetVisible(false) : null} // Close on mask press if enabled | ||
/> | ||
<Animated.View | ||
testID="AnimatedView" | ||
{...(dragOnContent && panResponder.panHandlers)} // Attach pan handlers to content if dragOnContent is true | ||
style={[ | ||
styles.container, | ||
{transform: pan.getTranslateTransform()}, | ||
{height: animatedHeight}, | ||
customStyles.container, | ||
]}> | ||
{draggable && ( // Show draggable icon if set it to true | ||
<View | ||
testID="DraggableView" | ||
{...(!dragOnContent && panResponder.panHandlers)} // Attach pan handlers to draggable icon if dragOnContent is false | ||
style={styles.draggableContainer}> | ||
<View | ||
{...(dragFromTopOnly && this.panResponder.panHandlers)} | ||
style={styles.draggableContainer} | ||
> | ||
<View style={[styles.draggableIcon, customStyles.draggableIcon]} /> | ||
</View> | ||
)} | ||
{children} | ||
</Animated.View> | ||
</KeyboardAvoidingView> | ||
</Modal> | ||
); | ||
} | ||
} | ||
testID="DraggableIcon" | ||
style={[styles.draggableIcon, customStyles.draggableIcon]} | ||
/> | ||
</View> | ||
)} | ||
{children} | ||
</Animated.View> | ||
</KeyboardAvoidingView> | ||
</Modal> | ||
); | ||
}); | ||
RBSheet.propTypes = { | ||
animationType: PropTypes.oneOf(["none", "slide", "fade"]), | ||
height: PropTypes.number, | ||
minClosingHeight: PropTypes.number, | ||
openDuration: PropTypes.number, | ||
closeDuration: PropTypes.number, | ||
closeOnDragDown: PropTypes.bool, | ||
closeOnTouchablesDragDown: PropTypes.bool, | ||
closeOnPressMask: PropTypes.bool, | ||
dragFromTopOnly: PropTypes.bool, | ||
closeOnPressBack: PropTypes.bool, | ||
keyboardAvoidingViewEnabled: PropTypes.bool, | ||
customStyles: PropTypes.objectOf(PropTypes.object), | ||
customModalProps: PropTypes.objectOf(PropTypes.any), | ||
onClose: PropTypes.func, | ||
onOpen: PropTypes.func, | ||
children: PropTypes.node | ||
}; | ||
RBSheet.defaultProps = { | ||
animationType: "none", | ||
height: 260, | ||
minClosingHeight: 0, | ||
openDuration: 300, | ||
closeDuration: 200, | ||
closeOnDragDown: false, | ||
closeOnTouchablesDragDown: false, | ||
dragFromTopOnly: false, | ||
closeOnPressMask: true, | ||
closeOnPressBack: true, | ||
keyboardAvoidingViewEnabled: Platform.OS === "ios", | ||
customStyles: {}, | ||
customModalProps: {}, | ||
onClose: null, | ||
onOpen: null, | ||
children: <View /> | ||
}; | ||
// Exporting the RBSheet component | ||
export default RBSheet; |
@@ -1,2 +0,2 @@ | ||
import { StyleSheet } from "react-native"; | ||
import {StyleSheet} from 'react-native'; | ||
@@ -6,18 +6,18 @@ const styles = StyleSheet.create({ | ||
flex: 1, | ||
backgroundColor: "#00000077" | ||
backgroundColor: '#00000077', | ||
}, | ||
mask: { | ||
flex: 1, | ||
backgroundColor: "transparent" | ||
backgroundColor: 'transparent', | ||
}, | ||
container: { | ||
backgroundColor: "#fff", | ||
width: "100%", | ||
backgroundColor: '#fff', | ||
width: '100%', | ||
height: 0, | ||
overflow: "hidden" | ||
overflow: 'hidden', | ||
}, | ||
draggableContainer: { | ||
width: "100%", | ||
alignItems: "center", | ||
backgroundColor: "transparent" | ||
width: '100%', | ||
alignItems: 'center', | ||
backgroundColor: 'transparent', | ||
}, | ||
@@ -29,6 +29,6 @@ draggableIcon: { | ||
margin: 10, | ||
backgroundColor: "#ccc" | ||
} | ||
backgroundColor: '#ccc', | ||
}, | ||
}); | ||
export default styles; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
14
18586
260
1
173
1