Skip to content

Custom Spinner Options #110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,10 @@ A Slider is a viewport that masks slides. The Slider component must wrap one or
| 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 |
| classNameTrayWrap | [string|null] | null | No | Optional className string that is applied to a div that surrounds the Slider's tray |
| 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) |
| onMasterSpinner | [function|null] | null | No | Optional callback function that is called when the Master Spinner is visible. Requires that <CarouselProvider /> set hasMasterSpinner to true |
| 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. |
| 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 |
| 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.) |
| 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 |

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

Expand Down
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"homepage": "https://github.com/express-labs/pure-react-carousel#readme",
"devDependencies": {
"@types/react": "^16.7.6",
"acorn": "^6.0.4",
"babel-eslint": "^10.0.1",
"babel-plugin-transform-class-properties": "^6.24.1",
Expand Down
7 changes: 7 additions & 0 deletions src/App/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Example7,
Example8,
Example9,
Example10,
} from './examples';
import s from './style.scss';

Expand Down Expand Up @@ -109,6 +110,12 @@ class DevelopmentApp extends React.Component {
</section>
)}

{ (value === '0' || value === '10') && (
<section id="example--10">
<Example10 />
</section>
)}

</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/App/examples/Example1/Example1.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default () => (
naturalSlideHeight={500}
hasMasterSpinner
>
<h1 className={s.headline}>Carousel (With Master Loading Spinner)</h1>
<h2 className={s.headline}>Carousel (With Master Loading Spinner)</h2>
<p>
This spinner will go away after all the images have loaded. You might want to use
Chrome dev tools to throttle the network connection so you can see the spinner.
Expand Down
16 changes: 16 additions & 0 deletions src/App/examples/Example10/CustomSpinner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import s from './CustomSpinner.scss';

export default class CustomSpinner extends React.Component {
static meta = {
VERSION: '0.0.0',
}

render() {
return (
<svg className={s.spinner} viewBox="0 0 50 50">
<circle className={s.path} cx="25" cy="25" r="20" fill="none" strokeWidth="5" />
</svg>
);
}
}
40 changes: 40 additions & 0 deletions src/App/examples/Example10/CustomSpinner.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// thanks to Fabio Ottaviani
// https://codepen.io/supah/pen/BjYLdW

.spinner {
animation: rotate 2s linear infinite;
z-index: 2;
position: absolute;
top: 50%;
left: 50%;
margin: -25px 0 0 -25px;
width: 50px;
height: 50px;
}

.path {
stroke: hsl(210, 70, 75);
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}

@keyframes rotate {
100% {
transform: rotate(360deg);
}
}

@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
52 changes: 52 additions & 0 deletions src/App/examples/Example10/Example10.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import {
ButtonBack, ButtonFirst, ButtonLast, ButtonNext,
CarouselProvider, DotGroup, ImageWithZoom, Slide, Slider,
} from '../../..';

import CustomSpinner from './CustomSpinner';


import s from '../../style.scss';


export default () => (
<CarouselProvider
visibleSlides={3}
totalSlides={6}
step={3}
naturalSlideWidth={400}
naturalSlideHeight={500}
hasMasterSpinner
>
<h1 className={s.headline}>Carousel (Custom Spinner)</h1>
<p>
This uses a customized spinner.
</p>
<Slider className={s.slider} spinner={() => <CustomSpinner />}>
<Slide index={0}>
<ImageWithZoom src="./media/img01.jpeg" spinner={() => <CustomSpinner />} />
</Slide>
<Slide index={1}>
<ImageWithZoom src="./media/img02.jpeg" />
</Slide>
<Slide index={2}>
<ImageWithZoom src="./media/img03.jpeg" />
</Slide>
<Slide index={3}>
<ImageWithZoom src="./media/img04.jpeg" />
</Slide>
<Slide index={4}>
<ImageWithZoom src="./media/img05.jpeg" />
</Slide>
<Slide index={5}>
<ImageWithZoom src="./media/img06.jpeg" />
</Slide>
</Slider>
<ButtonFirst>First</ButtonFirst>
<ButtonBack>Back</ButtonBack>
<ButtonNext>Next</ButtonNext>
<ButtonLast>Last</ButtonLast>
<DotGroup />
</CarouselProvider>
);
3 changes: 3 additions & 0 deletions src/App/examples/Example10/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Example10 from './Example10';

export default Example10;
1 change: 1 addition & 0 deletions src/App/examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as Example6 } from './Example6';
export { default as Example7 } from './Example7';
export { default as Example8 } from './Example8';
export { default as Example9 } from './Example9';
export { default as Example10 } from './Example10';
13 changes: 9 additions & 4 deletions src/ImageWithZoom/ImageWithZoom.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
static propTypes = {
// alt: PropTypes.string,
carouselStore: PropTypes.object.isRequired,
spinner: PropTypes.func,
src: PropTypes.string.isRequired,
srcZoomed: PropTypes.string,
tag: PropTypes.string,
Expand All @@ -21,6 +22,7 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {

static defaultProps = {
isPinchZoomEnabled: true,
spinner: null,
srcZoomed: null,
tag: 'div',
}
Expand Down Expand Up @@ -58,8 +60,8 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
return Math.sqrt(((x2 - x1) ** 2) + ((y2 - y1) ** 2));
}

constructor() {
super();
constructor(props) {
super(props);

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

renderLoading() {
if (this.state.isImageLoading) {
const { spinner } = this.props;
return (
<div
className={cn([s.imageLoadingSpinnerContainer, 'carousel__image-loading-spinner-container'])}
>
<Spinner />
{ spinner && spinner() }
{ !spinner && <Spinner />}
</div>
);
}
Expand All @@ -261,9 +265,10 @@ const ImageWithZoom = class ImageWithZoom extends React.Component {
const {
carouselStore,
isPinchZoomEnabled,
tag: Tag,
spinner,
src,
srcZoomed,
tag: Tag,
...filteredProps
} = this.props;

Expand Down
23 changes: 23 additions & 0 deletions src/ImageWithZoom/__tests__/ImageWithZoom.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,29 @@ const touchEnd = {


describe('<ImageWithZoom />', () => {
describe('unit tests', () => {
describe('renderLoading', () => {
it('should render a custom spinner if supplied', () => {
const instance = new ImageWithZoom({
spinner: () => <div className="custom-spinner" />,
});
instance.state.isImageLoading = true;
const wrapper = shallow(instance.renderLoading());
expect(wrapper.find('.custom-spinner').exists()).toBe(true);
});
it('should render a the default spinner if no custom spinner was supplied', () => {
const instance = new ImageWithZoom({});
instance.state.isImageLoading = true;
const wrapper = shallow(instance.renderLoading());
expect(wrapper.find('Spinner').exists()).toBe(true);
});
it('should return null if imageIsLoading is false', () => {
const instance = new ImageWithZoom({});
instance.state.isImageLoading = false;
expect(instance.renderLoading()).toBe(null);
});
});
});
describe('integration tests', () => {
let wrapper;
let imageWithZoom;
Expand Down
8 changes: 6 additions & 2 deletions src/Slider/Slider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const Slider = class Slider extends React.Component {
playDirection: CarouselPropTypes.direction.isRequired,
slideSize: PropTypes.number.isRequired,
slideTraySize: PropTypes.number.isRequired,
spinner: PropTypes.func,
step: PropTypes.number.isRequired,
style: PropTypes.object,
tabIndex: PropTypes.number,
Expand All @@ -51,6 +52,7 @@ const Slider = class Slider extends React.Component {
disableKeyboard: false,
moveThreshold: 0.1,
onMasterSpinner: null,
spinner: null,
style: {},
tabIndex: null,
trayTag: 'ul',
Expand Down Expand Up @@ -427,7 +429,7 @@ const Slider = class Slider extends React.Component {
}

renderMasterSpinner() {
const { hasMasterSpinner, masterSpinnerFinished } = this.props;
const { hasMasterSpinner, masterSpinnerFinished, spinner } = this.props;

if (hasMasterSpinner && (!masterSpinnerFinished)) {
if (typeof this.props.onMasterSpinner === 'function') this.props.onMasterSpinner();
Expand All @@ -439,7 +441,8 @@ const Slider = class Slider extends React.Component {
'carousel__master-spinner-container',
])}
>
<Spinner />
{spinner && spinner()}
{!spinner && <Spinner />}
</div>
);
}
Expand Down Expand Up @@ -473,6 +476,7 @@ const Slider = class Slider extends React.Component {
playDirection,
slideSize,
slideTraySize,
spinner,
style,
tabIndex,
totalSlides,
Expand Down
33 changes: 33 additions & 0 deletions src/Slider/__tests__/Slider.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,39 @@ describe('<Slider />', () => {
expect(ev.stopPropagation).toHaveBeenCalledTimes(1);
});
});
describe('renderMasterSpinner', () => {
it('should render a custom spinner if supplied', () => {
const instance = new Slider({
hasMasterSpinner: true,
masterSpinnerFinished: false,
spinner: () => <div className="custom-spinner" />,
});
const wrapper = shallow(instance.renderMasterSpinner());
expect(wrapper.find('.custom-spinner').exists()).toBe(true);
});
it('should render a the default spinner if no custom spinner was supplied', () => {
const instance = new Slider({
hasMasterSpinner: true,
masterSpinnerFinished: false,
});
const wrapper = shallow(instance.renderMasterSpinner());
expect(wrapper.find('Spinner').exists()).toBe(true);
});
it('should return null if hasMasterSpinner is false', () => {
const instance = new Slider({
hasMasterSpinner: false,
masterSpinnerFinished: false,
});
expect(instance.renderMasterSpinner()).toBe(null);
});
it('should return null if masterSpinnerFinished is true', () => {
const instance = new Slider({
hasMasterSpinner: true,
masterSpinnerFinished: true,
});
expect(instance.renderMasterSpinner()).toBe(null);
});
});
});
describe('integration tests', () => {
let props;
Expand Down
1 change: 1 addition & 0 deletions typings/carouselElements.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface SliderProps {
readonly moveThreshold?: number,
readonly onMasterSpinner?: () => void
readonly style?: {}
readonly spinner?: () => void
readonly trayTag?: string
}
type SliderInterface = React.ComponentClass<SliderProps>
Expand Down