From e7f25857ce8efd5d99a2d5ed74a29239a927cac0 Mon Sep 17 00:00:00 2001 From: Anukrati Agrawal Date: Fri, 11 Sep 2026 23:14:40 -0700 Subject: [PATCH 1/3] Fix app title text scaling Render the visible Windows app title in React Native so it follows the system text-size setting while preserving the native window title, caption buttons, and drag behavior. Adapt the native caption height when text scaling changes and add regression coverage for the scalable title. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 18f319cb-b4f4-4b6b-ae4d-b8f429333447 --- __tests__/AppTitleBar.test.tsx | 30 ++++++++++++++++++++++ src/App.tsx | 33 +++++++++++++++++-------- src/components/AppTitleBar.tsx | 44 +++++++++++++++++++++++++++++++++ windows/rngallery/rngallery.cpp | 29 ++++++++++++++-------- 4 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 __tests__/AppTitleBar.test.tsx create mode 100644 src/components/AppTitleBar.tsx diff --git a/__tests__/AppTitleBar.test.tsx b/__tests__/AppTitleBar.test.tsx new file mode 100644 index 00000000..68af2fe5 --- /dev/null +++ b/__tests__/AppTitleBar.test.tsx @@ -0,0 +1,30 @@ +/** + * @format + */ + +import React from 'react'; +import {Text} from 'react-native'; +import {act, create, ReactTestRenderer} from 'react-test-renderer'; +import { + AppTitleBar, + getAppTitleBarHeight, +} from '../src/components/AppTitleBar'; + +test('uses the tall caption only when text scaling is enabled', () => { + expect(getAppTitleBarHeight(1)).toBe(32); + expect(getAppTitleBarHeight(2)).toBe(48); +}); + +test('allows the visible app title to follow the system text scale', async () => { + let tree!: ReactTestRenderer; + + await act(async () => { + tree = create(); + }); + + const title = tree.root.findByType(Text); + + expect(title.props.children).toBe('React Native Gallery'); + expect(title.props.allowFontScaling).toBe(true); + expect(title.props.accessibilityRole).toBe('header'); +}); diff --git a/src/App.tsx b/src/App.tsx index 272ba4ac..2d835c67 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { useColorScheme, KeyboardEvent as RNKeyboardEvent, AccessibilityInfo, + Platform, ScrollView, } from 'react-native'; import {NavigationContainer, useNavigation} from './Navigation'; @@ -31,12 +32,19 @@ import { NavigationHistoryProvider, useNavigationHistory, } from './hooks/useNavigationHistory'; +import {AppTitleBar} from './components/AppTitleBar'; // Context for signaling focus to ScreenWrapper hamburger export const FocusScreenWrapperContext = React.createContext(null); export const FocusScreenWrapperSetterContext = React.createContext>>(() => {}); const styles = StyleSheet.create({ + appContainer: { + flex: 1, + }, + navigationContainer: { + flex: 1, + }, menu: { margin: 5, height: 34, @@ -541,16 +549,21 @@ export default function App() { - - - + + {Platform.OS === 'windows' && } + + + + + + diff --git a/src/components/AppTitleBar.tsx b/src/components/AppTitleBar.tsx new file mode 100644 index 00000000..ca3f6a61 --- /dev/null +++ b/src/components/AppTitleBar.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { + PlatformColor, + StyleSheet, + Text, + useWindowDimensions, + View, +} from 'react-native'; + +const styles = StyleSheet.create({ + container: { + alignItems: 'flex-start', + backgroundColor: PlatformColor('Background'), + justifyContent: 'center', + paddingLeft: 40, + paddingRight: 140, + }, + title: { + color: PlatformColor('TextFillColorPrimary'), + fontSize: 12, + }, +}); + +export const getAppTitleBarHeight = (fontScale: number) => + fontScale > 1 ? 48 : 32; + +export function AppTitleBar() { + const {fontScale} = useWindowDimensions(); + + return ( + + + React Native Gallery + + + ); +} diff --git a/windows/rngallery/rngallery.cpp b/windows/rngallery/rngallery.cpp index f81cc525..2160ec07 100644 --- a/windows/rngallery/rngallery.cpp +++ b/windows/rngallery/rngallery.cpp @@ -85,22 +85,25 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR auto titleBar = appWindow.TitleBar(); if (titleBar) { - // Enable title bar theming to follow system theme - titleBar.ExtendsContentIntoTitleBar(false); + // Render the app title in React Native so it follows the system text scale. + // The system continues to provide the caption buttons and drag region. + titleBar.ExtendsContentIntoTitleBar(true); // Capture the DispatcherQueue so we can marshal theme updates to the UI thread auto dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread(); - // Function to apply current system theme colors - auto applySystemTheme = [titleBar]() + auto applyTitleBarSettings = [titleBar]() { try { winrt::Windows::UI::ViewManagement::UISettings uiSettings; auto foreground = uiSettings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Foreground); auto background = uiSettings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Background); + auto heightOption = uiSettings.TextScaleFactor() > 1 + ? winrt::Microsoft::UI::Windowing::TitleBarHeightOption::Tall + : winrt::Microsoft::UI::Windowing::TitleBarHeightOption::Standard; - // Apply system theme colors to title bar + titleBar.PreferredHeightOption(heightOption); titleBar.ForegroundColor(foreground); titleBar.BackgroundColor(background); titleBar.ButtonForegroundColor(foreground); @@ -122,18 +125,24 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR } }; - // Apply initial theme - applySystemTheme(); + applyTitleBarSettings(); // Listen for system theme changes using the static uiSettings so the // event registration persists for the lifetime of the application. - s_uiSettings.ColorValuesChanged([applySystemTheme, dispatcherQueue](auto const &, auto const &) + s_uiSettings.ColorValuesChanged([applyTitleBarSettings, dispatcherQueue](auto const &, auto const &) { // ColorValuesChanged fires on a background thread, so dispatch // the title bar update back to the UI thread. if (dispatcherQueue) { - dispatcherQueue.TryEnqueue([applySystemTheme]() { - applySystemTheme(); + dispatcherQueue.TryEnqueue([applyTitleBarSettings]() { + applyTitleBarSettings(); + }); + } }); + s_uiSettings.TextScaleFactorChanged([applyTitleBarSettings, dispatcherQueue](auto const &, auto const &) + { + if (dispatcherQueue) { + dispatcherQueue.TryEnqueue([applyTitleBarSettings]() { + applyTitleBarSettings(); }); } }); } From 13e888aa52122dc109456281c63f2025ddf9a712 Mon Sep 17 00:00:00 2001 From: Anukrati Agrawal Date: Sat, 12 Sep 2026 19:23:35 -0700 Subject: [PATCH 2/3] Fix live title bar scaling Use the rendered title height to keep the React content boundary synchronized with the native caption when Windows text size changes. RNW 0.84 updates text layout live without publishing a new Dimensions fontScale value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 18f319cb-b4f4-4b6b-ae4d-b8f429333447 --- __tests__/AppTitleBar.test.tsx | 39 +++++++++++++++++++++++++++++++--- src/components/AppTitleBar.tsx | 17 ++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/__tests__/AppTitleBar.test.tsx b/__tests__/AppTitleBar.test.tsx index 68af2fe5..b2d3806c 100644 --- a/__tests__/AppTitleBar.test.tsx +++ b/__tests__/AppTitleBar.test.tsx @@ -3,7 +3,7 @@ */ import React from 'react'; -import {Text} from 'react-native'; +import {StyleSheet, Text, View} from 'react-native'; import {act, create, ReactTestRenderer} from 'react-test-renderer'; import { AppTitleBar, @@ -11,8 +11,8 @@ import { } from '../src/components/AppTitleBar'; test('uses the tall caption only when text scaling is enabled', () => { - expect(getAppTitleBarHeight(1)).toBe(32); - expect(getAppTitleBarHeight(2)).toBe(48); + expect(getAppTitleBarHeight(false)).toBe(32); + expect(getAppTitleBarHeight(true)).toBe(48); }); test('allows the visible app title to follow the system text scale', async () => { @@ -28,3 +28,36 @@ test('allows the visible app title to follow the system text scale', async () => expect(title.props.allowFontScaling).toBe(true); expect(title.props.accessibilityRole).toBe('header'); }); + +test('updates the title bar height when the rendered title scale changes', async () => { + let tree!: ReactTestRenderer; + + await act(async () => { + tree = create(); + }); + + const title = tree.root.findByType(Text); + const getContainerHeight = () => + StyleSheet.flatten(tree.root.findByType(View).props.style).height; + + await act(async () => { + title.props.onLayout({ + nativeEvent: {layout: {height: 16}}, + }); + }); + expect(getContainerHeight()).toBe(32); + + await act(async () => { + title.props.onLayout({ + nativeEvent: {layout: {height: 32}}, + }); + }); + expect(getContainerHeight()).toBe(48); + + await act(async () => { + title.props.onLayout({ + nativeEvent: {layout: {height: 16}}, + }); + }); + expect(getContainerHeight()).toBe(32); +}); diff --git a/src/components/AppTitleBar.tsx b/src/components/AppTitleBar.tsx index ca3f6a61..60ca4871 100644 --- a/src/components/AppTitleBar.tsx +++ b/src/components/AppTitleBar.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { + LayoutChangeEvent, PlatformColor, StyleSheet, Text, @@ -7,6 +8,8 @@ import { View, } from 'react-native'; +const STANDARD_TITLE_LINE_HEIGHT = 16; + const styles = StyleSheet.create({ container: { alignItems: 'flex-start', @@ -21,21 +24,29 @@ const styles = StyleSheet.create({ }, }); -export const getAppTitleBarHeight = (fontScale: number) => - fontScale > 1 ? 48 : 32; +export const getAppTitleBarHeight = (isTextScaled: boolean) => + isTextScaled ? 48 : 32; export function AppTitleBar() { const {fontScale} = useWindowDimensions(); + const [isTitleScaled, setIsTitleScaled] = React.useState(fontScale > 1); + + const onTitleLayout = React.useCallback((event: LayoutChangeEvent) => { + setIsTitleScaled( + event.nativeEvent.layout.height > STANDARD_TITLE_LINE_HEIGHT, + ); + }, []); return ( React Native Gallery From cdadfbb8eee740e7cae7b7572f6991766813cc46 Mon Sep 17 00:00:00 2001 From: Anukrati Agrawal Date: Mon, 14 Sep 2026 11:59:25 -0700 Subject: [PATCH 3/3] Prevent app title wrapping Keep the scalable Windows title on one ellipsized line so layout height remains a reliable text-scaling signal at narrow window widths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0f63ea4f-a16b-4fa3-b141-785aec89b5e4 --- __tests__/AppTitleBar.test.tsx | 2 ++ src/components/AppTitleBar.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/__tests__/AppTitleBar.test.tsx b/__tests__/AppTitleBar.test.tsx index b2d3806c..4ba835ca 100644 --- a/__tests__/AppTitleBar.test.tsx +++ b/__tests__/AppTitleBar.test.tsx @@ -27,6 +27,8 @@ test('allows the visible app title to follow the system text scale', async () => expect(title.props.children).toBe('React Native Gallery'); expect(title.props.allowFontScaling).toBe(true); expect(title.props.accessibilityRole).toBe('header'); + expect(title.props.numberOfLines).toBe(1); + expect(title.props.ellipsizeMode).toBe('tail'); }); test('updates the title bar height when the rendered title scale changes', async () => { diff --git a/src/components/AppTitleBar.tsx b/src/components/AppTitleBar.tsx index 60ca4871..0fb05108 100644 --- a/src/components/AppTitleBar.tsx +++ b/src/components/AppTitleBar.tsx @@ -46,6 +46,8 @@ export function AppTitleBar() { React Native Gallery