Skip to content
This repository was archived by the owner on Nov 27, 2022. It is now read-only.

Commit 86e66d0

Browse files
authored
feat: add tabBarSpacing property (#1142)
1 parent 610651c commit 86e66d0

File tree

5 files changed

+147
-15
lines changed

5 files changed

+147
-15
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,10 @@ Style to apply to the inner container for tabs.
518518

519519
Style to apply to the tab bar container.
520520

521+
### `gap`
522+
523+
Define a spacing between tabs.
524+
521525
## Using with other libraries
522526

523527
### [React Navigation](https://github.com/react-navigation/react-navigation)

example/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import TabBarIconExample from './TabBarIconExample';
2121
import CustomIndicatorExample from './CustomIndicatorExample';
2222
import CustomTabBarExample from './CustomTabBarExample';
2323
import CoverflowExample from './CoverflowExample';
24+
import TabBarGapExample from './TabBarGapExample';
2425

2526
type State = {
2627
title: string;
@@ -47,6 +48,7 @@ const EXAMPLE_COMPONENTS: ExampleComponentType[] = [
4748
CustomIndicatorExample,
4849
CustomTabBarExample,
4950
CoverflowExample,
51+
TabBarGapExample,
5052
];
5153

5254
const KeepAwake = () => {

example/src/TabBarGapExample.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import * as React from 'react';
2+
import { StyleSheet } from 'react-native';
3+
import {
4+
TabView,
5+
TabBar,
6+
SceneMap,
7+
NavigationState,
8+
SceneRendererProps,
9+
} from 'react-native-tab-view';
10+
import Article from './Shared/Article';
11+
import Albums from './Shared/Albums';
12+
import Chat from './Shared/Chat';
13+
import Contacts from './Shared/Contacts';
14+
15+
type State = NavigationState<{
16+
key: string;
17+
title: string;
18+
}>;
19+
20+
export default class TabBarGapExample extends React.Component<{}, State> {
21+
// eslint-disable-next-line react/sort-comp
22+
static title = 'Tab bar gap';
23+
static backgroundColor = '#3f51b5';
24+
static appbarElevation = 0;
25+
26+
state = {
27+
index: 1,
28+
routes: [
29+
{ key: 'article', title: 'Article' },
30+
{ key: 'contacts', title: 'Contacts' },
31+
{ key: 'albums', title: 'Albums' },
32+
{ key: 'chat', title: 'Chat ' },
33+
],
34+
};
35+
36+
private handleIndexChange = (index: number) =>
37+
this.setState({
38+
index,
39+
});
40+
41+
private renderTabBar = (
42+
props: SceneRendererProps & { navigationState: State }
43+
) => (
44+
<TabBar
45+
{...props}
46+
scrollEnabled
47+
indicatorStyle={styles.indicator}
48+
style={styles.tabbar}
49+
tabStyle={styles.tab}
50+
labelStyle={styles.label}
51+
gap={40}
52+
/>
53+
);
54+
55+
private renderScene = SceneMap({
56+
albums: Albums,
57+
contacts: Contacts,
58+
article: Article,
59+
chat: Chat,
60+
});
61+
62+
render() {
63+
return (
64+
<TabView
65+
lazy
66+
navigationState={this.state}
67+
renderScene={this.renderScene}
68+
renderTabBar={this.renderTabBar}
69+
onIndexChange={this.handleIndexChange}
70+
/>
71+
);
72+
}
73+
}
74+
75+
const styles = StyleSheet.create({
76+
tabbar: {
77+
backgroundColor: '#3f51b5',
78+
},
79+
tab: {
80+
width: 'auto',
81+
},
82+
indicator: {
83+
backgroundColor: '#ffeb3b',
84+
},
85+
label: {
86+
fontWeight: '400',
87+
},
88+
});

src/TabBar.tsx

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,18 @@ export type Props<T extends Route> = SceneRendererProps & {
5959
labelStyle?: StyleProp<TextStyle>;
6060
contentContainerStyle?: StyleProp<ViewStyle>;
6161
style?: StyleProp<ViewStyle>;
62+
gap?: number;
6263
};
6364

6465
type State = {
6566
layout: Layout;
6667
tabWidths: { [key: string]: number };
6768
};
6869

70+
const Separator = ({ width }: { width: number }) => {
71+
return <View style={{ width }} />;
72+
};
73+
6974
export default class TabBar<T extends Route> extends React.Component<
7075
Props<T>,
7176
State
@@ -84,6 +89,7 @@ export default class TabBar<T extends Route> extends React.Component<
8489
renderIndicator: (props: IndicatorProps<Route>) => (
8590
<TabBarIndicator {...props} />
8691
),
92+
gap: 0,
8793
};
8894

8995
state: State = {
@@ -175,6 +181,7 @@ export default class TabBar<T extends Route> extends React.Component<
175181
return routes.reduce<number>(
176182
(acc, _, i) =>
177183
acc +
184+
(i > 0 ? props.gap ?? 0 : 0) +
178185
this.getComputedTabWidth(
179186
i,
180187
layout,
@@ -224,7 +231,12 @@ export default class TabBar<T extends Route> extends React.Component<
224231

225232
// To get the current index centered we adjust scroll amount by width of indexes
226233
// 0 through (i - 1) and add half the width of current index i
227-
return total + (index === i ? tabWidth / 2 : tabWidth);
234+
return (
235+
total +
236+
(index === i
237+
? (tabWidth + (props.gap ?? 0)) / 2
238+
: tabWidth + (props.gap ?? 0))
239+
);
228240
},
229241
0
230242
);
@@ -299,12 +311,16 @@ export default class TabBar<T extends Route> extends React.Component<
299311
contentContainerStyle,
300312
style,
301313
indicatorContainerStyle,
314+
gap = 0,
302315
} = this.props;
303316
const { layout, tabWidths } = this.state;
304317
const { routes } = navigationState;
305318

306319
const isWidthDynamic = this.getFlattenedTabWidth(tabStyle) === 'auto';
307320
const tabBarWidth = this.getTabBarWidth(this.props, this.state);
321+
const separatorsWidth = Math.max(0, routes.length - 1) * gap;
322+
const separatorPercent = (separatorsWidth / tabBarWidth) * 100;
323+
308324
const tabBarWidthPercent = `${routes.length * 40}%`;
309325
const translateX = this.getTranslateX(
310326
this.scrollAmount,
@@ -321,8 +337,8 @@ export default class TabBar<T extends Route> extends React.Component<
321337
style={[
322338
styles.indicatorContainer,
323339
scrollEnabled ? { transform: [{ translateX }] as any } : null,
324-
tabBarWidth
325-
? { width: tabBarWidth }
340+
tabBarWidth > separatorsWidth
341+
? { width: tabBarWidth - separatorsWidth }
326342
: scrollEnabled
327343
? { width: tabBarWidthPercent }
328344
: null,
@@ -334,7 +350,9 @@ export default class TabBar<T extends Route> extends React.Component<
334350
layout,
335351
navigationState,
336352
jumpTo,
337-
width: isWidthDynamic ? 'auto' : `${100 / routes.length}%`,
353+
width: isWidthDynamic
354+
? 'auto'
355+
: `${(100 - separatorPercent) / routes.length}%`,
338356
style: indicatorStyle,
339357
getTabWidth: (i: number) =>
340358
this.getComputedTabWidth(
@@ -345,6 +363,7 @@ export default class TabBar<T extends Route> extends React.Component<
345363
tabWidths,
346364
this.getFlattenedTabWidth(tabStyle)
347365
),
366+
gap,
348367
})}
349368
</Animated.View>
350369
<View style={styles.scroll}>
@@ -363,7 +382,12 @@ export default class TabBar<T extends Route> extends React.Component<
363382
contentContainerStyle={[
364383
styles.tabContent,
365384
scrollEnabled
366-
? { width: tabBarWidth || tabBarWidthPercent }
385+
? {
386+
width:
387+
tabBarWidth > separatorsWidth
388+
? tabBarWidth
389+
: tabBarWidthPercent,
390+
}
367391
: styles.container,
368392
contentContainerStyle,
369393
]}
@@ -380,7 +404,7 @@ export default class TabBar<T extends Route> extends React.Component<
380404
)}
381405
ref={this.scrollViewRef}
382406
>
383-
{routes.map((route: T) => {
407+
{routes.map((route: T, index) => {
384408
const props: TabBarItemProps<T> & { key: string } = {
385409
key: route.key,
386410
position: position,
@@ -438,10 +462,15 @@ export default class TabBar<T extends Route> extends React.Component<
438462
style: tabStyle,
439463
};
440464

441-
return renderTabBarItem ? (
442-
renderTabBarItem(props)
443-
) : (
444-
<TabBarItem {...props} />
465+
return (
466+
<React.Fragment key={route.key}>
467+
{gap > 0 && index > 0 ? <Separator width={gap} /> : null}
468+
{renderTabBarItem ? (
469+
renderTabBarItem(props)
470+
) : (
471+
<TabBarItem {...props} />
472+
)}
473+
</React.Fragment>
445474
);
446475
})}
447476
</Animated.ScrollView>

src/TabBarIndicator.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export type Props<T extends Route> = SceneRendererProps & {
1818
width: string | number;
1919
style?: StyleProp<ViewStyle>;
2020
getTabWidth: GetTabWidth;
21+
gap?: number;
2122
};
2223

2324
export default class TabBarIndicator<T extends Route> extends React.Component<
@@ -59,14 +60,15 @@ export default class TabBarIndicator<T extends Route> extends React.Component<
5960
private getTranslateX = (
6061
position: Animated.AnimatedInterpolation,
6162
routes: Route[],
62-
getTabWidth: GetTabWidth
63+
getTabWidth: GetTabWidth,
64+
gap?: number
6365
) => {
6466
const inputRange = routes.map((_, i) => i);
6567

6668
// every index contains widths at all previous indices
6769
const outputRange = routes.reduce<number[]>((acc, _, i) => {
6870
if (i === 0) return [0];
69-
return [...acc, acc[i - 1] + getTabWidth(i - 1)];
71+
return [...acc, acc[i - 1] + getTabWidth(i - 1) + (gap ?? 0)];
7072
}, []);
7173

7274
const translateX = position.interpolate({
@@ -79,16 +81,23 @@ export default class TabBarIndicator<T extends Route> extends React.Component<
7981
};
8082

8183
render() {
82-
const { position, navigationState, getTabWidth, width, style, layout } =
83-
this.props;
84+
const {
85+
position,
86+
navigationState,
87+
getTabWidth,
88+
width,
89+
style,
90+
layout,
91+
gap,
92+
} = this.props;
8493
const { routes } = navigationState;
8594

8695
const transform = [];
8796

8897
if (layout.width) {
8998
const translateX =
9099
routes.length > 1
91-
? this.getTranslateX(position, routes, getTabWidth)
100+
? this.getTranslateX(position, routes, getTabWidth, gap)
92101
: 0;
93102

94103
transform.push({ translateX });

0 commit comments

Comments
 (0)