Skip to content

Commit

Permalink
refactor: tweak tab view
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Mar 4, 2024
1 parent e3b2b13 commit bb8ed5e
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 109 deletions.
51 changes: 26 additions & 25 deletions packages/material-top-tabs/src/views/MaterialTopTabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import {
import Color from 'color';
import * as React from 'react';
import { type StyleProp, StyleSheet, type ViewStyle } from 'react-native';
import {
TabBar,
TabBarIndicator,
type TabDescriptor,
} from 'react-native-tab-view';
import { TabBar, TabBarIndicator } from 'react-native-tab-view';

import type { MaterialTopTabBarProps } from '../types';

Expand Down Expand Up @@ -59,31 +55,37 @@ export function MaterialTopTabBar({
focusedOptions.tabBarInactiveTintColor ??
Color(activeColor).alpha(0.5).rgb().string();

const tabBarOptions: Record<string, TabDescriptor<{ key: string }>> = {};
const tabBarOptions = Object.fromEntries(
state.routes.map((route) => {
const { options } = descriptors[route.key];

state.routes.forEach((route) => {
const { options } = descriptors[route.key];
tabBarOptions[route.key] = {
testID: options.tabBarButtonTestID,
accessibilityLabel: options.tabBarAccessibilityLabel,
badge: options.tabBarBadge,
icon: options.tabBarShowIcon === false ? undefined : options.tabBarIcon,
label: options.tabBarShowLabel === false ? undefined : renderLabel,
labelAllowFontScaling: options.tabBarAllowFontScaling,
labelStyle: options.tabBarLabelStyle,
labelText:
options.tabBarShowLabel === false
? undefined
: options.title !== undefined
? options.title
: route.name,
};
});
return [
route.key,
{
testID: options.tabBarButtonTestID,
accessibilityLabel: options.tabBarAccessibilityLabel,
badge: options.tabBarBadge,
icon:
options.tabBarShowIcon === false ? undefined : options.tabBarIcon,
label: options.tabBarShowLabel === false ? undefined : renderLabel,
labelAllowFontScaling: options.tabBarAllowFontScaling,
labelStyle: options.tabBarLabelStyle,
labelText:
options.tabBarShowLabel === false
? undefined
: options.title !== undefined
? options.title
: route.name,
},
];
})
);

return (
<TabBar
{...rest}
navigationState={state}
options={tabBarOptions}
direction={direction}
scrollEnabled={focusedOptions.tabBarScrollEnabled}
bounces={focusedOptions.tabBarBounces}
Expand Down Expand Up @@ -118,7 +120,6 @@ export function MaterialTopTabBar({
target: route.key,
})
}
options={tabBarOptions}
renderIndicator={({ navigationState: state, ...rest }) => {
return focusedOptions.tabBarIndicator ? (
focusedOptions.tabBarIndicator({
Expand Down
158 changes: 86 additions & 72 deletions packages/react-native-tab-view/src/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -443,87 +443,101 @@ export function TabBar<T extends Route>({

const renderItem = React.useCallback(
({ item: route, index }: ListRenderItemInfo<T>) => {
const props: TabBarItemProps<T> & { key: string } = {
key: route.key,
position: position,
route: route,
navigationState: navigationState,
options: {
testID: getTestIdDefault({ route }),
labelText: getLabelTextDefault({ route }),
accessible: getAccessibleDefault({ route }),
accessibilityLabel: getAccessibilityLabelDefault({ route }),
...commonOptions,
...options?.[route.key],
},
activeColor: activeColor,
inactiveColor: inactiveColor,
pressColor: pressColor,
pressOpacity: pressOpacity,
onLayout: isWidthDynamic
? (e: LayoutChangeEvent) => {
measuredTabWidths.current[route.key] = e.nativeEvent.layout.width;
const {
testID = getTestIdDefault({ route }),
labelText = getLabelTextDefault({ route }),
accessible = getAccessibleDefault({ route }),
accessibilityLabel = getAccessibilityLabelDefault({ route }),
} = {
...commonOptions,
...options?.[route.key],
};

// When we have measured widths for all of the tabs, we should updates the state
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
// If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items.
if (
routes.length > MEASURE_PER_BATCH &&
index === MEASURE_PER_BATCH &&
routes
.slice(0, MEASURE_PER_BATCH)
.every(
(r) => typeof measuredTabWidths.current[r.key] === 'number'
)
) {
setTabWidths({ ...measuredTabWidths.current });
} else if (
routes.every(
const onLayout = isWidthDynamic
? (e: LayoutChangeEvent) => {
measuredTabWidths.current[route.key] = e.nativeEvent.layout.width;

// When we have measured widths for all of the tabs, we should updates the state
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
// If we have more than 10 routes divide updating tabWidths into multiple batches. Here we update only first batch of 10 items.
if (
routes.length > MEASURE_PER_BATCH &&
index === MEASURE_PER_BATCH &&
routes
.slice(0, MEASURE_PER_BATCH)
.every(
(r) => typeof measuredTabWidths.current[r.key] === 'number'
)
) {
// When we have measured widths for all of the tabs, we should updates the state
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
setTabWidths({ ...measuredTabWidths.current });
}
) {
setTabWidths({ ...measuredTabWidths.current });
} else if (
routes.every(
(r) => typeof measuredTabWidths.current[r.key] === 'number'
)
) {
// When we have measured widths for all of the tabs, we should updates the state
// We avoid doing separate setState for each layout since it triggers multiple renders and slows down app
setTabWidths({ ...measuredTabWidths.current });
}
: undefined,
onPress: () => {
const event: Scene<T> & Event = {
route,
defaultPrevented: false,
preventDefault: () => {
event.defaultPrevented = true;
},
};
}
: undefined;

const onPress = () => {
const event: Scene<T> & Event = {
route,
defaultPrevented: false,
preventDefault: () => {
event.defaultPrevented = true;
},
};

onTabPress?.(event);
onTabPress?.(event);

if (event.defaultPrevented) {
return;
}
if (event.defaultPrevented) {
return;
}

jumpTo(route.key);
};

jumpTo(route.key);
},
onLongPress: () => onTabLongPress?.({ route }),
labelStyle: labelStyle,
const onLongPress = () => onTabLongPress?.({ route });

// Calculate the default width for tab for FlatList to work
const defaultTabWidth = !isWidthDynamic
? getComputedTabWidth(
index,
layout,
routes,
scrollEnabled,
tabWidths,
getFlattenedTabWidth(tabStyle),
getFlattenedPaddingRight(contentContainerStyle),
getFlattenedPaddingLeft(contentContainerStyle),
gap
)
: undefined;

const props = {
key: route.key,
position,
route,
navigationState,
testID,
labelText,
accessible,
accessibilityLabel,
activeColor,
inactiveColor,
pressColor,
pressOpacity,
onLayout,
onPress,
onLongPress,
labelStyle,
style: tabStyle,
// Calculate the deafult width for tab for FlatList to work
defaultTabWidth: !isWidthDynamic
? getComputedTabWidth(
index,
layout,
routes,
scrollEnabled,
tabWidths,
getFlattenedTabWidth(tabStyle),
getFlattenedPaddingRight(contentContainerStyle),
getFlattenedPaddingLeft(contentContainerStyle),
gap
)
: undefined,
defaultTabWidth,
android_ripple,
};
} satisfies TabBarItemProps<T> & { key: string };

return (
<>
Expand Down
16 changes: 4 additions & 12 deletions packages/react-native-tab-view/src/TabBarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import { PlatformPressable } from './PlatformPressable';
import { TabBarItemLabel } from './TabBarItemLabel';
import type { NavigationState, Route, TabDescriptor } from './types';

export type Props<T extends Route> = {
export type Props<T extends Route> = TabDescriptor<T> & {
position: Animated.AnimatedInterpolation<number>;
route: T;
navigationState: NavigationState<T>;
activeColor?: string;
inactiveColor?: string;
pressColor?: string;
pressOpacity?: number;
options?: TabDescriptor<T>;
onLayout?: (event: LayoutChangeEvent) => void;
onPress: () => void;
onLongPress: () => void;
Expand Down Expand Up @@ -251,15 +250,9 @@ const MemoizedTabBarItemInternal = React.memo(
) as typeof TabBarItemInternal;

export function TabBarItem<T extends Route>(props: Props<T>) {
const {
onPress,
onLongPress,
onLayout,
navigationState,
route,
options,
...rest
} = props;
const { onPress, onLongPress, onLayout, navigationState, route, ...rest } =
props;

const onPressLatest = useLatestCallback(onPress);
const onLongPressLatest = useLatestCallback(onLongPress);
const onLayoutLatest = useLatestCallback(onLayout ? onLayout : () => {});
Expand All @@ -269,7 +262,6 @@ export function TabBarItem<T extends Route>(props: Props<T>) {
return (
<MemoizedTabBarItemInternal
{...rest}
{...options}
onPress={onPressLatest}
onLayout={onLayoutLatest}
onLongPress={onLongPressLatest}
Expand Down

0 comments on commit bb8ed5e

Please sign in to comment.