Skip to content

Commit 3e1d39a

Browse files
authored
Custom Spinner Options (#110)
* Custom Spinner Options * update docs
1 parent 2cb4dad commit 3e1d39a

File tree

15 files changed

+217
-8
lines changed

15 files changed

+217
-8
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,10 @@ A Slider is a viewport that masks slides. The Slider component must wrap one or
211211
| classNameTray | [string|null] | null | No | Optional className string that is applied to the Slider's tray. The "tray" is the DOM element that contains the slides. The type of DOM element is specified by the trayTag property |
212212
| classNameTrayWrap | [string|null] | null | No | Optional className string that is applied to a div that surrounds the Slider's tray |
213213
| moveThreshold | number | 0.1 | No | Threshold to control the drag distance that triggers a scroll to the next or previous slide. (slide width or height * moveThreshold = drag pixel distance required to scroll) |
214+
| onMasterSpinner | [function|null] | null | No | Optional callback function that is called when the Master Spinner is visible. Requires that <CarouselProvider /> set hasMasterSpinner to true |
215+
| spinner | function | null | No | Optional inline JSX (aka "render props") to render your own custom spinner. Example `() => <MyCustomSpinnerComponent />` If left blank, the default spinner is used. |
214216
| style | object | {} | No | Optional css styles to add to the Slider. Note: internal css properties take precedence over any styles specified in the styles object |
215217
| trayTag | string | 'ul' | No | The HTML tag to used for the tray (the thing that holds all the slides and moves the slides back and forth.) |
216-
| onMasterSpinner | [function&#124;null] | null | No | Optional callback function that is called when the Master Spinner is visible. Requires that &lt;CarouselProvider /> set hasMasterSpinner to true |
217218

218219
#### The Slider component creates the following pseudo HTML by default.
219220

package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
},
3939
"homepage": "https://github.com/express-labs/pure-react-carousel#readme",
4040
"devDependencies": {
41+
"@types/react": "^16.7.6",
4142
"acorn": "^6.0.4",
4243
"babel-eslint": "^10.0.1",
4344
"babel-plugin-transform-class-properties": "^6.24.1",

src/App/App.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Example7,
1010
Example8,
1111
Example9,
12+
Example10,
1213
} from './examples';
1314
import s from './style.scss';
1415

@@ -109,6 +110,12 @@ class DevelopmentApp extends React.Component {
109110
</section>
110111
)}
111112

113+
{ (value === '0' || value === '10') && (
114+
<section id="example--10">
115+
<Example10 />
116+
</section>
117+
)}
118+
112119
</div>
113120
);
114121
}

src/App/examples/Example1/Example1.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default () => (
1515
naturalSlideHeight={500}
1616
hasMasterSpinner
1717
>
18-
<h1 className={s.headline}>Carousel (With Master Loading Spinner)</h1>
18+
<h2 className={s.headline}>Carousel (With Master Loading Spinner)</h2>
1919
<p>
2020
This spinner will go away after all the images have loaded. You might want to use
2121
Chrome dev tools to throttle the network connection so you can see the spinner.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import s from './CustomSpinner.scss';
3+
4+
export default class CustomSpinner extends React.Component {
5+
static meta = {
6+
VERSION: '0.0.0',
7+
}
8+
9+
render() {
10+
return (
11+
<svg className={s.spinner} viewBox="0 0 50 50">
12+
<circle className={s.path} cx="25" cy="25" r="20" fill="none" strokeWidth="5" />
13+
</svg>
14+
);
15+
}
16+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// thanks to Fabio Ottaviani
2+
// https://codepen.io/supah/pen/BjYLdW
3+
4+
.spinner {
5+
animation: rotate 2s linear infinite;
6+
z-index: 2;
7+
position: absolute;
8+
top: 50%;
9+
left: 50%;
10+
margin: -25px 0 0 -25px;
11+
width: 50px;
12+
height: 50px;
13+
}
14+
15+
.path {
16+
stroke: hsl(210, 70, 75);
17+
stroke-linecap: round;
18+
animation: dash 1.5s ease-in-out infinite;
19+
}
20+
21+
@keyframes rotate {
22+
100% {
23+
transform: rotate(360deg);
24+
}
25+
}
26+
27+
@keyframes dash {
28+
0% {
29+
stroke-dasharray: 1, 150;
30+
stroke-dashoffset: 0;
31+
}
32+
50% {
33+
stroke-dasharray: 90, 150;
34+
stroke-dashoffset: -35;
35+
}
36+
100% {
37+
stroke-dasharray: 90, 150;
38+
stroke-dashoffset: -124;
39+
}
40+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
import {
3+
ButtonBack, ButtonFirst, ButtonLast, ButtonNext,
4+
CarouselProvider, DotGroup, ImageWithZoom, Slide, Slider,
5+
} from '../../..';
6+
7+
import CustomSpinner from './CustomSpinner';
8+
9+
10+
import s from '../../style.scss';
11+
12+
13+
export default () => (
14+
<CarouselProvider
15+
visibleSlides={3}
16+
totalSlides={6}
17+
step={3}
18+
naturalSlideWidth={400}
19+
naturalSlideHeight={500}
20+
hasMasterSpinner
21+
>
22+
<h1 className={s.headline}>Carousel (Custom Spinner)</h1>
23+
<p>
24+
This uses a customized spinner.
25+
</p>
26+
<Slider className={s.slider} spinner={() => <CustomSpinner />}>
27+
<Slide index={0}>
28+
<ImageWithZoom src="./media/img01.jpeg" spinner={() => <CustomSpinner />} />
29+
</Slide>
30+
<Slide index={1}>
31+
<ImageWithZoom src="./media/img02.jpeg" />
32+
</Slide>
33+
<Slide index={2}>
34+
<ImageWithZoom src="./media/img03.jpeg" />
35+
</Slide>
36+
<Slide index={3}>
37+
<ImageWithZoom src="./media/img04.jpeg" />
38+
</Slide>
39+
<Slide index={4}>
40+
<ImageWithZoom src="./media/img05.jpeg" />
41+
</Slide>
42+
<Slide index={5}>
43+
<ImageWithZoom src="./media/img06.jpeg" />
44+
</Slide>
45+
</Slider>
46+
<ButtonFirst>First</ButtonFirst>
47+
<ButtonBack>Back</ButtonBack>
48+
<ButtonNext>Next</ButtonNext>
49+
<ButtonLast>Last</ButtonLast>
50+
<DotGroup />
51+
</CarouselProvider>
52+
);

src/App/examples/Example10/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Example10 from './Example10';
2+
3+
export default Example10;

src/App/examples/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export { default as Example6 } from './Example6';
77
export { default as Example7 } from './Example7';
88
export { default as Example8 } from './Example8';
99
export { default as Example9 } from './Example9';
10+
export { default as Example10 } from './Example10';

src/ImageWithZoom/ImageWithZoom.jsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
1313
static propTypes = {
1414
// alt: PropTypes.string,
1515
carouselStore: PropTypes.object.isRequired,
16+
spinner: PropTypes.func,
1617
src: PropTypes.string.isRequired,
1718
srcZoomed: PropTypes.string,
1819
tag: PropTypes.string,
@@ -21,6 +22,7 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
2122

2223
static defaultProps = {
2324
isPinchZoomEnabled: true,
25+
spinner: null,
2426
srcZoomed: null,
2527
tag: 'div',
2628
}
@@ -58,8 +60,8 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
5860
return Math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2));
5961
}
6062

61-
constructor() {
62-
super();
63+
constructor(props) {
64+
super(props);
6365

6466
// state changes that require a re-render
6567
this.state = {
@@ -244,11 +246,13 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
244246

245247
renderLoading() {
246248
if (this.state.isImageLoading) {
249+
const { spinner } = this.props;
247250
return (
248251
<div
249252
className={cn([s.imageLoadingSpinnerContainer, 'carousel__image-loading-spinner-container'])}
250253
>
251-
<Spinner />
254+
{ spinner && spinner() }
255+
{ !spinner && <Spinner />}
252256
</div>
253257
);
254258
}
@@ -261,9 +265,10 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
261265
const {
262266
carouselStore,
263267
isPinchZoomEnabled,
264-
tag: Tag,
268+
spinner,
265269
src,
266270
srcZoomed,
271+
tag: Tag,
267272
...filteredProps
268273
} = this.props;
269274

src/ImageWithZoom/__tests__/ImageWithZoom.test.jsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,29 @@ const touchEnd = {
5757

5858

5959
describe('<ImageWithZoom />', () => {
60+
describe('unit tests', () => {
61+
describe('renderLoading', () => {
62+
it('should render a custom spinner if supplied', () => {
63+
const instance = new ImageWithZoom({
64+
spinner: () => <div className="custom-spinner" />,
65+
});
66+
instance.state.isImageLoading = true;
67+
const wrapper = shallow(instance.renderLoading());
68+
expect(wrapper.find('.custom-spinner').exists()).toBe(true);
69+
});
70+
it('should render a the default spinner if no custom spinner was supplied', () => {
71+
const instance = new ImageWithZoom({});
72+
instance.state.isImageLoading = true;
73+
const wrapper = shallow(instance.renderLoading());
74+
expect(wrapper.find('Spinner').exists()).toBe(true);
75+
});
76+
it('should return null if imageIsLoading is false', () => {
77+
const instance = new ImageWithZoom({});
78+
instance.state.isImageLoading = false;
79+
expect(instance.renderLoading()).toBe(null);
80+
});
81+
});
82+
});
6083
describe('integration tests', () => {
6184
let wrapper;
6285
let imageWithZoom;

src/Slider/Slider.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const Slider = class Slider extends React.Component {
3333
playDirection: CarouselPropTypes.direction.isRequired,
3434
slideSize: PropTypes.number.isRequired,
3535
slideTraySize: PropTypes.number.isRequired,
36+
spinner: PropTypes.func,
3637
step: PropTypes.number.isRequired,
3738
style: PropTypes.object,
3839
tabIndex: PropTypes.number,
@@ -51,6 +52,7 @@ const Slider = class Slider extends React.Component {
5152
disableKeyboard: false,
5253
moveThreshold: 0.1,
5354
onMasterSpinner: null,
55+
spinner: null,
5456
style: {},
5557
tabIndex: null,
5658
trayTag: 'ul',
@@ -427,7 +429,7 @@ const Slider = class Slider extends React.Component {
427429
}
428430

429431
renderMasterSpinner() {
430-
const { hasMasterSpinner, masterSpinnerFinished } = this.props;
432+
const { hasMasterSpinner, masterSpinnerFinished, spinner } = this.props;
431433

432434
if (hasMasterSpinner && (!masterSpinnerFinished)) {
433435
if (typeof this.props.onMasterSpinner === 'function') this.props.onMasterSpinner();
@@ -439,7 +441,8 @@ const Slider = class Slider extends React.Component {
439441
'carousel__master-spinner-container',
440442
])}
441443
>
442-
<Spinner />
444+
{spinner && spinner()}
445+
{!spinner && <Spinner />}
443446
</div>
444447
);
445448
}
@@ -473,6 +476,7 @@ const Slider = class Slider extends React.Component {
473476
playDirection,
474477
slideSize,
475478
slideTraySize,
479+
spinner,
476480
style,
477481
tabIndex,
478482
totalSlides,

src/Slider/__tests__/Slider.test.jsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,39 @@ describe('<Slider />', () => {
318318
expect(ev.stopPropagation).toHaveBeenCalledTimes(1);
319319
});
320320
});
321+
describe('renderMasterSpinner', () => {
322+
it('should render a custom spinner if supplied', () => {
323+
const instance = new Slider({
324+
hasMasterSpinner: true,
325+
masterSpinnerFinished: false,
326+
spinner: () => <div className="custom-spinner" />,
327+
});
328+
const wrapper = shallow(instance.renderMasterSpinner());
329+
expect(wrapper.find('.custom-spinner').exists()).toBe(true);
330+
});
331+
it('should render a the default spinner if no custom spinner was supplied', () => {
332+
const instance = new Slider({
333+
hasMasterSpinner: true,
334+
masterSpinnerFinished: false,
335+
});
336+
const wrapper = shallow(instance.renderMasterSpinner());
337+
expect(wrapper.find('Spinner').exists()).toBe(true);
338+
});
339+
it('should return null if hasMasterSpinner is false', () => {
340+
const instance = new Slider({
341+
hasMasterSpinner: false,
342+
masterSpinnerFinished: false,
343+
});
344+
expect(instance.renderMasterSpinner()).toBe(null);
345+
});
346+
it('should return null if masterSpinnerFinished is true', () => {
347+
const instance = new Slider({
348+
hasMasterSpinner: true,
349+
masterSpinnerFinished: true,
350+
});
351+
expect(instance.renderMasterSpinner()).toBe(null);
352+
});
353+
});
321354
});
322355
describe('integration tests', () => {
323356
let props;

typings/carouselElements.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface SliderProps {
99
readonly moveThreshold?: number,
1010
readonly onMasterSpinner?: () => void
1111
readonly style?: {}
12+
readonly spinner?: () => void
1213
readonly trayTag?: string
1314
}
1415
type SliderInterface = React.ComponentClass<SliderProps>

0 commit comments

Comments
 (0)