@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # @akinon/pz-theme
2
+
3
+ ## 2.0.0-beta.21
4
+
5
+ ### Patch Changes
6
+
7
+ - 5d44ff77: ZERO-4312: Extract ThemePlaceholder system into new @akinon/pz-theme package and strip native widget code
8
+
9
+ Introduce a dedicated `@akinon/pz-theme` package that contains the ThemePlaceholder system (CMS-driven sections/blocks, designer features, utilities). Remove native widget registration infrastructure from both `@akinon/pz-theme` and `projectzeronext` so the beta branch can be merged to main with only stable ThemePlaceholder functionality. Native widget integrations will be reintroduced via `@akinon/pz-theme` once they stabilize on the `theme-editor` branch.
10
+
11
+ Consumers should update imports:
12
+
13
+ - `@akinon/next/components/theme-editor/*` -> `@akinon/pz-theme/src/*`
14
+ - Add `@akinon/pz-theme` to package.json dependencies (matching `@akinon/next` version)
15
+
16
+ - Updated dependencies [5d44ff77]
17
+ - @akinon/next@2.0.0-beta.21
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@akinon/pz-theme",
3
+ "description": "Theme package for Project Zero Next — ThemePlaceholder system and theme editor infrastructure",
4
+ "version": "2.0.0-beta.21",
5
+ "license": "MIT",
6
+ "main": "src/index.ts",
7
+ "peerDependencies": {
8
+ "@akinon/next": "2.0.0-beta.21",
9
+ "react": "^18.0.0 || ^19.0.0",
10
+ "react-dom": "^18.0.0 || ^19.0.0"
11
+ },
12
+ "dependencies": {
13
+ "clsx": "2.1.1",
14
+ "lottie-web": "^5.13.0",
15
+ "tailwind-merge": "^2.5.4"
16
+ },
17
+ "devDependencies": {
18
+ "@akinon/next": "2.0.0-beta.21",
19
+ "@types/node": "^18.7.8",
20
+ "@types/react": "^18.0.17",
21
+ "@types/react-dom": "^18.0.6",
22
+ "prettier": "^3.0.3",
23
+ "react": "19.2.5",
24
+ "react-dom": "19.2.5",
25
+ "typescript": "^5.2.2"
26
+ }
27
+ }
package/readme.md ADDED
@@ -0,0 +1,23 @@
1
+ # @akinon/pz-theme
2
+
3
+ Theme package for Project Zero Next. Contains the ThemePlaceholder system and theme editor infrastructure.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @akinon/pz-theme
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### ThemePlaceholder
14
+
15
+ Render CMS-driven sections on a page:
16
+
17
+ ```tsx
18
+ import ThemePlaceholder from '@akinon/pz-theme/theme-placeholder';
19
+
20
+ export default async function Page() {
21
+ return <ThemePlaceholder slug="homepage-body" />;
22
+ }
23
+ ```
@@ -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;