216 lines
7.7 KiB
JavaScript
216 lines
7.7 KiB
JavaScript
'use client';
|
|
|
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
import React from 'react';
|
|
import { Freeze } from 'react-freeze';
|
|
import { Image, StyleSheet, findNodeHandle, processColor } from 'react-native';
|
|
import { freezeEnabled } from '../../core';
|
|
import BottomTabsScreenNativeComponent from '../../fabric/bottom-tabs/BottomTabsScreenNativeComponent';
|
|
import { featureFlags } from '../../flags';
|
|
import { bottomTabsDebugLog } from '../../private/logging';
|
|
|
|
/**
|
|
* EXPERIMENTAL API, MIGHT CHANGE W/O ANY NOTICE
|
|
*/
|
|
function BottomTabsScreen(props) {
|
|
const componentNodeRef = React.useRef(null);
|
|
const componentNodeHandle = React.useRef(-1);
|
|
React.useEffect(() => {
|
|
if (componentNodeRef.current != null) {
|
|
componentNodeHandle.current = findNodeHandle(componentNodeRef.current) ?? -1;
|
|
} else {
|
|
componentNodeHandle.current = -1;
|
|
}
|
|
}, []);
|
|
const [nativeViewIsVisible, setNativeViewIsVisible] = React.useState(false);
|
|
const {
|
|
onWillAppear,
|
|
onDidAppear,
|
|
onWillDisappear,
|
|
onDidDisappear,
|
|
isFocused = false,
|
|
freezeContents,
|
|
icon,
|
|
iconResource,
|
|
selectedIcon,
|
|
standardAppearance,
|
|
scrollEdgeAppearance,
|
|
...rest
|
|
} = props;
|
|
const shouldFreeze = shouldFreezeScreen(nativeViewIsVisible, isFocused, freezeContents);
|
|
const onWillAppearCallback = React.useCallback(event => {
|
|
bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onWillAppear received`);
|
|
setNativeViewIsVisible(true);
|
|
onWillAppear?.(event);
|
|
}, [onWillAppear]);
|
|
const onDidAppearCallback = React.useCallback(event => {
|
|
bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onDidAppear received`);
|
|
onDidAppear?.(event);
|
|
}, [onDidAppear]);
|
|
const onWillDisappearCallback = React.useCallback(event => {
|
|
bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onWillDisappear received`);
|
|
onWillDisappear?.(event);
|
|
}, [onWillDisappear]);
|
|
const onDidDisappearCallback = React.useCallback(event => {
|
|
bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current}] onDidDisappear received`);
|
|
setNativeViewIsVisible(false);
|
|
onDidDisappear?.(event);
|
|
}, [onDidDisappear]);
|
|
bottomTabsDebugLog(`TabsScreen [${componentNodeHandle.current ?? -1}] render; tabKey: ${rest.tabKey} shouldFreeze: ${shouldFreeze}, isFocused: ${isFocused} nativeViewIsVisible: ${nativeViewIsVisible}`);
|
|
const iconProps = parseIconsToNativeProps(icon, selectedIcon);
|
|
let parsedIconResource;
|
|
if (iconResource) {
|
|
parsedIconResource = Image.resolveAssetSource(iconResource);
|
|
if (!parsedIconResource) {
|
|
console.error('[RNScreens] failed to resolve an asset for bottom tab icon');
|
|
}
|
|
}
|
|
return /*#__PURE__*/React.createElement(BottomTabsScreenNativeComponent, _extends({
|
|
collapsable: false,
|
|
style: styles.fillParent,
|
|
onWillAppear: onWillAppearCallback,
|
|
onDidAppear: onDidAppearCallback,
|
|
onWillDisappear: onWillDisappearCallback,
|
|
onDidDisappear: onDidDisappearCallback,
|
|
isFocused: isFocused
|
|
// I'm keeping undefined as a fallback if `Image.resolveAssetSource` has failed for some reason.
|
|
// It won't render any icon, but it will prevent from crashing on the native side which is expecting
|
|
// ReadableMap. Passing `iconResource` directly will result in crash, because `require` API is returning
|
|
// double as a value.
|
|
,
|
|
iconResource: parsedIconResource || undefined
|
|
}, iconProps, {
|
|
standardAppearance: mapAppearanceToNativeProp(standardAppearance),
|
|
scrollEdgeAppearance: mapAppearanceToNativeProp(scrollEdgeAppearance)
|
|
// @ts-ignore - This is debug only anyway
|
|
,
|
|
ref: componentNodeRef
|
|
}, rest), /*#__PURE__*/React.createElement(Freeze, {
|
|
freeze: shouldFreeze,
|
|
placeholder: rest.placeholder
|
|
}, rest.children));
|
|
}
|
|
function mapAppearanceToNativeProp(appearance) {
|
|
if (!appearance) return undefined;
|
|
const {
|
|
stacked,
|
|
inline,
|
|
compactInline,
|
|
tabBarBackgroundColor,
|
|
tabBarShadowColor
|
|
} = appearance;
|
|
return {
|
|
...appearance,
|
|
stacked: mapItemAppearanceToNativeProp(stacked),
|
|
inline: mapItemAppearanceToNativeProp(inline),
|
|
compactInline: mapItemAppearanceToNativeProp(compactInline),
|
|
tabBarBackgroundColor: processColor(tabBarBackgroundColor),
|
|
tabBarShadowColor: processColor(tabBarShadowColor)
|
|
};
|
|
}
|
|
function mapItemAppearanceToNativeProp(itemAppearance) {
|
|
if (!itemAppearance) return undefined;
|
|
const {
|
|
normal,
|
|
selected,
|
|
focused,
|
|
disabled
|
|
} = itemAppearance;
|
|
return {
|
|
...itemAppearance,
|
|
normal: mapItemStateAppearanceToNativeProp(normal),
|
|
selected: mapItemStateAppearanceToNativeProp(selected),
|
|
focused: mapItemStateAppearanceToNativeProp(focused),
|
|
disabled: mapItemStateAppearanceToNativeProp(disabled)
|
|
};
|
|
}
|
|
function mapItemStateAppearanceToNativeProp(itemStateAppearance) {
|
|
if (!itemStateAppearance) return undefined;
|
|
const {
|
|
tabBarItemTitleFontColor,
|
|
tabBarItemIconColor,
|
|
tabBarItemBadgeBackgroundColor,
|
|
tabBarItemTitleFontWeight
|
|
} = itemStateAppearance;
|
|
return {
|
|
...itemStateAppearance,
|
|
tabBarItemTitleFontColor: processColor(tabBarItemTitleFontColor),
|
|
tabBarItemIconColor: processColor(tabBarItemIconColor),
|
|
tabBarItemBadgeBackgroundColor: processColor(tabBarItemBadgeBackgroundColor),
|
|
tabBarItemTitleFontWeight: tabBarItemTitleFontWeight !== undefined ? String(tabBarItemTitleFontWeight) : undefined
|
|
};
|
|
}
|
|
function shouldFreezeScreen(nativeViewVisible, screenFocused, freezeOverride) {
|
|
if (!freezeEnabled()) {
|
|
return false;
|
|
}
|
|
if (freezeOverride !== undefined) {
|
|
return freezeOverride;
|
|
}
|
|
if (featureFlags.experiment.controlledBottomTabs) {
|
|
// If the tabs are JS controlled, we want to freeze only when given view is not focused && it is not currently visible
|
|
return !nativeViewVisible && !screenFocused;
|
|
}
|
|
return !nativeViewVisible;
|
|
}
|
|
function parseIconToNativeProps(icon) {
|
|
if (!icon) {
|
|
return {};
|
|
}
|
|
if ('sfSymbolName' in icon) {
|
|
// iOS-specific: SFSymbol usage
|
|
return {
|
|
iconType: 'sfSymbol',
|
|
iconSfSymbolName: icon.sfSymbolName
|
|
};
|
|
} else if ('imageSource' in icon) {
|
|
return {
|
|
iconType: 'image',
|
|
iconImageSource: icon.imageSource
|
|
};
|
|
} else if ('templateSource' in icon) {
|
|
// iOS-specifig: image as a template usage
|
|
return {
|
|
iconType: 'template',
|
|
iconImageSource: icon.templateSource
|
|
};
|
|
} else {
|
|
// iOS-specific: SFSymbol, image as a template usage
|
|
throw new Error('[RNScreens] Incorrect icon format. You must provide sfSymbolName, imageSource or templateSource.');
|
|
}
|
|
}
|
|
function parseIconsToNativeProps(icon, selectedIcon) {
|
|
const {
|
|
iconImageSource,
|
|
iconSfSymbolName,
|
|
iconType
|
|
} = parseIconToNativeProps(icon);
|
|
const {
|
|
iconImageSource: selectedIconImageSource,
|
|
iconSfSymbolName: selectedIconSfSymbolName,
|
|
iconType: selectedIconType
|
|
} = parseIconToNativeProps(selectedIcon);
|
|
if (iconType !== undefined && selectedIconType !== undefined && iconType !== selectedIconType) {
|
|
throw new Error('[RNScreens] icon and selectedIcon must be same type.');
|
|
} else if (iconType === undefined && selectedIconType !== undefined) {
|
|
// iOS-specific: UIKit requirement
|
|
throw new Error('[RNScreens] To use selectedIcon prop, the icon prop must also be provided.');
|
|
}
|
|
return {
|
|
iconType,
|
|
iconImageSource,
|
|
iconSfSymbolName,
|
|
selectedIconImageSource,
|
|
selectedIconSfSymbolName
|
|
};
|
|
}
|
|
export default BottomTabsScreen;
|
|
const styles = StyleSheet.create({
|
|
fillParent: {
|
|
position: 'absolute',
|
|
flex: 1,
|
|
width: '100%',
|
|
height: '100%'
|
|
}
|
|
});
|
|
//# sourceMappingURL=BottomTabsScreen.js.map
|