├── .gitignore ├── .npmignore ├── README.md ├── ezgif.com-video-to-gif.gif ├── index.js ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | ezgif.com-video-to-gif.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-circular-menu 2 | 3 | A Ripple menu effect for your react native application 4 | 5 | [![npm version](https://img.shields.io/npm/v/react-native-circular-menu.svg?style=flat-square)](https://www.npmjs.com/package/react-native-circular-menu) 6 | [![NPM downloads](https://img.shields.io/npm/dm/react-native-circular-menu.svg?style=flat-square)](https://www.npmjs.com/package/react-native-circular-menu) 7 | [![Package Quality](http://npm.packagequality.com/shield/react-native-circular-menu.svg?style=flat-square)](http://packagequality.com/#?package=react-native-circular-menu) 8 | [![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg?style=flat-square)](http://paypal.me/dcergo) 9 | 10 | # Example 11 | 12 | ![example](https://github.com/cinder92/react-native-circular-menu/blob/master/ezgif.com-video-to-gif.gif) 13 | 14 | # How to install 15 | 16 | `yarn add react-native-circular-menu` 17 | 18 | # How to use 19 | 20 | ``` 21 | ... 22 | import CircularMenu from 'react-native-circular-menu'; 23 | 24 | const App = () => { 25 | const [show, setState] = useState(false); 26 | 27 | const renderItems = () => [Menu Item]; 28 | 29 | const renderCloseBtn = () => ( 30 | { 32 | setState(!show); 33 | }}> 34 | Close Menu 35 | 36 | ); 37 | 38 | return ( 39 | 40 | 52 | ); 53 | ``` 54 | 55 | # Props 56 | 57 | | Name | Description | Required | 58 | | ------------ | --------------------------------------------------------- | -------- | 59 | | show | Set `true` to show and `false` to hide | yes | 60 | | color | Change color of ripple effect | no | 61 | | size | Change the scale size of circle (default : 20) | no | 62 | | items | Menu items (array) | yes | 63 | | position | Change position of animation circle (default : `topLeft`) | no | 64 | | closeBtn | Receives a component for close button | yes | 65 | | openDelay | Change delay before the menu opens (default : 250) | no | 66 | | closeDelay | Change delay before the menu closes (default : 350) | no | 67 | | contentStyle | Style object for the child container | no | 68 | 69 | # Positions 70 | 71 | Circular Menu supports 7 positions, `topLeft`, `topCenter`, `topRight`, `center`, `bottomLeft`, `bottomCenter`, `bottomRight`. 72 | 73 | # TODO 74 | 75 | - [x] Test in iPhone 76 | - [x] Test in Android 77 | - [ ] Add animations 78 | 79 | # of coooourse PR are welcome :) 80 | -------------------------------------------------------------------------------- /ezgif.com-video-to-gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cinder92/react-native-circular-menu/66d61ca319e6f78cfb91e8347aa18513a4d17479/ezgif.com-video-to-gif.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useReducer } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { View, StyleSheet, Animated, Easing, Dimensions } from "react-native"; 4 | 5 | const screen = Dimensions.get("window"); 6 | 7 | const CircularMenu = (props) => { 8 | const reducer = (state, newState) => ({ ...state, ...newState }); 9 | const [state, _] = useReducer(reducer, { 10 | animatedStyle: new Animated.Value(0), 11 | animatedContent: new Animated.Value(0), 12 | color: props.color, 13 | size: props.size, 14 | position: props.position, 15 | }); 16 | 17 | const _handleUpdate = () => { 18 | const { show, openDelay, closeDelay } = props; 19 | const { size } = state; 20 | 21 | const currentSize = size ?? 20; 22 | const _openDelay = openDelay ?? 250; 23 | const _closeDelay = closeDelay ?? 350; 24 | 25 | Animated.timing(state.animatedStyle, { 26 | toValue: show ? currentSize : 0, 27 | delay: show ? _openDelay : 0, 28 | useNativeDriver: true, 29 | easing: Easing.inOut(Easing.ease), 30 | }).start(); 31 | 32 | Animated.timing(state.animatedContent, { 33 | toValue: show ? 1 : 0, 34 | delay: show ? _closeDelay : 0, 35 | useNativeDriver: true, 36 | easing: Easing.inOut(Easing.ease), 37 | }).start(); 38 | }; 39 | 40 | const _computeStyles = () => { 41 | const { position } = state; 42 | let _position = position; 43 | 44 | // default position is topLeft 45 | if (position == null || position.length == 0) { 46 | _position = "topLeft"; 47 | } 48 | 49 | switch (_position) { 50 | case "topLeft": 51 | return { 52 | top: 0, 53 | left: -20, 54 | }; 55 | case "topRight": 56 | return { 57 | top: 0, 58 | right: -20, 59 | }; 60 | case "topCenter": 61 | return { 62 | top: 0, 63 | left: screen.width / 3, 64 | }; 65 | case "center": 66 | return { 67 | top: screen.height / 3, 68 | left: screen.width / 3, 69 | }; 70 | case "bottomCenter": 71 | return { 72 | bottom: 0, 73 | left: screen.width / 3, 74 | }; 75 | case "bottomLeft": 76 | return { 77 | bottom: 0, 78 | left: -20, 79 | }; 80 | case "bottomRight": 81 | return { 82 | bottom: 0, 83 | right: -20, 84 | }; 85 | default: 86 | return {}; 87 | } 88 | }; 89 | 90 | useEffect(() => _handleUpdate(), [props.show, props.size]); 91 | 92 | const { animatedStyle, animatedContent, color } = state; 93 | const { show, items, closeBtn, contentStyle } = props; 94 | 95 | const _animatedStyles = [ 96 | { 97 | transform: [ 98 | { 99 | scale: animatedStyle, 100 | }, 101 | ], 102 | 103 | ...defaultStyles.container, 104 | backgroundColor: color ? color : "#F23B58", 105 | ..._computeStyles(), 106 | }, 107 | ]; 108 | 109 | return ( 110 | 111 | 112 | 118 | {closeBtn} 119 | {items} 120 | 121 | 122 | ); 123 | }; 124 | 125 | CircularMenu.propTypes = { 126 | color: PropTypes.string, 127 | show: PropTypes.bool.isRequired, 128 | items: PropTypes.array.isRequired, 129 | size: PropTypes.number, 130 | position: PropTypes.arrayOf([ 131 | "topCenter", 132 | "topLeft", 133 | "topRight", 134 | "center", 135 | "bottomLeft", 136 | "bottomCenter", 137 | "bottomRight", 138 | ]), 139 | closeBtn: PropTypes.element, 140 | openDelay: PropTypes.number, 141 | closeDelay: PropTypes.number, 142 | contentStyle: PropTypes.object, 143 | }; 144 | 145 | const defaultStyles = StyleSheet.create({ 146 | container: { 147 | width: 100, 148 | height: 100, 149 | backgroundColor: "#F23B58", 150 | borderRadius: 50, 151 | position: "absolute", 152 | }, 153 | 154 | wrap: { 155 | position: "absolute", 156 | top: 0, 157 | left: 0, 158 | right: 0, 159 | bottom: 0, 160 | zIndex: 4, 161 | }, 162 | 163 | content: { 164 | alignItems: "center", 165 | justifyContent: "center", 166 | zIndex: 5, 167 | }, 168 | }); 169 | 170 | export default CircularMenu; 171 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-circular-menu", 3 | "version": "0.1.0", 4 | "description": "A Ripple menu effect for your react native application", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/cinder92/react-native-circular-menu" 9 | }, 10 | "keywords": [ 11 | "react", 12 | "native", 13 | "react-native", 14 | "menu", 15 | "circular" 16 | ], 17 | "author": "cinder92 - Dante Cervantes", 18 | "license": "ISC", 19 | "dependencies": { 20 | "prop-types": "^15.8.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "js-tokens@^3.0.0 || ^4.0.0": 6 | version "4.0.0" 7 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 8 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 9 | 10 | loose-envify@^1.4.0: 11 | version "1.4.0" 12 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 13 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 14 | dependencies: 15 | js-tokens "^3.0.0 || ^4.0.0" 16 | 17 | object-assign@^4.1.1: 18 | version "4.1.1" 19 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 20 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 21 | 22 | prop-types@^15.8.1: 23 | version "15.8.1" 24 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" 25 | integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== 26 | dependencies: 27 | loose-envify "^1.4.0" 28 | object-assign "^4.1.1" 29 | react-is "^16.13.1" 30 | 31 | react-is@^16.13.1: 32 | version "16.13.1" 33 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 34 | integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 35 | --------------------------------------------------------------------------------