Hone logo
Hone
Problems

Interactive Modal Component in React with TypeScript

Develop a reusable and accessible modal component in React using TypeScript. This component will be a fundamental building block for modern web applications, enabling users to view important information or take actions without navigating away from the current page.

Problem Description

You are tasked with creating a React functional component called Modal that displays content within a modal overlay. This component should be highly customizable and adhere to accessibility best practices.

Key Requirements:

  1. Component Structure: The Modal component should accept children as its content, a isOpen boolean prop to control its visibility, an onClose function prop to handle closing the modal, and a title string prop for the modal's header.
  2. Visibility Control: The modal should only be rendered when isOpen is true.
  3. Content Display: The children prop should be rendered within the modal's body.
  4. Closing Mechanism:
    • A close button (e.g., an "X" icon) must be present in the modal header. Clicking this button should trigger the onClose function.
    • Clicking outside the modal content (on the overlay) should also trigger the onClose function.
    • Pressing the Escape key should also trigger the onClose function.
  5. Accessibility:
    • The modal should be focusable. When opened, focus should be managed within the modal.
    • The modal should have appropriate ARIA attributes (role="dialog", aria-modal="true", aria-labelledby).
  6. Styling: Provide basic, presentational CSS (or a mechanism to pass styles) to ensure the modal is visually distinct, overlays the page, and has a defined structure (header, body). You can use inline styles or a separate CSS file.
  7. TypeScript: All component props and internal logic must be typed using TypeScript.

Expected Behavior:

  • When isOpen is false, the modal should not be rendered.
  • When isOpen is true, the modal overlay and its content should appear.
  • Clicking the close button, clicking outside the modal content, or pressing Escape should close the modal by calling the onClose function.
  • Focus should be trapped within the modal when it's open.
  • The modal should have a title displayed in its header.

Edge Cases:

  • Empty Children: The modal should gracefully handle cases where children is empty or null.
  • Modal with no title: The component should still function correctly if the title prop is omitted or an empty string.

Examples

Example 1: Basic Modal Usage

import React, { useState } from 'react';
import Modal from './Modal'; // Assuming Modal is in ./Modal.tsx

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);

  const openModal = () => setIsModalOpen(true);
  const closeModal = () => setIsModalOpen(false);

  return (
    <div>
      <h1>My App</h1>
      <button onClick={openModal}>Open Modal</button>

      <Modal
        isOpen={isModalOpen}
        onClose={closeModal}
        title="Welcome!"
      >
        <p>This is the content of my first modal.</p>
        <p>It can contain any React elements!</p>
      </Modal>
    </div>
  );
}

export default App;

Expected Output (when "Open Modal" button is clicked):

A modal overlay appears covering the page. In the center, a modal box is displayed with:

  • A header containing the title "Welcome!" and a close button (e.g., "X").
  • A body containing the text "This is the content of my first modal." and "It can contain any React elements!".

Clicking the "X" button, clicking the overlay behind the modal, or pressing Escape will close the modal.

Example 2: Modal with Different Content

import React, { useState } from 'react';
import Modal from './Modal';

function AnotherComponent() {
  const [showInfoModal, setShowInfoModal] = useState(false);

  const openInfoModal = () => setShowInfoModal(true);
  const closeInfoModal = () => setShowInfoModal(false);

  return (
    <div>
      <button onClick={openInfoModal}>Show Info</button>
      <Modal
        isOpen={showInfoModal}
        onClose={closeInfoModal}
        title="Information"
      >
        <h2>More Details</h2>
        <ul>
          <li>Item 1</li>
          <li>Item 2</li>
        </ul>
        <button onClick={closeInfoModal}>Got It</button>
      </Modal>
    </div>
  );
}

export default AnotherComponent;

Expected Output (when "Show Info" button is clicked):

A modal overlay appears. The modal box has:

  • A header with the title "Information" and a close button.
  • A body containing "More Details", an unordered list with two items, and a "Got It" button.

Clicking the "X" button or the overlay will close the modal. Clicking the "Got It" button will also close the modal.

Example 3: Empty Modal Content

import React, { useState } from 'react';
import Modal from './Modal';

function EmptyModalExample() {
  const [isModalVisible, setIsModalVisible] = useState(false);

  const toggleModal = () => setIsModalVisible(!isModalVisible);

  return (
    <div>
      <button onClick={toggleModal}>Toggle Empty Modal</button>
      <Modal
        isOpen={isModalVisible}
        onClose={toggleModal}
        title="Empty State"
      >
        {/* No children provided */}
      </Modal>
    </div>
  );
}

export default EmptyModalExample;

Expected Output (when "Toggle Empty Modal" is clicked):

A modal overlay appears with a header "Empty State" and a close button. The body is empty. The modal should still be functional and dismissible.

Constraints

  • The Modal component should be a functional component.
  • Use React Hooks for state management and lifecycle effects (e.g., useState, useEffect).
  • The solution must be written entirely in TypeScript.
  • Do not use external modal libraries (e.g., react-modal, Material-UI modals). The goal is to build it from scratch.
  • Styling should be manageable. You can use CSS Modules, styled-components, or simple inline styles for presentation.
  • The onClose prop will always be provided.

Notes

  • Consider using ReactDOM.createPortal to render the modal outside the DOM hierarchy of its parent, which can help with z-index issues and isolation.
  • For focus management, you'll need to:
    • When the modal opens, move focus to the first focusable element within the modal (e.g., the close button or the first interactive element in children).
    • When the modal closes, return focus to the element that triggered the modal (if possible and appropriate).
    • Trap focus within the modal so users cannot tab out of it while it's open.
  • The useEffect hook will be crucial for handling the Escape key press and managing focus.
  • Think about how to structure your JSX to facilitate easy styling and accessibility.
  • Ensure the overlay dimming effect is visually distinct from the modal content area.
Loading editor...
typescript