├── .babelrc ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── App.js ├── App.test.js ├── README.md ├── app.json ├── assets ├── icons │ ├── app-icon.png │ └── loading-icon.png ├── images │ ├── 1.jpeg │ ├── 2.jpeg │ ├── 3.jpeg │ ├── 4.jpg │ ├── 5.jpeg │ └── main.png └── qr-code.png ├── implementation.gif ├── package.json ├── preview.gif └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | .*/Libraries/react-native/ReactNative.js 16 | 17 | ; Additional create-react-native-app ignores 18 | 19 | ; Ignore duplicate module providers 20 | .*/node_modules/fbemitter/lib/* 21 | 22 | ; Ignore misbehaving dev-dependencies 23 | .*/node_modules/xdl/build/* 24 | .*/node_modules/reqwest/tests/* 25 | 26 | ; Ignore missing expo-sdk dependencies (temporarily) 27 | ; https://github.com/expo/expo/issues/162 28 | .*/node_modules/expo/src/* 29 | 30 | ; Ignore react-native-fbads dependency of the expo sdk 31 | .*/node_modules/react-native-fbads/* 32 | 33 | [include] 34 | 35 | [libs] 36 | node_modules/react-native/Libraries/react-native/react-native-interface.js 37 | node_modules/react-native/flow 38 | flow/ 39 | 40 | [options] 41 | module.system=haste 42 | 43 | emoji=true 44 | 45 | experimental.strict_type_args=true 46 | 47 | munge_underscores=true 48 | 49 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 50 | 51 | suppress_type=$FlowIssue 52 | suppress_type=$FlowFixMe 53 | suppress_type=$FixMe 54 | 55 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) 56 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(4[0-7]\\|[1-3][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ 57 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 58 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 59 | 60 | unsafe.enable_getters_and_setters=true 61 | 62 | [version] 63 | ^0.47.0 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | AppRegistry, 4 | StyleSheet, 5 | StatusBar, 6 | View, 7 | ScrollView, 8 | Text, 9 | Image, 10 | PanResponder, 11 | Animated, 12 | Dimensions, 13 | TouchableHighlight, 14 | TouchableOpacity, 15 | Easing 16 | } from 'react-native'; 17 | 18 | import Icon from 'react-native-vector-icons/Ionicons'; 19 | import { LinearGradient } from 'expo'; 20 | 21 | import resolveAssetSource from 'resolveAssetSource'; 22 | 23 | 24 | const images = [ 25 | { 26 | url: require('./assets/images/1.jpeg'), 27 | text: 'MOUNTAINS' 28 | }, 29 | { 30 | url: require('./assets/images/2.jpeg'), 31 | text: 'TRAVEL' 32 | }, 33 | { 34 | url: require('./assets/images/3.jpeg'), 35 | text: 'ROAD' 36 | }, 37 | { 38 | url: require('./assets/images/4.jpg'), 39 | text: 'TRAVEL' 40 | } 41 | ] 42 | 43 | 44 | export default class App extends React.Component { 45 | constructor(props){ 46 | super(props) 47 | 48 | this.state = { 49 | viewWidth: Dimensions.get('window').width, 50 | viewHeight: Dimensions.get('window').height, 51 | 52 | imageWidth: Dimensions.get('window').width * 0.8, 53 | 54 | animatedContentScaleX: new Animated.Value(1), 55 | animatedContentScaleY: new Animated.Value(1), 56 | 57 | animatedTopMenuMargin: new Animated.Value(-Dimensions.get('window').height), 58 | 59 | activeMenuItem: [ [true, false, false], [false,false, false], [false, false,false]], 60 | imageSizeCoef: 0.8 61 | }; 62 | 63 | this.getImgHeight = this.getImgHeight.bind(this); 64 | this.openTopMenu = this.openTopMenu.bind(this); 65 | this.closeTopMenu = this.closeTopMenu.bind(this); 66 | this.makeActiveItem = this.makeActiveItem.bind(this); 67 | } 68 | 69 | openTopMenu(){ 70 | Animated.parallel([ 71 | Animated.timing( 72 | this.state.animatedContentScaleY, 73 | { 74 | toValue: 0.8, 75 | easing: Easing.cubic, 76 | duration: 500 77 | }), 78 | Animated.timing( 79 | this.state.animatedContentScaleX, 80 | { 81 | toValue: 0.8, 82 | easing: Easing.cubic, 83 | duration: 500 84 | }), 85 | Animated.timing( 86 | this.state.animatedTopMenuMargin, 87 | { 88 | toValue: 20, 89 | easing: Easing.cubic, 90 | duration: 500 91 | }) 92 | ]).start() 93 | } 94 | 95 | closeTopMenu(){ 96 | Animated.parallel([ 97 | Animated.timing( 98 | this.state.animatedContentScaleY, 99 | { 100 | toValue: 1, 101 | easing: Easing.cubic, 102 | duration: 300 103 | }), 104 | Animated.timing( 105 | this.state.animatedContentScaleX, 106 | { 107 | toValue: 1, 108 | easing: Easing.cubic, 109 | duration: 300 110 | }), 111 | Animated.timing( 112 | this.state.animatedTopMenuMargin, 113 | { 114 | toValue: -Dimensions.get('window').height, 115 | easing: Easing.cubic, 116 | duration: 300 117 | }) 118 | ]).start() 119 | } 120 | 121 | getImgHeight(img, coef) { 122 | let source = resolveAssetSource(img); 123 | const scaleFactor = (source.width / (this.state.viewWidth * this.state.imageSizeCoef )); 124 | const imageHeight = source.height / scaleFactor; 125 | 126 | return imageHeight; 127 | } 128 | 129 | getCardContainerHeight(img) { 130 | let source = resolveAssetSource(img); 131 | const scaleFactor = (source.width / this.state.viewWidth); 132 | const imageHeight = source.height / scaleFactor; 133 | 134 | return imageHeight; 135 | } 136 | 137 | makeActiveItem(row,column){ 138 | let menuItemsTemp = this.state.activeMenuItem; 139 | for (let i = 0; i < 3; i++){ 140 | for(let j = 0; j < 3; j++){ 141 | menuItemsTemp[i][j] = false; 142 | } 143 | } 144 | menuItemsTemp[row][column] = true; 145 | this.setState({activeMenuItem: menuItemsTemp}); 146 | } 147 | 148 | renderTopMenu(){ 149 | const rows = 3; 150 | const colums = 3; 151 | 152 | const menuLabels = [['FEED','TIMELINE','PROFILE'],['LIST','COMPOSE','STATS'],['GALLERY','CAPTURE','DISCOVER']]; 153 | 154 | const topMenuItems = []; 155 | 156 | const activeMenuItemStyle = { 157 | fontSize: 15, 158 | fontFamily: 'sans-serif-medium', 159 | color: 'white' 160 | } 161 | const menuItemStyle = { 162 | fontSize: 14, 163 | fontFamily: 'sans-serif-medium', 164 | color: '#c4a1fc' 165 | } 166 | const menuRowStyle = { 167 | flex: 1, 168 | flexDirection: 'row', 169 | justifyContent: 'space-around', 170 | alignItems: 'center' 171 | } 172 | 173 | for (let i = 0; i < rows; i++){ 174 | let menuRow = []; 175 | for(let j = 0; j < colums; j++){ 176 | menuRow.push( 177 | this.makeActiveItem(i,j)}> 178 | {menuLabels[i][j]} 179 | 180 | ) 181 | } 182 | topMenuItems.push( 183 | 184 | {menuRow} 185 | 186 | ); 187 | } 188 | 189 | return topMenuItems; 190 | } 191 | 192 | render() { 193 | const profileImage = { 194 | width: 50, 195 | height: 50, 196 | borderRadius: 50 197 | } 198 | 199 | return ( 200 | 201 | 202 | 203 | 204 | 205 | CARMEN RIVERA 206 | 207 | this.closeTopMenu() }> 208 | 209 | 210 | 211 | 212 | {this.renderTopMenu()} 213 | 214 | LOGOUT 215 | 216 | 217 | 218 | 219 | 220 | 221 | this.openTopMenu() } > 222 | 223 | 224 | GALLERY 225 | 226 | 227 | 228 | 229 | 230 | {images.map( (image,index) => { 231 | return ( 232 | 233 | 234 | 235 | {image.text} 236 | 237 | 238 | 239 | 240 | 242 | {index + 1} 243 | 244 | 245 | 246 | ) 247 | } 248 | )} 249 | 250 | 251 | 252 | 253 | ); 254 | } 255 | } 256 | 257 | 258 | const styles = StyleSheet.create({ 259 | container: { 260 | flex: 1, 261 | backgroundColor: '#A76BFF' 262 | }, 263 | content: { 264 | flex: 5 265 | }, 266 | menuContentStyle: { 267 | flex: 4, justifyContent: 'space-around' 268 | }, 269 | menuProfileStyle: { 270 | flex: 1, 271 | flexDirection: 'row', 272 | alignItems: 'center' 273 | }, 274 | userProfileStyle: { 275 | flex: 1, 276 | flexDirection: 'row', 277 | justifyContent: 'flex-start', 278 | alignItems: 'center', 279 | marginLeft: 15 280 | }, 281 | userLabelStyle: { 282 | fontSize: 14, 283 | fontFamily: 'sans-serif-medium', 284 | color: 'white', 285 | marginLeft: 15 286 | }, 287 | logoutRowStyle: { 288 | flex: 1.5, 289 | flexDirection: 'row', 290 | justifyContent: 'flex-start', 291 | alignItems: 'center' 292 | }, 293 | logoutLabelStyle: { 294 | marginLeft: 35, 295 | fontSize: 14, 296 | fontFamily: 'sans-serif-medium', 297 | color: '#c4a1fc' 298 | }, 299 | mainContentStyle: { 300 | flex: 1, 301 | backgroundColor: '#DCE4E7', 302 | }, 303 | titleLabelStyle: { 304 | fontSize: 15, 305 | fontFamily: 'sans-serif-medium', 306 | color: 'black', 307 | fontWeight: 'bold' 308 | }, 309 | headerContainerStyle: { 310 | flex: 1 , 311 | flexDirection: 'row', 312 | justifyContent: 'space-between', 313 | alignItems: 'center', 314 | }, 315 | imageContainerStyle: { 316 | position: 'absolute', top: 60, bottom: 0, left: 55, right: 0, 317 | backgroundColor: '#FFFFFF', 318 | flexDirection: 'row', 319 | justifyContent: 'space-between', 320 | alignItems: 'flex-end', 321 | }, 322 | imageLabelStyle: { 323 | marginBottom: 20, 324 | marginLeft: 20, 325 | fontSize: 15, 326 | fontFamily: 'sans-serif-light', 327 | color: 'black', 328 | fontWeight: 'bold' 329 | }, 330 | imageStyle: { 331 | position: 'absolute', 332 | top: 0, 333 | left: 25, 334 | }, 335 | counterContainerStyle: { 336 | width: 40, 337 | height: 40, 338 | justifyContent: 'center', 339 | alignItems: 'center' 340 | }, 341 | counterLabelStyle: { 342 | fontSize: 15, 343 | fontFamily: 'sans-serif-medium', 344 | color: 'black' 345 | } 346 | }); 347 | -------------------------------------------------------------------------------- /App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App'; 3 | 4 | import renderer from 'react-test-renderer'; 5 | 6 | it('renders without crashing', () => { 7 | const rendered = renderer.create().toJSON(); 8 | expect(rendered).toBeTruthy(); 9 | }); 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Animated Menu Transition 2 | 3 | Concept for animated top-down menu transition implemented with React Native 4 | 5 | [Expo Demo](https://expo.io/@devilsanek/animated-menu) 6 | 7 | Inspired by: https://www.uplabs.com/posts/relate-ux-parallax-scrolling-sidemenu-1 8 | 9 | | Original design | Implementation | 10 | | ------------------------- |:-----------------------:| 11 | | ![Output sample](preview.gif)|![Output sample](implementation.gif) | 12 | 13 | 14 | ### Instructions 15 | 16 | - [Install NodeJS](https://nodejs.org/en/) 17 | - [Install and setup React Native](https://facebook.github.io/react-native/docs/getting-started.html) 18 | - Clone repository: `git clone https://github.com/NadiKuts/react-native-animated-menu.git` 19 | 20 | - Navigate to the created folder: `cd react-native-animated-menu` 21 | 22 | - To run on either iPhone or Android check this page: https://github.com/react-community/create-react-native-app#creating-an-app 23 | 24 | ### Demo 25 | 26 | Install Expo App on your [Android smartphone](https://play.google.com/store/apps/details?id=host.exp.exponent&referrer=www) or [iPhone](https://itunes.apple.com/app/apple-store/id982107779?ct=www&mt=8). 27 | 28 | Scan this QR-code with your Expo App. 29 | 30 | ![alt text](https://github.com/NadiKuts/react-native-animated-menu/blob/master/assets/qr-code.png) 31 | 32 | ... or go [here](https://expo.io/@devilsanek/animated-menu) and try it out! 33 | 34 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "animated-menu", 4 | "description": "Animated top-down menu transition", 5 | "slug": "animated-menu", 6 | "privacy": "public", 7 | "sdkVersion": "19.0.0", 8 | "version": "1.0.0", 9 | "orientation": "portrait", 10 | "primaryColor": "#cccccc", 11 | "icon": "./assets/images/main.png", 12 | "loading": { 13 | "icon": "./assets/icons/loading-icon.png", 14 | "hideExponentText": false 15 | }, 16 | "packagerOpts": { 17 | "assetExts": ["ttf", "mp4"] 18 | }, 19 | "ios": { 20 | "supportsTablet": true 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /assets/icons/app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/icons/app-icon.png -------------------------------------------------------------------------------- /assets/icons/loading-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/icons/loading-icon.png -------------------------------------------------------------------------------- /assets/images/1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/images/1.jpeg -------------------------------------------------------------------------------- /assets/images/2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/images/2.jpeg -------------------------------------------------------------------------------- /assets/images/3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/images/3.jpeg -------------------------------------------------------------------------------- /assets/images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/images/4.jpg -------------------------------------------------------------------------------- /assets/images/5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/images/5.jpeg -------------------------------------------------------------------------------- /assets/images/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/images/main.png -------------------------------------------------------------------------------- /assets/qr-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/assets/qr-code.png -------------------------------------------------------------------------------- /implementation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/implementation.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crnatest", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "react-native-scripts": "1.1.0", 7 | "jest-expo": "~19.0.0", 8 | "react-test-renderer": "16.0.0-alpha.12" 9 | }, 10 | "main": "./node_modules/react-native-scripts/build/bin/crna-entry.js", 11 | "scripts": { 12 | "start": "react-native-scripts start", 13 | "eject": "react-native-scripts eject", 14 | "android": "react-native-scripts android", 15 | "ios": "react-native-scripts ios", 16 | "test": "node node_modules/jest/bin/jest.js --watch" 17 | }, 18 | "jest": { 19 | "preset": "jest-expo" 20 | }, 21 | "dependencies": { 22 | "expo": "^19.0.0", 23 | "react": "16.0.0-alpha.12", 24 | "react-native": "^0.46.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NadiKuts/react-native-animated-menu/24ef0622801c6a213aee7bf852e1f005da13b763/preview.gif --------------------------------------------------------------------------------