Skip to content

Commit

Permalink
feat: implement freezeOnBlur (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
okwasniewski authored Dec 16, 2024
1 parent f17fa06 commit c9f13ad
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 7 deletions.
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 type { ImageSource } from 'react-native/Libraries/Image/ImageSource';
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 @@ interface Props<Route extends BaseRoute> {
*/
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 @@ const TabView = <Route extends BaseRoute>({
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 @@ -311,11 +320,14 @@ const TabView = <Route extends BaseRoute>({
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 @@ const TabView = <Route extends BaseRoute>({
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

0 comments on commit c9f13ad

Please sign in to comment.