@janbox/storefront-builder 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +480 -0
  2. package/dist/editor/hooks/use-editor-state.d.ts +13 -1
  3. package/dist/editor/lib/index.d.ts +4 -1
  4. package/dist/editor/ui/dialog/dialog/helpers.d.ts +1 -1
  5. package/dist/editor/ui/dialog/dialog-close/dialog-close.d.ts +1 -1
  6. package/dist/editor.js +356 -351
  7. package/dist/{index-CBDllFpx.js → index-BU9pvpw_.js} +263 -256
  8. package/dist/index.js +428 -419
  9. package/dist/lib/accordion/README.md +39 -0
  10. package/dist/lib/accordion-content/README.md +36 -0
  11. package/dist/lib/accordion-group/README.md +106 -0
  12. package/dist/lib/accordion-summary/README.md +37 -0
  13. package/dist/lib/box/README.md +107 -0
  14. package/dist/lib/button/README.md +94 -0
  15. package/dist/lib/cell/README.md +119 -0
  16. package/dist/lib/countdown-timer/README.md +157 -0
  17. package/dist/lib/flex-item/README.md +111 -0
  18. package/dist/lib/flexbox/README.md +120 -0
  19. package/dist/lib/grid/README.md +133 -0
  20. package/dist/lib/heading/README.md +53 -0
  21. package/dist/lib/icon/README.md +98 -0
  22. package/dist/lib/image/README.md +107 -0
  23. package/dist/lib/link/README.md +111 -0
  24. package/dist/lib/list-item/README.md +31 -0
  25. package/dist/lib/marquee/README.md +111 -0
  26. package/dist/lib/marquee-item/README.md +31 -0
  27. package/dist/lib/paragraph/README.md +82 -0
  28. package/dist/lib/root/README.md +33 -0
  29. package/dist/lib/swiper/README.md +108 -0
  30. package/dist/lib/swiper-slide/README.md +32 -0
  31. package/dist/lib/tab/README.md +35 -0
  32. package/dist/lib/tab-content/README.md +35 -0
  33. package/dist/lib/tab-list/README.md +35 -0
  34. package/dist/lib/tab-panel/README.md +35 -0
  35. package/dist/lib/tabs/README.md +90 -0
  36. package/dist/lib/text/README.md +90 -0
  37. package/dist/lib/unknown/README.md +48 -0
  38. package/dist/lib/unordered-list/README.md +82 -0
  39. package/dist/lib/video/README.md +91 -0
  40. package/package.json +4 -10
  41. package/dist/editor/lib/events.d.ts +0 -5
@@ -0,0 +1,111 @@
1
+ # LinkNode
2
+
3
+ Anchor node render ra `<a>` qua React Router `Link`. Là canvas node — có thể chứa text hoặc các node con khác làm nội dung link.
4
+
5
+ ## Resolved name
6
+
7
+ `Link`
8
+
9
+ ## Configurable props
10
+
11
+ ### Top-level props
12
+
13
+ | Prop | Type | Description |
14
+ | --- | --- | --- |
15
+ | `to` | `string \| { pathname: string }` | Đường dẫn điều hướng |
16
+ | `target` | `'_self' \| '_blank' \| '_parent' \| '_top'` | Cách mở link |
17
+ | `rel` | `string` | SEO attribute, ví dụ `'nofollow noreferrer'` |
18
+ | `textVariant` | `TypographySizeVariant` | Preset typography size từ theme |
19
+
20
+ `textVariant` hỗ trợ responsive: `textVariant="sm"` xs, `md={{ textVariant: 'base' }}` desktop.
21
+
22
+ **`TypographySizeVariant`:** `'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl'`
23
+
24
+ ### `sx` props
25
+
26
+ ```ts
27
+ sx?: {
28
+ // typography (áp dụng toàn breakpoint — không responsive)
29
+ color?: string
30
+ fontStyle?: 'normal' | 'italic'
31
+ fontWeight?: number | string
32
+ textDecorationLine?: 'none' | 'underline' | 'line-through'
33
+ textAlign?: 'left' | 'center' | 'right' | 'justify'
34
+ textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize'
35
+
36
+ // spacing
37
+ paddingTop?: number
38
+ paddingRight?: number
39
+ paddingBottom?: number
40
+ paddingLeft?: number
41
+ marginTop?: number
42
+ marginRight?: number
43
+ marginBottom?: number
44
+ marginLeft?: number
45
+
46
+ // border
47
+ borderStyle?: string
48
+ borderColor?: string
49
+ borderTopWidth?: number
50
+ borderRightWidth?: number
51
+ borderBottomWidth?: number
52
+ borderLeftWidth?: number
53
+ borderTopLeftRadius?: number | string
54
+ borderTopRightRadius?: number | string
55
+ borderBottomRightRadius?: number | string
56
+ borderBottomLeftRadius?: number | string
57
+
58
+ // responsive overrides
59
+ sm?: { /* same keys above */ }
60
+ md?: { /* same keys above */ }
61
+ lg?: { /* same keys above */ }
62
+ }
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ ```tsx
68
+ import { LinkNode } from '@janbox/storefront-builder';
69
+
70
+ // Link đơn giản
71
+ <LinkNode to="/products">View all products</LinkNode>
72
+
73
+ // Mở tab mới, no-follow
74
+ <LinkNode to="https://example.com" target="_blank" rel="nofollow noreferrer">
75
+ External link
76
+ </LinkNode>
77
+
78
+ // Link dạng button (có border, padding)
79
+ <LinkNode
80
+ to="/checkout"
81
+ sx={{
82
+ paddingTop: 12,
83
+ paddingBottom: 12,
84
+ paddingLeft: 24,
85
+ paddingRight: 24,
86
+ borderTopLeftRadius: 6,
87
+ borderTopRightRadius: 6,
88
+ borderBottomLeftRadius: 6,
89
+ borderBottomRightRadius: 6,
90
+ backgroundColor: '#fa8c16',
91
+ color: '#ffffff',
92
+ }}
93
+ >
94
+ Checkout
95
+ </LinkNode>
96
+
97
+ // textVariant responsive
98
+ <LinkNode to="/sale" textVariant="sm" md={{ textVariant: 'base' }}>
99
+ Sale items
100
+ </LinkNode>
101
+ ```
102
+
103
+ ## Rules
104
+
105
+ - **Canvas**: yes — chứa node con.
106
+ - **canMoveIn**: bị block khi prop `dangerouslySetInnerHTML` đang được dùng.
107
+ - Editor preview: click bị `preventDefault` khi editor đang bật để tránh navigate.
108
+
109
+ ## Related nodes
110
+
111
+ - [`ButtonNode`](../button/README.md) — dùng khi cần CTA có style button từ theme
@@ -0,0 +1,31 @@
1
+ # ListItemNode
2
+
3
+ Một item trong `UnorderedListNode` (`li`). Là canvas node — chứa bất kỳ node con nào.
4
+
5
+ ## Resolved name
6
+
7
+ `ListItem`
8
+
9
+ ## Configurable props
10
+
11
+ Không có props riêng. Styling được control qua parent `UnorderedListNode`.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { ListItemNode, TextNode } from '@janbox/storefront-builder';
17
+
18
+ <ListItemNode>
19
+ <TextNode>Item content</TextNode>
20
+ </ListItemNode>
21
+ ```
22
+
23
+ ## Rules
24
+
25
+ - **Canvas**: yes — chứa bất kỳ node con nào.
26
+ - **Selectable**: no — chỉnh sửa qua parent `UnorderedListNode` inspector.
27
+ - **Parent**: phải là `UnorderedListNode`.
28
+
29
+ ## Related nodes
30
+
31
+ - [`UnorderedListNode`](../unordered-list/README.md) — parent list container
@@ -0,0 +1,111 @@
1
+ # MarqueeNode
2
+
3
+ Auto-scrolling ticker/marquee container dùng Embla Carousel Auto Scroll. Chỉ chấp nhận `MarqueeItemNode` children.
4
+
5
+ ## Resolved name
6
+
7
+ `Marquee`
8
+
9
+ ## Configurable props
10
+
11
+ ### Top-level props
12
+
13
+ | Prop | Type | Description |
14
+ | --- | --- | --- |
15
+ | `spaceBetween` | `number` | Khoảng cách giữa các items (px) |
16
+ | `autoScrollOptions` | `AutoScrollOptionsType` | Options từ embla-carousel-auto-scroll |
17
+ | `separatorIcon.source` | `string` | Raw SVG string cho icon phân cách giữa các items |
18
+ | `separatorIcon.size` | `number` | Kích thước separator icon (px) |
19
+ | `separatorIcon.sx.color` | `string` | Màu separator icon |
20
+
21
+ `spaceBetween` hỗ trợ responsive.
22
+
23
+ > Editor tự động set `autoScrollOptions.playOnInit = false` khi đang trong edit mode để tránh scroll khi đang drag/drop.
24
+
25
+ ### `sx` props
26
+
27
+ ```ts
28
+ sx?: {
29
+ // spacing
30
+ paddingTop?: number
31
+ paddingRight?: number
32
+ paddingBottom?: number
33
+ paddingLeft?: number
34
+ marginTop?: number
35
+ marginRight?: number
36
+ marginBottom?: number
37
+ marginLeft?: number
38
+
39
+ // border
40
+ borderStyle?: string
41
+ borderColor?: string
42
+ borderTopWidth?: number
43
+ borderRightWidth?: number
44
+ borderBottomWidth?: number
45
+ borderLeftWidth?: number
46
+ borderTopLeftRadius?: number | string
47
+ borderTopRightRadius?: number | string
48
+ borderBottomRightRadius?: number | string
49
+ borderBottomLeftRadius?: number | string
50
+
51
+ // background
52
+ backgroundColor?: string
53
+ backgroundImage?: string
54
+ backgroundPosition?: string
55
+ backgroundRepeat?: string
56
+ backgroundSize?: string
57
+ backgroundAttachment?: string
58
+ background?: string
59
+
60
+ // responsive overrides
61
+ sm?: { /* same keys above */ }
62
+ md?: { /* same keys above */ }
63
+ lg?: { /* same keys above */ }
64
+ }
65
+ ```
66
+
67
+ ## Usage
68
+
69
+ ```tsx
70
+ import { MarqueeNode, MarqueeItemNode, ImageNode } from '@janbox/storefront-builder';
71
+
72
+ // Marquee cơ bản, auto-scroll từ đầu
73
+ <MarqueeNode
74
+ spaceBetween={32}
75
+ autoScrollOptions={{ speed: 1 }}
76
+ >
77
+ <MarqueeItemNode><ImageNode src="..." alt="Logo 1" sx={{ width: 100 }} /></MarqueeItemNode>
78
+ <MarqueeItemNode><ImageNode src="..." alt="Logo 2" sx={{ width: 100 }} /></MarqueeItemNode>
79
+ <MarqueeItemNode><ImageNode src="..." alt="Logo 3" sx={{ width: 100 }} /></MarqueeItemNode>
80
+ </MarqueeNode>
81
+
82
+ // Với separator icon giữa các item
83
+ <MarqueeNode
84
+ spaceBetween={24}
85
+ separatorIcon={{
86
+ source: '<svg>...</svg>',
87
+ size: 16,
88
+ sx: { color: '#fa8c16' },
89
+ }}
90
+ >
91
+ <MarqueeItemNode>Item 1</MarqueeItemNode>
92
+ <MarqueeItemNode>Item 2</MarqueeItemNode>
93
+ </MarqueeNode>
94
+
95
+ // Responsive spacing
96
+ <MarqueeNode
97
+ spaceBetween={16}
98
+ md={{ spaceBetween: 32 }}
99
+ >
100
+ <MarqueeItemNode>...</MarqueeItemNode>
101
+ </MarqueeNode>
102
+ ```
103
+
104
+ ## Rules
105
+
106
+ - **Canvas**: yes — chứa `MarqueeItemNode` children.
107
+ - **canMoveIn**: chỉ chấp nhận `MarqueeItemNode`.
108
+
109
+ ## Related nodes
110
+
111
+ - [`MarqueeItemNode`](../marquee-item/README.md) — mỗi item trong marquee
@@ -0,0 +1,31 @@
1
+ # MarqueeItemNode
2
+
3
+ Một item trong `MarqueeNode`. Là canvas node — chứa bất kỳ node con nào.
4
+
5
+ ## Resolved name
6
+
7
+ `MarqueeItem`
8
+
9
+ ## Configurable props
10
+
11
+ Không có props riêng. Styling được control qua parent `MarqueeNode`.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { MarqueeItemNode, ImageNode, TextNode } from '@janbox/storefront-builder';
17
+
18
+ <MarqueeItemNode>
19
+ <ImageNode src="..." alt="Brand logo" sx={{ width: 80, height: 40, objectFit: 'contain' }} />
20
+ </MarqueeItemNode>
21
+ ```
22
+
23
+ ## Rules
24
+
25
+ - **Canvas**: yes — chứa bất kỳ node con nào.
26
+ - **Selectable**: no — chỉnh sửa qua parent `MarqueeNode` inspector.
27
+ - **Parent**: phải là `MarqueeNode`.
28
+
29
+ ## Related nodes
30
+
31
+ - [`MarqueeNode`](../marquee/README.md) — parent scrolling container
@@ -0,0 +1,82 @@
1
+ # ParagraphNode
2
+
3
+ Text node dành cho đoạn văn bản. Dùng chung `TextInspector` với `TextNode` nhưng không cho phép đổi tag — luôn render ra `p`.
4
+
5
+ ## Resolved name
6
+
7
+ `Paragraph`
8
+
9
+ ## Configurable props
10
+
11
+ Giống hệt `TextNode` ngoại trừ prop `as` bị loại bỏ. Xem chi tiết tại [TextNode](../text/README.md#configurable-props).
12
+
13
+ ### `sx` props
14
+
15
+ ```ts
16
+ sx?: {
17
+ // typography (áp dụng toàn breakpoint — không responsive)
18
+ color?: string
19
+ fontStyle?: 'normal' | 'italic'
20
+ fontWeight?: number | string
21
+ textDecorationLine?: 'none' | 'underline' | 'line-through'
22
+ textAlign?: 'left' | 'center' | 'right' | 'justify'
23
+ textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize'
24
+
25
+ // size
26
+ width?: number | string
27
+
28
+ // spacing
29
+ paddingTop?: number
30
+ paddingRight?: number
31
+ paddingBottom?: number
32
+ paddingLeft?: number
33
+ marginTop?: number
34
+ marginRight?: number
35
+ marginBottom?: number
36
+ marginLeft?: number
37
+
38
+ // responsive overrides
39
+ sm?: { /* same keys above */ }
40
+ md?: { /* same keys above */ }
41
+ lg?: { /* same keys above */ }
42
+ }
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ ```tsx
48
+ import { ParagraphNode } from '@janbox/storefront-builder';
49
+
50
+ // Đoạn văn đơn giản
51
+ <ParagraphNode>
52
+ Lorem ipsum dolor sit amet.
53
+ </ParagraphNode>
54
+
55
+ // Với sizeVariant responsive
56
+ <ParagraphNode sizeVariant="sm" md={{ sizeVariant: 'base' }}>
57
+ Body copy text
58
+ </ParagraphNode>
59
+
60
+ // Styled
61
+ <ParagraphNode
62
+ sx={{
63
+ color: '#555555',
64
+ textAlign: 'center',
65
+ marginTop: 8,
66
+ marginBottom: 8,
67
+ }}
68
+ >
69
+ Centered paragraph
70
+ </ParagraphNode>
71
+ ```
72
+
73
+ ## Rules
74
+
75
+ - **Canvas**: no — không chứa node con.
76
+ - **Inline editing**: có thể double-click để chỉnh nội dung trong canvas.
77
+ - **Tag**: cố định là `p`, không thể đổi.
78
+
79
+ ## Related nodes
80
+
81
+ - [`TextNode`](../text/README.md) — base component dùng chung inspector
82
+ - [`HeadingNode`](../heading/README.md) — dành cho tiêu đề với tag `h1`–`h6`
@@ -0,0 +1,33 @@
1
+ # RootNode
2
+
3
+ Top-level canvas container của toàn bộ trang. Luôn là node gốc trong `Canvas` — không thể xóa hay di chuyển.
4
+
5
+ ## Resolved name
6
+
7
+ `Root`
8
+
9
+ ## Configurable props
10
+
11
+ Không có props. Chỉ nhận `children`.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { Composer, Canvas } from '@janbox/storefront-builder';
17
+ import { RootNode } from '@janbox/storefront-builder';
18
+
19
+ // Render mode (storefront)
20
+ <Composer>
21
+ <Canvas data={savedNodes}>
22
+ <RootNode />
23
+ </Canvas>
24
+ </Composer>
25
+
26
+ // Editor mode — RootNode được Editor tự mount, không cần khai báo thủ công
27
+ ```
28
+
29
+ ## Notes
30
+
31
+ - Khi editor bật (`enabled: true`): render `div` với `minHeight: 100vh` và padding mặc định để tạo không gian kéo thả.
32
+ - Khi editor tắt (render mode): render `Fragment` — không thêm DOM wrapper.
33
+ - `RootNode` phải là node root duy nhất trong `Canvas`. Không được nest `RootNode` bên trong node khác.
@@ -0,0 +1,108 @@
1
+ # SwiperNode
2
+
3
+ Carousel/slider container dùng Embla Carousel. Chỉ chấp nhận `SwiperSlideNode` children.
4
+
5
+ ## Resolved name
6
+
7
+ `Swiper` (displayName: `Carousel`)
8
+
9
+ ## Configurable props
10
+
11
+ ### Top-level props
12
+
13
+ | Prop | Type | Description |
14
+ | --- | --- | --- |
15
+ | `slidesPerView` | `number \| 'auto'` | Số slides hiển thị cùng lúc |
16
+ | `spaceBetween` | `number` | Khoảng cách giữa các slides (px) |
17
+ | `autoplayOptions.active` | `boolean` | Bật autoplay |
18
+ | `autoplayOptions.delay` | `number` | Delay giữa các lần chuyển slide (ms) |
19
+ | `autoplayOptions.stopOnLastSnap` | `boolean` | `false` = loop, `true` = dừng ở slide cuối |
20
+
21
+ `slidesPerView` và `spaceBetween` hỗ trợ responsive.
22
+
23
+ ### `sx` props
24
+
25
+ ```ts
26
+ sx?: {
27
+ // spacing
28
+ paddingTop?: number
29
+ paddingRight?: number
30
+ paddingBottom?: number
31
+ paddingLeft?: number
32
+ marginTop?: number
33
+ marginRight?: number
34
+ marginBottom?: number
35
+ marginLeft?: number
36
+
37
+ // border
38
+ borderStyle?: string
39
+ borderColor?: string
40
+ borderTopWidth?: number
41
+ borderRightWidth?: number
42
+ borderBottomWidth?: number
43
+ borderLeftWidth?: number
44
+ borderTopLeftRadius?: number | string
45
+ borderTopRightRadius?: number | string
46
+ borderBottomRightRadius?: number | string
47
+ borderBottomLeftRadius?: number | string
48
+
49
+ // background
50
+ backgroundColor?: string
51
+ backgroundImage?: string
52
+ backgroundPosition?: string
53
+ backgroundRepeat?: string
54
+ backgroundSize?: string
55
+ backgroundAttachment?: string
56
+ background?: string
57
+
58
+ // responsive overrides
59
+ sm?: { /* same keys above */ }
60
+ md?: { /* same keys above */ }
61
+ lg?: { /* same keys above */ }
62
+ }
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ ```tsx
68
+ import { SwiperNode, SwiperSlideNode } from '@janbox/storefront-builder';
69
+
70
+ // 3 slides hiển thị, autoplay với loop
71
+ <SwiperNode
72
+ slidesPerView={3}
73
+ spaceBetween={16}
74
+ autoplayOptions={{
75
+ active: true,
76
+ delay: 3000,
77
+ stopOnLastSnap: false, // loop
78
+ }}
79
+ >
80
+ <SwiperSlideNode>Slide 1</SwiperSlideNode>
81
+ <SwiperSlideNode>Slide 2</SwiperSlideNode>
82
+ <SwiperSlideNode>Slide 3</SwiperSlideNode>
83
+ </SwiperNode>
84
+
85
+ // Responsive: 1 slide mobile, 3 slides desktop
86
+ <SwiperNode
87
+ slidesPerView={1}
88
+ spaceBetween={8}
89
+ md={{ slidesPerView: 3, spaceBetween: 24 }}
90
+ sx={{
91
+ paddingTop: 16,
92
+ paddingBottom: 16,
93
+ }}
94
+ >
95
+ <SwiperSlideNode>...</SwiperSlideNode>
96
+ <SwiperSlideNode>...</SwiperSlideNode>
97
+ <SwiperSlideNode>...</SwiperSlideNode>
98
+ </SwiperNode>
99
+ ```
100
+
101
+ ## Rules
102
+
103
+ - **Canvas**: yes — chứa `SwiperSlideNode` children.
104
+ - **canMoveIn**: chỉ chấp nhận `SwiperSlideNode`.
105
+
106
+ ## Related nodes
107
+
108
+ - [`SwiperSlideNode`](../swiper-slide/README.md) — mỗi slide item
@@ -0,0 +1,32 @@
1
+ # SwiperSlideNode
2
+
3
+ Một slide trong `SwiperNode`. Là canvas node — chứa bất kỳ node con nào.
4
+
5
+ ## Resolved name
6
+
7
+ `SwiperSlide`
8
+
9
+ ## Configurable props
10
+
11
+ Không có props riêng. Styling được control qua parent `SwiperNode`.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { SwiperSlideNode, ImageNode, TextNode } from '@janbox/storefront-builder';
17
+
18
+ <SwiperSlideNode>
19
+ <ImageNode src="..." alt="..." sx={{ width: '100%' }} />
20
+ <TextNode>Slide caption</TextNode>
21
+ </SwiperSlideNode>
22
+ ```
23
+
24
+ ## Rules
25
+
26
+ - **Canvas**: yes — chứa bất kỳ node con nào.
27
+ - **Selectable**: no — chỉnh sửa qua parent `SwiperNode` inspector.
28
+ - **Parent**: phải là `SwiperNode`.
29
+
30
+ ## Related nodes
31
+
32
+ - [`SwiperNode`](../swiper/README.md) — parent carousel container
@@ -0,0 +1,35 @@
1
+ # TabNode
2
+
3
+ Tab header button — click để switch giữa các panels.
4
+
5
+ ## Resolved name
6
+
7
+ `Tab`
8
+
9
+ ## Configurable props
10
+
11
+ ### Top-level props
12
+
13
+ | Prop | Type | Description |
14
+ | --- | --- | --- |
15
+ | `dangerouslySetInnerHTML.__html` | `string` | Text hiển thị trong tab button |
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { TabNode } from '@janbox/storefront-builder';
21
+
22
+ <TabNode dangerouslySetInnerHTML={{ __html: 'Overview' }} />
23
+ ```
24
+
25
+ ## Rules
26
+
27
+ - **Canvas**: no.
28
+ - **Selectable**: no — chỉnh sửa qua parent `TabsNode` inspector.
29
+ - **Parent**: phải là `TabListNode`.
30
+
31
+ ## Related nodes
32
+
33
+ - [`TabsNode`](../tabs/README.md) — root container
34
+ - [`TabListNode`](../tab-list/README.md) — parent container
35
+ - [`TabPanelNode`](../tab-panel/README.md) — panel tương ứng
@@ -0,0 +1,35 @@
1
+ # TabContentNode
2
+
3
+ Container cho danh sách tab panels. Chỉ chấp nhận `TabPanelNode` children.
4
+
5
+ ## Resolved name
6
+
7
+ `TabContent`
8
+
9
+ ## Configurable props
10
+
11
+ Không có props riêng. Styling được control qua parent `TabsNode`.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { TabContentNode, TabPanelNode } from '@janbox/storefront-builder';
17
+
18
+ <TabContentNode>
19
+ <TabPanelNode>Panel 1 content</TabPanelNode>
20
+ <TabPanelNode>Panel 2 content</TabPanelNode>
21
+ <TabPanelNode>Panel 3 content</TabPanelNode>
22
+ </TabContentNode>
23
+ ```
24
+
25
+ ## Rules
26
+
27
+ - **Canvas**: yes — chứa `TabPanelNode` children.
28
+ - **Selectable**: no.
29
+ - **canMoveIn**: chỉ chấp nhận `TabPanelNode`.
30
+ - **Parent**: phải là `TabsNode`.
31
+
32
+ ## Related nodes
33
+
34
+ - [`TabsNode`](../tabs/README.md) — root container
35
+ - [`TabPanelNode`](../tab-panel/README.md) — mỗi panel content
@@ -0,0 +1,35 @@
1
+ # TabListNode
2
+
3
+ Container cho danh sách tab headers. Chỉ chấp nhận `TabNode` children.
4
+
5
+ ## Resolved name
6
+
7
+ `TabList`
8
+
9
+ ## Configurable props
10
+
11
+ Không có props riêng. Styling được control qua parent `TabsNode`.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { TabListNode, TabNode } from '@janbox/storefront-builder';
17
+
18
+ <TabListNode>
19
+ <TabNode dangerouslySetInnerHTML={{ __html: 'Tab 1' }} />
20
+ <TabNode dangerouslySetInnerHTML={{ __html: 'Tab 2' }} />
21
+ <TabNode dangerouslySetInnerHTML={{ __html: 'Tab 3' }} />
22
+ </TabListNode>
23
+ ```
24
+
25
+ ## Rules
26
+
27
+ - **Canvas**: yes — chứa `TabNode` children.
28
+ - **Selectable**: no.
29
+ - **canMoveIn**: chỉ chấp nhận `TabNode`.
30
+ - **Parent**: phải là `TabsNode`.
31
+
32
+ ## Related nodes
33
+
34
+ - [`TabsNode`](../tabs/README.md) — root container
35
+ - [`TabNode`](../tab/README.md) — tab header button
@@ -0,0 +1,35 @@
1
+ # TabPanelNode
2
+
3
+ Nội dung của một tab — hiển thị khi tab tương ứng được active. Là canvas node — chứa bất kỳ node con nào.
4
+
5
+ ## Resolved name
6
+
7
+ `TabPanel`
8
+
9
+ ## Configurable props
10
+
11
+ Không có props riêng.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { TabPanelNode, ParagraphNode } from '@janbox/storefront-builder';
17
+
18
+ <TabPanelNode>
19
+ <ParagraphNode>
20
+ Tab panel content goes here. Can contain any nodes.
21
+ </ParagraphNode>
22
+ </TabPanelNode>
23
+ ```
24
+
25
+ ## Rules
26
+
27
+ - **Canvas**: yes — chứa bất kỳ node con nào.
28
+ - **Selectable**: no.
29
+ - **Parent**: phải là `TabContentNode`.
30
+
31
+ ## Related nodes
32
+
33
+ - [`TabsNode`](../tabs/README.md) — root container
34
+ - [`TabContentNode`](../tab-content/README.md) — parent container
35
+ - [`TabNode`](../tab/README.md) — tab header tương ứng