@janbox/storefront-builder 1.0.4 → 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 (69) hide show
  1. package/README.md +477 -0
  2. package/dist/editor/ui/dialog/dialog/helpers.d.ts +1 -1
  3. package/dist/editor/ui/dialog/dialog-close/dialog-close.d.ts +1 -1
  4. package/dist/editor.js +2 -2
  5. package/dist/{index-CBDllFpx.js → index-DrJkOkbq.js} +13 -13
  6. package/dist/index.js +2 -2
  7. package/dist/lib/accordion/README.md +39 -0
  8. package/dist/lib/accordion-content/README.md +36 -0
  9. package/dist/lib/accordion-group/README.md +106 -0
  10. package/dist/lib/accordion-summary/README.md +37 -0
  11. package/dist/lib/box/README.md +107 -0
  12. package/dist/lib/button/README.md +94 -0
  13. package/dist/lib/cell/README.md +119 -0
  14. package/dist/lib/countdown-timer/README.md +157 -0
  15. package/dist/lib/flex-item/README.md +111 -0
  16. package/dist/lib/flexbox/README.md +120 -0
  17. package/dist/lib/grid/README.md +133 -0
  18. package/dist/lib/heading/README.md +53 -0
  19. package/dist/lib/icon/README.md +98 -0
  20. package/dist/lib/image/README.md +107 -0
  21. package/dist/lib/link/README.md +111 -0
  22. package/dist/lib/list-item/README.md +31 -0
  23. package/dist/lib/marquee/README.md +111 -0
  24. package/dist/lib/marquee-item/README.md +31 -0
  25. package/dist/lib/paragraph/README.md +82 -0
  26. package/dist/lib/root/README.md +33 -0
  27. package/dist/lib/swiper/README.md +108 -0
  28. package/dist/lib/swiper-slide/README.md +32 -0
  29. package/dist/lib/tab/README.md +35 -0
  30. package/dist/lib/tab-content/README.md +35 -0
  31. package/dist/lib/tab-list/README.md +35 -0
  32. package/dist/lib/tab-panel/README.md +35 -0
  33. package/dist/lib/tabs/README.md +90 -0
  34. package/dist/lib/text/README.md +90 -0
  35. package/dist/lib/unknown/README.md +48 -0
  36. package/dist/lib/unordered-list/README.md +82 -0
  37. package/dist/lib/video/README.md +91 -0
  38. package/lib/accordion/README.md +39 -0
  39. package/lib/accordion-content/README.md +36 -0
  40. package/lib/accordion-group/README.md +106 -0
  41. package/lib/accordion-summary/README.md +37 -0
  42. package/lib/box/README.md +107 -0
  43. package/lib/button/README.md +94 -0
  44. package/lib/cell/README.md +119 -0
  45. package/lib/countdown-timer/README.md +157 -0
  46. package/lib/flex-item/README.md +111 -0
  47. package/lib/flexbox/README.md +120 -0
  48. package/lib/grid/README.md +133 -0
  49. package/lib/heading/README.md +53 -0
  50. package/lib/icon/README.md +98 -0
  51. package/lib/image/README.md +107 -0
  52. package/lib/link/README.md +111 -0
  53. package/lib/list-item/README.md +31 -0
  54. package/lib/marquee/README.md +111 -0
  55. package/lib/marquee-item/README.md +31 -0
  56. package/lib/paragraph/README.md +82 -0
  57. package/lib/root/README.md +33 -0
  58. package/lib/swiper/README.md +108 -0
  59. package/lib/swiper-slide/README.md +32 -0
  60. package/lib/tab/README.md +35 -0
  61. package/lib/tab-content/README.md +35 -0
  62. package/lib/tab-list/README.md +35 -0
  63. package/lib/tab-panel/README.md +35 -0
  64. package/lib/tabs/README.md +90 -0
  65. package/lib/text/README.md +90 -0
  66. package/lib/unknown/README.md +48 -0
  67. package/lib/unordered-list/README.md +82 -0
  68. package/lib/video/README.md +91 -0
  69. package/package.json +5 -9
@@ -0,0 +1,111 @@
1
+ # FlexItemNode
2
+
3
+ Một item trong `FlexboxNode`. Kiểm soát kích thước, grow/shrink, thứ tự của từng item trong flex container.
4
+
5
+ ## Resolved name
6
+
7
+ `FlexItem`
8
+
9
+ ## Configurable props
10
+
11
+ Tất cả props nằm trong `sx`. Không có top-level prop riêng.
12
+
13
+ ### `sx` props
14
+
15
+ ```ts
16
+ sx?: {
17
+ // flex item behavior
18
+ flexGrow?: number // auto grow: 1 = bật, undefined = tắt
19
+ flexShrink?: number // auto shrink: 0 = tắt, undefined = bật
20
+
21
+ // size
22
+ width?: number | string
23
+ height?: number | string
24
+
25
+ // order (thứ tự hiển thị)
26
+ order?: 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
+ // border
39
+ borderStyle?: string
40
+ borderColor?: string
41
+ borderTopWidth?: number
42
+ borderRightWidth?: number
43
+ borderBottomWidth?: number
44
+ borderLeftWidth?: number
45
+ borderTopLeftRadius?: number | string
46
+ borderTopRightRadius?: number | string
47
+ borderBottomRightRadius?: number | string
48
+ borderBottomLeftRadius?: number | string
49
+
50
+ // background
51
+ backgroundColor?: string
52
+ backgroundImage?: string
53
+ backgroundPosition?: string
54
+ backgroundRepeat?: string
55
+ backgroundSize?: string
56
+ backgroundAttachment?: string
57
+ background?: string
58
+
59
+ // responsive overrides
60
+ sm?: { /* same keys above */ }
61
+ md?: { /* same keys above */ }
62
+ lg?: { /* same keys above */ }
63
+ }
64
+ ```
65
+
66
+ ## Usage
67
+
68
+ ```tsx
69
+ import { FlexboxNode, FlexItemNode } from '@janbox/storefront-builder';
70
+
71
+ // Item cố định width, item còn lại grow để fill
72
+ <FlexboxNode sx={{ flexDirection: 'row', columnGap: 16 }}>
73
+ <FlexItemNode sx={{ width: 200 }}>Sidebar</FlexItemNode>
74
+ <FlexItemNode sx={{ flexGrow: 1 }}>Main content</FlexItemNode>
75
+ </FlexboxNode>
76
+
77
+ // Item không shrink khi container hẹp
78
+ <FlexboxNode sx={{ flexDirection: 'row' }}>
79
+ <FlexItemNode sx={{ flexShrink: 0, width: 120 }}>Fixed</FlexItemNode>
80
+ <FlexItemNode sx={{ flexGrow: 1 }}>Flexible</FlexItemNode>
81
+ </FlexboxNode>
82
+
83
+ // Responsive: full width mobile, 50% desktop
84
+ <FlexboxNode sx={{ flexDirection: 'column', md: { flexDirection: 'row' } }}>
85
+ <FlexItemNode
86
+ sx={{
87
+ width: '100%',
88
+ md: { width: '50%' },
89
+ }}
90
+ >
91
+ Left
92
+ </FlexItemNode>
93
+ <FlexItemNode
94
+ sx={{
95
+ width: '100%',
96
+ md: { width: '50%' },
97
+ }}
98
+ >
99
+ Right
100
+ </FlexItemNode>
101
+ </FlexboxNode>
102
+ ```
103
+
104
+ ## Rules
105
+
106
+ - **Canvas**: yes — chứa bất kỳ node con nào.
107
+ - **Parent**: phải là `FlexboxNode`.
108
+
109
+ ## Related nodes
110
+
111
+ - [`FlexboxNode`](../flexbox/README.md) — parent bắt buộc của `FlexItemNode`
@@ -0,0 +1,120 @@
1
+ # FlexboxNode
2
+
3
+ Flex container node (`display: flex`). Chỉ chấp nhận `FlexItemNode` làm con trực tiếp.
4
+
5
+ ## Resolved name
6
+
7
+ `Flexbox`
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
+ // flex layout
22
+ flexDirection?: 'row' | 'column'
23
+ flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse'
24
+ justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'
25
+ alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'
26
+ rowGap?: number
27
+ columnGap?: number
28
+
29
+ // size
30
+ width?: number | string
31
+ height?: number | string
32
+
33
+ // spacing
34
+ paddingTop?: number
35
+ paddingRight?: number
36
+ paddingBottom?: number
37
+ paddingLeft?: number
38
+ marginTop?: number
39
+ marginRight?: number
40
+ marginBottom?: number
41
+ marginLeft?: number
42
+
43
+ // border
44
+ borderStyle?: string
45
+ borderColor?: string
46
+ borderTopWidth?: number
47
+ borderRightWidth?: number
48
+ borderBottomWidth?: number
49
+ borderLeftWidth?: number
50
+ borderTopLeftRadius?: number | string
51
+ borderTopRightRadius?: number | string
52
+ borderBottomRightRadius?: number | string
53
+ borderBottomLeftRadius?: number | string
54
+
55
+ // background
56
+ backgroundColor?: string
57
+ backgroundImage?: string
58
+ backgroundPosition?: string
59
+ backgroundRepeat?: string
60
+ backgroundSize?: string
61
+ backgroundAttachment?: string
62
+ background?: string
63
+
64
+ // responsive overrides
65
+ sm?: { /* same keys above */ }
66
+ md?: { /* same keys above */ }
67
+ lg?: { /* same keys above */ }
68
+ }
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ```tsx
74
+ import { FlexboxNode, FlexItemNode } from '@janbox/storefront-builder';
75
+
76
+ // Row ngang, căn giữa dọc
77
+ <FlexboxNode sx={{ flexDirection: 'row', alignItems: 'center', columnGap: 16 }}>
78
+ <FlexItemNode>Item A</FlexItemNode>
79
+ <FlexItemNode>Item B</FlexItemNode>
80
+ </FlexboxNode>
81
+
82
+ // Đổi direction theo breakpoint: column trên mobile, row trên desktop
83
+ <FlexboxNode
84
+ sx={{
85
+ flexDirection: 'column',
86
+ rowGap: 12,
87
+ md: {
88
+ flexDirection: 'row',
89
+ columnGap: 24,
90
+ alignItems: 'center',
91
+ },
92
+ }}
93
+ >
94
+ <FlexItemNode>Item A</FlexItemNode>
95
+ <FlexItemNode>Item B</FlexItemNode>
96
+ </FlexboxNode>
97
+
98
+ // Space-between, wrap
99
+ <FlexboxNode
100
+ sx={{
101
+ flexDirection: 'row',
102
+ flexWrap: 'wrap',
103
+ justifyContent: 'space-between',
104
+ rowGap: 16,
105
+ }}
106
+ >
107
+ <FlexItemNode>Item A</FlexItemNode>
108
+ <FlexItemNode>Item B</FlexItemNode>
109
+ <FlexItemNode>Item C</FlexItemNode>
110
+ </FlexboxNode>
111
+ ```
112
+
113
+ ## Rules
114
+
115
+ - **Canvas**: yes.
116
+ - **canMoveIn**: chỉ chấp nhận `FlexItemNode`.
117
+
118
+ ## Related nodes
119
+
120
+ - [`FlexItemNode`](../flex-item/README.md) — con trực tiếp duy nhất của `FlexboxNode`
@@ -0,0 +1,133 @@
1
+ # GridNode
2
+
3
+ A CSS Grid container node. Accepts only `CellNode` children — the editor enforces this via `canMoveIn`.
4
+
5
+ ## Resolved name
6
+
7
+ `Grid`
8
+
9
+ ## Configurable props
10
+
11
+ Props mà user có thể chỉnh trong editor inspector. Tất cả đều hỗ trợ responsive trừ `container`.
12
+
13
+ ### Top-level props
14
+
15
+ | Prop | Type | Description |
16
+ | --- | --- | --- |
17
+ | `cols` | `number` | Số cột — generates `grid-template-columns: repeat(cols, minmax(0, 1fr))` |
18
+ | `container` | `boolean` | Bật max-width container constraint |
19
+
20
+ ### `sx` props
21
+
22
+ `sx` nhận `StyledCSS` với responsive keys `sm`, `md`, `lg` bên trong:
23
+
24
+ ```ts
25
+ sx?: {
26
+ // layout
27
+ columnGap?: number
28
+ rowGap?: number
29
+ alignItems?: 'start' | 'center' | 'end' | 'stretch'
30
+ justifyItems?: 'start' | 'center' | 'end' | 'stretch'
31
+ width?: number | string
32
+ height?: number | string
33
+
34
+ // spacing
35
+ paddingTop?: number
36
+ paddingRight?: number
37
+ paddingBottom?: number
38
+ paddingLeft?: number
39
+ marginTop?: number
40
+ marginRight?: number
41
+ marginBottom?: number
42
+ marginLeft?: number
43
+
44
+ // border
45
+ borderStyle?: string
46
+ borderColor?: string
47
+ borderTopWidth?: number
48
+ borderRightWidth?: number
49
+ borderBottomWidth?: number
50
+ borderLeftWidth?: number
51
+ borderTopLeftRadius?: number | string
52
+ borderTopRightRadius?: number | string
53
+ borderBottomRightRadius?: number | string
54
+ borderBottomLeftRadius?: number | string
55
+
56
+ // background
57
+ backgroundColor?: string
58
+ backgroundImage?: string
59
+ backgroundPosition?: string
60
+ backgroundRepeat?: string
61
+ backgroundSize?: string
62
+ backgroundAttachment?: string
63
+ background?: string
64
+
65
+ // responsive overrides
66
+ sm?: { /* same keys above */ }
67
+ md?: { /* same keys above */ }
68
+ lg?: { /* same keys above */ }
69
+ }
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ ```tsx
75
+ import { GridNode, CellNode } from '@janbox/storefront-builder';
76
+
77
+ // 2 cột cố định
78
+ <GridNode cols={2}>
79
+ <CellNode>...</CellNode>
80
+ <CellNode>...</CellNode>
81
+ </GridNode>
82
+
83
+ // Responsive: 1 cột mobile, 3 cột desktop
84
+ <GridNode cols={1} md={{ cols: 3 }}>
85
+ <CellNode>...</CellNode>
86
+ <CellNode>...</CellNode>
87
+ <CellNode>...</CellNode>
88
+ </GridNode>
89
+
90
+ // sx responsive: gap nhỏ trên mobile, lớn hơn trên desktop
91
+ <GridNode
92
+ cols={1}
93
+ md={{ cols: 2 }}
94
+ sx={{
95
+ columnGap: 8,
96
+ rowGap: 8,
97
+ paddingTop: 16,
98
+ paddingBottom: 16,
99
+ md: {
100
+ columnGap: 24,
101
+ rowGap: 24,
102
+ paddingTop: 32,
103
+ paddingBottom: 32,
104
+ },
105
+ }}
106
+ >
107
+ <CellNode>...</CellNode>
108
+ <CellNode>...</CellNode>
109
+ </GridNode>
110
+
111
+ // Container với background
112
+ <GridNode
113
+ container
114
+ cols={2}
115
+ sx={{
116
+ backgroundColor: '#f5f5f5',
117
+ paddingTop: 32,
118
+ paddingBottom: 32,
119
+ }}
120
+ >
121
+ <CellNode>...</CellNode>
122
+ <CellNode>...</CellNode>
123
+ </GridNode>
124
+ ```
125
+
126
+ ## Rules
127
+
128
+ - **Canvas**: yes — accepts dropped children.
129
+ - **canMoveIn**: only `CellNode` children are allowed.
130
+
131
+ ## Related nodes
132
+
133
+ - [`CellNode`](../cell/README.md) — the only valid direct child of `GridNode`
@@ -0,0 +1,53 @@
1
+ # HeadingNode
2
+
3
+ Text node dành cho tiêu đề. Dùng chung `TextInspector` với `TextNode` nhưng giới hạn tag trong `h1`–`h6` và `div`.
4
+
5
+ ## Resolved name
6
+
7
+ `Heading`
8
+
9
+ ## Configurable props
10
+
11
+ Giống hệt `TextNode`. Xem chi tiết tại [TextNode](../text/README.md#configurable-props).
12
+
13
+ ### Điểm khác biệt
14
+
15
+ | Prop | Giá trị cho phép | Default |
16
+ | --- | --- | --- |
17
+ | `as` | `'h1' \| 'h2' \| 'h3' \| 'h4' \| 'h5' \| 'h6' \| 'div'` | `'p'` (từ Text) |
18
+
19
+ ## Usage
20
+
21
+ ```tsx
22
+ import { HeadingNode } from '@janbox/storefront-builder';
23
+
24
+ // H1 mặc định
25
+ <HeadingNode as="h1" sizeVariant="4xl">
26
+ Page Title
27
+ </HeadingNode>
28
+
29
+ // Responsive size
30
+ <HeadingNode
31
+ as="h2"
32
+ sizeVariant="2xl"
33
+ md={{ sizeVariant: '4xl' }}
34
+ sx={{ color: '#111111', textAlign: 'center' }}
35
+ >
36
+ Section Title
37
+ </HeadingNode>
38
+
39
+ // Dùng div khi không cần semantic heading nhưng muốn style heading
40
+ <HeadingNode as="div" sizeVariant="xl" sx={{ fontWeight: 700 }}>
41
+ Display text
42
+ </HeadingNode>
43
+ ```
44
+
45
+ ## Rules
46
+
47
+ - **Canvas**: no — không chứa node con.
48
+ - **Inline editing**: có thể double-click để chỉnh nội dung trong canvas.
49
+
50
+ ## Related nodes
51
+
52
+ - [`TextNode`](../text/README.md) — base component dùng chung inspector
53
+ - [`ParagraphNode`](../paragraph/README.md) — dành cho nội dung dạng đoạn văn
@@ -0,0 +1,98 @@
1
+ # IconNode
2
+
3
+ SVG icon node. Render inline SVG từ raw string hoặc URL.
4
+
5
+ ## Resolved name
6
+
7
+ `Icon`
8
+
9
+ ## Configurable props
10
+
11
+ ### Top-level props
12
+
13
+ | Prop | Type | Description |
14
+ | --- | --- | --- |
15
+ | `source` | `string` | Raw SVG string hoặc URL tới file SVG |
16
+ | `size` | `number` | Kích thước icon (px) — set cả `width` và `height` |
17
+
18
+ `size` hỗ trợ responsive: `size={24}` xs, `md={{ size: 32 }}` desktop.
19
+
20
+ ### `sx` props
21
+
22
+ ```ts
23
+ sx?: {
24
+ // color (áp dụng toàn breakpoint — không responsive)
25
+ color?: string
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
+ // 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
+ // responsive overrides
50
+ sm?: { /* same keys above */ }
51
+ md?: { /* same keys above */ }
52
+ lg?: { /* same keys above */ }
53
+ }
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ ```tsx
59
+ import { IconNode } from '@janbox/storefront-builder';
60
+
61
+ // Icon từ raw SVG
62
+ <IconNode
63
+ source='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="..."/></svg>'
64
+ size={24}
65
+ sx={{ color: '#333333' }}
66
+ />
67
+
68
+ // Responsive size
69
+ <IconNode
70
+ source="https://example.com/icon.svg"
71
+ size={20}
72
+ md={{ size: 28 }}
73
+ sx={{ color: '#fa8c16' }}
74
+ />
75
+
76
+ // Icon với background tròn
77
+ <IconNode
78
+ source='<svg>...</svg>'
79
+ size={16}
80
+ sx={{
81
+ color: '#ffffff',
82
+ paddingTop: 8,
83
+ paddingBottom: 8,
84
+ paddingLeft: 8,
85
+ paddingRight: 8,
86
+ backgroundColor: '#fa8c16',
87
+ borderTopLeftRadius: '50%',
88
+ borderTopRightRadius: '50%',
89
+ borderBottomLeftRadius: '50%',
90
+ borderBottomRightRadius: '50%',
91
+ }}
92
+ />
93
+ ```
94
+
95
+ ## Rules
96
+
97
+ - **Canvas**: no — không chứa node con.
98
+ - `source` bắt buộc phải có — nếu không có, icon sẽ không hiển thị.
@@ -0,0 +1,107 @@
1
+ # ImageNode
2
+
3
+ Image node render ra `<img>`. Không chứa node con.
4
+
5
+ ## Resolved name
6
+
7
+ `Image`
8
+
9
+ ## Configurable props
10
+
11
+ ### Top-level props
12
+
13
+ | Prop | Type | Description |
14
+ | --- | --- | --- |
15
+ | `src` | `string` | URL ảnh |
16
+ | `alt` | `string` | Alt text cho SEO và accessibility |
17
+ | `loading` | `'eager' \| 'lazy'` | Preload: `'eager'` = tải ngay, `'lazy'` = lazy load (default) |
18
+
19
+ ### `sx` props
20
+
21
+ ```ts
22
+ sx?: {
23
+ // size
24
+ width?: number | string
25
+ height?: number | string // chỉ hiển thị khi aspectRatio là 'auto'
26
+
27
+ // aspect ratio
28
+ aspectRatio?: 'auto' | '1 / 1' | '16 / 9' | '9 / 16' | string // ví dụ custom: '4 / 3'
29
+
30
+ // object fit (áp dụng toàn breakpoint — không responsive)
31
+ objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
32
+
33
+ // spacing
34
+ paddingTop?: number
35
+ paddingRight?: number
36
+ paddingBottom?: number
37
+ paddingLeft?: number
38
+ marginTop?: number
39
+ marginRight?: number
40
+ marginBottom?: number
41
+ marginLeft?: number
42
+
43
+ // border
44
+ borderStyle?: string
45
+ borderColor?: string
46
+ borderTopWidth?: number
47
+ borderRightWidth?: number
48
+ borderBottomWidth?: number
49
+ borderLeftWidth?: number
50
+ borderTopLeftRadius?: number | string
51
+ borderTopRightRadius?: number | string
52
+ borderBottomRightRadius?: number | string
53
+ borderBottomLeftRadius?: number | string
54
+
55
+ // responsive overrides
56
+ sm?: { /* same keys above */ }
57
+ md?: { /* same keys above */ }
58
+ lg?: { /* same keys above */ }
59
+ }
60
+ ```
61
+
62
+ > Khi `aspectRatio` được set (khác `auto`), prop `height` bị ẩn trong inspector vì chiều cao tự tính từ ratio.
63
+
64
+ ## Usage
65
+
66
+ ```tsx
67
+ import { ImageNode } from '@janbox/storefront-builder';
68
+
69
+ // Ảnh cơ bản
70
+ <ImageNode
71
+ src="https://example.com/photo.jpg"
72
+ alt="Product photo"
73
+ loading="lazy"
74
+ sx={{ width: '100%', objectFit: 'cover' }}
75
+ />
76
+
77
+ // Tỉ lệ 16:9, full width
78
+ <ImageNode
79
+ src="https://example.com/banner.jpg"
80
+ alt="Banner"
81
+ sx={{
82
+ width: '100%',
83
+ aspectRatio: '16 / 9',
84
+ objectFit: 'cover',
85
+ }}
86
+ />
87
+
88
+ // Thumbnail vuông, responsive width
89
+ <ImageNode
90
+ src="https://example.com/thumb.jpg"
91
+ alt="Thumbnail"
92
+ sx={{
93
+ width: 80,
94
+ aspectRatio: '1 / 1',
95
+ objectFit: 'cover',
96
+ borderTopLeftRadius: 8,
97
+ borderTopRightRadius: 8,
98
+ borderBottomLeftRadius: 8,
99
+ borderBottomRightRadius: 8,
100
+ md: { width: 120 },
101
+ }}
102
+ />
103
+ ```
104
+
105
+ ## Rules
106
+
107
+ - **Canvas**: no — không chứa node con.