Skip to content

Commit 9bc5577

Browse files
feat: add support for CircleOverlay (#80)
1 parent 1772697 commit 9bc5577

3 files changed

Lines changed: 236 additions & 0 deletions

File tree

src/components/Circle.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { useContext, useEffect, useState } from 'react';
2+
import MapContext from '../context/MapContext';
3+
import CircleProps from './CircleProps';
4+
5+
export default function Circle({
6+
coordinate,
7+
radius,
8+
9+
visible = true,
10+
enabled = true,
11+
selected = false,
12+
13+
onSelect = undefined,
14+
onDeselect = undefined,
15+
16+
lineDash = [],
17+
lineDashOffset = 0,
18+
lineWidth = 1,
19+
20+
strokeColor = 'rgb(0, 122, 255)',
21+
strokeOpacity = 1,
22+
23+
fillColor = 'rgb(0, 122, 255)',
24+
fillOpacity = 0.1,
25+
}: CircleProps) {
26+
const [circle, setCircle] = useState<mapkit.CircleOverlay | null>(null);
27+
const map = useContext(MapContext);
28+
29+
useEffect(() => {
30+
if (map === null) return undefined;
31+
32+
const { latitude, longitude } = coordinate;
33+
const mapKitCoordinate = new mapkit.Coordinate(latitude, longitude);
34+
const overlay = new mapkit.CircleOverlay(mapKitCoordinate, radius);
35+
map.addOverlay(overlay);
36+
setCircle(overlay);
37+
38+
return () => {
39+
map.removeOverlay(overlay);
40+
};
41+
}, [map]);
42+
43+
// Simple properties
44+
const properties = { visible, enabled, selected };
45+
Object.entries(properties).forEach(([propertyName, prop]) => {
46+
useEffect(() => {
47+
if (!circle) return;
48+
// @ts-ignore
49+
circle[propertyName] = prop;
50+
}, [circle, prop]);
51+
});
52+
53+
// Simple style properties
54+
const styleProperties = {
55+
lineDash,
56+
lineDashOffset,
57+
lineWidth,
58+
59+
strokeColor,
60+
strokeOpacity,
61+
62+
fillColor,
63+
fillOpacity,
64+
};
65+
Object.entries(styleProperties).forEach(([propertyName, prop]) => {
66+
useEffect(() => {
67+
if (!circle) return;
68+
// @ts-ignore
69+
circle.style[propertyName] = prop;
70+
}, [circle, prop]);
71+
});
72+
73+
// Events
74+
const events = [
75+
{ name: 'select', handler: onSelect },
76+
{ name: 'deselect', handler: onDeselect },
77+
] as const;
78+
events.forEach(({ name, handler }) => {
79+
useEffect(() => {
80+
if (!circle || !handler) return undefined;
81+
82+
const handlerWithoutParameters = () => handler();
83+
84+
circle.addEventListener(name, handlerWithoutParameters);
85+
return () => circle.removeEventListener(name, handlerWithoutParameters);
86+
}, [circle, handler]);
87+
});
88+
89+
return null;
90+
}

src/components/CircleProps.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Coordinate } from '../util/parameters';
2+
3+
export default interface CircleProps {
4+
/**
5+
* The coordinate of the circle overlay’s center.
6+
* @see {@link https://developer.apple.com/documentation/mapkitjs/circleoverlay/coordinate}
7+
*/
8+
coordinate: Coordinate;
9+
10+
/**
11+
* The radius of the circle overlay, in meters.
12+
* @see {@link https://developer.apple.com/documentation/mapkitjs/circleoverlay/radius}
13+
*/
14+
radius: number;
15+
16+
/**
17+
* A Boolean value that determines whether the circle is visible.
18+
* @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/visible}
19+
*/
20+
visible?: boolean;
21+
22+
/**
23+
* A Boolean value that determines whether the circle responds to user interaction.
24+
* @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/enabled}
25+
*/
26+
enabled?: boolean;
27+
28+
/**
29+
* A Boolean value that determines whether the map displays the circle in a selected state.
30+
* @see {@link https://developer.apple.com/documentation/mapkitjs/overlay/selected}
31+
*/
32+
selected?: boolean;
33+
34+
/**
35+
* Event fired when the circle is selected.
36+
*/
37+
onSelect?: () => void;
38+
39+
/**
40+
* Event fired when the circle is deselected.
41+
*/
42+
onDeselect?: () => void;
43+
44+
/**
45+
* The stroke color of the line. Accepts any valid CSS color value.
46+
* The default is `rgb(0, 122, 255)`.
47+
* Set this to `null` to remove the line stroke.
48+
* @see {@link https://developer.apple.com/documentation/mapkitjs/style/strokeColor}
49+
*/
50+
strokeColor?: string | null;
51+
52+
/**
53+
* The opacity of the stroke as a number between 0 and 1.
54+
* The default value is `1`.
55+
* @see {@link https://developer.apple.com/documentation/mapkitjs/style/strokeOpacity}
56+
*/
57+
strokeOpacity?: number;
58+
59+
/**
60+
* The line width of the stroke for overlays, in CSS pixels.
61+
* The default value is `1`.
62+
* @see {@link https://developer.apple.com/documentation/mapkitjs/style/lineWidth}
63+
*/
64+
lineWidth?: number;
65+
66+
/**
67+
* An array defining the line’s dash pattern, where numbers represent line and
68+
* gap lengths in CSS pixels. For example, `[10, 5]` means draw for 10 pixels
69+
* and leave a 5‑pixel gap repeatedly. Set to `[]` for solid lines (default).
70+
* MapKit JS duplicates the array if it has an odd number of elements.
71+
* @see {@link https://developer.apple.com/documentation/mapkitjs/style/lineDash}
72+
*/
73+
lineDash?: number[];
74+
75+
/**
76+
* The number of CSS pixels to offset the start of the dash pattern.
77+
* Has no effect when `lineDash` is set to draw solid lines.
78+
* The default value is `0`.
79+
* @see {@link https://developer.apple.com/documentation/mapkitjs/style/lineDashOffset}
80+
*/
81+
lineDashOffset?: number;
82+
83+
/**
84+
* The fill color used for the shape. Accepts any valid CSS color value.
85+
* The default is `rgb(0, 122, 255)`.
86+
* Set this to `null` for no fill.
87+
* @see {@link https://developer.apple.com/documentation/mapkitjs/style/fillColor}
88+
*/
89+
fillColor?: string | null;
90+
91+
/**
92+
* The opacity to apply to the fill, as a number between 0 and 1.
93+
* The default value is `0.1`.
94+
* @see {@link https://developer.apple.com/documentation/mapkitjs/style/fillOpacity}
95+
*/
96+
fillOpacity?: number;
97+
}

src/stories/Circle.stories.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React, { useMemo } from 'react';
2+
import { Meta, StoryFn } from '@storybook/react';
3+
import { fn } from '@storybook/test';
4+
import './stories.css';
5+
import Map from '../components/Map';
6+
import { CoordinateRegion } from '../util/parameters';
7+
import Circle from '../components/Circle';
8+
9+
// @ts-ignore
10+
const token = import.meta.env.STORYBOOK_MAPKIT_JS_TOKEN!;
11+
12+
export default {
13+
title: 'Components/Circle',
14+
component: Circle,
15+
parameters: {
16+
layout: 'fullscreen',
17+
},
18+
args: {
19+
onSelect: fn(),
20+
onDeselect: fn(),
21+
},
22+
} as Meta<typeof Circle>;
23+
24+
type CircleProps = React.ComponentProps<typeof Circle>;
25+
26+
const Template: StoryFn<CircleProps> = (args) => {
27+
const initialRegion: CoordinateRegion = useMemo(
28+
() => ({
29+
centerLatitude: 48,
30+
centerLongitude: 14,
31+
latitudeDelta: 22,
32+
longitudeDelta: 55,
33+
}),
34+
[],
35+
);
36+
return (
37+
<Map
38+
token={token}
39+
initialRegion={initialRegion}
40+
minCameraDistance={300}
41+
includedPOICategories={[]}
42+
>
43+
<Circle {...args} />
44+
</Map>
45+
);
46+
};
47+
48+
export const Default = Template.bind({});
49+
Default.args = { coordinate: { latitude: 46.52, longitude: 6.57 }, radius: 100000 };

0 commit comments

Comments
 (0)