Skip to content

Commit

Permalink
feat: new MapControl component
Browse files Browse the repository at this point in the history
The MapControl component allows users to add custom react components to the controls of a Map instance.
  • Loading branch information
usefulthink committed Nov 3, 2023
1 parent 9fa403b commit 110247b
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/components/__tests__/map-control.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import '@testing-library/jest-dom';

import React, {ReactElement} from 'react';
import {initialize} from '@googlemaps/jest-mocks';
import {cleanup, render} from '@testing-library/react';

import {APIProvider} from '../api-provider';
import {Map} from '../map';
import {ControlPosition, MapControl} from '../map-control';
import {waitForMockInstance} from './__utils__/wait-for-mock-instance';

jest.mock('../../libraries/google-maps-api-loader');

let wrapper: ({children}: {children: React.ReactNode}) => ReactElement | null;

beforeEach(() => {
initialize();

wrapper = ({children}: {children: React.ReactNode}) => (
<APIProvider apiKey={'apikey'}>
<Map zoom={10} center={{lat: 0, lng: 0}}>
{children}
</Map>
</APIProvider>
);
});

afterEach(() => {
cleanup();
jest.restoreAllMocks();
});

test('control is added to the map', async () => {
render(
<MapControl position={ControlPosition.TOP_LEFT}>
<button>control button</button>
</MapControl>,
{wrapper}
);

const map = await waitForMockInstance(google.maps.Map);
const controlsArray = map.controls[google.maps.ControlPosition.TOP_LEFT];

expect(controlsArray.push).toHaveBeenCalled();

const [controlEl] = (controlsArray.push as jest.Mock).mock.calls[0];
expect(controlEl).toHaveTextContent('control button');
});
61 changes: 61 additions & 0 deletions src/components/map-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {useEffect, useMemo} from 'react';
import {createPortal} from 'react-dom';
import {useMap} from '../hooks/use-map';

import type {PropsWithChildren} from 'react';

type MapControlProps = PropsWithChildren<{
position: ControlPosition;
}>;

/**
* Names of the `google.maps.ControlPosition` constants.
* They have to be duplicated here since we can't wait for the maps API to load.
*/
export enum ControlPosition {
BLOCK_END_INLINE_CENTER = 'BLOCK_END_INLINE_CENTER',
BLOCK_END_INLINE_END = 'BLOCK_END_INLINE_END',
BLOCK_END_INLINE_START = 'BLOCK_END_INLINE_START',
BLOCK_START_INLINE_CENTER = 'BLOCK_START_INLINE_CENTER',
BLOCK_START_INLINE_END = 'BLOCK_START_INLINE_END',
BLOCK_START_INLINE_START = 'BLOCK_START_INLINE_START',
BOTTOM_CENTER = 'BOTTOM_CENTER',
BOTTOM_LEFT = 'BOTTOM_LEFT',
BOTTOM_RIGHT = 'BOTTOM_RIGHT',
INLINE_END_BLOCK_CENTER = 'INLINE_END_BLOCK_CENTER',
INLINE_END_BLOCK_END = 'INLINE_END_BLOCK_END',
INLINE_END_BLOCK_START = 'INLINE_END_BLOCK_START',
INLINE_START_BLOCK_CENTER = 'INLINE_START_BLOCK_CENTER',
INLINE_START_BLOCK_END = 'INLINE_START_BLOCK_END',
INLINE_START_BLOCK_START = 'INLINE_START_BLOCK_START',
LEFT_BOTTOM = 'LEFT_BOTTOM',
LEFT_CENTER = 'LEFT_CENTER',
LEFT_TOP = 'LEFT_TOP',
RIGHT_BOTTOM = 'RIGHT_BOTTOM',
RIGHT_CENTER = 'RIGHT_CENTER',
RIGHT_TOP = 'RIGHT_TOP',
TOP_CENTER = 'TOP_CENTER',
TOP_LEFT = 'TOP_LEFT',
TOP_RIGHT = 'TOP_RIGHT'
}

export const MapControl = ({children, position}: MapControlProps) => {
const controlContainer = useMemo(() => document.createElement('div'), []);
const map = useMap();

useEffect(() => {
if (!map) return;

const controlPosition = google.maps.ControlPosition[position];
const controls = map.controls[controlPosition];

controls.push(controlContainer);

return () => {
const index = controls.getArray().indexOf(controlContainer);
controls.removeAt(index);
};
}, [map, position]);

return createPortal(children, controlContainer);
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './components/advanced-marker';
export * from './components/api-provider';
export * from './components/info-window';
export * from './components/map';
export * from './components/map-control';
export * from './components/marker';
export * from './components/pin';
export * from './hooks/use-api-loading-status';
Expand Down

0 comments on commit 110247b

Please sign in to comment.