@janbox/storefront-builder 1.0.3 → 1.0.5

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 (70) hide show
  1. package/README.md +477 -0
  2. package/dist/editor/lib/index.d.ts +3 -3
  3. package/dist/editor/lib/sidebar/index.d.ts +1 -1
  4. package/dist/editor/lib/sidebar/insert-action.d.ts +2 -2
  5. package/dist/editor/ui/dialog/dialog/helpers.d.ts +1 -1
  6. package/dist/editor/ui/dialog/dialog-close/dialog-close.d.ts +1 -1
  7. package/dist/editor.js +326 -313
  8. package/dist/lib/accordion/README.md +39 -0
  9. package/dist/lib/accordion-content/README.md +36 -0
  10. package/dist/lib/accordion-group/README.md +106 -0
  11. package/dist/lib/accordion-summary/README.md +37 -0
  12. package/dist/lib/box/README.md +107 -0
  13. package/dist/lib/button/README.md +94 -0
  14. package/dist/lib/cell/README.md +119 -0
  15. package/dist/lib/countdown-timer/README.md +157 -0
  16. package/dist/lib/flex-item/README.md +111 -0
  17. package/dist/lib/flexbox/README.md +120 -0
  18. package/dist/lib/grid/README.md +133 -0
  19. package/dist/lib/heading/README.md +53 -0
  20. package/dist/lib/icon/README.md +98 -0
  21. package/dist/lib/image/README.md +107 -0
  22. package/dist/lib/link/README.md +111 -0
  23. package/dist/lib/list-item/README.md +31 -0
  24. package/dist/lib/marquee/README.md +111 -0
  25. package/dist/lib/marquee-item/README.md +31 -0
  26. package/dist/lib/paragraph/README.md +82 -0
  27. package/dist/lib/root/README.md +33 -0
  28. package/dist/lib/swiper/README.md +108 -0
  29. package/dist/lib/swiper-slide/README.md +32 -0
  30. package/dist/lib/tab/README.md +35 -0
  31. package/dist/lib/tab-content/README.md +35 -0
  32. package/dist/lib/tab-list/README.md +35 -0
  33. package/dist/lib/tab-panel/README.md +35 -0
  34. package/dist/lib/tabs/README.md +90 -0
  35. package/dist/lib/text/README.md +90 -0
  36. package/dist/lib/unknown/README.md +48 -0
  37. package/dist/lib/unordered-list/README.md +82 -0
  38. package/dist/lib/video/README.md +91 -0
  39. package/lib/accordion/README.md +39 -0
  40. package/lib/accordion-content/README.md +36 -0
  41. package/lib/accordion-group/README.md +106 -0
  42. package/lib/accordion-summary/README.md +37 -0
  43. package/lib/box/README.md +107 -0
  44. package/lib/button/README.md +94 -0
  45. package/lib/cell/README.md +119 -0
  46. package/lib/countdown-timer/README.md +157 -0
  47. package/lib/flex-item/README.md +111 -0
  48. package/lib/flexbox/README.md +120 -0
  49. package/lib/grid/README.md +133 -0
  50. package/lib/heading/README.md +53 -0
  51. package/lib/icon/README.md +98 -0
  52. package/lib/image/README.md +107 -0
  53. package/lib/link/README.md +111 -0
  54. package/lib/list-item/README.md +31 -0
  55. package/lib/marquee/README.md +111 -0
  56. package/lib/marquee-item/README.md +31 -0
  57. package/lib/paragraph/README.md +82 -0
  58. package/lib/root/README.md +33 -0
  59. package/lib/swiper/README.md +108 -0
  60. package/lib/swiper-slide/README.md +32 -0
  61. package/lib/tab/README.md +35 -0
  62. package/lib/tab-content/README.md +35 -0
  63. package/lib/tab-list/README.md +35 -0
  64. package/lib/tab-panel/README.md +35 -0
  65. package/lib/tabs/README.md +90 -0
  66. package/lib/text/README.md +90 -0
  67. package/lib/unknown/README.md +48 -0
  68. package/lib/unordered-list/README.md +82 -0
  69. package/lib/video/README.md +91 -0
  70. package/package.json +5 -9
@@ -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`.