@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-builderPeer dependencies
pnpm add react@^19 react-dom@^19 @janbox/storefront-ui@>=2Package 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ề
Editorxem 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:
- Truyền
data— ưu tiên render từ serialized data (JSON từ editor) - Truyền
children— nếu không truyềndata, 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() và 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
- RootNode — container gốc của canvas
- BoxNode — block container
- FlexboxNode — flex container
- FlexItemNode — flex item
- GridNode — CSS grid container
- CellNode — grid cell
Typography
- TextNode — inline/block text
- HeadingNode — h1–h6 heading
- ParagraphNode — paragraph
Media
Navigation
- LinkNode — anchor link
- ButtonNode — CTA button
Lists
- UnorderedListNode — danh sách không thứ tự
- ListItemNode — item trong danh sách
Accordion
- AccordionGroupNode — accordion container
- AccordionNode — accordion item
- AccordionSummaryNode — accordion header
- AccordionContentNode — accordion content
Tabs
- TabsNode — tabs container
- TabListNode — container chứa tab headers
- TabNode — tab header button
- TabContentNode — container chứa tab panels
- TabPanelNode — nội dung tab panel
Carousel
- SwiperNode — carousel container
- SwiperSlideNode — carousel slide
Marquee
- MarqueeNode — auto-scrolling ticker
- MarqueeItemNode — ticker item
Utilities
- CountdownTimerNode — đồng hồ đếm ngược
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 typecheckYêu cầu Node.js >= 20 và pnpm.
