<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
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js