From f4872506c3f287db495debb0dc5dc65e7c4bc6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Gaczo=C5=82?= Date: Thu, 30 Apr 2026 11:57:48 +0200 Subject: [PATCH 1/2] add colorScheme to SplitHost --- ios/conversion/RNSConversions-SplitView.mm | 16 ++++++++++++++++ ios/conversion/RNSConversions.h | 2 ++ .../split/RNSSplitAppearanceApplicator.swift | 1 + ios/gamma/split/RNSSplitHostComponentView.h | 1 + ios/gamma/split/RNSSplitHostComponentView.mm | 5 +++++ src/components/gamma/split/SplitHost.types.ts | 17 ++++++++++++++++- .../gamma/split/SplitHostNativeComponent.ts | 3 +++ 7 files changed, 44 insertions(+), 1 deletion(-) diff --git a/ios/conversion/RNSConversions-SplitView.mm b/ios/conversion/RNSConversions-SplitView.mm index c9a18a8f0b..faa1153e22 100644 --- a/ios/conversion/RNSConversions-SplitView.mm +++ b/ios/conversion/RNSConversions-SplitView.mm @@ -162,6 +162,22 @@ RNSOrientation RNSOrientationFromRNSSplitHostOrientation(react::RNSSplitHostOrie } } +UIUserInterfaceStyle UIUserInterfaceStyleFromHostProp(react::RNSSplitHostColorScheme colorScheme) +{ + using enum facebook::react::RNSSplitHostColorScheme; + switch (colorScheme) { + case Inherit: + return UIUserInterfaceStyleUnspecified; + case Light: + return UIUserInterfaceStyleLight; + case Dark: + return UIUserInterfaceStyleDark; + default: + RCTLogError(@"[RNScreens] unsupported color scheme"); + return UIUserInterfaceStyleUnspecified; + } +} + #pragma mark SplitScreen props RNSSplitScreenColumnType RNSSplitScreenColumnTypeFromScreenProp(facebook::react::RNSSplitScreenColumnType columnType) diff --git a/ios/conversion/RNSConversions.h b/ios/conversion/RNSConversions.h index 9f28007079..974e855e29 100644 --- a/ios/conversion/RNSConversions.h +++ b/ios/conversion/RNSConversions.h @@ -134,6 +134,8 @@ std::optional SplitViewTopColumnForCollapsingFromHo RNSOrientation RNSOrientationFromRNSSplitHostOrientation(react::RNSSplitHostOrientation orientation); +UIUserInterfaceStyle UIUserInterfaceStyleFromHostProp(react::RNSSplitHostColorScheme colorScheme); + #pragma mark SplitScreen props RNSSplitScreenColumnType RNSSplitScreenColumnTypeFromScreenProp(react::RNSSplitScreenColumnType columnType); diff --git a/ios/gamma/split/RNSSplitAppearanceApplicator.swift b/ios/gamma/split/RNSSplitAppearanceApplicator.swift index 617da145ef..56dc98379f 100644 --- a/ios/gamma/split/RNSSplitAppearanceApplicator.swift +++ b/ios/gamma/split/RNSSplitAppearanceApplicator.swift @@ -65,6 +65,7 @@ class RNSSplitAppearanceApplicator { // Step 1 - general settings splitHostController.displayModeButtonVisibility = splitHost.displayModeButtonVisibility splitHostController.preferredSplitBehavior = splitHost.preferredSplitBehavior + splitHostController.overrideUserInterfaceStyle = splitHost.colorScheme #if !os(tvOS) splitHostController.primaryBackgroundStyle = splitHost.primaryBackgroundStyle #endif diff --git a/ios/gamma/split/RNSSplitHostComponentView.h b/ios/gamma/split/RNSSplitHostComponentView.h index 0538aa09ac..dca7ea15e3 100644 --- a/ios/gamma/split/RNSSplitHostComponentView.h +++ b/ios/gamma/split/RNSSplitHostComponentView.h @@ -62,6 +62,7 @@ NS_ASSUME_NONNULL_BEGIN #endif // RNS_IPHONE_OS_VERSION_AVAILABLE(26_0) @property (nonatomic, readonly) RNSOrientation orientation; +@property (nonatomic, readonly) UIUserInterfaceStyle colorScheme; @end diff --git a/ios/gamma/split/RNSSplitHostComponentView.mm b/ios/gamma/split/RNSSplitHostComponentView.mm index 721c480be0..164f1904f1 100644 --- a/ios/gamma/split/RNSSplitHostComponentView.mm +++ b/ios/gamma/split/RNSSplitHostComponentView.mm @@ -227,6 +227,11 @@ - (void)updateProps:(const facebook::react::Props::Shared &)props rnscreens::conversion::SplitViewPreferredDisplayModeFromHostProp(newComponentProps.preferredDisplayMode); } + if (oldComponentProps.colorScheme != newComponentProps.colorScheme) { + _needsSplitAppearanceUpdate = true; + _colorScheme = rnscreens::conversion::UIUserInterfaceStyleFromHostProp(newComponentProps.colorScheme); + } + #if !TARGET_OS_TV if (oldComponentProps.primaryBackgroundStyle != newComponentProps.primaryBackgroundStyle) { _needsSplitAppearanceUpdate = true; diff --git a/src/components/gamma/split/SplitHost.types.ts b/src/components/gamma/split/SplitHost.types.ts index 0e70f18036..9039e5a1b1 100644 --- a/src/components/gamma/split/SplitHost.types.ts +++ b/src/components/gamma/split/SplitHost.types.ts @@ -1,5 +1,5 @@ import type { NativeSyntheticEvent, ViewProps } from 'react-native'; -import type { InterfaceOrientation } from '../../shared/types'; +import type { InterfaceOrientation, ColorScheme } from '../../shared/types'; // eslint-disable-next-line @typescript-eslint/ban-types type GenericEmptyEvent = Readonly<{}>; @@ -28,6 +28,8 @@ export type SplitDisplayMode = export type SplitHostOrientation = InterfaceOrientation | 'inherit'; +export type SplitHostColorScheme = ColorScheme | 'inherit'; + export interface SplitColumnMetrics { /** * @summary Minimum width for the primary sidebar. @@ -217,6 +219,19 @@ export interface SplitHostProps extends ViewProps { * @platform ios */ orientation?: SplitHostOrientation | undefined; + /** + * @summary Specifies the color scheme used by the container and any child containers. + * + * The following values are currently supported: + * - `inherit` - the interface style from parent, + * - `light` - the light interface style, + * - `dark` - the dark interface style. + * + * @default inherit + * + * @platform ios + */ + colorScheme?: SplitHostColorScheme; /** * @summary Determines whether gestures are enabled to change the display mode. */ diff --git a/src/fabric/gamma/split/SplitHostNativeComponent.ts b/src/fabric/gamma/split/SplitHostNativeComponent.ts index 2a648bcfd6..91444837f2 100644 --- a/src/fabric/gamma/split/SplitHostNativeComponent.ts +++ b/src/fabric/gamma/split/SplitHostNativeComponent.ts @@ -41,6 +41,8 @@ type SplitViewOrientation = | 'landscapeLeft' | 'landscapeRight'; +type SplitViewColorScheme = 'inherit' | 'light' | 'dark'; + type SplitViewPrimaryBackgroundStyle = 'default' | 'none' | 'sidebar'; type SplitViewTopColumnForCollapsing = @@ -78,6 +80,7 @@ interface NativeProps extends ViewProps { >; columnMetrics?: ColumnMetrics | undefined; orientation?: CT.WithDefault; + colorScheme?: CT.WithDefault; primaryBackgroundStyle?: CT.WithDefault< SplitViewPrimaryBackgroundStyle, 'default' From 8cb4597373edfe629be9c7d4c2432265b4e77fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20Gaczo=C5=82?= Date: Thu, 30 Apr 2026 12:44:59 +0200 Subject: [PATCH 2/2] add test-split-color-scheme.tsx --- .../tests/single-feature-tests/split/index.ts | 7 +- .../split/test-split-color-scheme.tsx | 138 ++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 apps/src/tests/single-feature-tests/split/test-split-color-scheme.tsx diff --git a/apps/src/tests/single-feature-tests/split/index.ts b/apps/src/tests/single-feature-tests/split/index.ts index fb71522122..9c55892674 100644 --- a/apps/src/tests/single-feature-tests/split/index.ts +++ b/apps/src/tests/single-feature-tests/split/index.ts @@ -1,8 +1,13 @@ import type { ScenarioGroup } from '@apps/tests/shared/helpers'; import TestTopColumnForCollapsing from './test-top-column-for-collapsing'; import TestCommandShowColumn from './test-command-show-column'; +import TestColorScheme from './test-split-color-scheme'; -const scenarios = { TestTopColumnForCollapsing, TestCommandShowColumn }; +const scenarios = { + TestTopColumnForCollapsing, + TestCommandShowColumn, + TestColorScheme, +}; const SplitScenarioGroup: ScenarioGroup = { name: 'Split', diff --git a/apps/src/tests/single-feature-tests/split/test-split-color-scheme.tsx b/apps/src/tests/single-feature-tests/split/test-split-color-scheme.tsx new file mode 100644 index 0000000000..966bb9ac71 --- /dev/null +++ b/apps/src/tests/single-feature-tests/split/test-split-color-scheme.tsx @@ -0,0 +1,138 @@ +import { + Appearance, + ColorSchemeName, + Platform, + ScrollView, + StyleSheet, + Text, + TextInput, + View, +} from 'react-native'; +import type { ScenarioDescription } from '@apps/tests/shared/helpers'; +import { createScenario } from '@apps/tests/shared/helpers'; +import React, { useEffect, useState } from 'react'; +import { SettingsPicker } from '@apps/shared'; +import { Split } from 'react-native-screens/experimental'; +import { SplitHostColorScheme } from 'react-native-screens/components/gamma/split/SplitHost.types'; + +const scenarioDescription: ScenarioDescription = { + name: 'Split Color Scheme', + key: 'test-split-color-scheme', + details: + 'Tests how Split handles system, React Native and prop color scheme.', + platforms: ['ios'], +}; + +export function ConfigColumn({ + reactColorScheme, + setReactColorScheme, + hostColorScheme, + setHostColorScheme, +}: { + reactColorScheme: ColorSchemeName; + setReactColorScheme: (value: ColorSchemeName) => void; + hostColorScheme: SplitHostColorScheme; + setHostColorScheme: (value: SplitHostColorScheme) => void; +}) { + return ( + + + + There are 3 sources of color scheme, in ascending order of precedence: + system, React Native and our property on SplitHost. + + + + + System color scheme + + Switch system color scheme via Cmd+Shift+A (iOS simulator). + + + + + React Native's color scheme + + label={'colorScheme'} + value={reactColorScheme} + onValueChange={setReactColorScheme} + items={['unspecified', 'light', 'dark']} + /> + + + + SplitHost color scheme + > + label={'colorScheme'} + value={hostColorScheme} + onValueChange={setHostColorScheme} + items={['inherit', 'light', 'dark']} + /> + + + ); +} + +export function TestColumn() { + return ( + + + + ); +} + +export function App() { + const [hostColorScheme, setHostColorScheme] = + useState('inherit'); + const [reactColorScheme, setReactColorScheme] = + useState('unspecified'); + + useEffect(() => { + Appearance.setColorScheme(reactColorScheme); + }, [reactColorScheme]); + + return ( + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + containerCenter: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + content: { + padding: 20, + paddingTop: Platform.OS === 'android' ? 60 : undefined, + }, + heading: { + fontSize: 24, + fontWeight: 'bold', + marginBottom: 5, + color: 'rgb(0, 122, 255)', + }, + section: { + marginBottom: 10, + }, + text: { + color: 'gray', + }, +}); + +export default createScenario(App, scenarioDescription);