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 🙏

© 2025 – Pkg Stats / Ryan Hefner

web-skystudio

v0.3.0

Published

Config-driven site builder primitives with ready-to-use components and APIs

Readme

web-skystudio

Config-driven site builder primitives used by Nover. This package exposes:

  • SiteBuilder component
  • Prebuilt sections (hero, feature-grid, story, product-showcase, media, statement, etc.)
  • Blueprint helpers to compose landing pages
  • Ready-to-use components (Header, Footer, ProductCard, Button, Input, Modal, Toast, Pagination)
  • API utilities (request, AuthApi, CartApi, OrderApi)
  • Utility hooks (useBodyScrollLock, useOnClickOutside, useEscapeToClose)
  • Utility functions (currency)

Installation

npm install web-skystudio

Or inside a monorepo:

npm install --workspace web-skystudio
npm run --workspace web-skystudio build

Usage

🚀 간단한 방법 (추천)

방법 1: 헬퍼 함수 사용

import { SiteBuilder, hero, products, media, statement } from "web-skystudio";

const sections = [
  hero({
    title: "환영합니다",
    description: "간단하게 페이지를 구성해보세요",
    actions: [{ label: "시작하기", href: "/start" }],
  }),
  products({
    title: "인기 상품",
    items: productList,
  }),
  media({
    type: "image",
    src: "/banner.jpg",
    alt: "배너",
  }),
  statement(["브랜드 슬로건", "Brand Slogan"]),
];

return <SiteBuilder sections={sections} />;

방법 2: 빌더 패턴 사용 (체이닝)

import { SiteBuilder, createPage } from "web-skystudio";

const sections = createPage()
  .hero({
    title: "환영합니다",
    actions: [{ label: "시작하기", href: "/start" }],
  })
  .products({
    title: "인기 상품",
    items: productList,
  })
  .media({
    type: "image",
    src: "/banner.jpg",
  })
  .statement(["브랜드 슬로건"])
  .build();

return <SiteBuilder sections={sections} />;

📦 직접 사용 가능한 컴포넌트

Header 컴포넌트

import { Header } from "web-skystudio";

export const Layout = () => {
  return (
    <Header
      variant="default"
      menu={[
        { key: "shop", name: "Shop", url: "/shop" },
        { key: "about", name: "About", url: "/about" },
      ]}
      logo={{ src: "/logo.png", alt: "Logo", href: "/" }}
      topText="환영합니다"
      showSearch={true}
      showCart={true}
      showLogin={true}
      loggedIn={false}
    />
  );
};

🔐 인증 기능

로그인 훅 사용

"use client";
import { useLogin } from "web-skystudio";

export const LoginForm = () => {
  const { register, handleSubmit, onSubmit, serverError } = useLogin();

  return (
    <form onSubmit={onSubmit}>
      <input {...register("email")} type="email" />
      <input {...register("password")} type="password" />
      {serverError && <p>{serverError}</p>}
      <button type="submit">로그인</button>
    </form>
  );
};

직접 API 호출

import { AuthApi } from "web-skystudio";

await AuthApi.login({
  memEmail: "[email protected]",
  memPw: "password123",
});

🌐 API 요청 유틸리티

import { request, ApiError } from "web-skystudio";

try {
  const data = await request<{ ok: boolean }>("/api/endpoint", {
    method: "POST",
    body: JSON.stringify({ ... })
  });
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`HTTP ${error.status}: ${error.message}`);
  }
}

🛒 장바구니 API

import { CartApi } from "web-skystudio";

// 장바구니에 상품 추가
await CartApi.addToCart({ pdopNo: "123", count: 2 });

// 장바구니 조회
const cart = await CartApi.getCart();
console.log(cart.items);

📦 주문 API

import { OrderApi } from "web-skystudio";

// 주문 생성
const order = await OrderApi.createOrder({
  shipName: "홍길동",
  shipPhone: "010-1234-5678",
  shipZipCode: "12345",
  shipAddress: "서울시 강남구",
  shipDetailedAddress: "123번지",
  shipMessage: "문 앞에 놔주세요",
  paymentMethod: "card",
  useReserve: 0,
});

📦 블루프린트 사용 (복잡한 페이지)

import { SiteBuilder, buildCommerceHomeSections } from "web-skystudio";

const sections = buildCommerceHomeSections({
  primaryItems: [...],
  banners: [
    { type: "image", src: "https://..." },
  ],
});

return <SiteBuilder sections={sections} />;

Styling requirements

SectionSectionHeader는 Tailwind 토큰을 기대합니다. src/app/globals.css에서 사용하는 테마 레이어(색상, 타이포)를 프로젝트 전역 스타일에 복사하거나 동일한 토큰을 선언해야 합니다.

Footer 컴포넌트

import { Footer } from "web-skystudio";

<Footer
  companyName="My Company"
  businessNumber="123-45-67890"
  address="서울특별시 강남구"
  email="[email protected]"
  logo={{ src: "/logo.svg", alt: "Logo" }}
/>;

ProductCard 컴포넌트

import { ProductCard } from "web-skystudio";

const product = {
  id: "1",
  name: "상품명",
  originalPrice: 50000,
  discountedPrice: 40000,
  images: ["/image1.jpg", "/image2.jpg"],
  href: "/product/1",
};

<ProductCard data={product} />;

Button 컴포넌트

import { Button } from "web-skystudio";

<Button variant="default" onClick={handleClick}>
  클릭하세요
</Button>

<Button variant="kakao" fullWidth>
  카카오 로그인
</Button>

Input 컴포넌트

import { Input } from "web-skystudio";

<Input type="email" placeholder="이메일" {...register("email")} />;

Modal 컴포넌트

import { Modal } from "web-skystudio";

const [isOpen, setIsOpen] = useState(false);

<Modal open={isOpen} onClose={() => setIsOpen(false)}>
  <div className="p-6">
    <h2>모달 제목</h2>
    <p>모달 내용</p>
  </div>
</Modal>;

Toast 알림

import { ToastProvider, useToast } from "web-skystudio";

// 앱 최상위에 ToastProvider 추가
<ToastProvider>
  <App />
</ToastProvider>;

// 컴포넌트에서 사용
const { addToast } = useToast();
addToast({ message: "성공했습니다!", variant: "success" });

Pagination 컴포넌트

import { Pagination } from "web-skystudio";

const [page, setPage] = useState(0);

<Pagination page={page} totalPages={10} onChange={setPage} />;

🪝 유틸리티 훅

useBodyScrollLock

import { useBodyScrollLock } from "web-skystudio";

const Modal = ({ isOpen }) => {
  useBodyScrollLock(isOpen);
  // ...
};

useOnClickOutside

import { useOnClickOutside } from "web-skystudio";

const ref = useRef<HTMLDivElement>(null);
useOnClickOutside(ref, () => setIsOpen(false));

useEscapeToClose

import { useEscapeToClose } from "web-skystudio";

useEscapeToClose(isOpen, () => setIsOpen(false));

🛠️ 유틸리티 함수

currency

import { currency } from "web-skystudio";

currency(1000); // "1,000원"
currency(1234567); // "1,234,567원"

사용 가능한 헬퍼 함수

  • hero(props, id?) - 히어로 섹션
  • products(props, id?) - 제품 쇼케이스
  • media(mediaOrProps, id?) - 미디어 섹션
  • statement(lines, id?, options?) - 문구/카피 섹션
  • story(props, id?) - 스토리 섹션
  • features(props, id?) - 기능 그리드
  • cta(props, id?) - CTA 섹션
  • faq(props, id?) - FAQ 섹션
  • logos(props, id?) - 로고 월
  • createPage() - 빌더 패턴 시작

Feeding data

블루프린트는 순수 데이터만 받습니다. DTO를 그대로 넘기지 말고 ProductShowcaseItem / SectionMedia로 변환하세요.

import { mapProductsToShowcaseItems } from "@/shared/lib/mapProductsToShowcaseItems";

const heroMedia = { type: "custom" as const, element: <MainImageSection slot="배너 1" /> };

const sections = buildCampaignLandingSections({
  hero: { title: "Motion Capsule" },
  heroMedia,
  featuredItems: mapProductsToShowcaseItems(productList),
});

Custom registry

필요 시 기본 섹션을 덮어쓸 수 있습니다.

const registry = {
  media: (props) => <MyMedia {...props} />,
};

<SiteBuilder sections={sections} registry={registry} />;

Building & publishing

npm run --workspace web-skystudio clean
npm run --workspace web-skystudio build
# npm publish --workspace web-skystudio   (레지스트리 인증 필요)

자세한 설정/어댑터 예시는 레포 루트 README와 /builder playground를 참고하세요.