Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
import type * as React from 'react';
import {ColorValue} from '../../StyleSheet/StyleSheet';

export type StatusBarStyle = 'default' | 'light-content' | 'dark-content';
export type StatusBarStyle =
| 'default'
| 'auto'
| 'light-content'
| 'dark-content';

export type StatusBarAnimation = 'none' | 'fade' | 'slide';

Expand Down
64 changes: 58 additions & 6 deletions packages/react-native/Libraries/Components/StatusBar/StatusBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
*/

import type {ColorValue} from '../../StyleSheet/StyleSheet';
import type {EventSubscription} from '../../vendor/emitter/EventEmitter';

import processColor from '../../StyleSheet/processColor';
import * as Appearance from '../../Utilities/Appearance';
import Platform from '../../Utilities/Platform';
import NativeStatusBarManagerAndroid from './NativeStatusBarManagerAndroid';
import NativeStatusBarManagerIOS from './NativeStatusBarManagerIOS';
Expand All @@ -25,6 +27,11 @@ export type StatusBarStyle = keyof {
* Default status bar style (dark for iOS, light for Android)
*/
default: string,
/**
* Automatically picks `light-content` or `dark-content` based on the current
* color scheme. Updates whenever the color scheme changes.
*/
auto: string,
/**
* Dark background, white texts and icons
*/
Expand Down Expand Up @@ -105,7 +112,7 @@ type StatusBarBaseProps = Readonly<{
/**
* Sets the color of the status bar text.
*/
barStyle?: ?('default' | 'light-content' | 'dark-content'),
barStyle?: ?('default' | 'auto' | 'light-content' | 'dark-content'),
}>;

export type StatusBarProps = Readonly<{
Expand Down Expand Up @@ -133,13 +140,24 @@ type StackProps = {
};

/**
* Merges the prop stack with the default values.
* Returns the bar style to use when `barStyle` is `'auto'`, picked against
* the current color scheme.
*/
function getAutoBarStyle(): 'light-content' | 'dark-content' {
return Appearance.getColorScheme() === 'dark'
? 'light-content'
: 'dark-content';
}

/**
* Merges the prop stack with the default values, resolving the `'auto'`
* barStyle to a concrete value.
*/
function mergePropsStack(
propsStack: Array<Object>,
defaultValues: Object,
): Object {
return propsStack.reduce(
const merged: StackProps = propsStack.reduce(
(prev, cur) => {
for (const prop in cur) {
if (cur[prop] != null) {
Expand All @@ -150,6 +168,12 @@ function mergePropsStack(
},
{...defaultValues},
);

if (merged.barStyle?.value === 'auto') {
merged.barStyle = {...merged.barStyle, value: getAutoBarStyle()};
}

return merged;
}

/**
Expand Down Expand Up @@ -247,9 +271,16 @@ class StatusBar extends React.Component<StatusBarProps> {
// Timer for updating the native module values at the end of the frame.
static _updateImmediate: ?number = null;

// The current merged values from the props stack.
// The current merged values from the props stack. `barStyle.value` is stored
// in its resolved form (never `'auto'`), so diff comparisons reflect what
// was actually sent to the native module.
static _currentValues: ?StackProps = null;

// Number of mounted `StatusBar` instances. Used to lazily subscribe to color
// scheme changes only while at least one instance is on screen.
static _mountedCount: number = 0;
static _appearanceSubscription: ?EventSubscription = null;

// TODO(janic): Provide a real API to deal with status bar height. See the
// discussion in #6195.
/**
Expand Down Expand Up @@ -289,10 +320,11 @@ class StatusBar extends React.Component<StatusBarProps> {
static setBarStyle(style: StatusBarStyle, animated?: boolean) {
animated = animated || false;
StatusBar._defaultProps.barStyle.value = style;
const resolvedStyle = style === 'auto' ? getAutoBarStyle() : style;
if (Platform.OS === 'ios') {
NativeStatusBarManagerIOS.setStyle(style, animated);
NativeStatusBarManagerIOS.setStyle(resolvedStyle, animated);
} else if (Platform.OS === 'android') {
NativeStatusBarManagerAndroid.setStyle(style);
NativeStatusBarManagerAndroid.setStyle(resolvedStyle);
}
}

Expand Down Expand Up @@ -407,6 +439,16 @@ class StatusBar extends React.Component<StatusBarProps> {
// stack. This allows having multiple StatusBar components and the one that is
// added last or is deeper in the view hierarchy will have priority.
this._stackEntry = StatusBar.pushStackEntry(this.props);

if (StatusBar._mountedCount === 0) {
// Re-run the native update when the system color scheme changes so any
// `barStyle: 'auto'` entries resolve to the new appropriate value.
StatusBar._appearanceSubscription = Appearance.addChangeListener(() => {
StatusBar._updatePropsStack();
});
}

StatusBar._mountedCount++;
}

componentWillUnmount() {
Expand All @@ -415,6 +457,16 @@ class StatusBar extends React.Component<StatusBarProps> {
if (this._stackEntry != null) {
StatusBar.popStackEntry(this._stackEntry);
}

StatusBar._mountedCount--;

if (
StatusBar._appearanceSubscription != null &&
StatusBar._mountedCount === 0
) {
StatusBar._appearanceSubscription.remove();
StatusBar._appearanceSubscription = null;
}
}

componentDidUpdate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,33 @@ describe('StatusBar', () => {
false,
);
});
it('resolves auto barStyle against the current color scheme', () => {
const Appearance = require('../../../Utilities/Appearance');
const Platform = require('../../../Utilities/Platform').default;

const nativeStatusBarManager =
Platform.OS === 'ios'
? require('../NativeStatusBarManagerIOS').default
: require('../NativeStatusBarManagerAndroid').default;

const appearanceSpy = jest.spyOn(Appearance, 'getColorScheme');
const setStyleSpy = jest.spyOn(nativeStatusBarManager, 'setStyle');

appearanceSpy.mockReturnValue('light');
setStyleSpy.mockClear();

StatusBar.setBarStyle('auto');
expect(setStyleSpy.mock.calls[0][0]).toBe('dark-content');

appearanceSpy.mockReturnValue('dark');
setStyleSpy.mockClear();

StatusBar.setBarStyle('auto');
expect(setStyleSpy.mock.calls[0][0]).toBe('light-content');

appearanceSpy.mockRestore();
setStyleSpy.mockRestore();
});
it('renders the statusbar but should not be visible', async () => {
const component = await create(<StatusBar hidden={true} />);
expect(component.toTree()?.props.hidden).toBe(true);
Expand Down
11 changes: 6 additions & 5 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<08dd369849273136812ea5edbda6e1df>>
* @generated SignedSource<<a438af540f18c77572f05cef9481b7a6>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -4948,7 +4948,7 @@ declare type StatusBarAnimation = keyof {
}
declare type StatusBarBaseProps = {
readonly animated?: boolean
readonly barStyle?: "dark-content" | "default" | "light-content"
readonly barStyle?: "auto" | "dark-content" | "default" | "light-content"
readonly hidden?: boolean
}
declare type StatusBarProps = Readonly<
Expand All @@ -4963,6 +4963,7 @@ declare type StatusBarPropsIOS = {
readonly showHideTransition?: "fade" | "none" | "slide"
}
declare type StatusBarStyle = keyof {
auto: string
"dark-content": string
default: string
"light-content": string
Expand Down Expand Up @@ -6213,10 +6214,10 @@ export {
ShareContent, // 7c627896
ShareOptions, // 800c3a4e
SimpleTask, // 0e619d11
StatusBar, // 5e08d563
StatusBar, // 875b4eca
StatusBarAnimation, // 7fd047e6
StatusBarProps, // 06c98add
StatusBarStyle, // 986b2051
StatusBarProps, // b72a9127
StatusBarStyle, // 78f53eea
StyleProp, // fa0e9b4a
StyleSheet, // e77dd046
SubmitBehavior, // c4ddf490
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {

const colors = ['#ff0000', '#00ff00', '#0000ff', 'rgba(0, 0, 0, 0.4)'];

const barStyles = ['default', 'light-content', 'dark-content'];
const barStyles = ['default', 'auto', 'light-content', 'dark-content'];

const showHideTransitions = ['fade', 'slide'];

Expand Down
Loading