mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 12:43:11 +10:00
SearchPage: open mobile SearchMap in modal
This commit is contained in:
parent
fc4336f0f7
commit
234b2a69d7
5 changed files with 155 additions and 11 deletions
62
src/containers/SearchPage/MapIcon.js
Normal file
62
src/containers/SearchPage/MapIcon.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
|
||||
const MapIcon = props => {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width="27"
|
||||
height="27"
|
||||
viewBox="0 0 27 27"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<filter
|
||||
x="-3.8%"
|
||||
y="-10.4%"
|
||||
width="107.7%"
|
||||
height="129.2%"
|
||||
filterUnits="objectBoundingBox"
|
||||
id="a"
|
||||
>
|
||||
<feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1" />
|
||||
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
|
||||
<feColorMatrix
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"
|
||||
in="shadowBlurOuter1"
|
||||
result="shadowMatrixOuter1"
|
||||
/>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1" /><feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g
|
||||
filter="url(#a)"
|
||||
transform="translate(-41 -12)"
|
||||
strokeWidth="1.4"
|
||||
fill="none"
|
||||
fillRule="evenodd"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path
|
||||
d="M51.667 32L46 27.952V15l5.667 4.048L57.333 15 63 19.048V32l-5.667-4.048zM51.7 19v13M57.3 28V15"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const { string } = PropTypes;
|
||||
|
||||
MapIcon.defaultProps = {
|
||||
className: null,
|
||||
};
|
||||
|
||||
MapIcon.propTypes = {
|
||||
className: string,
|
||||
};
|
||||
|
||||
export default MapIcon;
|
||||
|
|
@ -73,8 +73,46 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.openMobileMap {
|
||||
/* Font */
|
||||
@apply --marketplaceH4FontStyles;
|
||||
|
||||
/* Positioning */
|
||||
position: fixed;
|
||||
bottom: 48px;
|
||||
left: 50vw;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
/* Button's layout */
|
||||
padding: 12px 24px;
|
||||
background-color: var(--matterColorLight);
|
||||
border-radius: 24px;
|
||||
border: 0;
|
||||
box-shadow: var(--boxShadow);
|
||||
cursor: pointer;
|
||||
transition: var(--transitionStyleButton);
|
||||
|
||||
& .openMobileMapIcon {
|
||||
stroke: var(--matterColor);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--marketplaceColor);
|
||||
box-shadow: var(--boxShadowPopupLight);
|
||||
|
||||
& .openMobileMapIcon {
|
||||
stroke: var(--marketplaceColor);
|
||||
}
|
||||
}
|
||||
|
||||
@media (--viewportMedium) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.mapPanel {
|
||||
display: none;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
/**
|
||||
|
|
@ -94,6 +132,8 @@
|
|||
}
|
||||
|
||||
.map {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
/* Map is fixed so that it doesn't scroll along search results */
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import { getListingsById } from '../../ducks/marketplaceData.duck';
|
|||
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
|
||||
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
|
||||
import { SearchMap, ModalInMobile, PageLayout, SearchResultsPanel, Topbar } from '../../components';
|
||||
|
||||
import { searchListings, searchMapListings } from './SearchPage.duck';
|
||||
import MapIcon from './MapIcon';
|
||||
import css from './SearchPage.css';
|
||||
|
||||
// Pagination page size might need to be dynamic on responsive page layouts
|
||||
|
|
@ -24,6 +26,7 @@ const RESULT_PAGE_SIZE = 12;
|
|||
const SHARETRIBE_API_MAX_PAGE_SIZE = 100;
|
||||
const MAX_SEARCH_RESULT_PAGES_ON_MAP = 5; // 100 * 5 = 500 listings are shown on a map.
|
||||
const DEBOUNCE_MAP_BOUNDS_CHANGE = 500; // bounds_change event is fired too often while dragging
|
||||
const MODAL_BREAKPOINT = 768; /* Search is in modal on mobile layout */
|
||||
|
||||
const pickSearchParamsOnly = params => {
|
||||
const { address, origin, bounds } = params || {};
|
||||
|
|
@ -34,11 +37,16 @@ export class SearchPageComponent extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isSearchMapOpenOnMobile: false,
|
||||
};
|
||||
|
||||
// Initiating map creates 'bounds_changes' event
|
||||
// we listen to that event to make new searches
|
||||
// So, if the search comes from location search input,
|
||||
// we need to by pass 2nd search created by initial 'bounds_changes' event
|
||||
this.useLocationSearchBounds = true;
|
||||
this.modalOpenedBoundsChange = false;
|
||||
|
||||
this.onBoundsChanged = debounce(this.onBoundsChanged.bind(this), DEBOUNCE_MAP_BOUNDS_CHANGE);
|
||||
this.fetchMoreListingsToMap = this.fetchMoreListingsToMap.bind(this);
|
||||
|
|
@ -71,9 +79,10 @@ export class SearchPageComponent extends Component {
|
|||
latlngBounds: ['bounds'],
|
||||
});
|
||||
|
||||
// If boundsChanged url param is given or original location search is rendered once,
|
||||
// If boundsChanged url param is given (and we have not just opened mobile map modal)
|
||||
// or original location search is rendered once,
|
||||
// we start to react to 'bounds_changed' event by generating new searches
|
||||
if (boundsChanged || (!this.useLocationSearchBounds && !boundsChanged)) {
|
||||
if ((boundsChanged && !this.modalOpenedBoundsChange) || !this.useLocationSearchBounds) {
|
||||
const viewportBounds = googleMap.getBounds();
|
||||
const bounds = googleBoundsToSDKBounds(viewportBounds);
|
||||
const origin = googleLatLngToSDKLatLng(viewportBounds.getCenter());
|
||||
|
|
@ -82,6 +91,7 @@ export class SearchPageComponent extends Component {
|
|||
history.push(createResourceLocatorString('SearchPage', flattenedRoutes, {}, searchParams));
|
||||
} else {
|
||||
this.useLocationSearchBounds = false;
|
||||
this.modalOpenedBoundsChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,6 +199,18 @@ export class SearchPageComponent extends Component {
|
|||
</h2>
|
||||
);
|
||||
|
||||
const searchMap = (
|
||||
<SearchMap
|
||||
bounds={bounds}
|
||||
center={origin}
|
||||
listings={mapListings || []}
|
||||
onBoundsChanged={this.onBoundsChanged}
|
||||
isOpenOnModal={this.state.isSearchMapOpenOnMobile}
|
||||
/>
|
||||
);
|
||||
const showSearchMapInMobile = this.state.isSearchMapOpenOnMobile ? searchMap : null;
|
||||
const searchMapMaybe = window.innerWidth < MODAL_BREAKPOINT ? showSearchMapInMobile : searchMap;
|
||||
|
||||
const searchParamsForPagination = parse(location.search);
|
||||
|
||||
return (
|
||||
|
|
@ -227,20 +249,28 @@ export class SearchPageComponent extends Component {
|
|||
search={searchParamsForPagination}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className={css.openMobileMap}
|
||||
onClick={() => {
|
||||
this.useLocationSearchBounds = true;
|
||||
this.modalOpenedBoundsChange = true;
|
||||
this.setState({ isSearchMapOpenOnMobile: true });
|
||||
}}
|
||||
>
|
||||
<MapIcon className={css.openMobileMapIcon} />
|
||||
<FormattedMessage id="SearchPage.openMapView" />
|
||||
</button>
|
||||
</div>
|
||||
<ModalInMobile
|
||||
className={css.mapPanel}
|
||||
id="SearchPage.map"
|
||||
isModalOpenOnMobile={false}
|
||||
isModalOpenOnMobile={this.state.isSearchMapOpenOnMobile}
|
||||
onClose={() => this.setState({ isSearchMapOpenOnMobile: false })}
|
||||
showAsModalMaxWidth={MODAL_BREAKPOINT}
|
||||
onManageDisableScrolling={onManageDisableScrolling}
|
||||
>
|
||||
<div className={css.map}>
|
||||
<SearchMap
|
||||
bounds={bounds}
|
||||
center={origin}
|
||||
listings={mapListings || []}
|
||||
onBoundsChanged={this.onBoundsChanged}
|
||||
/>
|
||||
{searchMapMaybe}
|
||||
</div>
|
||||
</ModalInMobile>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -61,11 +61,21 @@ exports[`SearchPageComponent matches snapshot 1`] = `
|
|||
rootClassName={null}
|
||||
search={Object {}} />
|
||||
</div>
|
||||
<button
|
||||
onClick={[Function]}>
|
||||
<MapIcon
|
||||
className={null} />
|
||||
<FormattedMessage
|
||||
id="SearchPage.openMapView"
|
||||
values={Object {}} />
|
||||
</button>
|
||||
</div>
|
||||
<withViewport(ModalInMobileComponent)
|
||||
id="SearchPage.map"
|
||||
isModalOpenOnMobile={false}
|
||||
onManageDisableScrolling={[Function]}>
|
||||
onClose={[Function]}
|
||||
onManageDisableScrolling={[Function]}
|
||||
showAsModalMaxWidth={768}>
|
||||
<div>
|
||||
<SearchMapComponent
|
||||
bounds={null}
|
||||
|
|
@ -76,6 +86,7 @@ exports[`SearchPageComponent matches snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
className=""
|
||||
isOpenOnModal={false}
|
||||
listings={Array []}
|
||||
mapRootClassName={null}
|
||||
onBoundsChanged={[Function]}
|
||||
|
|
|
|||
|
|
@ -251,6 +251,7 @@
|
|||
"SearchPage.loadingResults": "Loading search results…",
|
||||
"SearchPage.loadingResults": "Loading search results…",
|
||||
"SearchPage.noResults": "Could not find any listings.",
|
||||
"SearchPage.openMapView": "Map view",
|
||||
"SearchPage.searchError": "Search failed. Please try again.",
|
||||
"SearchResultsPanel.nextPage": "Next page",
|
||||
"SearchResultsPanel.previousPage": "Previous page",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue