├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── README.md ├── android ├── .npmignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── rnimmersivebars │ ├── ImmersiveBars.java │ ├── ImmersiveBarsModule.java │ └── ImmersiveBarsPackage.java ├── index.android.js ├── index.d.ts ├── index.js ├── package.json ├── promo ├── android_10.gif └── android_m.gif └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [crutchcorn] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | gradle/ 3 | android/.gradle/ 4 | gradlew 5 | gradlew.bat 6 | local.properties 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | *.iml 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules 37 | jspm_packages 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | .idea 45 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | Screenshots 3 | .gif 4 | *.iml 5 | # OSX 6 | # 7 | .DS_Store 8 | # Xcode 9 | # 10 | build/ 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata 20 | *.xccheckout 21 | *.moved-aside 22 | DerivedData 23 | *.hmap 24 | *.ipa 25 | *.xcuserstate 26 | 27 | # node.js 28 | # 29 | node_modules/ 30 | npm-debug.log 31 | examples/ 32 | screenshot/ 33 | *.md 34 | promo/ 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | React Native Immersive Bars 3 |

4 | 5 |
6 | 7 | [![Our npm path](https://badgen.net/npm/v/react-native-immersive-bars)](https://www.npmjs.com/package/react-native-immersive-bars/) 8 | 9 |
10 | 11 |
12 | 13 | A preview in Android 10 14 | 15 | A preview in Android M 16 | 17 |
18 | 19 | > Do you like the button slider that changes from light to dark mode? [That's another package that I've built: `react-native-button-toggle-group`](https://github.com/crutchcorn/react-native-button-toggle-group) 20 | 21 | [Google officially suggests apps that target Q or higher to render content under the statusbar and navbar](http://youtube.com/watch?v=Nf-fP2u9vjI). 22 | However, [doing so previously was difficult to do and required some customization and tweaking](https://unicorn-utterances.com/posts/draw-under-navbar-using-react-native/). Not to mention the variant support for other APIs. 23 | 24 | Well, this package enables you to easily do so across various Android APIs levels. It also mocks out usage with iOS and other platforms, so you're able to use it in cross-platform apps. 25 | 26 | > ⚠ This package only supports Android 5.0 and higher ⚠ 27 | > 28 | > This package is meant to be used alongside [the `react-native-safe-area-context` package](https://github.com/th3rdwave/react-native-safe-area-context) in order 29 | > to provide proper padding and margins in order to keep scrollviews and others from being stuck forever under the navbar. 30 | > 31 | > Please refer to that package's README for more information on how to use it. 32 | 33 | ## Install 34 | 35 | ``` 36 | npm i --save react-native-immersive-bars 37 | ``` 38 | 39 | Or 40 | 41 | ``` 42 | yarn add react-native-immersive-bars 43 | ``` 44 | 45 | ### Android 46 | 47 | If you're targeting API 29+ in your React Native app, you need to do one more step to enable the fully transparent bars. Add: 48 | 49 | ```xml 50 | false 51 | ``` 52 | 53 | To: 54 | 55 | ``` 56 | android\app\src\main\res\values\styles.xml 57 | ``` 58 | 59 | ## Usage 60 | 61 | ### Usage in JavaScript 62 | 63 | ```jsx 64 | import {changeBarColors} from 'react-native-immersive-bars'; 65 | 66 | // ... 67 | 68 | React.useEffect(() => { 69 | changeBarColors(isDarkMode, '#50000000', 'transparent'); 70 | // or changeBarColors(isDarkMode); 71 | }, [isDarkMode]); 72 | ``` 73 | 74 | The `changeBarColors` function has a single required parameter and two optional ones. 75 | 76 | - `isDarkMode` (Required): If the app is in dark mode or not - will apply proper styling to icons and statusbar/navbar background 77 | - `translucentLightStr` (Optional): When a translucent bar must be drawn (due to API restrictions), what color it should be drawn in light mode 78 | - `translucentDarkStr` (Optional): When a translucent bar must be drawn (due to API restrictions), what color it should be drawn in dark mode 79 | 80 | Both `translucentLightStr` and `translucentDarkStr` accept and color that [Color.parseColor](https://developer.android.com/reference/android/graphics/Color#parseColor(java.lang.String)) is able to handle as well as the string `'transparent'`. 81 | 82 | > THIS MEANS THAT THREE DIGIT HEX SHORTHAND LIKE `#FFF` WILL CAUSE YOUR APP TO CRASH 83 | 84 | ### Adding to `onCreate` (Optional) 85 | 86 | If you only use the JavaScript code, your app will flash the navbar once the App.tsx code finally renders. 87 | If you want to avoid a jump like that, you can edit your code in: 88 | 89 | ``` 90 | android > app > src > main > java > yourpackagepath > MainActivity.java 91 | ``` 92 | 93 | And change the code to reflect this: 94 | 95 | ```java 96 | import com.facebook.react.ReactActivity; 97 | import com.rnimmersivebars.ImmersiveBars; // here 98 | 99 | public class MainActivity extends ReactActivity { 100 | @Override 101 | protected void onCreate(Bundle savedInstanceState) { 102 | boolean isDarkMode = false; // Customize this to match your app's default theme 103 | ImmersiveBars.changeBarColors(this, isDarkMode); // here 104 | super.onCreate(savedInstanceState); 105 | } 106 | // ...other code 107 | } 108 | ``` 109 | 110 | ## Alternatives 111 | 112 | If you don't need to use a fullscreen navbar, then you can simply change the color of the navbar itself with this package: 113 | 114 | - [`react-native-navigation-bar-color`](https://github.com/thebylito/react-native-navigation-bar-color) 115 | 116 | Note that this package does not play nice with [`react-native-safe-area-context`'s edge detection](https://github.com/th3rdwave/react-native-safe-area-context/). 117 | 118 | 119 | Otherwise, if you want to hide the navbar and the statusbar in their entirety, I'd suggest taking a look at the following package: 120 | 121 | - [`react-native-immersive`](https://github.com/mockingbot/react-native-immersive) 122 | -------------------------------------------------------------------------------- /android/.npmignore: -------------------------------------------------------------------------------- 1 | /build 2 | gradle/ 3 | gradlew 4 | gradlew.bat 5 | local.properties 6 | proguard-rules.pro -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | def getExtOrDefault(name, defaultValue) { 2 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : defaultValue 3 | } 4 | 5 | buildscript { 6 | repositories { 7 | jcenter() 8 | google() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.1.0' 13 | } 14 | } 15 | 16 | apply plugin: 'com.android.library' 17 | 18 | android { 19 | compileSdkVersion getExtOrDefault('compileSdkVersion', 29) 20 | 21 | defaultConfig { 22 | minSdkVersion getExtOrDefault('minSdkVersion', 21) 23 | targetSdkVersion getExtOrDefault('targetSdkVersion', 29) 24 | versionCode 1 25 | versionName "1.0" 26 | } 27 | lintOptions { 28 | abortOnError false 29 | } 30 | } 31 | 32 | repositories { 33 | mavenCentral() 34 | mavenLocal() 35 | google() 36 | } 37 | 38 | dependencies { 39 | api 'com.facebook.react:react-native:+' 40 | } -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivebars/ImmersiveBars.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivebars; 2 | 3 | import android.os.Build; 4 | import android.graphics.Color; 5 | import android.view.View; 6 | import android.view.Window; 7 | import android.app.Activity; 8 | 9 | public class ImmersiveBars { 10 | /** 11 | * Allowing usage of lambda for Promise replacement within the file 12 | */ 13 | interface ChangeColorsCallback 14 | { 15 | void finished(boolean worked); 16 | } 17 | 18 | /** 19 | * For usage in the onCreate method 20 | */ 21 | public static void changeBarColors(final Activity activity, final Boolean isDarkMode) { 22 | changeBarColors(activity, isDarkMode, "", ""); 23 | } 24 | 25 | /** 26 | * For usage in the React Module 27 | */ 28 | public static void changeBarColors(final Activity activity, final Boolean isDarkMode, final String translucentLightStr, final String translucentDarkStr) { 29 | if (activity == null) { 30 | return; 31 | } 32 | final Window window = activity.getWindow(); 33 | activity.runOnUiThread(new Runnable() { 34 | @Override 35 | public void run() { 36 | /** 37 | * Handle the color setting 38 | */ 39 | int translucentLightColor; 40 | if (translucentLightStr.isEmpty()) { 41 | translucentLightColor = Color.parseColor("#50000000"); 42 | } else if (translucentLightStr.equals("transparent")) { 43 | translucentLightColor = Color.TRANSPARENT; 44 | } else { 45 | translucentLightColor = Color.parseColor(translucentLightStr); 46 | } 47 | 48 | int translucentDarkColor; 49 | if (translucentDarkStr.isEmpty() || translucentDarkStr.equals("transparent")) { 50 | translucentDarkColor = Color.TRANSPARENT; 51 | } else { 52 | translucentDarkColor = Color.parseColor(translucentDarkStr); 53 | } 54 | 55 | // Set the navbar to be drawn over 56 | // Both flags were added in Level 16 57 | int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 58 | 59 | // M was the first version that supported light mode status bar 60 | boolean shouldUseTransparentStatusBar = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; 61 | // O was the first version that supported light mode nav bar 62 | boolean shouldUseTransparentNavBar = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; 63 | 64 | if (shouldUseTransparentStatusBar) { 65 | window.setStatusBarColor(Color.TRANSPARENT); 66 | if (!isDarkMode) { 67 | flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; 68 | } 69 | } else { 70 | window.setStatusBarColor(isDarkMode ? translucentDarkColor : translucentLightColor); 71 | } 72 | 73 | if (shouldUseTransparentNavBar) { 74 | window.setNavigationBarColor(Color.TRANSPARENT); 75 | if (!isDarkMode) { 76 | flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; 77 | } 78 | } else { 79 | window.setNavigationBarColor(isDarkMode ? translucentDarkColor: translucentLightColor); 80 | } 81 | 82 | window.getDecorView().setSystemUiVisibility(flags); 83 | } 84 | }); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivebars/ImmersiveBarsModule.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivebars; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.ReactApplicationContext; 5 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 6 | import com.facebook.react.bridge.ReactMethod; 7 | import com.facebook.react.bridge.Promise; 8 | import com.facebook.react.bridge.WritableMap; 9 | import com.facebook.react.uimanager.IllegalViewOperationException; 10 | 11 | public class ImmersiveBarsModule extends ReactContextBaseJavaModule { 12 | private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY"; 13 | private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity"; 14 | 15 | public ImmersiveBarsModule(ReactApplicationContext reactContext) { 16 | super(reactContext); 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return "ImmersiveBars"; 22 | } 23 | 24 | @ReactMethod 25 | public void changeBarColors(final Boolean isDarkMode, Promise promise) { 26 | ImmersiveBars.changeBarColors( 27 | getCurrentActivity(), 28 | isDarkMode, 29 | "", 30 | "" 31 | ); 32 | } 33 | 34 | @ReactMethod 35 | public void changeBarColors(final Boolean isDarkMode, final String translucentLightStr, final String translucentDarkStr) { 36 | ImmersiveBars.changeBarColors( 37 | getCurrentActivity(), 38 | isDarkMode, 39 | translucentLightStr, 40 | translucentDarkStr 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnimmersivebars/ImmersiveBarsPackage.java: -------------------------------------------------------------------------------- 1 | package com.rnimmersivebars; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class ImmersiveBarsPackage implements ReactPackage { 13 | 14 | // Deprecated RN 0.47 15 | public List> createJSModules() { 16 | return Collections.emptyList(); 17 | } 18 | 19 | @Override 20 | public List createViewManagers(ReactApplicationContext reactContext) { 21 | return Collections.emptyList(); 22 | } 23 | 24 | @Override 25 | public List createNativeModules( 26 | ReactApplicationContext reactContext) { 27 | List modules = new ArrayList<>(); 28 | modules.add(new ImmersiveBarsModule(reactContext)); 29 | return modules; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from "react-native"; 2 | 3 | const { ImmersiveBars } = NativeModules; 4 | 5 | const changeBarColors = ( 6 | isDarkMode = false, 7 | translucentLightStr = "", 8 | translucentDarkStr = "", 9 | ) => { 10 | ImmersiveBars.changeBarColors(isDarkMode, translucentLightStr, translucentDarkStr); 11 | }; 12 | 13 | export { changeBarColors }; 14 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-native-immersive-bars" { 2 | export function changeBarColors( 3 | isDarkMode: boolean, 4 | translucentLightStr?: string, 5 | translucentDarkStr?: string 6 | ): void; 7 | } 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const changeBarColors = () => {}; 2 | 3 | export { changeBarColors }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-immersive-bars", 3 | "version": "1.0.4", 4 | "description": "React Native component to change bottom bar/navigation bar to be transparent on Android", 5 | "mainNote": "Do not add .js extension. It breaks the ability for metrto to detect platforms", 6 | "main": "index", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/crutchcorn/react-native-immersive-bars.git" 13 | }, 14 | "keywords": [ 15 | "react-native", 16 | "react-native-component", 17 | "react-native-immersive-bars", 18 | "immersive-mode", 19 | "transparent-navbar", 20 | "transparent-statusbar", 21 | "android" 22 | ], 23 | "author": "crutchcorn", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/crutchcorn/react-native-immersive-bars/issues" 27 | }, 28 | "peerDependencies": { 29 | "react-native": ">=0.57.0" 30 | }, 31 | "homepage": "https://github.com/crutchcorn/react-native-immersive-bars#readme" 32 | } 33 | -------------------------------------------------------------------------------- /promo/android_10.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/react-native-immersive-bars/4358f5db97d68cee83b4f79d53d2da25d2b064b2/promo/android_10.gif -------------------------------------------------------------------------------- /promo/android_m.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oceanbit/react-native-immersive-bars/4358f5db97d68cee83b4f79d53d2da25d2b064b2/promo/android_m.gif -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | --------------------------------------------------------------------------------