diff --git a/docs/api-reference/components/map-control.md b/docs/api-reference/components/map-control.md index 87a6fcb3..3ca1b6e1 100644 --- a/docs/api-reference/components/map-control.md +++ b/docs/api-reference/components/map-control.md @@ -45,5 +45,14 @@ const App = () => ( 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. +### Optional + +#### `className`: string + +A CSS class name applied to the container element that wraps the control content +on the map. This is useful for styling or targeting the control from outside, +since the children are rendered into a container that is managed by the Maps +JavaScript API rather than directly into the React tree. + [gmp-custom-ctrl]: https://developers.google.com/maps/documentation/javascript/controls#CustomControls [gmp-ctrl-pos]: https://developers.google.com/maps/documentation/javascript/controls#ControlPositioning diff --git a/src/components/__tests__/map-control.test.tsx b/src/components/__tests__/map-control.test.tsx index 01f20a37..7e89c691 100644 --- a/src/components/__tests__/map-control.test.tsx +++ b/src/components/__tests__/map-control.test.tsx @@ -39,3 +39,53 @@ test('control is added to the map', () => { const [controlEl] = (controlsArray.push as jest.Mock).mock.calls[0]; expect(controlEl).toHaveTextContent('control button'); }); + +test('className prop is applied to the control container', () => { + render( + + + + ); + + const controlsArray = mapInstance.controls[ControlPosition.BOTTOM_CENTER]; + const [controlEl] = (controlsArray.push as jest.Mock).mock.calls[0]; + + expect(controlEl).toHaveClass('custom-control'); +}); + +test('className prop updates are reflected on the control container', () => { + const {rerender} = render( + + + + ); + + const controlsArray = mapInstance.controls[ControlPosition.BOTTOM_CENTER]; + const [controlEl] = (controlsArray.push as jest.Mock).mock.calls[0]; + + expect(controlEl).toHaveClass('initial-class'); + + rerender( + + + + ); + + expect(controlEl).toHaveClass('updated-class'); + expect(controlEl).not.toHaveClass('initial-class'); + + rerender( + + + + ); + + expect(controlEl).not.toHaveClass('updated-class'); + expect(controlEl).not.toHaveClass('initial-class'); +}); diff --git a/src/components/map-control.tsx b/src/components/map-control.tsx index f875142f..d5915136 100644 --- a/src/components/map-control.tsx +++ b/src/components/map-control.tsx @@ -6,6 +6,7 @@ import type {PropsWithChildren} from 'react'; type MapControlProps = PropsWithChildren<{ position: ControlPosition; + className?: string; }>; /** @@ -48,11 +49,17 @@ export type ControlPosition = export const MapControl: FunctionComponent = ({ children, - position + position, + className }) => { const controlContainer = useMemo(() => document.createElement('div'), []); const map = useMap(); + useEffect(() => { + // eslint-disable-next-line react-hooks/immutability -- the control container DOM node is intentionally mutated from effects + controlContainer.className = className ?? ''; + }, [controlContainer, className]); + useEffect(() => { if (!map) return;