<div id="app"></div>
body {
--bg: #fafafa;
--fg: #0d0d0d;
--bg2: #f0f0f0;
background: var(--bg);
color: var(--fg);
font-family: sans-serif;
}
main {
display: grid;
place-items: center;
min-height: 100vh;
min-height: 100dvh;
}
.modal {
position: fixed;
inset: 0;
}
.modal-backdrop {
display: grid;
place-items: center;
background: rgba(0,0,0,0.5);
}
.modal-content {
background: var(--bg);
padding: 1rem;
margin: 1rem;
width: min(100%, 400px);
}
.modal-header {
display: flex;
margin-bottom: 1rem;
}
.modal-header i {
display: block;
padding: 0.25rem;
font-size: 1rem;
background: var(--bg2);
width: 1.25rem;
aspect-ratio: 1;
text-align: center;
cursor: pointer;
}
p {
margin-bottom: 2rem;
}
@media (max-width: 768px) {
.modal-content {
width: 100%;
height: 100%;
}
}
/*
* https://frontendeval.com/questions/modal-overlay
*
* Build a modal control and overlay
*/
const Modal = ({ isOpen, onClose, children }) => {
const ref = React.useRef(null);
React.useEffect(()=>{
const onKeyDown = (e) => {
if(e.keyCode === 27) {
onClose()
}
}
if(ref.current) {
ref.current.focus()
ref.current.addEventListener('keydown', onKeyDown);
}
return () => ref.current && ref.current.removeEventListener('keydown', onKeyDown);
}, [ref])
return (
<div className="modal modal-backdrop" onClick={onClose} ref={ref} tabIndex={'-1'}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<i onClick={onClose}>x</i>
</div>
{children}
</div>
</div>
);
};
const App = () => {
const [isModalOpen, setIsModalOpen] = React.useState(false);
const [selectedOffer, setSelectedOffer] = React.useState("");
const [acceptedOffer, setAcceptedOffer] = React.useState("");
const OFFERS = [
{ id: "offer1", label: "offer one" },
{ id: "offer2", label: "offer two" }
];
const onCtaClick = (offer) => {
setSelectedOffer(offer);
setIsModalOpen(true);
};
const GenericModalContent = ({
message = "Click the button below to accept our amazing offer!",
offer
}) => {
const onCtaClick = () => {
setAcceptedOffer(offer);
setIsModalOpen(false);
};
const ctaLabel = `Accept ${offer.label}`;
return (
<>
<p>{message}</p>
<button onClick={onCtaClick}>{ctaLabel}</button>
</>
);
};
const getModalContent = (offer) => {
const map = {
offer1: <GenericModalContent offer={offer} />,
offer2: <GenericModalContent offer={offer} />
};
return map[offer.id];
};
return (
<main>
{OFFERS.map(({ id, label }) => {
const isOfferAccepted = id === acceptedOffer.id;
const ctaLabel = `${isOfferAccepted ? "Accepted" : "Show"} ${label}`;
return (
<button
onClick={() => onCtaClick({ id, label })}
disabled={isOfferAccepted}
>
{ctaLabel}
</button>
);
})}
{isModalOpen && (
<Modal open={isModalOpen} onClose={() => setIsModalOpen(false)}>
{getModalContent(selectedOffer)}
</Modal>
)}
</main>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
View Compiled
This Pen doesn't use any external CSS resources.