├── .watchmanconfig ├── src ├── tableTop.png ├── button.js ├── graphic.js ├── introContent.js └── inputOverlay.js ├── .babelrc ├── README.md ├── App.test.js ├── app.json ├── .gitignore ├── package.json ├── App.js └── .flowconfig /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /src/tableTop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atticoos/react-native-todo-empty-state-transition/HEAD/src/tableTop.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"], 3 | "env": { 4 | "development": { 5 | "plugins": ["transform-react-jsx-source"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dribble Inspired Todo Empty State UI+UX 2 | 3 | https://dribbble.com/shots/4282122-Empty-State 4 | 5 | https://expo.io/@atticoos/todoemptystate 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Todo Empty State Transition", 4 | "slug": "todoemptystate", 5 | "privacy": "public", 6 | "description": "An inspired UI+UX from https://dribbble.com/shots/4282122-Empty-State", 7 | "sdkVersion": "25.0.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # expo 4 | .expo/ 5 | 6 | # dependencies 7 | /node_modules 8 | 9 | # misc 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | -------------------------------------------------------------------------------- /src/button.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import glamorous from 'glamorous-native' 3 | 4 | export default function Button ({children, ...props}) { 5 | return ( 6 | 7 | {children} 8 | 9 | ) 10 | } 11 | 12 | const Touchable = glamorous.touchableHighlight({ 13 | backgroundColor: 'rgb(253, 183, 58)', 14 | paddingVertical: 10, 15 | paddingHorizontal: 20, 16 | borderRadius: 6 17 | }); 18 | Touchable.propsAreStyleOverrides = true; 19 | 20 | const Text = glamorous.text({ 21 | color: '#fff', 22 | fontSize: 16 23 | }); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TodoEmptyState", 3 | "version": "0.1.0", 4 | "private": true, 5 | "devDependencies": { 6 | "jest-expo": "25.0.0", 7 | "react-native-scripts": "1.11.1", 8 | "react-test-renderer": "16.2.0" 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" 17 | }, 18 | "jest": { 19 | "preset": "jest-expo" 20 | }, 21 | "dependencies": { 22 | "expo": "^25.0.0", 23 | "glamorous-native": "^1.3.0", 24 | "react": "16.2.0", 25 | "react-native": "0.52.0", 26 | "recompose": "^0.26.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/graphic.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import glamorous from 'glamorous-native' 3 | import {Animated} from 'react-native' 4 | import {withProps} from 'recompose'; 5 | 6 | export default function Graphic (props) { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | const Transition = glamorous(Animated.View)( 15 | { 16 | position: 'absolute', 17 | top: 120, 18 | }, 19 | ({transition}) => ({ 20 | transform: [ 21 | // {perspective: 1000}, 22 | { 23 | translateY: transition.interpolate({ 24 | inputRange: [0, 1], 25 | outputRange: [0, -20] 26 | }) 27 | }, 28 | { 29 | scale: transition.interpolate({ 30 | inputRange: [0, 1], 31 | outputRange: [1, 6] 32 | }) 33 | }, 34 | {rotate: transition.interpolate({ 35 | inputRange: [0, 1], 36 | outputRange: ['0deg', '-20deg'] 37 | }) 38 | } 39 | ] 40 | }) 41 | ); 42 | 43 | const TableTop = withProps({ 44 | source: require('./tableTop.png'), 45 | resizeMode: 'contain' 46 | })(glamorous.image({ 47 | width: 215, 48 | height: 200 49 | })); 50 | -------------------------------------------------------------------------------- /src/introContent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import glamorous from 'glamorous-native' 3 | import {Animated} from 'react-native' 4 | import Button from './button'; 5 | 6 | export default function IntroContent ({onAddNote, ...props}) { 7 | return ( 8 | 9 | Add your first note 10 | 11 | 12 | Relax and write something{`\n`}beautiful 13 | 14 | 15 | 18 | 19 | 20 | UI by @nimasha_perera{`\n`}UX by Edoardo Mercati 21 | 22 | 23 | ) 24 | } 25 | 26 | const Transition = glamorous(Animated.View)( 27 | { 28 | position: 'absolute', 29 | alignItems: 'center', 30 | top: 350 31 | }, 32 | ({transition}) => ({ 33 | transform: [ 34 | { 35 | translateY: transition.interpolate({ 36 | inputRange: [0, 1], 37 | outputRange: [0, 600] 38 | }) 39 | } 40 | ] 41 | }) 42 | ) 43 | 44 | const PrimaryText = glamorous.text({ 45 | fontSize: 16, 46 | color: '#0f0f0f', 47 | fontWeight: 'bold', 48 | marginVertical: 6 49 | }); 50 | 51 | const SecondaryText = glamorous.text({ 52 | fontSize: 16, 53 | lineHeight: 24, 54 | textAlign: 'center', 55 | color: 'lightgray' 56 | }) 57 | SecondaryText.propsAreStyleOverrides = true; 58 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Animated, Easing, StyleSheet, Text, TextInput, View } from 'react-native'; 3 | import InputOverlay from './src/inputOverlay'; 4 | import Graphic from './src/graphic'; 5 | import IntroContent from './src/introContent'; 6 | 7 | export default class App extends React.Component { 8 | state = { 9 | transition: new Animated.Value(0), 10 | inputActive: false 11 | } 12 | 13 | transitionToInput() { 14 | this.setState({inputActive: true}) 15 | Animated.timing( 16 | this.state.transition, 17 | { 18 | toValue: 1, 19 | easing: Easing.in(Easing.exp), 20 | userNativeDriver: true 21 | } 22 | ).start(); 23 | } 24 | 25 | transitionToSplash() { 26 | Animated.timing( 27 | this.state.transition, 28 | {toValue: 0, userNativeDriver: true} 29 | ).start(() => this.setState({inputActive: false})); 30 | } 31 | 32 | render() { 33 | return ( 34 | 35 | 38 | 39 | this.transitionToInput()} 42 | /> 43 | 44 | {this.state.inputActive && ( 45 | this.transitionToSplash()} 49 | /> 50 | )} 51 | 52 | ); 53 | } 54 | } 55 | 56 | const styles = StyleSheet.create({ 57 | container: { 58 | flex: 1, 59 | paddingTop: 40, 60 | backgroundColor: '#fff', 61 | alignItems: 'center' 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /src/inputOverlay.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import glamorous from 'glamorous-native' 3 | import {Animated, Keyboard} from 'react-native' 4 | 5 | export default function InputOverlay ({onDismiss, ...props}) { 6 | return ( 7 | 8 | 9 | H1 10 | 11 | { 12 | Keyboard.dismiss(); 13 | onDismiss(); 14 | }} /> 15 | 16 | 17 | ) 18 | } 19 | 20 | function Dismiss (props) { 21 | return ( 22 | 34 | 35 | X 36 | 37 | 38 | ) 39 | } 40 | 41 | const TextInput = glamorous.textInput({ 42 | color: 'gray', 43 | flex: 1, 44 | marginHorizontal: 5, 45 | fontSize: 22 46 | }) 47 | 48 | const TransitionContainer = glamorous(Animated.View)( 49 | { 50 | position: 'absolute', 51 | backgroundColor: '#fff', 52 | paddingTop: 60, 53 | top: 0, 54 | left: 0, 55 | right: 0, 56 | bottom: 0, 57 | paddingHorizontal: 30 58 | }, 59 | ({transition, enter}) => ({ 60 | opacity: transition.interpolate({ 61 | inputRange: [0, enter, 1], 62 | outputRange: [0, 0, 1] 63 | }), 64 | transform: [ 65 | { 66 | translateY: transition.interpolate({ 67 | inputRange: [0, enter, 1], 68 | outputRange: [35, 35, 0] 69 | }) 70 | }, 71 | { 72 | rotate: transition.interpolate({ 73 | inputRange: [0, 1], 74 | outputRange: ['25deg', '0deg'] 75 | }) 76 | } 77 | ] 78 | }) 79 | ); 80 | 81 | const Row = glamorous.view({ 82 | flexDirection: 'row' 83 | }) 84 | 85 | const InputType = glamorous.text({ 86 | fontSize: 16, 87 | marginTop: 2, 88 | color: 'lightgray' 89 | }) 90 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore templates for 'react-native init' 6 | /node_modules/react-native/local-cli/templates/.* 7 | 8 | ; Ignore RN jest 9 | /node_modules/react-native/jest/.* 10 | 11 | ; Ignore RNTester 12 | /node_modules/react-native/RNTester/.* 13 | 14 | ; Ignore the website subdir 15 | /node_modules/react-native/website/.* 16 | 17 | ; Ignore the Dangerfile 18 | /node_modules/react-native/danger/dangerfile.js 19 | 20 | ; Ignore Fbemitter 21 | /node_modules/fbemitter/.* 22 | 23 | ; Ignore "BUCK" generated dirs 24 | /node_modules/react-native/\.buckd/ 25 | 26 | ; Ignore unexpected extra "@providesModule" 27 | .*/node_modules/.*/node_modules/fbjs/.* 28 | 29 | ; Ignore polyfills 30 | /node_modules/react-native/Libraries/polyfills/.* 31 | 32 | ; Ignore various node_modules 33 | /node_modules/react-native-gesture-handler/.* 34 | /node_modules/expo/.* 35 | /node_modules/react-navigation/.* 36 | /node_modules/xdl/.* 37 | /node_modules/reqwest/.* 38 | /node_modules/metro-bundler/.* 39 | 40 | [include] 41 | 42 | [libs] 43 | node_modules/react-native/Libraries/react-native/react-native-interface.js 44 | node_modules/react-native/flow/ 45 | node_modules/expo/flow/ 46 | 47 | [options] 48 | emoji=true 49 | 50 | module.system=haste 51 | 52 | module.file_ext=.js 53 | module.file_ext=.jsx 54 | module.file_ext=.json 55 | module.file_ext=.ios.js 56 | 57 | munge_underscores=true 58 | 59 | 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' 60 | 61 | suppress_type=$FlowIssue 62 | suppress_type=$FlowFixMe 63 | suppress_type=$FlowFixMeProps 64 | suppress_type=$FlowFixMeState 65 | suppress_type=$FixMe 66 | 67 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\) 68 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native_oss[a-z,_]*\\)?)\\)?:? #[0-9]+ 69 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 70 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 71 | 72 | unsafe.enable_getters_and_setters=true 73 | 74 | [version] 75 | ^0.56.0 76 | --------------------------------------------------------------------------------