@akinon/pz-theme 2.0.35-rc.0 → 2.0.35
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/CHANGELOG.md +12 -28
- package/package.json +4 -3
- package/src/__tests__/theme-contract.test.ts +130 -0
- package/src/blocks/__tests__/text-block.test.tsx +8 -1
- package/src/blocks/__tests__/video-block.test.tsx +14 -7
- package/src/blocks/counter-block.tsx +44 -56
- package/src/blocks/group-block.tsx +23 -22
- package/src/blocks/hotspot-block.tsx +171 -80
- package/src/blocks/input-block.tsx +15 -17
- package/src/blocks/link-block.tsx +18 -21
- package/src/blocks/slider-block.tsx +15 -12
- package/src/components/with-designer-features.tsx +2 -0
- package/src/editor-bridge.ts +160 -0
- package/src/hooks/use-designer-features.tsx +30 -37
- package/src/placeholder-registry.ts +5 -7
- package/src/sections/__tests__/countdown-campaign-banner-section.test.tsx +8 -4
- package/src/sections/__tests__/coupon-banner-section.test.tsx +4 -2
- package/src/sections/before-after-section.tsx +189 -134
- package/src/sections/contact-form-section.tsx +185 -60
- package/src/sections/countdown-campaign-banner-section.tsx +73 -27
- package/src/sections/coupon-banner-section.tsx +152 -48
- package/src/sections/featured-product-spotlight-section.tsx +67 -66
- package/src/sections/find-in-store-section.tsx +37 -25
- package/src/sections/hover-showcase-section.tsx +221 -89
- package/src/sections/image-hotspot-section.tsx +208 -94
- package/src/sections/installment-options-section.tsx +268 -259
- package/src/sections/notification-banner-section.tsx +111 -25
- package/src/sections/order-tracking-lookup-section.tsx +272 -247
- package/src/sections/posts-slider-section.tsx +17 -16
- package/src/sections/pre-order-launch-banner-section.tsx +31 -50
- package/src/sections/section-wrapper.tsx +9 -10
- package/src/sections/shipping-threshold-progress-section.tsx +72 -29
- package/src/sections/stats-counter-section.tsx +68 -30
- package/src/sections/tabs-section.tsx +124 -147
- package/src/theme-placeholder-client.tsx +11 -14
- package/src/theme-placeholder-wrapper.tsx +42 -51
- package/src/theme-section.tsx +27 -34
- package/src/utils/__tests__/transform-styles.test.ts +97 -0
- package/src/utils/index.ts +9 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
4
|
|
|
5
|
+
import { postToEditor } from '../editor-bridge';
|
|
6
|
+
import { PreviewToEditorMessageType } from '@akinon/theme-contract';
|
|
5
7
|
interface BlockInfo {
|
|
6
8
|
id: string;
|
|
7
9
|
type: string;
|
|
@@ -30,19 +32,16 @@ export function useDesignerFeatures({
|
|
|
30
32
|
e.stopPropagation();
|
|
31
33
|
|
|
32
34
|
if (window.parent) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
},
|
|
44
|
-
'*'
|
|
45
|
-
);
|
|
35
|
+
postToEditor({
|
|
36
|
+
type: 'SELECT_BLOCK',
|
|
37
|
+
data: {
|
|
38
|
+
placeholderId,
|
|
39
|
+
sectionId,
|
|
40
|
+
blockId,
|
|
41
|
+
blockType: blockInfo?.type,
|
|
42
|
+
blockLabel: blockInfo?.label
|
|
43
|
+
}
|
|
44
|
+
});
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
},
|
|
@@ -50,19 +49,16 @@ export function useDesignerFeatures({
|
|
|
50
49
|
);
|
|
51
50
|
|
|
52
51
|
const createActionHandler = useCallback(
|
|
53
|
-
(actionType:
|
|
52
|
+
(actionType: PreviewToEditorMessageType) => () => {
|
|
54
53
|
if (window.parent) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
},
|
|
64
|
-
'*'
|
|
65
|
-
);
|
|
54
|
+
postToEditor({
|
|
55
|
+
type: actionType,
|
|
56
|
+
data: {
|
|
57
|
+
placeholderId,
|
|
58
|
+
sectionId,
|
|
59
|
+
blockId
|
|
60
|
+
}
|
|
61
|
+
});
|
|
66
62
|
}
|
|
67
63
|
},
|
|
68
64
|
[blockId, placeholderId, sectionId]
|
|
@@ -71,18 +67,15 @@ export function useDesignerFeatures({
|
|
|
71
67
|
const handleRename = useCallback(
|
|
72
68
|
(newLabel: string) => {
|
|
73
69
|
if (window.parent) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
'*'
|
|
85
|
-
);
|
|
70
|
+
postToEditor({
|
|
71
|
+
type: 'RENAME_BLOCK',
|
|
72
|
+
data: {
|
|
73
|
+
placeholderId,
|
|
74
|
+
sectionId,
|
|
75
|
+
blockId,
|
|
76
|
+
label: newLabel
|
|
77
|
+
}
|
|
78
|
+
});
|
|
86
79
|
}
|
|
87
80
|
},
|
|
88
81
|
[blockId, placeholderId, sectionId]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { postToEditor } from './editor-bridge';
|
|
1
2
|
const placeholderRegistry = new Set<string>();
|
|
2
3
|
let registryTimeout: NodeJS.Timeout | null = null;
|
|
3
4
|
|
|
@@ -15,13 +16,10 @@ export const registerPlaceholder = (slug: string) => {
|
|
|
15
16
|
window.self !== window.top
|
|
16
17
|
) {
|
|
17
18
|
const availablePlaceholders = Array.from(placeholderRegistry);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
'*'
|
|
24
|
-
);
|
|
19
|
+
postToEditor({
|
|
20
|
+
type: 'PLACEHOLDERS_AVAILABLE',
|
|
21
|
+
data: { placeholders: availablePlaceholders }
|
|
22
|
+
});
|
|
25
23
|
}
|
|
26
24
|
}, 100);
|
|
27
25
|
};
|
|
@@ -25,17 +25,19 @@ const renderSection = (props: any = {}) => {
|
|
|
25
25
|
let utils: ReturnType<typeof render>;
|
|
26
26
|
act(() => {
|
|
27
27
|
utils = render(
|
|
28
|
-
<CountdownCampaignBannerSection
|
|
28
|
+
<CountdownCampaignBannerSection
|
|
29
|
+
section={makeSection(props.section)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
29
32
|
);
|
|
30
33
|
});
|
|
31
|
-
// @ts-expect-error assigned inside act
|
|
32
34
|
return utils;
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
// Fixed "now" so countdown math is deterministic.
|
|
36
38
|
const NOW = new Date('2026-06-26T00:00:00.000Z').getTime();
|
|
37
39
|
// now + 1d 2h 3m 4s
|
|
38
|
-
const DELTA_MS = (((
|
|
40
|
+
const DELTA_MS = (((1 * 24 + 2) * 60 + 3) * 60 + 4) * 1000;
|
|
39
41
|
const END_MS = NOW + DELTA_MS;
|
|
40
42
|
|
|
41
43
|
beforeEach(() => {
|
|
@@ -92,7 +94,9 @@ describe('CountdownCampaignBannerSection - parseEndDate', () => {
|
|
|
92
94
|
|
|
93
95
|
describe('CountdownCampaignBannerSection - expired', () => {
|
|
94
96
|
it('renders the default expired message for a past date', () => {
|
|
95
|
-
renderSection({
|
|
97
|
+
renderSection({
|
|
98
|
+
section: { properties: { 'end-date': '2000-01-01T00:00:00.000Z' } }
|
|
99
|
+
});
|
|
96
100
|
|
|
97
101
|
expect(screen.getByText('Campaign ended')).toBeInTheDocument();
|
|
98
102
|
// No timer cells when expired.
|
|
@@ -42,7 +42,7 @@ const renderSection = (section: AnyBlock, props: AnyBlock = {}) =>
|
|
|
42
42
|
<CouponBannerSection
|
|
43
43
|
section={section as never}
|
|
44
44
|
currentBreakpoint="desktop"
|
|
45
|
-
{...(props as
|
|
45
|
+
{...(props as object)}
|
|
46
46
|
/>
|
|
47
47
|
);
|
|
48
48
|
|
|
@@ -62,7 +62,9 @@ describe('CouponBannerSection — role resolution', () => {
|
|
|
62
62
|
|
|
63
63
|
renderSection(section);
|
|
64
64
|
|
|
65
|
-
const ids = screen
|
|
65
|
+
const ids = screen
|
|
66
|
+
.getAllByTestId('tb')
|
|
67
|
+
.map((el) => el.getAttribute('data-id'));
|
|
66
68
|
// label block rendered through ThemeBlock (role slot) ...
|
|
67
69
|
expect(ids).toContain('label-1');
|
|
68
70
|
// ... and the plain content block kept in the content column.
|
|
@@ -8,6 +8,7 @@ import { twMerge } from 'tailwind-merge';
|
|
|
8
8
|
import clsx from 'clsx';
|
|
9
9
|
import ThemeBlock from '../theme-block';
|
|
10
10
|
|
|
11
|
+
import { postToEditor } from '../editor-bridge';
|
|
11
12
|
interface BeforeAfterSectionProps {
|
|
12
13
|
section: Section;
|
|
13
14
|
currentBreakpoint?: string;
|
|
@@ -32,54 +33,99 @@ export default function BeforeAfterSection({
|
|
|
32
33
|
const styles = section.styles || {};
|
|
33
34
|
const properties = section.properties || {};
|
|
34
35
|
|
|
35
|
-
const beforeBlock = section.blocks?.find(b => b.label === 'Before Image');
|
|
36
|
-
const afterBlock = section.blocks?.find(b => b.label === 'After Image');
|
|
36
|
+
const beforeBlock = section.blocks?.find((b) => b.label === 'Before Image');
|
|
37
|
+
const afterBlock = section.blocks?.find((b) => b.label === 'After Image');
|
|
38
|
+
|
|
39
|
+
const height = getResponsiveValue(
|
|
40
|
+
styles.height,
|
|
41
|
+
currentBreakpoint,
|
|
42
|
+
'500px'
|
|
43
|
+
) as string | number;
|
|
44
|
+
const width = getResponsiveValue(
|
|
45
|
+
styles.width,
|
|
46
|
+
currentBreakpoint,
|
|
47
|
+
'100%'
|
|
48
|
+
) as string;
|
|
49
|
+
const maxWidth = getResponsiveValue(
|
|
50
|
+
styles['max-width'],
|
|
51
|
+
currentBreakpoint,
|
|
52
|
+
'normal'
|
|
53
|
+
) as string;
|
|
37
54
|
|
|
38
|
-
const height = getResponsiveValue(styles.height, currentBreakpoint, '500px') as string | number;
|
|
39
|
-
const width = getResponsiveValue(styles.width, currentBreakpoint, '100%') as string;
|
|
40
|
-
const maxWidth = getResponsiveValue(styles['max-width'], currentBreakpoint, 'normal') as string;
|
|
41
|
-
|
|
42
55
|
const sliderColor = resolveThemeCssVariables(
|
|
43
|
-
getResponsiveValue(
|
|
56
|
+
getResponsiveValue(
|
|
57
|
+
styles['slider-color'],
|
|
58
|
+
currentBreakpoint,
|
|
59
|
+
'#ffffff'
|
|
60
|
+
) as string,
|
|
44
61
|
themeSettings
|
|
45
62
|
);
|
|
46
63
|
|
|
47
64
|
const renderBlock = (block: any) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
return (
|
|
66
|
+
<ThemeBlock
|
|
67
|
+
key={block.id}
|
|
68
|
+
block={block}
|
|
69
|
+
placeholderId={placeholderId}
|
|
70
|
+
sectionId={section.id}
|
|
71
|
+
isDesigner={isDesigner}
|
|
72
|
+
isSelected={selectedBlockId === block.id}
|
|
73
|
+
selectedBlockId={selectedBlockId}
|
|
74
|
+
currentBreakpoint={currentBreakpoint}
|
|
75
|
+
onMoveUp={() => {
|
|
76
|
+
if (window.parent)
|
|
77
|
+
postToEditor({
|
|
78
|
+
type: 'MOVE_BLOCK_UP',
|
|
79
|
+
data: { placeholderId, sectionId: section.id, blockId: block.id }
|
|
80
|
+
});
|
|
81
|
+
}}
|
|
82
|
+
onMoveDown={() => {
|
|
83
|
+
if (window.parent)
|
|
84
|
+
postToEditor({
|
|
85
|
+
type: 'MOVE_BLOCK_DOWN',
|
|
86
|
+
data: { placeholderId, sectionId: section.id, blockId: block.id }
|
|
87
|
+
});
|
|
88
|
+
}}
|
|
89
|
+
onDuplicate={() => {
|
|
90
|
+
if (window.parent)
|
|
91
|
+
postToEditor({
|
|
92
|
+
type: 'DUPLICATE_BLOCK',
|
|
93
|
+
data: { placeholderId, sectionId: section.id, blockId: block.id }
|
|
94
|
+
});
|
|
95
|
+
}}
|
|
96
|
+
onToggleVisibility={() => {
|
|
97
|
+
if (window.parent)
|
|
98
|
+
postToEditor({
|
|
99
|
+
type: 'TOGGLE_BLOCK_VISIBILITY',
|
|
100
|
+
data: { placeholderId, sectionId: section.id, blockId: block.id }
|
|
101
|
+
});
|
|
102
|
+
}}
|
|
103
|
+
onDelete={() => {
|
|
104
|
+
if (window.parent)
|
|
105
|
+
postToEditor({
|
|
106
|
+
type: 'DELETE_BLOCK',
|
|
107
|
+
data: { placeholderId, sectionId: section.id, blockId: block.id }
|
|
108
|
+
});
|
|
109
|
+
}}
|
|
110
|
+
onRename={(newLabel) => {
|
|
111
|
+
if (window.parent)
|
|
112
|
+
postToEditor({
|
|
113
|
+
type: 'RENAME_BLOCK',
|
|
114
|
+
data: {
|
|
115
|
+
placeholderId,
|
|
116
|
+
sectionId: section.id,
|
|
117
|
+
blockId: block.id,
|
|
118
|
+
label: newLabel
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
78
124
|
};
|
|
79
125
|
|
|
80
126
|
useEffect(() => {
|
|
81
127
|
if (!containerRef.current) return;
|
|
82
|
-
|
|
128
|
+
|
|
83
129
|
setContainerWidth(containerRef.current.offsetWidth);
|
|
84
130
|
|
|
85
131
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -87,32 +133,33 @@ export default function BeforeAfterSection({
|
|
|
87
133
|
setContainerWidth(entry.contentRect.width);
|
|
88
134
|
}
|
|
89
135
|
});
|
|
90
|
-
|
|
136
|
+
|
|
91
137
|
observer.observe(containerRef.current);
|
|
92
|
-
|
|
138
|
+
|
|
93
139
|
return () => observer.disconnect();
|
|
94
140
|
}, []);
|
|
95
141
|
|
|
96
142
|
const handleStart = (clientX: number) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
143
|
+
setIsResizing(true);
|
|
144
|
+
if (containerRef.current) {
|
|
145
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
146
|
+
const x = Math.max(0, Math.min(clientX - rect.left, rect.width));
|
|
147
|
+
setSliderPosition((x / rect.width) * 100);
|
|
148
|
+
}
|
|
103
149
|
};
|
|
104
150
|
|
|
105
151
|
const handleMouseDown = (e: React.MouseEvent) => handleStart(e.clientX);
|
|
106
|
-
const handleTouchStart = (e: React.TouchEvent) =>
|
|
152
|
+
const handleTouchStart = (e: React.TouchEvent) =>
|
|
153
|
+
handleStart(e.touches[0].clientX);
|
|
107
154
|
|
|
108
155
|
const handleMove = useCallback(
|
|
109
156
|
(clientX: number) => {
|
|
110
157
|
if (!isResizing || !containerRef.current) return;
|
|
111
|
-
|
|
158
|
+
|
|
112
159
|
const rect = containerRef.current.getBoundingClientRect();
|
|
113
160
|
const x = Math.max(0, Math.min(clientX - rect.left, rect.width));
|
|
114
161
|
const percentage = (x / rect.width) * 100;
|
|
115
|
-
|
|
162
|
+
|
|
116
163
|
setSliderPosition(percentage);
|
|
117
164
|
},
|
|
118
165
|
[isResizing]
|
|
@@ -121,42 +168,43 @@ export default function BeforeAfterSection({
|
|
|
121
168
|
const handleMouseMove = (e: React.MouseEvent) => {
|
|
122
169
|
if (isResizing) handleMove(e.clientX);
|
|
123
170
|
};
|
|
124
|
-
|
|
171
|
+
|
|
125
172
|
const handleTouchMove = (e: React.TouchEvent) => {
|
|
126
173
|
if (isResizing) handleMove(e.touches[0].clientX);
|
|
127
174
|
};
|
|
128
|
-
|
|
175
|
+
|
|
129
176
|
useEffect(() => {
|
|
130
177
|
const handleGlobalEnd = () => setIsResizing(false);
|
|
131
178
|
const handleGlobalMove = (e: MouseEvent) => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
179
|
+
if (isResizing) {
|
|
180
|
+
e.preventDefault();
|
|
181
|
+
handleMove(e.clientX);
|
|
182
|
+
}
|
|
136
183
|
};
|
|
137
|
-
|
|
184
|
+
|
|
138
185
|
const handleGlobalTouchMove = (e: TouchEvent) => {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
186
|
+
if (isResizing) {
|
|
187
|
+
handleMove(e.touches[0].clientX);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
143
190
|
|
|
144
191
|
if (isResizing) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
192
|
+
window.addEventListener('mouseup', handleGlobalEnd);
|
|
193
|
+
window.addEventListener('touchend', handleGlobalEnd);
|
|
194
|
+
window.addEventListener('mousemove', handleGlobalMove);
|
|
195
|
+
window.addEventListener('touchmove', handleGlobalTouchMove, {
|
|
196
|
+
passive: false
|
|
197
|
+
});
|
|
149
198
|
}
|
|
150
199
|
|
|
151
200
|
return () => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
201
|
+
window.removeEventListener('mouseup', handleGlobalEnd);
|
|
202
|
+
window.removeEventListener('touchend', handleGlobalEnd);
|
|
203
|
+
window.removeEventListener('mousemove', handleGlobalMove);
|
|
204
|
+
window.removeEventListener('touchmove', handleGlobalTouchMove);
|
|
156
205
|
};
|
|
157
206
|
}, [isResizing, handleMove]);
|
|
158
207
|
|
|
159
|
-
|
|
160
208
|
const maxWidthClass =
|
|
161
209
|
maxWidth === 'narrow'
|
|
162
210
|
? 'max-w-4xl'
|
|
@@ -165,81 +213,88 @@ export default function BeforeAfterSection({
|
|
|
165
213
|
: maxWidth === 'full'
|
|
166
214
|
? 'w-full'
|
|
167
215
|
: '';
|
|
168
|
-
|
|
216
|
+
|
|
169
217
|
const hasMaxWidth = maxWidth !== 'none' && maxWidth !== 'full';
|
|
170
218
|
|
|
171
219
|
return (
|
|
172
|
-
<div
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
220
|
+
<div
|
|
221
|
+
className={twMerge(
|
|
222
|
+
clsx(
|
|
223
|
+
'relative mx-auto select-none overflow-hidden group/before-after',
|
|
224
|
+
hasMaxWidth && 'mx-auto',
|
|
225
|
+
maxWidthClass
|
|
226
|
+
)
|
|
227
|
+
)}
|
|
228
|
+
style={{
|
|
229
|
+
height: typeof height === 'number' ? `${height}px` : height,
|
|
230
|
+
width: width === 'fill' ? '100%' : width,
|
|
231
|
+
maxWidth: maxWidth === 'none' ? 'none' : undefined
|
|
232
|
+
}}
|
|
233
|
+
ref={containerRef}
|
|
234
|
+
onMouseDown={handleMouseDown}
|
|
235
|
+
onTouchStart={handleTouchStart}
|
|
236
|
+
onMouseMove={handleMouseMove}
|
|
237
|
+
onTouchMove={handleTouchMove}
|
|
190
238
|
>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
239
|
+
<div className="absolute inset-0 w-full h-full">
|
|
240
|
+
{afterBlock ? (
|
|
241
|
+
<div className="w-full h-full pointer-events-none">
|
|
242
|
+
{renderBlock(afterBlock)}
|
|
243
|
+
</div>
|
|
244
|
+
) : (
|
|
245
|
+
<div className="w-full h-full bg-gray-200 flex items-center justify-center text-gray-400 select-none pointer-events-none">
|
|
246
|
+
No After Image
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
202
250
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
251
|
+
<div
|
|
252
|
+
className="absolute top-0 left-0 h-full overflow-hidden border-r"
|
|
253
|
+
style={{
|
|
254
|
+
width: `${sliderPosition}%`,
|
|
255
|
+
borderColor: sliderColor
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
<div
|
|
259
|
+
className="relative h-full"
|
|
260
|
+
style={{ width: containerWidth ? `${containerWidth}px` : '100%' }}
|
|
209
261
|
>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
>
|
|
214
|
-
{beforeBlock ? (
|
|
215
|
-
<div className="absolute top-0 left-0 w-full h-full pointer-events-none">
|
|
216
|
-
{renderBlock(beforeBlock)}
|
|
217
|
-
</div>
|
|
218
|
-
) : (
|
|
219
|
-
<div className="absolute top-0 left-0 w-full h-full bg-gray-300 flex items-center justify-center text-gray-500 select-none pointer-events-none">
|
|
220
|
-
No Before Image
|
|
221
|
-
</div>
|
|
222
|
-
)}
|
|
262
|
+
{beforeBlock ? (
|
|
263
|
+
<div className="absolute top-0 left-0 w-full h-full pointer-events-none">
|
|
264
|
+
{renderBlock(beforeBlock)}
|
|
223
265
|
</div>
|
|
266
|
+
) : (
|
|
267
|
+
<div className="absolute top-0 left-0 w-full h-full bg-gray-300 flex items-center justify-center text-gray-500 select-none pointer-events-none">
|
|
268
|
+
No Before Image
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
224
271
|
</div>
|
|
272
|
+
</div>
|
|
225
273
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
274
|
+
<div
|
|
275
|
+
className={clsx('absolute top-0 bottom-0 w-1 z-20 cursor-ew-resize')}
|
|
276
|
+
style={{
|
|
277
|
+
left: `${sliderPosition}%`,
|
|
278
|
+
backgroundColor: sliderColor,
|
|
279
|
+
transform: 'translateX(-50%)'
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 bg-white rounded-full shadow-lg flex items-center justify-center text-gray-800 pointer-events-none">
|
|
283
|
+
<svg
|
|
284
|
+
width="24"
|
|
285
|
+
height="24"
|
|
286
|
+
viewBox="0 0 24 24"
|
|
287
|
+
fill="none"
|
|
288
|
+
stroke="currentColor"
|
|
289
|
+
strokeWidth="2"
|
|
290
|
+
strokeLinecap="round"
|
|
291
|
+
strokeLinejoin="round"
|
|
292
|
+
>
|
|
293
|
+
<path d="M10 15l-3-3 3-3" />
|
|
294
|
+
<path d="M14 15l3-3-3-3" />
|
|
295
|
+
</svg>
|
|
242
296
|
</div>
|
|
297
|
+
</div>
|
|
243
298
|
</div>
|
|
244
299
|
);
|
|
245
300
|
}
|