Skip to content

Commit

Permalink
Merge pull request #327 from rdjpalmer/space-listing-card
Browse files Browse the repository at this point in the history
Add additional features to destination listing cards
  • Loading branch information
rdjpalmer committed Sep 4, 2017
2 parents 459ee4d + 418dc42 commit 17f6d27
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 53 deletions.
80 changes: 80 additions & 0 deletions components/Cards/Cards.story.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,86 @@ storiesOf('Cards', module)
href="#"
/>
))
.add('SpaceListingCard with additional actions', () => (
<SpaceListingCard
price="$10,000,000"
priceUnit="/day"
location="Shoreditch, London"
size="1000 sqft"
name="Bold Street Shop"
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="#"
>
<button>Save space</button>
</SpaceListingCard>
))
.add('Favouritable SpaceListingCard', () => (
<SpaceListingCard
price="$10,000,000"
priceUnit="/day"
location="Shoreditch, London"
size="1000 sqft"
name="Bold Street Shop"
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="#"
favouriteable
/>
))
.add('Favouritable SpaceListingCard as a favourite', () => (
<SpaceListingCard
price="$10,000,000"
priceUnit="/day"
location="Shoreditch, London"
size="1000 sqft"
name="Bold Street Shop"
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="#"
favourite
favouriteable
/>
))
.add('CondensedSpaceCard', () => (
<CondensedSpaceCard
price="$10,000,000"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
box-shadow: 0 2px 7px 0 rgba(0, 0, 0, 0.1);
}

.heart {
position: absolute;
top: var(--size-regular);
left: var(--size-regular);
z-index: 2;
font-size: 1.8rem;
line-height: 0;
}

.inner {
display: block;
border-radius: 4px 4px 0 0;
Expand Down Expand Up @@ -115,7 +124,7 @@
}

.body {
padding: var(--size-medium);
padding: var(--size-regular);
display: block;
background-color: var(--color-white);
color: var(--color-greyDarker);
Expand All @@ -127,6 +136,15 @@
overflow: hidden;
}

.footer {
margin-top: var(--size-regular);
}

.bodyLink {
text-decoration: none;
color: var(--color-greyDarker);
}

.title {
display: flex;
justify-content: space-between;
Expand Down
129 changes: 78 additions & 51 deletions components/Cards/DestinationListingCard/DestinationListingCard.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { Component, PropTypes } from 'react';
import cx from 'classnames';

import getValidIndex from '../../../utils/getValidIndex/getValidIndex';
import Carousel from '../../Carousel/Carousel';
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 FittedImage from '../../FittedImage/FittedImage';
import css from './DestinationListingCard.css';

export default class DestinationListingCard extends Component {
Expand All @@ -32,6 +34,10 @@ export default class DestinationListingCard extends Component {
information: PropTypes.array,
onClick: PropTypes.func,
fixedHeight: PropTypes.bool,
children: PropTypes.node,
onFavouriteClick: PropTypes.func,
favourite: PropTypes.bool,
favouriteable: PropTypes.bool,
};

static defaultProps = {
Expand All @@ -41,10 +47,13 @@ export default class DestinationListingCard extends Component {
images: [],
information: [],
fixedHeight: false,
onClick: noop,
onFavouriteClick: noop,
};

state = {
visibleImageIndex: 0,
fav: false,
};

handleNextImage = () => {
Expand All @@ -55,7 +64,7 @@ export default class DestinationListingCard extends Component {
visibleImageIndex: newIndex,
};
});
}
};

handlePrevImage = () => {
this.setState(({ visibleImageIndex }, { images }) => {
Expand All @@ -65,7 +74,7 @@ export default class DestinationListingCard extends Component {
visibleImageIndex: newIndex,
};
});
}
};

render() {
const { visibleImageIndex } = this.state;
Expand All @@ -86,6 +95,10 @@ export default class DestinationListingCard extends Component {
information,
onClick,
fixedHeight,
children,
onFavouriteClick,
favourite,
favouriteable,
...rest,
} = this.props;

Expand All @@ -98,6 +111,13 @@ export default class DestinationListingCard extends Component {
fixedHeight ? css.fixedHeight : null,
) }
>
{ favouriteable && (
<HeartBtn
className={ css.heart }
onClick={ onFavouriteClick }
active={ favourite }
/>
) }
<div className={ cx(css.carousel, carouselClassName) }>
{ carouselOverlay }
<BtnContainer
Expand Down Expand Up @@ -135,54 +155,61 @@ export default class DestinationListingCard extends Component {
</Carousel>
</div>
</div>
<a href={ href } className={ cx(css.body, bodyClassName) } onClick={ onClick }>
<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 className={ cx(css.body, bodyClassName) }>
<a href={ href } onClick={ 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>
{ 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>
);
<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>
return accu === null
? [wrappedEl]
: [...accu, spacer, wrappedEl];
},
null,
)
}
</div>
</a>
{ children && (
<div className={ css.footer }>
{ children }
</div>
) }
</div>
</div>
);
}
Expand Down
70 changes: 70 additions & 0 deletions components/HeartBtn/HeartBtn.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.root:focus {
outline: none;
}

.icon {
transition: color 150ms ease-in, transform 150ms ease-in;
display: inline-block;
width: 1em;
height: 1em;
}

.dark,
.dark:hover,
.dark:focus {
color: rgba(0, 0, 0, 0.25);
}

.light,
.light:focus {
color: rgba(255, 255, 255, 0.25);
}

.dark:hover {
color: rgba(0, 0, 0, 0.75);
}

.light:hover {
color: rgba(255, 255, 255, 0.75);
}

.icon svg {
stroke-width: 3px;
}

.dark .icon svg {
stroke: var(--color-white);
}

.light .icon svg {
stroke: transparent;
}

.root:active .icon,
.root:active .active,
.root:focus .icon {
transform: scale(0.9);
}

.active {
color: var(--color-red);
animation-name: fillIn;
animation-duration: 350ms;
animation-fill-mode: forwards;
animation-timing-function: cubic-bezier(0, 1.384, 0.531, 1.417);
animation-iteration-count: 1;
}

@keyframes fillIn {
0% {
transform: scale(1);
}

50% {
transform: scale(1.15);
}

100% {
transform: scale(1);
}
}
Loading

0 comments on commit 17f6d27

Please sign in to comment.