From 00fc616de0572bade8aa85052cdc8290360b1d7f Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sat, 14 Dec 2019 22:25:25 +0100 Subject: [PATCH] feat: add custom theme support (#211) --- example/package.json | 1 + example/src/Screens/MaterialTopTabs.tsx | 21 +- example/src/Screens/NativeStack.tsx | 187 ++++++++------- example/src/Shared/Albums.tsx | 2 +- example/src/Shared/Article.tsx | 28 ++- example/src/Shared/Chat.tsx | 35 ++- example/src/Shared/Contacts.tsx | 34 +-- example/src/index.tsx | 211 +++++++++++------ packages/bottom-tabs/package.json | 4 +- .../bottom-tabs/src/views/BottomTabBar.tsx | 14 +- .../bottom-tabs/src/views/BottomTabItem.tsx | 21 +- .../bottom-tabs/src/views/BottomTabView.tsx | 31 ++- packages/drawer/package.json | 3 +- packages/drawer/src/index.tsx | 1 + packages/drawer/src/types.tsx | 2 +- packages/drawer/src/views/DrawerContent.tsx | 34 +-- .../src/views/DrawerContentScrollView.tsx | 43 ++++ packages/drawer/src/views/DrawerItem.tsx | 38 +-- .../src/views/MaterialBottomTabView.tsx | 20 +- packages/material-top-tabs/package.json | 3 +- .../src/views/MaterialTopTabBar.tsx | 45 ++-- .../src/views/MaterialTopTabView.tsx | 17 +- .../native-stack/src/views/HeaderConfig.tsx | 15 +- .../src/views/NativeStackView.tsx | 21 +- .../native/src/NavigationNativeContainer.tsx | 18 +- packages/native/src/index.tsx | 5 + packages/native/src/theming/DarkTheme.tsx | 14 ++ packages/native/src/theming/DefaultTheme.tsx | 14 ++ packages/native/src/theming/ThemeContext.tsx | 7 + packages/native/src/theming/ThemeProvider.tsx | 14 ++ packages/native/src/theming/useTheme.tsx | 8 + packages/native/src/types.tsx | 10 + packages/stack/src/types.tsx | 4 + .../src/views/Header/HeaderBackButton.tsx | 161 ++++++------- .../src/views/Header/HeaderBackground.tsx | 21 +- .../stack/src/views/Header/HeaderSegment.tsx | 3 +- .../stack/src/views/Header/HeaderTitle.tsx | 19 +- packages/stack/src/views/Stack/Card.tsx | 12 +- .../stack/src/views/Stack/CardContainer.tsx | 219 +++++++++--------- yarn.lock | 19 ++ 40 files changed, 846 insertions(+), 533 deletions(-) create mode 100644 packages/drawer/src/views/DrawerContentScrollView.tsx create mode 100644 packages/native/src/theming/DarkTheme.tsx create mode 100644 packages/native/src/theming/DefaultTheme.tsx create mode 100644 packages/native/src/theming/ThemeContext.tsx create mode 100644 packages/native/src/theming/ThemeProvider.tsx create mode 100644 packages/native/src/theming/useTheme.tsx create mode 100644 packages/native/src/types.tsx diff --git a/example/package.json b/example/package.json index 4c1cf7d4..33b3f416 100644 --- a/example/package.json +++ b/example/package.json @@ -20,6 +20,7 @@ "dependencies": { "@expo/vector-icons": "^10.0.0", "@react-native-community/masked-view": "0.1.5", + "color": "^3.1.2", "expo": "^36.0.0", "expo-asset": "~8.0.0", "query-string": "^6.9.0", diff --git a/example/src/Screens/MaterialTopTabs.tsx b/example/src/Screens/MaterialTopTabs.tsx index f4131f0d..2d0025d2 100644 --- a/example/src/Screens/MaterialTopTabs.tsx +++ b/example/src/Screens/MaterialTopTabs.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { StyleSheet } from 'react-native'; import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'; import Albums from '../Shared/Albums'; import Contacts from '../Shared/Contacts'; @@ -15,13 +14,7 @@ const MaterialTopTabs = createMaterialTopTabNavigator(); export default function MaterialTopTabsScreen() { return ( - + ); } - -const styles = StyleSheet.create({ - tabBar: { - backgroundColor: 'white', - }, - tabLabel: { - color: 'black', - }, - tabIndicator: { - backgroundColor: 'tomato', - }, -}); diff --git a/example/src/Screens/NativeStack.tsx b/example/src/Screens/NativeStack.tsx index 91a0497b..da2d0c66 100644 --- a/example/src/Screens/NativeStack.tsx +++ b/example/src/Screens/NativeStack.tsx @@ -7,6 +7,7 @@ import { RouteProp, ParamListBase, useFocusEffect, + useTheme, } from '@react-navigation/native'; import { DrawerNavigationProp } from '@react-navigation/drawer'; import { StackNavigationProp } from '@react-navigation/stack'; @@ -23,90 +24,113 @@ type NativeStackParams = { type NativeStackNavigation = NativeStackNavigationProp; +const Title = ({ children }: { children: React.ReactNode }) => { + const { colors } = useTheme(); + + return {children}; +}; + +const Paragraph = ({ children }: { children: React.ReactNode }) => { + const { colors } = useTheme(); + + return ( + {children} + ); +}; + const ArticleScreen = ({ navigation, }: { navigation: NativeStackNavigation; route: RouteProp; -}) => ( - - - - - - What is Lorem Ipsum? - - Lorem Ipsum is simply dummy text of the printing and typesetting industry. - Lorem Ipsum has been the industry's standard dummy text ever since - the 1500s, when an unknown printer took a galley of type and scrambled it - to make a type specimen book. It has survived not only five centuries, but - also the leap into electronic typesetting, remaining essentially - unchanged. It was popularised in the 1960s with the release of Letraset - sheets containing Lorem Ipsum passages, and more recently with desktop - publishing software like Aldus PageMaker including versions of Lorem - Ipsum. - - Where does it come from? - - Contrary to popular belief, Lorem Ipsum is not simply random text. It has - roots in a piece of classical Latin literature from 45 BC, making it over - 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney - College in Virginia, looked up one of the more obscure Latin words, - consectetur, from a Lorem Ipsum passage, and going through the cites of - the word in classical literature, discovered the undoubtable source. Lorem - Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum - et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 - BC. This book is a treatise on the theory of ethics, very popular during - the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor - sit amet..", comes from a line in section 1.10.32. - - - The standard chunk of Lorem Ipsum used since the 1500s is reproduced below - for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus - Bonorum et Malorum" by Cicero are also reproduced in their exact - original form, accompanied by English versions from the 1914 translation - by H. Rackham. - - Why do we use it? - - It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. The point of using - Lorem Ipsum is that it has a more-or-less normal distribution of letters, - as opposed to using "Content here, content here", making it look - like readable English. Many desktop publishing packages and web page - editors now use Lorem Ipsum as their default model text, and a search for - "lorem ipsum" will uncover many web sites still in their - infancy. Various versions have evolved over the years, sometimes by - accident, sometimes on purpose (injected humour and the like). - - Where can I get some? - - There are many variations of passages of Lorem Ipsum available, but the - majority have suffered alteration in some form, by injected humour, or - randomised words which don't look even slightly believable. If you - are going to use a passage of Lorem Ipsum, you need to be sure there - isn't anything embarrassing hidden in the middle of text. All the - Lorem Ipsum generators on the Internet tend to repeat predefined chunks as - necessary, making this the first true generator on the Internet. It uses a - dictionary of over 200 Latin words, combined with a handful of model - sentence structures, to generate Lorem Ipsum which looks reasonable. The - generated Lorem Ipsum is therefore always free from repetition, injected - humour, or non-characteristic words etc. - - -); +}) => { + const { colors } = useTheme(); + + return ( + + + + + + What is Lorem Ipsum? + + Lorem Ipsum is simply dummy text of the printing and typesetting + industry. Lorem Ipsum has been the industry's standard dummy text + ever since the 1500s, when an unknown printer took a galley of type and + scrambled it to make a type specimen book. It has survived not only five + centuries, but also the leap into electronic typesetting, remaining + essentially unchanged. It was popularised in the 1960s with the release + of Letraset sheets containing Lorem Ipsum passages, and more recently + with desktop publishing software like Aldus PageMaker including versions + of Lorem Ipsum. + + Where does it come from? + + Contrary to popular belief, Lorem Ipsum is not simply random text. It + has roots in a piece of classical Latin literature from 45 BC, making it + over 2000 years old. Richard McClintock, a Latin professor at + Hampden-Sydney College in Virginia, looked up one of the more obscure + Latin words, consectetur, from a Lorem Ipsum passage, and going through + the cites of the word in classical literature, discovered the + undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 + of "de Finibus Bonorum et Malorum" (The Extremes of Good and + Evil) by Cicero, written in 45 BC. This book is a treatise on the theory + of ethics, very popular during the Renaissance. The first line of Lorem + Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in + section 1.10.32. + + + The standard chunk of Lorem Ipsum used since the 1500s is reproduced + below for those interested. Sections 1.10.32 and 1.10.33 from "de + Finibus Bonorum et Malorum" by Cicero are also reproduced in their + exact original form, accompanied by English versions from the 1914 + translation by H. Rackham. + + Why do we use it? + + It is a long established fact that a reader will be distracted by the + readable content of a page when looking at its layout. The point of + using Lorem Ipsum is that it has a more-or-less normal distribution of + letters, as opposed to using "Content here, content here", + making it look like readable English. Many desktop publishing packages + and web page editors now use Lorem Ipsum as their default model text, + and a search for "lorem ipsum" will uncover many web sites + still in their infancy. Various versions have evolved over the years, + sometimes by accident, sometimes on purpose (injected humour and the + like). + + Where can I get some? + + There are many variations of passages of Lorem Ipsum available, but the + majority have suffered alteration in some form, by injected humour, or + randomised words which don't look even slightly believable. If you + are going to use a passage of Lorem Ipsum, you need to be sure there + isn't anything embarrassing hidden in the middle of text. All the + Lorem Ipsum generators on the Internet tend to repeat predefined chunks + as necessary, making this the first true generator on the Internet. It + uses a dictionary of over 200 Latin words, combined with a handful of + model sentence structures, to generate Lorem Ipsum which looks + reasonable. The generated Lorem Ipsum is therefore always free from + repetition, injected humour, or non-characteristic words etc. + + + ); +}; const AlbumsScreen = ({ navigation, @@ -191,21 +215,16 @@ const styles = StyleSheet.create({ button: { margin: 8, }, - container: { - backgroundColor: 'white', - }, content: { paddingVertical: 16, }, title: { - color: '#000', fontWeight: 'bold', fontSize: 24, marginVertical: 8, marginHorizontal: 16, }, paragraph: { - color: '#000', fontSize: 16, lineHeight: 24, marginVertical: 8, diff --git a/example/src/Shared/Albums.tsx b/example/src/Shared/Albums.tsx index 83be09f4..467507b8 100644 --- a/example/src/Shared/Albums.tsx +++ b/example/src/Shared/Albums.tsx @@ -38,7 +38,7 @@ export default function Albums() { const styles = StyleSheet.create({ container: { - backgroundColor: '#343C46', + backgroundColor: '#000', }, content: { flexDirection: 'row', diff --git a/example/src/Shared/Article.tsx b/example/src/Shared/Article.tsx index c5c1cc20..8b499b76 100644 --- a/example/src/Shared/Article.tsx +++ b/example/src/Shared/Article.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { View, Text, Image, ScrollView, StyleSheet } from 'react-native'; -import { useScrollToTop } from '@react-navigation/native'; +import { useScrollToTop, useTheme } from '@react-navigation/native'; type Props = { date?: string; @@ -19,10 +19,12 @@ export default function Article({ useScrollToTop(ref); + const { colors } = useTheme(); + return ( @@ -31,24 +33,26 @@ export default function Article({ source={require('../../assets/avatar-1.png')} /> - {author.name} - {date} + + {author.name} + + {date} - Lorem Ipsum - + Lorem Ipsum + Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. - + Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. - + Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very @@ -61,9 +65,6 @@ export default function Article({ } const styles = StyleSheet.create({ - container: { - backgroundColor: 'white', - }, content: { paddingVertical: 16, }, @@ -77,13 +78,12 @@ const styles = StyleSheet.create({ justifyContent: 'center', }, name: { - color: '#000', fontWeight: 'bold', fontSize: 16, lineHeight: 24, }, timestamp: { - color: '#999', + opacity: 0.5, fontSize: 14, lineHeight: 21, }, @@ -93,14 +93,12 @@ const styles = StyleSheet.create({ borderRadius: 24, }, title: { - color: '#000', fontWeight: 'bold', fontSize: 36, marginVertical: 8, marginHorizontal: 16, }, paragraph: { - color: '#000', fontSize: 16, lineHeight: 24, marginVertical: 8, diff --git a/example/src/Shared/Chat.tsx b/example/src/Shared/Chat.tsx index 57d3581a..8a18a77f 100644 --- a/example/src/Shared/Chat.tsx +++ b/example/src/Shared/Chat.tsx @@ -7,7 +7,8 @@ import { ScrollView, StyleSheet, } from 'react-native'; -import { useScrollToTop } from '@react-navigation/native'; +import { useScrollToTop, useTheme } from '@react-navigation/native'; +import Color from 'color'; const MESSAGES = [ 'okay', @@ -21,6 +22,8 @@ export default function Chat() { useScrollToTop(ref); + const { colors } = useTheme(); + return ( - + {text} @@ -56,7 +62,14 @@ export default function Chat() { })} @@ -67,7 +80,6 @@ export default function Chat() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#eceff1', }, inverted: { transform: [{ scaleY: -1 }], @@ -97,22 +109,9 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, borderRadius: 20, }, - sent: { - backgroundColor: '#cfd8dc', - }, - received: { - backgroundColor: '#2196F3', - }, - sentText: { - color: 'black', - }, - receivedText: { - color: 'white', - }, input: { height: 48, paddingVertical: 12, paddingHorizontal: 24, - backgroundColor: 'white', }, }); diff --git a/example/src/Shared/Contacts.tsx b/example/src/Shared/Contacts.tsx index 454874e3..238a5983 100644 --- a/example/src/Shared/Contacts.tsx +++ b/example/src/Shared/Contacts.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { View, Text, FlatList, StyleSheet } from 'react-native'; -import { useScrollToTop } from '@react-navigation/native'; +import { useScrollToTop, useTheme } from '@react-navigation/native'; type Item = { name: string; number: number }; @@ -57,27 +57,35 @@ const CONTACTS: Item[] = [ { name: 'Vincent Sandoval', number: 2606111495 }, ]; -class ContactItem extends React.PureComponent<{ - item: { name: string; number: number }; -}> { - render() { - const { item } = this.props; +const ContactItem = React.memo( + ({ item }: { item: { name: string; number: number } }) => { + const { colors } = useTheme(); return ( - + {item.name.slice(0, 1).toUpperCase()} - {item.name} - {item.number} + {item.name} + + {item.number} + ); } -} +); + +const ItemSeparator = () => { + const { colors } = useTheme(); + + return ( + + ); +}; export default function Contacts() { const ref = React.useRef>(null); @@ -86,8 +94,6 @@ export default function Contacts() { const renderItem = ({ item }: { item: Item }) => ; - const ItemSeparator = () => ; - return ( (); const Stack = createStackNavigator(); -const PERSISTENCE_KEY = 'NAVIGATION_STATE'; +const NAVIGATION_PERSISTENCE_KEY = 'NAVIGATION_STATE'; +const THEME_PERSISTENCE_KEY = 'THEME_TYPE'; Asset.loadAsync(StackAssets); @@ -102,6 +121,8 @@ export default function App() { }, }); + const [theme, setTheme] = React.useState(DefaultTheme); + const [isReady, setIsReady] = React.useState(false); const [initialState, setInitialState] = React.useState< InitialState | undefined @@ -113,7 +134,9 @@ export default function App() { let state = await getInitialState(); if (state === undefined) { - const savedState = await AsyncStorage.getItem(PERSISTENCE_KEY); + const savedState = await AsyncStorage.getItem( + NAVIGATION_PERSISTENCE_KEY + ); state = savedState ? JSON.parse(savedState) : undefined; } @@ -121,6 +144,14 @@ export default function App() { setInitialState(state); } } finally { + try { + const themeName = await AsyncStorage.getItem(THEME_PERSISTENCE_KEY); + + setTheme(themeName === 'dark' ? DarkTheme : DefaultTheme); + } catch (e) { + // Ignore + } + setIsReady(true); } }; @@ -128,78 +159,126 @@ export default function App() { restoreState(); }, [getInitialState]); + const paperTheme = React.useMemo(() => { + const t = theme.dark ? PaperDarkTheme : PaperLightTheme; + + return { + ...t, + colors: { + ...t.colors, + ...theme.colors, + surface: theme.colors.card, + accent: theme.dark ? 'rgb(255, 55, 95)' : 'rgb(255, 45, 85)', + }, + }; + }, [theme.colors, theme.dark]); + if (!isReady) { return null; } return ( - - AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)) - } - > - - ( - - ), - }} - > - {({ - navigation, - }: { - navigation: DrawerNavigationProp; - }) => ( - - ( - navigation.toggleDrawer()} - /> - ), - }} - > - {({ - navigation, - }: { - navigation: StackNavigationProp; - }) => ( - - {(Object.keys(SCREENS) as Array).map( - name => ( + + {Platform.OS === 'ios' && ( + + )} + + AsyncStorage.setItem( + NAVIGATION_PERSISTENCE_KEY, + JSON.stringify(state) + ) + } + theme={theme} + > + + ( + + ), + }} + > + {({ + navigation, + }: { + navigation: DrawerNavigationProp; + }) => ( + + ( + navigation.toggleDrawer()} + /> + ), + }} + > + {({ + navigation, + }: { + navigation: StackNavigationProp; + }) => ( + + + Dark theme + { + AsyncStorage.setItem( + THEME_PERSISTENCE_KEY, + theme.dark ? 'light' : 'dark' + ); + + setTheme(t => (t.dark ? DefaultTheme : DarkTheme)); + }} + /> + + + {(Object.keys(SCREENS) as Array< + keyof typeof SCREENS + >).map(name => ( navigation.push(name)} /> - ) - )} - + ))} + + )} + + {(Object.keys(SCREENS) as Array).map( + name => ( + + ) )} - - {(Object.keys(SCREENS) as Array).map( - name => ( - - ) - )} - - )} - - - + + )} + + + + ); } diff --git a/packages/bottom-tabs/package.json b/packages/bottom-tabs/package.json index 1f4d1966..b460fd3e 100644 --- a/packages/bottom-tabs/package.json +++ b/packages/bottom-tabs/package.json @@ -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", diff --git a/packages/bottom-tabs/src/views/BottomTabBar.tsx b/packages/bottom-tabs/src/views/BottomTabBar.tsx index b949bb4b..8c800045 100644 --- a/packages/bottom-tabs/src/views/BottomTabBar.tsx +++ b/packages/bottom-tabs/src/views/BottomTabBar.tsx @@ -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({ { if (showLabel === false) { return null; diff --git a/packages/bottom-tabs/src/views/BottomTabView.tsx b/packages/bottom-tabs/src/views/BottomTabView.tsx index 5b21b8ae..c986ef46 100644 --- a/packages/bottom-tabs/src/views/BottomTabView.tsx +++ b/packages/bottom-tabs/src/views/BottomTabView.tsx @@ -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 ( + + {children} + + ); +} + export default class BottomTabView extends React.Component { static defaultProps = { lazy: true, @@ -97,15 +118,9 @@ export default class BottomTabView extends React.Component { style={StyleSheet.absoluteFill} isVisible={isFocused} > - + {descriptors[route.key].render()} - + ); })} diff --git a/packages/drawer/package.json b/packages/drawer/package.json index d15da303..6bfc7ec3 100644 --- a/packages/drawer/package.json +++ b/packages/drawer/package.json @@ -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", diff --git a/packages/drawer/src/index.tsx b/packages/drawer/src/index.tsx index e8be5a7e..4f46688e 100644 --- a/packages/drawer/src/index.tsx +++ b/packages/drawer/src/index.tsx @@ -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 diff --git a/packages/drawer/src/types.tsx b/packages/drawer/src/types.tsx index 5a56dc05..27be1afc 100644 --- a/packages/drawer/src/types.tsx +++ b/packages/drawer/src/types.tsx @@ -121,7 +121,7 @@ export type DrawerNavigationOptions = { export type DrawerContentComponentProps = T & { state: DrawerNavigationState; - navigation: NavigationHelpers; + navigation: DrawerNavigationHelpers; descriptors: DrawerDescriptorMap; /** * Animated node which represents the current progress of the drawer's open state. diff --git a/packages/drawer/src/views/DrawerContent.tsx b/packages/drawer/src/views/DrawerContent.tsx index 37ac7579..7104bdab 100644 --- a/packages/drawer/src/views/DrawerContent.tsx +++ b/packages/drawer/src/views/DrawerContent.tsx @@ -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 ( - - - + + + ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, -}); diff --git a/packages/drawer/src/views/DrawerContentScrollView.tsx b/packages/drawer/src/views/DrawerContentScrollView.tsx new file mode 100644 index 00000000..9a702b62 --- /dev/null +++ b/packages/drawer/src/views/DrawerContentScrollView.tsx @@ -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 ( + + {children} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, +}); diff --git a/packages/drawer/src/views/DrawerItem.tsx b/packages/drawer/src/views/DrawerItem.tsx index a951d62e..e8fdf0d4 100644 --- a/packages/drawer/src/views/DrawerItem.tsx +++ b/packages/drawer/src/views/DrawerItem.tsx @@ -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 diff --git a/packages/material-bottom-tabs/src/views/MaterialBottomTabView.tsx b/packages/material-bottom-tabs/src/views/MaterialBottomTabView.tsx index 7d4138ad..6b87006e 100644 --- a/packages/material-bottom-tabs/src/views/MaterialBottomTabView.tsx +++ b/packages/material-bottom-tabs/src/views/MaterialBottomTabView.tsx @@ -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 ( navigation.dispatch({ diff --git a/packages/material-top-tabs/package.json b/packages/material-top-tabs/package.json index efe83537..f4c99b43 100644 --- a/packages/material-top-tabs/package.json +++ b/packages/material-top-tabs/package.json @@ -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", diff --git a/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx b/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx index 7d5f03b6..b6580437 100644 --- a/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx +++ b/packages/material-top-tabs/src/views/MaterialTopTabBar.tsx @@ -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 ( descriptors[route.key].options.tabBarAccessibilityLabel } diff --git a/packages/material-top-tabs/src/views/MaterialTopTabView.tsx b/packages/material-top-tabs/src/views/MaterialTopTabView.tsx index 552d4122..08ff254d 100644 --- a/packages/material-top-tabs/src/views/MaterialTopTabView.tsx +++ b/packages/material-top-tabs/src/views/MaterialTopTabView.tsx @@ -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 ( + + {children} + + ); +} + export default class MaterialTopTabView extends React.PureComponent { static defaultProps = { tabBarPosition: 'top', @@ -93,7 +104,9 @@ export default class MaterialTopTabView extends React.PureComponent { target: state.key, }) } - renderScene={({ route }) => descriptors[route.key].render()} + renderScene={({ route }) => ( + {descriptors[route.key].render()} + )} navigationState={state} renderTabBar={this.renderTabBar} renderLazyPlaceholder={this.renderLazyPlaceholder} diff --git a/packages/native-stack/src/views/HeaderConfig.tsx b/packages/native-stack/src/views/HeaderConfig.tsx index 986dd1cf..3a1fcd79 100644 --- a/packages/native-stack/src/views/HeaderConfig.tsx +++ b/packages/native-stack/src/views/HeaderConfig.tsx @@ -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 ? ( {headerRight()} diff --git a/packages/native-stack/src/views/NativeStackView.tsx b/packages/native-stack/src/views/NativeStackView.tsx index 8cf2c759..5d6b2aa2 100644 --- a/packages/native-stack/src/views/NativeStackView.tsx +++ b/packages/native-stack/src/views/NativeStackView.tsx @@ -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 ( - + {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({ }} > - {renderScene()} + + {renderScene()} + ); })} @@ -64,11 +75,7 @@ export default function NativeStackView({ } const styles = StyleSheet.create({ - content: { - flex: 1, - backgroundColor: '#eee', - }, - scenes: { + container: { flex: 1, }, }); diff --git a/packages/native/src/NavigationNativeContainer.tsx b/packages/native/src/NavigationNativeContainer.tsx index cc5d6e37..d40a75c2 100644 --- a/packages/native/src/NavigationNativeContainer.tsx +++ b/packages/native/src/NavigationNativeContainer.tsx @@ -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 ) { const refContainer = React.useRef(null); @@ -27,11 +35,9 @@ const NavigationNativeContainer = React.forwardRef(function NativeContainer( React.useImperativeHandle(ref, () => refContainer.current); return ( - + + + ); }); diff --git a/packages/native/src/index.tsx b/packages/native/src/index.tsx index 17507389..8d52375d 100644 --- a/packages/native/src/index.tsx +++ b/packages/native/src/index.tsx @@ -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'; diff --git a/packages/native/src/theming/DarkTheme.tsx b/packages/native/src/theming/DarkTheme.tsx new file mode 100644 index 00000000..cdd3cf60 --- /dev/null +++ b/packages/native/src/theming/DarkTheme.tsx @@ -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; diff --git a/packages/native/src/theming/DefaultTheme.tsx b/packages/native/src/theming/DefaultTheme.tsx new file mode 100644 index 00000000..04241979 --- /dev/null +++ b/packages/native/src/theming/DefaultTheme.tsx @@ -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; diff --git a/packages/native/src/theming/ThemeContext.tsx b/packages/native/src/theming/ThemeContext.tsx new file mode 100644 index 00000000..3d8b58b4 --- /dev/null +++ b/packages/native/src/theming/ThemeContext.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; +import DefaultTheme from './DefaultTheme'; +import { Theme } from '../types'; + +const ThemeContext = React.createContext(DefaultTheme); + +export default ThemeContext; diff --git a/packages/native/src/theming/ThemeProvider.tsx b/packages/native/src/theming/ThemeProvider.tsx new file mode 100644 index 00000000..a88baeb1 --- /dev/null +++ b/packages/native/src/theming/ThemeProvider.tsx @@ -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 ( + {children} + ); +} diff --git a/packages/native/src/theming/useTheme.tsx b/packages/native/src/theming/useTheme.tsx new file mode 100644 index 00000000..f3489eb9 --- /dev/null +++ b/packages/native/src/theming/useTheme.tsx @@ -0,0 +1,8 @@ +import * as React from 'react'; +import ThemeContext from './ThemeContext'; + +export default function useTheme() { + const theme = React.useContext(ThemeContext); + + return theme; +} diff --git a/packages/native/src/types.tsx b/packages/native/src/types.tsx new file mode 100644 index 00000000..b4c9f7b3 --- /dev/null +++ b/packages/native/src/types.tsx @@ -0,0 +1,10 @@ +export type Theme = { + dark: boolean; + colors: { + primary: string; + background: string; + card: string; + text: string; + border: string; + }; +}; diff --git a/packages/stack/src/types.tsx b/packages/stack/src/types.tsx index 69c4df37..e95dd07b 100644 --- a/packages/stack/src/types.tsx +++ b/packages/stack/src/types.tsx @@ -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. */ diff --git a/packages/stack/src/views/Header/HeaderBackButton.tsx b/packages/stack/src/views/Header/HeaderBackButton.tsx index 41248795..07478a36 100644 --- a/packages/stack/src/views/Header/HeaderBackButton.tsx +++ b/packages/stack/src/views/Header/HeaderBackButton.tsx @@ -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 State = { - fullLabelWidth?: number; -}; - -class HeaderBackButton extends React.Component { - static defaultProps = { - pressColorAndroid: 'rgba(0, 0, 0, .32)', - tintColor: Platform.select({ - ios: '#037aff', - web: '#5f6368', - }), - labelVisible: Platform.OS === 'ios', - truncatedLabel: 'Back', - }; - - state: State = {}; - - private handleLabelLayout = (e: LayoutChangeEvent) => { - const { onLabelLayout } = this.props; - +type Props = StackHeaderLeftButtonProps; + +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(); + + const [initialLabelWidth, setInitialLabelWidth] = React.useState< + undefined | number + >(undefined); + + const tintColor = + customTintColor !== undefined + ? customTintColor + : Platform.select({ + ios: colors.primary, + default: colors.text, + }); + + 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 { ); }; - 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 { } }; - 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 { 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 { {labelElement} ); - } - - private handlePress = () => - this.props.onPress && requestAnimationFrame(this.props.onPress); - - render() { - const { pressColorAndroid, label, disabled } = this.props; + }; - return ( - - - {this.renderBackImage()} - {this.renderLabel()} - - - ); - } + const handlePress = () => onPress && requestAnimationFrame(onPress); + + return ( + + + {renderBackImage()} + {renderLabel()} + + + ); } const styles = StyleSheet.create({ @@ -253,5 +246,3 @@ const styles = StyleSheet.create({ transform: [{ scaleX: I18nManager.isRTL ? -1 : 1 }], }, }); - -export default HeaderBackButton; diff --git a/packages/stack/src/views/Header/HeaderBackground.tsx b/packages/stack/src/views/Header/HeaderBackground.tsx index bff73d71..ea79f686 100644 --- a/packages/stack/src/views/Header/HeaderBackground.tsx +++ b/packages/stack/src/views/Header/HeaderBackground.tsx @@ -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 ; + const { colors } = useTheme(); + + return ( + + ); } 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)', }, }), }, diff --git a/packages/stack/src/views/Header/HeaderSegment.tsx b/packages/stack/src/views/Header/HeaderSegment.tsx index 9f40473b..25b92d6b 100644 --- a/packages/stack/src/views/Header/HeaderSegment.tsx +++ b/packages/stack/src/views/Header/HeaderSegment.tsx @@ -339,7 +339,8 @@ export default class HeaderSegment extends React.Component { children: currentTitle, onLayout: this.handleTitleLayout, allowFontScaling: titleAllowFontScaling, - style: [{ color: headerTintColor }, customTitleStyle], + tintColor: headerTintColor, + style: customTitleStyle, })} {right ? ( diff --git a/packages/stack/src/views/Header/HeaderTitle.tsx b/packages/stack/src/views/Header/HeaderTitle.tsx index 9d8d2912..f776e930 100644 --- a/packages/stack/src/views/Header/HeaderTitle.tsx +++ b/packages/stack/src/views/Header/HeaderTitle.tsx @@ -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 ( - + ); } @@ -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', }, }), }); diff --git a/packages/stack/src/views/Stack/Card.tsx b/packages/stack/src/views/Stack/Card.tsx index 2ca83f7d..32d6fdc8 100755 --- a/packages/stack/src/views/Stack/Card.tsx +++ b/packages/stack/src/views/Stack/Card.tsx @@ -853,11 +853,7 @@ export default class Card extends React.Component { {children} @@ -905,10 +901,4 @@ const styles = StyleSheet.create({ height: 3, shadowOffset: { width: 1, height: -1 }, }, - transparent: { - backgroundColor: 'transparent', - }, - opaque: { - backgroundColor: '#eee', - }, }); diff --git a/packages/stack/src/views/Stack/CardContainer.tsx b/packages/stack/src/views/Stack/CardContainer.tsx index 2d0cf653..ad479d82 100644 --- a/packages/stack/src/views/Stack/CardContainer.tsx +++ b/packages/stack/src/views/Stack/CardContainer.tsx @@ -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 { - 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 { 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 ( - - - - {renderScene({ route: scene.route })} - - {headerMode === 'screen' - ? renderHeader({ - mode: 'screen', - layout, - insets, - scenes: [previousScene, scene], - state, - getPreviousRoute, - styleInterpolator: headerStyleInterpolator, - }) - : null} - - - ); - } + return ( + + + {renderScene({ route: scene.route })} + {headerMode === 'screen' + ? renderHeader({ + mode: 'screen', + layout, + insets, + scenes: [previousScene, scene], + state, + getPreviousRoute, + styleInterpolator: headerStyleInterpolator, + }) + : null} + + + ); } const styles = StyleSheet.create({ diff --git a/yarn.lock b/yarn.lock index a7adfdff..51090570 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2721,6 +2721,25 @@ resolved "https://registry.yarnpkg.com/@types/cli-table/-/cli-table-0.3.0.tgz#f1857156bf5fd115c6a2db260ba0be1f8fc5671c" integrity sha512-QnZUISJJXyhyD6L1e5QwXDV/A5i2W1/gl6D6YMc8u0ncPepbv/B4w3S+izVvtAg60m6h+JP09+Y/0zF2mojlFQ== +"@types/color-convert@*": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d" + integrity sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg== + dependencies: + "@types/color-name" "*" + +"@types/color-name@*": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/color@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.0.tgz#40f8a6bf2fd86e969876b339a837d8ff1b0a6e30" + integrity sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q== + dependencies: + "@types/color-convert" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"