overlay-kit

repository·main·Indexed 20 days ago

https://github.com/toss/overlay-kit

A React library for the declarative management of overlays such as modals, popups, and dialogs. It provides an OverlayProvider and a set of utilities—including overlay.open, overlay.openAsync, overlay.close, and overlay.unmount—to decouple UI components from business logic and simplify the management of overlay lifecycles and state.

Tokens
30.6K
Snippets
83
Records
95
Agent score
69%

What's inside overlay-kit

  1. What is overlay-kit and why use it?

    main

    Overview

    overlay-kit is a library for declaratively managing overlays (modals, popups, dialogs) in React. It aims to solve common problems in traditional overlay management:

    Problems Solved

    1. Complexity of State Management: Eliminates the need to manually manage useState or global state for every overlay.
    2. Repetitive Event Handling: Encapsulates the logic for opening, closing, and returning results, reducing boilerplate.
    3. Lack of Reusability: Decouples UI from logic by using Promises to return values, making components easier to reuse.

    Core Goals

    • Declarative Design: Follows React's philosophy for managing UI states.
    • Increased Productivity: Allows developers to focus on UI and business logic by encapsulating event handling.
    • Extensibility: Separates UI from behavior and uses Promises for high reusability.
  2. How overlay-kit manages overlays

    main

    Instead of manually managing useState or global state for every modal and popup, overlay-kit provides a declarative way to trigger overlays.

    Key concepts:

    • Declarative Management: You define what the overlay looks like in a callback, and overlay-kit handles the when and how of its lifecycle.
    • Decoupled Logic: By using openAsync, you can separate the UI component (the Dialog) from the business logic that reacts to the user's choice (the await result).
    • Lifecycle Controls: The library provides isOpen, close, and unmount to give you fine-grained control over the mounting and visibility state without polluting your component's local state.
  3. Understand the Declarative Overlay Pattern

    main

    Traditional overlay management is often imperative, requiring developers to manually manage visibility state (e.g., using useState) and handle open/close event logic within the parent component. This leads to complex state management, repetitive event handling, and tightly coupled UI/logic.

    overlay-kit uses a Declarative Overlay Pattern where overlays are managed based on behavior rather than explicit state. Instead of managing an isOpen boolean, you call overlay.open(), which provides the necessary control functions (isOpen, close) directly to the overlay component's render function. This improves readability, reduces code duplication, and promotes co-location of logic.

    // Declarative Approach with overlay-kit
    import { overlay } from 'overlay-kit';
    
    function Overlay() {
      return (
        <Button
          onClick={() => {
            overlay.open(({ isOpen, close }) => (
              <Dialog open={isOpen} onClose={close}>
                <DialogTitle>Declarative Overlay</DialogTitle>
                <DialogActions>
                  <Button onClick={close}>OK</Button>
                  <Button onClick={close}>Cancel</Button>
                </DialogActions>
              </Dialog>
            ));
          }}
        >
          Open
        </Button>
      );
    }
  4. Difference between close and unmount

    main

    When closing an overlay, you have two options provided in the overlay.open callback:

    1. close: Runs the close animation. The overlay's state (e.g., local component state) is retained in memory. If the overlay is reopened, the previous state is restored.
    2. unmount: Immediately removes the overlay from memory, skipping any close animations. If the overlay is reopened, the state is reset.

    Best Practice: Use close to show a smooth exit animation, then use unmount to release the memory once the animation is complete.

  5. Release overlay memory using `unmount`

    main

    By default, overlays may remain in memory even after they are closed. To completely remove an overlay from the DOM and memory, use the unmount function provided in the overlay.open callback.

    Important: If your overlay has a closing animation, calling unmount immediately will interrupt the animation. You should wait for the animation to finish before calling unmount.

  6. Difference between `overlay.open` and `overlay.openAsync`

    main

    The choice between these two methods depends on whether you need to await a result from the overlay:

    • overlay.open: Used for basic overlay operations where you don't need to capture a return value from the overlay's lifecycle.
    • overlay.openAsync: Returns a Promise that resolves when the overlay is closed via the close function. This is ideal for workflows like confirmation dialogs where the calling code needs to know the user's choice.

    overlay.openAsync supports TypeScript generics to define the type of the resolved value.

    // overlay.open
    overlay.open(({ isOpen, close }) => (
      <Dialog open={isOpen} onClose={close}>
        <p>Simple overlay</p>
      </Dialog>
    ));
    
    // overlay.openAsync
    const result = await overlay.openAsync<boolean>(({ isOpen, close }) => (
      <Dialog open={isOpen} onClose={() => close(false)}>
        <Button onClick={() => close(true)}>Confirm</Button>
      </Dialog>
    ));
    
    console.log(result ? 'Yes' : 'No');
  7. How overlay-kit works and why to use it

    main

    Traditional overlay management often suffers from complex state management (using useState or global state), repetitive event handling, and tight coupling between UI and logic.

    overlay-kit solves these by:

    1. Declarative Management: Aligning with React's philosophy to manage overlays declaratively.
    2. Encapsulation: Hiding state management and event handling logic, allowing developers to focus on UI and business logic.
    3. Improved Reusability: Using a Promise-based approach (openAsync) to separate UI from the logic that consumes the overlay's result.
  8. Use Material UI Dialog with overlay-kit

    main

    When using MUI's Dialog component, map the open prop to isOpen and the onClose prop to the close function provided by the overlay.open callback. This allows overlay-kit to manage the visibility and dismissal of the MUI dialog.

    import { OverlayProvider, overlay } from 'overlay-kit';
    import Button from '@mui/material/Button';
    import Dialog from '@mui/material/Dialog';
    import DialogTitle from '@mui/material/DialogTitle';
    import DialogActions from '@mui/material/DialogActions';
    
    function App() {
      return (
        <Button
          variant="contained"
          onClick={() => {
            overlay.open(({ isOpen, close }) => (
              <Dialog open={isOpen} onClose={close}>
                <DialogTitle>Are you sure you want to continue?</DialogTitle>
                <DialogActions>
                  <Button onClick={close}>No</Button>
                  <Button onClick={close}>Yes</Button>
                </DialogActions>
              </Dialog>
            ));
          }}
        >
          Open Confirm Dialog
        </Button>
      );
    }
  9. Basic usage of overlay-kit with Chakra UI Dialog

    main

    When using Chakra v3's Dialog.Root, you must sync its open state with the isOpen state provided by overlay-kit.

    To handle automatic dismissal (like backdrop clicks or the ESC key), use the onOpenChange prop. Since onOpenChange receives an object { open: boolean }, you should call close() whenever !e.open is true.

    Ensure your application is wrapped in both ChakraProvider (with a value={defaultSystem}) and OverlayProvider.

    import { OverlayProvider, overlay } from 'overlay-kit';
    import { Button, ChakraProvider, Dialog, Portal, defaultSystem } from '@chakra-ui/react';
    
    function App() {
      return (
        <Button
          colorPalette="blue"
          onClick={() => {
            overlay.open(({ isOpen, close }) => (
              <Dialog.Root open={isOpen} onOpenChange={(e) => !e.open && close()}>
                <Portal>
                  <Dialog.Backdrop />
                  <Dialog.Positioner>
                    <Dialog.Content>
                      <Dialog.Header>
                        <Dialog.Title>Are you sure you want to continue?</Dialog.Title>
                      </Dialog.Header>
                      <Dialog.Footer gap={2}>
                        <Button variant="outline" onClick={close}>
                          No
                        </Button>
                        <Button colorPalette="blue" onClick={close}>
                          Yes
                        </Button>
                      </Dialog.Footer>
                    </Dialog.Content>
                  </Dialog.Positioner>
                </Portal>
              </Dialog.Root>
            ));
          }}
        >
          Open Confirm Dialog
        </Button>
      );
    }