Skip to content

Commit d6ec9b2

Browse files
fix(Carousel): Hide Pagination Bar if not required (#101)
[ci skip]
1 parent f7a7958 commit d6ec9b2

File tree

7 files changed

+827
-764
lines changed

7 files changed

+827
-764
lines changed

packages/main/__karma_snapshots__/Carousel.md

Lines changed: 547 additions & 496 deletions
Large diffs are not rendered by default.

packages/main/src/components/Carousel/Carousel.jss.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { fonts } from '@ui5/webcomponents-react-base';
21
import { JSSTheme } from '../../interfaces/JSSTheme';
32

4-
const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({
3+
const styles = ({ parameters }: JSSTheme) => ({
54
carousel: {
65
position: 'relative',
76
overflow: 'hidden',
87
boxSizing: 'border-box',
98
border: '1px solid transparent',
109
touchAction: 'pan-y',
1110
minWidth: '15.5rem',
12-
fontFamily: fonts.sapUiFontFamily,
11+
fontFamily: parameters.sapUiFontFamily,
12+
backgroundColor: parameters.sapUiBaseBG,
1313
'&:hover': {
1414
'& [data-value="paginationArrow"]': {
1515
opacity: 1
@@ -22,22 +22,17 @@ const styles = ({ theme, contentDensity, parameters }: JSSTheme) => ({
2222
whiteSpace: 'nowrap',
2323
textAlign: 'start',
2424
fontSize: '0',
25-
backgroundColor: parameters.sapUiBaseBG,
26-
'& > *': {
27-
transitionProperty: 'transform',
28-
transitionTimingFunction: 'cubic-bezier(0.46, 0, 0.44, 1)',
29-
transitionDuration: '0.5s',
30-
display: 'inline-block',
31-
verticalAlign: 'top',
32-
whiteSpace: 'normal',
33-
fontSize: '1rem',
34-
backgroundColor: parameters.sapUiBaseBG
35-
}
25+
transition: 'transform 0.5s cubic-bezier(0.46, 0, 0.44, 1)'
3626
},
3727
carouselItem: {
3828
width: '100%',
3929
height: '100%',
40-
overflow: 'hidden'
30+
overflow: 'hidden',
31+
display: 'inline-block',
32+
verticalAlign: 'top',
33+
whiteSpace: 'normal',
34+
fontSize: '1rem',
35+
visibility: 'hidden'
4136
},
4237
carouselItemContentIndicator: {
4338
padding: '0 4rem',

packages/main/src/components/Carousel/Carousel.karma.tsx

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,7 @@ describe('Carousel', () => {
7070
children: cloneElement(wrapper.props().children, { activePage: 1 })
7171
});
7272
wrapper.update();
73-
// @ts-ignore
74-
const instance = wrapper.find(Carousel.InnerComponent).instance();
75-
// @ts-ignore
76-
expect(instance.state.activePage).to.equal(1);
73+
expect(wrapper.debug()).to.matchSnapshot();
7774
});
7875

7976
it('Update activePage via prop', () => {
@@ -92,77 +89,57 @@ describe('Carousel', () => {
9289
.find(Icon)
9390
.last()
9491
.simulate('click');
95-
wrapper.update();
96-
// @ts-ignore
97-
const instance = wrapper.find(Carousel.InnerComponent).instance();
98-
// @ts-ignore
99-
expect(instance.state.activePage).to.equal(1);
10092
expect(getEventFromCallback(callback).getParameter('selectedIndex')).to.equal(1);
10193
});
10294

10395
it('Navigation to previous page', () => {
104-
const wrapper = mountThemedComponent(renderCarousel({ activePage: 1 }));
96+
const callback = sinon.spy();
97+
const wrapper = mountThemedComponent(renderCarousel({ activePage: 1, onPageChanged: callback }));
10598
wrapper
10699
.find(Icon)
107100
.first()
108101
.simulate('click');
109-
wrapper.update();
110-
// @ts-ignore
111-
const instance = wrapper.find(Carousel.InnerComponent).instance();
112-
// @ts-ignore
113-
expect(instance.state.activePage).to.equal(0);
102+
expect(getEventFromCallback(callback).getParameter('selectedIndex')).to.equal(0);
114103
});
115104

116105
it('Navigation to previous page - w/o Loop', () => {
117-
const wrapper = mountThemedComponent(renderCarousel({ activePage: 0 }));
106+
const callback = sinon.spy();
107+
const wrapper = mountThemedComponent(renderCarousel({ activePage: 0, onPageChanged: callback }));
118108
wrapper
119109
.find(Icon)
120110
.first()
121111
.simulate('click');
122-
wrapper.update();
123-
// @ts-ignore
124-
const instance = wrapper.find(Carousel.InnerComponent).instance();
125-
// @ts-ignore
126-
expect(instance.state.activePage).to.equal(0);
112+
expect(callback.called).to.equal(false);
127113
});
128114

129115
it('Navigation to previous page - w/ Loop', () => {
130-
const wrapper = mountThemedComponent(renderCarousel({ activePage: 0, loop: true }));
116+
const callback = sinon.spy();
117+
const wrapper = mountThemedComponent(renderCarousel({ activePage: 0, loop: true, onPageChanged: callback }));
131118
wrapper
132119
.find(Icon)
133120
.first()
134121
.simulate('click');
135-
wrapper.update();
136-
// @ts-ignore
137-
const instance = wrapper.find(Carousel.InnerComponent).instance();
138-
// @ts-ignore
139-
expect(instance.state.activePage).to.equal(6);
122+
expect(getEventFromCallback(callback).getParameter('selectedIndex')).to.equal(6);
140123
});
141124

142125
it('Navigation to next page - w/o Loop', () => {
143-
const wrapper = mountThemedComponent(renderCarousel({ activePage: 6 }));
126+
const callback = sinon.spy();
127+
const wrapper = mountThemedComponent(renderCarousel({ activePage: 6, onPageChanged: callback }));
144128
wrapper
145129
.find(Icon)
146130
.last()
147131
.simulate('click');
148-
wrapper.update();
149-
// @ts-ignore
150-
const instance = wrapper.find(Carousel.InnerComponent).instance();
151-
// @ts-ignore
152-
expect(instance.state.activePage).to.equal(6);
132+
expect(callback.called).to.equal(false);
153133
});
154134

155135
it('Navigation to next page - w/ Loop', () => {
156-
const wrapper = mountThemedComponent(renderCarousel({ activePage: 6, loop: true }));
136+
const callback = sinon.spy();
137+
const wrapper = mountThemedComponent(renderCarousel({ activePage: 6, loop: true, onPageChanged: callback }));
157138
wrapper
158139
.find(Icon)
159140
.last()
160141
.simulate('click');
161-
wrapper.update();
162-
// @ts-ignore
163-
const instance = wrapper.find(Carousel.InnerComponent).instance();
164-
// @ts-ignore
165-
expect(instance.state.activePage).to.equal(0);
142+
expect(getEventFromCallback(callback).getParameter('selectedIndex')).to.equal(0);
166143
});
167144

168145
it('Carousel with 1 child', () => {

packages/main/src/components/Carousel/CarouselPagination.jss.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@ const styles = ({ parameters }: JSSTheme) => ({
1212
backgroundColor: parameters.sapUiPageFooterBackground
1313
},
1414
paginationTop: {
15-
borderBottom: `1px solid ${parameters.sapUiPageFooterBorderColor}`,
16-
'&$paginationArrowContent': {
17-
'& $paginationArrow': {
18-
top: 'calc(50% + 2rem) !important'
19-
}
20-
}
15+
borderBottom: `1px solid ${parameters.sapUiPageFooterBorderColor}`
2116
},
2217
paginationBottom: {
2318
borderTop: `1px solid ${parameters.sapUiPageFooterBorderColor}`
@@ -76,14 +71,33 @@ const styles = ({ parameters }: JSSTheme) => ({
7671
boxShadow: parameters.sapUiShadowLevel1,
7772
'&:first-child': {
7873
position: 'absolute',
79-
top: 'calc(50% - 2rem)',
74+
top: 'calc(50% - 2.75rem)',
75+
left: '0.5rem',
76+
opacity: 0,
77+
zIndex: ZIndex.InputModal
78+
},
79+
'&:last-child': {
80+
position: 'absolute',
81+
top: 'calc(50% - 2.75rem)',
82+
right: '0.5rem',
83+
opacity: 0,
84+
zIndex: ZIndex.InputModal
85+
}
86+
}
87+
},
88+
paginationArrowContentNoBar: {
89+
'& $paginationArrow': {
90+
boxShadow: parameters.sapUiShadowLevel1,
91+
'&:first-child': {
92+
position: 'absolute',
93+
top: 'calc(50% - 1rem)',
8094
left: '0.5rem',
8195
opacity: 0,
8296
zIndex: ZIndex.InputModal
8397
},
8498
'&:last-child': {
8599
position: 'absolute',
86-
top: 'calc(50% - 2rem)',
100+
top: 'calc(50% - 1rem)',
87101
right: '0.5rem',
88102
opacity: 0,
89103
zIndex: ZIndex.InputModal
Lines changed: 84 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
import { Event, StyleClassHelper, withStyles } from '@ui5/webcomponents-react-base';
2-
import React, { Children, Component } from 'react';
3-
import { ClassProps } from '../../interfaces/ClassProps';
1+
import { StyleClassHelper } from '@ui5/webcomponents-react-base';
2+
import React, { Children, FC, useMemo } from 'react';
3+
import { createUseStyles } from 'react-jss';
4+
import { JSSTheme } from '../../interfaces/JSSTheme';
45
import { CarouselArrowsPlacement } from '../../lib/CarouselArrowsPlacement';
56
import { Icon } from '../../lib/Icon';
67
import { Label } from '../../lib/Label';
78
import { PlacementType } from '../../lib/PlacementType';
89
import styles from './CarouselPagination.jss';
910

11+
const useStyles = createUseStyles<JSSTheme, keyof ReturnType<typeof styles>>(styles);
12+
1013
export interface CarouselPaginationPropTypes {
1114
/**
1215
* Defines where the carousel's arrows are placed.
@@ -26,78 +29,96 @@ export interface CarouselPaginationPropTypes {
2629
* The default value is PlacementType.Bottom.
2730
*/
2831
pageIndicatorPlacement?: PlacementType.Top | PlacementType.Bottom;
29-
}
3032

31-
interface CarouselPaginationInternalProps extends CarouselPaginationPropTypes, ClassProps {
32-
goToPreviousPage: (e: Event) => void;
33-
goToNextPage: (e: Event) => void;
33+
/**
34+
* Index of the active page to be displayed
35+
*/
3436
activePage?: number;
35-
}
3637

37-
@withStyles(styles)
38-
export class CarouselPagination extends Component<CarouselPaginationInternalProps> {
39-
private static TEXT_INDICATOR_THRESHOLD = 8;
38+
goToPreviousPage?: (e: any) => void;
39+
goToNextPage?: (e: any) => void;
40+
}
4041

41-
private handleGoToNextPage = (e) => {
42-
this.props.goToNextPage(Event.of(this, e));
43-
};
42+
const TEXT_INDICATOR_THRESHOLD = 8;
43+
const CarouselPagination: FC<CarouselPaginationPropTypes> = (props) => {
44+
const classes = useStyles();
4445

45-
private handleGoToPreviousPage = (e) => {
46-
this.props.goToPreviousPage(Event.of(this, e));
47-
};
46+
const {
47+
arrowsPlacement,
48+
children,
49+
showPageIndicator,
50+
pageIndicatorPlacement,
51+
activePage,
52+
goToPreviousPage,
53+
goToNextPage
54+
} = props;
4855

49-
render() {
50-
const { arrowsPlacement, children, showPageIndicator, pageIndicatorPlacement, classes, activePage } = this.props;
56+
const numberOfChildren = React.Children.count(children);
57+
const showTextIndicator = numberOfChildren >= TEXT_INDICATOR_THRESHOLD;
5158

52-
const numberOfChildren = React.Children.count(children);
53-
const showTextIndicator = numberOfChildren >= CarouselPagination.TEXT_INDICATOR_THRESHOLD;
59+
const paginationClasses = StyleClassHelper.of(classes.pagination);
60+
if (arrowsPlacement === CarouselArrowsPlacement.Content) {
61+
paginationClasses.put(classes.paginationArrowContent);
62+
}
63+
if (pageIndicatorPlacement === PlacementType.Top) {
64+
paginationClasses.put(classes.paginationTop);
65+
}
66+
if (pageIndicatorPlacement === PlacementType.Bottom) {
67+
paginationClasses.put(classes.paginationBottom);
68+
}
5469

55-
const paginationClasses = StyleClassHelper.of(classes.pagination);
56-
if (arrowsPlacement === CarouselArrowsPlacement.Content) {
57-
paginationClasses.put(classes.paginationArrowContent);
58-
}
59-
if (pageIndicatorPlacement === PlacementType.Top) {
60-
paginationClasses.put(classes.paginationTop);
61-
}
62-
if (pageIndicatorPlacement === PlacementType.Bottom) {
63-
paginationClasses.put(classes.paginationBottom);
64-
}
70+
const shouldRenderPaginationBar = useMemo(() => {
71+
return showPageIndicator || arrowsPlacement === CarouselArrowsPlacement.PageIndicator;
72+
}, [showPageIndicator, arrowsPlacement]);
6573

74+
if (!shouldRenderPaginationBar) {
6675
return (
67-
<div className={paginationClasses.valueOf()}>
68-
<div
69-
data-value={arrowsPlacement === CarouselArrowsPlacement.Content ? 'paginationArrow' : null}
70-
className={classes.paginationArrow}
71-
onClick={this.handleGoToPreviousPage}
72-
>
73-
<Icon src="slim-arrow-left" />
76+
<div className={classes.paginationArrowContentNoBar}>
77+
<div data-value="paginationArrow" className={classes.paginationArrow} onClick={goToPreviousPage}>
78+
<Icon src="sap-icon://slim-arrow-left" />
7479
</div>
75-
76-
{showPageIndicator && (
77-
<div className={classes.paginationIndicator}>
78-
{showTextIndicator && <Label>{`Showing ${activePage + 1} of ${numberOfChildren}`}</Label>}
79-
80-
{!showTextIndicator &&
81-
Children.map(children, (item, index) => (
82-
<span
83-
key={index}
84-
className={`${activePage === index ? classes.paginationIconActive : null} ${classes.paginationIcon}`}
85-
aria-label={`Item ${index + 1} of ${numberOfChildren} displayed`}
86-
>
87-
{index + 1}
88-
</span>
89-
))}
90-
</div>
91-
)}
92-
93-
<div
94-
data-value={arrowsPlacement === CarouselArrowsPlacement.Content ? 'paginationArrow' : null}
95-
className={classes.paginationArrow}
96-
onClick={this.handleGoToNextPage}
97-
>
98-
<Icon src="slim-arrow-right" />
80+
<div data-value="paginationArrow" className={classes.paginationArrow} onClick={goToNextPage}>
81+
<Icon src="sap-icon://slim-arrow-right" />
9982
</div>
10083
</div>
10184
);
10285
}
103-
}
86+
87+
return (
88+
<div className={paginationClasses.valueOf()}>
89+
<div
90+
data-value={arrowsPlacement === CarouselArrowsPlacement.Content ? 'paginationArrow' : null}
91+
className={classes.paginationArrow}
92+
onClick={goToPreviousPage}
93+
>
94+
<Icon src="sap-icon://slim-arrow-left" />
95+
</div>
96+
97+
<div className={classes.paginationIndicator}>
98+
{showPageIndicator && showTextIndicator && <Label>{`Showing ${activePage + 1} of ${numberOfChildren}`}</Label>}
99+
100+
{showPageIndicator &&
101+
!showTextIndicator &&
102+
Children.map(children, (item, index) => (
103+
<span
104+
key={index}
105+
className={`${activePage === index ? classes.paginationIconActive : null} ${classes.paginationIcon}`}
106+
aria-label={`Item ${index + 1} of ${numberOfChildren} displayed`}
107+
>
108+
{index + 1}
109+
</span>
110+
))}
111+
</div>
112+
113+
<div
114+
data-value={arrowsPlacement === CarouselArrowsPlacement.Content ? 'paginationArrow' : null}
115+
className={classes.paginationArrow}
116+
onClick={goToNextPage}
117+
>
118+
<Icon src="sap-icon://slim-arrow-right" />
119+
</div>
120+
</div>
121+
);
122+
};
123+
124+
export { CarouselPagination };

packages/main/src/components/Carousel/demo.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { boolean, number, select } from '@storybook/addon-knobs';
2+
import { action } from '@storybook/addon-actions';
23
import { storiesOf } from '@storybook/react';
34
import React from 'react';
45
import { Carousel } from '../../lib/Carousel';
@@ -10,6 +11,7 @@ function renderCarousel() {
1011
return (
1112
<Carousel
1213
activePage={number('active', 0)}
14+
onPageChanged={action('onPageChanged')}
1315
arrowsPlacement={select(
1416
'arrowsPlacement',
1517
Object.values(CarouselArrowsPlacement),

0 commit comments

Comments
 (0)