Skip to content

Commit

Permalink
Add <SpaceListingCard /> component
Browse files Browse the repository at this point in the history
Provides a semantically correct foundation for space listing cards,
including an image carousel, it's controls and anchors for linking to
other pages
  • Loading branch information
Richard Palmer committed Feb 9, 2017
1 parent 59d7a22 commit 15d0b4d
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
21 changes: 21 additions & 0 deletions components/Cards/Cards.story.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { storiesOf, action } from '@kadira/storybook';
import Card from './Card/Card';
import PictureCard from './PictureCard/PictureCard';
import SpaceFeatureCard from './SpaceFeatureCard/SpaceFeatureCard';
import SpaceListingCard from './SpaceListingCard/SpaceListingCard';
import EditorialCard from './EditorialCard/EditorialCard';
import GuideCard from './EditorialCard/GuideCard';
import EventCard from './EditorialCard/EventCard';
Expand Down Expand Up @@ -50,6 +51,26 @@ storiesOf('Cards', module)
src="https://source.unsplash.com/random"
/>
))
.add('SpaceListingCard', () => (
<SpaceListingCard
images={
[{
src: 'https://source.unsplash.com/random/500x500',
alt: 'hello',
}, {
src: 'https://source.unsplash.com/random/500x503',
alt: 'hello2',
}, {
src: 'https://source.unsplash.com/random/500x502',
alt: 'hello',
}, {
src: 'https://source.unsplash.com/random/500x501',
alt: 'hello2',
}]
}
href="#"
/>
))
.add('EditorialCard', () => (
<EditorialCard
title="Mulberry Street"
Expand Down
72 changes: 72 additions & 0 deletions components/Cards/SpaceListingCard/SpaceListingCard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
.root {
position: relative;
background-color: var(--color-white);
border: 1px solid var(--color-greyLighter);
border-radius: 4px;
}

.inner {
display: block;
}

.carousel {
position: relative;
width: 100%;
height: 100%;
}

.carouselControls {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
z-index: 1;
}

.control {
position: absolute;
color: var(--color-white);
font-size: 1rem;
background-color: transparent;
border: 0;
opacity: 0.75;
}

.control:hover,
.control:focus,
.control:active {
color: var(--color-white);
opacity: 1;
}

.control:disabled,
.control:hover:disabled,
.control:active:disabled,
.control:focus:disabled {
opacity: 0.3;
color: var(--color-white);
background-color: transparent;
border: 0;
}

.prev {
right: auto;
left: var(--size-medium);
transform: translateY(-50%) rotate(270deg);
}
.next {
right: var(--size-medium);
left: auto;
transform: translateY(-50%) rotate(90deg);
}

.image {
width: 100%;
max-height: 13.75rem;
object-fit: cover;
}

.spaceDetail {
padding: var(--size-small);
display: block;
}
108 changes: 108 additions & 0 deletions components/Cards/SpaceListingCard/SpaceListingCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { Component, PropTypes } from 'react';
import cx from 'classnames';

import getValidIndex from '../../../utils/getValidIndex/getValidIndex';
import Carousel from '../../Carousel/Carousel';
import BtnContainer from '../../BtnContainer/BtnContainer';
import Icon from '../../Icon/Icon';
import ScreenReadable from '../../ScreenReadable/ScreenReadable';
import FittedImage from '../../FittedImage/FittedImage';
import css from './SpaceListingCard.css';

export default class SpaceListingCard extends Component {
static propTypes = {
href: PropTypes.string,
images: PropTypes.arrayOf(
PropTypes.shape({
src: PropTypes.string,
alt: PropTypes.string,
})
),
accessibilityNextLabel: PropTypes.string,
accessibilityPrevLabel: PropTypes.string,
};

static defaultProps = {
accessibilityNextLabel: 'Show next slide',
accessibilityPrevLabel: 'Show previous slide',
};

state = {
visibleImageIndex: 0,
};

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,
} = this.props;

return (
<div className={ css.root }>
<div className={ css.carousel }>
<div className={ css.carouselControls }>
<BtnContainer
onClick={ this.handlePrevImage }
className={ cx(css.control, css.prev) }
>
<Icon name="chevron" />
<ScreenReadable>{ accessibilityPrevLabel }</ScreenReadable>
</BtnContainer>
<BtnContainer
onClick={ this.handleNextImage }
className={ cx(css.control, css.next) }
>
<Icon name="chevron" />
<ScreenReadable>{ accessibilityNextLabel }</ScreenReadable>
</BtnContainer>
</div>
<a
href={ href }
className={ css.inner }
>
<Carousel lowestVisibleItemIndex={ visibleImageIndex } wrapAround>
{ images.map(({ src, alt }) => (
<div key={ src }>
<FittedImage
className={ css.image }
src={ src }
alt={ alt }
/>
</div>
)) }
</Carousel>
</a>
</div>
<a
href={ href }
className={ css.spaceDetail }
>
{ /* TODO: Add space details e.g., name and price here */}
</a>
</div>
);
}
}
9 changes: 9 additions & 0 deletions components/Cards/SpaceListingCard/SpaceListingCard.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import { render } from 'react-dom';

import SpaceListingCard from './SpaceListingCard';

it('renders without crashing', () => {
const div = document.createElement('div');
render(<SpaceListingCard images={ [] } />, div);
});

0 comments on commit 15d0b4d

Please sign in to comment.