Skip to content

Commit

Permalink
feat(Map): add option to fit map to address as default view (#158)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `center` and `zoom` props have been deprecated in favour of `defaultArea` that supports also fitting map boundaries based on address lookup
  • Loading branch information
eszthoff authored Aug 15, 2019
1 parent 4741f9a commit aefc9e7
Show file tree
Hide file tree
Showing 9 changed files with 704 additions and 755 deletions.
1,285 changes: 576 additions & 709 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@
"homepage": "https://github.com/textkernel/oneui#readme",
"dependencies": {
"@babel/polyfill": "7.0.0",
"@react-google-maps/api": "1.4.2",
"@react-google-maps/api": "1.5.4",
"css-vars-ponyfill": "1.16.4",
"prop-types": "^15.7.2",
"rc-slider": "8.6.13",
"downshift": "3.2.10",
"fast-memoize": "2.5.1",
"prop-types": "^15.7.2",
"rc-slider": "8.6.13",
"react-modal": "3.9.1"
},
"peerDependencies": {
Expand Down
7 changes: 6 additions & 1 deletion src/__mocks__/googleApiMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const EMPTY_CLASS = class {};
export const fitBoundsMock = jest.fn();
export const setZoomMock = jest.fn();
export const setCenterMock = jest.fn();
export const geocodeMock = jest.fn();

export class Map {
fitBounds() {
Expand Down Expand Up @@ -312,7 +313,11 @@ export const google = {
ZERO_RESULTS: 'ZERO_RESULTS',
},
},
Geocoder: class {},
Geocoder: class {
geocode(req, cb) {
geocodeMock(req, cb);
}
},
GeocoderStatus: {
ERROR: 'ERROR',
INVALID_REQUEST: 'INVALID_REQUEST',
Expand Down
53 changes: 35 additions & 18 deletions src/components/Map/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const circleOptions = radius => ({
});

const Map = React.forwardRef((props, ref) => {
const { center, zoom, markers, mapContainerStyle, ...rest } = props;
const { defaultArea, markers, mapContainerStyle, ...rest } = props;
const mapRef = ref || React.createRef();

const fitBounds = React.useCallback(() => {
Expand All @@ -40,20 +40,29 @@ const Map = React.forwardRef((props, ref) => {
}
map.fitBounds(bounds);
});
} else if (defaultArea.address) {
const { Geocoder } = window.google.maps;
const geocoder = new Geocoder();

geocoder.geocode({ address: defaultArea.address }, (result, status) => {
if (status === 'OK') {
map.fitBounds(result[0].geometry.viewport);
} else {
// TODO: add error handling
}
});
} else {
map.setCenter(center);
map.setZoom(zoom);
map.setCenter(defaultArea.center);
map.setZoom(defaultArea.zoom);
}
}, [center, mapRef, markers, zoom]);
}, [defaultArea, mapRef, markers]);

useEffect(fitBounds);

return (
<GoogleMap
ref={mapRef}
onLoad={fitBounds}
center={center}
zoom={zoom}
mapContainerStyle={mapContainerStyle}
options={{
fullscreenControl: false,
Expand Down Expand Up @@ -87,16 +96,22 @@ const Map = React.forwardRef((props, ref) => {
Map.displayName = 'Map';

Map.propTypes = {
/** The default center of the map to be used if no markers are present */
center: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.number),
/** The default parameters to determine the viewport when no markers are present. */
defaultArea: PropTypes.oneOfType([
PropTypes.shape({
address: PropTypes.string,
}),
PropTypes.shape({
lng: PropTypes.number.isRequired,
lat: PropTypes.number.isRequired,
center: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.number),
PropTypes.shape({
lng: PropTypes.number.isRequired,
lat: PropTypes.number.isRequired,
}),
]),
zoom: PropTypes.number,
}),
]),
/** The default zoom of the map to be used if no markers are present */
zoom: PropTypes.number,
/** The markers to be shown on the map. When present, map will zoom automatically to display them
* The radius is in meters on the Earth's surface.
*/
Expand All @@ -110,7 +125,7 @@ Map.propTypes = {
})
),
/** The style of the map container. It has to have explicit width and height (requirement from Google).
* Altenatively you can set explicit size on the parent container, then, by default, the map will scale to match that
* Alternatively you can set explicit size on the parent container, then, by default, the map will scale to match that
*/
mapContainerStyle: PropTypes.shape({
width: PropTypes.string.isRequired,
Expand All @@ -119,11 +134,13 @@ Map.propTypes = {
};

Map.defaultProps = {
center: {
lat: 52.3922288,
lng: 4.9338793,
defaultArea: {
center: {
lat: 52.3922288,
lng: 4.9338793,
},
zoom: 7,
},
zoom: 7,
markers: [],
mapContainerStyle: {
height: '100%',
Expand Down
4 changes: 2 additions & 2 deletions src/components/Map/MapWithGoogleLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ MapWithGoogleLoader.propTypes = {
* For available values see: https://developers.google.com/maps/faq#languagesupport
*/
language: PropTypes.string,
/** Regonal setting for the map. By default Google uses US.
* For adetails see: https://developers.google.com/maps/documentation/javascript/localization#Region
/** Regional setting for the map. By default Google uses US.
* For details see: https://developers.google.com/maps/documentation/javascript/localization#Region
*/
region: PropTypes.string,
/** other props to pass to the google loader. For details see: https://react-google-maps-api-docs.netlify.com/#loadscriptnext */
Expand Down
44 changes: 44 additions & 0 deletions src/components/Map/__mocks__/geocodeResponse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"results": [
{
"address_components": [
{
"long_name": "Netherlands",
"short_name": "NL",
"types": ["country", "political"]
}
],
"formatted_address": "Netherlands",
"geometry": {
"bounds": {
"northeast": {
"lat": 53.6316,
"lng": 7.227510199999999
},
"southwest": {
"lat": 50.75038379999999,
"lng": 3.3316001
}
},
"location": {
"lat": 52.132633,
"lng": 5.291265999999999
},
"location_type": "APPROXIMATE",
"viewport": {
"northeast": {
"lat": 53.6756,
"lng": 7.227140500000001
},
"southwest": {
"lat": 50.7503837,
"lng": 3.3316
}
}
},
"place_id": "ChIJu-SH28MJxkcRnwq9_851obM",
"types": ["country", "political"]
}
],
"status": "OK"
}
21 changes: 14 additions & 7 deletions src/components/Map/__tests__/Map.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import stabGoogleApi, {
fitBoundsMock,
setZoomMock,
setCenterMock,
geocodeMock,
} from '../../../__mocks__/googleApiMock';
import geocodeResponse from '../__mocks__/geocodeResponse.json';
import Map from '../Map';

stabGoogleApi();

describe('<MapRendered/> that renders a Map with markers', () => {
describe('<Map/> that renders a Map with markers', () => {
const regionMarker = {
center: {
lat: 52.3922288,
Expand All @@ -30,9 +32,16 @@ describe('<MapRendered/> that renders a Map with markers', () => {
});
it('should set center and zoom when rendered with default props', () => {
mount(<Map />);
// call on init Map and once onLoad
expect(setZoomMock).toHaveBeenCalledTimes(2);
expect(setCenterMock).toHaveBeenCalledTimes(2);
expect(setZoomMock).toHaveBeenCalledTimes(1);
expect(setCenterMock).toHaveBeenCalledTimes(1);
});
it('should fit bounds when rendered with defaultArea as address', () => {
geocodeMock.mockImplementationOnce((req, cb) => {
cb(geocodeResponse.results, geocodeResponse.status);
});
mount(<Map defaultArea={{ address: 'nl' }} />);
expect(geocodeMock).toHaveBeenCalledTimes(1);
expect(fitBoundsMock).toHaveBeenCalledTimes(1);
});
it('should render with markers', () => {
const wrapper = mount(<Map markers={[pointMarker, regionMarker]} />);
Expand All @@ -53,10 +62,8 @@ describe('<MapRendered/> that renders a Map with markers', () => {
});
it('should fit to default center and zoom if markers removed', () => {
const wrapper = mount(<Map markers={[pointMarker, regionMarker]} />);
wrapper.setProps({ markers: [] });
expect(setZoomMock).toHaveBeenCalledTimes(1);
expect(setCenterMock).toHaveBeenCalledTimes(1);
wrapper.setProps({ markers: [] });
expect(setZoomMock).toHaveBeenCalledTimes(2);
expect(setCenterMock).toHaveBeenCalledTimes(2);
});
});
19 changes: 7 additions & 12 deletions src/components/Map/__tests__/__snapshots__/Map.spec.js.snap
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<MapRendered/> that renders a Map with markers should render with default props 1`] = `
exports[`<Map/> that renders a Map with markers should render with default props 1`] = `
<Map
center={
defaultArea={
Object {
"lat": 52.3922288,
"lng": 4.9338793,
"center": Object {
"lat": 52.3922288,
"lng": 4.9338793,
},
"zoom": 7,
}
}
mapContainerStyle={
Expand All @@ -15,15 +18,8 @@ exports[`<MapRendered/> that renders a Map with markers should render with defau
}
}
markers={Array []}
zoom={7}
>
<GoogleMap
center={
Object {
"lat": 52.3922288,
"lng": 4.9338793,
}
}
mapContainerStyle={
Object {
"height": "100%",
Expand All @@ -39,7 +35,6 @@ exports[`<MapRendered/> that renders a Map with markers should render with defau
"streetViewControl": false,
}
}
zoom={7}
>
<div
style={
Expand Down
20 changes: 17 additions & 3 deletions stories/Map.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import { withKnobs, select, text } from '@storybook/addon-knobs';
import { withKnobs, select, text, boolean, number } from '@storybook/addon-knobs';
import { MapWithGoogleLoader } from '@textkernel/oneui';

storiesOf('Atoms|Map', module)
.addDecorator(withKnobs)
.addDecorator(withInfo)
.add(
'Map',
() => {
Expand Down Expand Up @@ -41,6 +39,22 @@ storiesOf('Atoms|Map', module)
<MapWithGoogleLoader
apiKey=""
markers={select('Markers', markers, [defaultMarker])}
defaultArea={
boolean('Use address to set default area', true)
? {
address: text(
'Address to fit map to when no markers are present',
'NL'
),
}
: {
center: {
lat: number('Default center latitude', 52.3922288),
lng: number('Default center longitude', 4.9338793),
},
zoom: number('Default zoom', 7),
}
}
/>
</div>
);
Expand Down

0 comments on commit aefc9e7

Please sign in to comment.