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
65 changes: 65 additions & 0 deletions __tests__/AppTitleBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @format
*/

import React from 'react';
import {StyleSheet, Text, View} 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(false)).toBe(32);
expect(getAppTitleBarHeight(true)).toBe(48);
});

test('allows the visible app title to follow the system text scale', async () => {
let tree!: ReactTestRenderer;

await act(async () => {
tree = create(<AppTitleBar />);
});

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');
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 () => {
let tree!: ReactTestRenderer;

await act(async () => {
tree = create(<AppTitleBar />);
});

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);
});
33 changes: 23 additions & 10 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
useColorScheme,
KeyboardEvent as RNKeyboardEvent,
AccessibilityInfo,
Platform,
ScrollView,
} from 'react-native';
import {NavigationContainer, useNavigation} from './Navigation';
Expand All @@ -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<number | null>(null);
export const FocusScreenWrapperSetterContext = React.createContext<React.Dispatch<React.SetStateAction<number | null>>>(() => {});

const styles = StyleSheet.create({
appContainer: {
flex: 1,
},
navigationContainer: {
flex: 1,
},
menu: {
margin: 5,
height: 34,
Expand Down Expand Up @@ -541,16 +549,21 @@ export default function App() {
<ThemeSetterContext.Provider value={setRawTheme}>
<RawThemeContext.Provider value={rawtheme}>
<ThemeContext.Provider value={theme}>
<NavigationContainer
theme={
isHighContrast
? HighContrastTheme
: theme === 'dark'
? DarkTheme
: LightTheme
}>
<NavigationAwareDrawer />
</NavigationContainer>
<View style={styles.appContainer}>
{Platform.OS === 'windows' && <AppTitleBar />}
<View style={styles.navigationContainer}>
<NavigationContainer
theme={
isHighContrast
? HighContrastTheme
: theme === 'dark'
? DarkTheme
: LightTheme
}>
<NavigationAwareDrawer />
</NavigationContainer>
</View>
</View>
</ThemeContext.Provider>
</RawThemeContext.Provider>
</ThemeSetterContext.Provider>
Expand Down
57 changes: 57 additions & 0 deletions src/components/AppTitleBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import {
LayoutChangeEvent,
PlatformColor,
StyleSheet,
Text,
useWindowDimensions,
View,
} from 'react-native';

const STANDARD_TITLE_LINE_HEIGHT = 16;

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 = (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 (
<View
style={[
styles.container,
{height: getAppTitleBarHeight(isTitleScaled)},
]}>
<Text
accessibilityRole="header"
allowFontScaling={true}
ellipsizeMode="tail"
numberOfLines={1}
onLayout={onTitleLayout}
style={styles.title}>
React Native Gallery
</Text>
</View>
);
}
29 changes: 19 additions & 10 deletions windows/rngallery/rngallery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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();
});
} });
}
Expand Down