@akinon/pz-theme 2.0.0-beta.21

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 (62) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/package.json +27 -0
  3. package/readme.md +23 -0
  4. package/src/blocks/accordion-block.tsx +136 -0
  5. package/src/blocks/block-renderer-registry.tsx +77 -0
  6. package/src/blocks/button-block.tsx +593 -0
  7. package/src/blocks/counter-block.tsx +348 -0
  8. package/src/blocks/divider-block.tsx +20 -0
  9. package/src/blocks/embed-block.tsx +208 -0
  10. package/src/blocks/group-block.tsx +116 -0
  11. package/src/blocks/hotspot-block.tsx +147 -0
  12. package/src/blocks/icon-block.tsx +230 -0
  13. package/src/blocks/image-block.tsx +142 -0
  14. package/src/blocks/image-gallery-block.tsx +269 -0
  15. package/src/blocks/input-block.tsx +123 -0
  16. package/src/blocks/link-block.tsx +216 -0
  17. package/src/blocks/lottie-block.tsx +325 -0
  18. package/src/blocks/map-block.tsx +89 -0
  19. package/src/blocks/slider-block.tsx +595 -0
  20. package/src/blocks/tab-block.tsx +10 -0
  21. package/src/blocks/text-block.tsx +52 -0
  22. package/src/blocks/video-block.tsx +122 -0
  23. package/src/components/action-toolbar.tsx +305 -0
  24. package/src/components/designer-overlay.tsx +74 -0
  25. package/src/components/with-designer-features.tsx +142 -0
  26. package/src/dynamic-font-loader.tsx +79 -0
  27. package/src/hooks/use-designer-features.tsx +100 -0
  28. package/src/hooks/use-visibility-context.ts +27 -0
  29. package/src/index.ts +21 -0
  30. package/src/placeholder-registry.ts +31 -0
  31. package/src/sections/before-after-section.tsx +245 -0
  32. package/src/sections/contact-form-section.tsx +564 -0
  33. package/src/sections/countdown-campaign-banner-section.tsx +433 -0
  34. package/src/sections/coupon-banner-section.tsx +710 -0
  35. package/src/sections/divider-section.tsx +62 -0
  36. package/src/sections/featured-product-spotlight-section.tsx +507 -0
  37. package/src/sections/find-in-store-section.tsx +1995 -0
  38. package/src/sections/hover-showcase-section.tsx +326 -0
  39. package/src/sections/image-hotspot-section.tsx +142 -0
  40. package/src/sections/installment-options-section.tsx +1065 -0
  41. package/src/sections/notification-banner-section.tsx +173 -0
  42. package/src/sections/order-tracking-lookup-section.tsx +1379 -0
  43. package/src/sections/posts-slider-section.tsx +472 -0
  44. package/src/sections/pre-order-launch-banner-section.tsx +687 -0
  45. package/src/sections/section-renderer-registry.tsx +89 -0
  46. package/src/sections/section-wrapper.tsx +135 -0
  47. package/src/sections/shipping-threshold-progress-section.tsx +586 -0
  48. package/src/sections/stats-counter-section.tsx +486 -0
  49. package/src/sections/tabs-section.tsx +578 -0
  50. package/src/theme-block.tsx +102 -0
  51. package/src/theme-page-context.tsx +27 -0
  52. package/src/theme-placeholder-client.tsx +218 -0
  53. package/src/theme-placeholder-wrapper.tsx +786 -0
  54. package/src/theme-placeholder.tsx +305 -0
  55. package/src/theme-section.tsx +1241 -0
  56. package/src/theme-settings-context.tsx +13 -0
  57. package/src/utils/index.ts +791 -0
  58. package/src/utils/iterator-utils.test.ts +224 -0
  59. package/src/utils/iterator-utils.ts +617 -0
  60. package/src/utils/page-context-discovery.ts +119 -0
  61. package/src/utils/publish-window.ts +86 -0
  62. package/src/utils/visibility-rules.ts +188 -0
@@ -0,0 +1,218 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import ThemeSection, { Section } from './theme-section';
5
+ import {
6
+ registerPlaceholder,
7
+ unregisterPlaceholder
8
+ } from './placeholder-registry';
9
+ import { useVisibilityContext } from './hooks/use-visibility-context';
10
+ import { applyVisibilityRulesToSections } from './utils/visibility-rules';
11
+ import { isPublishWindowVisible } from './utils/publish-window';
12
+
13
+ // Global flag to track if IFRAME_READY has been sent (survives component remount)
14
+ declare global {
15
+ interface Window {
16
+ __iframeReadySent?: boolean;
17
+ }
18
+ }
19
+
20
+ interface ThemePlaceholderClientProps {
21
+ slug: string;
22
+ initialSections?: Section[];
23
+ initialPlaceholderId?: string;
24
+ }
25
+
26
+ export default function ThemePlaceholderClient({
27
+ slug,
28
+ initialSections = [],
29
+ initialPlaceholderId = ''
30
+ }: ThemePlaceholderClientProps) {
31
+ const [sections, setSections] = useState<Section[]>(initialSections);
32
+ const [placeholderSlug, setPlaceholderSlug] =
33
+ useState<string>(initialPlaceholderId);
34
+ const [selectedSectionId, setSelectedSectionId] = useState<string | null>(
35
+ null
36
+ );
37
+ const [selectedBlockId, setSelectedBlockId] = useState<string | null>(null);
38
+ const [currentBreakpoint, setCurrentBreakpoint] = useState<string>('desktop');
39
+ const [isDesigner, setIsDesigner] = useState(false);
40
+ const visibilityContext = useVisibilityContext(currentBreakpoint);
41
+ const renderedSections = applyVisibilityRulesToSections(
42
+ sections,
43
+ visibilityContext
44
+ );
45
+
46
+ useEffect(() => {
47
+ if (typeof window === 'undefined') {
48
+ return;
49
+ }
50
+
51
+ const checkDesignerMode = () => {
52
+ return window.self !== window.top;
53
+ };
54
+
55
+ const isInIframe = checkDesignerMode();
56
+
57
+ if (isInIframe && window.parent) {
58
+ // Only send IFRAME_READY once per page session to prevent re-initialization
59
+ if (!window.__iframeReadySent) {
60
+ window.parent.postMessage(
61
+ {
62
+ type: 'IFRAME_READY'
63
+ },
64
+ '*'
65
+ );
66
+ window.__iframeReadySent = true;
67
+ }
68
+
69
+ registerPlaceholder(slug);
70
+ }
71
+
72
+ const handleMessage = (event: MessageEvent) => {
73
+ switch (event.data?.type) {
74
+ case 'SET_THEME_EDITOR_COOKIE':
75
+ if (event.data.data) {
76
+ setIsDesigner(true);
77
+ }
78
+ break;
79
+
80
+ case 'LOAD_THEME':
81
+ case 'UPDATE_THEME': {
82
+ const theme = event.data.data?.theme;
83
+ if (theme?.placeholders) {
84
+ const placeholder = theme.placeholders.find(
85
+ (p: { slug: string }) => p.slug === slug
86
+ );
87
+ if (placeholder) {
88
+ setPlaceholderSlug(placeholder.slug);
89
+ if (placeholder.sections) {
90
+ setSections(placeholder.sections);
91
+ }
92
+ }
93
+ }
94
+ break;
95
+ }
96
+
97
+ case 'SELECT_SECTION': {
98
+ const { sectionId } = event.data.data || {};
99
+ if (sectionId) {
100
+ setSelectedSectionId(sectionId);
101
+ setSelectedBlockId(null);
102
+ }
103
+ break;
104
+ }
105
+
106
+ case 'SELECT_BLOCK': {
107
+ const { blockId } = event.data.data || {};
108
+ if (blockId) {
109
+ setSelectedBlockId(blockId);
110
+ setSelectedSectionId(null);
111
+ }
112
+ break;
113
+ }
114
+
115
+ case 'CHANGE_BREAKPOINT': {
116
+ const { breakpoint } = event.data.data || {};
117
+ if (breakpoint) {
118
+ setCurrentBreakpoint(breakpoint);
119
+ }
120
+ break;
121
+ }
122
+
123
+ case 'CLEAR_SELECTION': {
124
+ setSelectedSectionId(null);
125
+ setSelectedBlockId(null);
126
+ break;
127
+ }
128
+ }
129
+ };
130
+
131
+ window.addEventListener('message', handleMessage);
132
+ return () => {
133
+ window.removeEventListener('message', handleMessage);
134
+ unregisterPlaceholder(slug);
135
+ };
136
+ }, [slug]);
137
+
138
+ const sendAction = (
139
+ action: string,
140
+ data: {
141
+ placeholderId?: string;
142
+ sectionId?: string;
143
+ blockId?: string;
144
+ label?: string;
145
+ }
146
+ ) => {
147
+ if (window.parent) {
148
+ window.parent.postMessage(
149
+ {
150
+ type: action,
151
+ data
152
+ },
153
+ '*'
154
+ );
155
+ }
156
+ };
157
+
158
+ return (
159
+ <div data-placeholder={slug} className="theme-placeholder">
160
+ {renderedSections
161
+ .sort((a, b) => a.order - b.order)
162
+ .filter((section) =>
163
+ !section.hidden &&
164
+ (isDesigner ||
165
+ isPublishWindowVisible(section.properties?.['publish-window']))
166
+ )
167
+ .map((section) => (
168
+ <ThemeSection
169
+ key={section.id}
170
+ section={section}
171
+ placeholderId={placeholderSlug}
172
+ isDesigner={isDesigner}
173
+ isSelected={selectedSectionId === section.id}
174
+ selectedBlockId={selectedBlockId}
175
+ currentBreakpoint={currentBreakpoint}
176
+ onSelect={setSelectedSectionId}
177
+ onMoveUp={() =>
178
+ sendAction('MOVE_SECTION_UP', {
179
+ placeholderId: placeholderSlug,
180
+ sectionId: section.id
181
+ })
182
+ }
183
+ onMoveDown={() =>
184
+ sendAction('MOVE_SECTION_DOWN', {
185
+ placeholderId: placeholderSlug,
186
+ sectionId: section.id
187
+ })
188
+ }
189
+ onDuplicate={() =>
190
+ sendAction('DUPLICATE_SECTION', {
191
+ placeholderId: placeholderSlug,
192
+ sectionId: section.id
193
+ })
194
+ }
195
+ onToggleVisibility={() =>
196
+ sendAction('TOGGLE_SECTION_VISIBILITY', {
197
+ placeholderId: placeholderSlug,
198
+ sectionId: section.id
199
+ })
200
+ }
201
+ onDelete={() =>
202
+ sendAction('DELETE_SECTION', {
203
+ placeholderId: placeholderSlug,
204
+ sectionId: section.id
205
+ })
206
+ }
207
+ onRename={(newLabel) =>
208
+ sendAction('RENAME_SECTION', {
209
+ placeholderId: placeholderSlug,
210
+ sectionId: section.id,
211
+ label: newLabel
212
+ })
213
+ }
214
+ />
215
+ ))}
216
+ </div>
217
+ );
218
+ }