Skip to content
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

feat: implement freezeOnBlur #207

Merged
merged 1 commit into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/purple-baboons-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'react-native-bottom-tabs': patch
'@bottom-tabs/react-navigation': patch
---

feat: add freezeOnBlur
8 changes: 4 additions & 4 deletions apps/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- react-native-bottom-tabs (0.7.3):
- react-native-bottom-tabs (0.7.6):
- DoubleConversion
- glog
- RCT-Folly (= 2024.01.01.00)
Expand All @@ -1222,7 +1222,7 @@ PODS:
- React-graphics
- React-ImageManager
- React-jsi
- react-native-bottom-tabs/common (= 0.7.3)
- react-native-bottom-tabs/common (= 0.7.6)
- React-NativeModulesApple
- React-RCTFabric
- React-rendererdebug
Expand All @@ -1234,7 +1234,7 @@ PODS:
- SDWebImageSVGCoder (>= 1.7.0)
- SwiftUIIntrospect (~> 1.0)
- Yoga
- react-native-bottom-tabs/common (0.7.3):
- react-native-bottom-tabs/common (0.7.6):
- DoubleConversion
- glog
- RCT-Folly (= 2024.01.01.00)
Expand Down Expand Up @@ -1943,7 +1943,7 @@ SPEC CHECKSUMS:
React-logger: d79b704bf215af194f5213a6b7deec50ba8e6a9b
React-Mapbuffer: b982d5bba94a8bc073bda48f0d27c9b28417fae3
React-microtasksnativemodule: 8fa285fed833a04a754bf575f8ded65fc240b88d
react-native-bottom-tabs: b6b3dc2e971c860a0a6d763701929d1899f666a0
react-native-bottom-tabs: 084cfd4d4b1e74c03f4196b3f62d39445882f45f
react-native-safe-area-context: 73505107f7c673cd550a561aeb6271f152c483b6
React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794
React-NativeModulesApple: b8465afc883f5bf3fe8bac3767e394d581a5f123
Expand Down
5 changes: 5 additions & 0 deletions apps/example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import TintColorsExample from './Examples/TintColors';
import NativeBottomTabsEmbeddedStacks from './Examples/NativeBottomTabsEmbeddedStacks';
import NativeBottomTabsSVGs from './Examples/NativeBottomTabsSVGs';
import NativeBottomTabsRemoteIcons from './Examples/NativeBottomTabsRemoteIcons';
import NativeBottomTabsFreezeOnBlur from './Examples/NativeBottomTabsFreezeOnBlur';

const FourTabsIgnoreSafeArea = () => {
return <FourTabs ignoresTopSafeArea />;
Expand Down Expand Up @@ -102,6 +103,10 @@ const examples = [
name: 'Four Tabs - Transparent scroll edge appearance',
platform: 'ios',
},
{
component: NativeBottomTabsFreezeOnBlur,
name: 'Native Bottom Tabs with freezeOnBlur',
},
{
component: FourTabsOpaqueScrollEdgeAppearance,
name: 'Four Tabs - Opaque scroll edge appearance',
Expand Down
115 changes: 115 additions & 0 deletions apps/example/src/Examples/NativeBottomTabsFreezeOnBlur.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as React from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation';

const store = new Set<Dispatch>();

type Dispatch = (value: number) => void;

function useValue() {
const [value, setValue] = React.useState<number>(0);

React.useEffect(() => {
const dispatch = (value: number) => {
setValue(value);
};
store.add(dispatch);
return () => {
store.delete(dispatch);
};
}, [setValue]);

return value;
}

function HomeScreen() {
return (
<View style={styles.screenContainer}>
<Text>Home!</Text>
</View>
);
}

function DetailsScreen(props: any) {
const value = useValue();
const screenName = props?.route?.params?.screenName;
// only 1 'render' should appear at the time
console.log(`${Platform.OS} Details Screen render ${value} ${screenName}`);
return (
<View style={styles.screenContainer}>
<Text>Details!</Text>
<Text style={{ alignSelf: 'center' }}>
Details Screen {value} {screenName ? screenName : ''}{' '}
</Text>
</View>
);
}
const Tab = createNativeBottomTabNavigator();

export default function NativeBottomTabsFreezeOnBlur() {
React.useEffect(() => {
let timer = 0;
const interval = setInterval(() => {
timer = timer + 1;
store.forEach((dispatch) => dispatch(timer));
}, 3000);
return () => clearInterval(interval);
}, []);

return (
<Tab.Navigator
screenOptions={{
freezeOnBlur: true,
}}
>
<Tab.Screen
name="Article"
component={HomeScreen}
initialParams={{
screenName: 'Article',
}}
options={{
tabBarIcon: () => require('../../assets/icons/article_dark.png'),
}}
/>
<Tab.Screen
name="Albums"
component={DetailsScreen}
initialParams={{
screenName: 'Albums',
}}
options={{
tabBarIcon: () => require('../../assets/icons/grid_dark.png'),
}}
/>
<Tab.Screen
name="Contact"
component={DetailsScreen}
initialParams={{
screenName: 'Contact',
}}
options={{
tabBarIcon: () => require('../../assets/icons/person_dark.png'),
}}
/>
<Tab.Screen
name="Chat"
component={DetailsScreen}
initialParams={{
screenName: 'Chat',
}}
options={{
tabBarIcon: () => require('../../assets/icons/chat_dark.png'),
}}
/>
</Tab.Navigator>
);
}

const styles = StyleSheet.create({
screenContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
9 changes: 8 additions & 1 deletion packages/react-native-bottom-tabs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,18 @@
"react": "18.3.1",
"react-native": "0.75.4",
"react-native-builder-bob": "^0.32.1",
"react-native-screens": "4.3.0",
"typescript": "^5.2.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
"react-native": "*",
"react-native-screens": ">=3.29.0"
},
"peerDependenciesMeta": {
"react-native-screens": {
"optional": true
}
},
"react-native-builder-bob": {
"source": "src",
Expand Down
26 changes: 26 additions & 0 deletions packages/react-native-bottom-tabs/src/Screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react';
import { View } from 'react-native';
import type { StyleProp, ViewProps, ViewStyle } from 'react-native';

interface Props extends ViewProps {
visible: boolean;
children?: React.ReactNode;
freezeOnBlur?: boolean;
style?: StyleProp<ViewStyle>;
collapsable?: boolean;
}

let Screens: typeof import('react-native-screens') | undefined;

try {
Screens = require('react-native-screens');
} catch (e) {
// Ignore
}

export function Screen({ visible, ...rest }: Props) {
if (Screens?.screensEnabled()) {
return <Screens.Screen activityState={visible ? 2 : 0} {...rest} />;
}
return <View {...rest} />;
}
16 changes: 14 additions & 2 deletions packages/react-native-bottom-tabs/src/TabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import TabViewAdapter from './TabViewAdapter';
import useLatestCallback from 'use-latest-callback';
import type { BaseRoute, NavigationState } from './types';
import { Screen } from './Screen';

const isAppleSymbol = (icon: any): icon is { sfSymbol: string } =>
icon?.sfSymbol;
Expand Down Expand Up @@ -116,6 +117,13 @@
*/
getTestID?: (props: { route: Route }) => string | undefined;

/**
* Get freezeOnBlur for the current screen. Uses false by default.
* Defaults to `true` when `enableFreeze()` is run at the top of the application.
*
*/
getFreezeOnBlur?: (props: { route: Route }) => boolean | undefined;

/**
* Background color of the tab bar.
*/
Expand Down Expand Up @@ -160,6 +168,7 @@
tabBarInactiveTintColor: inactiveTintColor,
getLazy = ({ route }: { route: Route }) => route.lazy,
getLabelText = ({ route }: { route: Route }) => route.title,
getFreezeOnBlur = ({ route }: { route: Route }) => route.freezeOnBlur,
getIcon = ({ route, focused }: { route: Route; focused: boolean }) =>
route.unfocusedIcon
? focused
Expand Down Expand Up @@ -201,7 +210,7 @@

if (!loaded.includes(focusedKey)) {
// Set the current tab to be loaded if it was not loaded before
setLoaded((loaded) => [...loaded, focusedKey]);

Check warning on line 213 in packages/react-native-bottom-tabs/src/TabView.tsx

View workflow job for this annotation

GitHub Actions / lint

'loaded' is already declared in the upper scope on line 209 column 10
}

const icons = React.useMemo(
Expand Down Expand Up @@ -311,11 +320,14 @@
const focused = route.key === focusedKey;
const opacity = focused ? 1 : 0;
const zIndex = focused ? 0 : -1;
const freezeOnBlur = getFreezeOnBlur({ route });

return (
<View
<Screen
key={route.key}
collapsable={false}
visible={focused}
freezeOnBlur={freezeOnBlur}
pointerEvents={focused ? 'auto' : 'none'}
accessibilityElementsHidden={!focused}
importantForAccessibility={
Expand All @@ -331,7 +343,7 @@
route,
jumpTo,
})}
</View>
</Screen>
);
})}
</TabViewAdapter>
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-bottom-tabs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type BaseRoute = {
activeTintColor?: string;
hidden?: boolean;
testID?: string;
freezeOnBlur?: boolean;
};

export type NavigationState<Route extends BaseRoute> = {
Expand Down
9 changes: 9 additions & 0 deletions packages/react-navigation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ export type NativeBottomTabNavigationOptions = {
* TestID for the tab.
*/
tabBarButtonTestID?: string;

/**
* Whether inactive screens should be suspended from re-rendering. Defaults to `false`.
* Defaults to `true` when `enableFreeze()` is run at the top of the application.
*
* Only supported on iOS and Android.
*/
freezeOnBlur?: boolean;
};

export type NativeBottomTabDescriptor = Descriptor<
Expand All @@ -117,5 +125,6 @@ export type NativeBottomTabNavigationConfig = Partial<
| 'onTabLongPress'
| 'getActiveTintColor'
| 'getTestID'
| 'getFreezeOnBlur'
>
>;
3 changes: 3 additions & 0 deletions packages/react-navigation/src/views/NativeBottomTabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ export default function NativeBottomTabView({
const options = descriptors[route.key]?.options;
return options?.tabBarItemHidden === true;
}}
getFreezeOnBlur={({ route }) =>
descriptors[route.key]?.options.freezeOnBlur
}
getTestID={({ route }) =>
descriptors[route.key]?.options.tabBarButtonTestID
}
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15614,12 +15614,17 @@ __metadata:
react: 18.3.1
react-native: 0.75.4
react-native-builder-bob: ^0.32.1
react-native-screens: 4.3.0
sf-symbols-typescript: ^2.0.0
typescript: ^5.2.2
use-latest-callback: ^0.2.1
peerDependencies:
react: "*"
react-native: "*"
react-native-screens: ">=3.29.0"
peerDependenciesMeta:
react-native-screens:
optional: true
languageName: unknown
linkType: soft

Expand Down
Loading