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

drawer-kit

v1.0.3

Published

A declarative React drawer management library inspired by toss/overlay-kit

Readme

🎯 Drawer-Kit

React용 선언적 drawer 관리 라이브러리 - toss/overlay-kit에서 영감을 받아 제작

간단하고 직관적인 API로 drawer 컴포넌트를 관리할 수 있는 강력하고 유연한 React 라이브러리입니다. vaul을 기반으로 구축되었으며, toss/overlay-kit의 디자인에서 영감을 받았습니다.

npm version TypeScript React License: MIT

✨ 주요 기능

  • 🎨 선언적 API - 간단하고 직관적인 drawer 관리
  • 🚀 TypeScript 우선 - 완전한 TypeScript 지원과 뛰어난 개발자 경험
  • 📱 모바일 최적화 - 부드러운 모바일 상호작용을 위한 vaul 기반
  • 🎭 다양한 방향 - 상단, 하단, 좌측, 우측 drawer 지원
  • 🔄 비동기 지원 - Promise 기반 drawer 상호작용
  • 🎪 이벤트 시스템 - drawer 생명주기 (lifecycle) 에 대한 이벤트 콜백

📦 설치

npm install drawer-kit
# 또는
yarn add drawer-kit
# 또는
pnpm add drawer-kit

🚀 빠른 시작

1. Provider 설정

앱을 DrawerProvider로 감싸세요:

import { DrawerProvider } from "drawer-kit";

function App() {
  return (
    <DrawerProvider>
      <YourApp />
    </DrawerProvider>
  );
}

2. 기본 사용법

import { drawer } from "drawer-kit";

function MyComponent() {
  const openDrawer = () => {
    drawer.open(({ close, unmount }) => (
      <div style={{ padding: "20px" }}>
        <h2>안녕하세요!</h2>
        <p>간단한 drawer 예제입니다.</p>
        <button onClick={close}>닫기</button>
        <button onClick={unmount}>언마운트</button>
      </div>
    ));
  };

  return <button onClick={openDrawer}>Drawer 열기</button>;
}

3. 비동기 Drawer

import { drawer } from "drawer-kit";

function MyComponent() {
  const openAsyncDrawer = async () => {
    try {
      const result = await drawer.openAsync(({ close, unmount }) => (
        <div style={{ padding: "20px" }}>
          <h2>작업 확인</h2>
          <p>진행하시겠습니까?</p>
          <button onClick={() => close("confirmed")}>예</button>
          <button onClick={() => close("cancelled")}>아니오</button>
        </div>
      ));

      console.log("사용자 선택:", result); // 'confirmed' 또는 'cancelled'
    } catch (error) {
      console.log("취소되었습니다");
    }
  };

  return <button onClick={openAsyncDrawer}>비동기 Drawer 열기</button>;
}

📚 API 참조

핵심 API

drawer.open(controller, options?)

주어진 컨트롤러 컴포넌트로 drawer를 엽니다.

const drawerId = drawer.open(MyDrawerComponent, {
  direction: "bottom",
  modal: true,
  dismissible: true,
});

drawer.openAsync(controller, options?)

drawer를 열고 결과와 함께 Promise를 반환합니다.

const result = await drawer.openAsync(MyAsyncDrawerComponent, {
  direction: "right",
  modal: false,
});

drawer.close(drawerId)

ID로 특정 drawer를 닫습니다.

drawer.close("drawer-123");

drawer.unmount(drawerId)

ID로 특정 drawer를 언마운트합니다.

drawer.unmount("drawer-123");

drawer.closeAll()

모든 열린 drawer를 닫습니다.

drawer.closeAll();

drawer.unmountAll()

모든 열린 drawer를 언마운트합니다.

drawer.unmountAll();

DrawerOptions

| 옵션 | 타입 | 기본값 | 설명 | | ------------------ | ---------------------------------------- | --------------- | ------------------------------------- | | direction | 'top' \| 'bottom' \| 'left' \| 'right' | 'bottom' | drawer 열리는 방향 | | modal | boolean | true | 모달 여부 (배경 상호작용 차단) | | dismissible | boolean | true | 드래그나 외부 클릭으로 닫기 가능 여부 | | container | HTMLElement | document.body | drawer 포털 컨테이너 요소 | | repositionInputs | boolean | false | 키보드 표시 시 입력 필드 재배치 여부 | | onOpenChange | (open: boolean) => void | - | 열림 상태 변경 콜백 | | onClose | () => void | - | 닫힘 콜백 | | onAnimationEnd | (open: boolean) => void | - | 애니메이션 종료 콜백 |

Controller Props

컨트롤러 컴포넌트는 다음 props를 받습니다:

interface DrawerControllerProps {
  isOpen: boolean; // 열림 상태
  close: () => void; // 닫기 함수
  unmount: () => void; // 언마운트 함수
}

// 비동기 drawer용
interface DrawerAsyncControllerProps<T> {
  isOpen: boolean;
  close: (result: T) => void; // 결과와 함께 닫기
  unmount: () => void;
}

🎨 예제

방향별 예제

// 하단 drawer (기본값)
drawer.open(MyComponent, { direction: "bottom" });

// 상단 drawer
drawer.open(MyComponent, { direction: "top" });

// 좌측 drawer
drawer.open(MyComponent, { direction: "left" });

// 우측 drawer
drawer.open(MyComponent, { direction: "right" });

모달 vs 비모달

// 모달 drawer (배경 상호작용 차단)
drawer.open(MyComponent, { modal: true });

// 비모달 drawer (배경 상호작용 허용)
drawer.open(MyComponent, { modal: false });

이벤트 콜백

drawer.open(MyComponent, {
  onOpenChange: (open) => {
    console.log("상태:", open ? "열림" : "닫힘");
  },
  onClose: () => {
    console.log("닫혔습니다");
  },
  onAnimationEnd: (open) => {
    console.log("애니메이션 완료:", open ? "열림" : "닫힘");
  },
});

비닫기 가능한 Drawer

drawer.open(MyComponent, {
  dismissible: false, // 외부 클릭이나 드래그로 닫기 불가
  direction: "bottom",
});

입력 필드 재배치

drawer.open(MyComponent, {
  repositionInputs: true, // 키보드 표시 시 입력 필드 재배치
  direction: "bottom",
});

🧪 테스트

라이브러리에는 포괄적인 테스트 유틸리티가 포함되어 있습니다:

import { render, screen, fireEvent } from "@testing-library/react";
import { drawer } from "drawer-kit";

test("drawer 열기 및 닫기", () => {
  const TestComponent = () => (
    <button
      onClick={() =>
        drawer.open(({ close }) => (
          <div>
            <h1>테스트 Drawer</h1>
            <button onClick={close}>닫기</button>
          </div>
        ))
      }
    >
      Drawer 열기
    </button>
  );

  render(<TestComponent />);

  fireEvent.click(screen.getByText("Drawer 열기"));
  expect(screen.getByText("테스트 Drawer")).toBeInTheDocument();

  fireEvent.click(screen.getByText("닫기"));
  expect(screen.queryByText("테스트 Drawer")).not.toBeInTheDocument();
});

🎯 고급 사용법

커스텀 컨테이너

const customContainer = document.getElementById("drawer-container");

drawer.open(MyComponent, {
  container: customContainer,
});

다중 Drawer

// 여러 drawer 열기
const drawer1 = drawer.open(Component1, { direction: "bottom" });
const drawer2 = drawer.open(Component2, { direction: "right" });

// 특정 drawer 닫기
drawer.close(drawer1);

// 모든 drawer 닫기
drawer.closeAll();

복잡한 비동기 플로우

const handleComplexFlow = async () => {
  try {
    // 첫 번째 drawer - 사용자 확인 받기
    const confirmed = await drawer.openAsync(({ close }) => (
      <div>
        <h2>항목 삭제</h2>
        <p>이 항목을 삭제하시겠습니까?</p>
        <button onClick={() => close(true)}>삭제</button>
        <button onClick={() => close(false)}>취소</button>
      </div>
    ));

    if (!confirmed) return;

    // 두 번째 drawer - 진행 상황 표시
    const progressDrawer = drawer.open(({ close }) => (
      <div>
        <h2>삭제 중...</h2>
        <div>삭제 중입니다...</div>
        <button onClick={close}>닫기</button>
      </div>
    ));

    // 비동기 작업 시뮬레이션
    await deleteItem();
    drawer.close(progressDrawer);

    // 세 번째 drawer - 성공 표시
    await drawer.openAsync(({ close }) => (
      <div>
        <h2>성공!</h2>
        <p>삭제가 완료되었습니다.</p>
        <button onClick={() => close()}>확인</button>
      </div>
    ));
  } catch (error) {
    console.error("취소되었습니다:", error);
  }
};

🎨 스타일링

Drawer-kit은 합리적인 기본값을 제공하지만 완전한 커스터마이징을 허용합니다:

/* 커스텀 drawer 스타일 */
[data-vaul-drawer] {
  background-color: white;
  border-radius: 12px 12px 0 0;
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
}

[data-vaul-overlay] {
  background-color: rgba(0, 0, 0, 0.5);
}

/* 방향별 스타일 */
[data-vaul-drawer][data-vaul-drawer-direction="left"] {
  border-radius: 0 12px 12px 0;
}

[data-vaul-drawer][data-vaul-drawer-direction="right"] {
  border-radius: 12px 0 0 12px;
}

🔧 개발

사전 요구사항

  • Node.js 18+
  • pnpm (권장) 또는 npm

설정

# 저장소 클론
git clone https://github.com/jinseok9338/drawer-kit.git
cd drawer-kit

# 의존성 설치
pnpm install

# 개발 서버 시작
pnpm dev

# 테스트 실행
pnpm test

# 라이브러리 빌드
pnpm build

프로젝트 구조

src/
├── drawer-kit/           # 메인 라이브러리 코드
│   ├── components/       # React 컴포넌트
│   ├── context/         # React context와 hooks
│   ├── events/          # 이벤트 시스템
│   ├── types/           # TypeScript 정의
│   └── utils/           # 유틸리티 함수
├── test-ui/             # 개발 테스트 UI
└── tests/               # 테스트 파일

🤝 기여하기

기여를 환영합니다! 자세한 내용은 기여 가이드를 참조하세요.

  1. 저장소를 포크하세요
  2. 기능 브랜치를 만드세요 (git checkout -b feature/amazing-feature)
  3. 변경사항을 커밋하세요 (git commit -m '멋진 기능 추가')
  4. 브랜치에 푸시하세요 (git push origin feature/amazing-feature)
  5. Pull Request를 열어주세요

📄 라이선스

이 프로젝트는 MIT 라이선스 하에 있습니다. 자세한 내용은 LICENSE 파일을 참조하세요.

🙏 감사의 말

  • toss/overlay-kit - 우아한 API 디자인 영감을 주셔서 감사합니다
  • vaul - 부드러운 drawer 애니메이션과 모바일 상호작용을 위해
  • React - 훌륭한 프레임워크를 위해
  • TypeScript - 타입 안전성을 위해

📞 지원