mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 12:43:11 +10:00
Merge pull request #877 from sharetribe/reusable-search-map
Reusable map container
This commit is contained in:
commit
31f4dda8bf
7 changed files with 165 additions and 29 deletions
|
|
@ -13,6 +13,8 @@ way to update this template, but currently, we follow a pattern:
|
|||
---
|
||||
|
||||
## Upcoming version
|
||||
* [change] Reusable SearchMap.
|
||||
[#877](https://github.com/sharetribe/flex-template-web/pull/877)
|
||||
* [change] Use seeded random for client side coordinate obfuscation
|
||||
[#874](https://github.com/sharetribe/flex-template-web/pull/874)
|
||||
|
||||
|
|
|
|||
90
src/components/SearchMap/ReusableMapContainer.js
Normal file
90
src/components/SearchMap/ReusableMapContainer.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { node, string } from 'prop-types';
|
||||
import css from './SearchMap.css';
|
||||
|
||||
class ReusableMapContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.reusableSearchMapElement =
|
||||
window.reusableSearchMapElement || document.createElement('div');
|
||||
|
||||
if (!props.className) {
|
||||
console.warn('ReusableMapContainer should get className prop which defines its layout');
|
||||
}
|
||||
// If no className is given, we use some defaults, which makes it easier to debug loading.
|
||||
const mapLayoutClassName = props.className || css.defaultMapLayout;
|
||||
|
||||
this.el = window.reusableSearchMapElement;
|
||||
this.el.id = 'search-map';
|
||||
this.el.classList.add(mapLayoutClassName);
|
||||
}
|
||||
|
||||
this.mountNode = null;
|
||||
this.renderSearchMap = this.renderSearchMap.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.renderSearchMap();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.renderSearchMap();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.el.classList.add(css.reusableMapHidden);
|
||||
this.mountNode.removeChild(this.el);
|
||||
document.body.appendChild(this.el);
|
||||
}
|
||||
|
||||
renderSearchMap() {
|
||||
const targetDomNode = document.getElementById(this.el.id);
|
||||
|
||||
// Check if we have already added map somewhere on the DOM
|
||||
if (!targetDomNode) {
|
||||
if (this.mountNode && !this.mountNode.firstChild) {
|
||||
// If mountable, but not yet mounted, append rendering context inside SPA rendering tree.
|
||||
this.mountNode.appendChild(this.el);
|
||||
} else if (!this.mountNode) {
|
||||
// if no mountNode is found, append this outside SPA rendering tree (to document body)
|
||||
document.body.appendChild(this.el);
|
||||
}
|
||||
} else {
|
||||
this.el.classList.remove(css.reusableMapHidden);
|
||||
|
||||
if (this.mountNode && !this.mountNode.firstChild) {
|
||||
// Move the map to correct location if we have rendered the map before
|
||||
// (but it's not yet moved to correct location of rendering tree).
|
||||
document.body.removeChild(this.el);
|
||||
this.mountNode.appendChild(this.el);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(this.props.children, this.el);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={css.reusableMap}
|
||||
ref={node => {
|
||||
this.mountNode = node;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReusableMapContainer.defaultProps = {
|
||||
className: string,
|
||||
};
|
||||
|
||||
ReusableMapContainer.propTypes = {
|
||||
children: node.isRequired,
|
||||
className: string,
|
||||
};
|
||||
|
||||
export default ReusableMapContainer;
|
||||
|
|
@ -10,3 +10,34 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.reusableMap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.defaultMapLayout {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/**
|
||||
* When reusable map is attached right to the body it's hidden.
|
||||
* Specificity rule is added to overwrite positioning coming from props.className
|
||||
*/
|
||||
body > .reusableMapHidden {
|
||||
position: absolute;
|
||||
top: -1000px;
|
||||
left: -1000px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
top: -1000px;
|
||||
left: -1000px;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { googleBoundsToSDKBounds } from '../../util/googleMaps';
|
|||
import { SearchMapInfoCard, SearchMapPriceLabel, SearchMapGroupLabel } from '../../components';
|
||||
import config from '../../config';
|
||||
|
||||
import ReusableMapContainer from './ReusableMapContainer';
|
||||
import css from './SearchMap.css';
|
||||
|
||||
const LABEL_HANDLE = 'SearchMapLabel';
|
||||
|
|
@ -240,6 +241,7 @@ export class SearchMapComponent extends Component {
|
|||
const {
|
||||
className,
|
||||
rootClassName,
|
||||
reuseableContainerClassName,
|
||||
center,
|
||||
isOpenOnModal,
|
||||
listings: originalListings,
|
||||
|
|
@ -263,28 +265,30 @@ export class SearchMapComponent extends Component {
|
|||
// container element listens clicks so that opened SearchMapInfoCard can be closed
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
return isMapsLibLoaded ? (
|
||||
<MapWithGoogleMap
|
||||
containerElement={<div className={classes} onClick={this.onMapClicked} />}
|
||||
mapElement={<div className={mapClasses} />}
|
||||
center={center}
|
||||
isOpenOnModal={isOpenOnModal}
|
||||
listings={listings}
|
||||
activeListingId={activeListingId}
|
||||
infoCardOpen={this.state.infoCardOpen}
|
||||
onListingClicked={this.onListingClicked}
|
||||
onMapLoad={this.onMapLoadHandler}
|
||||
onIdle={() => {
|
||||
if (this.googleMap) {
|
||||
onIdle(this.googleMap);
|
||||
}
|
||||
}}
|
||||
onCloseAsModal={() => {
|
||||
if (onCloseAsModal) {
|
||||
onCloseAsModal();
|
||||
}
|
||||
}}
|
||||
zoom={zoom}
|
||||
/>
|
||||
<ReusableMapContainer className={reuseableContainerClassName}>
|
||||
<MapWithGoogleMap
|
||||
containerElement={<div className={classes} onClick={this.onMapClicked} />}
|
||||
mapElement={<div className={mapClasses} />}
|
||||
center={center}
|
||||
isOpenOnModal={isOpenOnModal}
|
||||
listings={listings}
|
||||
activeListingId={activeListingId}
|
||||
infoCardOpen={this.state.infoCardOpen}
|
||||
onListingClicked={this.onListingClicked}
|
||||
onMapLoad={this.onMapLoadHandler}
|
||||
onIdle={() => {
|
||||
if (this.googleMap) {
|
||||
onIdle(this.googleMap);
|
||||
}
|
||||
}}
|
||||
onCloseAsModal={() => {
|
||||
if (onCloseAsModal) {
|
||||
onCloseAsModal();
|
||||
}
|
||||
}}
|
||||
zoom={zoom}
|
||||
/>
|
||||
</ReusableMapContainer>
|
||||
) : (
|
||||
<div className={classes} />
|
||||
);
|
||||
|
|
@ -293,31 +297,33 @@ export class SearchMapComponent extends Component {
|
|||
}
|
||||
|
||||
SearchMapComponent.defaultProps = {
|
||||
className: null,
|
||||
rootClassName: null,
|
||||
mapRootClassName: null,
|
||||
reuseableContainerClassName: null,
|
||||
bounds: null,
|
||||
center: new sdkTypes.LatLng(0, 0),
|
||||
activeListingId: null,
|
||||
className: null,
|
||||
isOpenOnModal: false,
|
||||
listings: [],
|
||||
mapRootClassName: null,
|
||||
onCloseAsModal: null,
|
||||
rootClassName: null,
|
||||
useLocationSearchBounds: true,
|
||||
zoom: 11,
|
||||
coordinatesConfig: config.coordinates,
|
||||
};
|
||||
|
||||
SearchMapComponent.propTypes = {
|
||||
className: string,
|
||||
rootClassName: string,
|
||||
mapRootClassName: string,
|
||||
reuseableContainerClassName: string,
|
||||
bounds: propTypes.latlngBounds,
|
||||
center: propTypes.latlng,
|
||||
activeListingId: propTypes.uuid,
|
||||
className: string,
|
||||
isOpenOnModal: bool,
|
||||
listings: arrayOf(propTypes.listing),
|
||||
mapRootClassName: string,
|
||||
onCloseAsModal: func,
|
||||
onIdle: func.isRequired,
|
||||
rootClassName: string,
|
||||
useLocationSearchBounds: bool, // eslint-disable-line react/no-unused-prop-types
|
||||
zoom: number,
|
||||
coordinatesConfig: shape({
|
||||
|
|
|
|||
|
|
@ -137,6 +137,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.mapWrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
|
|
|||
|
|
@ -251,9 +251,10 @@ export class SearchPageComponent extends Component {
|
|||
showAsModalMaxWidth={MODAL_BREAKPOINT}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
>
|
||||
<div className={css.map}>
|
||||
<div className={css.mapWrapper}>
|
||||
{shouldShowSearchMap ? (
|
||||
<SearchMap
|
||||
reuseableContainerClassName={css.map}
|
||||
activeListingId={activeListingId}
|
||||
bounds={bounds}
|
||||
center={origin}
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ exports[`SearchPageComponent matches snapshot 1`] = `
|
|||
mapRootClassName={null}
|
||||
onCloseAsModal={[Function]}
|
||||
onIdle={[Function]}
|
||||
reuseableContainerClassName={null}
|
||||
rootClassName={null}
|
||||
useLocationSearchBounds={true}
|
||||
zoom={11}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue