From 429b0189855aeb4132c231aa237bcadf9b551f5b Mon Sep 17 00:00:00 2001 From: Youssef Henna Date: Mon, 15 May 2023 19:35:58 +0300 Subject: [PATCH] Updated how markers & callouts are rendered to maintain types + added default callout for title/description --- packages/maps/src/components/MapCallout.tsx | 26 +++++-- packages/maps/src/components/MapMarker.tsx | 84 ++++++++++++++++++--- packages/maps/src/components/MapView.tsx | 48 +++++++----- packages/maps/src/index.tsx | 2 +- 4 files changed, 121 insertions(+), 39 deletions(-) diff --git a/packages/maps/src/components/MapCallout.tsx b/packages/maps/src/components/MapCallout.tsx index 0b91937ca..73eee1aa7 100644 --- a/packages/maps/src/components/MapCallout.tsx +++ b/packages/maps/src/components/MapCallout.tsx @@ -1,17 +1,27 @@ import * as React from "react"; -import { Callout as MapCalloutComponent } from "./react-native-maps"; import type { MapCalloutProps as MapCalloutComponentProps } from "react-native-maps"; +import { Callout as MapCalloutComponent } from "./react-native-maps"; export interface MapCalloutProps extends Omit { showTooltip?: boolean; } -// Has to be a function named 'Callout' to be matched as a Callout component and not a custom view for marker -// See: https://github.com/teovillanueva/react-native-web-maps/blob/81278079c6f26a707d915d69de9a00080c305957/packages/react-native-web-maps/src/components/marker.web.tsx#L79 -export function Callout({ - showTooltip, - ...rest -}: React.PropsWithChildren) { - return ; +/** + * Renders nothing, serves as placeholder for props + * Rendering exposed as function to avoid having an intermediary component that changes the type + * + * This is done because the underlying package has logic dependant on the type of child + * See: https://github.com/teovillanueva/react-native-web-maps/blob/5f3d0ec7c24f789c3df30c1d6d7223e638ff5868/packages/react-native-web-maps/src/components/marker.web.tsx#L79 + */ +const MapCallout: React.FC> = () => { + return null; +}; + +export function renderCallout(props: MapCalloutProps, key: React.Key) { + return ( + + ); } + +export default MapCallout; diff --git a/packages/maps/src/components/MapMarker.tsx b/packages/maps/src/components/MapMarker.tsx index 37dfe46e1..e540731d1 100644 --- a/packages/maps/src/components/MapMarker.tsx +++ b/packages/maps/src/components/MapMarker.tsx @@ -1,7 +1,14 @@ import * as React from "react"; -import { Image, ImageSourcePropType } from "react-native"; +import { + Image, + ImageSourcePropType, + View, + StyleSheet, + Text, +} from "react-native"; import { Marker as MapMarkerComponent } from "./react-native-maps"; import type { MapMarkerProps as MapMarkerComponentProps } from "react-native-maps"; +import MapCallout, { renderCallout } from "./MapCallout"; export interface MapMarkerProps extends Omit { @@ -12,17 +19,55 @@ export interface MapMarkerProps onPress?: (latitude: number, longitude: number) => void; } -const MapMarker: React.FC> = ({ - latitude, - longitude, - pinImage, - pinImageSize = 50, - onPress, - children, - ...rest -}) => { +/** + * Renders nothing, serves as placeholder for props + * Rendering exposed as function to avoid having an intermediary component that changes the type + * + * This is done because the underlying package has logic dependant on the type of child + */ +const MapMarker: React.FC> = () => { + return null; +}; + +export function renderMarker( + { + latitude, + longitude, + pinImage, + pinImageSize = 50, + onPress, + children, + title, + description, + ...rest + }: MapMarkerProps, + key: React.Key +) { + const childrenArray = React.Children.toArray(children); + + const calloutChildren = childrenArray.filter( + (child) => (child as React.ReactElement).type === MapCallout + ); + + const nonCalloutChildren = childrenArray.filter( + (child) => (child as React.ReactElement).type !== MapCallout + ); + + // Add default callout for title/description + if (calloutChildren.length === 0 && (title || description)) { + calloutChildren.push( + + + {title && {title}} + {description && {description}} + + + ); + } + return ( > = ({ }} {...rest} > + {nonCalloutChildren} + {pinImage && ( > = ({ }} /> )} - {children} + + {calloutChildren.map((callout, index) => + renderCallout((callout as React.ReactElement).props, index) + )} ); -}; +} + +const style = StyleSheet.create({ + title: { + fontWeight: "600", + textAlign: "center", + }, + description: { + textAlign: "center", + }, +}); export default MapMarker; diff --git a/packages/maps/src/components/MapView.tsx b/packages/maps/src/components/MapView.tsx index 387ec0fd6..aa1f9447c 100644 --- a/packages/maps/src/components/MapView.tsx +++ b/packages/maps/src/components/MapView.tsx @@ -7,6 +7,7 @@ import type { Region, MapViewProps as MapViewComponentProps, } from "react-native-maps"; +import MapMarker, { renderMarker } from "./MapMarker"; export interface MapViewProps extends MapViewComponentProps { apiKey: string; @@ -69,6 +70,32 @@ class MapView extends React.Component< this.mapRef.current.animateCamera(camera); } + getMarkers(): React.ReactElement[] { + const { markersData, renderItem, keyExtractor, children } = this.props; + + if (markersData && renderItem) { + const markers: React.ReactElement[] = []; + + markersData.forEach((item, index) => { + const component = renderItem?.({ item, index }); + + if (component && component.type === MapMarker) { + const key = keyExtractor ? keyExtractor(item, index) : index; + markers.push( + React.cloneElement(component, { + key, + }) + ); + } + }); + return markers; + } else { + return React.Children.toArray(children).filter( + (child) => (child as React.ReactElement).type === MapMarker + ) as React.ReactElement[]; + } + } + render() { const { apiKey, @@ -78,12 +105,8 @@ class MapView extends React.Component< zoom, showsCompass = false, loadingEnabled = true, - markersData, - renderItem, - keyExtractor, onRegionChange, style, - children, ...rest } = this.props; @@ -112,20 +135,9 @@ class MapView extends React.Component< style={[styles.map, style]} {...rest} > - {markersData && renderItem - ? markersData.map((item, index) => { - const component = renderItem({ item, index }); - - if (!component) { - return null; - } - - const key = keyExtractor ? keyExtractor(item, index) : index; - return React.cloneElement(component, { - key, - }); - }) - : children} + {this.getMarkers().map((marker, index) => + renderMarker(marker.props, index) + )} ); } diff --git a/packages/maps/src/index.tsx b/packages/maps/src/index.tsx index 51dac1a57..61e94fb24 100644 --- a/packages/maps/src/index.tsx +++ b/packages/maps/src/index.tsx @@ -1,3 +1,3 @@ export { default as MapView } from "./components/MapView"; export { default as MapMarker } from "./components/MapMarker"; -export { Callout as MapCallout } from "./components/MapCallout"; +export { default as MapCallout } from "./components/MapCallout";