mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
SearchMapListingCard opens when price 'marker' is clicked
This commit is contained in:
parent
7a6ac4506f
commit
90adf90c13
4 changed files with 265 additions and 2 deletions
|
|
@ -39,17 +39,21 @@ import NoImageIcon from './NoImageIcon';
|
|||
import css from './ResponsiveImage.css';
|
||||
|
||||
const ResponsiveImage = props => {
|
||||
const { className, rootClassName, alt, image, nameSet, sizes } = props;
|
||||
const { className, rootClassName, alt, noImageMessage, image, nameSet, sizes } = props;
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
if (image == null || nameSet.length === 0) {
|
||||
const noImageClasses = classNames(rootClassName || css.root, css.noImageContainer, className);
|
||||
|
||||
// NoImageMessage is needed for listing images on top the map (those component lose context)
|
||||
// https://github.com/tomchentw/react-google-maps/issues/578
|
||||
const noImageMessageText = noImageMessage || <FormattedMessage id="ResponsiveImage.noImage" />;
|
||||
/* eslint-disable jsx-a11y/img-redundant-alt */
|
||||
return (
|
||||
<div className={noImageClasses}>
|
||||
<div className={css.noImageWrapper}>
|
||||
<NoImageIcon className={css.noImageIcon} />
|
||||
<div className={css.noImageText}><FormattedMessage id="ResponsiveImage.noImage" /></div>
|
||||
<div className={css.noImageText}>{noImageMessageText}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -79,6 +83,7 @@ ResponsiveImage.defaultProps = {
|
|||
image: null,
|
||||
nameSet: [],
|
||||
sizes: null,
|
||||
noImageMessage: null,
|
||||
};
|
||||
|
||||
ResponsiveImage.propTypes = {
|
||||
|
|
@ -93,6 +98,7 @@ ResponsiveImage.propTypes = {
|
|||
})
|
||||
),
|
||||
sizes: string,
|
||||
noImageMessage: string,
|
||||
};
|
||||
|
||||
export default ResponsiveImage;
|
||||
|
|
|
|||
132
src/components/SearchMapListingCard/SearchMapListingCard.css
Normal file
132
src/components/SearchMapListingCard/SearchMapListingCard.css
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
display: block;
|
||||
position: relative;
|
||||
height: auto;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.anchor {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
/**
|
||||
* Since caret is absolutely positioned,
|
||||
* label must have relative to be included to the same rendering layer
|
||||
*/
|
||||
position: relative;
|
||||
|
||||
width: 250px;
|
||||
min-height: 207px;
|
||||
|
||||
/* Font */
|
||||
@apply --marketplaceH5FontStyles;
|
||||
color: var(--matterColor);
|
||||
|
||||
background-color: var(--matterColorLight);
|
||||
|
||||
/* Borders */
|
||||
border-style: solid;
|
||||
border-color: var(--matterColorNegative);
|
||||
border-width: 1px;
|
||||
border-radius: 4px;
|
||||
box-shadow: var(--boxShadowPopupLight);
|
||||
|
||||
/* Dimensions */
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
transition: var(--transitionStyleButton);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
box-shadow: var(--boxShadowPopup);
|
||||
}
|
||||
|
||||
/* Overwrite dimensions from font styles */
|
||||
@media (--viewportMedium) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.threeToTwoWrapper {
|
||||
/* Layout */
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Firefox doesn't support image aspect ratio inside flexbox */
|
||||
.aspectWrapper {
|
||||
padding-bottom: 66.6667%; /* 3:2 Aspect Ratio */
|
||||
background: var(--matterColorNegative); /* Loading BG color */
|
||||
}
|
||||
|
||||
.rootForImage {
|
||||
/* Layout - image will take space defined by aspect ratio wrapper */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.price {
|
||||
flex-shrink: 0;
|
||||
font-weight: var(--fontWeightSemiBold);
|
||||
color: var(--marketplaceColor);
|
||||
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.caretShadow {
|
||||
/* Caret / arrow dimensions and position */
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
bottom: -3px;
|
||||
left: 50%;
|
||||
margin-left: -3px;
|
||||
transform: rotate(45deg);
|
||||
|
||||
/* Caret should have same box-shadow as label */
|
||||
box-shadow: var(--boxShadowPopupLight);
|
||||
}
|
||||
|
||||
.caret {
|
||||
|
||||
/* Caret / arrow dimensions and position */
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
bottom: -3px;
|
||||
left: 50%;
|
||||
margin-left: -3px;
|
||||
transform: rotate(45deg);
|
||||
|
||||
/* Caret should have same bg-color and border as label */
|
||||
background-color: var(--matterColorLight);
|
||||
border-right-style: solid;
|
||||
border-right-color: var(--matterColorNegative);
|
||||
border-right-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: var(--matterColorNegative);
|
||||
border-bottom-width: 1px;
|
||||
|
||||
}
|
||||
123
src/components/SearchMapListingCard/SearchMapListingCard.js
Normal file
123
src/components/SearchMapListingCard/SearchMapListingCard.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { OverlayView } from 'react-google-maps';
|
||||
import { compose } from 'redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { injectIntl, intlShape } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import * as propTypes from '../../util/propTypes';
|
||||
import { formatMoney } from '../../util/currency';
|
||||
import config from '../../config';
|
||||
import { withFlattenedRoutes } from '../../util/contextHelpers';
|
||||
import { createResourceLocatorString } from '../../util/routes';
|
||||
import { ResponsiveImage } from '../../components';
|
||||
|
||||
import css from './SearchMapListingCard.css';
|
||||
|
||||
// Center label so that caret is pointing to correct pixel.
|
||||
// (vertical positioning: height + arrow) */
|
||||
const getPixelPositionOffset = (width, height) => {
|
||||
return { x: -1 * (width / 2), y: -1 * (height + 3) };
|
||||
};
|
||||
|
||||
const createURL = (flattenedRoutes, history, listing) => {
|
||||
const id = listing.id.uuid;
|
||||
const slug = encodeURIComponent(listing.attributes.title.split(' ').join('-'));
|
||||
const pathParams = { id, slug };
|
||||
return createResourceLocatorString('ListingPage', flattenedRoutes, pathParams, {});
|
||||
};
|
||||
|
||||
class SearchMapListingCard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.clickHandler = this.clickHandler.bind(this);
|
||||
}
|
||||
|
||||
clickHandler(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// To avoid full page refresh we need to use internal router
|
||||
const { flattenedRoutes, history, listing } = this.props;
|
||||
history.push(createURL(flattenedRoutes, history, listing));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, rootClassName, intl, flattenedRoutes, history, listing } = this.props;
|
||||
const { title, price } = listing.attributes;
|
||||
const formattedPrice = price && price.currency === config.currencyConfig.currency
|
||||
? formatMoney(intl, config.currencyConfig, price)
|
||||
: price.currency;
|
||||
const firstImage = listing.images && listing.images.length > 0 ? listing.images[0] : null;
|
||||
const geolocation = listing.attributes.geolocation;
|
||||
const urlToListing = createURL(flattenedRoutes, history, listing);
|
||||
|
||||
// Explicit type change to object literal for Google OverlayViews (geolocation is SDK type)
|
||||
const latLngLiteral = { lat: geolocation.lat, lng: geolocation.lng };
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
|
||||
return (
|
||||
<OverlayView
|
||||
position={latLngLiteral}
|
||||
mapPaneName={OverlayView.FLOAT_PANE}
|
||||
getPixelPositionOffset={getPixelPositionOffset}
|
||||
styles={{ zIndex: 1 }}
|
||||
>
|
||||
<div className={classes}>
|
||||
<a alt={title} className={css.anchor} href={urlToListing} onClick={this.clickHandler}>
|
||||
<div className={css.caretShadow} />
|
||||
<div className={css.card}>
|
||||
<div className={css.threeToTwoWrapper}>
|
||||
<div className={css.aspectWrapper}>
|
||||
<ResponsiveImage
|
||||
rootClassName={css.rootForImage}
|
||||
alt={title}
|
||||
noImageMessage={intl.formatMessage({ id: 'SearchMapListingCard.noImage' })}
|
||||
image={firstImage}
|
||||
nameSet={[
|
||||
{ name: 'landscape-crop', size: '1x' },
|
||||
{ name: 'landscape-crop2x', size: '2x' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.info}>
|
||||
<div className={css.price}>
|
||||
{formattedPrice}
|
||||
</div>
|
||||
<div className={css.name}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.caret} />
|
||||
</a>
|
||||
</div>
|
||||
</OverlayView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SearchMapListingCard.defaultProps = {
|
||||
className: null,
|
||||
rootClassName: null,
|
||||
};
|
||||
|
||||
const { arrayOf, func, shape, string } = PropTypes;
|
||||
|
||||
SearchMapListingCard.propTypes = {
|
||||
className: string,
|
||||
rootClassName: string,
|
||||
listing: propTypes.listing.isRequired,
|
||||
|
||||
// from withFlattenedRoutes
|
||||
flattenedRoutes: arrayOf(propTypes.route).isRequired,
|
||||
|
||||
// from withRouter
|
||||
history: shape({
|
||||
push: func.isRequired,
|
||||
}).isRequired,
|
||||
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default compose(withRouter, withFlattenedRoutes, injectIntl)(SearchMapListingCard);
|
||||
|
|
@ -42,6 +42,7 @@ import RoutesProvider from './RoutesProvider/RoutesProvider';
|
|||
import SaleDetailsPanel from './SaleDetailsPanel/SaleDetailsPanel';
|
||||
import SearchIcon from './SearchIcon/SearchIcon';
|
||||
import SearchMap from './SearchMap/SearchMap';
|
||||
import SearchMapListingCard from './SearchMapListingCard/SearchMapListingCard';
|
||||
import SearchMapPriceLabel from './SearchMapPriceLabel/SearchMapPriceLabel';
|
||||
import SearchResultsPanel from './SearchResultsPanel/SearchResultsPanel';
|
||||
import SelectField from './SelectField/SelectField';
|
||||
|
|
@ -103,6 +104,7 @@ export {
|
|||
SaleDetailsPanel,
|
||||
SearchIcon,
|
||||
SearchMap,
|
||||
SearchMapListingCard,
|
||||
SearchMapPriceLabel,
|
||||
SearchResultsPanel,
|
||||
SecondaryButton,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue