@akinon/next 2.0.0-beta.19 → 2.0.0-beta.20

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 (73) hide show
  1. package/CHANGELOG.md +20 -13
  2. package/assets/styles/index.scss +84 -0
  3. package/components/client-root.tsx +107 -1
  4. package/components/link.tsx +46 -16
  5. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  6. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  7. package/components/theme-editor/blocks/button-block.tsx +593 -0
  8. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  9. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  10. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  11. package/components/theme-editor/blocks/group-block.tsx +116 -0
  12. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  13. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  14. package/components/theme-editor/blocks/image-block.tsx +137 -0
  15. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  16. package/components/theme-editor/blocks/input-block.tsx +123 -0
  17. package/components/theme-editor/blocks/link-block.tsx +216 -0
  18. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  19. package/components/theme-editor/blocks/map-block.tsx +89 -0
  20. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  21. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  22. package/components/theme-editor/blocks/text-block.tsx +52 -0
  23. package/components/theme-editor/blocks/video-block.tsx +122 -0
  24. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  25. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  26. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  27. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  28. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  29. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  30. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  31. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  32. package/components/theme-editor/placeholder-registry.ts +31 -0
  33. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  34. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  35. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  36. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  37. package/components/theme-editor/sections/divider-section.tsx +62 -0
  38. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  39. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  40. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  41. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  42. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  43. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  44. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  45. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  46. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  47. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  48. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  49. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  50. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  51. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  52. package/components/theme-editor/theme-block.tsx +102 -0
  53. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  54. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  55. package/components/theme-editor/theme-placeholder.tsx +288 -0
  56. package/components/theme-editor/theme-section.tsx +1224 -0
  57. package/components/theme-editor/theme-settings-context.tsx +13 -0
  58. package/components/theme-editor/utils/index.ts +792 -0
  59. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  60. package/components/theme-editor/utils/publish-window.ts +86 -0
  61. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  62. package/data/client/misc.ts +13 -1
  63. package/data/server/widget.ts +68 -1
  64. package/data/urls.ts +3 -1
  65. package/hooks/use-router.ts +53 -19
  66. package/lib/cache.ts +1 -0
  67. package/package.json +4 -2
  68. package/redux/reducers/index.ts +2 -0
  69. package/redux/reducers/widget.ts +80 -0
  70. package/types/commerce/widget.ts +33 -0
  71. package/types/widget.ts +80 -0
  72. package/utils/widget-styles.ts +107 -0
  73. package/with-pz-config.js +1 -1
@@ -0,0 +1,578 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { Section } from '../theme-section';
3
+ import ThemeBlock from '../theme-block';
4
+ import {
5
+ getCSSStyles,
6
+ resolveThemeCssVariables,
7
+ resolveThemeStyleObject
8
+ } from '../utils';
9
+ import { twMerge } from 'tailwind-merge';
10
+ import clsx from 'clsx';
11
+ import { useThemeSettingsContext } from '../theme-settings-context';
12
+
13
+ interface TabsSectionProps {
14
+ section: Section;
15
+ currentBreakpoint?: string;
16
+ placeholderId?: string;
17
+ isDesigner?: boolean;
18
+ selectedBlockId?: string | null;
19
+ }
20
+
21
+ const TabsSection: React.FC<TabsSectionProps> = ({
22
+ section,
23
+ currentBreakpoint = 'desktop',
24
+ placeholderId = '',
25
+ isDesigner = false,
26
+ selectedBlockId = null
27
+ }) => {
28
+ const [activeTabIndex, setActiveTabIndex] = useState(0);
29
+ const themeSettings = useThemeSettingsContext();
30
+
31
+ // Separate tab blocks from non-tab blocks (like headers)
32
+ const nonTabBlocks = section.blocks
33
+ .filter((block) => block.type !== 'tab' && (isDesigner ? true : !block.hidden))
34
+ .sort((a, b) => (a.order || 0) - (b.order || 0));
35
+
36
+ const tabBlocks = section.blocks
37
+ .filter((block) => block.type === 'tab' && (isDesigner ? true : !block.hidden))
38
+ .sort((a, b) => (a.order || 0) - (b.order || 0));
39
+
40
+ const getResponsiveValue = useCallback(
41
+ <T,>(value: unknown, fallback: T): T => {
42
+ if (value === undefined || value === null || value === '') return fallback;
43
+ if (typeof value === 'object' && !Array.isArray(value)) {
44
+ const responsiveValue = value as Record<string, T | undefined> & {
45
+ desktop?: T;
46
+ };
47
+ const matchedValue =
48
+ responsiveValue[currentBreakpoint] ?? responsiveValue.desktop;
49
+ return matchedValue !== undefined ? (matchedValue as T) : fallback;
50
+ }
51
+ return value as T;
52
+ },
53
+ [currentBreakpoint]
54
+ );
55
+
56
+ const findTabIndexBySelectedBlock = useCallback(
57
+ (blockId: string | null): number => {
58
+ if (!blockId) return -1;
59
+
60
+ const hasBlockInTree = (blocks?: Section['blocks']): boolean => {
61
+ if (!blocks || blocks.length === 0) return false;
62
+
63
+ for (const block of blocks) {
64
+ if (block.id === blockId) return true;
65
+ if (hasBlockInTree(block.blocks)) return true;
66
+ }
67
+
68
+ return false;
69
+ };
70
+
71
+ return tabBlocks.findIndex((tabBlock) => {
72
+ if (tabBlock.id === blockId) return true;
73
+ return hasBlockInTree(tabBlock.blocks);
74
+ });
75
+ },
76
+ [tabBlocks]
77
+ );
78
+
79
+ useEffect(() => {
80
+ if (tabBlocks.length === 0) {
81
+ if (activeTabIndex !== 0) setActiveTabIndex(0);
82
+ return;
83
+ }
84
+
85
+ const selectedTabIndex = findTabIndexBySelectedBlock(selectedBlockId);
86
+ if (selectedTabIndex >= 0) {
87
+ if (selectedTabIndex !== activeTabIndex) {
88
+ setActiveTabIndex(selectedTabIndex);
89
+ }
90
+ return;
91
+ }
92
+
93
+ if (activeTabIndex > tabBlocks.length - 1) {
94
+ setActiveTabIndex(tabBlocks.length - 1);
95
+ }
96
+ }, [
97
+ activeTabIndex,
98
+ findTabIndexBySelectedBlock,
99
+ selectedBlockId,
100
+ tabBlocks
101
+ ]);
102
+
103
+ const activeTabColor = resolveThemeCssVariables(
104
+ getResponsiveValue<string>(
105
+ section.styles?.['active-tab-color'],
106
+ 'var(--theme-primary)'
107
+ ),
108
+ themeSettings
109
+ );
110
+ const activeTabBorderColor = resolveThemeCssVariables(
111
+ getResponsiveValue<string>(
112
+ section.styles?.['active-tab-border-color'],
113
+ 'var(--theme-primary)'
114
+ ),
115
+ themeSettings
116
+ );
117
+ const activeTabBackgroundColor = resolveThemeCssVariables(
118
+ getResponsiveValue<string>(
119
+ section.styles?.['active-tab-background-color'],
120
+ 'transparent'
121
+ ),
122
+ themeSettings
123
+ );
124
+ const headerBorderBottom = resolveThemeCssVariables(
125
+ getResponsiveValue<string>(
126
+ section.styles?.['header-border-bottom'],
127
+ '1px solid #e5e7eb'
128
+ ),
129
+ themeSettings
130
+ );
131
+
132
+ const kebabToCamel = (str: string): string => {
133
+ return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
134
+ };
135
+
136
+ const getCSSProperties = (
137
+ properties?: Record<string, unknown>
138
+ ): React.CSSProperties => {
139
+ if (!properties) return {};
140
+
141
+ const styles: Record<string, string | number> = {};
142
+
143
+ Object.keys(properties).forEach((key) => {
144
+ const value = getResponsiveValue<string | number | undefined>(
145
+ properties[key],
146
+ undefined
147
+ );
148
+ if (value === undefined || value === null) return;
149
+
150
+ const camelKey = kebabToCamel(key);
151
+ let finalValue: string | number = value;
152
+
153
+ if (key.startsWith('padding-') || key.startsWith('margin-')) {
154
+ finalValue =
155
+ typeof value === 'number' ? `${value}px` : (value as string);
156
+ } else if (typeof value === 'string') {
157
+ finalValue = resolveThemeCssVariables(value, themeSettings);
158
+ }
159
+
160
+ styles[camelKey] = finalValue;
161
+ });
162
+
163
+ return styles as React.CSSProperties;
164
+ };
165
+
166
+ const sectionStyles = {
167
+ ...getCSSProperties(section.styles),
168
+ position: 'relative'
169
+ } as React.CSSProperties;
170
+
171
+ const containerStyles: Record<string, string | number> = {
172
+ ...(sectionStyles as Record<string, string | number>)
173
+ };
174
+
175
+ const maxWidth = getResponsiveValue(
176
+ section.styles?.['max-width'],
177
+ 'normal'
178
+ ) as string;
179
+ const maxWidthClass =
180
+ maxWidth === 'narrow'
181
+ ? 'max-w-4xl'
182
+ : maxWidth === 'normal'
183
+ ? 'max-w-7xl'
184
+ : '';
185
+ const hasMaxWidth = maxWidth !== 'none' && maxWidth !== 'full';
186
+
187
+ if (tabBlocks.length === 0) {
188
+ return (
189
+ <div
190
+ className="p-4 text-gray-400 border border-dashed border-gray-300 rounded"
191
+ style={sectionStyles}
192
+ >
193
+ No tabs available
194
+ </div>
195
+ );
196
+ }
197
+
198
+ return (
199
+ <div
200
+ className={twMerge(
201
+ clsx(
202
+ 'relative z-10 group/blocks w-full',
203
+ hasMaxWidth && 'mx-auto',
204
+ maxWidthClass
205
+ )
206
+ )}
207
+ style={containerStyles as React.CSSProperties}
208
+ >
209
+ {/* Render non-tab blocks first (like FAQ Header) */}
210
+ {nonTabBlocks.map((block, index) => (
211
+ <ThemeBlock
212
+ key={block.id || `non-tab-block-${index}`}
213
+ block={block}
214
+ placeholderId={placeholderId}
215
+ sectionId={section.id}
216
+ isDesigner={isDesigner}
217
+ isSelected={selectedBlockId === block.id}
218
+ selectedBlockId={selectedBlockId}
219
+ onMoveUp={() => {
220
+ if (window.parent) {
221
+ window.parent.postMessage(
222
+ {
223
+ type: 'MOVE_BLOCK_UP',
224
+ data: {
225
+ placeholderId,
226
+ sectionId: section.id,
227
+ blockId: block.id
228
+ }
229
+ },
230
+ '*'
231
+ );
232
+ }
233
+ }}
234
+ onMoveDown={() => {
235
+ if (window.parent) {
236
+ window.parent.postMessage(
237
+ {
238
+ type: 'MOVE_BLOCK_DOWN',
239
+ data: {
240
+ placeholderId,
241
+ sectionId: section.id,
242
+ blockId: block.id
243
+ }
244
+ },
245
+ '*'
246
+ );
247
+ }
248
+ }}
249
+ onDuplicate={() => {
250
+ if (window.parent) {
251
+ window.parent.postMessage(
252
+ {
253
+ type: 'DUPLICATE_BLOCK',
254
+ data: {
255
+ placeholderId,
256
+ sectionId: section.id,
257
+ blockId: block.id
258
+ }
259
+ },
260
+ '*'
261
+ );
262
+ }
263
+ }}
264
+ onToggleVisibility={() => {
265
+ if (window.parent) {
266
+ window.parent.postMessage(
267
+ {
268
+ type: 'TOGGLE_BLOCK_VISIBILITY',
269
+ data: {
270
+ placeholderId,
271
+ sectionId: section.id,
272
+ blockId: block.id
273
+ }
274
+ },
275
+ '*'
276
+ );
277
+ }
278
+ }}
279
+ onDelete={() => {
280
+ if (window.parent) {
281
+ window.parent.postMessage(
282
+ {
283
+ type: 'DELETE_BLOCK',
284
+ data: {
285
+ placeholderId,
286
+ sectionId: section.id,
287
+ blockId: block.id
288
+ }
289
+ },
290
+ '*'
291
+ );
292
+ }
293
+ }}
294
+ onRename={(newLabel) => {
295
+ if (window.parent) {
296
+ window.parent.postMessage(
297
+ {
298
+ type: 'RENAME_BLOCK',
299
+ data: {
300
+ placeholderId,
301
+ sectionId: section.id,
302
+ blockId: block.id,
303
+ label: newLabel
304
+ }
305
+ },
306
+ '*'
307
+ );
308
+ }
309
+ }}
310
+ />
311
+ ))}
312
+
313
+ {/* Tab headers */}
314
+ <div
315
+ style={{
316
+ display: 'flex',
317
+ gap: '0px',
318
+ borderBottom: headerBorderBottom,
319
+ position: 'relative'
320
+ }}
321
+ >
322
+ {tabBlocks.map((tabBlock, index) => {
323
+ const headerBlock = tabBlock.blocks?.find(
324
+ (b) => b.label === 'Tab Header'
325
+ );
326
+
327
+ if (!headerBlock) return null;
328
+
329
+ const isActive = index === activeTabIndex;
330
+ const headerStyles = resolveThemeStyleObject(
331
+ getCSSStyles(headerBlock.styles || {}, themeSettings, currentBreakpoint),
332
+ themeSettings
333
+ );
334
+ const cleanHeaderStyles = {
335
+ ...headerStyles
336
+ } as Record<string, string | number>;
337
+ const headerTextColor = cleanHeaderStyles.color as string | undefined;
338
+
339
+ delete cleanHeaderStyles.border;
340
+ delete cleanHeaderStyles.borderBottom;
341
+ delete cleanHeaderStyles.backgroundColor;
342
+ delete cleanHeaderStyles.color;
343
+
344
+ return (
345
+ <div
346
+ key={tabBlock.id || `tab-header-${index}`}
347
+ className="tab-header-wrapper"
348
+ style={{
349
+ position: 'relative'
350
+ }}
351
+ >
352
+ {isDesigner && (
353
+ <div
354
+ style={{
355
+ position: 'absolute',
356
+ top: 0,
357
+ left: 0,
358
+ right: 0,
359
+ zIndex: 10,
360
+ pointerEvents: 'none',
361
+ height: '100%'
362
+ }}
363
+ >
364
+ <ThemeBlock
365
+ block={tabBlock}
366
+ placeholderId={placeholderId}
367
+ sectionId={section.id}
368
+ isDesigner={isDesigner}
369
+ isSelected={selectedBlockId === tabBlock.id}
370
+ selectedBlockId={selectedBlockId}
371
+ onMoveUp={() => {
372
+ if (window.parent) {
373
+ window.parent.postMessage(
374
+ {
375
+ type: 'MOVE_BLOCK_UP',
376
+ data: {
377
+ placeholderId,
378
+ sectionId: section.id,
379
+ blockId: tabBlock.id
380
+ }
381
+ },
382
+ '*'
383
+ );
384
+ }
385
+ }}
386
+ onMoveDown={() => {
387
+ if (window.parent) {
388
+ window.parent.postMessage(
389
+ {
390
+ type: 'MOVE_BLOCK_DOWN',
391
+ data: {
392
+ placeholderId,
393
+ sectionId: section.id,
394
+ blockId: tabBlock.id
395
+ }
396
+ },
397
+ '*'
398
+ );
399
+ }
400
+ }}
401
+ onDuplicate={() => {
402
+ if (window.parent) {
403
+ window.parent.postMessage(
404
+ {
405
+ type: 'DUPLICATE_BLOCK',
406
+ data: {
407
+ placeholderId,
408
+ sectionId: section.id,
409
+ blockId: tabBlock.id
410
+ }
411
+ },
412
+ '*'
413
+ );
414
+ }
415
+ }}
416
+ onToggleVisibility={() => {
417
+ if (window.parent) {
418
+ window.parent.postMessage(
419
+ {
420
+ type: 'TOGGLE_BLOCK_VISIBILITY',
421
+ data: {
422
+ placeholderId,
423
+ sectionId: section.id,
424
+ blockId: tabBlock.id
425
+ }
426
+ },
427
+ '*'
428
+ );
429
+ }
430
+ }}
431
+ onDelete={() => {
432
+ if (window.parent) {
433
+ window.parent.postMessage(
434
+ {
435
+ type: 'DELETE_BLOCK',
436
+ data: {
437
+ placeholderId,
438
+ sectionId: section.id,
439
+ blockId: tabBlock.id
440
+ }
441
+ },
442
+ '*'
443
+ );
444
+ }
445
+ }}
446
+ onRename={(newLabel) => {
447
+ if (window.parent) {
448
+ window.parent.postMessage(
449
+ {
450
+ type: 'RENAME_BLOCK',
451
+ data: {
452
+ placeholderId,
453
+ sectionId: section.id,
454
+ blockId: tabBlock.id,
455
+ label: newLabel
456
+ }
457
+ },
458
+ '*'
459
+ );
460
+ }
461
+ }}
462
+ />
463
+ </div>
464
+ )}
465
+
466
+ <div
467
+ style={{
468
+ ...cleanHeaderStyles,
469
+ cursor: 'pointer',
470
+ borderTop: 'none',
471
+ borderLeft: 'none',
472
+ borderRight: 'none',
473
+ borderBottom: isActive
474
+ ? `2px solid ${activeTabBorderColor}`
475
+ : '2px solid transparent',
476
+ backgroundColor: isActive
477
+ ? activeTabBackgroundColor
478
+ : 'transparent',
479
+ color: isActive
480
+ ? activeTabColor
481
+ : headerTextColor || 'inherit',
482
+ position: 'relative',
483
+ zIndex: 1
484
+ }}
485
+ onClick={() => setActiveTabIndex(index)}
486
+ >
487
+ {headerBlock.blocks
488
+ ?.filter((childBlock) => (isDesigner ? true : !childBlock.hidden))
489
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
490
+ .map((childBlock, childIndex) => {
491
+ // If tab is active, we want to override the child's color with the active tab color
492
+ // So we remove the color from the child's styles to let it inherit
493
+ const blockToRender =
494
+ isActive && childBlock.styles?.color
495
+ ? {
496
+ ...childBlock,
497
+ styles: {
498
+ ...childBlock.styles,
499
+ color: undefined
500
+ }
501
+ }
502
+ : childBlock;
503
+
504
+ return (
505
+ <ThemeBlock
506
+ key={childBlock.id || `header-child-${childIndex}`}
507
+ block={blockToRender}
508
+ placeholderId={placeholderId}
509
+ sectionId={section.id}
510
+ isDesigner={isDesigner}
511
+ isSelected={selectedBlockId === childBlock.id}
512
+ selectedBlockId={selectedBlockId}
513
+ currentBreakpoint={currentBreakpoint}
514
+ onMoveUp={undefined}
515
+ onMoveDown={undefined}
516
+ onDuplicate={undefined}
517
+ onToggleVisibility={undefined}
518
+ onDelete={undefined}
519
+ onRename={undefined}
520
+ />
521
+ );
522
+ })}
523
+ </div>
524
+ </div>
525
+ );
526
+ })}
527
+ </div>
528
+
529
+ <div>
530
+ {tabBlocks.map((tabBlock, index) => {
531
+ if (index !== activeTabIndex) return null;
532
+
533
+ const contentBlock = tabBlock.blocks?.find(
534
+ (b) => b.label === 'Tab Content'
535
+ );
536
+
537
+ if (!contentBlock) return null;
538
+
539
+ const contentStyles = resolveThemeStyleObject(
540
+ getCSSStyles(contentBlock.styles || {}, themeSettings, currentBreakpoint),
541
+ themeSettings
542
+ );
543
+
544
+ return (
545
+ <div
546
+ key={tabBlock.id || `tab-content-${index}`}
547
+ style={contentStyles}
548
+ >
549
+ {contentBlock.blocks
550
+ ?.filter((childBlock) => (isDesigner ? true : !childBlock.hidden))
551
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
552
+ .map((childBlock, childIndex) => (
553
+ <ThemeBlock
554
+ key={childBlock.id || `content-child-${childIndex}`}
555
+ block={childBlock}
556
+ placeholderId={placeholderId}
557
+ sectionId={section.id}
558
+ isDesigner={isDesigner}
559
+ isSelected={selectedBlockId === childBlock.id}
560
+ selectedBlockId={selectedBlockId}
561
+ currentBreakpoint={currentBreakpoint}
562
+ onMoveUp={undefined}
563
+ onMoveDown={undefined}
564
+ onDuplicate={undefined}
565
+ onToggleVisibility={undefined}
566
+ onDelete={undefined}
567
+ onRename={undefined}
568
+ />
569
+ ))}
570
+ </div>
571
+ );
572
+ })}
573
+ </div>
574
+ </div>
575
+ );
576
+ };
577
+
578
+ export default TabsSection;
@@ -0,0 +1,102 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import blockRendererRegistry from './blocks/block-renderer-registry';
5
+ import { WithDesignerFeatures } from './components/with-designer-features';
6
+ import { isPublishWindowVisible } from './utils';
7
+
8
+ export interface Block {
9
+ id: string;
10
+ styleSourceId?: string;
11
+ type: string;
12
+ label: string;
13
+ value?: any;
14
+ properties: any;
15
+ styles: any;
16
+ order: number;
17
+ hidden: boolean;
18
+ locked?: boolean;
19
+ blocks?: Block[]; // Optional - only group blocks have nested blocks
20
+ isIterator?: boolean;
21
+ iteratorDataPath?: string;
22
+ }
23
+
24
+ interface ThemeBlockProps {
25
+ block: Block;
26
+ placeholderId: string;
27
+ selectedBlockId?: string | null;
28
+ sectionId: string;
29
+ isDesigner?: boolean;
30
+ isSelected?: boolean;
31
+ currentBreakpoint?: string;
32
+ onSelect?: (blockId: string) => void;
33
+ onMoveUp?: () => void;
34
+ onMoveDown?: () => void;
35
+ onDuplicate?: () => void;
36
+ onToggleVisibility?: () => void;
37
+ onDelete?: () => void;
38
+ onRename?: (newLabel: string) => void;
39
+ }
40
+
41
+ export default function ThemeBlock({
42
+ block,
43
+ placeholderId,
44
+ selectedBlockId,
45
+ sectionId,
46
+ isDesigner = false,
47
+ isSelected = false,
48
+ currentBreakpoint = 'desktop',
49
+ onSelect,
50
+ onMoveUp,
51
+ onMoveDown,
52
+ onDuplicate,
53
+ onToggleVisibility,
54
+ onDelete,
55
+ onRename
56
+ }: ThemeBlockProps) {
57
+ const BlockRenderer = blockRendererRegistry.getRenderer(block.type);
58
+
59
+ if (!BlockRenderer) {
60
+ return <div>Unknown block type: {block.type}</div>;
61
+ }
62
+
63
+ if (
64
+ !isDesigner &&
65
+ !isPublishWindowVisible(block.properties?.['publish-window'])
66
+ ) {
67
+ return null;
68
+ }
69
+
70
+ return (
71
+ <WithDesignerFeatures
72
+ block={block}
73
+ placeholderId={placeholderId}
74
+ sectionId={sectionId}
75
+ isDesigner={isDesigner}
76
+ isSelected={isSelected}
77
+ currentBreakpoint={currentBreakpoint}
78
+ onMoveUp={onMoveUp}
79
+ onMoveDown={onMoveDown}
80
+ onDuplicate={onDuplicate}
81
+ onToggleVisibility={onToggleVisibility}
82
+ onDelete={onDelete}
83
+ onRename={onRename}
84
+ >
85
+ <BlockRenderer
86
+ block={block}
87
+ placeholderId={placeholderId}
88
+ sectionId={sectionId}
89
+ isDesigner={isDesigner}
90
+ isSelected={isSelected}
91
+ selectedBlockId={selectedBlockId}
92
+ currentBreakpoint={currentBreakpoint}
93
+ onMoveUp={onMoveUp}
94
+ onMoveDown={onMoveDown}
95
+ onDuplicate={onDuplicate}
96
+ onToggleVisibility={onToggleVisibility}
97
+ onDelete={onDelete}
98
+ onRename={onRename}
99
+ />
100
+ </WithDesignerFeatures>
101
+ );
102
+ }