@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.
- package/README.md +480 -0
- package/dist/editor/hooks/use-editor-state.d.ts +13 -1
- package/dist/editor/lib/index.d.ts +4 -1
- package/dist/editor/ui/dialog/dialog/helpers.d.ts +1 -1
- package/dist/editor/ui/dialog/dialog-close/dialog-close.d.ts +1 -1
- package/dist/editor.js +356 -351
- package/dist/{index-CBDllFpx.js → index-BU9pvpw_.js} +263 -256
- package/dist/index.js +428 -419
- package/dist/lib/accordion/README.md +39 -0
- package/dist/lib/accordion-content/README.md +36 -0
- package/dist/lib/accordion-group/README.md +106 -0
- package/dist/lib/accordion-summary/README.md +37 -0
- package/dist/lib/box/README.md +107 -0
- package/dist/lib/button/README.md +94 -0
- package/dist/lib/cell/README.md +119 -0
- package/dist/lib/countdown-timer/README.md +157 -0
- package/dist/lib/flex-item/README.md +111 -0
- package/dist/lib/flexbox/README.md +120 -0
- package/dist/lib/grid/README.md +133 -0
- package/dist/lib/heading/README.md +53 -0
- package/dist/lib/icon/README.md +98 -0
- package/dist/lib/image/README.md +107 -0
- package/dist/lib/link/README.md +111 -0
- package/dist/lib/list-item/README.md +31 -0
- package/dist/lib/marquee/README.md +111 -0
- package/dist/lib/marquee-item/README.md +31 -0
- package/dist/lib/paragraph/README.md +82 -0
- package/dist/lib/root/README.md +33 -0
- package/dist/lib/swiper/README.md +108 -0
- package/dist/lib/swiper-slide/README.md +32 -0
- package/dist/lib/tab/README.md +35 -0
- package/dist/lib/tab-content/README.md +35 -0
- package/dist/lib/tab-list/README.md +35 -0
- package/dist/lib/tab-panel/README.md +35 -0
- package/dist/lib/tabs/README.md +90 -0
- package/dist/lib/text/README.md +90 -0
- package/dist/lib/unknown/README.md +48 -0
- package/dist/lib/unordered-list/README.md +82 -0
- package/dist/lib/video/README.md +91 -0
- package/package.json +4 -10
- package/dist/editor/lib/events.d.ts +0 -5
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# AccordionNode
|
|
2
|
+
|
|
3
|
+
Một accordion item. Chứa `AccordionSummaryNode` (header) và `AccordionContentNode` (nội dung). Không selectable — chỉnh sửa qua parent `AccordionGroupNode`.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`Accordion`
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
Không có props riêng. Tất cả styling được control qua `AccordionGroupNode`.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { AccordionNode, AccordionSummaryNode, AccordionContentNode } from '@janbox/storefront-builder';
|
|
17
|
+
|
|
18
|
+
<AccordionNode>
|
|
19
|
+
<AccordionSummaryNode>
|
|
20
|
+
<TextNode>Question title</TextNode>
|
|
21
|
+
</AccordionSummaryNode>
|
|
22
|
+
<AccordionContentNode>
|
|
23
|
+
<TextNode>Answer content</TextNode>
|
|
24
|
+
</AccordionContentNode>
|
|
25
|
+
</AccordionNode>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
- **Canvas**: no.
|
|
31
|
+
- **Selectable**: no — user không thể select trực tiếp node này trong editor.
|
|
32
|
+
- **Parent**: phải là `AccordionGroupNode`.
|
|
33
|
+
- **Children**: phải có `AccordionSummaryNode` và `AccordionContentNode`.
|
|
34
|
+
|
|
35
|
+
## Related nodes
|
|
36
|
+
|
|
37
|
+
- [`AccordionGroupNode`](../accordion-group/README.md) — parent container
|
|
38
|
+
- [`AccordionSummaryNode`](../accordion-summary/README.md) — header
|
|
39
|
+
- [`AccordionContentNode`](../accordion-content/README.md) — content area
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# AccordionContentNode
|
|
2
|
+
|
|
3
|
+
Content area của accordion item — phần expand/collapse khi click vào header. Là canvas node — chứa bất kỳ node con nào.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`AccordionContent`
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
Không có props riêng. Padding được config qua `AccordionGroupNode.accordionContentProps.sx`.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { AccordionContentNode, ParagraphNode } from '@janbox/storefront-builder';
|
|
17
|
+
|
|
18
|
+
<AccordionContentNode>
|
|
19
|
+
<ParagraphNode>
|
|
20
|
+
Answer content goes here. Can contain any nodes.
|
|
21
|
+
</ParagraphNode>
|
|
22
|
+
</AccordionContentNode>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Rules
|
|
26
|
+
|
|
27
|
+
- **Canvas**: yes — chứa bất kỳ node con nào.
|
|
28
|
+
- **Selectable**: no.
|
|
29
|
+
- **canMoveOut**: no — không thể kéo ra khỏi `AccordionNode`.
|
|
30
|
+
- **Parent**: phải là `AccordionNode`.
|
|
31
|
+
|
|
32
|
+
## Related nodes
|
|
33
|
+
|
|
34
|
+
- [`AccordionNode`](../accordion/README.md) — parent item
|
|
35
|
+
- [`AccordionGroupNode`](../accordion-group/README.md) — root container
|
|
36
|
+
- [`AccordionSummaryNode`](../accordion-summary/README.md) — header
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# AccordionGroupNode
|
|
2
|
+
|
|
3
|
+
Container cho danh sách accordion items. Quản lý open mode (single/multiple) và styling chung cho tất cả items.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`AccordionGroup` (displayName: `Accordion`)
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
### Top-level props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Description |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `openMode` | `'single' \| 'multiple'` | Single: chỉ 1 item mở cùng lúc. Multiple: nhiều items mở cùng lúc |
|
|
16
|
+
| `accordionSummaryProps.sx` | `StyledCSS` | Styling áp dụng cho tất cả accordion headers |
|
|
17
|
+
| `accordionContentProps.sx` | `StyledCSS` | Styling áp dụng cho tất cả accordion content areas |
|
|
18
|
+
|
|
19
|
+
### `sx` props
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
sx?: {
|
|
23
|
+
// layout
|
|
24
|
+
width?: number | string
|
|
25
|
+
rowGap?: number // khoảng cách giữa các items
|
|
26
|
+
|
|
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
|
+
// responsive overrides
|
|
38
|
+
sm?: { /* same keys above */ }
|
|
39
|
+
md?: { /* same keys above */ }
|
|
40
|
+
lg?: { /* same keys above */ }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Styling cho headers (accordionSummaryProps.sx)
|
|
44
|
+
accordionSummaryProps?: {
|
|
45
|
+
sx?: {
|
|
46
|
+
paddingTop?: number
|
|
47
|
+
paddingRight?: number
|
|
48
|
+
paddingBottom?: number
|
|
49
|
+
paddingLeft?: number
|
|
50
|
+
// ... responsive
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Styling cho content (accordionContentProps.sx)
|
|
55
|
+
accordionContentProps?: {
|
|
56
|
+
sx?: {
|
|
57
|
+
paddingTop?: number
|
|
58
|
+
paddingRight?: number
|
|
59
|
+
paddingBottom?: number
|
|
60
|
+
paddingLeft?: number
|
|
61
|
+
// ... responsive
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { AccordionGroupNode, AccordionNode } from '@janbox/storefront-builder';
|
|
70
|
+
|
|
71
|
+
// Single open mode
|
|
72
|
+
<AccordionGroupNode
|
|
73
|
+
openMode="single"
|
|
74
|
+
sx={{ rowGap: 8 }}
|
|
75
|
+
accordionSummaryProps={{ sx: { paddingTop: 12, paddingBottom: 12 } }}
|
|
76
|
+
accordionContentProps={{ sx: { paddingTop: 16, paddingBottom: 16 } }}
|
|
77
|
+
>
|
|
78
|
+
<AccordionNode>...</AccordionNode>
|
|
79
|
+
<AccordionNode>...</AccordionNode>
|
|
80
|
+
</AccordionGroupNode>
|
|
81
|
+
|
|
82
|
+
// Multiple items có thể mở cùng lúc
|
|
83
|
+
<AccordionGroupNode
|
|
84
|
+
openMode="multiple"
|
|
85
|
+
sx={{
|
|
86
|
+
width: '100%',
|
|
87
|
+
rowGap: 4,
|
|
88
|
+
md: { rowGap: 8 },
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<AccordionNode>...</AccordionNode>
|
|
92
|
+
<AccordionNode>...</AccordionNode>
|
|
93
|
+
<AccordionNode>...</AccordionNode>
|
|
94
|
+
</AccordionGroupNode>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Rules
|
|
98
|
+
|
|
99
|
+
- **Canvas**: yes — chứa `AccordionNode` children.
|
|
100
|
+
- **canMoveIn**: chỉ chấp nhận `AccordionNode`.
|
|
101
|
+
|
|
102
|
+
## Related nodes
|
|
103
|
+
|
|
104
|
+
- [`AccordionNode`](../accordion/README.md) — item bên trong group
|
|
105
|
+
- [`AccordionSummaryNode`](../accordion-summary/README.md) — header của mỗi item
|
|
106
|
+
- [`AccordionContentNode`](../accordion-content/README.md) — content area của mỗi item
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# AccordionSummaryNode
|
|
2
|
+
|
|
3
|
+
Header của accordion item — phần luôn hiển thị, click để toggle open/close. Là canvas node — chứa text/icon/node con.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`AccordionSummary`
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
Không có props riêng. Padding được config qua `AccordionGroupNode.accordionSummaryProps.sx`.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { AccordionSummaryNode, TextNode, IconNode } from '@janbox/storefront-builder';
|
|
17
|
+
|
|
18
|
+
<AccordionSummaryNode>
|
|
19
|
+
<FlexboxNode sx={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
|
20
|
+
<TextNode>Question title</TextNode>
|
|
21
|
+
<IconNode source="..." size={16} />
|
|
22
|
+
</FlexboxNode>
|
|
23
|
+
</AccordionSummaryNode>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
- **Canvas**: yes — chứa bất kỳ node con nào.
|
|
29
|
+
- **Selectable**: no.
|
|
30
|
+
- **canMoveOut**: no — không thể kéo ra khỏi `AccordionNode`.
|
|
31
|
+
- **Parent**: phải là `AccordionNode`.
|
|
32
|
+
|
|
33
|
+
## Related nodes
|
|
34
|
+
|
|
35
|
+
- [`AccordionNode`](../accordion/README.md) — parent item
|
|
36
|
+
- [`AccordionGroupNode`](../accordion-group/README.md) — root container
|
|
37
|
+
- [`AccordionContentNode`](../accordion-content/README.md) — content area
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# BoxNode
|
|
2
|
+
|
|
3
|
+
Block container đa năng (render ra `div`). Là canvas node — chứa bất kỳ node con nào. Dùng để nhóm các element, tạo section, hay làm wrapper có background/border.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`Box` (displayName: `Block`)
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
### Top-level props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Description |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `container` | `boolean` | Bật max-width container constraint |
|
|
16
|
+
|
|
17
|
+
### `sx` props
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
sx?: {
|
|
21
|
+
// size
|
|
22
|
+
width?: number | string
|
|
23
|
+
height?: number | string
|
|
24
|
+
|
|
25
|
+
// spacing
|
|
26
|
+
paddingTop?: number
|
|
27
|
+
paddingRight?: number
|
|
28
|
+
paddingBottom?: number
|
|
29
|
+
paddingLeft?: number
|
|
30
|
+
marginTop?: number
|
|
31
|
+
marginRight?: number
|
|
32
|
+
marginBottom?: number
|
|
33
|
+
marginLeft?: number
|
|
34
|
+
|
|
35
|
+
// border
|
|
36
|
+
borderStyle?: string
|
|
37
|
+
borderColor?: string
|
|
38
|
+
borderTopWidth?: number
|
|
39
|
+
borderRightWidth?: number
|
|
40
|
+
borderBottomWidth?: number
|
|
41
|
+
borderLeftWidth?: number
|
|
42
|
+
borderTopLeftRadius?: number | string
|
|
43
|
+
borderTopRightRadius?: number | string
|
|
44
|
+
borderBottomRightRadius?: number | string
|
|
45
|
+
borderBottomLeftRadius?: number | string
|
|
46
|
+
|
|
47
|
+
// background
|
|
48
|
+
backgroundColor?: string
|
|
49
|
+
backgroundImage?: string
|
|
50
|
+
backgroundPosition?: string
|
|
51
|
+
backgroundRepeat?: string
|
|
52
|
+
backgroundSize?: string
|
|
53
|
+
backgroundAttachment?: string
|
|
54
|
+
background?: string
|
|
55
|
+
|
|
56
|
+
// responsive overrides
|
|
57
|
+
sm?: { /* same keys above */ }
|
|
58
|
+
md?: { /* same keys above */ }
|
|
59
|
+
lg?: { /* same keys above */ }
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { BoxNode } from '@janbox/storefront-builder';
|
|
67
|
+
|
|
68
|
+
// Wrapper đơn giản
|
|
69
|
+
<BoxNode sx={{ padding: 16 }}>
|
|
70
|
+
<TextNode>Hello</TextNode>
|
|
71
|
+
</BoxNode>
|
|
72
|
+
|
|
73
|
+
// Section có background
|
|
74
|
+
<BoxNode
|
|
75
|
+
sx={{
|
|
76
|
+
backgroundColor: '#f5f5f5',
|
|
77
|
+
paddingTop: 48,
|
|
78
|
+
paddingBottom: 48,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
...
|
|
82
|
+
</BoxNode>
|
|
83
|
+
|
|
84
|
+
// Container với responsive padding
|
|
85
|
+
<BoxNode
|
|
86
|
+
container
|
|
87
|
+
sx={{
|
|
88
|
+
paddingTop: 24,
|
|
89
|
+
paddingBottom: 24,
|
|
90
|
+
md: {
|
|
91
|
+
paddingTop: 48,
|
|
92
|
+
paddingBottom: 48,
|
|
93
|
+
},
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
...
|
|
97
|
+
</BoxNode>
|
|
98
|
+
|
|
99
|
+
// Box ẩn trên mobile, hiện trên desktop — dùng DisplayOn (qua editor)
|
|
100
|
+
// Có thể kết hợp với sx.width/height để tạo spacer cố định
|
|
101
|
+
<BoxNode sx={{ width: '100%', height: 32, md: { height: 64 } }} />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Rules
|
|
105
|
+
|
|
106
|
+
- **Canvas**: yes — chứa bất kỳ node con nào.
|
|
107
|
+
- **canMoveIn**: không giới hạn node type.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# ButtonNode
|
|
2
|
+
|
|
3
|
+
CTA button node dùng component `Button` từ `@janbox/storefront-ui`. Có thể render như button thường hoặc link button qua prop `anchor`.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`Button`
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
### Top-level props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Description |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `dangerouslySetInnerHTML.__html` | `string` | Text hiển thị trong button |
|
|
16
|
+
| `color` | `ColorVariant` | Màu theme của button |
|
|
17
|
+
| `variant` | `ButtonVariant` | Kiểu hiển thị của button |
|
|
18
|
+
| `size` | `ButtonSizeVariant` | Kích thước button |
|
|
19
|
+
| `anchor.to` | `string \| { pathname: string }` | Link destination |
|
|
20
|
+
| `anchor.target` | `'_self' \| '_blank' \| '_parent' \| '_top'` | Cách mở link |
|
|
21
|
+
|
|
22
|
+
`size` hỗ trợ responsive: `size="sm"` xs, `md={{ size: 'lg' }}` desktop.
|
|
23
|
+
|
|
24
|
+
**`ColorVariant`:** `'primary' | 'secondary' | 'green' | 'red' | 'orange' | 'blue' | 'neutral'`
|
|
25
|
+
|
|
26
|
+
**`ButtonVariant`:** `'contained' | 'outlined' | 'text'`
|
|
27
|
+
|
|
28
|
+
**`ButtonSizeVariant`:** `'sm' | 'md' | 'lg'`
|
|
29
|
+
|
|
30
|
+
### `sx` props
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
sx?: {
|
|
34
|
+
// size
|
|
35
|
+
width?: number | string
|
|
36
|
+
|
|
37
|
+
// spacing (margin only)
|
|
38
|
+
marginTop?: number
|
|
39
|
+
marginRight?: number
|
|
40
|
+
marginBottom?: number
|
|
41
|
+
marginLeft?: number
|
|
42
|
+
|
|
43
|
+
// responsive overrides
|
|
44
|
+
sm?: { /* same keys above */ }
|
|
45
|
+
md?: { /* same keys above */ }
|
|
46
|
+
lg?: { /* same keys above */ }
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { ButtonNode } from '@janbox/storefront-builder';
|
|
54
|
+
|
|
55
|
+
// Button cơ bản
|
|
56
|
+
<ButtonNode
|
|
57
|
+
color="primary"
|
|
58
|
+
variant="contained"
|
|
59
|
+
size="md"
|
|
60
|
+
dangerouslySetInnerHTML={{ __html: 'Buy now' }}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
// Link button
|
|
64
|
+
<ButtonNode
|
|
65
|
+
color="primary"
|
|
66
|
+
variant="contained"
|
|
67
|
+
size="lg"
|
|
68
|
+
anchor={{ to: '/checkout', target: '_self' }}
|
|
69
|
+
dangerouslySetInnerHTML={{ __html: 'Checkout' }}
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
// Responsive size + width
|
|
73
|
+
<ButtonNode
|
|
74
|
+
color="orange"
|
|
75
|
+
variant="outlined"
|
|
76
|
+
size="sm"
|
|
77
|
+
md={{ size: 'lg' }}
|
|
78
|
+
sx={{
|
|
79
|
+
width: '100%',
|
|
80
|
+
marginTop: 16,
|
|
81
|
+
md: {
|
|
82
|
+
width: 'fit-content',
|
|
83
|
+
marginTop: 24,
|
|
84
|
+
},
|
|
85
|
+
}}
|
|
86
|
+
dangerouslySetInnerHTML={{ __html: 'View products' }}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Rules
|
|
91
|
+
|
|
92
|
+
- **Canvas**: no — không chứa node con.
|
|
93
|
+
- Khi editor đang bật, `anchor` bị unset lúc render để tránh navigate trong canvas.
|
|
94
|
+
- Nội dung button được chỉnh qua `dangerouslySetInnerHTML.__html` trong inspector.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# CellNode
|
|
2
|
+
|
|
3
|
+
Một ô (column) trong `GridNode`. Hỗ trợ `colSpan` để chiếm nhiều cột. Là canvas node — chứa bất kỳ node con nào.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`Cell`
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
### Top-level props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Description |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `colSpan` | `number` | Số cột mà cell chiếm (`grid-column: span colSpan`) |
|
|
16
|
+
|
|
17
|
+
`colSpan` hỗ trợ responsive: `colSpan={1}` cho mobile, `md={{ colSpan: 2 }}` cho desktop.
|
|
18
|
+
|
|
19
|
+
### `sx` props
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
sx?: {
|
|
23
|
+
// size
|
|
24
|
+
height?: number | string
|
|
25
|
+
|
|
26
|
+
// order (thứ tự hiển thị trong grid)
|
|
27
|
+
order?: number | string
|
|
28
|
+
|
|
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 { GridNode, CellNode } from '@janbox/storefront-builder';
|
|
71
|
+
|
|
72
|
+
// Cell mặc định (1 cột)
|
|
73
|
+
<GridNode cols={3}>
|
|
74
|
+
<CellNode>Content A</CellNode>
|
|
75
|
+
<CellNode>Content B</CellNode>
|
|
76
|
+
<CellNode>Content C</CellNode>
|
|
77
|
+
</GridNode>
|
|
78
|
+
|
|
79
|
+
// colSpan: cell chiếm 2/3 cột, cell còn lại chiếm 1/3
|
|
80
|
+
<GridNode cols={3}>
|
|
81
|
+
<CellNode colSpan={2}>Wide content</CellNode>
|
|
82
|
+
<CellNode colSpan={1}>Narrow content</CellNode>
|
|
83
|
+
</GridNode>
|
|
84
|
+
|
|
85
|
+
// colSpan responsive: full-width mobile, 2 cột trên desktop
|
|
86
|
+
<GridNode cols={1} md={{ cols: 3 }}>
|
|
87
|
+
<CellNode colSpan={1} md={{ colSpan: 2 }}>
|
|
88
|
+
Wide on desktop
|
|
89
|
+
</CellNode>
|
|
90
|
+
<CellNode colSpan={1}>
|
|
91
|
+
Normal
|
|
92
|
+
</CellNode>
|
|
93
|
+
</GridNode>
|
|
94
|
+
|
|
95
|
+
// sx responsive
|
|
96
|
+
<CellNode
|
|
97
|
+
colSpan={1}
|
|
98
|
+
sx={{
|
|
99
|
+
paddingTop: 12,
|
|
100
|
+
paddingBottom: 12,
|
|
101
|
+
backgroundColor: '#fff',
|
|
102
|
+
md: {
|
|
103
|
+
paddingTop: 24,
|
|
104
|
+
paddingBottom: 24,
|
|
105
|
+
},
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
...
|
|
109
|
+
</CellNode>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Rules
|
|
113
|
+
|
|
114
|
+
- **Canvas**: yes — chứa bất kỳ node con nào.
|
|
115
|
+
- **Parent**: phải là `GridNode`.
|
|
116
|
+
|
|
117
|
+
## Related nodes
|
|
118
|
+
|
|
119
|
+
- [`GridNode`](../grid/README.md) — parent bắt buộc của `CellNode`
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# CountdownTimerNode
|
|
2
|
+
|
|
3
|
+
Đồng hồ đếm ngược đến một mốc thời gian. Hiển thị các time units (days, hours, minutes, seconds...) kèm caption.
|
|
4
|
+
|
|
5
|
+
## Resolved name
|
|
6
|
+
|
|
7
|
+
`CountdownTimer`
|
|
8
|
+
|
|
9
|
+
## Configurable props
|
|
10
|
+
|
|
11
|
+
### Top-level props
|
|
12
|
+
|
|
13
|
+
| Prop | Type | Description |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `date` | `Date` | Thời điểm đích để đếm ngược đến |
|
|
16
|
+
| `units` | `{ value: TimeUnit; caption?: string }[]` | Danh sách time units hiển thị, theo thứ tự |
|
|
17
|
+
| `hideAfterExpired` | `boolean` | Ẩn toàn bộ component sau khi hết giờ |
|
|
18
|
+
|
|
19
|
+
**`TimeUnit`:** `'months' | 'weeks' | 'days' | 'hours' | 'minutes' | 'seconds'`
|
|
20
|
+
|
|
21
|
+
### `unitItemProps.sx` — Styling cho mỗi unit box
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
unitItemProps?: {
|
|
25
|
+
sx?: {
|
|
26
|
+
width?: number | string
|
|
27
|
+
rowGap?: number // khoảng cách giữa số và caption
|
|
28
|
+
paddingTop?: number
|
|
29
|
+
paddingRight?: number
|
|
30
|
+
paddingBottom?: number
|
|
31
|
+
paddingLeft?: number
|
|
32
|
+
backgroundColor?: string
|
|
33
|
+
borderTopLeftRadius?: number | string
|
|
34
|
+
borderTopRightRadius?: number | string
|
|
35
|
+
borderBottomLeftRadius?: number | string
|
|
36
|
+
borderBottomRightRadius?: number | string
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `timeProps` — Styling cho số đếm
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
timeProps?: {
|
|
45
|
+
sizeVariant?: TypographySizeVariant // responsive
|
|
46
|
+
sx?: {
|
|
47
|
+
color?: string
|
|
48
|
+
fontWeight?: number | string
|
|
49
|
+
fontStyle?: 'normal' | 'italic'
|
|
50
|
+
textDecorationLine?: 'none' | 'underline' | 'line-through'
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `captionProps` — Styling cho caption (label dưới số)
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
captionProps?: {
|
|
59
|
+
sizeVariant?: TypographySizeVariant // responsive
|
|
60
|
+
sx?: {
|
|
61
|
+
color?: string
|
|
62
|
+
fontWeight?: number | string
|
|
63
|
+
fontStyle?: 'normal' | 'italic'
|
|
64
|
+
textDecorationLine?: 'none' | 'underline' | 'line-through'
|
|
65
|
+
textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `sx` props (outer container)
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
sx?: {
|
|
74
|
+
width?: number | string
|
|
75
|
+
flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse'
|
|
76
|
+
|
|
77
|
+
// spacing
|
|
78
|
+
paddingTop?: number
|
|
79
|
+
paddingRight?: number
|
|
80
|
+
paddingBottom?: number
|
|
81
|
+
paddingLeft?: number
|
|
82
|
+
marginTop?: number
|
|
83
|
+
marginRight?: number
|
|
84
|
+
marginBottom?: number
|
|
85
|
+
marginLeft?: number
|
|
86
|
+
|
|
87
|
+
// border
|
|
88
|
+
borderStyle?: string
|
|
89
|
+
borderColor?: string
|
|
90
|
+
borderTopWidth?: number
|
|
91
|
+
borderRightWidth?: number
|
|
92
|
+
borderBottomWidth?: number
|
|
93
|
+
borderLeftWidth?: number
|
|
94
|
+
borderTopLeftRadius?: number | string
|
|
95
|
+
borderTopRightRadius?: number | string
|
|
96
|
+
borderBottomRightRadius?: number | string
|
|
97
|
+
borderBottomLeftRadius?: number | string
|
|
98
|
+
|
|
99
|
+
// background
|
|
100
|
+
backgroundColor?: string
|
|
101
|
+
backgroundImage?: string
|
|
102
|
+
backgroundPosition?: string
|
|
103
|
+
backgroundRepeat?: string
|
|
104
|
+
backgroundSize?: string
|
|
105
|
+
backgroundAttachment?: string
|
|
106
|
+
background?: string
|
|
107
|
+
|
|
108
|
+
// responsive overrides
|
|
109
|
+
sm?: { /* same keys above */ }
|
|
110
|
+
md?: { /* same keys above */ }
|
|
111
|
+
lg?: { /* same keys above */ }
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Usage
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { CountdownTimerNode } from '@janbox/storefront-builder';
|
|
119
|
+
|
|
120
|
+
// Đếm ngược đến ngày sale
|
|
121
|
+
<CountdownTimerNode
|
|
122
|
+
date={new Date('2026-12-31T23:59:59')}
|
|
123
|
+
units={[
|
|
124
|
+
{ value: 'days', caption: 'Days' },
|
|
125
|
+
{ value: 'hours', caption: 'Hours' },
|
|
126
|
+
{ value: 'minutes', caption: 'Mins' },
|
|
127
|
+
{ value: 'seconds', caption: 'Secs' },
|
|
128
|
+
]}
|
|
129
|
+
hideAfterExpired={false}
|
|
130
|
+
sx={{ columnGap: 8 }}
|
|
131
|
+
unitItemProps={{
|
|
132
|
+
sx: {
|
|
133
|
+
width: 64,
|
|
134
|
+
paddingTop: 8,
|
|
135
|
+
paddingBottom: 8,
|
|
136
|
+
backgroundColor: '#fa8c16',
|
|
137
|
+
borderTopLeftRadius: 8,
|
|
138
|
+
borderTopRightRadius: 8,
|
|
139
|
+
borderBottomLeftRadius: 8,
|
|
140
|
+
borderBottomRightRadius: 8,
|
|
141
|
+
},
|
|
142
|
+
}}
|
|
143
|
+
timeProps={{
|
|
144
|
+
sizeVariant: '2xl',
|
|
145
|
+
sx: { color: '#ffffff', fontWeight: 700 },
|
|
146
|
+
}}
|
|
147
|
+
captionProps={{
|
|
148
|
+
sizeVariant: 'xs',
|
|
149
|
+
sx: { color: '#ffffff', textTransform: 'uppercase' },
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Rules
|
|
155
|
+
|
|
156
|
+
- **Canvas**: no — không chứa node con.
|
|
157
|
+
- Khi editor đang bật, component hiển thị placeholder nếu `units` rỗng hoặc đã hết giờ với `hideAfterExpired: true`.
|