153 lines
4.5 KiB
TypeScript
153 lines
4.5 KiB
TypeScript
import {
|
|
createNavigatorFactory,
|
|
TabRouter,
|
|
useNavigationBuilder,
|
|
type DefaultNavigatorOptions,
|
|
type NavigationProp,
|
|
type ParamListBase,
|
|
type TabActionHelpers,
|
|
type TabNavigationState,
|
|
type TabRouterOptions,
|
|
} from '@react-navigation/native';
|
|
import { withLayoutContext } from 'expo-router';
|
|
import type { ImageSourcePropType } from 'react-native';
|
|
import TabView, { type AppleIcon } from 'react-native-bottom-tabs';
|
|
|
|
// Pro-Screen Optionen (kompatibel mit Expo Router's Tabs.Screen API)
|
|
export type NativeTabsScreenOptions = {
|
|
title?: string;
|
|
tabBarIcon?: (props: { focused: boolean }) => AppleIcon | ImageSourcePropType;
|
|
tabBarBadge?: string;
|
|
// Expo-Router-Konvention: href === null → Screen NICHT in TabBar zeigen
|
|
href?: string | null;
|
|
};
|
|
|
|
type NativeTabNavigationEventMap = {
|
|
tabPress: { data: undefined; canPreventDefault: true };
|
|
tabLongPress: { data: undefined };
|
|
};
|
|
|
|
// Native-spezifische Tab-Layer-Optionen (iOS 26 Glass-Pill, Haptik, etc.)
|
|
type NativeOnlyOptions = {
|
|
sidebarAdaptable?: boolean;
|
|
hapticFeedbackEnabled?: boolean;
|
|
disablePageAnimations?: boolean;
|
|
scrollEdgeAppearance?: 'default' | 'opaque' | 'transparent';
|
|
minimizeBehavior?: 'automatic' | 'onScrollDown' | 'onScrollUp' | 'never';
|
|
tabBarActiveTintColor?: string;
|
|
tabBarInactiveTintColor?: string;
|
|
labeled?: boolean;
|
|
tabLabelStyle?: {
|
|
fontFamily?: string;
|
|
fontWeight?: string;
|
|
fontSize?: number;
|
|
};
|
|
};
|
|
|
|
type Props = DefaultNavigatorOptions<
|
|
ParamListBase,
|
|
string | undefined,
|
|
TabNavigationState<ParamListBase>,
|
|
NativeTabsScreenOptions,
|
|
NativeTabNavigationEventMap,
|
|
NavigationProp<ParamListBase>
|
|
> &
|
|
TabRouterOptions &
|
|
NativeOnlyOptions;
|
|
|
|
function NativeTabsNavigator({
|
|
id,
|
|
initialRouteName,
|
|
children,
|
|
screenOptions,
|
|
layout,
|
|
sidebarAdaptable = true,
|
|
hapticFeedbackEnabled = true,
|
|
disablePageAnimations,
|
|
scrollEdgeAppearance,
|
|
minimizeBehavior,
|
|
tabBarActiveTintColor,
|
|
tabBarInactiveTintColor,
|
|
labeled = true,
|
|
tabLabelStyle,
|
|
}: Props) {
|
|
const { state, descriptors, navigation, NavigationContent } =
|
|
useNavigationBuilder<
|
|
TabNavigationState<ParamListBase>,
|
|
TabRouterOptions,
|
|
TabActionHelpers<ParamListBase>,
|
|
NativeTabsScreenOptions,
|
|
NativeTabNavigationEventMap
|
|
>(TabRouter, {
|
|
id,
|
|
initialRouteName,
|
|
children,
|
|
screenOptions,
|
|
layout,
|
|
});
|
|
|
|
return (
|
|
<NavigationContent>
|
|
<TabView
|
|
labeled={labeled}
|
|
sidebarAdaptable={sidebarAdaptable}
|
|
hapticFeedbackEnabled={hapticFeedbackEnabled}
|
|
disablePageAnimations={disablePageAnimations}
|
|
scrollEdgeAppearance={scrollEdgeAppearance}
|
|
minimizeBehavior={minimizeBehavior}
|
|
tabBarActiveTintColor={tabBarActiveTintColor}
|
|
tabBarInactiveTintColor={tabBarInactiveTintColor}
|
|
tabLabelStyle={tabLabelStyle}
|
|
navigationState={{
|
|
index: state.index,
|
|
routes: state.routes.map((route) => {
|
|
const options = descriptors[route.key].options;
|
|
return {
|
|
key: route.key,
|
|
title: options.title ?? route.name,
|
|
focusedIcon: options.tabBarIcon
|
|
? options.tabBarIcon({ focused: true })
|
|
: undefined,
|
|
unfocusedIcon: options.tabBarIcon
|
|
? options.tabBarIcon({ focused: false })
|
|
: undefined,
|
|
badge: options.tabBarBadge,
|
|
hidden: options.href === null,
|
|
};
|
|
}),
|
|
}}
|
|
onIndexChange={(index) => {
|
|
const route = state.routes[index];
|
|
const event = navigation.emit({
|
|
type: 'tabPress',
|
|
target: route.key,
|
|
canPreventDefault: true,
|
|
});
|
|
if (!event.defaultPrevented) {
|
|
navigation.dispatch({
|
|
type: 'NAVIGATE',
|
|
payload: { name: route.name, merge: true },
|
|
target: state.key,
|
|
});
|
|
}
|
|
}}
|
|
onTabLongPress={(index) => {
|
|
const route = state.routes[index];
|
|
navigation.emit({
|
|
type: 'tabLongPress',
|
|
target: route.key,
|
|
});
|
|
}}
|
|
renderScene={({ route }) => descriptors[route.key].render()}
|
|
/>
|
|
</NavigationContent>
|
|
);
|
|
}
|
|
|
|
export const createNativeTabNavigator = createNavigatorFactory(NativeTabsNavigator);
|
|
|
|
const NativeTabNav = createNativeTabNavigator();
|
|
|
|
// withLayoutContext-wrapped Navigator für Expo-Router-Kompatibilität
|
|
export const NativeTabs = withLayoutContext(NativeTabNav.Navigator);
|