-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new MapControl component (#51)
The MapControl component allows users to add custom react components to the controls of a Map instance.
- Loading branch information
1 parent
b01fc8b
commit 7eb49ed
Showing
5 changed files
with
159 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# `<MapControl>` Component | ||
|
||
The `MapControl` component can be used to render components into the | ||
control-containers of a map instance. | ||
|
||
The Maps JavaScript API uses a custom layout algorithm for map controls. | ||
While you can add your buttons or whatever controls you need on top of | ||
the map canvas, that isn't much of an option when you need to mix built-in | ||
controls with your own controls. In this case adding your controls to | ||
the map is the best option. | ||
|
||
See [the official documentation on this topic][gmp-custom-ctrl]. | ||
|
||
## Usage | ||
|
||
You can add as many `MapControl` components as you like to any `Map`, multiple | ||
controls for the same position are possible as well. | ||
|
||
```tsx | ||
import { | ||
APIProvider, | ||
ControlPosition, | ||
Map, | ||
MapControl | ||
} from '@vis.gl/react-google-maps'; | ||
|
||
const App = () => ( | ||
<APIProvider apiKey={'...'}> | ||
<Map {...mapProps}> | ||
<MapControl position={ControlPosition.TOP_LEFT}> | ||
.. any component here will be added to the control-containers of the | ||
google map instance .. | ||
</MapControl> | ||
</Map> | ||
</APIProvider> | ||
); | ||
``` | ||
|
||
## Props | ||
|
||
### Required | ||
|
||
#### `position`: ControlPosition | ||
|
||
The position is specified as one of the values of the `ControlPosition` enum, which | ||
is an exact copy of the [`google.maps.ControlPosition`][gmp-ctrl-pos] type. | ||
|
||
[gmp-custom-ctrl]: https://developers.google.com/maps/documentation/javascript/controls#CustomControls | ||
[gmp-ctrl-pos]: https://developers.google.com/maps/documentation/javascript/controls#ControlPositioning |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.BOTTOM_CENTER}> | ||
<button>control button</button> | ||
</MapControl>, | ||
{wrapper} | ||
); | ||
|
||
const map = await waitForMockInstance(google.maps.Map); | ||
const controlsArray = map.controls[ControlPosition.BOTTOM_CENTER]; | ||
|
||
expect(controlsArray.push).toHaveBeenCalled(); | ||
|
||
const [controlEl] = (controlsArray.push as jest.Mock).mock.calls[0]; | ||
expect(controlEl).toHaveTextContent('control button'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
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; | ||
}>; | ||
|
||
/** | ||
* Copy of the `google.maps.ControlPosition` constants. | ||
* They have to be duplicated here since we can't wait for the maps API to load to be able to use them. | ||
*/ | ||
export enum ControlPosition { | ||
BLOCK_END_INLINE_CENTER = 0, | ||
BLOCK_END_INLINE_END = 1, | ||
BLOCK_END_INLINE_START = 2, | ||
BLOCK_START_INLINE_CENTER = 3, | ||
BLOCK_START_INLINE_END = 4, | ||
BLOCK_START_INLINE_START = 5, | ||
BOTTOM_CENTER = 6, | ||
BOTTOM_LEFT = 7, | ||
BOTTOM_RIGHT = 8, | ||
INLINE_END_BLOCK_CENTER = 9, | ||
INLINE_END_BLOCK_END = 10, | ||
INLINE_END_BLOCK_START = 11, | ||
INLINE_START_BLOCK_CENTER = 12, | ||
INLINE_START_BLOCK_END = 13, | ||
INLINE_START_BLOCK_START = 14, | ||
LEFT_BOTTOM = 15, | ||
LEFT_CENTER = 16, | ||
LEFT_TOP = 17, | ||
RIGHT_BOTTOM = 18, | ||
RIGHT_CENTER = 19, | ||
RIGHT_TOP = 20, | ||
TOP_CENTER = 21, | ||
TOP_LEFT = 22, | ||
TOP_RIGHT = 23 | ||
} | ||
|
||
export const MapControl = ({children, position}: MapControlProps) => { | ||
const controlContainer = useMemo(() => document.createElement('div'), []); | ||
const map = useMap(); | ||
|
||
useEffect(() => { | ||
if (!map) return; | ||
|
||
const controls = map.controls[position]; | ||
|
||
controls.push(controlContainer); | ||
|
||
return () => { | ||
const index = controls.getArray().indexOf(controlContainer); | ||
controls.removeAt(index); | ||
}; | ||
}, [map, position]); | ||
|
||
return createPortal(children, controlContainer); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters