Skip to content
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

Add ImageCarousel #457

Merged
merged 11 commits into from
Sep 13, 2018
66 changes: 66 additions & 0 deletions src/components/ImageCarousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* eslint-disable react/default-props-match-prop-types */
// Enable the above rule when the following is addressed https://github.com/yannickcr/eslint-plugin-react/issues/1674

import React from 'react';
import Icon from './Icon';
import Modal from './Modal';
import UncontrolledCarousel from './UncontrolledCarousel';

export default class ImageCarousel extends React.Component {
static propTypes = {
...UncontrolledCarousel.propTypes,
...Modal.propTypes
};

static defaultProps = {
autoPlay: false,
backdrop: true,
fade: false,
items: [],
toggle: () => {}
};

handleEscape(e) {
if (e.key === 'Escape') {
this.props.toggle();
}
}

componentDidMount() {
document.addEventListener('keyup', e => this.handleEscape(e));
}

componentWillUnmount() {
document.removeEventListener('keyup', e => this.handleEscape(e));
}

render() {
const { autoPlay, controls, items, indicators, interval, toggle, ...props } = this.props;

// TODO temp - remove need for style tag below:
return (
<Modal
external={
<div className="h-100">
<Icon
name="times"
size="2x"
className="text-white"
style={{ position: 'fixed', top: '2rem', right: '2rem', zIndex: 15000 }}
onClick={toggle}
/>
<UncontrolledCarousel
className="d-flex align-items-center h-100"
items={items}
indicators={indicators}
interval={autoPlay ? interval : 0}
controls={controls}
autoPlay={autoPlay}
/>
</div>
}
{...props}
/>
);
}
}
3 changes: 3 additions & 0 deletions src/components/UncontrolledCarousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import UncontrolledCarousel from 'reactstrap/lib/UncontrolledCarousel';

export default UncontrolledCarousel;
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import Row from './components/Row';
import TabContent from './components/TabContent';
import TabPane from './components/TabPane';
import UncontrolledButtonDropdown from './components/UncontrolledButtonDropdown';
import UncontrolledCarousel from './components/UncontrolledCarousel';
import UncontrolledCollapse from './components/UncontrolledCollapse';
import UncontrolledDropdown from './components/UncontrolledDropdown';
import UncontrolledTooltip from './components/UncontrolledTooltip';
Expand Down Expand Up @@ -100,6 +101,7 @@ import HasManyFields from './components/HasManyFields.js';
import HasManyFieldsAdd from './components/HasManyFieldsAdd.js';
import HasManyFieldsRow from './components/HasManyFieldsRow.js';
import HelpBubble from './components/HelpBubble.js';
import ImageCarousel from './components/ImageCarousel.js';
import Icon from './components/Icon.js';
import InfoBox from './components/InfoBox.js';
import LabelBadge from './components/LabelBadge.js';
Expand Down Expand Up @@ -205,6 +207,7 @@ export {
TabPane,
Table,
UncontrolledButtonDropdown,
UncontrolledCarousel,
UncontrolledCollapse,
UncontrolledDropdown,
UncontrolledTooltip,
Expand Down Expand Up @@ -243,6 +246,7 @@ export {
HasManyFieldsRow,
HelpBubble,
Icon,
ImageCarousel,
InfoBox,
LabelBadge,
MonthCalendar,
Expand Down
39 changes: 39 additions & 0 deletions stories/ImageCarousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { boolean, number } from '@storybook/addon-knobs';

import { ImageCarousel } from '../src';

const items = [
{
src: 'https://picsum.photos/800/600?random',
altText: 'Slide 1',
caption: 'Slide 1',
header: 'Slide 1 Header'
},
{
src: 'https://picsum.photos/1000/750?random',
altText: 'Slide 2',
caption: 'Slide 2',
header: 'Slide 2 Header'
},
{
src: 'https://picsum.photos/1200/900?random',
altText: 'Slide 3',
caption: 'Slide 3',
header: 'Slide 3 Header'
}
];

storiesOf('ImageCarousel', module)
.addWithInfo('default props', () => (
<ImageCarousel
items={items}
fade={boolean('fade', ImageCarousel.defaultProps.fade)}
isOpen={boolean('isOpen', true)}
indicators={boolean('indicators', true)}
interval={number('interval', ImageCarousel.defaultProps.interval)}
controls={boolean('controls', true)}
autoPlay={boolean('autoPlay', ImageCarousel.defaultProps.autoPlay)}
/>
));
1 change: 1 addition & 0 deletions stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import './Forms';
import './HasManyFields';
import './HelpBubble';
import './Icon';
import './ImageCarousel';
import './Input';
import './InfoBox';
import './InputGroup';
Expand Down
76 changes: 76 additions & 0 deletions test/components/ImageCarousel.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import assert from 'assert';
import { shallow } from 'enzyme';
import React from 'react';
import sinon from 'sinon';

import {
Icon,
ImageCarousel,
Modal,
UncontrolledCarousel
} from '../../src';

describe('<ImageCarousel />', () => {
it('should render a modal with desired props', () => {
const component = shallow(<ImageCarousel isOpen />);
const modal = component.find(Modal);

assert.strictEqual(modal.prop('backdrop'), true);
assert.strictEqual(modal.prop('fade'), false);
assert.strictEqual(modal.prop('isOpen'), true);
});

it('should render a close button', () => {
const component = shallow(<ImageCarousel />);
const external = shallow(component.prop('external'));
const icon = external.find(Icon);

assert.strictEqual(icon.prop('name'), 'times');
});

it('should render a carousel', () => {
const items = [{ src: 'empire' }, { src: 'phantom' }, { src: 'force' }];
const component = shallow(<ImageCarousel items={items} />);
const external = shallow(component.prop('external'));
const carousel = external.find(UncontrolledCarousel);

assert.deepStrictEqual(carousel.prop('items'), [{ src: 'empire' }, { src: 'phantom' }, { src: 'force' }]);
assert.strictEqual(carousel.prop('indicators'), true);
assert.strictEqual(carousel.prop('controls'), true);
assert.strictEqual(carousel.prop('autoPlay'), false);
assert.strictEqual(carousel.prop('interval'), 0);
});

it('should call toggle when ESC is pressed', () => {
const spy = sinon.spy();
shallow(<ImageCarousel toggle={spy} />);

const event = new KeyboardEvent('keyup', { key: 'Escape' });
document.dispatchEvent(event);

assert(spy.calledOnce);
});

it('should not call toggle when other keys are pressed', () => {
const spy = sinon.spy();
shallow(<ImageCarousel toggle={spy} />);

const event = new KeyboardEvent('keyup', { key: 'Backspace' });
document.dispatchEvent(event);

assert(spy.notCalled);
});

describe('close button', () => {
it('should call toggle on click', () => {
const spy = sinon.spy();
const component = shallow(<ImageCarousel toggle={spy} />);
const external = shallow(component.prop('external'));
const icon = external.find(Icon);

icon.simulate('click');

assert(spy.calledOnce);
});
});
});