Support light and dark themes in RNTester

Summary:
Initial conversion of RNTester to support light and dark themes. Theming is implemented by providing the desired color theme via context. Example:

```
const ThemedContainer = props => (
  <RNTesterThemeContext.Consumer>
    {theme => {
      return (
        <View
          style={{
            paddingHorizontal: 8,
            paddingVertical: 16,
            backgroundColor: theme.SystemBackgroundColor,
          }}>
          {props.children}
        </View>
      );
    }}
  </RNTesterThemeContext.Consumer>
);
```

As RNTester's design follows the base iOS system appearance, I've chosen light and dark themes based on the actual iOS 13 semantic colors. The themes are RNTester-specific, however, and we'd expect individual apps to build their own color palettes.

## Examples

The new Appearance Examples screen demonstrates how context can be used to force a theme. It also displays the list of colors in each RNTester theme.

https://pxl.cl/HmzW (screenshot: Appearance Examples screen on RNTester with Dark Mode enabled. Displays useColorScheme hook, and context examples.)
https://pxl.cl/HmB3 (screenshot: Same screen, with light and dark RNTester themes visible)

Theming support in this diff mostly focused on the main screen and the Dark Mode examples screen. This required updating the components used by most of the examples, as you can see in this Image example:
https://pxl.cl/H0Hv (screenshot: Image Examples screen in Dark Mode theme)

Note that I have yet to go through every single example screen to update it. There's individual cases, such as the FlatList example screen, that are not fully converted to use a dark theme when appropriate. This can be taken care later as it's non-blocking.

Reviewed By: zackargyle

Differential Revision: D16681909

fbshipit-source-id: e47484d4b3f0963ef0cc3d8aff8ce3e9051ddbae
This commit is contained in:
Héctor Ramos
2019-08-31 10:05:06 -07:00
committed by Facebook Github Bot
parent ba56fa43f0
commit a397d330a4
14 changed files with 663 additions and 158 deletions
@@ -487,6 +487,12 @@ namespace facebook {
} // namespace react
} // namespace facebook
@implementation RCTCxxConvert (NativeAppearance_AppearancePreferences)
+ (RCTManagedPointer *)JS_NativeAppearance_AppearancePreferences:(id)json
{
return facebook::react::managedPointer<JS::NativeAppearance::AppearancePreferences>(json);
}
@end
folly::Optional<NativeAppearanceColorSchemeName> NSStringToNativeAppearanceColorSchemeName(NSString *value) {
static NSDictionary *dict = nil;
static dispatch_once_t onceToken;
@@ -510,12 +516,6 @@ NSString *NativeAppearanceColorSchemeNameToNSString(folly::Optional<NativeAppear
});
return value.hasValue() ? dict[@(value.value())] : nil;
}
@implementation RCTCxxConvert (NativeAppearance_AppearancePreferences)
+ (RCTManagedPointer *)JS_NativeAppearance_AppearancePreferences:(id)json
{
return facebook::react::managedPointer<JS::NativeAppearance::AppearancePreferences>(json);
}
@end
@implementation RCTCxxConvert (NativeAsyncStorage_SpecMultiGetCallbackErrorsElement)
+ (RCTManagedPointer *)JS_NativeAsyncStorage_SpecMultiGetCallbackErrorsElement:(id)json
{
@@ -452,13 +452,6 @@ namespace facebook {
};
} // namespace react
} // namespace facebook
typedef NS_ENUM(NSInteger, NativeAppearanceColorSchemeName) {
NativeAppearanceColorSchemeNameLight = 0,
NativeAppearanceColorSchemeNameDark,
};
folly::Optional<NativeAppearanceColorSchemeName> NSStringToNativeAppearanceColorSchemeName(NSString *value);
NSString *NativeAppearanceColorSchemeNameToNSString(folly::Optional<NativeAppearanceColorSchemeName> value);
namespace JS {
namespace NativeAppearance {
@@ -475,6 +468,13 @@ namespace JS {
@interface RCTCxxConvert (NativeAppearance_AppearancePreferences)
+ (RCTManagedPointer *)JS_NativeAppearance_AppearancePreferences:(id)json;
@end
typedef NS_ENUM(NSInteger, NativeAppearanceColorSchemeName) {
NativeAppearanceColorSchemeNameLight = 0,
NativeAppearanceColorSchemeNameDark,
};
folly::Optional<NativeAppearanceColorSchemeName> NSStringToNativeAppearanceColorSchemeName(NSString *value);
NSString *NativeAppearanceColorSchemeNameToNSString(folly::Optional<NativeAppearanceColorSchemeName> value);
namespace JS {
namespace NativeAsyncStorage {
+61 -26
View File
@@ -20,11 +20,13 @@ const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios');
const URIActionMap = require('./utils/URIActionMap');
const {
Appearance,
AppRegistry,
AsyncStorage,
BackHandler,
Button,
Linking,
Platform,
SafeAreaView,
StyleSheet,
Text,
@@ -35,6 +37,7 @@ const {
import type {RNTesterExample} from './types/RNTesterTypes';
import type {RNTesterAction} from './utils/RNTesterActions';
import type {RNTesterNavigationState} from './utils/RNTesterNavigationReducer';
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
type Props = {
exampleFromAppetizeParams?: ?string,
@@ -47,18 +50,40 @@ YellowBox.ignoreWarnings([
const APP_STATE_KEY = 'RNTesterAppState.v2';
const Header = ({onBack, title}: {onBack?: () => mixed, title: string}) => (
<SafeAreaView style={styles.headerContainer}>
<View style={styles.header}>
<View style={styles.headerCenter}>
<Text style={styles.title}>{title}</Text>
</View>
{onBack && (
<View style={styles.headerLeft}>
<Button title="Back" onPress={onBack} />
</View>
)}
</View>
</SafeAreaView>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<SafeAreaView
style={[
styles.headerContainer,
{
borderBottomColor: theme.SeparatorColor,
backgroundColor: theme.TertiarySystemBackgroundColor,
},
]}>
<View style={styles.header}>
<View style={styles.headerCenter}>
<Text style={{...styles.title, ...{color: theme.LabelColor}}}>
{title}
</Text>
</View>
{onBack && (
<View>
<Button
title="Back"
onPress={onBack}
color={Platform.select({
ios: theme.LinkColor,
default: undefined,
})}
/>
</View>
)}
</View>
</SafeAreaView>
);
}}
</RNTesterThemeContext.Consumer>
);
class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
@@ -88,6 +113,14 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
Linking.addEventListener('url', url => {
this._handleAction(URIActionMap(url));
});
Appearance.addChangeListener(prefs => {
this._handleAction(
RNTesterActions.ThemeAction(
prefs.colorScheme === 'dark' ? themes.dark : themes.light,
),
);
});
}
componentWillUnmount() {
@@ -114,27 +147,32 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
if (!this.state) {
return null;
}
const theme = this.state.theme;
if (this.state.openExample) {
const Component = RNTesterList.Modules[this.state.openExample];
if (Component && Component.external) {
return <Component onExampleExit={this._handleBack} />;
} else {
return (
<View style={styles.exampleContainer}>
<Header onBack={this._handleBack} title={Component.title} />
<RNTesterExampleContainer module={Component} />
</View>
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header onBack={this._handleBack} title={Component.title} />
<RNTesterExampleContainer module={Component} />
</View>
</RNTesterThemeContext.Provider>
);
}
}
return (
<View style={styles.exampleContainer}>
<Header title="RNTester" />
<RNTesterExampleList
onNavigate={this._handleAction}
list={RNTesterList}
/>
</View>
<RNTesterThemeContext.Provider value={theme}>
<View style={styles.exampleContainer}>
<Header title="RNTester" />
<RNTesterExampleList
onNavigate={this._handleAction}
list={RNTesterList}
/>
</View>
</RNTesterThemeContext.Provider>
);
}
}
@@ -142,14 +180,11 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> {
const styles = StyleSheet.create({
headerContainer: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#96969A',
backgroundColor: '#F5F5F6',
},
header: {
height: 40,
flexDirection: 'row',
},
headerLeft: {},
headerCenter: {
flex: 1,
position: 'absolute',
+39 -12
View File
@@ -13,6 +13,7 @@
const React = require('react');
const {StyleSheet, Text, View} = require('react-native');
import {RNTesterThemeContext} from './RNTesterTheme';
type Props = $ReadOnly<{|
children?: React.Node,
@@ -29,17 +30,47 @@ class RNTesterBlock extends React.Component<Props, State> {
render(): React.Node {
const description = this.props.description ? (
<Text style={styles.descriptionText}>{this.props.description}</Text>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<Text style={[styles.descriptionText, {color: theme.LabelColor}]}>
{this.props.description}
</Text>
);
}}
</RNTesterThemeContext.Consumer>
) : null;
return (
<View style={styles.container}>
<View style={styles.titleContainer}>
<Text style={styles.titleText}>{this.props.title}</Text>
{description}
</View>
<View style={styles.children}>{this.props.children}</View>
</View>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={[
styles.container,
{
borderColor: theme.SeparatorColor,
backgroundColor: theme.SystemBackgroundColor,
},
]}>
<View
style={[
styles.titleContainer,
{
borderBottomColor: theme.SeparatorColor,
backgroundColor: theme.QuaternarySystemFillColor,
},
]}>
<Text style={[styles.titleText, {color: theme.LabelColor}]}>
{this.props.title}
</Text>
{description}
</View>
<View style={styles.children}>{this.props.children}</View>
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
@@ -48,8 +79,6 @@ const styles = StyleSheet.create({
container: {
borderRadius: 3,
borderWidth: 0.5,
borderColor: '#d6d7da',
backgroundColor: '#ffffff',
margin: 10,
marginVertical: 5,
overflow: 'hidden',
@@ -58,8 +87,6 @@ const styles = StyleSheet.create({
borderBottomWidth: 0.5,
borderTopLeftRadius: 3,
borderTopRightRadius: 2.5,
borderBottomColor: '#d6d7da',
backgroundColor: '#f6f7f8',
paddingHorizontal: 10,
paddingVertical: 5,
},
+34 -18
View File
@@ -13,6 +13,7 @@
const React = require('react');
const {StyleSheet, TextInput, View} = require('react-native');
import {RNTesterThemeContext} from './RNTesterTheme';
type Props = {
filter: Function,
@@ -64,33 +65,48 @@ class RNTesterExampleFilter extends React.Component<Props, State> {
return null;
}
return (
<View style={styles.searchRow}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
onChangeText={text => {
this.setState(() => ({filter: text}));
}}
placeholder="Search..."
underlineColorAndroid="transparent"
style={styles.searchTextInput}
testID={this.props.testID}
value={this.state.filter}
/>
</View>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={[
styles.searchRow,
{backgroundColor: theme.GroupedBackgroundColor},
]}>
<TextInput
autoCapitalize="none"
autoCorrect={false}
clearButtonMode="always"
onChangeText={text => {
this.setState(() => ({filter: text}));
}}
placeholder="Search..."
placeholderTextColor={theme.PlaceholderTextColor}
underlineColorAndroid="transparent"
style={[
styles.searchTextInput,
{
color: theme.LabelColor,
backgroundColor: theme.SecondaryGroupedBackgroundColor,
borderColor: theme.QuaternaryLabelColor,
},
]}
testID={this.props.testID}
value={this.state.filter}
/>
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
const styles = StyleSheet.create({
searchRow: {
backgroundColor: '#eeeeee',
padding: 10,
},
searchTextInput: {
backgroundColor: 'white',
borderColor: '#cccccc',
borderRadius: 3,
borderWidth: 1,
paddingLeft: 8,
+101 -45
View File
@@ -26,6 +26,8 @@ const {
import type {ViewStyleProp} from '../../../Libraries/StyleSheet/StyleSheet';
import type {RNTesterExample} from '../types/RNTesterTypes';
import {RNTesterThemeContext} from './RNTesterTheme';
type Props = {
onNavigate: Function,
list: {
@@ -52,21 +54,54 @@ class RowComponent extends React.PureComponent<{
render() {
const {item} = this.props;
return (
<TouchableHighlight
onShowUnderlay={this.props.onShowUnderlay}
onHideUnderlay={this.props.onHideUnderlay}
onPress={this._onPress}>
<View style={styles.row}>
<Text style={styles.rowTitleText}>{item.module.title}</Text>
<Text style={styles.rowDetailText}>{item.module.description}</Text>
</View>
</TouchableHighlight>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<TouchableHighlight
onShowUnderlay={this.props.onShowUnderlay}
onHideUnderlay={this.props.onHideUnderlay}
onPress={this._onPress}>
<View
style={[
styles.row,
{backgroundColor: theme.SystemBackgroundColor},
]}>
<Text style={[styles.rowTitleText, {color: theme.LabelColor}]}>
{item.module.title}
</Text>
<Text
style={[
styles.rowDetailText,
{color: theme.SecondaryLabelColor},
]}>
{item.module.description}
</Text>
</View>
</TouchableHighlight>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
const renderSectionHeader = ({section}) => (
<Text style={styles.sectionHeader}>{section.title}</Text>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<Text
style={[
styles.sectionHeader,
{
color: theme.SecondaryLabelColor,
backgroundColor: theme.GroupedBackgroundColor,
},
]}>
{section.title}
</Text>
);
}}
</RNTesterThemeContext.Consumer>
);
class RNTesterExampleList extends React.Component<Props, $FlowFixMeState> {
@@ -89,29 +124,46 @@ class RNTesterExampleList extends React.Component<Props, $FlowFixMeState> {
];
return (
<View style={[styles.listContainer, this.props.style]}>
{this._renderTitleRow()}
<RNTesterExampleFilter
testID="explorer_search"
sections={sections}
filter={filter}
render={({filteredSections}) => (
<SectionList
ItemSeparatorComponent={ItemSeparator}
contentContainerStyle={styles.sectionListContentContainer}
style={styles.list}
sections={filteredSections}
renderItem={this._renderItem}
enableEmptySections={true}
itemShouldUpdate={this._itemShouldUpdate}
keyboardShouldPersistTaps="handled"
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
renderSectionHeader={renderSectionHeader}
/>
)}
/>
</View>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={[
styles.listContainer,
this.props.style,
{backgroundColor: theme.SecondaryGroupedBackgroundColor},
]}>
{this._renderTitleRow()}
<RNTesterExampleFilter
testID="explorer_search"
sections={sections}
filter={filter}
render={({filteredSections}) => (
<SectionList
ItemSeparatorComponent={ItemSeparator}
contentContainerStyle={{
backgroundColor: theme.SeparatorColor,
}}
style={{backgroundColor: theme.SystemBackgroundColor}}
sections={filteredSections}
renderItem={this._renderItem}
enableEmptySections={true}
itemShouldUpdate={this._itemShouldUpdate}
keyboardShouldPersistTaps="handled"
automaticallyAdjustContentInsets={false}
keyboardDismissMode="on-drag"
renderSectionHeader={renderSectionHeader}
backgroundColor={Platform.select({
ios: 'transparent',
default: undefined,
})}
/>
)}
/>
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
}
@@ -157,39 +209,44 @@ class RNTesterExampleList extends React.Component<Props, $FlowFixMeState> {
}
const ItemSeparator = ({highlighted}) => (
<View style={highlighted ? styles.separatorHighlighted : styles.separator} />
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={
highlighted
? [
styles.separatorHighlighted,
{backgroundColor: theme.OpaqueSeparatorColor},
]
: [styles.separator, {backgroundColor: theme.SeparatorColor}]
}
/>
);
}}
</RNTesterThemeContext.Consumer>
);
const styles = StyleSheet.create({
listContainer: {
flex: 1,
},
list: {
backgroundColor: '#eeeeee',
},
sectionHeader: {
backgroundColor: '#eeeeee',
padding: 5,
fontWeight: '500',
fontSize: 11,
},
row: {
backgroundColor: 'white',
justifyContent: 'center',
paddingHorizontal: 15,
paddingVertical: 8,
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: '#bbbbbb',
marginLeft: 15,
},
separatorHighlighted: {
height: StyleSheet.hairlineWidth,
backgroundColor: 'rgb(217, 217, 217)',
},
sectionListContentContainer: {
backgroundColor: 'white',
},
rowTitleText: {
fontSize: 17,
@@ -197,7 +254,6 @@ const styles = StyleSheet.create({
},
rowDetailText: {
fontSize: 15,
color: '#888888',
lineHeight: 20,
},
});
+18 -9
View File
@@ -12,8 +12,8 @@
const RNTesterTitle = require('./RNTesterTitle');
const React = require('react');
const {ScrollView, StyleSheet, View} = require('react-native');
import {RNTesterThemeContext} from './RNTesterTheme';
type Props = $ReadOnly<{|
children?: React.Node,
@@ -39,20 +39,29 @@ class RNTesterPage extends React.Component<Props> {
) : null;
const spacer = this.props.noSpacer ? null : <View style={styles.spacer} />;
return (
<View style={styles.container}>
{title}
<ContentWrapper style={styles.wrapper} {...wrapperProps}>
{this.props.children}
{spacer}
</ContentWrapper>
</View>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={[
styles.container,
{backgroundColor: theme.SecondarySystemBackgroundColor},
]}>
{title}
<ContentWrapper style={styles.wrapper} {...wrapperProps}>
{this.props.children}
{spacer}
</ContentWrapper>
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#e9eaed',
flex: 1,
},
spacer: {
+88
View File
@@ -0,0 +1,88 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import * as React from 'react';
import {Appearance} from 'react-native';
export type RNTesterTheme = {
LabelColor: string,
SecondaryLabelColor: string,
TertiaryLabelColor: string,
QuaternaryLabelColor: string,
PlaceholderTextColor: string,
SystemBackgroundColor: string,
SecondarySystemBackgroundColor: string,
TertiarySystemBackgroundColor: string,
GroupedBackgroundColor: string,
SecondaryGroupedBackgroundColor: string,
TertiaryGroupedBackgroundColor: string,
SystemFillColor: string,
SecondarySystemFillColor: string,
TertiarySystemFillColor: string,
QuaternarySystemFillColor: string,
SeparatorColor: string,
OpaqueSeparatorColor: string,
LinkColor: string,
SystemPurpleColor: string,
ToolbarColor: string,
};
export const RNTesterLightTheme = {
LabelColor: '#000000ff',
SecondaryLabelColor: '#3c3c4399',
TertiaryLabelColor: '#3c3c434c',
QuaternaryLabelColor: '#3c3c432d',
PlaceholderTextColor: '#3c3c434c',
SystemBackgroundColor: '#ffffffff',
SecondarySystemBackgroundColor: '#f2f2f7ff',
TertiarySystemBackgroundColor: '#ffffffff',
GroupedBackgroundColor: '#f2f2f7ff',
SecondaryGroupedBackgroundColor: '#ffffffff',
TertiaryGroupedBackgroundColor: '#f2f2f7ff',
SystemFillColor: '#78788033',
SecondarySystemFillColor: '#78788028',
TertiarySystemFillColor: '#7676801e',
QuaternarySystemFillColor: '#74748014',
SeparatorColor: '#3c3c4349',
OpaqueSeparatorColor: '#c6c6c8ff',
LinkColor: '#007affff',
SystemPurpleColor: '#af52deff',
ToolbarColor: '#e9eaedff',
};
export const RNTesterDarkTheme = {
LabelColor: '#ffffffff',
SecondaryLabelColor: '#ebebf599',
TertiaryLabelColor: '#ebebf54c',
QuaternaryLabelColor: '#ebebf528',
PlaceholderTextColor: '#ebebf54c',
SystemBackgroundColor: '#000000ff',
SecondarySystemBackgroundColor: '#1c1c1eff',
TertiarySystemBackgroundColor: '#2c2c2eff',
GroupedBackgroundColor: '#000000ff',
SecondaryGroupedBackgroundColor: '#1c1c1eff',
TertiaryGroupedBackgroundColor: '#2c2c2eff',
SystemFillColor: '#7878805b',
SecondarySystemFillColor: '#78788051',
TertiarySystemFillColor: '#7676803d',
QuaternarySystemFillColor: '#7676802d',
SeparatorColor: '#54545899',
OpaqueSeparatorColor: '#38383aff',
LinkColor: '#0984ffff',
SystemPurpleColor: '#bf5af2ff',
ToolbarColor: '#3c3c43ff',
};
export const themes = {light: RNTesterLightTheme, dark: RNTesterDarkTheme};
export const RNTesterThemeContext: React.Context<RNTesterTheme> = React.createContext(
Appearance.getColorScheme() === 'dark' ? themes.dark : themes.light,
);
+19 -5
View File
@@ -13,13 +13,29 @@
const React = require('react');
const {StyleSheet, Text, View} = require('react-native');
import {RNTesterThemeContext} from './RNTesterTheme';
class RNTesterTitle extends React.Component<$FlowFixMeProps> {
render(): React.Node {
return (
<View style={styles.container}>
<Text style={styles.text}>{this.props.title}</Text>
</View>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={[
styles.container,
{
borderColor: theme.SeparatorColor,
backgroundColor: theme.SystemBackgroundColor,
},
]}>
<Text style={[styles.text, {color: theme.LabelColor}]}>
{this.props.title}
</Text>
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
@@ -28,12 +44,10 @@ const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
margin: 10,
marginBottom: 0,
height: 45,
padding: 10,
backgroundColor: 'white',
},
text: {
fontSize: 19,
@@ -0,0 +1,201 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
import * as React from 'react';
import {Appearance, Text, View} from 'react-native';
import type {AppearancePreferences} from '../../../../Libraries/Utilities/NativeAppearance';
import {RNTesterThemeContext, themes} from '../../components/RNTesterTheme';
class ColorSchemeSubscription extends React.Component<
{},
{colorScheme: ?string},
> {
state = {
colorScheme: Appearance.getColorScheme(),
};
componentDidMount() {
Appearance.addChangeListener(this._handleAppearanceChange);
}
componentWillUnmount() {
Appearance.removeChangeListener(this._handleAppearanceChange);
}
_handleAppearanceChange = (preferences: AppearancePreferences) => {
const {colorScheme} = preferences;
this.setState({colorScheme});
};
render() {
return (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<ThemedContainer>
<ThemedText>{this.state.colorScheme}</ThemedText>
</ThemedContainer>
);
}}
</RNTesterThemeContext.Consumer>
);
}
}
const ThemedContainer = props => (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={{
paddingHorizontal: 8,
paddingVertical: 16,
backgroundColor: theme.SystemBackgroundColor,
}}>
{props.children}
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
const ThemedText = props => (
<RNTesterThemeContext.Consumer>
{theme => {
return <Text style={{color: theme.LabelColor}}>{props.children}</Text>;
}}
</RNTesterThemeContext.Consumer>
);
exports.title = 'Appearance';
exports.description = 'Light and dark user interface examples.';
exports.examples = [
{
title: 'Non-component `getColorScheme` API',
render(): React.Element<any> {
return <ColorSchemeSubscription />;
},
},
{
title: 'Consuming Context',
render(): React.Element<any> {
return (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<ThemedContainer>
<ThemedText>
This block of text inherits its theme via Context.
</ThemedText>
</ThemedContainer>
);
}}
</RNTesterThemeContext.Consumer>
);
},
},
{
title: 'Context forced to light theme',
render(): React.Element<any> {
return (
<RNTesterThemeContext.Provider value={themes.light}>
<ThemedContainer>
<ThemedText>
This block of text will always render with a light theme.
</ThemedText>
</ThemedContainer>
</RNTesterThemeContext.Provider>
);
},
},
{
title: 'Context forced to dark theme',
render(): React.Element<any> {
return (
<RNTesterThemeContext.Provider value={themes.dark}>
<ThemedContainer>
<ThemedText>
This block of text will always render with a dark theme.
</ThemedText>
</ThemedContainer>
</RNTesterThemeContext.Provider>
);
},
},
{
title: 'RNTester App Colors',
description: 'A light and a dark theme based on standard iOS 13 colors.',
render(): React.Element<any> {
const ColorShowcase = props => (
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View
style={{
marginVertical: 20,
backgroundColor: theme.SystemBackgroundColor,
}}>
<Text style={{fontWeight: '700', color: theme.LabelColor}}>
{props.themeName}
</Text>
{Object.keys(theme).map(key => (
<View style={{flexDirection: 'row'}} key={key}>
<View
style={{
width: 50,
height: 50,
paddingHorizontal: 8,
paddingVertical: 2,
backgroundColor: theme[key],
}}
/>
<View>
<Text
style={{
paddingHorizontal: 16,
paddingVertical: 2,
color: theme.LabelColor,
fontWeight: '600',
}}>
{key}
</Text>
<Text
style={{
paddingHorizontal: 16,
paddingVertical: 2,
color: theme.LabelColor,
}}>
{theme[key]}
</Text>
</View>
</View>
))}
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
return (
<View>
<RNTesterThemeContext.Provider value={themes.light}>
<ColorShowcase themeName="Light Mode" />
</RNTesterThemeContext.Provider>
<RNTesterThemeContext.Provider value={themes.dark}>
<ColorShowcase themeName="Dark Mode" />
</RNTesterThemeContext.Provider>
</View>
);
},
},
];
+49 -28
View File
@@ -13,6 +13,7 @@
const React = require('react');
const {Alert, Button, View, StyleSheet} = require('react-native');
import {RNTesterThemeContext} from '../../components/RNTesterTheme';
function onButtonPress(buttonName) {
Alert.alert(`${buttonName} has been pressed!`);
@@ -31,12 +32,19 @@ exports.examples = [
'everyone.': string),
render: function(): React.Node {
return (
<Button
onPress={() => onButtonPress('Simple')}
testID="simple_button"
title="Press Me"
accessibilityLabel="See an informative alert"
/>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<Button
onPress={() => onButtonPress('Simple')}
testID="simple_button"
color={theme.LinkColor}
title="Press Me"
accessibilityLabel="See an informative alert"
/>
);
}}
</RNTesterThemeContext.Consumer>
);
},
},
@@ -47,13 +55,19 @@ exports.examples = [
'Android, the color adjusts the background color of the button.': string),
render: function(): React.Node {
return (
<Button
onPress={() => onButtonPress('Purple')}
testID="purple_button"
title="Press Purple"
color="#841584"
accessibilityLabel="Learn more about purple"
/>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<Button
onPress={() => onButtonPress('Purple')}
testID="purple_button"
color={theme.SystemPurpleColor}
title="Press Purple"
accessibilityLabel="Learn more about purple"
/>
);
}}
</RNTesterThemeContext.Consumer>
);
},
},
@@ -63,21 +77,28 @@ exports.examples = [
'the button': string),
render: function(): React.Node {
return (
<View style={styles.container}>
<Button
onPress={() => onButtonPress('Left')}
testID="left_button"
title="This looks great!"
accessibilityLabel="This sounds great!"
/>
<Button
onPress={() => onButtonPress('Right')}
testID="right_button"
title="Ok!"
color="#841584"
accessibilityLabel="Ok, Great!"
/>
</View>
<RNTesterThemeContext.Consumer>
{theme => {
return (
<View style={styles.container}>
<Button
onPress={() => onButtonPress('Left')}
testID="left_button"
color={theme.LinkColor}
title="This looks great!"
accessibilityLabel="This sounds great!"
/>
<Button
onPress={() => onButtonPress('Right')}
testID="right_button"
color={theme.SystemPurpleColor}
title="Ok!"
accessibilityLabel="Ok, Great!"
/>
</View>
);
}}
</RNTesterThemeContext.Consumer>
);
},
},
+18 -2
View File
@@ -5,11 +5,13 @@
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
* @flow strict-local
*/
'use strict';
import type {RNTesterTheme} from '../components/RNTesterTheme';
export type RNTesterBackAction = {
type: 'RNTesterBackAction',
};
@@ -23,10 +25,16 @@ export type RNTesterExampleAction = {
openExample: string,
};
export type RNTesterThemeAction = {
type: 'RNTesterThemeAction',
theme: RNTesterTheme,
};
export type RNTesterAction =
| RNTesterBackAction
| RNTesterListAction
| RNTesterExampleAction;
| RNTesterExampleAction
| RNTesterThemeAction;
function Back(): RNTesterBackAction {
return {
@@ -47,10 +55,18 @@ function ExampleAction(openExample: string): RNTesterExampleAction {
};
}
function ThemeAction(theme: RNTesterTheme): RNTesterThemeAction {
return {
type: 'RNTesterThemeAction',
theme,
};
}
const RNTesterActions = {
Back,
ExampleList,
ExampleAction,
ThemeAction,
};
module.exports = RNTesterActions;
+5
View File
@@ -205,6 +205,11 @@ const APIExamples: Array<RNTesterExample> = [
module: require('../examples/Animated/AnimatedGratuitousApp/AnExApp'),
supportsTVOS: true,
},
{
key: 'AppearanceExample',
module: require('../examples/Appearance/AppearanceExample'),
supportsTVOS: false,
},
{
key: 'AppStateExample',
module: require('../examples/AppState/AppStateExample'),
@@ -10,10 +10,15 @@
'use strict';
import {themes} from '../components/RNTesterTheme';
import type {RNTesterTheme} from '../components/RNTesterTheme';
const RNTesterList = require('./RNTesterList');
import {Appearance} from 'react-native';
export type RNTesterNavigationState = {
openExample: ?string,
theme: RNTesterTheme,
};
function RNTesterNavigationReducer(
@@ -31,6 +36,8 @@ function RNTesterNavigationReducer(
return {
// A null openExample will cause the views to display the RNTester example list
openExample: null,
theme:
Appearance.getColorScheme() === 'dark' ? themes.dark : themes.light,
};
}
@@ -41,6 +48,16 @@ function RNTesterNavigationReducer(
if (ExampleModule) {
return {
openExample: action.openExample,
theme: state.theme,
};
}
}
if (action.type === 'RNTesterThemeAction') {
if (action.colorScheme) {
return {
openExample: state.openExample,
theme: action.colorScheme === 'dark' ? themes.dark : themes.light,
};
}
}