Skip to content

Instantly share code, notes, and snippets.

@devoNOTbevo
Last active September 13, 2022 06:23
Show Gist options
  • Save devoNOTbevo/c38258006925533981072245b52bf62b to your computer and use it in GitHub Desktop.
Save devoNOTbevo/c38258006925533981072245b52bf62b to your computer and use it in GitHub Desktop.
SelectElement: A sample React Component that has a button that initiates actions to hover over and element and drop a pin on that element. While "dropping", elements are outlined to show visibility. It then opens a modal to initiate a workflow from there.
import { ToastContainer, toast } from 'react-toastify';
import Modal from 'react-modal';
import { useEffect, useState } from 'react';
import useScreenshot from '../../hooks/screenshot-hook';
import useWishes from '../../hooks/wishes-hook';
import { MAP_MARKER_URL } from '../../config/constants';
import 'react-toastify/dist/ReactToastify.min.css';
Modal.setAppElement(document.body);
interface ActionBarProps {
closePanels: () => void;
openPanel: () => void;
}
const cleanUpDocument = () => {
// remove borders
const borderElems = document.getElementsByClassName('show-border');
Array.from(borderElems).forEach((e) => {
e.classList.remove('show-border');
});
// remove map marker pins
const mapMarkerElems = document.getElementsByClassName('dropped-pin');
Array.from(mapMarkerElems).forEach((e) => {
e.remove();
});
// show the tab again
const tab = document.getElementById('popout-tab');
tab?.classList.remove('hidden');
};
export default function ActionBar({ openPanel, closePanels }: ActionBarProps) {
const { attachScreenshot } = useWishes();
const [image, takeScreenshot, error] = useScreenshot();
const [markerXPos, setMarkerXPos] = useState(0);
const [markerYPos, setMarkerYPos] = useState(0);
const [showBordersOnHover, setShowBordersOnHover] = useState(false);
const [targetElement, setTargetElement] = useState<HTMLElement | null>(null);
const [modalIsOpen, setModalIsOpen] = useState(false);
// workflow starts with this function
const startCreateWishWorkflow = () => {
// set the cursor to be the map marker
document.body.classList.add('map-marker-cursor');
// use state + effect to create event listeners to
// show / hide element border
setShowBordersOnHover(true);
// add a one time click event listener to initiate workflow
// and "drop" pin into body of document
window.addEventListener(
'click',
(e) => {
const clickTarget = e.target as HTMLElement;
toast.dismiss();
// 1. turn off these event listeners
setShowBordersOnHover(false);
// 2. drop a pin onto the clicked target
setTargetElement(clickTarget);
// 3. remove cursor
document.body.classList.remove('map-marker-cursor');
// 4. insert image
const image = document.createElement('img');
image.classList.add('dropped-pin');
image.style.top = '-18px'; //height of image
image.style.left = '0';
image.src = MAP_MARKER_URL;
image.crossOrigin = 'anonymous';
image.onload = (e) => {
// 6. take the screen shot - the effect for the image
// itself will take workfflow from here
takeScreenshot(() => {
// 4. set modal open to display loader using callback
setModalIsOpen(true);
});
};
image.onerror = (err) => {
console.log('Error loading map marker image: ', err);
};
clickTarget.insertAdjacentElement('afterbegin', image);
},
{ once: true }
);
};
// do a useEffect for event listeners per React recommendation
// in short, the cleanup method is best way to remove
// event listeners.
useEffect(() => {
if (!showBordersOnHover) {
return;
}
// create event listeners to show/hide element border
// use functions so you can remove them later
const mouseoverListener: EventListener = (e) => {
(e.target as HTMLElement)?.classList.add('show-border');
};
const mouseoutListener: EventListener = (e) => {
(e.target as HTMLElement)?.classList.remove('show-border');
};
document.body.addEventListener('mouseover', mouseoverListener);
document.body.addEventListener('mouseout', mouseoutListener);
return () => {
document.body.removeEventListener('mouseout', mouseoutListener, false);
document.body.removeEventListener('mouseover', mouseoverListener, false);
};
}, [showBordersOnHover]);
// workflow handlers
const handleWorkflowStart = (e: React.MouseEvent) => {
// stop propogation to keep event handlers clean
e.stopPropagation();
// show toast for user feedback
toast.info('Now drop the pin to start a new wish!');
// set the x and y for future reference
setMarkerXPos(e.clientX);
setMarkerYPos(e.clientY);
startCreateWishWorkflow();
};
const handleWorkflowEnd = () => {
openPanel();
handleRequestToClose();
};
// modal handlers
const handleAfterModalHasOpened = () => {
console.log('modal did open');
};
const handleRequestToClose = () => {
console.log('modal close requested');
setModalIsOpen(false);
cleanUpDocument();
};
const handleSubmit = () => {
console.log('submitting...');
handleWorkflowEnd();
};
return (
<div className="grid-container single-column">
<button
className="action-button button-big secondary text-light"
onClick={handleWorkflowStart}
>
Start the workflow
</button>
<ToastContainer
position="top-right"
autoClose={15000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
pauseOnHover
></ToastContainer>
<Modal
isOpen={modalIsOpen}
onAfterOpen={handleAfterModalHasOpened}
onRequestClose={handleRequestToClose}
contentLabel="Create New Wish"
className="primary-light"
>
<SomeDialog onSubmit={handleSubmit} />
</Modal>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment