Skip to content

Commit 0e7281c

Browse files
Merge pull request #5 from Chia-Chi-Shen/interactive-popup
Interactive popup
2 parents 64a5ead + 8ce9221 commit 0e7281c

File tree

9 files changed

+402
-9
lines changed

9 files changed

+402
-9
lines changed

src/key.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import { IAzureMapOptions, AuthenticationType } from 'react-azure-maps';
22

3+
// export const mapOptions: IAzureMapOptions = {
4+
// authOptions: {
5+
// authType: AuthenticationType.anonymous,
6+
// clientId: '2a60774c-f588-423b-b004-56d213773ee6',
7+
// getToken: (resolve, reject) => {
8+
// fetch('https://anonymous-auth.azurewebsites.net/api/GetAccessToken-Prod')
9+
// .then((result) => result.text())
10+
// .then((result) => resolve(result))
11+
// .catch((error) => reject(new Error(`Failed to fetch anon auth token: ${error.message}`)));
12+
// },
13+
// },
14+
// center: [0, 0],
15+
// view: 'Auto',
16+
// };
17+
318
export const mapOptions: IAzureMapOptions = {
419
authOptions: {
5-
authType: AuthenticationType.anonymous,
6-
clientId: '2a60774c-f588-423b-b004-56d213773ee6',
7-
getToken: (resolve, reject) => {
8-
fetch('https://anonymous-auth.azurewebsites.net/api/GetAccessToken-Prod')
9-
.then((result) => result.text())
10-
.then((result) => resolve(result))
11-
.catch((error) => reject(new Error(`Failed to fetch anon auth token: ${error.message}`)));
12-
},
20+
authType: AuthenticationType.subscriptionKey,
21+
subscriptionKey: '013GHvO6nMXUbB0yrRDmgizOaO3tRzevKMzPA6Yl4PeFMPgqFuelJQQJ99AGACrJL3JAArohAAAgAZMPQDp7',
1322
},
1423
center: [0, 0],
1524
view: 'Auto',
File renamed without changes.

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { AzureMap, AzureMapsProvider, AzureMapPopup, IAzureMapPopup } from 'react-azure-maps';
2-
import { mapOptions } from '../../../key';
2+
import PopupContent from '../Interactive/PopupContent';
3+
import { mapOptions } from '../../../../key';
34

45
const Popup = ({ isVisible, options }: IAzureMapPopup) => {
56
// use position as argument would be better
@@ -11,6 +12,7 @@ const Popup = ({ isVisible, options }: IAzureMapPopup) => {
1112
isVisible={isVisible}
1213
options={options}
1314
popupContent={<div style={{ padding: '20px' }}>Hello World</div>}
15+
// popupContent={<PopupContent />}
1416
/>
1517
</AzureMap>
1618
</div>
Lines changed: 169 additions & 0 deletions
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+
`} />
Lines changed: 81 additions & 0 deletions
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 = {};
Lines changed: 58 additions & 0 deletions
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;
Lines changed: 33 additions & 0 deletions
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;

0 commit comments

Comments
 (0)