Skip to content

Localize the Mapbox map based on the search locale #524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/search-ui-react.mapboxmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A component that renders a map with markers to show result locations using Mapbo
**Signature:**

```typescript
declare function MapboxMap<T>({ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, scrollToResult, markerOptionsOverride, }: MapboxMapProps<T>): JSX.Element;
declare function MapboxMap<T>({ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, onPinClick, markerOptionsOverride, }: MapboxMapProps<T>): JSX.Element;
```

## Parameters
Expand All @@ -32,7 +32,7 @@ Description
</th></tr></thead>
<tbody><tr><td>

{ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, scrollToResult, markerOptionsOverride, }
{ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, onPinClick, markerOptionsOverride, }


</td><td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## MapboxMapProps.markerOptionsOverride property

The options to apply to the map markers based on whether it is selected. By default, the standard Mapbox pin is used. This prop should not be used with [PinComponent](./search-ui-react.mapboxmapprops.pincomponent.md) or with [renderPin](./search-ui-react.mapboxmapprops.renderpin.md)<!-- -->. If either are provided, markerOptionsOverride will be ignored.
The options to apply to the map markers based on whether it is selected.

**Signature:**

Expand Down
20 changes: 10 additions & 10 deletions docs/search-ui-react.mapboxmapprops.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ _(Optional)_ Interface for map customization derived from Mapbox GL's Map option

</td><td>

_(Optional)_ The options to apply to the map markers based on whether it is selected. By default, the standard Mapbox pin is used. This prop should not be used with [PinComponent](./search-ui-react.mapboxmapprops.pincomponent.md) or with [renderPin](./search-ui-react.mapboxmapprops.renderpin.md)<!-- -->. If either are provided, markerOptionsOverride will be ignored.
_(Optional)_ The options to apply to the map markers based on whether it is selected.


</td></tr>
Expand All @@ -170,58 +170,58 @@ _(Optional)_ A function which is called when user drags or zooms the map.
</td></tr>
<tr><td>

[PinComponent?](./search-ui-react.mapboxmapprops.pincomponent.md)
[onPinClick?](./search-ui-react.mapboxmapprops.onpinclick.md)


</td><td>


</td><td>

[PinComponent](./search-ui-react.pincomponent.md)<!-- -->&lt;T&gt;
(result: Result&lt;T&gt; \| undefined) =&gt; void


</td><td>

_(Optional)_ Custom Pin component to render for markers on the map. By default, the built-in marker image from Mapbox GL is used. This prop should not be used with [renderPin](./search-ui-react.mapboxmapprops.renderpin.md)<!-- -->. If both are provided, only PinComponent will be used.
_(Optional)_ A function that handles a pin click event.


</td></tr>
<tr><td>

[renderPin?](./search-ui-react.mapboxmapprops.renderpin.md)
[PinComponent?](./search-ui-react.mapboxmapprops.pincomponent.md)


</td><td>


</td><td>

(props: [PinComponentProps](./search-ui-react.pincomponentprops.md)<!-- -->&lt;T&gt; &amp; { container: HTMLElement; }) =&gt; void
[PinComponent](./search-ui-react.pincomponent.md)<!-- -->&lt;T&gt;


</td><td>

_(Optional)_ Render function for a custom marker on the map. This function takes in an HTML element and is responible for rendering the pin into that element, which will be used as the marker. By default, the built-in marker image from Mapbox GL is used. This prop should not be used with [PinComponent](./search-ui-react.mapboxmapprops.pincomponent.md)<!-- -->. If both are provided, only PinComponent will be used.
_(Optional)_ Custom Pin component to render for markers on the map. By default, the built-in marker image from Mapbox GL is used. This prop should not be used with [renderPin](./search-ui-react.mapboxmapprops.renderpin.md)<!-- -->. If both are provided, only PinComponent will be used.


</td></tr>
<tr><td>

[scrollToResult?](./search-ui-react.mapboxmapprops.scrolltoresult.md)
[renderPin?](./search-ui-react.mapboxmapprops.renderpin.md)


</td><td>


</td><td>

(result: Result&lt;T&gt; \| undefined) =&gt; void
(props: [PinComponentProps](./search-ui-react.pincomponentprops.md)<!-- -->&lt;T&gt; &amp; { container: HTMLElement; }) =&gt; void


</td><td>

_(Optional)_ A function that scrolls to the search result corresponding to the selected pin.
_(Optional)_ Render function for a custom marker on the map. This function takes in an HTML element and is responible for rendering the pin into that element, which will be used as the marker. By default, the built-in marker image from Mapbox GL is used. This prop should not be used with [PinComponent](./search-ui-react.mapboxmapprops.pincomponent.md)<!-- -->. If both are provided, only PinComponent will be used.


</td></tr>
Expand Down
13 changes: 13 additions & 0 deletions docs/search-ui-react.mapboxmapprops.onpinclick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/search-ui-react](./search-ui-react.md) &gt; [MapboxMapProps](./search-ui-react.mapboxmapprops.md) &gt; [onPinClick](./search-ui-react.mapboxmapprops.onpinclick.md)

## MapboxMapProps.onPinClick property

A function that handles a pin click event.

**Signature:**

```typescript
onPinClick?: (result: Result<T> | undefined) => void;
```
13 changes: 0 additions & 13 deletions docs/search-ui-react.mapboxmapprops.scrolltoresult.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/search-ui-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ A React Component which displays and collects location information in order to b
</td></tr>
<tr><td>

[MapboxMap({ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, scrollToResult, markerOptionsOverride, })](./search-ui-react.mapboxmap.md)
[MapboxMap({ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, onPinClick, markerOptionsOverride, })](./search-ui-react.mapboxmap.md)


</td><td>
Expand Down
1 change: 0 additions & 1 deletion docs/search-ui-react.pincomponentprops.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ type PinComponentProps<T> = {
mapbox: mapboxgl.Map;
result: Result<T>;
selected?: boolean;
onClick?: (result: Result<T>) => void;
};
```
5 changes: 2 additions & 3 deletions etc/search-ui-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ export interface LocationBiasProps {
}

// @public
export function MapboxMap<T>({ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, scrollToResult, markerOptionsOverride, }: MapboxMapProps<T>): JSX.Element;
export function MapboxMap<T>({ mapboxAccessToken, mapboxOptions, PinComponent, renderPin, getCoordinate, onDrag, iframeWindow, allowUpdates, onPinClick, markerOptionsOverride, }: MapboxMapProps<T>): JSX.Element;

// @public
export interface MapboxMapProps<T> {
Expand All @@ -502,11 +502,11 @@ export interface MapboxMapProps<T> {
mapboxOptions?: Omit<mapboxgl_2.MapboxOptions, 'container'>;
markerOptionsOverride?: (selected: boolean) => MarkerOptions;
onDrag?: OnDragHandler;
onPinClick?: (result: Result<T> | undefined) => void;
PinComponent?: PinComponent<T>;
renderPin?: (props: PinComponentProps<T> & {
container: HTMLElement;
}) => void;
scrollToResult?: (result: Result<T> | undefined) => void;
}

// @public
Expand Down Expand Up @@ -599,7 +599,6 @@ export type PinComponentProps<T> = {
mapbox: mapboxgl_2.Map;
result: Result<T>;
selected?: boolean;
onClick?: (result: Result<T>) => void;
};

// @public
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@yext/search-ui-react",
"version": "1.8.8",
"version": "1.8.9",
"description": "A library of React Components for powering Yext Search integrations",
"author": "watson@yext.com",
"license": "BSD-3-Clause",
Expand Down
80 changes: 59 additions & 21 deletions src/components/MapboxMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { useRef, useEffect, useState, useCallback } from 'react';
import mapboxgl, { MarkerOptions } from 'mapbox-gl';
import { Result, useSearchState } from '@yext/search-headless-react';
import { useDebouncedFunction } from '../hooks/useDebouncedFunction';
import _ from 'lodash';

import ReactDOM from 'react-dom';

/**
Expand All @@ -18,8 +20,6 @@ export type PinComponentProps<T> = {
result: Result<T>,
/** Where the pin is selected. */
selected?: boolean,
/** A function that handles pin clicks. */
onClick?: (result: Result<T>) => void
};

/**
Expand Down Expand Up @@ -101,15 +101,9 @@ export interface MapboxMapProps<T> {
* Otherwise, the map will not update its options once initially set.
*/
allowUpdates?: boolean,
/** A function that scrolls to the search result corresponding to the selected pin. */
scrollToResult?: (result: Result<T> | undefined) => void,
/**
* The options to apply to the map markers based on whether it is selected.
* By default, the standard Mapbox pin is used.
* This prop should not be used with {@link MapboxMapProps.PinComponent | PinComponent}
* or with {@link MapboxMapProps.renderPin | renderPin}. If either are provided,
* markerOptionsOverride will be ignored.
*/
/** A function that handles a pin click event. */
onPinClick?: (result: Result<T> | undefined) => void,
/** The options to apply to the map markers based on whether it is selected. */
markerOptionsOverride?: (selected: boolean) => MarkerOptions,
}

Expand Down Expand Up @@ -141,7 +135,7 @@ export function MapboxMap<T>({
onDrag,
iframeWindow,
allowUpdates = false,
scrollToResult,
onPinClick,
markerOptionsOverride,
}: MapboxMapProps<T>): JSX.Element {
const mapboxInstance = (iframeWindow as Window & { mapboxgl?: typeof mapboxgl })?.mapboxgl ?? mapboxgl;
Expand All @@ -162,14 +156,22 @@ export function MapboxMap<T>({
}, [])

useEffect(() => {
scrollToResult?.(selectedResult);
onPinClick?.(selectedResult);
}, [selectedResult])

const locale = useSearchState(state => state.meta?.locale);
// keep track of the previous value of mapboxOptions across renders
const prevMapboxOptions = useRef(mapboxOptions);

useEffect(() => {
if (mapContainer.current) {
if (map.current && allowUpdates) {
// Update to existing Map
handleMapboxOptionsUpdates(mapboxOptions, map.current);
// Compare current and previous mapboxOptions using deep equality
if (!_.isEqual(prevMapboxOptions.current, mapboxOptions)) {
// Update to existing Map
handleMapboxOptionsUpdates(mapboxOptions, map.current);
prevMapboxOptions.current = (mapboxOptions);
}
} else if (!map.current && mapboxInstance) {
const options: mapboxgl.MapboxOptions = {
container: mapContainer.current,
Expand Down Expand Up @@ -202,6 +204,33 @@ export function MapboxMap<T>({
}
}, [mapboxOptions, onDragDebounced]);

useEffect(() => {
const mapbox = map.current;
if (!mapbox || !locale) return;

const localizeMap = () => {
mapbox.getStyle().layers.forEach(layer => {
if (layer.type === "symbol" && layer.layout?.["text-field"]) {
mapbox.setLayoutProperty(
layer.id,
"text-field",
[
'coalesce',
['get', `name_${getMapboxLanguage(locale)}`],
['get', 'name']
]
);
}
});
}

if (mapbox.isStyleLoaded()) {
localizeMap();
} else {
mapbox.once("styledata", () => localizeMap())
}
}, [locale]);

useEffect(() => {
if (iframeWindow && map.current) {
map.current.resize();
Expand Down Expand Up @@ -231,26 +260,25 @@ export function MapboxMap<T>({
mapbox={mapbox}
result={result}
selected={selectedResult === result}
onClick={handlePinClick}
/>, el);
markerOptions.element = el;
} else if (renderPin) {
renderPin({ index: i, mapbox, result, container: el });
markerOptions.element = el;
} else if (markerOptionsOverride) {
}

if (markerOptionsOverride) {
markerOptions = {
...markerOptions,
...markerOptionsOverride(selectedResult === result)
}
}

const marker = new mapboxInstance.Marker(markerOptions)
.setLngLat({ lat: latitude, lng: longitude })
.addTo(mapbox);

if (!PinComponent) {
marker?.getElement().addEventListener('click', () => handlePinClick(result));
}

marker?.getElement().addEventListener('click', () => handlePinClick(result));
markers.current.push(marker);
bounds.extend([longitude, latitude]);
}
Expand Down Expand Up @@ -296,3 +324,13 @@ function getDefaultCoordinate<T>(result: Result<T>): Coordinate | undefined {
}
return yextDisplayCoordinate;
}

export function getMapboxLanguage(locale: string) {
try {
const localeOptions = new Intl.Locale(locale.replaceAll('_', '-'));
return localeOptions.script ? `${localeOptions.language}-${localeOptions.script}` : localeOptions.language;
} catch (e) {
console.warn(`Locale "${locale}" is not supported.`)
}
return 'en';
}
3 changes: 2 additions & 1 deletion test-site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading