Skip to content

Commit 8d8e404

Browse files
artyorsh32penkin
authored andcommitted
refactor(ui): tab-view enhancements (#363)
1 parent a5fafb1 commit 8d8e404

File tree

8 files changed

+86
-46
lines changed

8 files changed

+86
-46
lines changed

src/framework/ui/tab/tab.component.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import {
44
TouchableOpacity,
55
TouchableOpacityProps,
66
StyleSheet,
7+
StyleProp,
8+
TextStyle,
79
} from 'react-native';
810
import {
11+
allWithRest,
912
styled,
1013
StyledComponentProps,
1114
StyleType,
@@ -14,8 +17,10 @@ import {
1417
Text as TextComponent,
1518
Props as TextProps,
1619
} from '../text/text.component';
20+
import { TextStyleProps } from '../common/props';
1721

1822
interface TabProps {
23+
style?: StyleProp<TextStyle>;
1924
title?: string;
2025
icon?: (style: StyleType) => React.ReactElement<ImageProps>;
2126
selected?: boolean;
@@ -40,6 +45,9 @@ export class Tab extends React.Component<Props> {
4045
};
4146

4247
private getComponentStyle = (source: StyleType): StyleType => {
48+
const derivedStyle: TextStyle = StyleSheet.flatten(this.props.style);
49+
const { rest: derivedContainerStyle, ...derivedTextStyle } = allWithRest(derivedStyle, TextStyleProps);
50+
4351
const {
4452
textMarginVertical,
4553
textFontSize,
@@ -56,6 +64,7 @@ export class Tab extends React.Component<Props> {
5664
return {
5765
container: {
5866
...containerParameters,
67+
...derivedContainerStyle,
5968
...styles.container,
6069
},
6170
icon: {
@@ -71,6 +80,7 @@ export class Tab extends React.Component<Props> {
7180
lineHeight: textLineHeight,
7281
fontWeight: textFontWeight,
7382
color: textColor,
83+
...derivedTextStyle,
7484
...styles.title,
7585
},
7686
};

src/framework/ui/tab/tab.spec.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,12 @@ describe('@tab-view: component checks', () => {
152152
};
153153

154154
it('* emits onSelect with correct args', () => {
155+
const screenWidth: number = 375;
156+
155157
const onSelect = jest.fn();
156158

157159
const component = render(
158-
<Mock
159-
contentWidth={375}
160-
onSelect={onSelect}>
160+
<Mock onSelect={onSelect}>
161161
<ChildMock>
162162
<View/>
163163
</ChildMock>
@@ -169,10 +169,18 @@ describe('@tab-view: component checks', () => {
169169

170170
const scrollView: ReactTestInstance = component.getByType(ScrollView);
171171

172+
fireEvent(scrollView, 'layout', {
173+
nativeEvent: {
174+
layout: {
175+
width: screenWidth,
176+
},
177+
},
178+
});
179+
172180
fireEvent(scrollView, 'momentumScrollEnd', {
173181
nativeEvent: {
174182
contentOffset: {
175-
x: 375,
183+
x: screenWidth,
176184
},
177185
},
178186
});
@@ -189,7 +197,7 @@ describe('@tab-view: component checks', () => {
189197
});
190198

191199
const component: RenderAPI = render(
192-
<Mock contentWidth={375} shouldLoadComponent={shouldLoadComponent}>
200+
<Mock shouldLoadComponent={shouldLoadComponent}>
193201
<ChildMock>
194202
<View/>
195203
</ChildMock>
@@ -202,7 +210,7 @@ describe('@tab-view: component checks', () => {
202210
const scrollView: ReactTestInstance = component.getByType(ScrollView);
203211
const unloadedChild: ReactTestInstance = scrollView.props.children[disabledComponentIndex];
204212

205-
expect(unloadedChild.props.children).toEqual(undefined);
213+
expect(unloadedChild.props.children).toBeNull();
206214
});
207215
});
208216

src/framework/ui/tab/tabBar.component.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import {
33
StyleSheet,
4+
TextStyle,
45
View,
56
ViewProps,
67
} from 'react-native';
@@ -66,9 +67,11 @@ export class TabBar extends React.Component<Props> {
6667
};
6768

6869
private renderComponentChild = (element: TabElement, index: number): TabElement => {
70+
const derivedStyle: TextStyle = StyleSheet.flatten(element.props.style);
71+
6972
return React.cloneElement(element, {
7073
key: index,
71-
style: { flex: 1 },
74+
style: { ...derivedStyle, flex: 1 },
7275
selected: index === this.props.selectedIndex,
7376
onSelect: () => this.onChildPress(index),
7477
});

src/framework/ui/tab/tabBarIndicator.component.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ export class TabBarIndicator extends React.Component<Props> {
3737
public componentDidUpdate() {
3838
const { selectedPosition: index } = this.props;
3939

40-
this.scrollToIndex({
41-
index,
42-
animated: true,
43-
});
40+
this.scrollToIndex({ index, animated: true });
4441
}
4542

4643
/**

src/framework/ui/tab/tabView.component.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class TabView extends React.Component<Props> {
7777
const { children, ...elementProps } = element.props;
7878

7979
return {
80-
tab: React.createElement(Tab, { key: index, ...elementProps }),
80+
tab: React.cloneElement(element, { key: index, ...elementProps }),
8181
content: children,
8282
};
8383
};
@@ -107,7 +107,6 @@ export class TabView extends React.Component<Props> {
107107
<ViewPager
108108
ref={this.viewPagerRef}
109109
selectedIndex={selectedIndex}
110-
contentWidth={contentWidth}
111110
shouldLoadComponent={this.props.shouldLoadComponent}
112111
onOffsetChange={this.onPagerOffsetChange}
113112
onSelect={this.onPagerSelect}>

src/framework/ui/viewPager/viewPager.component.tsx

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {
33
View,
44
ScrollView,
55
ScrollViewProps,
6+
LayoutChangeEvent,
7+
StyleSheet,
8+
ViewProps,
69
} from 'react-native';
710
import { ScrollEvent } from '../common/type';
811

912
type ChildElement = React.ReactElement<any>;
1013

1114
interface ViewPagerProps {
12-
contentWidth: number;
1315
children: ChildElement | ChildElement[];
1416
selectedIndex?: number;
1517
shouldLoadComponent?: (index: number) => boolean;
@@ -27,6 +29,7 @@ export class ViewPager extends React.Component<Props> {
2729
};
2830

2931
private scrollViewRef: React.RefObject<ScrollView> = React.createRef();
32+
private contentWidth: number = 0;
3033

3134
public componentDidMount() {
3235
const { selectedIndex: index } = this.props;
@@ -41,15 +44,12 @@ export class ViewPager extends React.Component<Props> {
4144
public componentDidUpdate() {
4245
const { selectedIndex: index } = this.props;
4346

44-
this.scrollToIndex({
45-
index,
46-
animated: true,
47-
});
47+
this.scrollToIndex({ index, animated: true });
4848
}
4949

5050
public scrollToIndex(params: { index: number; animated?: boolean }) {
5151
const { index, ...rest } = params;
52-
const offset: number = this.props.contentWidth * index;
52+
const offset: number = this.contentWidth * index;
5353

5454
this.scrollToOffset({ offset, ...rest });
5555
}
@@ -70,47 +70,67 @@ export class ViewPager extends React.Component<Props> {
7070

7171
private onScrollEnd = (event: ScrollEvent) => {
7272
const { x: offset } = event.nativeEvent.contentOffset;
73-
const { selectedIndex: derivedSelectedIndex, contentWidth } = this.props;
73+
const { selectedIndex: derivedSelectedIndex } = this.props;
7474

75-
const selectedIndex: number = offset / contentWidth;
75+
const selectedIndex: number = offset / this.contentWidth;
7676

7777
if (selectedIndex !== derivedSelectedIndex && this.props.onSelect) {
78-
this.props.onSelect(selectedIndex);
78+
this.props.onSelect(Math.round(selectedIndex));
79+
}
80+
};
81+
82+
private onLayout = (event: LayoutChangeEvent) => {
83+
const { width } = event.nativeEvent.layout;
84+
this.contentWidth = width;
85+
86+
if (this.props.onLayout) {
87+
this.props.onLayout(event);
7988
}
8089
};
8190

8291
private renderComponentChild = (element: ChildElement, index: number): ChildElement => {
83-
const { shouldLoadComponent: shouldLoad, contentWidth } = this.props;
92+
const { shouldLoadComponent } = this.props;
93+
94+
const contentView: ChildElement | null = shouldLoadComponent(index) ? element : null;
8495

8596
return React.createElement(View, {
86-
...element.props,
8797
key: index,
88-
width: contentWidth,
89-
children: shouldLoad(index) ? element : undefined,
90-
});
98+
style: styles.contentViewContainer,
99+
}, contentView);
91100
};
92101

93102
private renderComponentChildren = (source: ChildElement | ChildElement[]): ChildElement[] => {
94103
return React.Children.map(source, this.renderComponentChild);
95104
};
96105

97106
public render(): React.ReactNode {
98-
const { children, ...derivedProps } = this.props;
107+
const { contentContainerStyle, children, ...derivedProps } = this.props;
99108
const componentChildren: ChildElement[] = this.renderComponentChildren(children);
100109

101110
return (
102111
<ScrollView
103-
{...derivedProps}
104-
ref={this.scrollViewRef}
105112
bounces={false}
113+
contentContainerStyle={[styles.contentContainer, contentContainerStyle]}
106114
showsHorizontalScrollIndicator={false}
115+
{...derivedProps}
116+
ref={this.scrollViewRef}
107117
scrollEventThrottle={16}
108118
horizontal={true}
109119
pagingEnabled={true}
110120
onScroll={this.onScroll}
111-
onMomentumScrollEnd={this.onScrollEnd}>
121+
onMomentumScrollEnd={this.onScrollEnd}
122+
onLayout={this.onLayout}>
112123
{componentChildren}
113124
</ScrollView>
114125
);
115126
}
116127
}
128+
129+
const styles = StyleSheet.create({
130+
contentContainer: {
131+
flex: 1,
132+
},
133+
contentViewContainer: {
134+
width: '100%',
135+
},
136+
});

src/framework/ui/viewPager/viewPager.spec.tsx

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,31 @@ describe('@view-pager: component checks', () => {
2626
);
2727

2828
it('* emits onSelect with correct args', () => {
29+
const screenWidth: number = 375;
30+
2931
const onSelect = jest.fn();
3032

3133
const component: RenderAPI = render(
32-
<Mock
33-
contentWidth={375}
34-
onSelect={onSelect}>
34+
<Mock onSelect={onSelect}>
3535
<ChildMock/>
3636
<ChildMock/>
3737
</Mock>,
3838
);
39+
3940
const scrollView: ReactTestInstance = component.getByType(ScrollView);
4041

42+
fireEvent(scrollView, 'layout', {
43+
nativeEvent: {
44+
layout: {
45+
width: screenWidth,
46+
},
47+
},
48+
});
49+
4150
fireEvent(scrollView, 'momentumScrollEnd', {
4251
nativeEvent: {
4352
contentOffset: {
44-
x: 375,
53+
x: screenWidth,
4554
},
4655
},
4756
});
@@ -50,12 +59,11 @@ describe('@view-pager: component checks', () => {
5059
});
5160

5261
it('* emits onOffsetChange with correct args', () => {
62+
5363
const onOffsetChange = jest.fn();
5464

5565
const component: RenderAPI = render(
56-
<Mock
57-
contentWidth={375}
58-
onOffsetChange={onOffsetChange}>
66+
<Mock onOffsetChange={onOffsetChange}>
5967
<ChildMock/>
6068
<ChildMock/>
6169
</Mock>,
@@ -77,9 +85,7 @@ describe('@view-pager: component checks', () => {
7785
const shouldLoadComponent = jest.fn();
7886

7987
render(
80-
<Mock
81-
contentWidth={375}
82-
shouldLoadComponent={shouldLoadComponent}>
88+
<Mock shouldLoadComponent={shouldLoadComponent}>
8389
<ChildMock/>
8490
<ChildMock/>
8591
</Mock>,
@@ -97,9 +103,7 @@ describe('@view-pager: component checks', () => {
97103
});
98104

99105
const component: RenderAPI = render(
100-
<Mock
101-
contentWidth={375}
102-
shouldLoadComponent={shouldLoadComponent}>
106+
<Mock shouldLoadComponent={shouldLoadComponent}>
103107
<ChildMock/>
104108
<ChildMock/>
105109
</Mock>,
@@ -109,7 +113,7 @@ describe('@view-pager: component checks', () => {
109113

110114
const unloadedChild = scrollView.props.children[disabledComponentIndex];
111115

112-
expect(unloadedChild.props.children).toEqual(undefined);
116+
expect(unloadedChild.props.children).toBeNull();
113117
});
114118

115119
});

src/playground/src/ui/screen/viewPager.component.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class ViewPagerScreen extends React.Component<Props, State> {
3636
return (
3737
<ViewPager
3838
selectedIndex={this.state.selectedIndex}
39-
contentWidth={Dimensions.get('window').width}
4039
onSelect={this.onIndexChange}>
4140
<View style={this.props.themedStyle.tabContainer}>
4241
<Text>Tab 1</Text>

0 commit comments

Comments
 (0)