Skip to content

Commit 8ce9221

Browse files
author
Savina Shen (Manpower Services Taiwan Co Ltd)
committed
add interactive popup docs
1 parent 81b98d1 commit 8ce9221

File tree

5 files changed

+291
-67
lines changed

5 files changed

+291
-67
lines changed

src/stories/MapAnnotations/Popup/Interactive/InteractivePopup.mdx

Lines changed: 160 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,168 @@ import { Meta, Source } from '@storybook/blocks';
22

33
import * as InteractivePopupStories from './InteractivePopup.stories';
44

5-
import Popup from './InteractivePopup';
5+
import InteractivePopup from './InteractivePopupExample';
66

77
<Meta of={InteractivePopupStories} />
88

9-
# Popup
10-
A popup is an information window anchored at a specified position on a map.
11-
With **react-azure-maps**, you can quickly customize the `Popup` component with these 3 props:
12-
<Popup isVisible options={{ position: [0, 0] }} />
13-
{/* show basic code */}
9+
# Interactive Popup
10+
Besides the static popup, you can also create an interactive popup containing React components.<br/>
11+
Therefore, you can easily change the states of the components both inside and outside the popup.<br/>
12+
## Example
13+
Here is an example of an interactive popup that shows a counter counting the number of times the user has clicked on the popup.<br/>
14+
You can also change the popup's color by clicking the button on the top left corner.
15+
16+
<InteractivePopup isVisible options={{ position: [0, 0] }} />
17+
18+
19+
Let's take a look of how the interactive popup is implemented.<br/>
20+
## Implementation
21+
### 1. Create an interactive popup component
22+
Here we initialize a new Popup instance and render the React node children for the popup's content.<br/>
23+
24+
<Source code={`
25+
import { useContext, useEffect, useState, ReactNode } from 'react';
26+
import { createRoot } from 'react-dom/client';
27+
import atlas from 'azure-maps-control';
28+
import { IAzureMapsContextProps, AzureMapsContext, IAzureMapPopupEvent } from 'react-azure-maps';
29+
30+
interface InteractivePopupProps {
31+
children: ReactNode;
32+
isVisible?: boolean;
33+
options?: atlas.PopupOptions;
34+
events?: IAzureMapPopupEvent[];
35+
}
36+
37+
const InteractivePopup = ({ children, isVisible = true, options, events }: InteractivePopupProps) => {
38+
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext);
39+
const containerRef = document.createElement('div');
40+
const root = createRoot(containerRef);
41+
const [popupRef] = useState<atlas.Popup>(new atlas.Popup({ ...options, content: containerRef }));
42+
43+
// Add events to the popup when it is mounted
44+
useEffect(() => {
45+
if (mapRef) {
46+
events &&
47+
events.forEach(({ eventName, callback }) => {
48+
mapRef.events.add(eventName, popupRef, callback);
49+
});
50+
return () => {
51+
mapRef.popups.remove(popupRef);
52+
};
53+
}
54+
}, []);
55+
56+
// Render the popup content and set the options
57+
useEffect(() => {
58+
root.render(children);
59+
popupRef.setOptions({
60+
...options,
61+
content: containerRef,
62+
});
63+
if (mapRef && isVisible && !popupRef.isOpen()) {
64+
popupRef.open(mapRef);
65+
}
66+
}, [options, children]);
67+
68+
// Toggle the popup visibility
69+
useEffect(() => {
70+
if (mapRef) {
71+
if (isVisible && !popupRef.isOpen()) {
72+
popupRef.open(mapRef);
73+
} else if (mapRef.popups.getPopups().length && !isVisible && popupRef.isOpen()) {
74+
popupRef.close();
75+
}
76+
}
77+
}, [isVisible]);
78+
79+
return null;
80+
};
81+
82+
export default InteractivePopup;
83+
84+
`} />
85+
86+
### 2. Create your popup content
87+
In this example we create a simple counter component that increments the count when the user clicks on the popup.<br/>
88+
Also, it accepts a background color as a prop to change the popup's color.<br/>
89+
**You can create any kind of React component as the popup content.**
1490

1591
<Source code={`
16-
<AzureMapPopup
17-
isVisible
18-
options={{ position: [0, 0] }}
19-
popupContent={<div style={{ padding: '20px' }}>Hello World</div>}
20-
/>
21-
`} />
92+
import { useState } from 'react';
93+
94+
const PopupContent = ({ bgColor }: { bgColor: string }) => {
95+
const [count, setCount] = useState(0);
96+
97+
return (
98+
<div style={{ padding: '10px', backgroundColor: bgColor, textAlign: 'center' }}>
99+
<h3>This is a counter:</h3>
100+
<p>You have clicked {count} times.</p>
101+
<button
102+
style={{
103+
border: '1px',
104+
padding: '4px',
105+
cursor: 'pointer',
106+
backgroundColor: 'gainsboro',
107+
}}
108+
onClick={() => {
109+
setCount(count + 1);
110+
}}
111+
>
112+
Click me
113+
</button>
114+
<button
115+
style={{
116+
border: '1px',
117+
padding: '4px',
118+
cursor: 'pointer',
119+
color: 'blue',
120+
backgroundColor: 'transparent',
121+
}}
122+
onClick={() => {
123+
setCount(0);
124+
}}
125+
>
126+
Reset
127+
</button>
128+
</div>
129+
);
130+
};
131+
132+
export default PopupContent;
133+
`}/>
134+
135+
### 3. Use the interactive popup
136+
Finally, you can use the interactive popup component on your map and pass your react component as children.<br/>
137+
138+
<Source code={`
139+
import { AzureMap, AzureMapsProvider } from 'react-azure-maps';
140+
import InteractivePopup from './InteractivePopup';
141+
import PopupContent from './PopupContent';
142+
import { useState } from 'react';
143+
144+
const YourMap = () => {
145+
const [bgColor, setBgColor] = useState('white');
146+
147+
// click to change color randomly
148+
const changeColor = () => {
149+
const color = \`#\${Math.floor(Math.random() * 16777215).toString(16)}\`;
150+
setBgColor(color);
151+
};
152+
return (
153+
<AzureMapsProvider>
154+
<div>
155+
<button onClick={changeColor} style={{ marginBottom: '10px' }}>
156+
Change popup color
157+
</button>
158+
<div style={...}>
159+
<AzureMap options={yourOptions}>
160+
<InteractivePopup isVisible options={{ position: [0, 0] }}>
161+
<PopupContent bgColor={bgColor} />
162+
</InteractivePopup>
163+
</AzureMap>
164+
</div>
165+
</div>
166+
</AzureMapsProvider>
167+
);
168+
};
169+
`} />

src/stories/MapAnnotations/Popup/Interactive/InteractivePopup.stories.ts

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,61 @@ const meta: Meta<typeof InteractivePopup> = {
1313
parameters: {
1414
storySource: {
1515
source: `
16-
import { AzureMap, AzureMapsProvider, AzureMapPopup, IAzureMapPopup } from 'react-azure-maps';
17-
18-
const Popup = () => {
19-
20-
return (
21-
<AzureMapsProvider>
22-
<AzureMap options={your_options}>
23-
<AzureMapPopup
24-
isVisible
25-
options={{ position: [0, 0] }}
26-
popupContent={<div style={{ padding: '20px' }}>Hello World</div>}
27-
/>
28-
</AzureMap>
29-
</AzureMapsProvider>
30-
);
16+
import { useContext, useEffect, useState, ReactNode } from 'react';
17+
import { createRoot } from 'react-dom/client';
18+
import atlas from 'azure-maps-control';
19+
import { IAzureMapsContextProps, AzureMapsContext, IAzureMapPopupEvent } from 'react-azure-maps';
20+
21+
interface InteractivePopupProps {
22+
children: ReactNode;
23+
isVisible?: boolean;
24+
options?: atlas.PopupOptions;
25+
events?: IAzureMapPopupEvent[];
26+
};
27+
28+
const InteractivePopup = ({ children, isVisible = true, options, events }: InteractivePopupProps) => {
29+
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext);
30+
const containerRef = document.createElement('div');
31+
const root = createRoot(containerRef);
32+
const [popupRef] = useState<atlas.Popup>(new atlas.Popup({ ...options, content: containerRef }));
33+
34+
// Add events to the popup when it is mounted
35+
useEffect(() => {
36+
if (mapRef) {
37+
events &&
38+
events.forEach(({ eventName, callback }) => {
39+
mapRef.events.add(eventName, popupRef, callback);
40+
});
41+
return () => {
42+
mapRef.popups.remove(popupRef);
43+
};
44+
}
45+
}, []);
46+
47+
// Render the popup content and set the options
48+
useEffect(() => {
49+
root.render(children);
50+
popupRef.setOptions({
51+
...options,
52+
content: containerRef,
53+
});
54+
if (mapRef && isVisible && !popupRef.isOpen()) {
55+
popupRef.open(mapRef);
56+
}
57+
}, [options, children]);
58+
59+
// Toggle the popup visibility
60+
useEffect(() => {
61+
if (mapRef) {
62+
if (isVisible && !popupRef.isOpen()) {
63+
popupRef.open(mapRef);
64+
} else if (mapRef.popups.getPopups().length && !isVisible && popupRef.isOpen()) {
65+
popupRef.close();
66+
}
67+
}
68+
}, [isVisible]);
69+
70+
return null;
3171
};
3272
`,
3373
},
Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,47 @@
1-
import { useContext, useEffect, useState, useRef } from 'react';
1+
import { useContext, useEffect, useState, ReactNode } from 'react';
22
import { createRoot } from 'react-dom/client';
33
import atlas from 'azure-maps-control';
4-
import { IAzureMapsContextProps, MapType, IAzureMapPopup, AzureMapsContext } from 'react-azure-maps';
4+
import { IAzureMapsContextProps, AzureMapsContext, IAzureMapPopupEvent } from 'react-azure-maps';
55

6-
const InteractivePopup = ({ isVisible, popupContent, options, events }: IAzureMapPopup) => {
6+
interface InteractivePopupProps {
7+
children: ReactNode;
8+
isVisible?: boolean;
9+
options?: atlas.PopupOptions;
10+
events?: IAzureMapPopupEvent[];
11+
}
12+
13+
const InteractivePopup = ({ children, isVisible = true, options, events }: InteractivePopupProps) => {
714
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext);
815
const containerRef = document.createElement('div');
916
const root = createRoot(containerRef);
10-
1117
const [popupRef] = useState<atlas.Popup>(new atlas.Popup({ ...options, content: containerRef }));
1218

13-
useCheckRefMount<MapType, boolean>(mapRef, true, (mref) => {
14-
events &&
15-
events.forEach(({ eventName, callback }) => {
16-
mref.events.add(eventName, popupRef, callback);
17-
});
18-
return () => {
19-
mref.popups.remove(popupRef);
20-
};
21-
});
19+
// Add events to the popup when it is mounted
20+
useEffect(() => {
21+
if (mapRef) {
22+
events &&
23+
events.forEach(({ eventName, callback }) => {
24+
mapRef.events.add(eventName, popupRef, callback);
25+
});
26+
return () => {
27+
mapRef.popups.remove(popupRef);
28+
};
29+
}
30+
}, []);
2231

32+
// Render the popup content and set the options
2333
useEffect(() => {
24-
root.render(popupContent);
34+
root.render(children);
2535
popupRef.setOptions({
2636
...options,
2737
content: containerRef,
2838
});
2939
if (mapRef && isVisible && !popupRef.isOpen()) {
3040
popupRef.open(mapRef);
3141
}
32-
}, [options, popupContent]);
42+
}, [options, children]);
3343

44+
// Toggle the popup visibility
3445
useEffect(() => {
3546
if (mapRef) {
3647
if (isVisible && !popupRef.isOpen()) {
@@ -44,12 +55,4 @@ const InteractivePopup = ({ isVisible, popupContent, options, events }: IAzureMa
4455
return null;
4556
};
4657

47-
function useCheckRefMount<T, T1>(dep: T | null, on: T1 | null, callback: (dep: T, on: T1) => void) {
48-
useEffect(() => {
49-
if (dep && on) {
50-
return callback(dep, on);
51-
}
52-
}, []);
53-
}
54-
5558
export default InteractivePopup;

src/stories/MapAnnotations/Popup/Interactive/InteractivePopupExample.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,30 @@ import InteractivePopup from './InteractivePopup';
44
import PopupContent from './PopupContent';
55
import { useState } from 'react';
66

7-
const Popup = ({ isVisible, options }: IAzureMapPopup) => {
8-
const [text, setText] = useState('Hello World');
7+
const InteractivePopupExample = ({ isVisible, options }: IAzureMapPopup) => {
8+
const [bgColor, setBgColor] = useState('white');
99

10-
// use position as argument would be better
10+
// click to change color randomly
11+
const changeColor = () => {
12+
const color = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
13+
setBgColor(color);
14+
};
1115
return (
1216
<AzureMapsProvider>
13-
<div className="defaultMap sb-unstyled">
14-
<button onClick={() => setText(text + '1')}>Change text</button>
15-
<AzureMap options={mapOptions}>
16-
<InteractivePopup isVisible={isVisible} options={options} popupContent={<PopupContent text={text} />} />
17-
</AzureMap>
17+
<div>
18+
<button onClick={changeColor} style={{ marginBottom: '10px' }}>
19+
Change popup color
20+
</button>
21+
<div className="defaultMap sb-unstyled">
22+
<AzureMap options={mapOptions}>
23+
<InteractivePopup isVisible={isVisible} options={options}>
24+
<PopupContent bgColor={bgColor} />
25+
</InteractivePopup>
26+
</AzureMap>
27+
</div>
1828
</div>
1929
</AzureMapsProvider>
2030
);
2131
};
2232

23-
export default Popup;
33+
export default InteractivePopupExample;

0 commit comments

Comments
 (0)