From 904fc78c9dba559812d456703e4890fb06b54520 Mon Sep 17 00:00:00 2001 From: Aswin Lakshmanan Date: Sat, 18 Feb 2023 20:49:22 +0530 Subject: [PATCH] feat(upgrade): add react-native-reanimated Add react-native-reanimated package to project and update readme with relevant information. Changes made: - Added react-native-reanimated package - Added example for custom hook usage - Updated readme This commit follows the conventional commit format. --- README.md | 126 +++++++++++++++++----------------------- babel.config.js | 1 + example/babel.config.js | 1 + example/package.json | 9 +-- example/src/App.tsx | 39 ++++++++++++- example/yarn.lock | 37 +++++++++++- package.json | 6 +- src/index.tsx | 124 ++++++++++++++++++--------------------- src/slider.types.ts | 1 - yarn.lock | 45 ++++++++++++-- 10 files changed, 232 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index fa79659..acf0dfa 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,60 @@ -# πŸ†• React Native Vertical Slider 🎚 +# :control_knobs: rn-vertical-slider -#### **(rn-vertical-slider)** +A highly customizable vertical slider component for React Native using React Native Gesture Handler and Reanimated. Support this project with a β˜… on [**Github**](https://github.com/sacmii/rn-vertical-slider). -###### A vertical Slider for React Native written entirely in javascript. Support this project with a β˜… on [**Github**](https://github.com/sacmii/rn-vertical-slider). +Alt text -## ✨Features +## :inbox_tray: Installation -- πŸ“ Completely written in Typescript -- πŸ”— No Native linking required +You can install this package using either Yarn or NPM. -![Example1](https://user-images.githubusercontent.com/12546974/82729464-63a73d00-9d15-11ea-99dc-e432e61d8398.gif) ![Example2](https://user-images.githubusercontent.com/12546974/82730380-b0dadd00-9d1c-11ea-889d-03249c6b5f76.gif) +### Alt text -## 🏁 Getting Started - -- To add this slider to your project : - -``` +```bash npm install rn-vertical-slider ``` -## 🎨 Usage - -- A basic example of slider +### Alt text +```bash +yarn add rn-vertical-slider ``` - { - console.log("CHANGE", value); - }} - onComplete={(value: number) => { - console.log("COMPLETE", value); - }} - width={50} - height={300} - step={1} - borderRadius={5} - minimumTrackTintColor={"gray"} - maximumTrackTintColor={"tomato"} - showBallIndicator - ballIndicatorColor={"gray"} - ballIndicatorTextColor={"white"} - /> + +## :bulb: Usage + +```bash +import VerticalSlider from 'rn-vertical-slider'; + +function App() { + const [value, setValue] = useState(0); + return ( + setValue(value)} + height={200} + width={40} + step={1} + min={0} + max={100} + borderRadius={5} + minimumTrackTintColor="#2979FF" + maximumTrackTintColor="#D1D1D6" + showBallIndicator + ballIndicatorColor="#2979FF" + ballIndicatorTextColor="#fff" + ballIndicatorWidth={80} + ballIndicatorHeight={40} + /> + ); +} ``` -## πŸŽ› Props +Alt text + +## :book: Props | Property | Type | Default | Description | -| :--------------------- | :------- | :-------- | :-------------------------------------------------------------------------------------------- | +|:-----------------------|:---------|:----------|:----------------------------------------------------------------------------------------------| | value | number | 0 | Value of the slider. | | disabled | bool | false | Enable or disable slider. | | min | number | 0 | Minimum value for slider. | @@ -69,48 +73,24 @@ npm install rn-vertical-slider | ballIndicatorHeight | number | 48 | Diameter of Indicator. [Width of Indicator : If renderIndicator present] | | ballIndicatorPosition | number | -50 | Horizontal position of Indicator with respect to current selected value. | | ballIndicatorTextColor | string | '#fff' | Indicator text color. | -| animationDuration | number | 0 | Animation Duration | | showBackgroundShadow | boolean | 0 | Display shadow on Indicator (If available) and Slider | | shadowProps | object | see below | Shadow Configuration for Slider | -| renderIndicator | boolean | 0 | Render a custom slider indicator | +| renderIndicator | boolean | 0 | Render a custom slider indicator -- #### shadowProps +## :art: Demo -shadowProps define the shadow properties for slider (Indicator Component if shown) -Default Props : +You can try the [example app](https://github.com/sacmii/rn-vertical-slider/tree/master/example) by cloning this repo and running the following commands: +```sh +cd example +yarn install +npx expo start ``` - { - shadowOffsetWidth = 0, - shadowOffsetHeight = 1, - shadowOpacity = 0.22, - shadowRadius = 2.22, - elevation = 3, - shadowColor = '#000', - } -``` - -- #### renderIndicator - -renderIndicator is used when you want to use custom indicator for the slider. _ballIndicatorHeight_, _ballIndicatorWidth_ will define the height and width of the component. - -- ##### Custom renderIndicator - - ![Custom Indicator](https://user-images.githubusercontent.com/12546974/82730062-89831080-9d1a-11ea-8f41-b37d02b79a69.gif) - -## πŸ“Œ Extras - -- _[Gradient Slider](https://github.com/sacmii/rn-vertical-slider-gradient)_ Slider with linear gradient -- **Github** β˜…'s are more additcting than Coffee 🀩 - -## β˜€οΈ License - -This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details -## 🚧 Contributing +## :handshake: Contributing -See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. +Contributions are welcome! Feel free to open an issue or submit a pull request if you find a bug or have a feature request. See the [contributing guide](https://github.com/sacmii/rn-vertical-slider/blob/master/CONTRIBUTING) to learn how to contribute to the repository and the development workflow. -## License +## :scroll: License -MIT +This project is licensed under the [MIT License](https://github.com/sacmii/rn-vertical-slider/blob/master/LICENSE). diff --git a/babel.config.js b/babel.config.js index f842b77..983e075 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,4 @@ module.exports = { presets: ['module:metro-react-native-babel-preset'], + plugins: ['react-native-reanimated/plugin'], }; diff --git a/example/babel.config.js b/example/babel.config.js index b85e43d..1dce2a7 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -7,6 +7,7 @@ module.exports = function (api) { return { presets: ['babel-preset-expo'], plugins: [ + ['react-native-reanimated/plugin'], [ 'module-resolver', { diff --git a/example/package.json b/example/package.json index b123c26..36bcaac 100644 --- a/example/package.json +++ b/example/package.json @@ -12,15 +12,16 @@ "expo": "~47.0.12", "expo-status-bar": "~1.4.2", "react": "18.1.0", - "react-native": "0.70.5", "react-dom": "18.1.0", + "react-native": "0.70.5", + "react-native-reanimated": "~2.12.0", "react-native-web": "~0.18.9" }, "devDependencies": { "@babel/core": "^7.12.9", - "babel-plugin-module-resolver": "^4.1.0", "@expo/webpack-config": "^0.17.2", - "babel-loader": "^8.1.0" + "babel-loader": "^8.1.0", + "babel-plugin-module-resolver": "^4.1.0" }, "private": true -} \ No newline at end of file +} diff --git a/example/src/App.tsx b/example/src/App.tsx index c4346e6..dc1cdbc 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,6 +1,36 @@ import * as React from 'react'; import { StyleSheet, View } from 'react-native'; +import Animated from 'react-native-reanimated'; import RnVerticalSlider from 'rn-vertical-slider'; +import { Ionicons } from '@expo/vector-icons'; + +const renderIcon = (newVal: number) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const iconProps = React.useMemo(() => { + let styles = { + name: '', + size: 30, + color: '#FFFBF5', + }; + if (newVal > 75) { + styles.name = 'heart-circle-outline'; + } else if (newVal > 50) { + styles.name = 'help-circle-outline'; + } else { + styles.name = 'heart-dislike-circle-outline'; + } + return styles; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [newVal]); + return ( + + + {/* @ts-ignore */} + + + + ); +}; const App: React.FC = () => { const [value, setValue] = React.useState(1); @@ -18,10 +48,15 @@ const App: React.FC = () => { width={50} height={300} step={1} + showBackgroundShadow={false} borderRadius={5} - minimumTrackTintColor={'gray'} - maximumTrackTintColor={'tomato'} + maximumTrackTintColor="#18122B" + minimumTrackTintColor="#635985" showBallIndicator + renderIndicator={renderIcon} + ballIndicatorWidth={50} + ballIndicatorHeight={50} + ballIndicatorPosition={0} ballIndicatorColor={'gray'} ballIndicatorTextColor={'white'} /> diff --git a/example/yarn.lock b/example/yarn.lock index 1e7742b..207b0b7 100644 --- a/example/yarn.lock +++ b/example/yarn.lock @@ -774,6 +774,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-object-assign@^7.16.7": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.18.6.tgz#7830b4b6f83e1374a5afb9f6111bcfaea872cdd2" + integrity sha512-mQisZ3JfqWh2gVXvfqYCAAyRs6+7oev+myBsTwW5RnPhYXOTuCEw2oe3YgxlXMViXUS53lG8koulI7mJ+8JE+A== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-object-super@^7.0.0", "@babel/plugin-transform-object-super@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" @@ -1016,7 +1023,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-typescript@^7.13.0": +"@babel/preset-typescript@^7.13.0", "@babel/preset-typescript@^7.16.7": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== @@ -1843,6 +1850,11 @@ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" integrity sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w== +"@types/invariant@^2.2.35": + version "2.2.35" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" + integrity sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -6323,6 +6335,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -8414,6 +8431,19 @@ react-native-gradle-plugin@^0.70.3: resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8" integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A== +react-native-reanimated@~2.12.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.12.0.tgz#5821eecfb1769b1617a67a2d4dec12fdeedb2b6e" + integrity sha512-nrlPyw+Hx9u4iJhZk9PoTvDo/QmVAd+bo7OK9Tv3hveNEF9++5oig/g3Uv9V93shy9avTYGsUprUvAEt/xdzeQ== + dependencies: + "@babel/plugin-transform-object-assign" "^7.16.7" + "@babel/preset-typescript" "^7.16.7" + "@types/invariant" "^2.2.35" + invariant "^2.2.4" + lodash.isequal "^4.5.0" + setimmediate "^1.0.5" + string-hash-64 "^1.0.3" + react-native-web@~0.18.9: version "0.18.12" resolved "https://registry.yarnpkg.com/react-native-web/-/react-native-web-0.18.12.tgz#d4bb3a783ece2514ba0508d7805b09c0a98f5a8e" @@ -9350,6 +9380,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +string-hash-64@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322" + integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw== + string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" diff --git a/package.json b/package.json index 87fbf5d..9d31fe6 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,8 @@ "registry": "https://registry.npmjs.org/" }, "devDependencies": { + "@commitlint/config-conventional": "^17.4.4", "@evilmartians/lefthook": "^1.2.2", - "@commitlint/config-conventional": "^17.0.2", "@react-native-community/eslint-config": "^3.0.2", "@release-it/conventional-changelog": "^5.0.0", "@types/jest": "^28.1.2", @@ -74,6 +74,7 @@ "react": "18.1.0", "react-native": "0.70.5", "react-native-builder-bob": "^0.20.0", + "react-native-reanimated": "^2.14.4", "release-it": "^15.0.0", "typescript": "^4.5.2" }, @@ -82,7 +83,8 @@ }, "peerDependencies": { "react": "*", - "react-native": "*" + "react-native": "*", + "react-native-reanimated": "*" }, "engines": { "node": ">= 16.0.0" diff --git a/src/index.tsx b/src/index.tsx index 4d07d21..cb09025 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,13 +1,15 @@ import * as React from 'react'; import { View, - Animated, PanResponder, StyleSheet, GestureResponderEvent, PanResponderGestureState, - Easing, } from 'react-native'; +import Animated, { + useAnimatedStyle, + useSharedValue, +} from 'react-native-reanimated'; import type { SliderProps } from './slider.types'; const VerticalSlider: React.FC = ({ @@ -31,7 +33,6 @@ const VerticalSlider: React.FC = ({ disabled = false, onChange = () => {}, onComplete = () => {}, - animationDuration = 100, value: currentValue = 0, }) => { const { @@ -84,14 +85,9 @@ const VerticalSlider: React.FC = ({ // End Slider base styles // Helper Variables - let _moveStartValue = React.useRef(0).current; - const value = React.useRef(new Animated.Value(currentValue)).current; - const sliderHeight = React.useRef(new Animated.Value(0)).current; - const ballHeight = React.useRef(new Animated.Value(0)).current; - - const getSliderHeight = (newValue: number) => { - return ((newValue - min) * height) / (max - min); - }; + const _moveStartValue = useSharedValue(0); + const _value = useSharedValue(currentValue); + const value = new Animated.Value(currentValue); // Calculating Values from props.value const calculateValues = () => { @@ -103,7 +99,6 @@ const VerticalSlider: React.FC = ({ // Initializing when component mounts // eslint-disable-next-line react-hooks/exhaustive-deps React.useEffect(calculateValues, []); - // Initializing when component mounts const _calculateValue = (gestureState: PanResponderGestureState) => { const ratio = -gestureState.dy / height; @@ -113,11 +108,13 @@ const VerticalSlider: React.FC = ({ min, Math.min( max, - _moveStartValue.valueOf() + Math.round((ratio * diff) / step) * step + // @ts-ignore + _moveStartValue.value + Math.round((ratio * diff) / step) * step ) ) : Math.floor( - Math.max(min, _moveStartValue.valueOf() + ratio * diff) * 100 + // @ts-ignore + Math.max(min, _moveStartValue.value + ratio * diff) * 100 ) / 100; }; @@ -128,43 +125,16 @@ const VerticalSlider: React.FC = ({ const updateNewValue = (newValue: number) => { let valueToUpdate = _clamp(newValue, min, max); + _value.value = valueToUpdate; value.setValue(valueToUpdate); - const _sliderHeight = getSliderHeight(valueToUpdate); - let _ballPosition = _sliderHeight; - const _ballHeight = renderIndicator - ? ballIndicatorHeight - : ballIndicatorWidth; - if (_ballPosition + _ballHeight >= height) { - _ballPosition = height - _ballHeight; - } else if (_ballPosition - _ballHeight <= 0) { - _ballPosition = 0; - } else { - _ballPosition = _ballPosition - _ballHeight / 2; - } - Animated.parallel([ - Animated.timing(sliderHeight, { - toValue: _sliderHeight, - easing: Easing.linear, - duration: animationDuration || 0, - useNativeDriver: false, - }), - Animated.timing(ballHeight, { - toValue: _ballPosition, - easing: Easing.linear, - duration: animationDuration || 0, - useNativeDriver: false, - }), - ]).start(); }; - // End Helper Variables // PanResponder handlers const onStartShouldSetPanResponder = () => true; const onMoveShouldSetPanResponder = () => false; const onPanResponderTerminationRequest = () => false; const onPanResponderGrant = () => { - // @ts-ignore - _moveStartValue = value._value; + _moveStartValue.value = _value.value; }; const onPanResponderMove = ( _event: GestureResponderEvent, @@ -208,6 +178,44 @@ const VerticalSlider: React.FC = ({ ).current; // End Value connected to state, slider height Animated Value, ballHeight Animated Value, panResponder + const sliderStyle = useAnimatedStyle( + () => ({ + height: ((_value.value - min) * height) / (max - min), + backgroundColor: minimumTrackTintColor, + borderRadius, + }), + [_value] + ); + + const ballStyle = useAnimatedStyle(() => { + let styles = { + height: renderIndicator ? ballIndicatorHeight : ballIndicatorWidth, + left: ballIndicatorPosition, + width: ballIndicatorWidth, + bottom: 0, + backgroundColor: 'transparent', + borderRadius: 0, + }; + // If renderIndicator is provided, then we don't need to calculate the position of ballIndicator + if (!renderIndicator) { + styles.backgroundColor = ballIndicatorColor; + styles.borderRadius = ballIndicatorWidth; + } + let _sliderHeight = ((_value.value - min) * height) / (max - min); + let _ballPosition = _sliderHeight; + const _ballHeight = + renderIndicator !== null ? ballIndicatorHeight : ballIndicatorWidth; + if (_ballPosition + _ballHeight >= height) { + _ballPosition = height - _ballHeight; + } else if (_ballPosition - _ballHeight <= 0) { + _ballPosition = 0; + } else { + _ballPosition = _ballPosition - _ballHeight / 2; + } + styles.bottom = _ballPosition; + return styles; + }, [_value]); + return ( = ({ ]} {...panResponder.panHandlers} > - + {showBallIndicator && ( {renderIndicator ? ( // @ts-ignore - renderIndicator(value._value) + renderIndicator(_value.value) ) : (