Skip to content

Commit c72bb12

Browse files
committed
chore: Common apps drawer navigator (#196)
* Adjust css navigator * Prepare reanimated app navigator * Finally is perfect * Improve macos example * Format files, clean up dependencies * Add missing ifndefs * Downgrade native-stack, fix binary search used for easings * Replace Pressable to work on android * Remove 'new' labels
1 parent e145903 commit c72bb12

File tree

34 files changed

+486
-362
lines changed

34 files changed

+486
-362
lines changed

apps/common-app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
"@react-native-community/slider": "4.5.5",
3939
"@react-native-masked-view/masked-view": "0.3.2",
4040
"@react-navigation/bottom-tabs": "^7.0.0",
41-
"@react-navigation/native": "^7.0.0",
41+
"@react-navigation/native": "^7.0.14",
4242
"@react-navigation/native-stack": "^7.0.0",
43-
"@react-navigation/stack": "^7.0.0",
43+
"@react-navigation/stack": "^7.1.1",
4444
"@shopify/flash-list": "patch:@shopify/flash-list@npm%3A1.7.2#~/.yarn/patches/@shopify-flash-list-npm-1.7.2-2a363895ca.patch",
4545
"@tsconfig/react-native": "^3.0.0",
4646
"@types/d3-shape": "^3.1.1",

apps/common-app/src/App.tsx

Lines changed: 155 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,156 @@
1-
// TODO - implement
2-
import { default as App } from '@/apps/css/App';
1+
import { PortalProvider } from '@gorhom/portal';
2+
import AsyncStorage from '@react-native-async-storage/async-storage';
3+
import { createDrawerNavigator } from '@react-navigation/drawer';
4+
import type { NavigationState } from '@react-navigation/native';
5+
import {
6+
getPathFromState,
7+
NavigationContainer,
8+
} from '@react-navigation/native';
9+
import { useCallback, useEffect, useState } from 'react';
10+
import { ActivityIndicator, Linking, Platform, View } from 'react-native';
11+
import { GestureHandlerRootView } from 'react-native-gesture-handler';
12+
import { SafeAreaProvider } from 'react-native-safe-area-context';
313

4-
export default App;
14+
import { colors, flex, radius, text } from '@/theme';
15+
import { IS_MACOS, IS_WEB, isFabric, noop } from '@/utils';
16+
17+
import { CSSApp, ReanimatedApp } from './apps';
18+
19+
export default function App() {
20+
const { isReady, navigationState, updateNavigationState } =
21+
useNavigationState();
22+
23+
if (!isReady) {
24+
return (
25+
<View style={[flex.fill, flex.center]}>
26+
<ActivityIndicator />
27+
</View>
28+
);
29+
}
30+
31+
return (
32+
<SafeAreaProvider>
33+
<GestureHandlerRootView style={flex.fill}>
34+
<NavigationContainer
35+
initialState={navigationState}
36+
linking={{
37+
getPathFromState: (state, options) =>
38+
getPathFromState(state, options).replace(/%2F/g, '/'),
39+
getStateFromPath: (path) => {
40+
const chunks = path.split('/').filter(Boolean);
41+
const routes = chunks.reduce<Array<{ name: string }>>(
42+
(result, chunk) => {
43+
const lastRoute = result[result.length - 1];
44+
const route = {
45+
name: lastRoute ? `${lastRoute.name}/${chunk}` : chunk,
46+
};
47+
result.push(route);
48+
return result;
49+
},
50+
[]
51+
);
52+
return { routes };
53+
},
54+
prefixes: [],
55+
}}
56+
onStateChange={updateNavigationState}>
57+
<PortalProvider>
58+
<Navigator />
59+
</PortalProvider>
60+
</NavigationContainer>
61+
</GestureHandlerRootView>
62+
</SafeAreaProvider>
63+
);
64+
}
65+
66+
const SCREENS = [
67+
{
68+
component: CSSApp,
69+
name: 'CSS',
70+
},
71+
{
72+
component: ReanimatedApp,
73+
name: 'Reanimated',
74+
},
75+
];
76+
77+
function Navigator() {
78+
if (IS_MACOS) {
79+
return <ReanimatedApp />;
80+
}
81+
82+
const Drawer = createDrawerNavigator();
83+
const screens = isFabric() || IS_WEB ? SCREENS : SCREENS.reverse();
84+
85+
return (
86+
<Drawer.Navigator
87+
screenOptions={{
88+
drawerActiveBackgroundColor: colors.primaryLight,
89+
drawerActiveTintColor: colors.primaryDark,
90+
drawerInactiveTintColor: colors.primary,
91+
drawerItemStyle: {
92+
borderRadius: radius.lg,
93+
},
94+
drawerLabelStyle: text.heading4,
95+
drawerPosition: 'right',
96+
drawerStyle: {
97+
backgroundColor: colors.background1,
98+
},
99+
headerShown: false,
100+
}}>
101+
{screens.map(({ component, name }) => (
102+
<Drawer.Screen component={component} key={name} name={name} />
103+
))}
104+
</Drawer.Navigator>
105+
);
106+
}
107+
108+
// copied from https://reactnavigation.org/docs/state-persistence/
109+
const PERSISTENCE_KEY = 'NAVIGATION_STATE_V1';
110+
111+
function useNavigationState() {
112+
const [isReady, setIsReady] = useState(!__DEV__);
113+
114+
const [navigationState, setNavigationState] = useState<
115+
NavigationState | undefined
116+
>();
117+
118+
const updateNavigationState = useCallback((state?: NavigationState) => {
119+
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)).catch(noop);
120+
}, []);
121+
122+
useEffect(() => {
123+
const restoreState = async () => {
124+
try {
125+
const initialUrl = await Linking.getInitialURL();
126+
127+
if (
128+
Platform.OS !== 'web' &&
129+
Platform.OS !== 'macos' &&
130+
initialUrl === null
131+
) {
132+
// Only restore state if there's no deep link and we're not on web
133+
const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
134+
// Erase the state immediately after fetching it.
135+
// This prevents the app to boot on the screen that previously crashed.
136+
updateNavigationState();
137+
const state = savedStateString
138+
? (JSON.parse(savedStateString) as NavigationState)
139+
: undefined;
140+
141+
if (state !== undefined) {
142+
setNavigationState(state);
143+
}
144+
}
145+
} finally {
146+
setIsReady(true);
147+
}
148+
};
149+
150+
if (!isReady) {
151+
restoreState().catch(noop);
152+
}
153+
}, [isReady, updateNavigationState]);
154+
155+
return { isReady, navigationState, updateNavigationState };
156+
}

apps/common-app/src/apps/css/App.tsx

Lines changed: 1 addition & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,5 @@
1-
import { PortalProvider } from '@gorhom/portal';
2-
import AsyncStorage from '@react-native-async-storage/async-storage';
3-
import type { NavigationState } from '@react-navigation/native';
4-
import {
5-
getPathFromState,
6-
NavigationContainer,
7-
} from '@react-navigation/native';
8-
import { useCallback, useEffect, useState } from 'react';
9-
import { GestureHandlerRootView } from 'react-native-gesture-handler';
10-
import { SafeAreaProvider } from 'react-native-safe-area-context';
11-
12-
import { flex } from '@/theme';
13-
import { IS_WEB, noop } from '@/utils';
14-
151
import { Navigator } from './navigation';
162

17-
const PERSISTENCE_KEY = 'NAVIGATION_STATE';
18-
193
export default function App() {
20-
const [isReady, setIsReady] = useState(false);
21-
const [navigationState, setNavigationState] = useState<NavigationState>();
22-
23-
useEffect(() => {
24-
if (IS_WEB) {
25-
return;
26-
}
27-
28-
const restoreState = async () => {
29-
try {
30-
const savedStateString = await AsyncStorage.getItem(PERSISTENCE_KEY);
31-
const state = savedStateString
32-
? (JSON.parse(savedStateString) as NavigationState)
33-
: undefined;
34-
if (state !== undefined) {
35-
setNavigationState(state);
36-
}
37-
} finally {
38-
setIsReady(true);
39-
}
40-
};
41-
if (!isReady) {
42-
restoreState().catch(noop);
43-
}
44-
}, [isReady]);
45-
46-
const persistNavigationState = useCallback((state?: NavigationState) => {
47-
AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(state)).catch(noop);
48-
}, []);
49-
50-
return (
51-
<SafeAreaProvider>
52-
<GestureHandlerRootView style={flex.fill}>
53-
<NavigationContainer
54-
initialState={navigationState}
55-
linking={{
56-
getPathFromState: (state, options) =>
57-
getPathFromState(state, options).replace(/%2F/g, '/'),
58-
getStateFromPath: (path) => {
59-
const chunks = path.split('/').filter(Boolean);
60-
const routes = chunks.reduce<Array<{ name: string }>>(
61-
(result, chunk) => {
62-
const lastRoute = result[result.length - 1];
63-
const route = {
64-
name: lastRoute ? `${lastRoute.name}/${chunk}` : chunk,
65-
};
66-
result.push(route);
67-
return result;
68-
},
69-
[]
70-
);
71-
return { routes };
72-
},
73-
prefixes: [],
74-
}}
75-
onStateChange={persistNavigationState}>
76-
<PortalProvider>
77-
<Navigator />
78-
</PortalProvider>
79-
</NavigationContainer>
80-
</GestureHandlerRootView>
81-
</SafeAreaProvider>
82-
);
4+
return <Navigator />;
835
}

apps/common-app/src/apps/css/examples/animations/routes.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,12 +512,10 @@ const routes = {
512512
Component: miscellaneous.UpdatingAnimationSettings,
513513
},
514514
KeyframeTimingFunctions: {
515-
labelTypes: ['new'],
516515
name: 'Keyframe Timing Functions',
517516
Component: miscellaneous.KeyframeTimingFunctions,
518517
},
519518
MultipleAnimations: {
520-
labelTypes: ['new'],
521519
name: 'Multiple Animations',
522520
Component: miscellaneous.MultipleAnimations,
523521
},

apps/common-app/src/apps/css/examples/animations/screens/miscellaneous/KeyframeTimingFunctions.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ export default function KeyframeTimingFunctions() {
8787
<ScrollScreen>
8888
<Stagger>
8989
<Section
90-
labelTypes={['new']}
9190
title="Keyframe Timing Functions"
9291
description={[
9392
'**Enable**, **disable** or **change** the animation timing function in the animation properties config below.',

apps/common-app/src/apps/css/examples/animations/screens/miscellaneous/MultipleAnimations.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ export default function MultipleAnimations() {
124124
<ScrollScreen>
125125
<Stagger>
126126
<Section
127-
labelTypes={['new']}
128127
title="Multiple Animations"
129128
description="This example demonstrates how to apply **multiple animations** to a single element. It also allows selecting **separate animation settings** or **sharing them** across all animations if only a single value is set.
130129
">

apps/common-app/src/apps/css/examples/transitions/routes.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ const routes = {
3838
Component: transitionSettings.TransitionTimingFunction,
3939
},
4040
TransitionBehavior: {
41-
labelTypes: ['new'],
4241
name: 'Transition Behavior',
4342
Component: transitionSettings.TransitionBehavior,
4443
},
@@ -57,12 +56,10 @@ const routes = {
5756
Component: miscellaneous.UpdatingTransitionSettings,
5857
},
5958
MultipleTransitionSettings: {
60-
labelTypes: ['new'],
6159
name: 'Multiple Transition Settings',
6260
Component: miscellaneous.MultipleTransitionSettings,
6361
},
6462
ReversingShortening: {
65-
labelTypes: ['new'],
6663
name: 'Reversing Shortening',
6764
Component: miscellaneous.ReversingShortening,
6865
},

apps/common-app/src/apps/css/examples/transitions/screens/miscellaneous/MultipleTransitionSettings.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ export default function MultipleTransitionSettings() {
9898
<Stagger>
9999
<Section
100100
description="Change the **transition property**, **duration**, **delay**, and **timing function** to see how they influence the transition between styles."
101-
labelTypes={['new']}
102101
title="Multiple Transition Settings">
103102
<ConfigSelector
104103
blockStyle={styles.block}

apps/common-app/src/apps/css/examples/transitions/screens/miscellaneous/ReversingShortening.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ export default function ReversingShortening() {
102102
<Stagger>
103103
<Section
104104
description="This example demonstrates how transition duration is shortened for **reverse transitions**. This applies only to transitions where the property is animated **between 2 states** that are applied interchangeably and the transition is **interrupted before finishing**."
105-
labelTypes={['new']}
106105
title="Reversing Shortening of Interrupted Transitions">
107106
<ConfigSelector
108107
blockStyle={styles.block}

apps/common-app/src/apps/css/examples/transitions/screens/transitionSettings/TransitionBehavior.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export default function TransitionBehavior() {
2222
{ label: 'normal (default)' },
2323
{ label: 'allowDiscrete', transitionBehavior: 'allowDiscrete' },
2424
],
25-
labelTypes: ['new'],
2625
title: 'Transition Behavior',
2726
},
2827
]}

0 commit comments

Comments
 (0)