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

@seojaeho/app-launcher

v0.1.11

Published

Unified app launcher menu component for CROSS ecosystem services

Downloads

1,890

Readme

@seojaeho/app-launcher

CROSS 생태계 서비스 전체에서 사용하는 통합 앱 런처 메뉴 컴포넌트.

S3에 호스팅된 글로벌 메뉴 메타데이터를 fetch하여 일관된 UI로 렌더링한다. Trigger는 headless(소비자가 자유롭게 구성), Content는 패키지가 디자인을 통제한다. Desktop에서는 Popover, Mobile에서는 Drawer로 자동 전환된다.

Install

npm install @seojaeho/app-launcher

Peer Dependencies

소비자 프로젝트에 아래 패키지가 이미 설치되어 있어야 한다.

  • react >= 18
  • react-dom >= 18
  • @tanstack/react-query >= 5

Quick Start

import {
  AppLauncher,
  AppLauncherTrigger,
  AppLauncherContent,
} from "@seojaeho/app-launcher";

function MyNav() {
  return (
    <AppLauncher env="production" theme="dark">
      <AppLauncherTrigger asChild>
        <button>Menu</button>
      </AppLauncherTrigger>
      <AppLauncherContent />
    </AppLauncher>
  );
}

별도의 CSS import 없이 스타일이 자동 적용된다.

Components

<AppLauncher>

Root 컴포넌트. Context Provider + Popover/Drawer Root 역할.

| Prop | Type | Default | Description | | ------------------ | ---------------------------------- | ---------- | ---------------------------------------------- | | env | 'dev' \| 'stage' \| 'production' | (필수) | 환경에 따라 메뉴 아이템의 URL이 결정된다 | | theme | 'dark' \| 'light' | 'dark' | 팝오버/드로어의 다크/라이트 테마 | | mobileBreakpoint | number | 768 | 이 px 이하에서 Drawer로 전환 (반응형 기준점) |

<AppLauncherTrigger>

Headless trigger. 소비자가 제공하는 엘리먼트를 trigger로 사용한다.

| Prop | Type | Default | Description | | --------- | --------- | ------- | ------------------------------------------------------ | | asChild | boolean | true | true이면 자식 엘리먼트를 trigger로 사용 (Radix 패턴) |

<AppLauncherContent>

Styled content. 패키지가 디자인을 통제한다. 2열 그리드 레이아웃.

| Prop | Type | Default | Description | | ------------ | ------------------------------ | ------- | ------------------------------- | | align | 'start' \| 'center' \| 'end' | 'end' | 팝오버 정렬 위치 (Desktop only) | | sideOffset | number | 12 | trigger와의 간격 px (Desktop) | | className | string | - | 추가 CSS 클래스 |

Responsive

  • Desktop (> mobileBreakpoint): Popover로 렌더링
  • Mobile (<= mobileBreakpoint): 하단 Drawer(Bottom Sheet)로 렌더링

소비자는 별도의 분기 처리 없이 패키지가 자동으로 전환한다.

<AppLauncher env="production" mobileBreakpoint={1024}>
  {/* 1024px 이하에서 Drawer로 전환 */}
</AppLauncher>

Usage Example

import {
  AppLauncher,
  AppLauncherTrigger,
  AppLauncherContent,
} from "@seojaeho/app-launcher";

function NavBar() {
  return (
    <AppLauncher env="production" theme="dark">
      <AppLauncherTrigger asChild>
        <button>
          <DotsGridIcon />
        </button>
      </AppLauncherTrigger>
      <AppLauncherContent align="end" sideOffset={8} />
    </AppLauncher>
  );
}

Data Source

메뉴 데이터는 S3에서 자동으로 fetch된다.

https://contents.crosstoken.io/global-menu/global-menu.json
  • @tanstack/react-queryuseQuery 사용
  • 캐시 전략: no-cache (항상 최신 데이터)
  • 메뉴 아이템은 order 필드 기준으로 정렬

useGlobalMenu Hook

패키지에서 useGlobalMenu hook도 export한다. 메뉴 데이터를 직접 사용해야 할 때:

import { useGlobalMenu } from "@seojaeho/app-launcher";

function MyComponent() {
  const { data, isLoading } = useGlobalMenu();
  // data.items: GlobalMenuItem[]
}

Styling

  • Vanilla CSS + CSS Custom Properties 사용
  • 클래스명은 al- 접두사로 네임스페이스 충돌 방지
  • injectStyle로 CSS가 JS에 포함되어 별도 CSS import 불필요
  • 테마는 data-theme 속성으로 전환

CSS Custom Properties Override

필요 시 CSS 변수를 override하여 커스텀 가능:

[data-theme="dark"] {
  --al-bg: #222;
  --al-bg-hover: #333;
}

| Variable | Description | | -------------------- | ---------------- | | --al-bg | 배경색 | | --al-bg-hover | hover 배경색 | | --al-text | 기본 텍스트 색 | | --al-text-secondary| 보조 텍스트 색 | | --al-border | 테두리 색 | | --al-shadow | 박스 그림자 | | --al-badge-bg | 뱃지 배경색 | | --al-badge-text | 뱃지 텍스트 색 | | --al-badge-new-bg | NEW 뱃지 배경색 | | --al-badge-new-text| NEW 뱃지 텍스트 색| | --al-drawer-overlay| Drawer 오버레이 색|

Types

type Environment = "dev" | "stage" | "production";

interface GlobalMenuItem {
  id: string;
  label: string;
  description: string;
  url: { dev: string; stage: string; production: string };
  iconUrl: string;
  order: number;
  type: string;
  badge: string | null;
  isNew: boolean;
}

interface GlobalMenu {
  version: string;
  items: GlobalMenuItem[];
}

Architecture

AppLauncher (Provider + Popover.Root / Drawer.Root)
├── AppLauncherTrigger (headless - 소비자 제공)
└── AppLauncherContent (styled - 패키지 통제)
    ├── Desktop → Popover.Content (2열 그리드)
    └── Mobile  → Drawer.Content  (2열 그리드)

내부적으로 useGlobalMenu hook이 S3에서 메뉴 데이터를 fetch하고, useMediaQuery로 반응형을 감지하여 Popover/Drawer를 자동 전환한다.