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:
SiteBuildercomponent- 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-skystudioOr inside a monorepo:
npm install --workspace web-skystudio
npm run --workspace web-skystudio buildUsage
🚀 간단한 방법 (추천)
방법 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
Section와 SectionHeader는 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를 참고하세요.
