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

@mshafiqyajid/react-sheet

v0.1.1

Published

Accessible bottom/side sheet with snap points and swipe-to-dismiss.

Readme

@mshafiqyajid/react-sheet

Accessible bottom/side sheet with snap points and swipe-to-dismiss.

Full docs →

Features

  • Bottom, top, left, and right sheet positions
  • Swipe-to-dismiss with configurable threshold
  • Optional snap points for multi-stop sheets
  • Focus trap and body scroll lock
  • Escape key to close
  • Smooth spring slide-in/out animations
  • Reduced-motion: fade only, no slide
  • Dark mode via [data-theme="dark"]
  • Headless hook + styled component
  • Zero dependencies (peer: React ≥ 17)

Install

npm install @mshafiqyajid/react-sheet

Styled component

import { useState } from "react";
import { SheetStyled } from "@mshafiqyajid/react-sheet/styled";
import "@mshafiqyajid/react-sheet/styles.css";

function App() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open sheet</button>

      <SheetStyled
        open={open}
        onOpenChange={setOpen}
        title="Options"
        footer={
          <button onClick={() => setOpen(false)}>Done</button>
        }
      >
        <p>Sheet content goes here.</p>
      </SheetStyled>
    </>
  );
}

Side variants

<SheetStyled open={open} onOpenChange={setOpen} side="right" title="Settings">
  <p>Slides in from the right.</p>
</SheetStyled>

<SheetStyled open={open} onOpenChange={setOpen} side="top" title="Notifications">
  <p>Slides down from the top.</p>
</SheetStyled>

Snap points

<SheetStyled
  open={open}
  onOpenChange={setOpen}
  snapPoints={["40vh", "80vh"]}
  defaultSnapPoint="40vh"
  onSnapPointChange={(snap) => console.log("snapped to", snap)}
>
  <p>Drag up to expand, drag down past threshold to dismiss.</p>
</SheetStyled>

Headless hook

import { useSheet } from "@mshafiqyajid/react-sheet";

function MySheet() {
  const { isOpen, open, close, sheetProps, overlayProps, handleProps, dragY } = useSheet({
    swipeToDismiss: true,
    swipeThreshold: 60,
  });

  return (
    <>
      <button onClick={open}>Open</button>
      {isOpen && (
        <div {...overlayProps} style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)" }}>
          <div
            {...sheetProps}
            style={{
              position: "fixed",
              bottom: 0,
              left: 0,
              right: 0,
              background: "#fff",
              borderRadius: "16px 16px 0 0",
              padding: "1rem",
              transform: dragY > 0 ? `translateY(${dragY}px)` : undefined,
            }}
          >
            <div {...handleProps} style={{ width: 40, height: 5, background: "#ccc", borderRadius: 9999, margin: "0 auto 1rem" }} />
            <p>Custom headless sheet.</p>
            <button onClick={close}>Close</button>
          </div>
        </div>
      )}
    </>
  );
}

API

SheetStyled props

| Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | — | Controlled open state | | defaultOpen | boolean | false | Uncontrolled default open state | | onOpenChange | (open: boolean) => void | — | Called when open state changes | | onAfterOpen | () => void | — | Fires after open transition completes | | onAfterClose | () => void | — | Fires after close transition completes | | side | "bottom" \| "top" \| "left" \| "right" | "bottom" | Which edge the sheet slides from | | snapPoints | (string \| number)[] | — | Snap heights/widths e.g. ["40vh", "80vh"] | | defaultSnapPoint | string \| number | — | Initial snap point | | onSnapPointChange | (snap: string \| number) => void | — | Called when snap point changes | | swipeToDismiss | boolean | true | Allow drag-to-dismiss | | swipeThreshold | number | 50 | Pixel drag distance to trigger dismiss | | closeOnOverlayClick | boolean | true | Close when clicking the overlay | | closeOnEsc | boolean | true | Close on Escape key | | showHandle | boolean | true | Show the drag handle pill | | title | ReactNode | — | Sheet heading | | description | ReactNode | — | Accessible description | | footer | ReactNode | — | Footer content | | children | ReactNode | — | Body content | | initialFocusRef | RefObject<HTMLElement> | — | Element to focus on open | | finalFocusRef | RefObject<HTMLElement> | — | Element to focus on close | | lockBodyScroll | boolean | true | Lock body scroll while open | | container | HTMLElement \| null | document.body | Portal target | | className | string | — | Extra class on the panel | | style | CSSProperties | — | Inline style on the panel | | ref | forwarded | — | Forwarded to the panel div |

useSheet options

| Option | Type | Default | Description | |--------|------|---------|-------------| | open | boolean | — | Controlled open state | | defaultOpen | boolean | false | Uncontrolled default | | onOpenChange | (open: boolean) => void | — | Open state change callback | | closeOnEsc | boolean | true | Close on Escape key | | swipeToDismiss | boolean | true | Track pointer drag | | swipeThreshold | number | 50 | Pixels to trigger dismiss | | lockBodyScroll | boolean | true | Lock body scroll |

useSheet return value

| Key | Type | Description | |-----|------|-------------| | isOpen | boolean | Current open state | | open | () => void | Open the sheet | | close | () => void | Close the sheet | | toggle | () => void | Toggle open/closed | | sheetProps | SheetProps | Spread onto the panel element | | overlayProps | OverlayProps | Spread onto the overlay element | | handleProps | HandleProps | Spread onto the drag handle element | | dragY | number | Current drag offset in pixels |

CSS custom properties

| Variable | Default | Description | |----------|---------|-------------| | --rsh-fg | #18181b | Foreground text color | | --rsh-fg-muted | #71717a | Muted text color | | --rsh-bg | #ffffff | Panel background | | --rsh-bg-header | #fafafa | Header background | | --rsh-bg-footer | #fafafa | Footer background | | --rsh-border | #e4e4e7 | Border color | | --rsh-overlay-bg | rgba(0,0,0,0.45) | Overlay backdrop color | | --rsh-radius | 16px | Panel corner radius | | --rsh-handle-bg | #d4d4d8 | Drag handle color | | --rsh-duration-panel | 350ms | Panel slide duration | | --rsh-duration-overlay | 220ms | Overlay fade duration |

License

MIT