-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
feat(navigation-bar): modernize to Material Design 3 #5006
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
3951136
b07f21a
44eeaf8
9196bac
834ae8a
77d1704
bbedae9
3f2e172
7d6b909
ee3ede5
6937d58
b102488
a5a012a
13d34e9
c6bc35f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import * as React from 'react'; | ||
| import { StyleSheet, View, useWindowDimensions } from 'react-native'; | ||
|
|
||
| import { | ||
| NavigationBar, | ||
| SegmentedButtons, | ||
| Switch, | ||
| Text, | ||
| } from 'react-native-paper'; | ||
|
|
||
| type VariantMode = 'auto' | 'stacked' | 'horizontal'; | ||
|
|
||
| // The flexible navigation bar switches to a horizontal item arrangement in | ||
| // medium-width windows. M3 recommends ~600dp as the breakpoint, but the | ||
| // component leaves the decision to the consumer — here it is driven by | ||
| // `useWindowDimensions`. | ||
| const MEDIUM_WINDOW_WIDTH = 600; | ||
|
|
||
| const routes = [ | ||
| { key: 'album', title: 'Album', focusedIcon: 'image-album', badge: 3 }, | ||
| { key: 'library', title: 'Library', focusedIcon: 'bookshelf' }, | ||
| { | ||
| key: 'favorites', | ||
| title: 'Favorites', | ||
| focusedIcon: 'heart', | ||
| unfocusedIcon: 'heart-outline', | ||
| }, | ||
| { | ||
| key: 'settings', | ||
| title: 'Settings', | ||
| focusedIcon: 'cog', | ||
| unfocusedIcon: 'cog-outline', | ||
| }, | ||
| ]; | ||
|
|
||
| const NavigationBarExample = () => { | ||
| const [index, setIndex] = React.useState(0); | ||
| const [labeled, setLabeled] = React.useState(true); | ||
| const [variantMode, setVariantMode] = React.useState<VariantMode>('auto'); | ||
|
|
||
| const { width } = useWindowDimensions(); | ||
| const autoVariant = width >= MEDIUM_WINDOW_WIDTH ? 'horizontal' : 'stacked'; | ||
| const variant = variantMode === 'auto' ? autoVariant : variantMode; | ||
|
|
||
| return ( | ||
| <View style={styles.container}> | ||
| <View style={styles.content}> | ||
| <Text variant="headlineSmall">{routes[index].title}</Text> | ||
|
|
||
| <View style={styles.controls}> | ||
| <View style={styles.row}> | ||
| <Text variant="labelLarge">Show labels</Text> | ||
| <Switch value={labeled} onValueChange={setLabeled} /> | ||
| </View> | ||
|
|
||
| <SegmentedButtons | ||
| value={variantMode} | ||
| onValueChange={(value) => setVariantMode(value as VariantMode)} | ||
| buttons={[ | ||
| { value: 'auto', label: `Auto (${autoVariant})` }, | ||
| { value: 'stacked', label: 'Stacked' }, | ||
| { value: 'horizontal', label: 'Horizontal' }, | ||
| ]} | ||
| /> | ||
| </View> | ||
| </View> | ||
|
|
||
| <NavigationBar | ||
| navigationState={{ index, routes }} | ||
| labeled={labeled} | ||
| variant={variant} | ||
| onTabPress={({ route }) => { | ||
| const nextIndex = routes.findIndex((r) => r.key === route.key); | ||
| if (nextIndex !== -1) { | ||
| setIndex(nextIndex); | ||
| } | ||
| }} | ||
| /> | ||
| </View> | ||
| ); | ||
| }; | ||
|
|
||
| NavigationBarExample.title = 'Navigation Bar (flexible)'; | ||
|
|
||
| export default NavigationBarExample; | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| }, | ||
| content: { | ||
| flex: 1, | ||
| justifyContent: 'center', | ||
| alignItems: 'center', | ||
| padding: 16, | ||
| }, | ||
| controls: { | ||
| marginTop: 32, | ||
| width: '100%', | ||
| maxWidth: 480, | ||
| gap: 24, | ||
| }, | ||
| row: { | ||
| flexDirection: 'row', | ||
| alignItems: 'center', | ||
| justifyContent: 'space-between', | ||
| }, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,11 +49,10 @@ type TouchableProps<Route extends BaseRoute> = TouchableRippleProps & { | |
|
|
||
| export type Props<Route extends BaseRoute> = { | ||
| /** | ||
| * Whether the shifting style is used, the active tab icon shifts up to show the label and the inactive tabs won't have a label. | ||
| * | ||
| * By default, this is `false` with theme version 3 and `true` when you have more than 3 tabs. | ||
| * Pass `shifting={false}` to explicitly disable this animation, or `shifting={true}` to always use this animation. | ||
| * Note that you need at least 2 tabs be able to run this animation. | ||
| * @deprecated The `shifting` style is a Material Design 2 pattern that is not | ||
| * part of Material Design 3 and no longer has any effect. It will be removed | ||
| * in a future version. To animate scene transitions, use `sceneAnimationType` | ||
| * and `sceneAnimationEnabled` instead. | ||
| */ | ||
| shifting?: boolean; | ||
| /** | ||
|
|
@@ -261,7 +260,7 @@ const SceneComponent = React.memo(({ component, ...rest }: any) => | |
|
|
||
| /** | ||
| * BottomNavigation provides quick navigation between top-level views of an app with a bottom navigation bar. | ||
| * It is primarily designed for use on mobile. If you want to use the navigation bar only see [`BottomNavigation.Bar`](BottomNavigationBar). | ||
| * It is primarily designed for use on mobile. If you want to use the navigation bar only see [`NavigationBar`](../NavigationBar). | ||
| * | ||
| * By default BottomNavigation uses primary color as a background, in dark theme with `adaptive` mode it will use surface colour instead. | ||
| * See [Dark Theme](https://callstack.github.io/react-native-paper/docs/guides/theming#dark-theme) for more information. | ||
|
|
@@ -330,7 +329,6 @@ const BottomNavigation = <Route extends BaseRoute>({ | |
| onTabPress, | ||
| onTabLongPress, | ||
| onIndexChange, | ||
| shifting: shiftingProp, | ||
| safeAreaInsets, | ||
| labelMaxFontSizeMultiplier = 1, | ||
| compact: compactProp, | ||
|
|
@@ -341,14 +339,6 @@ const BottomNavigation = <Route extends BaseRoute>({ | |
| const theme = useInternalTheme(themeOverrides); | ||
| const { scale } = theme.animation; | ||
| const compact = compactProp ?? false; | ||
| let shifting = shiftingProp ?? false; | ||
|
|
||
| if (shifting && navigationState.routes.length < 2) { | ||
| shifting = false; | ||
| console.warn( | ||
| 'BottomNavigation needs at least 2 tabs to run shifting animation' | ||
| ); | ||
| } | ||
|
|
||
| const focusedKey = navigationState.routes[navigationState.index].key; | ||
|
|
||
|
|
@@ -574,7 +564,6 @@ const BottomNavigation = <Route extends BaseRoute>({ | |
| animationEasing={sceneAnimationEasing} | ||
| onTabPress={handleTabPress} | ||
| onTabLongPress={onTabLongPress} | ||
| shifting={shifting} | ||
| safeAreaInsets={safeAreaInsets} | ||
| labelMaxFontSizeMultiplier={labelMaxFontSizeMultiplier} | ||
| compact={compact} | ||
|
|
@@ -612,6 +601,11 @@ BottomNavigation.SceneMap = <Route extends BaseRoute>(scenes: { | |
| ); | ||
| }; | ||
|
|
||
| /** | ||
| * @deprecated Use the top-level `NavigationBar` export instead. | ||
| * `BottomNavigation.Bar` is the M3 "original" navigation bar, superseded by the | ||
| * flexible `NavigationBar`. Kept as an alias for backwards compatibility. | ||
| */ | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in 13d34e9 — the deprecation block is removed;
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There shouldn't be any deprecations. Remove any old code entirely
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 13d34e9 — removed the |
||
| // @component ./BottomNavigationBar.tsx | ||
| BottomNavigation.Bar = BottomNavigationBar; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the
BottomNavigationcomponent still needed? it'd be inconsistent to have a mix of old and new APIsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kept intentionally.
BottomNavigationis the router-integrated scene component (owns tab screens + transitions);NavigationBaris the standalone bar for use with React Navigation's own navigator — same layering split as React Navigation itself, so it isn't an old-vs-new API mix. Every deprecation/alias is gone (13d34e9), so there's no MD2 surface remaining.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then the naming is inconsistent. both should use the same naming scheme.