feat: add custom theme support (#211)

This commit is contained in:
Satyajit Sahoo
2019-12-14 22:25:25 +01:00
committed by GitHub
parent 703edb3569
commit 00fc616de0
40 changed files with 840 additions and 527 deletions

View File

@@ -33,10 +33,12 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16"
"@react-navigation/routers": "^5.0.0-alpha.16",
"color": "^3.1.2"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",
"@types/color": "^3.0.0",
"@types/react": "^16.9.11",
"@types/react-native": "^0.60.22",
"del-cli": "^3.0.0",

View File

@@ -9,7 +9,11 @@ import {
ScaledSize,
Dimensions,
} from 'react-native';
import { NavigationContext, CommonActions } from '@react-navigation/native';
import {
NavigationContext,
CommonActions,
useTheme,
} from '@react-navigation/native';
import { SafeAreaConsumer } from 'react-native-safe-area-context';
import BottomTabItem from './BottomTabItem';
@@ -41,6 +45,8 @@ export default function BottomTabBar({
style,
tabStyle,
}: Props) {
const { colors } = useTheme();
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
const [layout, setLayout] = React.useState({ height: 0, width: 0 });
const [keyboardShown, setKeyboardShown] = React.useState(false);
@@ -162,6 +168,10 @@ export default function BottomTabBar({
<Animated.View
style={[
styles.tabBar,
{
backgroundColor: colors.card,
borderTopColor: colors.border,
},
keyboardHidesTabBar
? {
// When the keyboard is shown, slide down the tab bar
@@ -267,9 +277,7 @@ const styles = StyleSheet.create({
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#fff',
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: 'rgba(0, 0, 0, .3)',
elevation: 8,
},
content: {

View File

@@ -8,7 +8,8 @@ import {
ViewStyle,
TextStyle,
} from 'react-native';
import { Route } from '@react-navigation/native';
import { Route, useTheme } from '@react-navigation/native';
import Color from 'color';
import TabBarIcon from './TabBarIcon';
import { BottomTabBarButtonProps } from '../types';
@@ -113,8 +114,8 @@ export default function BottomTabBarItem({
onPress,
onLongPress,
horizontal,
activeTintColor = '#007AFF',
inactiveTintColor = '#8E8E93',
activeTintColor: customActiveTintColor,
inactiveTintColor: customInactiveTintColor,
activeBackgroundColor = 'transparent',
inactiveBackgroundColor = 'transparent',
showLabel = true,
@@ -123,6 +124,20 @@ export default function BottomTabBarItem({
labelStyle,
style,
}: Props) {
const { colors } = useTheme();
const activeTintColor =
customActiveTintColor === undefined
? colors.primary
: customActiveTintColor;
const inactiveTintColor =
customInactiveTintColor === undefined
? Color(colors.text)
.mix(Color(colors.card), 0.5)
.hex()
: customInactiveTintColor;
const renderLabel = ({ focused }: { focused: boolean }) => {
if (showLabel === false) {
return null;

View File

@@ -2,6 +2,7 @@ import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { TabNavigationState } from '@react-navigation/routers';
import { useTheme } from '@react-navigation/native';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
@@ -25,6 +26,26 @@ type State = {
loaded: number[];
};
function SceneContent({
isFocused,
children,
}: {
isFocused: boolean;
children: React.ReactNode;
}) {
const { colors } = useTheme();
return (
<View
accessibilityElementsHidden={!isFocused}
importantForAccessibility={isFocused ? 'auto' : 'no-hide-descendants'}
style={[styles.content, { backgroundColor: colors.background }]}
>
{children}
</View>
);
}
export default class BottomTabView extends React.Component<Props, State> {
static defaultProps = {
lazy: true,
@@ -97,15 +118,9 @@ export default class BottomTabView extends React.Component<Props, State> {
style={StyleSheet.absoluteFill}
isVisible={isFocused}
>
<View
accessibilityElementsHidden={!isFocused}
importantForAccessibility={
isFocused ? 'auto' : 'no-hide-descendants'
}
style={styles.content}
>
<SceneContent isFocused={isFocused}>
{descriptors[route.key].render()}
</View>
</SceneContent>
</ResourceSavingScene>
);
})}

View File

@@ -34,7 +34,8 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16"
"@react-navigation/routers": "^5.0.0-alpha.16",
"color": "^3.1.2"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -10,6 +10,7 @@ export { default as DrawerView } from './views/DrawerView';
export { default as DrawerItem } from './views/DrawerItem';
export { default as DrawerItemList } from './views/DrawerItemList';
export { default as DrawerContent } from './views/DrawerContent';
export { default as DrawerContentScrollView } from './views/DrawerContentScrollView';
/**
* Utilities

View File

@@ -121,7 +121,7 @@ export type DrawerNavigationOptions = {
export type DrawerContentComponentProps<T = DrawerContentOptions> = T & {
state: DrawerNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
/**
* Animated node which represents the current progress of the drawer's open state.

View File

@@ -1,36 +1,12 @@
import * as React from 'react';
import { ScrollView, StyleSheet } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
import DrawerItemList from './DrawerItemList';
import { DrawerContentComponentProps } from '../types';
import DrawerContentScrollView from './DrawerContentScrollView';
export default function DrawerContent({
contentContainerStyle,
style,
drawerPosition,
...rest
}: DrawerContentComponentProps) {
const insets = useSafeArea();
export default function DrawerContent(props: DrawerContentComponentProps) {
return (
<ScrollView
contentContainerStyle={[
{
paddingTop: insets.top + 4,
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
paddingRight: drawerPosition === 'right' ? insets.right : 0,
},
contentContainerStyle,
]}
style={[styles.container, style]}
>
<DrawerItemList {...rest} />
</ScrollView>
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
</DrawerContentScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -0,0 +1,43 @@
import * as React from 'react';
import { ScrollView, StyleSheet, ScrollViewProps } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
import { useTheme } from '@react-navigation/native';
type Props = ScrollViewProps & {
drawerPosition: 'left' | 'right';
children: React.ReactNode;
};
export default function DrawerContentScrollView({
contentContainerStyle,
style,
drawerPosition,
children,
...rest
}: Props) {
const insets = useSafeArea();
const { colors } = useTheme();
return (
<ScrollView
{...rest}
contentContainerStyle={[
{
paddingTop: insets.top + 4,
paddingLeft: drawerPosition === 'left' ? insets.left : 0,
paddingRight: drawerPosition === 'right' ? insets.right : 0,
},
contentContainerStyle,
]}
style={[styles.container, { backgroundColor: colors.card }, style]}
>
{children}
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});

View File

@@ -7,6 +7,8 @@ import {
ViewStyle,
TextStyle,
} from 'react-native';
import { useTheme } from '@react-navigation/native';
import Color from 'color';
import TouchableItem from './TouchableItem';
type Props = {
@@ -61,19 +63,29 @@ type Props = {
/**
* A component used to show an action item with an icon and a label in a navigation drawer.
*/
export default function DrawerItem({
icon,
label,
labelStyle,
focused = false,
activeTintColor = '#6200ee',
inactiveTintColor = 'rgba(0, 0, 0, .68)',
activeBackgroundColor = 'rgba(98, 0, 238, 0.12)',
inactiveBackgroundColor = 'transparent',
style,
onPress,
...rest
}: Props) {
export default function DrawerItem(props: Props) {
const { colors } = useTheme();
const {
icon,
label,
labelStyle,
focused = false,
activeTintColor = colors.primary,
inactiveTintColor = Color(colors.text)
.alpha(0.68)
.rgb()
.string(),
activeBackgroundColor = Color(activeTintColor)
.alpha(0.12)
.rgb()
.string(),
inactiveBackgroundColor = 'transparent',
style,
onPress,
...rest
} = props;
const { borderRadius = 4 } = StyleSheet.flatten(style || {});
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused

View File

@@ -1,8 +1,8 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { BottomNavigation } from 'react-native-paper';
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { Route } from '@react-navigation/native';
import { Route, useTheme } from '@react-navigation/native';
import { TabNavigationState, TabActions } from '@react-navigation/routers';
import {
@@ -25,9 +25,25 @@ export default function MaterialBottomTabView({
descriptors,
...rest
}: Props) {
const { dark, colors } = useTheme();
const theme = React.useMemo(() => {
const t = dark ? DarkTheme : DefaultTheme;
return {
...t,
colors: {
...t.colors,
...colors,
surface: colors.card,
},
};
}, [colors, dark]);
return (
<BottomNavigation
{...rest}
theme={theme}
navigationState={state}
onIndexChange={(index: number) =>
navigation.dispatch({

View File

@@ -34,7 +34,8 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.16"
"@react-navigation/routers": "^5.0.0-alpha.16",
"color": "^3.1.2"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -1,29 +1,46 @@
import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { TabBar } from 'react-native-tab-view';
import { Route } from '@react-navigation/native';
import { Route, useTheme } from '@react-navigation/native';
import Color from 'color';
import { MaterialTopTabBarProps } from '../types';
export default function TabBarTop({
state,
navigation,
descriptors,
activeTintColor = 'rgba(255, 255, 255, 1)',
inactiveTintColor = 'rgba(255, 255, 255, 0.7)',
allowFontScaling = true,
iconStyle,
labelStyle,
showIcon = false,
showLabel = true,
...rest
}: MaterialTopTabBarProps) {
export default function TabBarTop(props: MaterialTopTabBarProps) {
const { colors } = useTheme();
const {
state,
navigation,
descriptors,
activeTintColor = colors.text,
inactiveTintColor = Color(activeTintColor)
.alpha(0.5)
.rgb()
.string(),
allowFontScaling = true,
showIcon = false,
showLabel = true,
pressColor = Color(activeTintColor)
.alpha(0.08)
.rgb()
.string(),
iconStyle,
labelStyle,
indicatorStyle,
style,
...rest
} = props;
return (
<TabBar
{...rest}
navigationState={state}
activeColor={activeTintColor}
inactiveColor={inactiveTintColor}
indicatorStyle={[{ backgroundColor: colors.primary }, indicatorStyle]}
style={[{ backgroundColor: colors.card }, style]}
pressColor={pressColor}
getAccessibilityLabel={({ route }) =>
descriptors[route.key].options.tabBarAccessibilityLabel
}

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { View } from 'react-native';
import { TabView, SceneRendererProps } from 'react-native-tab-view';
import { Route } from '@react-navigation/native';
import { Route, useTheme } from '@react-navigation/native';
import { TabNavigationState, TabActions } from '@react-navigation/routers';
import MaterialTopTabBar from './MaterialTopTabBar';
@@ -18,6 +19,16 @@ type Props = MaterialTopTabNavigationConfig & {
tabBarPosition: 'top' | 'bottom';
};
function SceneContent({ children }: { children: React.ReactNode }) {
const { colors } = useTheme();
return (
<View style={{ flex: 1, backgroundColor: colors.background }}>
{children}
</View>
);
}
export default class MaterialTopTabView extends React.PureComponent<Props> {
static defaultProps = {
tabBarPosition: 'top',
@@ -93,7 +104,9 @@ export default class MaterialTopTabView extends React.PureComponent<Props> {
target: state.key,
})
}
renderScene={({ route }) => descriptors[route.key].render()}
renderScene={({ route }) => (
<SceneContent>{descriptors[route.key].render()}</SceneContent>
)}
navigationState={state}
renderTabBar={this.renderTabBar}
renderLazyPlaceholder={this.renderLazyPlaceholder}

View File

@@ -6,7 +6,7 @@ import {
ScreenStackHeaderRightView,
// eslint-disable-next-line import/no-unresolved
} from 'react-native-screens';
import { Route } from '@react-navigation/native';
import { Route, useTheme } from '@react-navigation/native';
import { NativeStackNavigationOptions } from '../types';
type Props = NativeStackNavigationOptions & {
@@ -14,6 +14,7 @@ type Props = NativeStackNavigationOptions & {
};
export default function HeaderConfig(props: Props) {
const { colors } = useTheme();
const {
route,
title,
@@ -52,17 +53,23 @@ export default function HeaderConfig(props: Props) {
titleColor={
headerTitleStyle.color !== undefined
? headerTitleStyle.color
: headerTintColor
: headerTintColor !== undefined
? headerTintColor
: colors.text
}
backTitle={headerBackTitleVisible ? headerBackTitle : ''}
backTitleFontFamily={headerBackTitleStyle.fontFamily}
backTitleFontSize={headerBackTitleStyle.fontSize}
color={headerTintColor}
color={headerTintColor !== undefined ? headerTintColor : colors.primary}
gestureEnabled={gestureEnabled === undefined ? true : gestureEnabled}
largeTitle={headerLargeTitle}
largeTitleFontFamily={headerLargeTitleStyle.fontFamily}
largeTitleFontSize={headerLargeTitleStyle.fontSize}
backgroundColor={headerStyle.backgroundColor}
backgroundColor={
headerStyle.backgroundColor !== undefined
? headerStyle.backgroundColor
: colors.card
}
>
{headerRight !== undefined ? (
<ScreenStackHeaderRightView>{headerRight()}</ScreenStackHeaderRightView>

View File

@@ -9,6 +9,7 @@ import {
ScreenProps,
// eslint-disable-next-line import/no-unresolved
} from 'react-native-screens';
import { useTheme } from '@react-navigation/native';
import HeaderConfig from './HeaderConfig';
import {
NativeStackNavigationHelpers,
@@ -34,8 +35,10 @@ export default function NativeStackView({
navigation,
descriptors,
}: Props) {
const { colors } = useTheme();
return (
<ScreenStack style={styles.scenes}>
<ScreenStack style={styles.container}>
{state.routes.map(route => {
const { options, render: renderScene } = descriptors[route.key];
const { presentation = 'push', animation, contentStyle } = options;
@@ -55,7 +58,15 @@ export default function NativeStackView({
}}
>
<HeaderConfig {...options} route={route} />
<View style={[styles.content, contentStyle]}>{renderScene()}</View>
<View
style={[
styles.container,
{ backgroundColor: colors.background },
contentStyle,
]}
>
{renderScene()}
</View>
</Screen>
);
})}
@@ -64,11 +75,7 @@ export default function NativeStackView({
}
const styles = StyleSheet.create({
content: {
flex: 1,
backgroundColor: '#eee',
},
scenes: {
container: {
flex: 1,
},
});

View File

@@ -4,7 +4,14 @@ import {
NavigationContainerProps,
NavigationContainerRef,
} from '@react-navigation/core';
import ThemeProvider from './theming/ThemeProvider';
import DefaultTheme from './theming/DefaultTheme';
import useBackButton from './useBackButton';
import { Theme } from './types';
type Props = NavigationContainerProps & {
theme?: Theme;
};
/**
* Container component which holds the navigation state
@@ -13,11 +20,12 @@ import useBackButton from './useBackButton';
*
* @param props.initialState Initial state object for the navigation tree.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.theme Theme object for the navigators.
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const NavigationNativeContainer = React.forwardRef(function NativeContainer(
props: NavigationContainerProps,
{ theme = DefaultTheme, ...rest }: Props,
ref: React.Ref<NavigationContainerRef>
) {
const refContainer = React.useRef<NavigationContainerRef>(null);
@@ -27,11 +35,9 @@ const NavigationNativeContainer = React.forwardRef(function NativeContainer(
React.useImperativeHandle(ref, () => refContainer.current);
return (
<NavigationContainer
{...props}
ref={refContainer}
children={props.children}
/>
<ThemeProvider value={theme}>
<NavigationContainer {...rest} ref={refContainer} />
</ThemeProvider>
);
});

View File

@@ -5,3 +5,8 @@ export { default as NavigationNativeContainer } from './NavigationNativeContaine
export { default as useBackButton } from './useBackButton';
export { default as useLinking } from './useLinking';
export { default as useScrollToTop } from './useScrollToTop';
export { default as DefaultTheme } from './theming/DefaultTheme';
export { default as DarkTheme } from './theming/DarkTheme';
export { default as ThemeProvider } from './theming/ThemeProvider';
export { default as useTheme } from './theming/useTheme';

View File

@@ -0,0 +1,14 @@
import { Theme } from '../types';
const DarkTheme: Theme = {
dark: true,
colors: {
primary: 'rgb(10, 132, 255)',
background: 'rgb(1, 1, 1)',
card: 'rgb(18, 18, 18)',
text: 'rgb(229, 229, 231)',
border: 'rgb(39, 39, 41)',
},
};
export default DarkTheme;

View File

@@ -0,0 +1,14 @@
import { Theme } from '../types';
const DefaultTheme: Theme = {
dark: false,
colors: {
primary: 'rgb(0, 122, 255)',
background: 'rgb(242, 242, 242)',
card: 'rgb(255, 255, 255)',
text: 'rgb(28, 28, 30)',
border: 'rgb(199, 199, 204)',
},
};
export default DefaultTheme;

View File

@@ -0,0 +1,7 @@
import * as React from 'react';
import DefaultTheme from './DefaultTheme';
import { Theme } from '../types';
const ThemeContext = React.createContext<Theme>(DefaultTheme);
export default ThemeContext;

View File

@@ -0,0 +1,14 @@
import * as React from 'react';
import ThemeContext from './ThemeContext';
import { Theme } from '../types';
type Props = {
value: Theme;
children: React.ReactNode;
};
export default function ThemeProvider({ value, children }: Props) {
return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
);
}

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
import ThemeContext from './ThemeContext';
export default function useTheme() {
const theme = React.useContext(ThemeContext);
return theme;
}

View File

@@ -0,0 +1,10 @@
export type Theme = {
dark: boolean;
colors: {
primary: string;
background: string;
card: string;
text: string;
border: string;
};
};

View File

@@ -406,6 +406,10 @@ export type StackHeaderTitleProps = {
* Whether title font should scale to respect Text Size accessibility settings.
*/
allowFontScaling?: boolean;
/**
* Tint color for the header.
*/
tintColor?: string;
/**
* Content of the title element. Usually the title string.
*/

View File

@@ -8,45 +8,56 @@ import {
LayoutChangeEvent,
} from 'react-native';
import Animated from 'react-native-reanimated';
import { useTheme } from '@react-navigation/native';
import MaskedView from '../MaskedView';
import TouchableItem from '../TouchableItem';
import { StackHeaderLeftButtonProps } from '../../types';
type Props = StackHeaderLeftButtonProps & {
tintColor: string;
};
type Props = StackHeaderLeftButtonProps;
type State = {
fullLabelWidth?: number;
};
export default function HeaderBackButton({
disabled,
allowFontScaling,
backImage,
label,
labelStyle,
labelVisible = Platform.OS === 'ios',
onLabelLayout,
onPress,
pressColorAndroid: customPressColorAndroid,
screenLayout,
tintColor: customTintColor,
titleLayout,
truncatedLabel = 'Back',
}: Props) {
const { dark, colors } = useTheme();
class HeaderBackButton extends React.Component<Props, State> {
static defaultProps = {
pressColorAndroid: 'rgba(0, 0, 0, .32)',
tintColor: Platform.select({
ios: '#037aff',
web: '#5f6368',
}),
labelVisible: Platform.OS === 'ios',
truncatedLabel: 'Back',
};
const [initialLabelWidth, setInitialLabelWidth] = React.useState<
undefined | number
>(undefined);
state: State = {};
const tintColor =
customTintColor !== undefined
? customTintColor
: Platform.select({
ios: colors.primary,
default: colors.text,
});
private handleLabelLayout = (e: LayoutChangeEvent) => {
const { onLabelLayout } = this.props;
const pressColorAndroid =
customPressColorAndroid !== undefined
? customPressColorAndroid
: dark
? 'rgba(255, 255, 255, .32)'
: 'rgba(0, 0, 0, .32)';
const handleLabelLayout = (e: LayoutChangeEvent) => {
onLabelLayout && onLabelLayout(e);
this.setState({
fullLabelWidth: e.nativeEvent.layout.x + e.nativeEvent.layout.width,
});
setInitialLabelWidth(e.nativeEvent.layout.x + e.nativeEvent.layout.width);
};
private shouldTruncateLabel = () => {
const { titleLayout, screenLayout, label } = this.props;
const { fullLabelWidth: initialLabelWidth } = this.state;
const shouldTruncateLabel = () => {
return (
!label ||
(initialLabelWidth &&
@@ -56,9 +67,7 @@ class HeaderBackButton extends React.Component<Props, State> {
);
};
private renderBackImage = () => {
const { backImage, labelVisible, tintColor } = this.props;
const renderBackImage = () => {
if (backImage) {
return backImage({ tintColor });
} else {
@@ -76,19 +85,8 @@ class HeaderBackButton extends React.Component<Props, State> {
}
};
private renderLabel() {
const {
label,
truncatedLabel,
allowFontScaling,
labelVisible,
backImage,
labelStyle,
tintColor,
screenLayout,
} = this.props;
const leftLabelText = this.shouldTruncateLabel() ? truncatedLabel : label;
const renderLabel = () => {
const leftLabelText = shouldTruncateLabel() ? truncatedLabel : label;
if (!labelVisible || leftLabelText === undefined) {
return null;
@@ -109,7 +107,7 @@ class HeaderBackButton extends React.Component<Props, State> {
onLayout={
// This measurement is used to determine if we should truncate the label when it doesn't fit
// Only measure it when label is not truncated because we want the measurement of full label
leftLabelText === label ? this.handleLabelLayout : undefined
leftLabelText === label ? handleLabelLayout : undefined
}
style={[
styles.label,
@@ -145,42 +143,37 @@ class HeaderBackButton extends React.Component<Props, State> {
{labelElement}
</MaskedView>
);
}
};
private handlePress = () =>
this.props.onPress && requestAnimationFrame(this.props.onPress);
const handlePress = () => onPress && requestAnimationFrame(onPress);
render() {
const { pressColorAndroid, label, disabled } = this.props;
return (
<TouchableItem
disabled={disabled}
accessible
accessibilityRole="button"
accessibilityComponentType="button"
accessibilityLabel={
label && label !== 'Back' ? `${label}, back` : 'Go back'
}
accessibilityTraits="button"
testID="header-back"
delayPressIn={0}
onPress={disabled ? undefined : this.handlePress}
pressColor={pressColorAndroid}
style={[styles.container, disabled && styles.disabled]}
hitSlop={Platform.select({
ios: undefined,
default: { top: 16, right: 16, bottom: 16, left: 16 },
})}
borderless
>
<React.Fragment>
{this.renderBackImage()}
{this.renderLabel()}
</React.Fragment>
</TouchableItem>
);
}
return (
<TouchableItem
disabled={disabled}
accessible
accessibilityRole="button"
accessibilityComponentType="button"
accessibilityLabel={
label && label !== 'Back' ? `${label}, back` : 'Go back'
}
accessibilityTraits="button"
testID="header-back"
delayPressIn={0}
onPress={disabled ? undefined : handlePress}
pressColor={pressColorAndroid}
style={[styles.container, disabled && styles.disabled]}
hitSlop={Platform.select({
ios: undefined,
default: { top: 16, right: 16, bottom: 16, left: 16 },
})}
borderless
>
<React.Fragment>
{renderBackImage()}
{renderLabel()}
</React.Fragment>
</TouchableItem>
);
}
const styles = StyleSheet.create({
@@ -253,5 +246,3 @@ const styles = StyleSheet.create({
transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }],
},
});
export default HeaderBackButton;

View File

@@ -1,21 +1,35 @@
import * as React from 'react';
import { StyleSheet, Platform, ViewProps } from 'react-native';
import Animated from 'react-native-reanimated';
import { useTheme } from '@react-navigation/native';
export default function HeaderBackground({ style, ...rest }: ViewProps) {
return <Animated.View style={[styles.container, style]} {...rest} />;
const { colors } = useTheme();
return (
<Animated.View
style={[
styles.container,
{
backgroundColor: colors.card,
borderBottomColor: colors.border,
shadowColor: colors.border,
},
style,
]}
{...rest}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
...Platform.select({
android: {
elevation: 4,
},
ios: {
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowOpacity: 0.85,
shadowRadius: 0,
shadowOffset: {
@@ -25,7 +39,6 @@ const styles = StyleSheet.create({
},
default: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: 'rgba(0, 0, 0, 0.20)',
},
}),
},

View File

@@ -339,7 +339,8 @@ export default class HeaderSegment extends React.Component<Props, State> {
children: currentTitle,
onLayout: this.handleTitleLayout,
allowFontScaling: titleAllowFontScaling,
style: [{ color: headerTintColor }, customTitleStyle],
tintColor: headerTintColor,
style: customTitleStyle,
})}
</Animated.View>
{right ? (

View File

@@ -1,14 +1,26 @@
import * as React from 'react';
import { StyleSheet, Platform, TextProps } from 'react-native';
import Animated from 'react-native-reanimated';
import { useTheme } from '@react-navigation/native';
type Props = TextProps & {
tintColor?: string;
children?: string;
};
export default function HeaderTitle({ style, ...rest }: Props) {
export default function HeaderTitle({ tintColor, style, ...rest }: Props) {
const { colors } = useTheme();
return (
<Animated.Text numberOfLines={1} {...rest} style={[styles.title, style]} />
<Animated.Text
numberOfLines={1}
{...rest}
style={[
styles.title,
{ color: tintColor === undefined ? colors.text : tintColor },
style,
]}
/>
);
}
@@ -17,17 +29,14 @@ const styles = StyleSheet.create({
ios: {
fontSize: 17,
fontWeight: '600',
color: 'rgba(0, 0, 0, .9)',
},
android: {
fontSize: 20,
fontWeight: '500',
color: 'rgba(0, 0, 0, .9)',
},
default: {
fontSize: 18,
fontWeight: '500',
color: '#3c4043',
},
}),
});

View File

@@ -853,11 +853,7 @@ export default class Card extends React.Component<Props> {
<PointerEventsView
active={active}
progress={this.props.current}
style={[
styles.content,
transparent ? styles.transparent : styles.opaque,
contentStyle,
]}
style={[styles.content, contentStyle]}
>
<StackCardAnimationContext.Provider value={animationContext}>
{children}
@@ -905,10 +901,4 @@ const styles = StyleSheet.create({
height: 3,
shadowOffset: { width: 1, height: -1 },
},
transparent: {
backgroundColor: 'transparent',
},
opaque: {
backgroundColor: '#eee',
},
});

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import Animated from 'react-native-reanimated';
import { StackNavigationState } from '@react-navigation/routers';
import { Route } from '@react-navigation/native';
import { Route, useTheme } from '@react-navigation/native';
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import Card from './Card';
import { Scene, Layout, StackHeaderMode, TransitionPreset } from '../../types';
@@ -53,30 +53,58 @@ type Props = TransitionPreset & {
floatingHeaderHeight: number;
};
export default class CardContainer extends React.PureComponent<Props> {
private handleOpen = () => {
const { scene, onTransitionEnd, onOpenRoute } = this.props;
export default function CardContainer({
active,
cardOverlayEnabled,
cardShadowEnabled,
cardStyle,
cardStyleInterpolator,
cardTransparent,
closing,
current,
floatingHeaderHeight,
focused,
gestureDirection,
gestureEnabled,
gestureResponseDistance,
gestureVelocityImpact,
getPreviousRoute,
headerMode,
headerShown,
headerStyleInterpolator,
headerTransparent,
index,
layout,
onCloseRoute,
onGoBack,
onOpenRoute,
onPageChangeCancel,
onPageChangeConfirm,
onPageChangeStart,
onTransitionEnd,
onTransitionStart,
previousScene,
renderHeader,
renderScene,
safeAreaInsetBottom,
safeAreaInsetLeft,
safeAreaInsetRight,
safeAreaInsetTop,
scene,
state,
transitionSpec,
}: Props) {
const handleOpen = () => {
onTransitionEnd && onTransitionEnd({ route: scene.route }, false);
onOpenRoute({ route: scene.route });
};
private handleClose = () => {
const { scene, onTransitionEnd, onCloseRoute } = this.props;
const handleClose = () => {
onTransitionEnd && onTransitionEnd({ route: scene.route }, true);
onCloseRoute({ route: scene.route });
};
private handleTransitionStart = ({ closing }: { closing: boolean }) => {
const {
scene,
onTransitionStart,
onPageChangeConfirm,
onPageChangeCancel,
onGoBack,
} = this.props;
const handleTransitionStart = ({ closing }: { closing: boolean }) => {
if (closing) {
onPageChangeConfirm && onPageChangeConfirm();
} else {
@@ -87,103 +115,70 @@ export default class CardContainer extends React.PureComponent<Props> {
closing && onGoBack({ route: scene.route });
};
render() {
const {
index,
layout,
active,
focused,
closing,
current,
state,
scene,
previousScene,
safeAreaInsetTop,
safeAreaInsetRight,
safeAreaInsetBottom,
safeAreaInsetLeft,
cardTransparent,
cardOverlayEnabled,
cardShadowEnabled,
cardStyle,
onPageChangeStart,
onPageChangeCancel,
gestureEnabled,
gestureResponseDistance,
gestureVelocityImpact,
floatingHeaderHeight,
headerShown,
getPreviousRoute,
headerMode,
headerTransparent,
renderHeader,
renderScene,
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
} = this.props;
const insets = {
top: safeAreaInsetTop,
right: safeAreaInsetRight,
bottom: safeAreaInsetBottom,
left: safeAreaInsetLeft,
};
const insets = {
top: safeAreaInsetTop,
right: safeAreaInsetRight,
bottom: safeAreaInsetBottom,
left: safeAreaInsetLeft,
};
const { colors } = useTheme();
return (
<Card
index={index}
active={active}
transparent={cardTransparent}
gestureDirection={gestureDirection}
layout={layout}
insets={insets}
current={current}
next={scene.progress.next}
closing={closing}
onOpen={this.handleOpen}
onClose={this.handleClose}
overlayEnabled={cardOverlayEnabled}
shadowEnabled={cardShadowEnabled}
onTransitionStart={this.handleTransitionStart}
onGestureBegin={onPageChangeStart}
onGestureCanceled={onPageChangeCancel}
gestureEnabled={gestureEnabled}
gestureResponseDistance={gestureResponseDistance}
gestureVelocityImpact={gestureVelocityImpact}
transitionSpec={transitionSpec}
styleInterpolator={cardStyleInterpolator}
accessibilityElementsHidden={!focused}
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
pointerEvents="box-none"
containerStyle={
headerMode === 'float' && !headerTransparent && headerShown !== false
? { marginTop: floatingHeaderHeight }
: null
}
contentStyle={cardStyle}
style={StyleSheet.absoluteFill}
>
<View style={styles.container}>
<View style={styles.scene}>
{renderScene({ route: scene.route })}
</View>
{headerMode === 'screen'
? renderHeader({
mode: 'screen',
layout,
insets,
scenes: [previousScene, scene],
state,
getPreviousRoute,
styleInterpolator: headerStyleInterpolator,
})
: null}
</View>
</Card>
);
}
return (
<Card
index={index}
active={active}
transparent={cardTransparent}
gestureDirection={gestureDirection}
layout={layout}
insets={insets}
current={current}
next={scene.progress.next}
closing={closing}
onOpen={handleOpen}
onClose={handleClose}
overlayEnabled={cardOverlayEnabled}
shadowEnabled={cardShadowEnabled}
onTransitionStart={handleTransitionStart}
onGestureBegin={onPageChangeStart}
onGestureCanceled={onPageChangeCancel}
gestureEnabled={gestureEnabled}
gestureResponseDistance={gestureResponseDistance}
gestureVelocityImpact={gestureVelocityImpact}
transitionSpec={transitionSpec}
styleInterpolator={cardStyleInterpolator}
accessibilityElementsHidden={!focused}
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
pointerEvents="box-none"
containerStyle={
headerMode === 'float' && !headerTransparent && headerShown !== false
? { marginTop: floatingHeaderHeight }
: null
}
contentStyle={[
{
backgroundColor: cardTransparent ? 'transparent' : colors.background,
},
cardStyle,
]}
style={StyleSheet.absoluteFill}
>
<View style={styles.container}>
<View style={styles.scene}>{renderScene({ route: scene.route })}</View>
{headerMode === 'screen'
? renderHeader({
mode: 'screen',
layout,
insets,
scenes: [previousScene, scene],
state,
getPreviousRoute,
styleInterpolator: headerStyleInterpolator,
})
: null}
</View>
</Card>
);
}
const styles = StyleSheet.create({