Skip to content

Commit a059100

Browse files
Merge pull request #105 from Chia-Chi-Shen/storybook
Add interactive popup example
2 parents 9caf285 + 4000731 commit a059100

8 files changed

+383
-1
lines changed

src/stories/MapAnnotations/Popup/Popup.tsx src/stories/MapAnnotations/Popup/Basic/Popup.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AzureMap, AzureMapsProvider, AzureMapPopup, IAzureMapPopup } from 'react-azure-maps';
2-
import { mapOptions } from '../../../key';
2+
import { mapOptions } from '../../../../key';
33

44
const Popup = ({ isVisible, options }: IAzureMapPopup) => {
55
// use position as argument would be better
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { Meta, Source } from '@storybook/blocks';
2+
3+
import * as InteractivePopupStories from './InteractivePopup.stories';
4+
5+
import InteractivePopup from './InteractivePopupExample';
6+
7+
<Meta of={InteractivePopupStories} />
8+
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.**
90+
91+
<Source code={`
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+
`} />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import InteractivePopup from './InteractivePopupExample';
3+
4+
const meta: Meta<typeof InteractivePopup> = {
5+
title: 'Map Annotations/Interactive Popup',
6+
component: InteractivePopup,
7+
args: {
8+
isVisible: true,
9+
options: {
10+
position: [0, 0],
11+
},
12+
},
13+
parameters: {
14+
storySource: {
15+
source: `
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;
71+
};
72+
`,
73+
},
74+
},
75+
};
76+
77+
export default meta;
78+
79+
type Story = StoryObj<typeof InteractivePopup>;
80+
81+
export const Example: Story = {};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useContext, useEffect, useState, ReactNode } from 'react';
2+
import { createRoot } from 'react-dom/client';
3+
import atlas from 'azure-maps-control';
4+
import { IAzureMapsContextProps, AzureMapsContext, IAzureMapPopupEvent } from 'react-azure-maps';
5+
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) => {
14+
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext);
15+
const containerRef = document.createElement('div');
16+
const root = createRoot(containerRef);
17+
const [popupRef] = useState<atlas.Popup>(new atlas.Popup({ ...options, content: containerRef }));
18+
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+
}, []);
31+
32+
// Render the popup content and set the options
33+
useEffect(() => {
34+
root.render(children);
35+
popupRef.setOptions({
36+
...options,
37+
content: containerRef,
38+
});
39+
if (mapRef && isVisible && !popupRef.isOpen()) {
40+
popupRef.open(mapRef);
41+
}
42+
}, [options, children]);
43+
44+
// Toggle the popup visibility
45+
useEffect(() => {
46+
if (mapRef) {
47+
if (isVisible && !popupRef.isOpen()) {
48+
popupRef.open(mapRef);
49+
} else if (mapRef.popups.getPopups().length && !isVisible && popupRef.isOpen()) {
50+
popupRef.close();
51+
}
52+
}
53+
}, [isVisible]);
54+
55+
return null;
56+
};
57+
58+
export default InteractivePopup;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { AzureMap, AzureMapsProvider, IAzureMapPopup } from 'react-azure-maps';
2+
import { mapOptions } from '../../../../key';
3+
import InteractivePopup from './InteractivePopup';
4+
import PopupContent from './PopupContent';
5+
import { useState } from 'react';
6+
7+
const InteractivePopupExample = ({ isVisible, options }: IAzureMapPopup) => {
8+
const [bgColor, setBgColor] = useState('white');
9+
10+
// click to change color randomly
11+
const changeColor = () => {
12+
const color = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
13+
setBgColor(color);
14+
};
15+
return (
16+
<AzureMapsProvider>
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>
28+
</div>
29+
</AzureMapsProvider>
30+
);
31+
};
32+
33+
export default InteractivePopupExample;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useState } from 'react';
2+
3+
const PopupContent = ({ bgColor }: { bgColor: string }) => {
4+
const [count, setCount] = useState(0);
5+
6+
return (
7+
<div style={{ padding: '10px', backgroundColor: bgColor, textAlign: 'center' }}>
8+
<h3>This is a counter:</h3>
9+
<p>You have clicked {count} times.</p>
10+
<button
11+
style={{
12+
border: '1px',
13+
padding: '4px',
14+
cursor: 'pointer',
15+
backgroundColor: 'gainsboro',
16+
}}
17+
onClick={() => {
18+
setCount(count + 1);
19+
}}
20+
>
21+
Click me
22+
</button>
23+
<button
24+
style={{
25+
border: '1px',
26+
padding: '4px',
27+
cursor: 'pointer',
28+
color: 'blue',
29+
backgroundColor: 'transparent',
30+
}}
31+
onClick={() => {
32+
setCount(0);
33+
}}
34+
>
35+
Reset
36+
</button>
37+
</div>
38+
);
39+
};
40+
41+
export default PopupContent;

0 commit comments

Comments
 (0)