Skip to content

Commit bcef1fa

Browse files
Inbal-Tishethanshar
authored andcommitted
Feat/carousel page width (#513)
* Adding support for pageWidth * fix paddingLeft * Adding PageControl and Counter options * adding onPagePress to example; fix for updateOffset * fix for loop with pageWidth * removing default pageControlPosition * snapToAlignment iOS only * fix demo screen with Carousel (adding containerStyle with flex) * removing loop from pageWidth * fix for initialPage offset and goToPage in Android * adjust demo screen * Fix for Android RTL with pageWidth * fix for android RTL - PageControl * replacing snapToInterval with snapToOffsets
1 parent 9e2123f commit bcef1fa

File tree

7 files changed

+157
-52
lines changed

7 files changed

+157
-52
lines changed

demo/src/screens/MainScreen.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,12 @@ export default class MainScreen extends Component {
301301
const keys = _.keys(data);
302302

303303
return (
304-
<Carousel migrate onChangePage={this.onChangePage} ref={carousel => (this.carousel = carousel)}>
304+
<Carousel
305+
migrate
306+
ref={carousel => (this.carousel = carousel)}
307+
containerStyle={{flex: 1}}
308+
onChangePage={this.onChangePage}
309+
>
305310
{_.map(data, (section, key) => {
306311
return (
307312
<View key={key} style={[styles.page, pageStyle]}>

demo/src/screens/componentScreenScreens/ModalScreen.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default class ModalScreen extends Component {
3737
color={Colors.dark10}
3838
size={15}
3939
/>
40-
<Carousel migrate onChangePage={currentPage => this.setState({currentPage})}>
40+
<Carousel migrate onChangePage={currentPage => this.setState({currentPage})} containerStyle={{flex: 1}}>
4141
<View bg-green50 flex style={styles.page}>
4242
<Modal.TopBar
4343
title='modal title'

demo/src/screens/componentScreens/ActionBarScreen.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default class ActionBarScreen extends Component {
3131
/>
3232
<Carousel
3333
migrate
34+
containerStyle={{flex: 1}}
3435
onChangePage={currentPage => this.setState({currentPage})}
3536
initialPage={this.state.currentPage}
3637
>

demo/src/screens/componentScreens/CarouselScreen.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React, {Component} from 'react';
22
import {StyleSheet} from 'react-native';
3-
import {View, Text, Carousel} from 'react-native-ui-lib'; // eslint-disable-line
3+
import {Constants, View, Text, Carousel} from 'react-native-ui-lib'; // eslint-disable-line
44

55

66
const INITIAL_PAGE = 0;
7+
const WIDTH = Constants.screenWidth - 120;
78

89
class CarouselScreen extends Component {
910
state = {
@@ -14,14 +15,31 @@ class CarouselScreen extends Component {
1415
this.setState({currentPage: index});
1516
}
1617

18+
onPagePress = (index) => {
19+
this.carousel.goToPage(index, true);
20+
}
21+
1722
render() {
1823
return (
1924
<View flex>
20-
<Carousel migrate loop onChangePage={(index => this.onChangePage(index))} /* initialPage={INITIAL_PAGE} */>
21-
<Page bg-cyan50>
25+
<Text text30 margin-20>Carousel</Text>
26+
<Carousel
27+
migrate
28+
ref={r => this.carousel = r}
29+
// loop
30+
onChangePage={(index => this.onChangePage(index))}
31+
pageWidth={WIDTH}
32+
// itemSpacings={20}
33+
// initialPage={INITIAL_PAGE}
34+
containerStyle={{height: 200/* , flex: 1 */}}
35+
pageControlPosition={'under'}
36+
pageControlProps={{onPagePress: this.onPagePress}}
37+
// showCounter
38+
>
39+
<Page bg-red50>
2240
<Text margin-15>PAGE 0</Text>
2341
</Page>
24-
<Page bg-red50>
42+
<Page bg-yellow20>
2543
<Text margin-15>PAGE 1</Text>
2644
</Page>
2745
<Page bg-purple50>
@@ -30,17 +48,18 @@ class CarouselScreen extends Component {
3048
<Page bg-green50>
3149
<Text margin-15>PAGE 3</Text>
3250
</Page>
33-
<Page bg-yellow20>
51+
<Page bg-cyan50>
3452
<Text margin-15>PAGE 4</Text>
3553
</Page>
3654
<Page bg-purple20>
3755
<Text margin-15>PAGE 5</Text>
3856
</Page>
39-
<Page bg-blue70>
57+
<Page bg-blue60>
4058
<Text margin-15>PAGE 6</Text>
4159
</Page>
4260
</Carousel>
43-
<View center style={{...StyleSheet.absoluteFillObject}} pointerEvents="none">
61+
62+
<View margin-20 center /*style={{...StyleSheet.absoluteFillObject}} */ pointerEvents="none">
4463
<Text text10>{this.state.currentPage}</Text>
4564
</View>
4665
</View>
@@ -58,7 +77,9 @@ const Page = ({children, ...others}) => {
5877

5978
const styles = StyleSheet.create({
6079
page: {
61-
flex: 1
80+
flex: 1,
81+
borderWidth: 1,
82+
borderRadius: 4
6283
}
6384
});
6485

src/components/carousel/CarouselPresenter.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,3 @@ export function isOutOfBounds(offset, props, pageWidth) {
3636

3737
return !_.inRange(offset, minLimit, maxLimit);
3838
}
39-
40-
// TODO: need to support more cases of page width in loop mode
41-
export function calcCarouselWidth(props) {
42-
const {pageWidth, loop} = props;
43-
44-
let length = getChildrenLength(props);
45-
length = loop ? length + 2 : length;
46-
return pageWidth * length;
47-
}

src/components/carousel/__tests__/CarouselPresenter.spec.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,4 @@ describe('Carousel presenter', () => {
4444
expect(uut.isOutOfBounds(481, {migrate: true, children: [{}, {}, {}]}, 120)).toBe(true);
4545
expect(uut.isOutOfBounds(1875, {migrate: true, children: [{}, {}, {}, {}]}, 375)).toBe(true);
4646
});
47-
48-
it('should calcCarouselWidth', () => {
49-
expect(uut.calcCarouselWidth({migrate: true, pageWidth: 70, children: [{}, {}, {}]})).toBe(210);
50-
expect(uut.calcCarouselWidth({migrate: true, pageWidth: 50, children: [{}, {}, {}]})).toBe(150);
51-
expect(uut.calcCarouselWidth({migrate: true, pageWidth: 150, loop: true, children: [{}, {}, {}]})).toBe(750);
52-
});
5347
});

src/components/carousel/index.js

Lines changed: 120 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ import PropTypes from 'prop-types';
33
import React from 'react';
44
import {ScrollView, StyleSheet} from 'react-native';
55
import {Constants} from '../../helpers';
6+
import {Colors} from '../../style';
67
import {BaseComponent} from '../../commons';
78
import View from '../view';
9+
import Text from '../text';
10+
import PageControl from '../pageControl';
811
import * as presenter from './CarouselPresenter';
912

1013

14+
const PAGE_CONTROL_POSITIONS = {
15+
OVER: 'over',
16+
UNDER: 'under'
17+
}
18+
1119
/**
1220
* @description: Carousel for scrolling pages horizontally
1321
* @gif: https://media.giphy.com/media/l0HU7f8gjpRlMRhKw/giphy.gif, https://media.giphy.com/media/3oFzmcjX9OhpyckhcQ/giphy.gif
@@ -25,9 +33,13 @@ export default class Carousel extends BaseComponent {
2533
*/
2634
initialPage: PropTypes.number,
2735
/**
28-
* the page width (all pages should have the same width)
36+
* the page width (all pages should have the same width). Does not work if passing 'loop' prop
2937
*/
3038
pageWidth: PropTypes.number,
39+
/**
40+
* the spacing between the items
41+
*/
42+
itemSpacings: PropTypes.number,
3143
/**
3244
* if true, will have infinite scroll
3345
*/
@@ -44,20 +56,39 @@ export default class Carousel extends BaseComponent {
4456
* the carousel style
4557
*/
4658
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
59+
/**
60+
* PageControl component props
61+
*/
62+
pageControlProps: PropTypes.shape(PageControl.propTypes),
63+
/**
64+
* The position of the PageControl component ['over', 'under'], otherwise it won't display
65+
*/
66+
pageControlPosition: PropTypes.oneOf(Object.values(PAGE_CONTROL_POSITIONS)),
67+
/**
68+
* whether to show a page counter (will not work with pageWidths)
69+
*/
70+
showCounter: PropTypes.bool,
71+
/**
72+
* the counter's text style
73+
*/
74+
counterTextStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array])
4775
};
4876

4977
static defaultProps = {
50-
initialPage: 0
78+
initialPage: 0,
79+
itemSpacings: 12
5180
};
5281

82+
static pageControlPositions = PAGE_CONTROL_POSITIONS;
83+
5384
constructor(props) {
5485
super(props);
5586

5687
this.carousel = React.createRef();
57-
const defaultPageWidth = props.pageWidth || Constants.screenWidth;
58-
88+
const defaultPageWidth = props.loop ? Constants.screenWidth : (props.pageWidth + props.itemSpacings || Constants.screenWidth);
89+
5990
this.state = {
60-
currentPage: props.initialPage,
91+
currentPage: this.shouldUsePageWidth() ? this.getCalcIndex(props.initialPage) : props.initialPage,
6192
currentStandingPage: props.initialPage,
6293
pageWidth: defaultPageWidth,
6394
initialOffset: {x: presenter.calcOffset(props, {currentPage: props.initialPage, pageWidth: defaultPageWidth})}
@@ -73,7 +104,7 @@ export default class Carousel extends BaseComponent {
73104
}
74105

75106
onOrientationChanged = () => {
76-
if (!this.props.pageWidth) {
107+
if (!this.props.pageWidth || this.props.loop) {
77108
this.setState({pageWidth: Constants.screenWidth});
78109
this.goToPage(this.state.currentPage, true);
79110
}
@@ -84,7 +115,8 @@ export default class Carousel extends BaseComponent {
84115
}
85116

86117
updateOffset = (animated = false) => {
87-
const x = presenter.calcOffset(this.props, this.state);
118+
const centerOffset = Constants.isIOS && this.shouldUsePageWidth() ? (Constants.screenWidth - this.state.pageWidth) / 2 : 0;
119+
const x = presenter.calcOffset(this.props, this.state) - centerOffset;
88120

89121
if (this.carousel) {
90122
this.carousel.current.scrollTo({x, animated});
@@ -111,6 +143,11 @@ export default class Carousel extends BaseComponent {
111143
return index;
112144
}
113145

146+
shouldUsePageWidth() {
147+
const {loop, pageWidth} = this.props;
148+
return !loop && pageWidth;
149+
}
150+
114151
onContentSizeChange = () => {
115152
// this is to handle initial scroll position (content offset)
116153
if (Constants.isAndroid) {
@@ -151,8 +188,10 @@ export default class Carousel extends BaseComponent {
151188
}
152189

153190
renderChild = (child, key) => {
191+
const paddingLeft = this.shouldUsePageWidth() ? this.props.itemSpacings : undefined;
192+
154193
return (
155-
<View style={{width: this.state.pageWidth}} key={key}>
194+
<View style={{width: this.state.pageWidth, paddingLeft}} key={key}>
156195
{child}
157196
</View>
158197
);
@@ -165,7 +204,7 @@ export default class Carousel extends BaseComponent {
165204
const childrenArray = React.Children.map(children, (child, index) => {
166205
return this.renderChild(child, `${index}`);
167206
});
168-
207+
169208
if (loop) {
170209
childrenArray.unshift(this.renderChild(children[length - 1], `${length - 1}-clone`));
171210
childrenArray.push(this.renderChild(children[0], `${0}-clone`));
@@ -174,32 +213,86 @@ export default class Carousel extends BaseComponent {
174213
return childrenArray;
175214
}
176215

216+
renderPageControl() {
217+
const {pageControlPosition, pageControlProps} = this.props;
218+
219+
if (pageControlPosition) {
220+
const pagesCount = presenter.getChildrenLength(this.props);
221+
const containerStyle = pageControlPosition === PAGE_CONTROL_POSITIONS.UNDER ?
222+
{marginVertical: 16} : {position: 'absolute', bottom: 16, alignSelf: 'center'};
223+
224+
return (
225+
<PageControl
226+
size={6}
227+
containerStyle={containerStyle}
228+
inactiveColor={Colors.dark60}
229+
color={Colors.dark20}
230+
{...pageControlProps}
231+
numOfPages={pagesCount}
232+
currentPage={this.getCalcIndex(this.state.currentPage)}
233+
/>
234+
);
235+
}
236+
}
237+
238+
renderCounter() {
239+
const {pageWidth, showCounter, counterTextStyle} = this.props;
240+
const {currentPage} = this.state;
241+
const pagesCount = presenter.getChildrenLength(this.props);
242+
243+
if (showCounter && !pageWidth) {
244+
return (
245+
<View center style={this.styles.counter}>
246+
<Text dark80 text90 style={[{fontWeight: 'bold'}, counterTextStyle]}>{currentPage + 1}/{pagesCount}</Text>
247+
</View>
248+
);
249+
}
250+
}
251+
177252
render() {
178-
const {containerStyle, ...others} = this.props;
179-
const {initialOffset} = this.state;
253+
const {containerStyle, itemSpacings, initialPage, ...others} = this.props;
254+
const {initialOffset, pageWidth} = this.state;
180255

256+
const scrollContainerStyle = this.shouldUsePageWidth() ? {paddingRight: itemSpacings} : undefined;
257+
const spacings = pageWidth === Constants.screenWidth ? 0 : itemSpacings;
258+
const initialBreak = pageWidth - (Constants.screenWidth - pageWidth - spacings) / 2;
259+
const snapToOffsets = _.times(presenter.getChildrenLength(this.props), (index) => initialBreak + index * pageWidth);
260+
181261
return (
182-
<ScrollView
183-
{...others}
184-
ref={this.carousel}
185-
style={[containerStyle, {flexGrow: 1}]}
186-
horizontal
187-
showsHorizontalScrollIndicator={false}
188-
pagingEnabled
189-
onScroll={this.onScroll}
190-
scrollEventThrottle={200}
191-
contentOffset={initialOffset}
192-
onContentSizeChange={this.onContentSizeChange}
193-
onMomentumScrollEnd={this.onMomentumScrollEnd}
194-
>
195-
{this.renderChildren()}
196-
</ScrollView>
262+
<View style={containerStyle}>
263+
<ScrollView
264+
{...others}
265+
ref={this.carousel}
266+
contentContainerStyle={scrollContainerStyle}
267+
horizontal
268+
showsHorizontalScrollIndicator={false}
269+
snapToOffsets={snapToOffsets}
270+
decelerationRate="fast"
271+
contentOffset={initialOffset} // iOS only
272+
scrollEventThrottle={200}
273+
onContentSizeChange={this.onContentSizeChange}
274+
onScroll={this.onScroll}
275+
onMomentumScrollEnd={this.onMomentumScrollEnd}
276+
>
277+
{this.renderChildren()}
278+
</ScrollView>
279+
{this.renderPageControl()}
280+
{this.renderCounter()}
281+
</View>
197282
);
198283
}
199284
}
200285

201286
function createStyles() {
202287
return StyleSheet.create({
203-
288+
counter: {
289+
paddingHorizontal: 8,
290+
paddingVertical: 3, // height: 24,
291+
borderRadius: 20,
292+
backgroundColor: Colors.rgba(Colors.black, 0.6),
293+
position: 'absolute',
294+
top: 12,
295+
right: 12
296+
}
204297
});
205298
}

0 commit comments

Comments
 (0)