Skip to content

Commit 4a98c02

Browse files
authored
chore: Tab navigator example (#369)
## Description This PR adds bottom tabs navigator example to test invalid content behavior on tab change.
1 parent 6427c8d commit 4a98c02

9 files changed

Lines changed: 301 additions & 4 deletions

File tree

example/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"@fortawesome/free-solid-svg-icons": "^6.5.2",
66
"@fortawesome/react-native-fontawesome": "^0.3.2",
77
"@react-native-async-storage/async-storage": "^1.24.0",
8+
"@react-navigation/bottom-tabs": "^7.3.10",
89
"@react-navigation/native": "7.0.14",
910
"@react-navigation/native-stack": "7.2.0",
1011
"@shopify/flash-list": "patch:@shopify/flash-list@npm%3A1.8.0#~/.yarn/patches/@shopify-flash-list-npm-1.8.0-54e02d8f74.patch",

example/app/src/examples/SortableFlex/PlaygroundExample.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getCategories } from '@/utils';
88

99
const DATA = getCategories(IS_WEB ? 30 : 10);
1010

11-
export default function Flex() {
11+
export default function PlaygroundExample() {
1212
return (
1313
<ScrollScreen includeNavBarHeight>
1414
<Sortable.Flex gap={10} padding={10}>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { faCircle } from '@fortawesome/free-solid-svg-icons';
2+
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
3+
import type { BottomTabNavigationOptions } from '@react-navigation/bottom-tabs';
4+
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
5+
import { StyleSheet, Text } from 'react-native';
6+
import Sortable from 'react-native-sortables';
7+
8+
import { FlexCell, Screen, ScrollScreen } from '@/components';
9+
import { colors, spacing, text } from '@/theme';
10+
import { getCategories } from '@/utils';
11+
12+
const Tab = createBottomTabNavigator();
13+
14+
const DATA_LENGTH = 30;
15+
const DATA = getCategories(DATA_LENGTH);
16+
17+
type FlexProps = {
18+
data: Array<string>;
19+
};
20+
21+
function Flex({ data }: FlexProps) {
22+
return (
23+
<Sortable.Flex columnGap={spacing.sm} rowGap={spacing.xs}>
24+
{data.map(item => (
25+
<FlexCell key={item} size='large'>
26+
{item}
27+
</FlexCell>
28+
))}
29+
</Sortable.Flex>
30+
);
31+
}
32+
33+
function Screen1() {
34+
return (
35+
<ScrollScreen contentContainerStyle={styles.scrollContainer}>
36+
<Text style={styles.screenTitle}>Screen 1 flex:</Text>
37+
<Flex data={DATA.slice(0, DATA_LENGTH / 2)} />
38+
</ScrollScreen>
39+
);
40+
}
41+
42+
function Screen2() {
43+
return (
44+
<ScrollScreen contentContainerStyle={styles.scrollContainer}>
45+
<Text style={styles.screenTitle}>Screen 2 flex:</Text>
46+
<Flex data={DATA.slice(DATA_LENGTH / 2)} />
47+
</ScrollScreen>
48+
);
49+
}
50+
51+
function Screen3() {
52+
return (
53+
<ScrollScreen contentContainerStyle={styles.scrollContainer}>
54+
<Text style={styles.screenTitle}>Screen without sortable flex</Text>
55+
</ScrollScreen>
56+
);
57+
}
58+
59+
const tabBarOptions: BottomTabNavigationOptions = {
60+
tabBarIcon: ({ focused }) => (
61+
<FontAwesomeIcon
62+
color={focused ? colors.primary : colors.foreground3}
63+
icon={faCircle}
64+
/>
65+
),
66+
tabBarLabel: ({ focused }) => (
67+
<Text style={focused ? styles.focusedLabel : styles.label}>Screen 1</Text>
68+
)
69+
};
70+
71+
function Tabs() {
72+
return (
73+
<Tab.Navigator
74+
// This option breaks sortable state when navigating between screens
75+
// https://github.com/MatiPl01/react-native-sortables/issues/308
76+
detachInactiveScreens={false}
77+
screenOptions={{
78+
tabBarStyle: {
79+
shadowColor: 'transparent'
80+
}
81+
}}>
82+
<Tab.Screen component={Screen1} name='Screen1' options={tabBarOptions} />
83+
<Tab.Screen component={Screen2} name='Screen2' options={tabBarOptions} />
84+
<Tab.Screen
85+
component={Screen3}
86+
name='No sortable'
87+
options={tabBarOptions}
88+
/>
89+
</Tab.Navigator>
90+
);
91+
}
92+
93+
export default function BottomTabsNavigatorExample() {
94+
return (
95+
<Screen style={styles.container} includeNavBarHeight>
96+
<Tabs />
97+
</Screen>
98+
);
99+
}
100+
101+
const styles = StyleSheet.create({
102+
container: {
103+
backgroundColor: colors.white
104+
},
105+
focusedLabel: {
106+
...text.label3,
107+
color: colors.primary
108+
},
109+
label: {
110+
...text.label3,
111+
color: colors.foreground3,
112+
fontWeight: 'normal'
113+
},
114+
screenTitle: {
115+
...text.label1,
116+
color: colors.foreground1,
117+
marginBottom: spacing.md
118+
},
119+
scrollContainer: {
120+
padding: spacing.md
121+
}
122+
});
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
export { default as ComplexLayoutExample } from './ComplexLayoutExample';
1+
import BottomTabsNavigatorExample from './BottomTabsNavigatorExample';
2+
import ComplexLayoutExample from './ComplexLayoutExample';
3+
4+
export { BottomTabsNavigatorExample, ComplexLayoutExample };
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * as features from './features';
22
export * as miscellaneous from './miscellaneous';
33
export { default as PlaygroundExample } from './PlaygroundExample';
4+
export * as tests from './tests';
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { faCircle } from '@fortawesome/free-solid-svg-icons';
2+
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
3+
import type { BottomTabNavigationOptions } from '@react-navigation/bottom-tabs';
4+
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
5+
import { useCallback } from 'react';
6+
import { StyleSheet, Text } from 'react-native';
7+
import type { SortableGridRenderItem } from 'react-native-sortables';
8+
import Sortable from 'react-native-sortables';
9+
10+
import { GridCard, Screen, ScrollScreen } from '@/components';
11+
import { colors, spacing, text } from '@/theme';
12+
13+
const Tab = createBottomTabNavigator();
14+
15+
const DATA_LENGTH = 36;
16+
const DATA = Array.from(
17+
{ length: DATA_LENGTH },
18+
(_, index) => `Item ${index + 1}`
19+
);
20+
21+
type GridProps = {
22+
data: Array<string>;
23+
};
24+
25+
function Grid({ data }: GridProps) {
26+
const renderItem = useCallback<SortableGridRenderItem<string>>(
27+
({ item }) => <GridCard>{item}</GridCard>,
28+
[]
29+
);
30+
31+
return (
32+
<Sortable.Grid
33+
columnGap={10}
34+
columns={3}
35+
data={data}
36+
renderItem={renderItem}
37+
rowGap={10}
38+
/>
39+
);
40+
}
41+
42+
function Screen1() {
43+
return (
44+
<ScrollScreen contentContainerStyle={styles.scrollContainer}>
45+
<Text style={styles.screenTitle}>Screen 1 grid:</Text>
46+
<Grid data={DATA.slice(0, DATA_LENGTH / 2)} />
47+
</ScrollScreen>
48+
);
49+
}
50+
51+
function Screen2() {
52+
return (
53+
<ScrollScreen contentContainerStyle={styles.scrollContainer}>
54+
<Text style={styles.screenTitle}>Screen 2 grid:</Text>
55+
<Grid data={DATA.slice(DATA_LENGTH / 2)} />
56+
</ScrollScreen>
57+
);
58+
}
59+
60+
function Screen3() {
61+
return (
62+
<ScrollScreen contentContainerStyle={styles.scrollContainer}>
63+
<Text style={styles.screenTitle}>Screen without sortable grid</Text>
64+
</ScrollScreen>
65+
);
66+
}
67+
68+
const tabBarOptions: BottomTabNavigationOptions = {
69+
tabBarIcon: ({ focused }) => (
70+
<FontAwesomeIcon
71+
color={focused ? colors.primary : colors.foreground3}
72+
icon={faCircle}
73+
/>
74+
),
75+
tabBarLabel: ({ children, focused }) => (
76+
<Text style={focused ? styles.focusedLabel : styles.label}>{children}</Text>
77+
)
78+
};
79+
80+
function Tabs() {
81+
return (
82+
<Tab.Navigator
83+
// This option breaks sortable state when navigating between screens
84+
// https://github.com/MatiPl01/react-native-sortables/issues/308
85+
detachInactiveScreens={false}
86+
screenOptions={{
87+
tabBarStyle: {
88+
shadowColor: 'transparent'
89+
}
90+
}}>
91+
<Tab.Screen
92+
component={Screen1}
93+
name='Sortable 1'
94+
options={tabBarOptions}
95+
/>
96+
<Tab.Screen
97+
component={Screen2}
98+
name='Sortable 2'
99+
options={tabBarOptions}
100+
/>
101+
<Tab.Screen
102+
component={Screen3}
103+
name='No sortable'
104+
options={tabBarOptions}
105+
/>
106+
</Tab.Navigator>
107+
);
108+
}
109+
110+
export default function BottomTabsNavigatorExample() {
111+
return (
112+
<Screen style={styles.container} includeNavBarHeight>
113+
<Tabs />
114+
</Screen>
115+
);
116+
}
117+
118+
const styles = StyleSheet.create({
119+
container: {
120+
backgroundColor: colors.white
121+
},
122+
focusedLabel: {
123+
...text.label3,
124+
color: colors.primary
125+
},
126+
label: {
127+
...text.label3,
128+
color: colors.foreground3,
129+
fontWeight: 'normal'
130+
},
131+
screenTitle: {
132+
...text.label1,
133+
color: colors.foreground1,
134+
marginBottom: spacing.md
135+
},
136+
scrollContainer: {
137+
padding: spacing.md
138+
}
139+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as BottomTabsNavigatorExample } from './BottomTabsNavigatorExample';

example/app/src/examples/navigation/routes.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,16 @@ const routes: Routes = {
7575
}
7676
}
7777
}
78-
})
78+
}),
79+
Tests: {
80+
name: 'Test examples',
81+
routes: {
82+
BottomTabsNavigator: {
83+
Component: SortableGrid.tests.BottomTabsNavigatorExample,
84+
name: 'Bottom Tabs Navigator'
85+
}
86+
}
87+
}
7988
}
8089
},
8190
SortableFlex: {
@@ -134,6 +143,10 @@ const routes: Routes = {
134143
ComplexLayout: {
135144
Component: SortableFlex.tests.ComplexLayoutExample,
136145
name: 'Complex Layout'
146+
},
147+
BottomTabsNavigator: {
148+
Component: SortableFlex.tests.BottomTabsNavigatorExample,
149+
name: 'Bottom Tabs Navigator'
137150
}
138151
}
139152
}

yarn.lock

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3781,6 +3781,22 @@ __metadata:
37813781
languageName: node
37823782
linkType: hard
37833783

3784+
"@react-navigation/bottom-tabs@npm:^7.3.10":
3785+
version: 7.3.10
3786+
resolution: "@react-navigation/bottom-tabs@npm:7.3.10"
3787+
dependencies:
3788+
"@react-navigation/elements": "npm:^2.3.8"
3789+
color: "npm:^4.2.3"
3790+
peerDependencies:
3791+
"@react-navigation/native": ^7.1.6
3792+
react: ">= 18.2.0"
3793+
react-native: "*"
3794+
react-native-safe-area-context: ">= 4.0.0"
3795+
react-native-screens: ">= 4.0.0"
3796+
checksum: 10c0/17adb7ebc38fd6d99866cf365f93099e5e83efdc0203051f3bbd7f731791f04c4323901d81ea2af9bb82a7ad22ff547d8a31f395a19de6160b7a3596990b1191
3797+
languageName: node
3798+
linkType: hard
3799+
37843800
"@react-navigation/core@npm:^7.3.1":
37853801
version: 7.8.5
37863802
resolution: "@react-navigation/core@npm:7.8.5"
@@ -3798,7 +3814,7 @@ __metadata:
37983814
languageName: node
37993815
linkType: hard
38003816

3801-
"@react-navigation/elements@npm:^2.2.5":
3817+
"@react-navigation/elements@npm:^2.2.5, @react-navigation/elements@npm:^2.3.8":
38023818
version: 2.3.8
38033819
resolution: "@react-navigation/elements@npm:2.3.8"
38043820
dependencies:
@@ -7987,6 +8003,7 @@ __metadata:
79878003
"@react-native-community/cli-platform-ios": "npm:18.0.0"
79888004
"@react-native/babel-preset": "npm:0.79.0"
79898005
"@react-native/metro-config": "npm:0.79.0"
8006+
"@react-navigation/bottom-tabs": "npm:^7.3.10"
79908007
"@react-navigation/native": "npm:7.0.14"
79918008
"@react-navigation/native-stack": "npm:7.2.0"
79928009
"@shopify/flash-list": "patch:@shopify/flash-list@npm%3A1.8.0#~/.yarn/patches/@shopify-flash-list-npm-1.8.0-54e02d8f74.patch"

0 commit comments

Comments
 (0)