@appearhere/bloom
Version:
Appear Here's pattern library and styleguide
221 lines (208 loc) • 6.55 kB
JavaScript
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import cx from 'classnames';
import noop from '../../../utils/noop';
import BtnContainer from '../../BtnContainer/BtnContainer';
import Carousel from '../../Carousel/Carousel';
import FittedImage from '../../FittedImage/FittedImage';
import getValidIndex from '../../../utils/getValidIndex/getValidIndex';
import HeartBtn from '../../HeartBtn/HeartBtn';
import Icon from '../../Icon/Icon';
import ScreenReadable from '../../ScreenReadable/ScreenReadable';
import css from './DestinationListingCard.css';
export default class DestinationListingCard extends Component {
static propTypes = {
href: PropTypes.string,
images: PropTypes.arrayOf(
PropTypes.shape({
src: PropTypes.string,
alt: PropTypes.string,
})
),
priceFromLabel: PropTypes.node,
price: PropTypes.node,
priceUnit: PropTypes.node,
badge: PropTypes.node,
name: PropTypes.node,
className: PropTypes.string,
bodyClassName: PropTypes.string,
carouselClassName: PropTypes.string,
accessibilityNextLabel: PropTypes.string,
accessibilityPrevLabel: PropTypes.string,
carouselOverlay: PropTypes.node,
information: PropTypes.array,
onClick: PropTypes.func,
fixedHeight: PropTypes.bool,
children: PropTypes.node,
onFavouriteClick: PropTypes.func,
favourite: PropTypes.bool,
favouriteable: PropTypes.bool,
};
static defaultProps = {
accessibilityNextLabel: 'Show next slide',
accessibilityPrevLabel: 'Show previous slide',
href: '#',
images: [],
information: [],
fixedHeight: false,
onClick: noop,
onFavouriteClick: noop,
};
state = {
visibleImageIndex: 0,
fav: false,
};
onClick = (e) => {
this.props.onClick(e, this.props.href);
}
handleNextImage = () => {
this.setState(({ visibleImageIndex }, { images }) => {
const newIndex = getValidIndex(visibleImageIndex + 1, images.length, 1);
return {
visibleImageIndex: newIndex,
};
});
};
handlePrevImage = () => {
this.setState(({ visibleImageIndex }, { images }) => {
const newIndex = getValidIndex(visibleImageIndex - 1, images.length, 1);
return {
visibleImageIndex: newIndex,
};
});
};
render() {
const { visibleImageIndex } = this.state;
const {
href,
images,
accessibilityPrevLabel,
accessibilityNextLabel,
priceFromLabel,
price,
priceUnit,
badge,
name,
className,
bodyClassName,
carouselClassName,
carouselOverlay,
information,
fixedHeight,
children,
onFavouriteClick,
favourite,
favouriteable,
...rest,
} = this.props;
return (
<div
{ ...rest }
className={ cx(
css.root,
className,
fixedHeight ? css.fixedHeight : null,
) }
>
{ favouriteable && (
<HeartBtn
className={ css.heart }
onClick={ onFavouriteClick }
active={ favourite }
/>
) }
<div className={ cx(css.carousel, carouselClassName) }>
{ carouselOverlay }
<BtnContainer
onClick={ this.handlePrevImage }
className={ cx(css.control, css.prev) }
>
<Icon className={ cx(css.icon, css.prevIcon) } name="chevron" />
<ScreenReadable>{ accessibilityPrevLabel }</ScreenReadable>
</BtnContainer>
<BtnContainer
onClick={ this.handleNextImage }
className={ cx(css.control, css.next) }
>
<Icon className={ cx(css.icon, css.nextIcon) } name="chevron" />
<ScreenReadable>{ accessibilityNextLabel }</ScreenReadable>
</BtnContainer>
<div className={ css.inner }>
<Carousel
lowestVisibleItemIndex={ visibleImageIndex }
wrapAround
swiping={ false }
dragging={ false }
>
{ images.map(({ src, alt }) => (
<a href={ href } key={ src } onClick={ this.onClick }>
<div className={ css.imageContainer }>
<FittedImage
className={ css.image }
src={ src }
alt={ alt }
/>
</div>
</a>
)) }
</Carousel>
</div>
</div>
<div className={ cx(css.body, bodyClassName) }>
<a href={ href } onClick={ this.onClick } className={ css.bodyLink }>
<div className={ css.title }>
<div className={ css.priceContainer }>
{ priceFromLabel &&
<span className={ css.priceFromLabel }>
{ priceFromLabel }
</span> }
<span className={ css.price }>
{ price }
</span>
{ '\u00a0' }
<span className={ css.priceUnit }>
{ priceUnit }
</span>
</div>
{ badge }
</div>
<div className={ css.name }>{ name }</div>
<div className={ css.additionalInformationBlock }>
{
information
.filter(info => info)
.map(info => <span>{ info }</span>)
.reduce((accu, elem, i, arr) => {
const wrappedEl = (
<span
key={ `info-${i}` }
className={ css.additionalInformationItem }
style={ {
maxWidth: `calc(${100 / arr.length}% - 1rem)`,
} }
>
{ elem }
</span>
);
const spacer = (
<span key={ `info-spacer-${i}` } className={ css.spacer }>•</span>
);
return accu === null
? [wrappedEl]
: [...accu, spacer, wrappedEl];
},
null,
)
}
</div>
</a>
{ children && (
<div className={ css.footer }>
{ children }
</div>
) }
</div>
</div>
);
}
}