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

@janbox/storefront-builder

v2.0.25

Published

Standalone storefront builder library extracted from craft-layers-portal

Readme

@janbox/storefront-builder

Thư viện visual page builder độc lập, xây dựng trên CraftJS. Hỗ trợ kéo-thả tạo storefront với giao diện editor đầy đủ, 33 node component có sẵn và responsive design theo breakpoint.

Cài đặt

pnpm add @janbox/storefront-builder

Peer dependencies

pnpm add react@^19 react-dom@^19 @janbox/storefront-ui@>=2

Package Exports

| Import path | Nội dung | | --- | --- | | @janbox/storefront-builder | Node components (33 nodes) | | @janbox/storefront-builder/editor | Giao diện editor đầy đủ (Editor component + toolbars) | | @janbox/storefront-builder/templates | Element groups và section templates có sẵn | | @janbox/storefront-builder/style.css | CSS bundle (bắt buộc import) |


Quick Start — Full Editor

Component Editor render giao diện visual builder hoàn chỉnh: header toolbar, sidebar thư viện component, canvas kéo-thả.

import '@janbox/storefront-builder/style.css';
import { Editor } from '@janbox/storefront-builder/editor';
import { HERO_SECTION_GROUP, FAQS_SECTION_GROUP } from '@janbox/storefront-builder/templates';
import type { Theme } from '@janbox/storefront-ui/theme';

const myTheme: Theme = {
  palette: {
    primary: {
      500: '#fa8c16',
      600: '#d46b08',
      // ...
    },
    // ...
  },
  typography: {
    base: { fontSize: '1rem', lineHeight: '1.5rem' },
    // ...
  },
};

export default function BuilderPage() {
  return (
    <Editor
      theme={myTheme}
      handlers={{
        upload: async (file) => URL.createObjectURL(file),
        submit: async (data) => {
          await savePage(data);
        },
        close: () => navigateBack(),
        saveTheme: async (theme) => {
          await api.saveTheme(theme); // optional: enables theme editing
        },
      }}
      insert={{
        groups: [HERO_SECTION_GROUP, FAQS_SECTION_GROUP],
      }}
    />
  );
}

Editor chiếm toàn bộ viewport (100vw × 100vh), nên đặt ở một route/page riêng.

Chi tiết đầy đủ về Editor xem tại editor/lib/README.md.


Quick Start — Render Only (không có editor UI)

Dùng Composer + Canvas để render một trang đã lưu, không cần giao diện editor. Đây là cách dùng cho phía storefront hiển thị cho người mua.

Canvas hỗ trợ hai chế độ render:

  1. Truyền data — ưu tiên render từ serialized data (JSON từ editor)
  2. Truyền children — nếu không truyền data, Canvas sẽ render children như JSX thông thường

Render từ serialized data

import '@janbox/storefront-builder/style.css';
import { Composer, Canvas } from '@janbox/storefront-builder';
import type { SerializedNodes } from '@janbox/storefront-builder';

const savedData: SerializedNodes = { /* JSON từ editor */ };

export default function StorefrontPage() {
  return (
    <Composer>
      <Canvas data={savedData} />
    </Composer>
  );
}

data nhận SerializedNodes object hoặc JSON string. Khi truyền data, Canvas sẽ bỏ qua children và render hoàn toàn từ dữ liệu đã serialize.

Render từ children (không có data)

import '@janbox/storefront-builder/style.css';
import { Composer, Canvas, RootNode, BoxNode, HeadingNode } from '@janbox/storefront-builder';

export default function StorefrontPage() {
  return (
    <Composer>
      <Canvas>
        <RootNode>
          <BoxNode>
            <HeadingNode text="Hello World" />
          </BoxNode>
        </RootNode>
      </Canvas>
    </Composer>
  );
}

Khi không truyền data, Canvas sử dụng children làm cây node mặc định để render.


Khái niệm cơ bản

Nodes

Mỗi phần tử trên canvas là một node — React component được đăng ký với CraftJS. Thư viện có 33 node có sẵn:

| Nhóm | Nodes | | --- | --- | | Layout | RootNode, Box, Flexbox, FlexItem, Grid, Cell | | Typography | Text, Paragraph, Heading | | Media | Image, Video, Icon | | Navigation | Link, Button | | Lists | UnorderedList, ListItem | | Tabs | Tabs, TabList, Tab, TabContent, TabPanel | | Accordion | Accordion, AccordionGroup, AccordionSummary, AccordionContent | | Carousel | Swiper, SwiperSlide | | Marquee | Marquee, MarqueeItem | | Utilities | CountdownTimer | | Internal | UnknownNode (fallback cho node không resolve được) |

Tất cả đều được export từ @janbox/storefront-builder.

Responsive Props

Tất cả style prop đều nhận plain value hoặc responsive object theo breakpoint:

// Breakpoints: xs (390px), sm (768px), md (1280px), lg (1680px)

// Plain value (áp dụng tại mọi breakpoint)
{ fontSize: '16px' }

// Responsive object (xs là base; các key khác override theo hướng tăng dần)
{ fontSize: { xs: '14px', md: '18px' } }

Breakpoint hiện tại được điều khiển qua query param ?screen=xs|sm|md|lg.

Serialized State

Trạng thái canvas là plain JSON (SerializedNodes). Dùng useComposer() để đọc và serialize:

import { useComposer } from '@janbox/storefront-builder';

function SaveButton() {
  const { query } = useComposer();

  const handleSave = () => {
    const json = query.serialize(); // JSON string dạng SerializedNodes
    // lưu vào backend
  };

  return <button onClick={handleSave}>Lưu</button>;
}

Templates

Templates là các nhóm kéo-thả có sẵn trong sidebar editor.

Sidebar Basics được tích hợp sẵn trong editor và chứa toàn bộ element cơ bản. Consumer không cần import hay truyền các nhóm này.

Section Groups

import {
  HERO_SECTION_GROUP,
  FAQS_SECTION_GROUP,
  GUARANTEE_SECTION_GROUP,
} from '@janbox/storefront-builder/templates';

Truyền vào Editor qua prop insert.groups:

<Editor
  theme={myTheme}
  handlers={{
    upload: async () => '',
    submit: () => {},
    close: () => {},
  }}
  insert={{
    groups: [HERO_SECTION_GROUP, FAQS_SECTION_GROUP],
  }}
/>

Custom Nodes

1. Tạo node component

// my-badge.node.tsx
import { defineNode, useNode } from '@janbox/storefront-builder';

type MyBadgeProps = { label: string; color?: string };

export const MyBadge = ({ label, color = '#fa8c16' }: MyBadgeProps) => {
  const { connectors } = useNode();

  return (
    <span
      ref={(el) => el && connectors.connect(el)}
      style={{ background: color, padding: '2px 8px', borderRadius: 4 }}
    >
      {label}
    </span>
  );
};

defineNode(MyBadge, {
  resolved: 'MyBadge',
  isCanvas: false,
  info: { displayName: 'Badge' },
  defaultProps: { label: 'Badge', color: '#fa8c16' },
});

2. Truyền resolver vào editor

import { MyBadge } from './my-badge.node';

<Editor
  theme={myTheme}
  handlers={{ upload: async () => '', submit: () => {}, close: () => {} }}
  resolver={{ MyBadge }}
  insert={{ groups: [] }}
/>

3. Thêm toolbar (tùy chọn)

// my-badge.toolbar.tsx
import { useNodeProps } from '@janbox/storefront-builder';

export function MyBadgeToolbar() {
  const { nodeProps, setNodeProps } = useNodeProps<MyBadgeProps>();

  return (
    <div>
      <label>Label</label>
      <input
        value={nodeProps.label}
        onChange={(e) => setNodeProps({ label: e.target.value })}
      />
    </div>
  );
}

Đăng ký toolbar trong defineNode:

defineNode(MyBadge, {
  resolved: 'MyBadge',
  info: { displayName: 'Badge' },
  related: { inspector: MyBadgeToolbar },
});

Hooks

Tất cả hooks phải được gọi bên trong cây <Composer>.

useComposer(collector?)

Wrapper nâng cao của CraftJS useEditor. Bổ sung thêm actions.duplicate()actions.move().

const { query, actions } = useComposer();

// Serialize trạng thái canvas hiện tại
const json = query.serialize();

// Duplicate một node
actions.duplicate(nodeId);

// Di chuyển một node
actions.move({ nodeId, sourceIndex: 0, destinationIndex: 2 });

useNodeProps<P>(options?)

Đọc và ghi props của node hiện tại (hoặc node chỉ định qua options.nodeId). Hỗ trợ cập nhật responsive.

const { nodeProps, setNodeProps, setNodeResponsiveProps } = useNodeProps<MyProps>();

// Set prop thường
setNodeProps({ color: '#ff0000' });

// Set responsive prop cho breakpoint cụ thể
setNodeResponsiveProps({ fontSize: '18px' }, 'md');

useNode()

Truy cập trực tiếp CraftJS node context (re-export từ @craftjs/core).

const { id, connectors, actions } = useNode();

Theme Editor

Sidebar tích hợp sẵn tab Theme cho phép xem và chỉnh sửa palette. Tính năng chỉnh sửa được kích hoạt khi truyền handlers.saveTheme:

<Editor
  theme={myTheme}
  handlers={{
    // ...
    saveTheme: async (theme) => {
      await api.updateTheme(theme);
    },
  }}
/>
  • Không truyền saveTheme → tab Theme chỉ hiển thị palette (view only)
  • Truyền saveTheme → hiện color picker cho primary/secondary + nút Save/Cancel
  • Cancel hoặc rời tab → reset về theme ban đầu
  • Save → gọi saveTheme(theme) async, có loading state

Stylesheet

Import stylesheet một lần ở entry của app:

import '@janbox/storefront-builder/style.css';

Theme Shape

Prop theme của Editor tuân theo kiểu Theme của @janbox/storefront-ui:

type Theme = {
  palette: {
    primary: Record<100 | 200 | 300 | 400 | 500 | 600 | 700 | 800, string>;
    secondary: Record<...>;
    red, orange, yellow, green, blue, violet, neutral: Record<...>;
    background: { subtle: string; default: string; emphasis: string };
    surface: { default: string };
    border: { default: string };
  };
  typography: Record<'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl', {
    fontSize: string;
    lineHeight: string;
  }>;
};

TypeScript

Package đi kèm đầy đủ file .d.ts. Các type chính:

import type {
  SerializedNodes,   // JSON state của canvas
  NodeId,            // string định danh node
  NodeTree,          // cây node
  Resolver,          // map resolvedName → component
  NodeConfig,        // config truyền vào defineNode()
} from '@janbox/storefront-builder';

Node Reference

Chi tiết props, usage và rules cho từng node:

Layout

Typography

Media

Navigation

Lists

Accordion

Tabs

Carousel

Marquee

Utilities

Internal

  • UnknownNode — fallback cho node không resolve được

Phát triển Monorepo

# Cài đặt dependencies
pnpm install

# Chạy demo app (React Router 7)
pnpm dev

# Build thư viện
pnpm build

# Type check
pnpm typecheck

Yêu cầu Node.js >= 20 và pnpm.