├── .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 | [](https://www.npmjs.com/package/react-native-circular-menu)
6 | [](https://www.npmjs.com/package/react-native-circular-menu)
7 | [](http://packagequality.com/#?package=react-native-circular-menu)
8 | [](http://paypal.me/dcergo)
9 |
10 | # Example
11 |
12 | 
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 |
41 | setState(!show)}>Open Menu
42 |
51 |
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 |
--------------------------------------------------------------------------------