Skip to content

Commit 1008a39

Browse files
authored
docs: add places ui kit example (#763)
* docs: add places ui kit example * refactor: tweaks * docs: add a few comments * refactor: disable poi clicking
1 parent 42c4c7e commit 1008a39

14 files changed

+740
-1
lines changed

examples/places-ui-kit/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Places Ui Kit Example
2+
3+
![image](https://user-images.githubusercontent.com/39244966/208682692-d5b23518-9e51-4a87-8121-29f71e41c777.png)
4+
5+
This is an example to show how to setup the Google Places UI Kit (Preview) webcomponents
6+
7+
## Enable the APIs
8+
9+
To use the Places UI Kit you need to enable the [API][places-ui-kit] in the Cloud Console.
10+
Additionally for the elevation component you need the [Elevation API][elevation-api]
11+
12+
## Google Maps Platform API Key
13+
14+
This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform.
15+
See [the official documentation][get-api-key] on how to create and configure your own key.
16+
17+
The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a
18+
file named `.env` in the example directory with the following content:
19+
20+
```shell title=".env"
21+
GOOGLE_MAPS_API_KEY="<YOUR API KEY HERE>"
22+
```
23+
24+
If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets)
25+
26+
## Development
27+
28+
Go into the example-directory and run
29+
30+
```shell
31+
npm install
32+
```
33+
34+
To start the example with the local library run
35+
36+
```shell
37+
npm run start-local
38+
```
39+
40+
The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example)
41+
42+
[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key
43+
[places-ui-kit]: https://console.cloud.google.com/apis/library/placewidgets.googleapis.com
44+
[elevation-api]: https://console.cloud.google.com/marketplace/product/google/elevation-backend.googleapis.com

examples/places-ui-kit/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1.0, user-scalable=no" />
8+
<title>Places UI Kit Example</title>
9+
<meta name="description" content="Places UI Kit Example" />
10+
<style>
11+
body {
12+
margin: 0;
13+
font-family: sans-serif;
14+
}
15+
#app {
16+
width: 100vw;
17+
height: 100vh;
18+
}
19+
</style>
20+
</head>
21+
<body>
22+
<div id="app"></div>
23+
<script type="module">
24+
import '@vis.gl/react-google-maps/examples.css';
25+
import '@vis.gl/react-google-maps/examples.js';
26+
import {renderToDom} from './src/app';
27+
28+
renderToDom(document.querySelector('#app'));
29+
</script>
30+
</body>
31+
</html>

examples/places-ui-kit/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"type": "module",
3+
"dependencies": {
4+
"@vis.gl/react-google-maps": "latest",
5+
"react": "^19.0.0",
6+
"react-dom": "^19.0.0",
7+
"vite": "^6.0.11"
8+
},
9+
"scripts": {
10+
"start": "vite",
11+
"start-local": "vite --config ../vite.config.local.js",
12+
"build": "vite build"
13+
}
14+
}

examples/places-ui-kit/src/app.tsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React, {useState, useMemo} from 'react';
2+
import {createRoot} from 'react-dom/client';
3+
import {APIProvider, Map} from '@vis.gl/react-google-maps';
4+
5+
import {PlaceListWebComponent} from './components/place-list-webcomponent';
6+
import {PlaceDetailsMarker} from './components/place-details-marker';
7+
import {SearchBar} from './components/search-bar';
8+
9+
import ControlPanel from './control-panel';
10+
11+
import './styles.css';
12+
13+
const API_KEY =
14+
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string);
15+
16+
if (!API_KEY) {
17+
console.error('Missing Google Maps API key');
18+
}
19+
20+
export type PlaceType =
21+
| 'restaurant'
22+
| 'cafe'
23+
| 'electric_vehicle_charging_station'
24+
| null;
25+
26+
export type DetailsSize = 'SMALL' | 'MEDIUM' | 'LARGE' | 'X_LARGE';
27+
28+
const MAP_CONFIG = {
29+
defaultZoom: 15,
30+
defaultCenter: {lat: 53.55, lng: 9.99},
31+
mapId: '49ae42fed52588c3',
32+
gestureHandling: 'greedy' as const,
33+
disableDefaultUI: true,
34+
clickableIcons: false
35+
};
36+
37+
const App = () => {
38+
const [places, setPlaces] = useState<google.maps.places.Place[]>([]);
39+
const [selectedPlaceId, setSelectedPlaceId] = useState<string | null>(null);
40+
const [locationId, setLocationId] = useState<string | null>(null);
41+
const [placeType, setPlaceType] = useState<PlaceType>('restaurant');
42+
const [detailsSize, setDetailsSize] = useState<DetailsSize>('MEDIUM');
43+
44+
// Memoize the place markers to prevent unnecessary re-renders
45+
// Only recreate when places, selection, or details size changes
46+
const placeMarkers = useMemo(() => {
47+
return places.map((place, index) => (
48+
<PlaceDetailsMarker
49+
detailsSize={detailsSize}
50+
key={place.id || index}
51+
selected={place.id === selectedPlaceId}
52+
place={place}
53+
onClick={() => setSelectedPlaceId(place.id)}
54+
/>
55+
));
56+
}, [places, selectedPlaceId, detailsSize]);
57+
58+
return (
59+
// APIProvider sets up the Google Maps JavaScript API with the specified key
60+
// Using 'alpha' version to access the latest features including UI Kit components
61+
<APIProvider apiKey={API_KEY} version="alpha">
62+
<div className="places-ui-kit">
63+
<div className="place-list-wrapper">
64+
{/*
65+
PlaceListWebComponent displays a list of places based on:
66+
- The selected place type (restaurant, cafe, etc.)
67+
- The current map location and bounds
68+
*/}
69+
<PlaceListWebComponent
70+
placeType={placeType}
71+
locationId={locationId}
72+
setPlaces={setPlaces}
73+
onPlaceSelect={place => setSelectedPlaceId(place?.id ?? null)}
74+
/>
75+
</div>
76+
77+
<div className="map-container">
78+
{/*
79+
The Map component renders the Google Map
80+
Clicking on the map background will deselect any selected place
81+
*/}
82+
<Map {...MAP_CONFIG} onClick={() => setSelectedPlaceId(null)}>
83+
{placeMarkers}
84+
</Map>
85+
86+
{/*
87+
SearchBar allows users to:
88+
- Select the type of place they want to find
89+
- Search for a specific location to center the map on
90+
*/}
91+
<SearchBar
92+
placeType={placeType}
93+
setPlaceType={setPlaceType}
94+
setLocationId={setLocationId}
95+
/>
96+
97+
{/*
98+
ControlPanel provides UI controls for adjusting the size of place details
99+
displayed in the InfoWindow
100+
*/}
101+
<ControlPanel
102+
detailsSize={detailsSize}
103+
onDetailSizeChange={setDetailsSize}
104+
/>
105+
</div>
106+
</div>
107+
</APIProvider>
108+
);
109+
};
110+
export default App;
111+
112+
// Helper function to render the app into a DOM container
113+
export function renderToDom(container: HTMLElement) {
114+
const root = createRoot(container);
115+
116+
root.render(
117+
<React.StrictMode>
118+
<App />
119+
</React.StrictMode>
120+
);
121+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, {useCallback} from 'react';
2+
import {useMap, useMapsLibrary} from '@vis.gl/react-google-maps';
3+
4+
interface Props {
5+
onPlaceSelect: (place: google.maps.places.Place | null) => void;
6+
}
7+
8+
// Type definitions for the events emitted by the gmp-place-autocomplete component
9+
interface GmpSelectEvent {
10+
placePrediction: {
11+
toPlace: () => google.maps.places.Place;
12+
};
13+
}
14+
15+
interface GmpPlaceSelectEvent {
16+
place: google.maps.places.Place;
17+
}
18+
19+
export const AutocompleteWebComponent = ({onPlaceSelect}: Props) => {
20+
// Load the places library to ensure the web component is available
21+
useMapsLibrary('places');
22+
23+
const map = useMap();
24+
25+
// Handle the selection of a place from the autocomplete component
26+
// This fetches additional place details and adjusts the map view
27+
const handlePlaceSelect = useCallback(
28+
async (place: google.maps.places.Place) => {
29+
try {
30+
// Fetch location and viewport data for the selected place
31+
await place.fetchFields({
32+
fields: ['location', 'viewport']
33+
});
34+
35+
// If the place has a viewport (area boundaries), adjust the map to show it
36+
if (place?.viewport) {
37+
map?.fitBounds(place.viewport);
38+
}
39+
40+
onPlaceSelect(place);
41+
} catch (error) {
42+
console.error('Error fetching place fields:', error);
43+
onPlaceSelect(null);
44+
}
45+
},
46+
[map, onPlaceSelect]
47+
);
48+
49+
// Handle the gmp-select event (used in alpha and future stable versions)
50+
const handleGmpSelect = useCallback(
51+
(event: GmpSelectEvent) => {
52+
try {
53+
// Convert the place prediction to a full Place object
54+
void handlePlaceSelect(event.placePrediction.toPlace());
55+
} catch (error) {
56+
console.error('Error handling gmp-select event:', error);
57+
onPlaceSelect(null);
58+
}
59+
},
60+
[handlePlaceSelect, onPlaceSelect]
61+
);
62+
63+
// Handle the gmp-placeselect event (deprecated but used in beta channel)
64+
const handleGmpPlaceSelect = useCallback(
65+
(event: GmpPlaceSelectEvent) => {
66+
void handlePlaceSelect(event.place);
67+
},
68+
[handlePlaceSelect]
69+
);
70+
71+
// Note: This is a React 19 thing to be able to treat custom elements this way.
72+
// In React before v19, you'd have to use a ref, or use the PlaceAutocompleteElement
73+
// constructor instead.
74+
return (
75+
<div className="autocomplete-container">
76+
{/*
77+
gmp-place-autocomplete is a Google Maps Web Component that provides a search box
78+
with automatic place suggestions as the user types.
79+
80+
It supports two event types for backward compatibility:
81+
- ongmp-select: Used in alpha and future stable versions
82+
- ongmp-placeselect: Deprecated but still used in beta channel
83+
*/}
84+
<gmp-place-autocomplete
85+
ongmp-select={handleGmpSelect as any}
86+
ongmp-placeselect={handleGmpPlaceSelect as any}
87+
aria-label="Search for a location"
88+
/>
89+
</div>
90+
);
91+
};
92+
93+
declare module 'react' {
94+
namespace JSX {
95+
interface IntrinsicElements {
96+
'gmp-place-autocomplete': React.DetailedHTMLProps<
97+
React.HTMLAttributes<google.maps.places.PlaceAutocompleteElement>,
98+
google.maps.places.PlaceAutocompleteElement
99+
>;
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)