npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@react-above/modal

v0.3.9

Published

A superior modal library for React

Downloads

5

Readme

@react-above/modal

A flexible headless modal library for your React app.

  • All commonly used features (closeOnClickOutside, closeOnEsc, Scroll and Focus locks as plugins)
  • Completely customizable and extendable (actually, it doesn't have an UI out of the box)
  • Lightweight
  • Plugin system (you can even do animations inside)
  • A11y attributes and convenient API for specifying them
  • 2 methods of rendering: children and render-prop
  • A lot of lifecycle methods available
  • Warning: NO full support for multiple/nested modals

Roadmap

  • [x] Core modal features
  • [x] Theme support
  • [x] Plugin system
  • [x] Close on ESC and click outside
  • [ ] Move plugins to separate repos
  • [ ] Theme-level lifecycle callbacks
  • [ ] Animation plugin
  • [ ] Plugin and theme repo templates
  • [ ] Better default theme
  • [ ] Test cases for everything
  • [ ] A small documentation and API reference in READMEs
  • [ ] Release v1.0.0
  • [ ] Logo and banner for react-above
  • [ ] A good comprehensive documentation on a separate website
  • [ ] Theme catalog with demos
  • [ ] Plugin catalog with demos
  • [ ] More themes

Installation

$ yarn add @react-above/modal

Usage

1. Create Modal component

/* Somewhere in your UI layer.. */

import { createModal, ScrollLockPlugin, FocusLockPlugin } from '@react-above/modal'

// you can use any theme instead of the default one
import { ThemeDefault } from '@react-above/modal-theme-default'

export const Modal = createModal({
  theme: ThemeDefault(),
  plugins: [ScrollLockPlugin(), FocusLockPlugin()],
})

2. Use anywhere

Children syntax

The most common usage method. The majority of modal libraries look like so.

import { Modal } from '@app/ui'

<Modal isOpen={isOpen} close={close}>
  <Modal.Surface>
    <Modal.Header title="My modal" close={close} />
    <Modal.Body>My modal description</Modal.Body>
  </Modal.Surface>
</Modal>

Render syntax

Also, react-above's Modal provides the render prop API for more flexibility.

It can be a simple inline-function, or a real React-component - you can freely use hooks inside.

import { Modal } from '@app/ui'

<Modal
  isOpen={isOpen}
  close={close}
  render={({ close }) => (
    <Modal.Surface>
      <Modal.Header title="My modal" close={close} />
      <Modal.Body>My modal description</Modal.Body>
    </Modal.Surface>
  )}
/>

API Reference

Shared types

type Elements = {
  html: HTMLElement
  body: HTMLElement
  screen: HTMLElement
  overlay: HTMLElement
  modal: HTMLElement
}

type LifecycleCallbacks = {
  /*
   * The Modal will wait for Promise to be resolved
   * You can use this behavior to implement animation delays
   */
  onAfterMount?: (elements: Elements) => Promise<void> | void
  onBeforeUnmount?: (elements: Elements) => Promise<void> | void

  /*
   * The DOM-postfixed callbacks are called inside useLayoutEffect
   * If you want to work with HTML nodes - this is the perfect place
   */
  onAfterMountDOM?: (elements: Elements) => void
  onBeforeUnmountDOM?: (elements: Elements) => void
}

type ModalFC = FC<ModalProps>

createModal

type Parameters = {
  /*
   * Theme is used to set up modal's Frame (screen + overlay + modal)
   * Also, Theme can extend Modal component with its specific sub-components,
   * so you can use them like Modal.Surface, Modal.Header, Modal.Body and etc.
   */
  theme: ThemeOutput

  /*
   * Plugins are used to add specific functionality to Modal
   * They have an access to Modal's elements and lifecycle callbacks
   * 
   * Some lifecycle callbacks may be asynchronous,
   * so you can implement animation delay in there
   */
  plugins?: PluginOutput[]

  /*
   * The function returning Modal's render target node
   * It's done as a function for the SSR-compatibility reasons
   */
  root?: () => HTMLElement
}

type ReturnType = ModalFC & {
  // ... custom Theme components
}

Modal

type ModalProps = {
  // no need to explain
  isOpen: boolean

  // "close" callback is used by inner functionality (like "closeOnClickOutside" and "closeOnEsc")
  close: () => void

  // the most common usage method - just pass content as a children
  children?: ReactNode

  /*
   * Render is useful for avoiding the execution of unwanted logic
   * It guarantees that your Renderer component will be called ONLY when modal is open
   * So, you can get rid of useless conditional rendering and hook calls
   * 
   * Also, ModalRenderer may be the common React component,
   * so you can safely use hooks inside
   */
  render?: ModalRenderer

  closeOnClickOutside?: boolean
  closeOnEsc?: boolean

  // title and description for a11y attributes
  aria?: Aria

  // custom root
  root?: () => HTMLElement

  // see in "Shared types" section
  ...LifecycleCallbacks
}

createPlugin

/*
 * Accepts Options type as a generic parameter
 * It defaults to "void"
 * 
 * In "build" callback, "void" transforms to "undefined",
 * to emulate "optional" parameter for better DX
 */
export const MyPlugin = createPlugin<MyPluginOptions | void>({
  build: (options: MyPluginOptions | undefined) => ({
    // LifecycleCallbacks (see in "Shared types" section)
  }),
})

A few words about multiple/nested modals

The main reasons why it's not implemented:

  • In most cases, the nested modals is an anti-pattern
  • It can cause inconvenient public API - most likely you would have to render something like ModalRoot
  • The Overlay shouldn't overlap, so it requires an additional work to be done. We would need a new type of lifecycle callbacks, and the more complicated Theme API
  • It's hard to implement and the resulting code won't look good

The current behavior:

  • You can open multiple Modals at once
  • The Overlay components will overlap - the background will become darker (in case of black transparent Overlay)
  • On click outside: the upper one Modal will be closed
  • On ESC press: ALL Modals will be closed