diff --git a/example/src/Examples/BadgeExample.tsx b/example/src/Examples/BadgeExample.tsx index d44bf45e10..9890af48ae 100644 --- a/example/src/Examples/BadgeExample.tsx +++ b/example/src/Examples/BadgeExample.tsx @@ -7,11 +7,14 @@ import { Paragraph, Switch, MD2Colors, + useTheme, + MD3Colors, } from 'react-native-paper'; import ScreenWrapper from '../ScreenWrapper'; const BadgeExample = () => { const [visible, setVisible] = React.useState(true); + const { isV3 } = useTheme(); return ( @@ -34,7 +37,14 @@ const BadgeExample = () => { 999+ @@ -45,11 +55,11 @@ const BadgeExample = () => { - + - + diff --git a/example/src/Examples/BottomNavigationExample.tsx b/example/src/Examples/BottomNavigationExample.tsx index 18d3d7b650..586e00446f 100644 --- a/example/src/Examples/BottomNavigationExample.tsx +++ b/example/src/Examples/BottomNavigationExample.tsx @@ -1,13 +1,14 @@ import * as React from 'react'; import { View, Image, Dimensions, StyleSheet, Platform } from 'react-native'; -import { BottomNavigation } from 'react-native-paper'; +import { BottomNavigation, useTheme } from 'react-native-paper'; import ScreenWrapper from '../ScreenWrapper'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; type RoutesState = Array<{ key: string; title: string; - icon: string; + focusedIcon: string; + unfocusedIcon?: string; color?: string; badge?: boolean; getAccessibilityLabel?: string; @@ -33,28 +34,42 @@ const PhotoGallery = ({ route }: Route) => { }; const BottomNavigationExample = () => { + const { isV3 } = useTheme(); const insets = useSafeAreaInsets(); const [index, setIndex] = React.useState(0); const [routes] = React.useState([ - { key: 'album', title: 'Album', icon: 'image-album', color: '#6200ee' }, + { + key: 'album', + title: 'Album', + focusedIcon: 'image-album', + ...(!isV3 && { color: '#6200ee' }), + }, { key: 'library', title: 'Library', - icon: 'inbox', - color: '#2962ff', + focusedIcon: 'inbox', badge: true, + ...(isV3 + ? { unfocusedIcon: 'inbox-outline' } + : { + color: '#2962ff', + }), }, { key: 'favorites', title: 'Favorites', - icon: 'heart', - color: '#00796b', + focusedIcon: 'heart', + ...(isV3 + ? { unfocusedIcon: 'heart-outline' } + : { + color: '#00796b', + }), }, { key: 'purchased', title: 'Purchased', - icon: 'shopping-music', - color: '#c51162', + focusedIcon: 'shopping-music', + ...(!isV3 && { color: '#c51162' }), }, ]); diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 1f12be80e9..2f60ced056 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -93,10 +93,14 @@ const Badge = ({ ...restStyle } = (StyleSheet.flatten(style) || {}) as TextStyle; - const textColor = getContrastingColor(backgroundColor || white, white, black); + const textColor = theme.isV3 + ? theme.colors.onError + : getContrastingColor(backgroundColor, white, black); const borderRadius = size / 2; + const paddingHorizontal = theme.isV3 ? 3 : 4; + return ( * const MyComponent = () => { * const [index, setIndex] = React.useState(0); * const [routes] = React.useState([ - * { key: 'music', title: 'Music', icon: 'queue-music' }, - * { key: 'albums', title: 'Albums', icon: 'album' }, - * { key: 'recents', title: 'Recents', icon: 'history' }, + * { key: 'music', title: 'Favorites', focusedIcon: 'heart', unfocusedIcon: 'heart-outline'}, + * { key: 'albums', title: 'Albums', focusedIcon: 'album' }, + * { key: 'recents', title: 'Recents', focusedIcon: 'history' }, + * { key: 'notifications', title: 'Notifications', focusedIcon: 'bell', unfocusedIcon: 'bell-outline' }, * ]); * * const renderScene = BottomNavigation.SceneMap({ @@ -343,8 +352,9 @@ const BottomNavigation = ({ sceneAnimationEnabled = false, onTabPress, onIndexChange, - shifting = navigationState.routes.length > 3, + shifting = theme.isV3 ? false : navigationState.routes.length > 3, safeAreaInsets, + compact = !theme.isV3, }: Props) => { const { scale } = theme.animation; @@ -435,13 +445,13 @@ const BottomNavigation = ({ Animated.parallel([ Animated.timing(rippleAnim, { toValue: 1, - duration: shifting ? 400 * scale : 0, + duration: theme.isV3 || shifting ? 400 * scale : 0, useNativeDriver: true, }), ...navigationState.routes.map((_, i) => Animated.timing(tabsAnims[i], { toValue: i === index ? 1 : 0, - duration: shifting ? 150 * scale : 0, + duration: theme.isV3 || shifting ? 150 * scale : 0, useNativeDriver: true, }) ), @@ -474,6 +484,7 @@ const BottomNavigation = ({ rippleAnim, scale, tabsAnims, + theme, ] ); @@ -537,7 +548,7 @@ const BottomNavigation = ({ ); const { routes } = navigationState; - const { colors, dark: isDarkTheme, mode } = theme; + const { colors, dark: isDarkTheme, mode, isV3 } = theme; const { backgroundColor: customBackground, elevation = 4 }: ViewStyle = StyleSheet.flatten(barStyle) || {}; @@ -548,7 +559,9 @@ const BottomNavigation = ({ ? overlay(elevation, colors?.surface) : colors?.primary; - const backgroundColor = shifting + const backgroundColor = isV3 + ? theme.colors.surface + : shifting ? indexAnim.interpolate({ inputRange: routes.map((_, i) => i), // FIXME: does outputRange support ColorValue or just strings? @@ -565,11 +578,19 @@ const BottomNavigation = ({ : true; const textColor = isDark ? white : black; + const activeTintColor = - typeof activeColor !== 'undefined' ? activeColor : textColor; + typeof activeColor !== 'undefined' + ? activeColor + : isV3 + ? theme.colors.onSecondaryContainer + : textColor; + const inactiveTintColor = typeof inactiveColor !== 'undefined' ? inactiveColor + : isV3 + ? theme.colors.onSurfaceVariant : color(textColor).alpha(0.5).rgb().string(); const touchColor = color(activeColor || activeTintColor) @@ -643,7 +664,7 @@ const BottomNavigation = ({ })} handleTabPress(index), testID: getTestID({ route }), accessibilityLabel: getAccessibilityLabel({ route }), @@ -772,17 +832,47 @@ const BottomNavigation = ({ accessibilityComponentType: 'button', accessibilityRole: Platform.OS === 'ios' ? 'button' : 'tab', accessibilityState: { selected: focused }, - style: styles.item, + style: [styles.item, isV3 && styles.v3Item], children: ( - + + {isV3 && ( + + )} {renderIcon ? ( renderIcon({ @@ -792,7 +882,7 @@ const BottomNavigation = ({ }) ) : ( @@ -801,7 +891,10 @@ const BottomNavigation = ({ {renderIcon ? ( @@ -812,25 +905,19 @@ const BottomNavigation = ({ }) ) : ( )} - + {typeof badge === 'boolean' ? ( - + ) : ( {badge} @@ -842,24 +929,32 @@ const BottomNavigation = ({ {renderLabel ? ( renderLabel({ route, focused: true, - color: activeTintColor, + color: activeLabelColor, }) ) : ( {getLabelText({ route })} @@ -876,14 +971,17 @@ const BottomNavigation = ({ renderLabel({ route, focused: false, - color: inactiveTintColor, + color: inactiveLabelColor, }) ) : ( {getLabelText({ route })} @@ -893,7 +991,7 @@ const BottomNavigation = ({ )} ) : ( - + !isV3 && )} ), @@ -966,6 +1064,9 @@ const styles = StyleSheet.create({ // The extra 4dp bottom padding is offset by label's height paddingVertical: 6, }, + v3Item: { + paddingVertical: 0, + }, ripple: { position: 'absolute', }, @@ -976,10 +1077,20 @@ const styles = StyleSheet.create({ marginHorizontal: 12, alignSelf: 'center', }, + v3IconContainer: { + height: 32, + width: 32, + marginBottom: 4, + marginTop: 0, + justifyContent: 'center', + }, iconWrapper: { ...StyleSheet.absoluteFillObject, alignItems: 'center', }, + v3IconWrapper: { + top: 4, + }, labelContainer: { height: 16, paddingBottom: 2, @@ -1002,6 +1113,20 @@ const styles = StyleSheet.create({ badgeContainer: { position: 'absolute', left: 0, - top: -2, + }, + v3TouchableContainer: { + paddingTop: 12, + paddingBottom: 16, + }, + v3NoLabelContainer: { + height: 80, + justifyContent: 'center', + alignItems: 'center', + }, + outline: { + width: OUTLINE_WIDTH, + height: OUTLINE_WIDTH / 2, + borderRadius: OUTLINE_WIDTH / 4, + alignSelf: 'center', }, }); diff --git a/src/components/__tests__/BottomNavigation.test.js b/src/components/__tests__/BottomNavigation.test.js index b010e1e312..68244db93a 100644 --- a/src/components/__tests__/BottomNavigation.test.js +++ b/src/components/__tests__/BottomNavigation.test.js @@ -32,7 +32,7 @@ const createState = (index, length) => ({ index, routes: Array.from({ length }, (_, i) => ({ key: `key-${i}`, - icon: icons[i], + focusedIcon: icons[i], title: `Route: ${i}`, })), }); diff --git a/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap b/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap index 461d7041e8..1921edd026 100644 --- a/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap +++ b/src/components/__tests__/__snapshots__/BottomNavigation.test.js.snap @@ -104,6 +104,8 @@ exports[`hides labels in non-shifting bottom navigation 1`] = ` Object { "marginBottom": 0, "marginHorizontal": 0, + }, + Object { "maxWidth": 504, }, ] @@ -126,14 +128,18 @@ exports[`hides labels in non-shifting bottom navigation 1`] = ` onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - Object { - "flex": 1, - "paddingVertical": 6, - } + Array [ + Object { + "flex": 1, + "paddingVertical": 6, + }, + false, + ] } > - magnify - + /> - magnify - + /> - camera - + /> - camera - + /> - inbox - + /> - inbox - + /> - magnify - + /> - magnify - + /> - camera - + /> - camera - + /> - inbox - + /> - inbox - + /> - heart - + /> - heart - + /> - shopping-music - + /> - shopping-music - + />