-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #105 from Chia-Chi-Shen/storybook
Add interactive popup example
- Loading branch information
Showing
8 changed files
with
383 additions
and
1 deletion.
There are no files selected for viewing
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
src/stories/MapAnnotations/Popup/Popup.tsx → ...ries/MapAnnotations/Popup/Basic/Popup.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
src/stories/MapAnnotations/Popup/Interactive/InteractivePopup.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import { Meta, Source } from '@storybook/blocks'; | ||
|
||
import * as InteractivePopupStories from './InteractivePopup.stories'; | ||
|
||
import InteractivePopup from './InteractivePopupExample'; | ||
|
||
<Meta of={InteractivePopupStories} /> | ||
|
||
# Interactive Popup | ||
Besides the static popup, you can also create an interactive popup containing React components.<br/> | ||
Therefore, you can easily change the states of the components both inside and outside the popup.<br/> | ||
## Example | ||
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/> | ||
You can also change the popup's color by clicking the button on the top left corner. | ||
|
||
<InteractivePopup isVisible options={{ position: [0, 0] }} /> | ||
|
||
|
||
Let's take a look of how the interactive popup is implemented.<br/> | ||
## Implementation | ||
### 1. Create an interactive popup component | ||
Here we initialize a new Popup instance and render the React node children for the popup's content.<br/> | ||
|
||
<Source code={` | ||
import { useContext, useEffect, useState, ReactNode } from 'react'; | ||
import { createRoot } from 'react-dom/client'; | ||
import atlas from 'azure-maps-control'; | ||
import { IAzureMapsContextProps, AzureMapsContext, IAzureMapPopupEvent } from 'react-azure-maps'; | ||
interface InteractivePopupProps { | ||
children: ReactNode; | ||
isVisible?: boolean; | ||
options?: atlas.PopupOptions; | ||
events?: IAzureMapPopupEvent[]; | ||
} | ||
const InteractivePopup = ({ children, isVisible = true, options, events }: InteractivePopupProps) => { | ||
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext); | ||
const containerRef = document.createElement('div'); | ||
const root = createRoot(containerRef); | ||
const [popupRef] = useState<atlas.Popup>(new atlas.Popup({ ...options, content: containerRef })); | ||
// Add events to the popup when it is mounted | ||
useEffect(() => { | ||
if (mapRef) { | ||
events && | ||
events.forEach(({ eventName, callback }) => { | ||
mapRef.events.add(eventName, popupRef, callback); | ||
}); | ||
return () => { | ||
mapRef.popups.remove(popupRef); | ||
}; | ||
} | ||
}, []); | ||
// Render the popup content and set the options | ||
useEffect(() => { | ||
root.render(children); | ||
popupRef.setOptions({ | ||
...options, | ||
content: containerRef, | ||
}); | ||
if (mapRef && isVisible && !popupRef.isOpen()) { | ||
popupRef.open(mapRef); | ||
} | ||
}, [options, children]); | ||
// Toggle the popup visibility | ||
useEffect(() => { | ||
if (mapRef) { | ||
if (isVisible && !popupRef.isOpen()) { | ||
popupRef.open(mapRef); | ||
} else if (mapRef.popups.getPopups().length && !isVisible && popupRef.isOpen()) { | ||
popupRef.close(); | ||
} | ||
} | ||
}, [isVisible]); | ||
return null; | ||
}; | ||
export default InteractivePopup; | ||
`} /> | ||
|
||
### 2. Create your popup content | ||
In this example we create a simple counter component that increments the count when the user clicks on the popup.<br/> | ||
Also, it accepts a background color as a prop to change the popup's color.<br/> | ||
**You can create any kind of React component as the popup content.** | ||
|
||
<Source code={` | ||
import { useState } from 'react'; | ||
const PopupContent = ({ bgColor }: { bgColor: string }) => { | ||
const [count, setCount] = useState(0); | ||
return ( | ||
<div style={{ padding: '10px', backgroundColor: bgColor, textAlign: 'center' }}> | ||
<h3>This is a counter:</h3> | ||
<p>You have clicked {count} times.</p> | ||
<button | ||
style={{ | ||
border: '1px', | ||
padding: '4px', | ||
cursor: 'pointer', | ||
backgroundColor: 'gainsboro', | ||
}} | ||
onClick={() => { | ||
setCount(count + 1); | ||
}} | ||
> | ||
Click me | ||
</button> | ||
<button | ||
style={{ | ||
border: '1px', | ||
padding: '4px', | ||
cursor: 'pointer', | ||
color: 'blue', | ||
backgroundColor: 'transparent', | ||
}} | ||
onClick={() => { | ||
setCount(0); | ||
}} | ||
> | ||
Reset | ||
</button> | ||
</div> | ||
); | ||
}; | ||
export default PopupContent; | ||
`}/> | ||
|
||
### 3. Use the interactive popup | ||
Finally, you can use the interactive popup component on your map and pass your react component as children.<br/> | ||
|
||
<Source code={` | ||
import { AzureMap, AzureMapsProvider } from 'react-azure-maps'; | ||
import InteractivePopup from './InteractivePopup'; | ||
import PopupContent from './PopupContent'; | ||
import { useState } from 'react'; | ||
const YourMap = () => { | ||
const [bgColor, setBgColor] = useState('white'); | ||
// click to change color randomly | ||
const changeColor = () => { | ||
const color = \`#\${Math.floor(Math.random() * 16777215).toString(16)}\`; | ||
setBgColor(color); | ||
}; | ||
return ( | ||
<AzureMapsProvider> | ||
<div> | ||
<button onClick={changeColor} style={{ marginBottom: '10px' }}> | ||
Change popup color | ||
</button> | ||
<div style={...}> | ||
<AzureMap options={yourOptions}> | ||
<InteractivePopup isVisible options={{ position: [0, 0] }}> | ||
<PopupContent bgColor={bgColor} /> | ||
</InteractivePopup> | ||
</AzureMap> | ||
</div> | ||
</div> | ||
</AzureMapsProvider> | ||
); | ||
}; | ||
`} /> |
81 changes: 81 additions & 0 deletions
81
src/stories/MapAnnotations/Popup/Interactive/InteractivePopup.stories.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import InteractivePopup from './InteractivePopupExample'; | ||
|
||
const meta: Meta<typeof InteractivePopup> = { | ||
title: 'Map Annotations/Interactive Popup', | ||
component: InteractivePopup, | ||
args: { | ||
isVisible: true, | ||
options: { | ||
position: [0, 0], | ||
}, | ||
}, | ||
parameters: { | ||
storySource: { | ||
source: ` | ||
import { useContext, useEffect, useState, ReactNode } from 'react'; | ||
import { createRoot } from 'react-dom/client'; | ||
import atlas from 'azure-maps-control'; | ||
import { IAzureMapsContextProps, AzureMapsContext, IAzureMapPopupEvent } from 'react-azure-maps'; | ||
interface InteractivePopupProps { | ||
children: ReactNode; | ||
isVisible?: boolean; | ||
options?: atlas.PopupOptions; | ||
events?: IAzureMapPopupEvent[]; | ||
}; | ||
const InteractivePopup = ({ children, isVisible = true, options, events }: InteractivePopupProps) => { | ||
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext); | ||
const containerRef = document.createElement('div'); | ||
const root = createRoot(containerRef); | ||
const [popupRef] = useState<atlas.Popup>(new atlas.Popup({ ...options, content: containerRef })); | ||
// Add events to the popup when it is mounted | ||
useEffect(() => { | ||
if (mapRef) { | ||
events && | ||
events.forEach(({ eventName, callback }) => { | ||
mapRef.events.add(eventName, popupRef, callback); | ||
}); | ||
return () => { | ||
mapRef.popups.remove(popupRef); | ||
}; | ||
} | ||
}, []); | ||
// Render the popup content and set the options | ||
useEffect(() => { | ||
root.render(children); | ||
popupRef.setOptions({ | ||
...options, | ||
content: containerRef, | ||
}); | ||
if (mapRef && isVisible && !popupRef.isOpen()) { | ||
popupRef.open(mapRef); | ||
} | ||
}, [options, children]); | ||
// Toggle the popup visibility | ||
useEffect(() => { | ||
if (mapRef) { | ||
if (isVisible && !popupRef.isOpen()) { | ||
popupRef.open(mapRef); | ||
} else if (mapRef.popups.getPopups().length && !isVisible && popupRef.isOpen()) { | ||
popupRef.close(); | ||
} | ||
} | ||
}, [isVisible]); | ||
return null; | ||
}; | ||
`, | ||
}, | ||
}, | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof InteractivePopup>; | ||
|
||
export const Example: Story = {}; |
58 changes: 58 additions & 0 deletions
58
src/stories/MapAnnotations/Popup/Interactive/InteractivePopup.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { useContext, useEffect, useState, ReactNode } from 'react'; | ||
import { createRoot } from 'react-dom/client'; | ||
import atlas from 'azure-maps-control'; | ||
import { IAzureMapsContextProps, AzureMapsContext, IAzureMapPopupEvent } from 'react-azure-maps'; | ||
|
||
interface InteractivePopupProps { | ||
children: ReactNode; | ||
isVisible?: boolean; | ||
options?: atlas.PopupOptions; | ||
events?: IAzureMapPopupEvent[]; | ||
} | ||
|
||
const InteractivePopup = ({ children, isVisible = true, options, events }: InteractivePopupProps) => { | ||
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext); | ||
const containerRef = document.createElement('div'); | ||
const root = createRoot(containerRef); | ||
const [popupRef] = useState<atlas.Popup>(new atlas.Popup({ ...options, content: containerRef })); | ||
|
||
// Add events to the popup when it is mounted | ||
useEffect(() => { | ||
if (mapRef) { | ||
events && | ||
events.forEach(({ eventName, callback }) => { | ||
mapRef.events.add(eventName, popupRef, callback); | ||
}); | ||
return () => { | ||
mapRef.popups.remove(popupRef); | ||
}; | ||
} | ||
}, []); | ||
|
||
// Render the popup content and set the options | ||
useEffect(() => { | ||
root.render(children); | ||
popupRef.setOptions({ | ||
...options, | ||
content: containerRef, | ||
}); | ||
if (mapRef && isVisible && !popupRef.isOpen()) { | ||
popupRef.open(mapRef); | ||
} | ||
}, [options, children]); | ||
|
||
// Toggle the popup visibility | ||
useEffect(() => { | ||
if (mapRef) { | ||
if (isVisible && !popupRef.isOpen()) { | ||
popupRef.open(mapRef); | ||
} else if (mapRef.popups.getPopups().length && !isVisible && popupRef.isOpen()) { | ||
popupRef.close(); | ||
} | ||
} | ||
}, [isVisible]); | ||
|
||
return null; | ||
}; | ||
|
||
export default InteractivePopup; |
33 changes: 33 additions & 0 deletions
33
src/stories/MapAnnotations/Popup/Interactive/InteractivePopupExample.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { AzureMap, AzureMapsProvider, IAzureMapPopup } from 'react-azure-maps'; | ||
import { mapOptions } from '../../../../key'; | ||
import InteractivePopup from './InteractivePopup'; | ||
import PopupContent from './PopupContent'; | ||
import { useState } from 'react'; | ||
|
||
const InteractivePopupExample = ({ isVisible, options }: IAzureMapPopup) => { | ||
const [bgColor, setBgColor] = useState('white'); | ||
|
||
// click to change color randomly | ||
const changeColor = () => { | ||
const color = `#${Math.floor(Math.random() * 16777215).toString(16)}`; | ||
setBgColor(color); | ||
}; | ||
return ( | ||
<AzureMapsProvider> | ||
<div> | ||
<button onClick={changeColor} style={{ marginBottom: '10px' }}> | ||
Change popup color | ||
</button> | ||
<div className="defaultMap sb-unstyled"> | ||
<AzureMap options={mapOptions}> | ||
<InteractivePopup isVisible={isVisible} options={options}> | ||
<PopupContent bgColor={bgColor} /> | ||
</InteractivePopup> | ||
</AzureMap> | ||
</div> | ||
</div> | ||
</AzureMapsProvider> | ||
); | ||
}; | ||
|
||
export default InteractivePopupExample; |
41 changes: 41 additions & 0 deletions
41
src/stories/MapAnnotations/Popup/Interactive/PopupContent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { useState } from 'react'; | ||
|
||
const PopupContent = ({ bgColor }: { bgColor: string }) => { | ||
const [count, setCount] = useState(0); | ||
|
||
return ( | ||
<div style={{ padding: '10px', backgroundColor: bgColor, textAlign: 'center' }}> | ||
<h3>This is a counter:</h3> | ||
<p>You have clicked {count} times.</p> | ||
<button | ||
style={{ | ||
border: '1px', | ||
padding: '4px', | ||
cursor: 'pointer', | ||
backgroundColor: 'gainsboro', | ||
}} | ||
onClick={() => { | ||
setCount(count + 1); | ||
}} | ||
> | ||
Click me | ||
</button> | ||
<button | ||
style={{ | ||
border: '1px', | ||
padding: '4px', | ||
cursor: 'pointer', | ||
color: 'blue', | ||
backgroundColor: 'transparent', | ||
}} | ||
onClick={() => { | ||
setCount(0); | ||
}} | ||
> | ||
Reset | ||
</button> | ||
</div> | ||
); | ||
}; | ||
|
||
export default PopupContent; |