Skip to content

Commit fe7b396

Browse files
Add vertical scroll to Carousel component (#1175)
* Add vertical scroll to Carousel component * cr fixes Co-authored-by: Ethan Sharabi <[email protected]>
1 parent 433ebef commit fe7b396

File tree

11 files changed

+206
-55
lines changed

11 files changed

+206
-55
lines changed

demo/src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ module.exports = {
3434
get CarouselScreen() {
3535
return require('./screens/componentScreens/CarouselScreen').default;
3636
},
37+
get CarouselVerticalScreen() {
38+
return require('./screens/componentScreens/CarouselVerticalScreen').default;
39+
},
3740
get CheckboxScreen() {
3841
return require('./screens/componentScreens/CheckboxScreen').default;
3942
},

demo/src/screens/MenuStructure.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const navigationData = {
8686
title: 'Layouts & Templates',
8787
screens: [
8888
{title: 'Carousel', tags: 'carousel', screen: 'unicorn.components.CarouselScreen'},
89+
{title: 'Carousel (Vertical)', tags: 'carousel', screen: 'unicorn.components.CarouselVerticalScreen'},
8990
{title: 'LoadingScreen', tags: 'loading screen', screen: 'unicorn.screens.LoadingScreen'},
9091
{title: 'Modal', tags: 'modal topbar screen', screen: 'unicorn.screens.ModalScreen'},
9192
{title: 'StateScreen', tags: 'empty state screen', screen: 'unicorn.screens.EmptyStateScreen'},
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {Carousel, Constants, Text, View, Colors} from 'react-native-ui-lib';
2+
import React, {Component} from 'react';
3+
import _ from 'lodash';
4+
import {StyleSheet} from 'react-native';
5+
import {
6+
renderBooleanOption,
7+
renderSliderOption
8+
} from '../ExampleScreenPresenter';
9+
10+
interface Props {}
11+
12+
interface State {
13+
numberOfPagesShown: number;
14+
autoplay: boolean;
15+
}
16+
17+
const BACKGROUND_COLORS = [
18+
Colors.red50,
19+
Colors.yellow20,
20+
Colors.purple50,
21+
Colors.green50,
22+
Colors.cyan50,
23+
Colors.purple20,
24+
Colors.blue60,
25+
Colors.red10,
26+
Colors.green20,
27+
Colors.purple60
28+
];
29+
30+
const pageHeight = Constants.windowHeight / 2;
31+
32+
class CarouselVerticalScreen extends Component<Props, State> {
33+
carousel = React.createRef<typeof Carousel>();
34+
35+
constructor(props: Props) {
36+
super(props);
37+
38+
this.state = {
39+
numberOfPagesShown: 5,
40+
autoplay: false
41+
};
42+
}
43+
44+
render() {
45+
const {numberOfPagesShown, autoplay} = this.state
46+
return (
47+
<View flex paddingT-20>
48+
<View marginH-20 marginB-20>
49+
{renderBooleanOption.call(this, 'autoplay', 'autoplay')}
50+
{renderSliderOption.call(
51+
this,
52+
'Number of pages shown',
53+
'numberOfPagesShown',
54+
{
55+
min: 3,
56+
max: 10,
57+
step: 1,
58+
initial: 5
59+
}
60+
)}
61+
</View>
62+
<Carousel
63+
key={'carousel'}
64+
ref={this.carousel}
65+
autoplay={autoplay}
66+
pageWidth={Constants.windowWidth}
67+
pageHeight={pageHeight}
68+
initialPage={0}
69+
containerStyle={{height: pageHeight}}
70+
allowAccessibleLayout
71+
horizontal={false}
72+
>
73+
{_.map([...Array(numberOfPagesShown)], (_, index) => (
74+
<Page
75+
style={{backgroundColor: BACKGROUND_COLORS[index]}}
76+
key={index}
77+
>
78+
<Text style={styles.pageText}>{index}</Text>
79+
</Page>
80+
))}
81+
</Carousel>
82+
</View>
83+
);
84+
}
85+
}
86+
87+
// @ts-ignore
88+
const Page = ({children, style, ...others}) => {
89+
return (
90+
<View center {...others} style={[styles.page, style]}>
91+
{children}
92+
</View>
93+
);
94+
};
95+
96+
const styles = StyleSheet.create({
97+
container: {},
98+
page: {
99+
flex: 1,
100+
height: pageHeight,
101+
width: Constants.windowWidth
102+
},
103+
pageText: {
104+
fontSize: 40,
105+
color: 'white'
106+
}
107+
});
108+
109+
export default CarouselVerticalScreen

demo/src/screens/componentScreens/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function registerScreens(registrar) {
1010
registrar('unicorn.components.CardsScreen', () => require('./CardsScreen').default);
1111
registrar('unicorn.animations.CardScannerScreen', () => require('../componentScreens/CardScannerScreen').default);
1212
registrar('unicorn.components.CarouselScreen', () => require('./CarouselScreen').default);
13+
registrar('unicorn.components.CarouselVerticalScreen', () => require('./CarouselVerticalScreen').default);
1314
registrar('unicorn.components.CheckboxScreen', () => require('./CheckboxScreen').default);
1415
registrar('unicorn.components.ChipScreen', () => require('./ChipScreen').default);
1516
registrar('unicorn.components.ConnectionStatusBar', () => require('./ConnectionStatusBarScreen').default);
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { CarouselProps, CarouselState } from './types';
22
export declare function getChildrenLength(props: CarouselProps): number;
3-
export declare function calcOffset(props: CarouselProps, state: Omit<CarouselState, 'initialOffset' | 'prevProps'>): number;
4-
export declare function calcPageIndex(offset: number, props: CarouselProps, pageWidth: number): number;
3+
export declare function calcOffset(props: CarouselProps, state: Omit<CarouselState, 'initialOffset' | 'prevProps'>): {
4+
x: number;
5+
y: number;
6+
};
7+
export declare function calcPageIndex(offset: number, props: CarouselProps, pageSize: number): number;
58
export declare function isOutOfBounds(offset: number, props: CarouselProps, pageWidth: number): boolean;

generatedTypes/components/carousel/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ declare class Carousel extends Component<CarouselProps, CarouselState> {
2424
pageWidth: number;
2525
initialOffset: {
2626
x: number;
27+
y: number;
2728
};
2829
prevProps: CarouselProps;
2930
} | {
@@ -47,7 +48,7 @@ declare class Carousel extends Component<CarouselProps, CarouselState> {
4748
getSnapToOffsets: () => number[] | undefined;
4849
shouldUsePageWidth(): number | false | undefined;
4950
shouldEnablePagination(): boolean | undefined;
50-
onContainerLayout: ({ nativeEvent: { layout: { width: containerWidth } } }: LayoutChangeEvent) => void;
51+
onContainerLayout: ({ nativeEvent: { layout: { width: containerWidth, height: containerHeight } } }: LayoutChangeEvent) => void;
5152
shouldAllowAccessibilityLayout(): boolean | undefined;
5253
onContentSizeChange: () => void;
5354
onMomentumScrollEnd: () => void;

generatedTypes/components/carousel/types.d.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export interface CarouselProps {
1313
* the page width (all pages should have the same width). Does not work if passing 'loop' prop
1414
*/
1515
pageWidth?: number;
16+
/**
17+
* the page height (all pages should have the same height).
18+
*/
19+
pageHeight?: number;
1620
/**
1721
* the spacing between the items
1822
*/
@@ -27,7 +31,7 @@ export interface CarouselProps {
2731
*/
2832
containerPaddingVertical?: number;
2933
/**
30-
* if true, will have infinite scroll
34+
* if true, will have infinite scroll (do not turn on for vertical scrolling)
3135
*/
3236
loop?: boolean;
3337
/**
@@ -78,12 +82,18 @@ export interface CarouselProps {
7882
* the amount of ms to wait before switching to the next page, in case autoplay is on
7983
*/
8084
autoplayInterval?: number;
85+
/**
86+
* When true the scroll view's children are arranged horizontally in a row
87+
* instead of vertically in a column. The default value is true.
88+
*/
89+
horizontal?: boolean | null;
8190
}
8291
export interface CarouselState {
8392
containerWidth?: number;
8493
currentPage: number;
8594
currentStandingPage?: number;
8695
pageWidth: number;
96+
pageHeight: number;
8797
initialOffset: PointPropType;
8898
prevProps: CarouselProps;
8999
}

src/components/carousel/CarouselPresenter.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,24 @@ export function getChildrenLength(props: CarouselProps): number {
77
}
88

99
export function calcOffset(props: CarouselProps, state: Omit<CarouselState, 'initialOffset' | 'prevProps'>) {
10-
const {currentPage, pageWidth} = state;
10+
const {currentPage, pageWidth, pageHeight} = state;
1111
const {loop, containerMarginHorizontal = 0} = props;
1212
const actualCurrentPage = loop ? currentPage + 1 : currentPage;
1313
const nonLoopAdjustment = !loop && currentPage > 0 ? containerMarginHorizontal : 0;
14-
const offset = pageWidth * actualCurrentPage - nonLoopAdjustment;
14+
const pageSize = props.horizontal ? pageWidth : pageHeight;
15+
const offset = pageSize * actualCurrentPage - nonLoopAdjustment;
16+
const offsetXY = {
17+
x: props.horizontal ? offset : 0,
18+
y: props.horizontal ? 0 : offset
19+
};
1520

16-
return offset;
21+
return offsetXY;
1722
}
1823

19-
export function calcPageIndex(offset: number, props: CarouselProps, pageWidth: number) {
24+
export function calcPageIndex(offset: number, props: CarouselProps, pageSize: number) {
2025
const pagesCount = getChildrenLength(props);
2126
const {loop} = props;
22-
const pageIndexIncludingClonedPages = Math.round(offset / pageWidth);
27+
const pageIndexIncludingClonedPages = Math.round(offset / pageSize);
2328

2429
let actualPageIndex;
2530
if (loop) {

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ describe('Carousel presenter', () => {
99

1010
describe('calcOffset', () => {
1111
it('should calcOffset (default mode)', () => {
12-
expect(uut.calcOffset({children: [{}, {}, {}]}, {pageWidth: 120, currentPage: 0})).toBe(0);
13-
expect(uut.calcOffset({children: [{}, {}, {}]}, {pageWidth: 120, currentPage: 1})).toBe(120);
14-
expect(uut.calcOffset({children: [{}, {}, {}]}, {pageWidth: 120, currentPage: 2})).toBe(240);
12+
expect(uut.calcOffset({children: [{}, {}, {}], horizontal: true}, {pageWidth: 120, pageHeight: 100, currentPage: 0})).toStrictEqual({x: 0, y: 0});
13+
expect(uut.calcOffset({children: [{}, {}, {}], horizontal: true}, {pageWidth: 120, pageHeight: 100, currentPage: 1})).toStrictEqual({x: 120, y: 0});
14+
expect(uut.calcOffset({children: [{}, {}, {}], horizontal: true}, {pageWidth: 120, pageHeight: 100, currentPage: 2})).toStrictEqual({x: 240, y: 0});
15+
expect(uut.calcOffset({children: [{}, {}, {}], horizontal: false}, {pageWidth: 80, pageHeight: 150, currentPage: 0})).toStrictEqual({x: 0, y: 0});
16+
expect(uut.calcOffset({children: [{}, {}, {}], horizontal: false}, {pageWidth: 80, pageHeight: 150, currentPage: 1})).toStrictEqual({x: 0, y: 150});
17+
expect(uut.calcOffset({children: [{}, {}, {}], horizontal: false}, {pageWidth: 80, pageHeight: 150, currentPage: 2})).toStrictEqual({x: 0, y: 300});
1518
});
1619

1720
it('should calcOffset (loop mode)', () => {
18-
expect(uut.calcOffset({loop: true, children: [{}, {}, {}]}, {pageWidth: 120, currentPage: 0})).toBe(120);
19-
expect(uut.calcOffset({loop: true, children: [{}, {}, {}]}, {pageWidth: 120, currentPage: 1})).toBe(240);
20-
expect(uut.calcOffset({loop: true, children: [{}, {}, {}]}, {pageWidth: 120, currentPage: 2})).toBe(360);
21+
expect(uut.calcOffset({loop: true, children: [{}, {}, {}], horizontal: true}, {pageWidth: 120, pageHeight: 100, currentPage: 0})).toStrictEqual({x: 120, y: 0});
22+
expect(uut.calcOffset({loop: true, children: [{}, {}, {}], horizontal: true}, {pageWidth: 120, pageHeight: 100, currentPage: 1})).toStrictEqual({x: 240, y: 0});
23+
expect(uut.calcOffset({loop: true, children: [{}, {}, {}], horizontal: true}, {pageWidth: 120, pageHeight: 100, currentPage: 2})).toStrictEqual({x: 360, y: 0});
2124
});
2225
});
2326

0 commit comments

Comments
 (0)