@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
package/CHANGELOG.md CHANGED
@@ -1,12 +1,22 @@
1
1
  # @akinon/next
2
2
 
3
- ## 2.0.0-beta.19
3
+ ## 2.0.0-beta.20
4
+
5
+ ### Minor Changes
6
+
7
+ - cf3a9901: ZERO-3585: Fix cookie handling for widget builder in ClientRoot component
8
+ - 9076e50e: ZERO-3136: Refactor DynamicWidgetContainer and WidgetContent for improved styling and component tagging
9
+ - d0e1ef78: ZERO-3232: Integrate widget styles generation directly in WidgetPlaceholder
10
+ - 1d10dde2: ZERO-3553: Add alignment style for image components
11
+ - 74c6a237: ZERO-3060: Drag and drop functionality on the site implemented
12
+
13
+ ## 1.125.0
4
14
 
5
15
  ### Minor Changes
6
16
 
7
17
  - cd68a97a: ZERO-4126: Enhance error handling in appFetch and related data handlers to throw notFound on 404 and 422 errors
8
18
 
9
- ## 2.0.0-beta.18
19
+ ## 1.124.0
10
20
 
11
21
  ### Minor Changes
12
22
 
@@ -16,7 +26,7 @@
16
26
  - bd431e36: ZERO-3278: improve checkout validation error messages for better user guidance
17
27
  - 54eac86b: ZERO-3271: add development logger system
18
28
 
19
- ## 2.0.0-beta.17
29
+ ## 1.123.0
20
30
 
21
31
  ### Minor Changes
22
32
 
@@ -28,7 +38,7 @@
28
38
  - 94a86fcc: ZERO-4065: Expand skipSegments array to include additional segments for route generation
29
39
  - bcaad120: ZERO-4158: Add logging for OAuth login and callback redirects
30
40
 
31
- ## 2.0.0-beta.16
41
+ ## 1.122.0
32
42
 
33
43
  ### Minor Changes
34
44
 
@@ -38,7 +48,7 @@
38
48
  - 9b7d0de6: ZERO-3393: Improve error handling in checkout middleware to support both object and array error formats
39
49
  - 5ec0faf8: ZERO-3355: Refactor Redis client creation to include optional password in configuration
40
50
 
41
- ## 2.0.0-beta.15
51
+ ## 1.121.0
42
52
 
43
53
  ### Minor Changes
44
54
 
@@ -46,21 +56,18 @@
46
56
  - d7e5178b: ZERO-3985: Add query string handling for orders redirection in middleware
47
57
  - b59fdd1c: ZERO-4009: Add password reset token validation
48
58
 
49
- ## 2.0.0-beta.14
59
+ ## 1.120.0
50
60
 
51
61
  ### Minor Changes
52
62
 
63
+ - b12527ec: ZERO-4102: Add support for post-checkout redirect in middleware
53
64
  - aef81c5d: ZERO-4034: Refactor checkout API to use dynamic store imports for improved performance
54
65
  - 0754c835: ZERO-4063: Add support for optional Redis password in cache handlers
55
- - c6c5c1cd: ZERO-4128: Use cookies API for pz-pos-error to ensure delivery on 303 redirects
56
-
57
- ## 2.0.0-beta.13
58
-
59
- ### Minor Changes
60
-
61
- - b12527ec: ZERO-4102: Add support for post-checkout redirect in middleware
62
66
  - a9f5cdb1: ZERO-4102: Fix post-checkout condition to additionally check for empty search parameters.
63
67
  - 01ee41f1: ZERO-4102: Implement a post-checkout flow by dynamically determining checkout paths and managing a pz-post-checkout-flow cookie.
68
+ - c6c5c1cd: ZERO-4128: Use cookies API for pz-pos-error to ensure delivery on 303 redirects
69
+
70
+ ## 1.119.0
64
71
 
65
72
  ## 1.118.0
66
73
 
@@ -51,3 +51,87 @@
51
51
  z-index: 1001;
52
52
  }
53
53
  }
54
+
55
+ .container-droppable {
56
+ outline: 2px dashed #4482ff !important;
57
+ outline-offset: -2px;
58
+ background-color: rgba(96, 165, 250, 0.1) !important;
59
+ }
60
+
61
+ .container-dragover {
62
+ background-color: rgba(59, 130, 246, 0.1);
63
+ }
64
+
65
+ .container-dragover::after {
66
+ content: '';
67
+ position: absolute;
68
+ top: 0;
69
+ left: 0;
70
+ right: 0;
71
+ bottom: 0;
72
+ border: 2px solid #4482ff;
73
+ pointer-events: none;
74
+ }
75
+
76
+ .dragging * {
77
+ cursor: default !important;
78
+ }
79
+
80
+ .dragging [data-component='true'] {
81
+ cursor: grabbing !important;
82
+ }
83
+
84
+ .dragging .drop-target {
85
+ cursor: grab !important;
86
+ outline: 2px dashed #4482ff !important;
87
+ outline-offset: -2px;
88
+ }
89
+
90
+ .dragging .no-drop-target {
91
+ cursor: no-drop !important;
92
+ outline: 2px dashed #e63946 !important;
93
+ outline-offset: -2px;
94
+ }
95
+
96
+ [data-component='true'] {
97
+ cursor: pointer;
98
+ }
99
+
100
+ .carousel-container {
101
+ li[aria-hidden='false'] {
102
+ div {
103
+ display: block;
104
+ }
105
+ }
106
+ }
107
+
108
+ .posts-slider-carousel {
109
+ width: 100%;
110
+
111
+ .react-multi-carousel-track {
112
+ align-items: stretch;
113
+ }
114
+ }
115
+
116
+ .posts-slider-dots {
117
+ position: relative;
118
+ display: flex;
119
+ justify-content: center;
120
+ gap: 8px;
121
+ margin-top: 16px;
122
+ padding: 0;
123
+ list-style: none;
124
+ }
125
+
126
+ .posts-slider-dots li button {
127
+ width: 8px;
128
+ height: 8px;
129
+ border-radius: 9999px;
130
+ border: none;
131
+ background: rgba(0, 0, 0, 0.25);
132
+ cursor: pointer;
133
+ }
134
+
135
+ .posts-slider-dots li.react-multi-carousel-dot--active button {
136
+ background: rgba(0, 0, 0, 0.6);
137
+ }
@@ -1,12 +1,24 @@
1
1
  'use client';
2
2
 
3
3
  import React from 'react';
4
+ import { useCallback, useEffect } from 'react';
4
5
  import { useMobileIframeHandler } from '../hooks';
6
+ import { useAppDispatch, useAppSelector } from '../redux/hooks';
7
+ import {
8
+ setComponents,
9
+ setDataSources,
10
+ setDesignMode,
11
+ setDraggingActive,
12
+ setPlaceholders,
13
+ setResponsive,
14
+ setSelectedComponentId,
15
+ setSelectedPlaceholder,
16
+ setSelectedWidget
17
+ } from '../redux/reducers/widget';
5
18
  import { LoggerPopup } from './logger-popup';
6
19
  import { LoggerProvider } from '../hooks/use-logger-context';
7
20
  import * as Sentry from '@sentry/nextjs';
8
21
  import { initSentry } from '../sentry';
9
- import { useEffect } from 'react';
10
22
 
11
23
  export default function ClientRoot({
12
24
  children,
@@ -18,6 +30,100 @@ export default function ClientRoot({
18
30
  const { preventPageRender } = useMobileIframeHandler({
19
31
  sessionId: sessionId || ''
20
32
  });
33
+ const { components } = useAppSelector((state) => state.widget);
34
+ const dispatch = useAppDispatch();
35
+
36
+ const postMessage = (message: {
37
+ type: string;
38
+ data: Record<string, unknown>;
39
+ }) => {
40
+ window.parent.postMessage(message, '*');
41
+ };
42
+
43
+ const handleWidgetMessage = useCallback(
44
+ (event: MessageEvent) => {
45
+ if (event.data.type === 'UPDATE_COMPONENTS') {
46
+ const { components } = event.data.data;
47
+
48
+ dispatch(setComponents(components));
49
+ } else if (event.data.type === 'UPDATE_WIDGET_SYSTEM') {
50
+ const {
51
+ responsive,
52
+ designMode,
53
+ selectedComponentId,
54
+ placeholders,
55
+ dataSources
56
+ } = event.data.data;
57
+
58
+ dispatch(setResponsive(responsive));
59
+ dispatch(setDesignMode(designMode));
60
+ dispatch(setSelectedComponentId(selectedComponentId));
61
+ dispatch(setPlaceholders(placeholders));
62
+ dispatch(setDataSources(dataSources));
63
+ } else if (event.data.type === 'SELECT_PLACEHOLDER') {
64
+ const { placeholderSlug } = event.data.data;
65
+
66
+ dispatch(setSelectedPlaceholder(placeholderSlug));
67
+ } else if (event.data.type === 'SELECT_WIDGET') {
68
+ const { widgetSlug } = event.data.data;
69
+
70
+ dispatch(setSelectedWidget(widgetSlug));
71
+
72
+ postMessage({
73
+ type: 'SELECT_COMPONENT',
74
+ data: {
75
+ componentId: null
76
+ }
77
+ });
78
+ } else if (event.data.type === 'DRAG_START') {
79
+ dispatch(setDraggingActive(true));
80
+ } else if (event.data.type === 'DRAG_END') {
81
+ dispatch(setDraggingActive(false));
82
+ } else if (event.data.type === 'GET_DROP_TARGET') {
83
+ const { x, y, component } = event.data.data;
84
+ const elements = document.elementsFromPoint(x, y);
85
+ const dropTargetId = elements
86
+ .find((element) => element.hasAttribute('data-component'))
87
+ ?.getAttribute('data-id');
88
+
89
+ postMessage({
90
+ type: 'DROP_TARGET_RESPONSE',
91
+ data: {
92
+ targetId: dropTargetId,
93
+ component
94
+ }
95
+ });
96
+ } else if (event.data.type === 'SET_COOKIE') {
97
+ const { key, value } = event.data.data.value;
98
+
99
+ if (key && value) {
100
+ let cookieString = '';
101
+ if (window.parent !== window) {
102
+ cookieString = `${key}=${value}; path=/; SameSite=None; Secure`;
103
+ } else if (key === 'widget_builder') {
104
+ cookieString = `${key}=false; path=/; SameSite=None; Secure`;
105
+ }
106
+
107
+ if (cookieString) {
108
+ document.cookie = cookieString;
109
+ }
110
+ }
111
+ }
112
+ },
113
+ [dispatch, components]
114
+ );
115
+
116
+ useEffect(() => {
117
+ window.addEventListener('message', handleWidgetMessage);
118
+
119
+ if (window.parent === window) {
120
+ document.cookie = 'widget_builder=false; path=/; SameSite=None; Secure';
121
+ }
122
+
123
+ return () => {
124
+ window.removeEventListener('message', handleWidgetMessage);
125
+ };
126
+ }, [handleWidgetMessage]);
21
127
 
22
128
  const initializeSentry = async () => {
23
129
  const response = await fetch('/api/sentry', { next: { revalidate: 0 } });
@@ -19,26 +19,56 @@ export const Link = ({ children, href, ...rest }: LinkProps) => {
19
19
  return '#';
20
20
  }
21
21
 
22
- if (
23
- typeof href !== 'string' ||
24
- urlSchemes.some((scheme) => href.startsWith(scheme))
25
- ) {
26
- return href;
27
- }
22
+ if (typeof href === 'string') {
23
+ const trimmedHref = href.trim();
24
+ if (!trimmedHref) {
25
+ return '#';
26
+ }
27
+
28
+ if (urlSchemes.some((scheme) => trimmedHref.startsWith(scheme))) {
29
+ if (
30
+ trimmedHref.startsWith('mailto:') ||
31
+ trimmedHref.startsWith('tel:')
32
+ ) {
33
+ return trimmedHref;
34
+ }
35
+
36
+ try {
37
+ new URL(trimmedHref);
38
+ return trimmedHref;
39
+ } catch {
40
+ return '#';
41
+ }
42
+ }
28
43
 
29
- const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
30
- const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
44
+ try {
45
+ new URL(trimmedHref, 'http://localhost');
46
+ } catch {
47
+ return '#';
48
+ }
31
49
 
32
- if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
33
- return hrefWithLocale;
34
- } else if (
35
- localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
36
- locale !== defaultLocaleValue
37
- ) {
38
- return hrefWithLocale;
50
+ const pathnameWithoutLocale = trimmedHref.replace(
51
+ urlLocaleMatcherRegex,
52
+ ''
53
+ );
54
+ const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
55
+
56
+ if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
57
+ return hrefWithLocale;
58
+ } else if (
59
+ localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
60
+ locale !== defaultLocaleValue
61
+ ) {
62
+ return hrefWithLocale;
63
+ }
64
+
65
+ return trimmedHref || '#';
39
66
  }
40
67
 
41
- return href || '#';
68
+ if (typeof href !== 'string') {
69
+ return href;
70
+ }
71
+ return '#';
42
72
  }, [href, defaultLocaleValue, locale, localeUrlStrategy]);
43
73
 
44
74
  return (
@@ -0,0 +1,136 @@
1
+ import React, { useState } from 'react';
2
+ import { BlockRendererProps } from './block-renderer-registry';
3
+ import ThemeBlock from '../theme-block';
4
+
5
+ const AccordionBlock: React.FC<BlockRendererProps> = ({
6
+ block,
7
+ placeholderId,
8
+ sectionId,
9
+ isDesigner,
10
+ selectedBlockId,
11
+ currentBreakpoint = 'desktop'
12
+ }) => {
13
+ const childBlocks = block.blocks || [];
14
+
15
+ const defaultOpen = block.properties?.defaultOpen === true;
16
+ const [isOpen, setIsOpen] = useState(defaultOpen);
17
+
18
+ const headingBlock = childBlocks.find((b) => b.label === 'Accordion Heading');
19
+ const contentBlock = childBlocks.find((b) => b.label === 'Accordion Content');
20
+
21
+ if (!headingBlock && !contentBlock) {
22
+ return (
23
+ <div className="p-4 text-gray-400 border border-dashed border-gray-300 rounded">
24
+ No accordion content
25
+ </div>
26
+ );
27
+ }
28
+
29
+ const renderHeading = () => {
30
+ if (!headingBlock) return null;
31
+
32
+ return headingBlock.blocks
33
+ ?.filter((childBlock) => (isDesigner ? true : !childBlock.hidden))
34
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
35
+ .map((childBlock, index) => {
36
+ const modifiedBlock =
37
+ childBlock.type === 'icon'
38
+ ? {
39
+ ...childBlock,
40
+ styles: {
41
+ ...childBlock.styles,
42
+ transform: {
43
+ desktop: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
44
+ mobile: isOpen ? 'rotate(180deg)' : 'rotate(0deg)'
45
+ },
46
+ transition: {
47
+ desktop: 'transform 0.3s ease-in-out',
48
+ mobile: 'transform 0.3s ease-in-out'
49
+ }
50
+ }
51
+ }
52
+ : childBlock;
53
+
54
+ return (
55
+ <ThemeBlock
56
+ key={childBlock.id || `heading-block-${index}`}
57
+ block={modifiedBlock}
58
+ placeholderId={placeholderId}
59
+ sectionId={sectionId}
60
+ isDesigner={isDesigner}
61
+ isSelected={selectedBlockId === childBlock.id}
62
+ selectedBlockId={selectedBlockId}
63
+ currentBreakpoint={currentBreakpoint}
64
+ onMoveUp={undefined}
65
+ onMoveDown={undefined}
66
+ onDuplicate={undefined}
67
+ onToggleVisibility={undefined}
68
+ onDelete={undefined}
69
+ onRename={undefined}
70
+ />
71
+ );
72
+ });
73
+ };
74
+
75
+ const renderContent = () => {
76
+ if (!contentBlock) return null;
77
+
78
+ return contentBlock.blocks
79
+ ?.filter((childBlock) => (isDesigner ? true : !childBlock.hidden))
80
+ .sort((a, b) => (a.order || 0) - (b.order || 0))
81
+ .map((childBlock, index) => {
82
+ return (
83
+ <ThemeBlock
84
+ key={childBlock.id || `content-block-${index}`}
85
+ block={childBlock}
86
+ placeholderId={placeholderId}
87
+ sectionId={sectionId}
88
+ isDesigner={isDesigner}
89
+ isSelected={selectedBlockId === childBlock.id}
90
+ selectedBlockId={selectedBlockId}
91
+ currentBreakpoint={currentBreakpoint}
92
+ onMoveUp={undefined}
93
+ onMoveDown={undefined}
94
+ onDuplicate={undefined}
95
+ onToggleVisibility={undefined}
96
+ onDelete={undefined}
97
+ onRename={undefined}
98
+ />
99
+ );
100
+ });
101
+ };
102
+
103
+ const toggleAccordion = (e: React.MouseEvent) => {
104
+ if (isDesigner) {
105
+ const target = e.target as HTMLElement;
106
+ if (
107
+ target.closest('[data-action-toolbar]') ||
108
+ target.closest('button') ||
109
+ target.closest('input')
110
+ ) {
111
+ return;
112
+ }
113
+ }
114
+ setIsOpen(!isOpen);
115
+ };
116
+
117
+ return (
118
+ <>
119
+ <div style={{ cursor: 'pointer' }} onClick={toggleAccordion}>
120
+ {renderHeading()}
121
+ </div>
122
+
123
+ <div
124
+ style={{
125
+ display: 'grid',
126
+ gridTemplateRows: isOpen ? '1fr' : '0fr',
127
+ transition: 'grid-template-rows 0.3s ease-in-out'
128
+ }}
129
+ >
130
+ <div style={{ overflow: 'hidden' }}>{renderContent()}</div>
131
+ </div>
132
+ </>
133
+ );
134
+ };
135
+
136
+ export default AccordionBlock;
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import { Block } from '../theme-block';
3
+ import TextBlock from './text-block';
4
+ import ImageBlock from './image-block';
5
+ import DividerBlock from './divider-block';
6
+ import GroupBlock from './group-block';
7
+ import ButtonBlock from './button-block';
8
+ import IconBlock from './icon-block';
9
+ import VideoBlock from './video-block';
10
+ import AccordionBlock from './accordion-block';
11
+ import TabBlock from './tab-block';
12
+ import CounterBlock from './counter-block';
13
+ import ImageGalleryBlock from './image-gallery-block';
14
+ import InputBlock from './input-block';
15
+ import SliderBlock from './slider-block';
16
+ import HotspotBlock from './hotspot-block';
17
+ import LinkBlock from './link-block';
18
+ import LottieBlock from './lottie-block';
19
+ import EmbedBlock from './embed-block';
20
+ import MapBlock from './map-block';
21
+
22
+ export interface BlockRendererProps {
23
+ block: Block;
24
+ placeholderId: string;
25
+ sectionId: string;
26
+ isDesigner: boolean;
27
+ isSelected?: boolean;
28
+ selectedBlockId?: string | null;
29
+ currentBreakpoint?: string;
30
+ onMoveUp?: () => void;
31
+ onMoveDown?: () => void;
32
+ onDuplicate?: () => void;
33
+ onToggleVisibility?: () => void;
34
+ onDelete?: () => void;
35
+ onRename?: (newLabel: string) => void;
36
+ }
37
+
38
+ type BlockRenderer = React.ComponentType<BlockRendererProps>;
39
+
40
+ class BlockRendererRegistry {
41
+ private renderers: Map<string, BlockRenderer> = new Map();
42
+
43
+ register(type: string, renderer: BlockRenderer) {
44
+ this.renderers.set(type, renderer);
45
+ }
46
+
47
+ getRenderer(type: string): BlockRenderer | undefined {
48
+ return this.renderers.get(type);
49
+ }
50
+
51
+ hasRenderer(type: string): boolean {
52
+ return this.renderers.has(type);
53
+ }
54
+ }
55
+
56
+ const blockRendererRegistry = new BlockRendererRegistry();
57
+
58
+ blockRendererRegistry.register('text', TextBlock);
59
+ blockRendererRegistry.register('image', ImageBlock);
60
+ blockRendererRegistry.register('divider', DividerBlock);
61
+ blockRendererRegistry.register('group', GroupBlock);
62
+ blockRendererRegistry.register('button', ButtonBlock);
63
+ blockRendererRegistry.register('icon', IconBlock);
64
+ blockRendererRegistry.register('video', VideoBlock);
65
+ blockRendererRegistry.register('accordion', AccordionBlock);
66
+ blockRendererRegistry.register('tab', TabBlock);
67
+ blockRendererRegistry.register('counter', CounterBlock);
68
+ blockRendererRegistry.register('image-gallery', ImageGalleryBlock);
69
+ blockRendererRegistry.register('input', InputBlock);
70
+ blockRendererRegistry.register('slider', SliderBlock);
71
+ blockRendererRegistry.register('hotspot', HotspotBlock);
72
+ blockRendererRegistry.register('link', LinkBlock);
73
+ blockRendererRegistry.register('lottie', LottieBlock);
74
+ blockRendererRegistry.register('embed', EmbedBlock);
75
+ blockRendererRegistry.register('map', MapBlock);
76
+
77
+ export default blockRendererRegistry;