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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qyu/reactcmp-modal

v1.0.3

Published

Generic Modal components for react

Readme

@qyu/reactcmp-modal

React component library for modal. Includes focus-management, animations, accessibility

Base usage without animation

First include required parts

// import styles with your bundler or copy them by hand and reference in your html
import "@qyu/reactcmp-modal/style/index.global"

// append modal root
const modalroot = document.createElement("div")

// modal-root is default id for modal target, if you choose that id it will be used as default for portal targets
modalroot.setAttribute("id", "modal-root")

document.body.append(modalroot)

Your App component

function App() {
    // register layers context - required, will throw if not done
    return <CmpContextLayersFilled>
        <Page />
    </CmpContextLayersFilled>
}

Your Page component

function Page() {
    // register page as a layer - used to add accesibility, styles and control focus
    // z = -1 as page is always lower than any modal
    // active - shows if page is visible (eg. display: none), exists - shows if page is rendered at all
    // istop represents if layer is top layer or not
    const istop = useLayerStackTop({ z: -1, active: true, exists: true })
    // used to control focus
    const ref_root = r.useRef<HTMLDivElement | null>(null)

    // focus controller will save focus when istop is changed to false and restore it back when istop is changed back
    useFocusCapture({
        active: istop,
        target_new: r.useCallback(() => ref_root.current, [])
    })

    // apply layer order based customisations
    return <div ref={ref_root} aria-hidden={!istop}>
        <Toggle />
    </div>
}

Your Toggle component

function Toggle() {
    // modal show controlled declaratively
    const [show, show_set] = r.useState(false)

    return <>
        <button onClick={() => show_set(true)}>
            Hello World
        </button>

        {/* overlay is required wrapper for any modal - it registers layers, manages focus etc */}
        <CmpOverlayInstant
            show={show}

            // optional, this represents existing z index, but does not affect anything in styles or render order
            layer_z={1}
            // optional, toggle if modal goes to not displayed. It will apply overlay_inactive styles (by default just make display: none)
            layer_active={false}
            // optional, target for modal portal, in this case optional as we have element with modal-root id that will be used by default
            root={document.getElementById("modal-root")}
            // optional, custom styles
            styles={{
                overlay: "myclassname"
                // ...
            }}
        >
            <CmpFG
                // optional, required for custom modal close events to work
                show_set={show_set}

                // optional, custom styles
                styles={{
                    foreground: "myclassname",
                    // ...
                }}

                // close events conditions, all optional, here represented with their default values
                // close on foreground press
                close_onpress={true}
                // close on escape press
                close_onescape={true}
                // close on escape press event if event target is not directly foreground
                close_onescape_global={true}
            >
                <div style={{
                    position: "absolute",
                    top: "50%",
                    left: "50%",
                    transform: "translateX(-50%) translateY(-50%);",
                    background: "white",
                    width: "200px",
                    height: "200px"
                }}>
                    Foreground is focused. Press on it or press enter/space/escape to close
                </div>
            </CmpFG>
        </CmpOverlayInstant>
    </>
}

Modal with animations

function ToggleAnimFade() {
    // modal show controlled declaratively
    const [show, show_set] = r.useState(false)

    return <>
        <button onClick={() => show_set(true)}>
            Hello World
        </button>

        {/* Animated Overlay required for any animation */}
        {/* Extends instant overlay properties so optional ones will be omited to not repeat */}
        <CmpOverlayAnimated
            show={show}

            // animation moves from 0 to 1 linear. Velocity represents movement per ms
            // optional, this animation will last 1/3 seconds. This is also default value
            animation_velocity={3e-3}

            // events, all optional
            on_didhide={() => console.log("didhide")}
            on_didshow={() => console.log("didshow")}
            on_willshow={() => console.log("willshow")}
            on_willhide={() => console.log("willhide")}
        >
            {/* Also extends all FG properties they also will be omited */}
            {/* Opacty based fade animation */}
            <CmpFGAnimFade
                // lenear by default
                // allows to add custom easing with bezier curve or any other function
                easing={state => state * state}
            >
                <div style={{
                    position: "absolute",
                    top: "50%",
                    left: "50%",
                    transform: "translateX(-50%) translateY(-50%);",
                    background: "white",
                    width: "200px",
                    height: "200px"
                }}>
                    Foreground is focused. Press on it or press enter/space/escape to close
                </div>
            </CmpFGAnimFade>
        </CmpOverlayAnimated>
    </>
}

Available animation out of the box

  • CmpFGAnimFade
  • CmpFGAnimSlide

List is poor so you might want to make custom one

Custom foreground animation

// animations are signal based so need to use theese libraries
import * as sc from "@qyu/signal-core"
import * as sr from "@qyu/signal-react"

function FGAnimCustom(props: { children?: r.ReactNode }) {
    const ref = r.useRef<HTMLDivElement | null>(null)
    // get state signal from context, defined in CmpOverlayAnimated
    const animstate = useContextAnimationGet()

    // update styles
    sr.useDOMStyleMap(
        r.useCallback(() => ref.current, []),
        r.useMemo(() => {
            return sc.osignal_new_pipe(animstate, state => {
                return {
                    opacity: `${state}`
                }
            })
        }, [animstate])
    )

    // normal foreground
    return <CmpFG ref={ref}>
        {props.children}
    </CmpFG>
}