Skip to content

Commit

Permalink
Merge pull request #105 from Chia-Chi-Shen/storybook
Browse files Browse the repository at this point in the history
Add interactive popup example
  • Loading branch information
Chia-Chi-Shen authored Oct 16, 2024
2 parents 9caf285 + 4000731 commit a059100
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 1 deletion.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AzureMap, AzureMapsProvider, AzureMapPopup, IAzureMapPopup } from 'react-azure-maps';
import { mapOptions } from '../../../key';
import { mapOptions } from '../../../../key';

const Popup = ({ isVisible, options }: IAzureMapPopup) => {
// use position as argument would be better
Expand Down
169 changes: 169 additions & 0 deletions src/stories/MapAnnotations/Popup/Interactive/InteractivePopup.mdx
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>
);
};
`} />
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 src/stories/MapAnnotations/Popup/Interactive/InteractivePopup.tsx
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;
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 src/stories/MapAnnotations/Popup/Interactive/PopupContent.tsx
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;

0 comments on commit a059100

Please sign in to comment.