├── .github
└── demo.gif
├── assets
├── icon.png
├── favicon.png
├── splash.png
└── adaptive-icon.png
├── src
├── assets
│ └── map.jpg
├── screens
│ └── Home.tsx
└── components
│ └── FloatingButton.tsx
├── tsconfig.json
├── babel.config.js
├── .gitignore
├── App.tsx
├── package.json
├── app.json
└── README.md
/.github/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/.github/demo.gif
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/assets/favicon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/src/assets/map.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/src/assets/map.jpg
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | plugins: ['react-native-reanimated/plugin'],
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
16 | # Temporary files created by Metro to check the health of the file watcher
17 | .metro-health-check*
18 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import { StatusBar } from 'expo-status-bar';
2 | import { StyleSheet, View } from 'react-native';
3 |
4 | import { Home } from './src/screens/Home';
5 |
6 | export default function App() {
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | const styles = StyleSheet.create({
16 | container: {
17 | flex: 1,
18 | alignItems: 'center',
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rn-fab-button",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "expo": "~48.0.15",
13 | "expo-status-bar": "~1.4.4",
14 | "react": "18.2.0",
15 | "react-native": "0.71.7",
16 | "react-native-reanimated": "~2.14.4"
17 | },
18 | "devDependencies": {
19 | "@babel/core": "^7.20.0",
20 | "@types/react": "~18.0.14",
21 | "typescript": "^4.9.4"
22 | },
23 | "private": true
24 | }
25 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "rn-fab-button",
4 | "slug": "rn-fab-button",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "assetBundlePatterns": [
15 | "**/*"
16 | ],
17 | "ios": {
18 | "supportsTablet": true
19 | },
20 | "android": {
21 | "adaptiveIcon": {
22 | "foregroundImage": "./assets/adaptive-icon.png",
23 | "backgroundColor": "#ffffff"
24 | }
25 | },
26 | "web": {
27 | "favicon": "./assets/favicon.png"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/screens/Home.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { ImageBackground, StyleSheet } from 'react-native';
3 | import Animated, {
4 | useAnimatedStyle,
5 | withTiming,
6 | } from 'react-native-reanimated';
7 |
8 | import { FloatingButton } from '../components/FloatingButton';
9 |
10 | const AnimatedImageBackground =
11 | Animated.createAnimatedComponent(ImageBackground);
12 |
13 | export function Home() {
14 | const [modalOpen, setModalOpen] = useState(false);
15 |
16 | const animatedStyle = useAnimatedStyle(() => {
17 | return {
18 | opacity: withTiming(modalOpen ? 0.25 : 0.5),
19 | };
20 | });
21 |
22 | function onPress() {
23 | setModalOpen((curr) => !curr);
24 | }
25 |
26 | return (
27 | <>
28 |
33 |
34 | >
35 | );
36 | }
37 | const styles = StyleSheet.create({
38 | image: {
39 | flex: 1,
40 | width: '100%',
41 | height: '100%',
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Native-Fab-Button
2 |
3 |
4 | A simple animated floating action button screen Built with React Native, Expo and Reanimated.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Features |
19 | Technologies |
20 | Installation
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | # :star: Features
30 |
31 | [(Back to top)](#React-Native-Fab-Button)
32 |
33 | Some key features are:
34 |
35 | - Customizable buttons
36 | - Smooth animations
37 |
38 | The application is built using React Native with Expo framework.
39 | The entire codebase is written using Typescript.
40 |
41 |
42 |
43 | # :keyboard: Technologies
44 |
45 | [(Back to top)](#React-Native-Fab-Button)
46 |
47 | This is what I used and learned with this project:
48 |
49 | - [x] React Native
50 | - [x] Expo
51 | - [x] Reanimated
52 | - [x] Typescript
53 |
54 |
55 |
56 | # :computer_mouse: Installation
57 |
58 | [(Back to top)](#React-Native-Fab-Button)
59 |
60 | To use this project, first you need NodeJS installed in your device,
61 | then you can follow the commands below:
62 |
63 | ```bash
64 | # Clone this repository
65 | git clone https://github.com/areasflavio/react-native-fab-button.git
66 |
67 | # Go into the repository
68 | cd react-native-fab-button
69 |
70 | # Install dependencies for the application
71 | yarn install
72 |
73 | # To start the development server, run the following command
74 | npm run start
75 |
76 | # You start the emulator following the terminal instructions or:
77 | npm run android # for android emulator
78 |
79 | npm run ios # for ios emulator
80 | ```
81 |
82 | # :man_technologist: Author
83 |
84 | [(Back to top)](#React-Native-Fab-Button)
85 |
86 | Build by Flávio Arêas 👋 [Get in touch!](https://www.linkedin.com/in/areasflavio/)
87 |
--------------------------------------------------------------------------------
/src/components/FloatingButton.tsx:
--------------------------------------------------------------------------------
1 | import { Entypo } from '@expo/vector-icons';
2 | import React, { useState } from 'react';
3 | import { StyleSheet, TouchableOpacity, View, ViewProps } from 'react-native';
4 | import Animated, {
5 | Extrapolate,
6 | interpolate,
7 | useAnimatedStyle,
8 | useSharedValue,
9 | withSpring,
10 | } from 'react-native-reanimated';
11 |
12 | interface FloatingButtonProps extends ViewProps {
13 | onPress: () => void;
14 | }
15 |
16 | export function FloatingButton({
17 | onPress,
18 | style,
19 | ...rest
20 | }: FloatingButtonProps) {
21 | const [isOpen, setIsOpen] = useState(false);
22 | const animation = useSharedValue(0);
23 |
24 | const rotationAnimatedStyle = useAnimatedStyle(() => {
25 | return {
26 | transform: [
27 | {
28 | rotate: withSpring(isOpen ? '45deg' : '0deg'),
29 | },
30 | ],
31 | };
32 | });
33 |
34 | const pinAnimatedStyle = useAnimatedStyle(() => {
35 | const translateYAnimation = interpolate(
36 | animation.value,
37 | [0, 1],
38 | [0, -20],
39 | Extrapolate.CLAMP
40 | );
41 |
42 | return {
43 | transform: [
44 | {
45 | scale: withSpring(animation.value),
46 | },
47 | {
48 | translateY: withSpring(translateYAnimation),
49 | },
50 | ],
51 | };
52 | });
53 |
54 | const thumbAnimatedStyle = useAnimatedStyle(() => {
55 | const translateYAnimation = interpolate(
56 | animation.value,
57 | [0, 1],
58 | [0, -30],
59 | Extrapolate.CLAMP
60 | );
61 |
62 | return {
63 | transform: [
64 | {
65 | scale: withSpring(animation.value),
66 | },
67 | {
68 | translateY: withSpring(translateYAnimation),
69 | },
70 | ],
71 | };
72 | });
73 |
74 | const heartAnimatedStyle = useAnimatedStyle(() => {
75 | const translateYAnimation = interpolate(
76 | animation.value,
77 | [0, 1],
78 | [0, -40],
79 | Extrapolate.CLAMP
80 | );
81 |
82 | return {
83 | transform: [
84 | {
85 | scale: withSpring(animation.value),
86 | },
87 | {
88 | translateY: withSpring(translateYAnimation),
89 | },
90 | ],
91 | };
92 | });
93 |
94 | const opacityAnimatedStyle = useAnimatedStyle(() => {
95 | const opacityAnimation = interpolate(
96 | animation.value,
97 | [0, 0.5, 1],
98 | [0, 0, 1],
99 | Extrapolate.CLAMP
100 | );
101 |
102 | return {
103 | opacity: withSpring(opacityAnimation),
104 | };
105 | });
106 |
107 | function toggleMenu() {
108 | onPress();
109 | setIsOpen((current) => {
110 | animation.value = current ? 0 : 1;
111 | return !current;
112 | });
113 | }
114 |
115 | return (
116 |
117 |
118 |
126 |
127 |
128 |
129 |
130 |
131 |
139 |
140 |
141 |
142 |
143 |
144 |
152 |
153 |
154 |
155 |
156 |
157 |
160 |
161 |
162 |
163 |
164 | );
165 | }
166 |
167 | const styles = StyleSheet.create({
168 | container: {
169 | alignItems: 'center',
170 | position: 'absolute',
171 | justifyContent: 'center',
172 | },
173 | button: {
174 | width: 60,
175 | height: 60,
176 | borderRadius: 30,
177 | alignItems: 'center',
178 | justifyContent: 'center',
179 | shadowRadius: 10,
180 | shadowColor: '#f02a4b',
181 | shadowOpacity: 0.3,
182 | shadowOffset: { height: 10, width: 10 },
183 | elevation: 10,
184 | },
185 | menu: {
186 | backgroundColor: '#f02a4b',
187 | },
188 | secondary: {
189 | width: 48,
190 | height: 48,
191 | borderRadius: 24,
192 | backgroundColor: '#ffffff',
193 | },
194 | });
195 |
--------------------------------------------------------------------------------