Skip to content
Open
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
35 changes: 21 additions & 14 deletions packages/react-native/Libraries/Utilities/Appearance.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

import {NativeEventSubscription} from '../EventEmitter/RCTNativeAppEventEmitter';

type ColorSchemeName = 'light' | 'dark' | 'unspecified';
type ColorSchemeName = 'light' | 'dark';

type ColorSchemeOverride = ColorSchemeName | 'unspecified';

export namespace Appearance {
type AppearancePreferences = {
Expand All @@ -19,33 +21,38 @@ export namespace Appearance {
type AppearanceListener = (preferences: AppearancePreferences) => void;

/**
* Note: Although color scheme is available immediately, it may change at any
* time. Any rendering logic or styles that depend on this should try to call
* this function on every render, rather than caching the value (for example,
* using inline styles rather than setting a value in a `StyleSheet`).
* Returns the active color scheme (`'light'` or `'dark'`). This value may
* change at runtime, either at the system level (e.g. scheduled color scheme
* change at sunrise or sunset) or when overridden at the app level via
* `setColorScheme()`.
*
* Prefer `useColorScheme()` in React components.
*
* Example: `const colorScheme = Appearance.getColorScheme();`
* Notes:
* - `null` will only be returned if the native Appearance module is
* unavailable (out of tree platforms).
*/
export function getColorScheme(): ColorSchemeName | null | undefined;

/**
* Set the color scheme preference. This is useful for overriding the default
* color scheme preference for the app. Note that this will not change the
* appearance of the system UI, only the appearance of the app.
* Only available on iOS 13+ and Android 10+.
* Force the application to always adopt a light or dark interface style. Pass
* `'unspecified'` to reset and follow the system default (removes any
* override). This does not affect the system UI, only the application.
*/
export function setColorScheme(scheme: ColorSchemeName): void;
export function setColorScheme(scheme: ColorSchemeOverride): void;

/**
* Add an event handler that is fired when appearance preferences change.
* Subscribe to color scheme changes. The listener receives the new appearance
* preferences whenever the color scheme changes, whether from a system event
* or a call to `setColorScheme()`.
*/
export function addChangeListener(
listener: AppearanceListener,
): NativeEventSubscription;
}

/**
* A new useColorScheme hook is provided as the preferred way of accessing
* the user's preferred color scheme (e.g. Dark Mode).
* Returns the active color scheme (`'light'` or `'dark'`). Automatically
* re-renders the component when the color scheme changes.
*/
export function useColorScheme(): ColorSchemeName;
35 changes: 24 additions & 11 deletions packages/react-native/Libraries/Utilities/Appearance.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
*/

import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import type {AppearancePreferences, ColorSchemeName} from './NativeAppearance';
import type {
AppearancePreferences,
ColorSchemeName,
ColorSchemeOverride,
} from './NativeAppearance';
import typeof INativeAppearance from './NativeAppearance';

import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
import EventEmitter from '../vendor/emitter/EventEmitter';

export type {AppearancePreferences};
export type {AppearancePreferences, ColorSchemeName, ColorSchemeOverride};

type Appearance = {
colorScheme: ?ColorSchemeName,
Expand Down Expand Up @@ -69,9 +73,16 @@ function getState(): NonNullable<typeof lazyState> {
}

/**
* Returns the current color scheme preference. This value may change, so the
* value should not be cached without either listening to changes or using
* the `useColorScheme` hook.
* Returns the active color scheme (`'light'` or `'dark'`). This value may
* change at runtime, either at the system level (e.g. scheduled color scheme
* change at sunrise or sunset) or when overridden at the app level via
* `setColorScheme()`.
*
* Prefer `useColorScheme()` in React components.
*
* Notes:
* - `null` will only be returned if the native Appearance module is unavailable
* (out of tree platforms).
*/
export function getColorScheme(): ?ColorSchemeName {
let colorScheme = null;
Expand All @@ -91,26 +102,28 @@ export function getColorScheme(): ?ColorSchemeName {
}

/**
* Updates the current color scheme to the supplied value.
* Force the application to always adopt a light or dark interface style. Pass
* `'unspecified'` to reset and follow the system default (removes any
* override). This does not affect the system UI, only the application.
*/
export function setColorScheme(colorScheme: ColorSchemeName): void {
export function setColorScheme(colorScheme: ColorSchemeOverride): void {
const state = getState();
const {NativeAppearance} = state;
if (NativeAppearance != null) {
NativeAppearance.setColorScheme(colorScheme);
state.appearance = {
// When setting to 'unspecified', get the actual system color scheme.
// Fall back to the passed value if getColorScheme() returns null.
colorScheme:
colorScheme === 'unspecified'
? (NativeAppearance.getColorScheme() ?? colorScheme)
? (NativeAppearance.getColorScheme() ?? null)
: colorScheme,
};
}
}

/**
* Add an event handler that is fired when appearance preferences change.
* Subscribe to color scheme changes. The listener receives the new appearance
* preferences whenever the color scheme changes, whether from a system event
* or a call to `setColorScheme()`.
*/
export function addChangeListener(
listener: ({colorScheme: ?ColorSchemeName}) => void,
Expand Down
8 changes: 8 additions & 0 deletions packages/react-native/Libraries/Utilities/useColorScheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ const subscribe = (onStoreChange: () => void) => {
return () => appearanceSubscription.remove();
};

/**
* Returns the active color scheme (`'light'` or `'dark'`). Automatically
* re-renders the component when the color scheme changes.
*
* Notes:
* - `null` will only be returned if the native Appearance module is unavailable
* (out of tree platforms).
*/
export default function useColorScheme(): ?ColorSchemeName {
return useSyncExternalStore(subscribe, getColorScheme);
}
15 changes: 9 additions & 6 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<<1c8637ab03a5fec9d39704d1ae305595>>
* @generated SignedSource<<4950f1efd16fed02b526f83325c8351d>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -1630,6 +1630,8 @@ declare namespace Appearance {
setColorScheme,
addChangeListener,
AppearancePreferences,
ColorSchemeName,
ColorSchemeOverride,
}
}
declare type AppearancePreferences = {
Expand Down Expand Up @@ -1856,7 +1858,8 @@ declare namespace CodegenTypes {
}
}
declare type ColorListenerCallback = (value: ColorValue) => unknown
declare type ColorSchemeName = "dark" | "light" | "unspecified"
declare type ColorSchemeName = "dark" | "light"
declare type ColorSchemeOverride = "dark" | "light" | "unspecified"
declare type ColorValue = ____ColorValue_Internal
declare type ComponentProvider = () => React.ComponentType<any>
declare type ComponentProviderInstrumentationHook = (
Expand Down Expand Up @@ -4709,7 +4712,7 @@ declare type Separators = {
updateProps: (select: "leading" | "trailing", newProps: Object) => void
}
declare type sequence = typeof sequence
declare function setColorScheme(colorScheme: ColorSchemeName): void
declare function setColorScheme(colorScheme: ColorSchemeOverride): void
declare function setComponentProviderInstrumentationHook(
hook: ComponentProviderInstrumentationHook,
): void
Expand Down Expand Up @@ -6070,7 +6073,7 @@ export {
AppState, // 12012be5
AppStateEvent, // 80f034c3
AppStateStatus, // 447e5ef2
Appearance, // 00cbaa0a
Appearance, // df9545f9
AutoCapitalize, // c0e857a0
BackHandler, // f139fc69
BackPressEventName, // 4620fb76
Expand All @@ -6080,7 +6083,7 @@ export {
ButtonProps, // 0df9cb59
Clipboard, // 41addb89
CodegenTypes, // 0b8108a8
ColorSchemeName, // 31a4350e
ColorSchemeName, // 6615edd6
ColorValue, // 98989a8f
ComponentProvider, // b5c60ddd
ComponentProviderInstrumentationHook, // 9f640048
Expand Down Expand Up @@ -6336,7 +6339,7 @@ export {
useAnimatedColor, // e3511f81
useAnimatedValue, // b18adb63
useAnimatedValueXY, // c7ee2332
useColorScheme, // c216d6f7
useColorScheme, // 29a517d5
usePressability, // b4e21b46
useWindowDimensions, // bb4b683f
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import type {TurboModule} from '../../../../Libraries/TurboModule/RCTExport';

import * as TurboModuleRegistry from '../../../../Libraries/TurboModule/TurboModuleRegistry';

export type ColorSchemeName = 'light' | 'dark' | 'unspecified';
export type ColorSchemeName = 'light' | 'dark';

export type ColorSchemeOverride = 'light' | 'dark' | 'unspecified';

export type AppearancePreferences = {
colorScheme?: ?ColorSchemeName,
};

export interface Spec extends TurboModule {
+getColorScheme: () => ?ColorSchemeName;
+setColorScheme: (colorScheme: ColorSchemeName) => void;
+setColorScheme: (colorScheme: ColorSchemeOverride) => void;

// RCTEventEmitter
+addListener: (eventName: string) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {useEffect, useState} from 'react';
import {Appearance, Button, Text, View, useColorScheme} from 'react-native';

function ColorSchemeSubscription() {
const [colorScheme, setColorScheme] = useState<?ColorSchemeName | string>(
const [colorScheme, setColorScheme] = useState<?ColorSchemeName>(
Appearance.getColorScheme(),
);

Expand Down Expand Up @@ -135,8 +135,9 @@ const ColorShowcase = (props: {themeName: string}) => (
);

const ToggleNativeAppearance = () => {
const [nativeColorScheme, setNativeColorScheme] =
useState<ColorSchemeName>('unspecified');
const [nativeColorScheme, setNativeColorScheme] = useState<
ColorSchemeName | 'unspecified',
>('unspecified');
const colorScheme = useColorScheme();

useEffect(() => {
Expand Down
Loading