@akinon/projectzero 2.0.0-beta.20 → 2.0.0-beta.22
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 +14 -0
- package/app-template/CHANGELOG.md +170 -0
- package/app-template/next.config.mjs +0 -1
- package/app-template/package.json +31 -30
- package/app-template/src/app/[pz]/[...prettyurl]/page.tsx +2 -2
- package/app-template/src/app/[pz]/account/layout.tsx +2 -1
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/blog/[slug]/page.tsx +4 -2
- package/app-template/src/app/[pz]/category/[pk]/page.tsx +11 -1
- package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +2 -2
- package/app-template/src/app/[pz]/layout.tsx +3 -1
- package/app-template/src/app/[pz]/list/page.tsx +11 -1
- package/app-template/src/app/[pz]/page.tsx +13 -35
- package/app-template/src/app/[pz]/pages/[slug]/page.tsx +19 -0
- package/app-template/src/app/[pz]/product/[pk]/page.tsx +2 -2
- package/app-template/src/app/api/barcode-search/route.ts +1 -1
- package/app-template/src/app/api/cache/route.ts +1 -1
- package/app-template/src/app/api/image-proxy/route.ts +1 -1
- package/app-template/src/app/api/logout/route.ts +1 -1
- package/app-template/src/app/api/product-categories/route.ts +1 -1
- package/app-template/src/app/api/similar-product-list/route.ts +1 -1
- package/app-template/src/app/api/similar-products/route.ts +1 -1
- package/app-template/src/app/api/virtual-try-on/route.ts +1 -1
- package/app-template/src/app/api/web-vitals/route.ts +1 -1
- package/app-template/src/components/quantity-selector.tsx +16 -4
- package/app-template/src/components/widget-content.tsx +3 -3
- package/app-template/src/routes/index.ts +6 -6
- package/app-template/src/utils/__tests__/theme-page-context.test.ts +145 -0
- package/app-template/src/utils/theme-page-context.ts +309 -0
- package/app-template/src/views/basket/basket-item.tsx +107 -691
- package/app-template/src/views/basket/index.ts +0 -2
- package/app-template/src/views/basket/summary.tsx +75 -496
- package/app-template/src/views/breadcrumb.tsx +38 -13
- package/app-template/src/views/category/category-header.tsx +66 -289
- package/app-template/src/views/category/category-info.tsx +24 -173
- package/app-template/src/views/category/filters/index.tsx +48 -208
- package/app-template/src/views/category/layout.tsx +5 -7
- package/app-template/src/views/checkout/index.tsx +0 -5
- package/app-template/src/views/checkout/steps/payment/index.tsx +2 -5
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -72
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +40 -171
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +12 -74
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +45 -128
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +27 -232
- package/app-template/src/views/checkout/summary.tsx +29 -303
- package/app-template/src/views/footer.tsx +13 -415
- package/app-template/src/views/guest-login/index.tsx +1 -1
- package/app-template/src/views/header/action-menu.tsx +45 -277
- package/app-template/src/views/header/band.tsx +21 -6
- package/app-template/src/views/header/index.tsx +47 -109
- package/app-template/src/views/header/mini-basket.tsx +45 -820
- package/app-template/src/views/header/navbar.tsx +111 -178
- package/app-template/src/views/header/search/index.tsx +32 -71
- package/app-template/src/views/header/search/results.tsx +65 -127
- package/app-template/src/views/product/accordion-wrapper.tsx +43 -135
- package/app-template/src/views/product/index.ts +1 -1
- package/app-template/src/views/product/layout.tsx +7 -2
- package/app-template/src/views/product/misc-buttons.tsx +25 -339
- package/app-template/src/views/product/product-actions.tsx +8 -137
- package/app-template/src/views/product/product-info.tsx +31 -69
- package/app-template/src/views/product/product-share.tsx +8 -11
- package/app-template/src/views/product/slider.tsx +79 -117
- package/app-template/src/views/product-item/index.tsx +46 -119
- package/app-template/src/widgets/footer-social.tsx +16 -47
- package/app-template/src/widgets/footer-subscription/index.tsx +17 -183
- package/codemods/migrate-auth-v5/index.js +339 -0
- package/codemods/migrate-auth-v5/transform.js +86 -0
- package/dist/commands/plugins.js +23 -2
- package/package.json +1 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +0 -15
- package/app-template/src/views/basket/basket-summary-context.tsx +0 -560
- package/app-template/src/views/basket/designer-context.tsx +0 -617
- package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +0 -190
- package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +0 -286
- package/app-template/src/views/breadcrumb/constants.ts +0 -15
- package/app-template/src/views/breadcrumb/index.tsx +0 -127
- package/app-template/src/views/category/native-widget-context.tsx +0 -257
- package/app-template/src/views/category/product-list-registrar.tsx +0 -665
- package/app-template/src/views/checkout/checkout-address-registrar.tsx +0 -254
- package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +0 -183
- package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +0 -259
- package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +0 -253
- package/app-template/src/views/checkout/checkout-summary-registrar.tsx +0 -183
- package/app-template/src/views/checkout/constants.ts +0 -5
- package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +0 -15
- package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +0 -18
- package/app-template/src/views/footer/footer-app-banner-context.tsx +0 -326
- package/app-template/src/views/footer/footer-bottom-context.tsx +0 -215
- package/app-template/src/views/footer/footer-bottom-wrapper.tsx +0 -74
- package/app-template/src/views/footer/footer-layout-constants.ts +0 -35
- package/app-template/src/views/footer/footer-layout-registrar.tsx +0 -342
- package/app-template/src/views/footer/footer-layout-switcher.tsx +0 -110
- package/app-template/src/views/footer/footer-menu-context.tsx +0 -211
- package/app-template/src/views/footer/footer-native-widgets.tsx +0 -60
- package/app-template/src/views/footer/footer-social-context.tsx +0 -254
- package/app-template/src/views/footer/footer-subscription-context.tsx +0 -210
- package/app-template/src/views/footer/footer-utils.ts +0 -43
- package/app-template/src/views/footer/footer-value-props-context.tsx +0 -326
- package/app-template/src/views/footer/logo-settings.ts +0 -183
- package/app-template/src/views/footer/native-widget-config.ts +0 -262
- package/app-template/src/views/footer/subscription-settings.ts +0 -122
- package/app-template/src/views/footer/use-footer-logo.ts +0 -162
- package/app-template/src/views/header/designer-context.tsx +0 -261
- package/app-template/src/views/header/header-announcement-registrar.tsx +0 -267
- package/app-template/src/views/header/header-client-wrapper.tsx +0 -496
- package/app-template/src/views/header/header-content.tsx +0 -1026
- package/app-template/src/views/header/header-currency-registrar.tsx +0 -348
- package/app-template/src/views/header/header-icons-context.tsx +0 -262
- package/app-template/src/views/header/header-language-registrar.tsx +0 -348
- package/app-template/src/views/header/header-layout-context.tsx +0 -143
- package/app-template/src/views/header/header-layout-registrar.tsx +0 -658
- package/app-template/src/views/header/header-logo-context.tsx +0 -228
- package/app-template/src/views/header/header-logo.tsx +0 -118
- package/app-template/src/views/header/header-mini-basket-context.tsx +0 -524
- package/app-template/src/views/header/header-search-registrar.tsx +0 -511
- package/app-template/src/views/header/header-text-slider-registrar.tsx +0 -382
- package/app-template/src/views/header/inline-search.tsx +0 -262
- package/app-template/src/views/header/navbar-menu-context.tsx +0 -219
- package/app-template/src/views/header/search/search-input.tsx +0 -61
- package/app-template/src/views/header/server-settings-parser.ts +0 -1105
- package/app-template/src/views/header/use-header-icons.ts +0 -241
- package/app-template/src/views/header/use-header-logo.ts +0 -213
- package/app-template/src/views/header/use-navbar-menu.ts +0 -179
- package/app-template/src/views/product/accordion-section.tsx +0 -61
- package/app-template/src/views/product/custom-button-group.tsx +0 -69
- package/app-template/src/views/product/favorites-button-section.tsx +0 -69
- package/app-template/src/views/product/find-in-store-section.tsx +0 -60
- package/app-template/src/views/product/product-info-section.tsx +0 -140
- package/app-template/src/views/product/quantity-section.tsx +0 -73
- package/app-template/src/views/product/sale-tag.tsx +0 -10
- package/app-template/src/views/product/share-section.tsx +0 -357
- package/app-template/src/views/product/variants-section.tsx +0 -126
- package/app-template/src/views/product-detail/constants.ts +0 -272
- package/app-template/src/views/product-detail/index.ts +0 -10
- package/app-template/src/views/product-detail/product-detail-registrar.tsx +0 -616
- package/app-template/src/widgets/footer-app-banner.tsx +0 -444
- package/app-template/src/widgets/footer-bottom.tsx +0 -127
- package/app-template/src/widgets/footer-menu-compact.tsx +0 -238
- package/app-template/src/widgets/footer-menu-two.tsx +0 -298
- package/app-template/src/widgets/footer-social-client.tsx +0 -251
- package/app-template/src/widgets/footer-value-props.tsx +0 -201
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Header Text Slider Section Registrar
|
|
5
|
-
*
|
|
6
|
-
* This component registers the "Text Slider" section for the header placeholder.
|
|
7
|
-
* The text slider section is a native widget that can be added via "Add Section" button.
|
|
8
|
-
* It displays scrolling/sliding text messages in the utility bar.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
createContext,
|
|
13
|
-
useContext,
|
|
14
|
-
useEffect,
|
|
15
|
-
useRef,
|
|
16
|
-
useState,
|
|
17
|
-
PropsWithChildren
|
|
18
|
-
} from 'react';
|
|
19
|
-
|
|
20
|
-
// Constants
|
|
21
|
-
export const HEADER_TEXT_SLIDER_PLACEHOLDER_ID = 'header';
|
|
22
|
-
export const HEADER_TEXT_SLIDER_SECTION_ID = 'header-text-slider';
|
|
23
|
-
export const HEADER_TEXT_SLIDER_WIDGET_SLUG = 'header-text-slider-settings-2';
|
|
24
|
-
|
|
25
|
-
// Global flag to track if registration has been done
|
|
26
|
-
declare global {
|
|
27
|
-
interface Window {
|
|
28
|
-
__headerTextSliderRegistered?: boolean;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Check if running inside designer iframe
|
|
34
|
-
*/
|
|
35
|
-
function isInDesignerMode(): boolean {
|
|
36
|
-
if (typeof window === 'undefined') return false;
|
|
37
|
-
return window.self !== window.top;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface ThemeSection {
|
|
41
|
-
id: string;
|
|
42
|
-
visible?: boolean;
|
|
43
|
-
properties?: Record<string, unknown>;
|
|
44
|
-
styles?: Record<string, unknown>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface ThemePlaceholder {
|
|
48
|
-
slug: string;
|
|
49
|
-
sections: ThemeSection[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface TextSliderItem {
|
|
53
|
-
text: string;
|
|
54
|
-
link?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface HeaderTextSliderProperties {
|
|
58
|
-
visible?: boolean;
|
|
59
|
-
items?: TextSliderItem[];
|
|
60
|
-
autoPlay?: boolean;
|
|
61
|
-
autoPlayInterval?: number;
|
|
62
|
-
showArrows?: boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface HeaderTextSliderContextValue {
|
|
66
|
-
isDesigner: boolean;
|
|
67
|
-
isTextSliderSectionSelected: boolean;
|
|
68
|
-
isSectionVisible: boolean;
|
|
69
|
-
properties: HeaderTextSliderProperties;
|
|
70
|
-
sectionStyles: Record<string, unknown>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const HeaderTextSliderContext = createContext<HeaderTextSliderContextValue>({
|
|
74
|
-
isDesigner: false,
|
|
75
|
-
isTextSliderSectionSelected: false,
|
|
76
|
-
isSectionVisible: false,
|
|
77
|
-
properties: {
|
|
78
|
-
items: [{ text: 'Welcome to our store!' }],
|
|
79
|
-
autoPlay: true,
|
|
80
|
-
autoPlayInterval: 3000,
|
|
81
|
-
showArrows: true
|
|
82
|
-
},
|
|
83
|
-
sectionStyles: {}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
export const useHeaderTextSlider = () => useContext(HeaderTextSliderContext);
|
|
87
|
-
|
|
88
|
-
interface HeaderTextSliderRegistrarProps extends PropsWithChildren {
|
|
89
|
-
/**
|
|
90
|
-
* Initial settings from server-side parsing (to avoid flash)
|
|
91
|
-
*/
|
|
92
|
-
initialSettings?: {
|
|
93
|
-
sectionStyles?: Record<string, unknown>;
|
|
94
|
-
properties?: HeaderTextSliderProperties;
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* HeaderTextSliderRegistrar
|
|
100
|
-
*
|
|
101
|
-
* Registers the Text Slider native section with Theme Editor.
|
|
102
|
-
* The section can be added via "Add Section" button in the header placeholder.
|
|
103
|
-
*/
|
|
104
|
-
export default function HeaderTextSliderRegistrar({
|
|
105
|
-
children,
|
|
106
|
-
initialSettings = {}
|
|
107
|
-
}: HeaderTextSliderRegistrarProps) {
|
|
108
|
-
const isDesignerRef = useRef(false);
|
|
109
|
-
const [sectionProperties, setSectionProperties] =
|
|
110
|
-
useState<HeaderTextSliderProperties>(
|
|
111
|
-
initialSettings.properties || {
|
|
112
|
-
items: [{ text: 'Welcome to our store!' }],
|
|
113
|
-
autoPlay: true,
|
|
114
|
-
autoPlayInterval: 3000,
|
|
115
|
-
showArrows: true
|
|
116
|
-
}
|
|
117
|
-
);
|
|
118
|
-
const [sectionStyles, setSectionStyles] = useState<Record<string, unknown>>(
|
|
119
|
-
initialSettings.sectionStyles || {}
|
|
120
|
-
);
|
|
121
|
-
const [isTextSliderSelected, setIsTextSliderSelected] = useState(false);
|
|
122
|
-
const [isSectionVisible, setIsSectionVisible] = useState(
|
|
123
|
-
initialSettings.properties?.visible ?? false
|
|
124
|
-
);
|
|
125
|
-
const hasRegisteredNativeWidget = useRef(false);
|
|
126
|
-
|
|
127
|
-
const hasInitialSettings = !!(
|
|
128
|
-
(initialSettings.sectionStyles &&
|
|
129
|
-
Object.keys(initialSettings.sectionStyles).length > 0) ||
|
|
130
|
-
(initialSettings.properties &&
|
|
131
|
-
Object.keys(initialSettings.properties).length > 0)
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
useEffect(() => {
|
|
135
|
-
isDesignerRef.current = isInDesignerMode();
|
|
136
|
-
}, []);
|
|
137
|
-
|
|
138
|
-
const isDesigner = isDesignerRef.current;
|
|
139
|
-
|
|
140
|
-
// Register native widget - only once on mount
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
const isInIframe =
|
|
143
|
-
typeof window !== 'undefined' && window.self !== window.top;
|
|
144
|
-
if (!isInIframe || !window.parent) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Skip if already registered
|
|
149
|
-
if (
|
|
150
|
-
typeof window !== 'undefined' &&
|
|
151
|
-
window.__headerTextSliderRegistered &&
|
|
152
|
-
hasRegisteredNativeWidget.current
|
|
153
|
-
) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Send native widget registration to Theme Editor
|
|
158
|
-
// Use initial settings for registration, not dynamic state
|
|
159
|
-
const nativeWidgetConfig = {
|
|
160
|
-
placeholderId: HEADER_TEXT_SLIDER_PLACEHOLDER_ID,
|
|
161
|
-
autoAdd: false,
|
|
162
|
-
section: {
|
|
163
|
-
id: HEADER_TEXT_SLIDER_SECTION_ID,
|
|
164
|
-
type: 'native',
|
|
165
|
-
label: 'Text Slider',
|
|
166
|
-
blocks: [],
|
|
167
|
-
properties: initialSettings.properties || {
|
|
168
|
-
items: [{ text: 'Welcome to our store!' }],
|
|
169
|
-
autoPlay: true,
|
|
170
|
-
autoPlayInterval: 3000,
|
|
171
|
-
showArrows: true
|
|
172
|
-
},
|
|
173
|
-
styles: initialSettings.sectionStyles || {}
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
window.parent.postMessage(
|
|
178
|
-
{
|
|
179
|
-
type: 'REGISTER_NATIVE_WIDGETS',
|
|
180
|
-
data: { widgets: [nativeWidgetConfig] }
|
|
181
|
-
},
|
|
182
|
-
'*'
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
// Mark as registered
|
|
186
|
-
window.__headerTextSliderRegistered = true;
|
|
187
|
-
hasRegisteredNativeWidget.current = true;
|
|
188
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
189
|
-
}, []);
|
|
190
|
-
|
|
191
|
-
// Apply highlight style to text slider container when section is selected
|
|
192
|
-
useEffect(() => {
|
|
193
|
-
if (typeof window === 'undefined') return;
|
|
194
|
-
|
|
195
|
-
const textSliderContainer = document.querySelector(
|
|
196
|
-
'[data-section-id="header-text-slider"]'
|
|
197
|
-
);
|
|
198
|
-
if (!textSliderContainer) return;
|
|
199
|
-
|
|
200
|
-
if (isTextSliderSelected) {
|
|
201
|
-
(textSliderContainer as HTMLElement).style.outline = '2px solid #3b82f6';
|
|
202
|
-
(textSliderContainer as HTMLElement).style.outlineOffset = '-2px';
|
|
203
|
-
} else {
|
|
204
|
-
(textSliderContainer as HTMLElement).style.outline = '';
|
|
205
|
-
(textSliderContainer as HTMLElement).style.outlineOffset = '';
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return () => {
|
|
209
|
-
(textSliderContainer as HTMLElement).style.outline = '';
|
|
210
|
-
(textSliderContainer as HTMLElement).style.outlineOffset = '';
|
|
211
|
-
};
|
|
212
|
-
}, [isTextSliderSelected]);
|
|
213
|
-
|
|
214
|
-
const hasReceivedInitialTheme = useRef(false);
|
|
215
|
-
|
|
216
|
-
// Listen for theme updates and selection changes from Theme Editor
|
|
217
|
-
useEffect(() => {
|
|
218
|
-
if (typeof window === 'undefined') return;
|
|
219
|
-
|
|
220
|
-
const handleMessage = (event: MessageEvent) => {
|
|
221
|
-
const { type, data } = event.data || {};
|
|
222
|
-
|
|
223
|
-
// Handle theme updates
|
|
224
|
-
if (
|
|
225
|
-
(type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
|
|
226
|
-
data?.theme?.placeholders
|
|
227
|
-
) {
|
|
228
|
-
const placeholder = data.theme.placeholders?.find(
|
|
229
|
-
(p: ThemePlaceholder) => p.slug === HEADER_TEXT_SLIDER_PLACEHOLDER_ID
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
const textSliderSection = placeholder?.sections?.find(
|
|
233
|
-
(s: ThemeSection) => s.id === HEADER_TEXT_SLIDER_SECTION_ID
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
if (textSliderSection) {
|
|
237
|
-
const sectionVisible = textSliderSection.properties?.visible;
|
|
238
|
-
if (sectionVisible !== undefined) {
|
|
239
|
-
const isVisible =
|
|
240
|
-
typeof sectionVisible === 'boolean'
|
|
241
|
-
? sectionVisible
|
|
242
|
-
: sectionVisible?.desktop === true ||
|
|
243
|
-
sectionVisible?.mobile === true;
|
|
244
|
-
setIsSectionVisible(isVisible);
|
|
245
|
-
} else if (typeof textSliderSection.visible === 'boolean') {
|
|
246
|
-
setIsSectionVisible(textSliderSection.visible);
|
|
247
|
-
} else {
|
|
248
|
-
setIsSectionVisible(true);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Skip property/style updates if we have initial settings and haven't
|
|
252
|
-
// received the first theme yet (to avoid format mismatch issues)
|
|
253
|
-
// After first theme load, we use UPDATE_SECTION_PROPERTY for changes
|
|
254
|
-
if (hasInitialSettings && !hasReceivedInitialTheme.current) {
|
|
255
|
-
hasReceivedInitialTheme.current = true;
|
|
256
|
-
// Don't apply properties/styles from UPDATE_THEME when we have server-side initial settings
|
|
257
|
-
// Individual property changes will come via UPDATE_SECTION_PROPERTY
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Only apply updates if we don't have initial settings (fresh load)
|
|
262
|
-
if (!hasInitialSettings) {
|
|
263
|
-
if (textSliderSection.properties) {
|
|
264
|
-
setSectionProperties((prev) => {
|
|
265
|
-
const newProps =
|
|
266
|
-
textSliderSection.properties as HeaderTextSliderProperties;
|
|
267
|
-
if (JSON.stringify(prev) === JSON.stringify(newProps)) {
|
|
268
|
-
return prev;
|
|
269
|
-
}
|
|
270
|
-
return newProps;
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (textSliderSection.styles) {
|
|
275
|
-
setSectionStyles((prev) => {
|
|
276
|
-
if (
|
|
277
|
-
JSON.stringify(prev) ===
|
|
278
|
-
JSON.stringify(textSliderSection.styles)
|
|
279
|
-
) {
|
|
280
|
-
return prev;
|
|
281
|
-
}
|
|
282
|
-
return textSliderSection.styles;
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
} else if (placeholder) {
|
|
287
|
-
setIsSectionVisible(false);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Handle property updates
|
|
292
|
-
if (type === 'UPDATE_SECTION_PROPERTY' || type === 'UPDATE_PROPERTY') {
|
|
293
|
-
const { sectionId, key, value, properties } = data || {};
|
|
294
|
-
|
|
295
|
-
if (sectionId === HEADER_TEXT_SLIDER_SECTION_ID) {
|
|
296
|
-
if (key && value !== undefined) {
|
|
297
|
-
setSectionProperties((prev) => ({
|
|
298
|
-
...prev,
|
|
299
|
-
[key]: value
|
|
300
|
-
}));
|
|
301
|
-
}
|
|
302
|
-
if (properties) {
|
|
303
|
-
setSectionProperties(properties as HeaderTextSliderProperties);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Handle style updates for the section
|
|
309
|
-
if (type === 'UPDATE_SECTION_STYLE' || type === 'UPDATE_STYLE') {
|
|
310
|
-
const { sectionId, key, value, styles } = data || {};
|
|
311
|
-
|
|
312
|
-
if (sectionId === HEADER_TEXT_SLIDER_SECTION_ID) {
|
|
313
|
-
if (key && value !== undefined) {
|
|
314
|
-
setSectionStyles((prev) => ({
|
|
315
|
-
...prev,
|
|
316
|
-
[key]: value
|
|
317
|
-
}));
|
|
318
|
-
}
|
|
319
|
-
if (styles) {
|
|
320
|
-
setSectionStyles(styles);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Handle selection changes
|
|
326
|
-
if (type === 'SELECT_SECTION') {
|
|
327
|
-
const { placeholderId, sectionId } = data || {};
|
|
328
|
-
|
|
329
|
-
const isSelected =
|
|
330
|
-
placeholderId === HEADER_TEXT_SLIDER_PLACEHOLDER_ID &&
|
|
331
|
-
sectionId === HEADER_TEXT_SLIDER_SECTION_ID;
|
|
332
|
-
|
|
333
|
-
setIsTextSliderSelected(isSelected);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Handle block selection
|
|
337
|
-
if (type === 'SELECT_BLOCK') {
|
|
338
|
-
const { sectionId } = data || {};
|
|
339
|
-
if (sectionId === HEADER_TEXT_SLIDER_SECTION_ID) {
|
|
340
|
-
setIsTextSliderSelected(true);
|
|
341
|
-
} else {
|
|
342
|
-
setIsTextSliderSelected(false);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Handle deselection
|
|
347
|
-
if (type === 'DESELECT' || type === 'CLEAR_SELECTION') {
|
|
348
|
-
setIsTextSliderSelected(false);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Handle visibility toggle
|
|
352
|
-
if (type === 'TOGGLE_SECTION_VISIBILITY') {
|
|
353
|
-
const { sectionId } = data || {};
|
|
354
|
-
if (sectionId === HEADER_TEXT_SLIDER_SECTION_ID) {
|
|
355
|
-
setIsSectionVisible((prev) => !prev);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
window.addEventListener('message', handleMessage);
|
|
361
|
-
return () => window.removeEventListener('message', handleMessage);
|
|
362
|
-
}, [hasInitialSettings, isDesigner]);
|
|
363
|
-
|
|
364
|
-
// If no children, just return null (registration-only mode)
|
|
365
|
-
if (!children) {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
<HeaderTextSliderContext.Provider
|
|
371
|
-
value={{
|
|
372
|
-
isDesigner,
|
|
373
|
-
isTextSliderSectionSelected: isTextSliderSelected,
|
|
374
|
-
isSectionVisible,
|
|
375
|
-
properties: sectionProperties,
|
|
376
|
-
sectionStyles
|
|
377
|
-
}}
|
|
378
|
-
>
|
|
379
|
-
{children}
|
|
380
|
-
</HeaderTextSliderContext.Provider>
|
|
381
|
-
);
|
|
382
|
-
}
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Inline Search Component
|
|
5
|
-
*
|
|
6
|
-
* A simplified search input that appears directly in the header
|
|
7
|
-
* for the "two-row" layout. Unlike the modal search, this is always
|
|
8
|
-
* visible and expands inline results.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { useState, useRef, useMemo, useCallback } from 'react';
|
|
12
|
-
import { useRouter } from '@akinon/next/hooks';
|
|
13
|
-
import { Icon } from '@theme/components';
|
|
14
|
-
import { ROUTES } from '@theme/routes';
|
|
15
|
-
import clsx from 'clsx';
|
|
16
|
-
import { useDesignerFeatures } from '@akinon/next/components/theme-editor/hooks/use-designer-features';
|
|
17
|
-
|
|
18
|
-
interface InlineSearchProps {
|
|
19
|
-
placeholder?: string;
|
|
20
|
-
className?: string;
|
|
21
|
-
style?: React.CSSProperties;
|
|
22
|
-
iconStyles?: Record<string, unknown>;
|
|
23
|
-
/** Block ID for theme editor selection */
|
|
24
|
-
blockId?: string;
|
|
25
|
-
/** Whether in designer mode (disables interactions) */
|
|
26
|
-
isDesigner?: boolean;
|
|
27
|
-
/** Placeholder ID for theme editor */
|
|
28
|
-
placeholderId?: string;
|
|
29
|
-
/** Section ID for theme editor */
|
|
30
|
-
sectionId?: string;
|
|
31
|
-
/** Whether this block is currently selected */
|
|
32
|
-
isSelected?: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Extract responsive style value for the current breakpoint
|
|
37
|
-
* Styles from theme editor come as { desktop: "value", tablet: "value", ... }
|
|
38
|
-
*/
|
|
39
|
-
function extractStyleValue(
|
|
40
|
-
value: unknown,
|
|
41
|
-
breakpoint = 'desktop'
|
|
42
|
-
): string | number | undefined {
|
|
43
|
-
if (value === null || value === undefined) return undefined;
|
|
44
|
-
if (typeof value === 'string' || typeof value === 'number') return value;
|
|
45
|
-
if (typeof value === 'object' && value !== null) {
|
|
46
|
-
const obj = value as Record<string, string | number>;
|
|
47
|
-
// Try current breakpoint, fallback to desktop, then first available value
|
|
48
|
-
return obj[breakpoint] ?? obj['desktop'] ?? Object.values(obj)[0];
|
|
49
|
-
}
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Convert theme editor style object to CSS properties
|
|
55
|
-
* Supports both kebab-case (from theme editor) and camelCase (from server parser)
|
|
56
|
-
*/
|
|
57
|
-
function convertStyles(
|
|
58
|
-
styles: Record<string, unknown> | undefined,
|
|
59
|
-
breakpoint = 'desktop'
|
|
60
|
-
): React.CSSProperties {
|
|
61
|
-
if (!styles) return {};
|
|
62
|
-
|
|
63
|
-
const result: React.CSSProperties = {};
|
|
64
|
-
|
|
65
|
-
// Map theme editor keys (kebab-case) to CSS property names (camelCase)
|
|
66
|
-
const styleMap: Record<string, keyof React.CSSProperties> = {
|
|
67
|
-
// Kebab-case keys (from theme editor postMessage)
|
|
68
|
-
width: 'width',
|
|
69
|
-
'max-width': 'maxWidth',
|
|
70
|
-
height: 'height',
|
|
71
|
-
'background-color': 'backgroundColor',
|
|
72
|
-
'border-color': 'borderColor',
|
|
73
|
-
'border-width': 'borderWidth',
|
|
74
|
-
'border-radius': 'borderRadius',
|
|
75
|
-
'font-size': 'fontSize',
|
|
76
|
-
'padding-left': 'paddingLeft',
|
|
77
|
-
'padding-right': 'paddingRight',
|
|
78
|
-
// CamelCase keys (from server-side parser or direct style pass)
|
|
79
|
-
maxWidth: 'maxWidth',
|
|
80
|
-
backgroundColor: 'backgroundColor',
|
|
81
|
-
borderColor: 'borderColor',
|
|
82
|
-
borderWidth: 'borderWidth',
|
|
83
|
-
borderRadius: 'borderRadius',
|
|
84
|
-
fontSize: 'fontSize',
|
|
85
|
-
paddingLeft: 'paddingLeft',
|
|
86
|
-
paddingRight: 'paddingRight'
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
Object.entries(styles).forEach(([key, value]) => {
|
|
90
|
-
const cssKey = styleMap[key];
|
|
91
|
-
if (cssKey) {
|
|
92
|
-
const extracted = extractStyleValue(value, breakpoint);
|
|
93
|
-
if (extracted !== undefined) {
|
|
94
|
-
(result as Record<string, unknown>)[cssKey] = extracted;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
return result;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export default function InlineSearch({
|
|
103
|
-
placeholder = 'Search...',
|
|
104
|
-
className,
|
|
105
|
-
style,
|
|
106
|
-
iconStyles,
|
|
107
|
-
blockId,
|
|
108
|
-
isDesigner = false,
|
|
109
|
-
placeholderId = 'header',
|
|
110
|
-
sectionId = 'header-search',
|
|
111
|
-
isSelected = false
|
|
112
|
-
}: InlineSearchProps) {
|
|
113
|
-
const router = useRouter();
|
|
114
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
115
|
-
const [searchText, setSearchText] = useState('');
|
|
116
|
-
|
|
117
|
-
// Convert theme editor styles to CSS properties
|
|
118
|
-
const computedStyles = useMemo(
|
|
119
|
-
() => convertStyles(style as unknown as Record<string, unknown>),
|
|
120
|
-
[style]
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
// Extract font-size for the input element
|
|
124
|
-
const inputStyle = useMemo(() => {
|
|
125
|
-
const fontSize = computedStyles.fontSize;
|
|
126
|
-
return fontSize ? { fontSize } : {};
|
|
127
|
-
}, [computedStyles]);
|
|
128
|
-
|
|
129
|
-
// Extract icon color and size from block styles
|
|
130
|
-
const iconColor = useMemo(() => {
|
|
131
|
-
const color = extractStyleValue(iconStyles?.color);
|
|
132
|
-
return typeof color === 'string' ? color : undefined;
|
|
133
|
-
}, [iconStyles]);
|
|
134
|
-
|
|
135
|
-
const iconSize = useMemo(() => {
|
|
136
|
-
// iconSize comes from properties, not styles - check both
|
|
137
|
-
const size =
|
|
138
|
-
extractStyleValue(iconStyles?.iconSize) ||
|
|
139
|
-
extractStyleValue(iconStyles?.['icon-size']);
|
|
140
|
-
if (size === undefined) return 18;
|
|
141
|
-
const numSize = typeof size === 'string' ? parseInt(size, 10) : size;
|
|
142
|
-
return isNaN(numSize) ? 18 : numSize;
|
|
143
|
-
}, [iconStyles]);
|
|
144
|
-
|
|
145
|
-
// Extract custom icon SVG from block properties
|
|
146
|
-
const customIcon = useMemo(() => {
|
|
147
|
-
const icon = iconStyles?.icon;
|
|
148
|
-
if (!icon) return null;
|
|
149
|
-
// Handle responsive object format
|
|
150
|
-
const iconValue =
|
|
151
|
-
typeof icon === 'object' && icon !== null && 'desktop' in icon
|
|
152
|
-
? (icon as { desktop?: string }).desktop
|
|
153
|
-
: icon;
|
|
154
|
-
// Check if it's SVG content
|
|
155
|
-
if (typeof iconValue === 'string' && iconValue.includes('<svg')) {
|
|
156
|
-
return iconValue;
|
|
157
|
-
}
|
|
158
|
-
return null;
|
|
159
|
-
}, [iconStyles]);
|
|
160
|
-
|
|
161
|
-
// Designer features for theme editor selection
|
|
162
|
-
const { handleClick: handleDesignerClick } = useDesignerFeatures({
|
|
163
|
-
blockId: blockId || 'header-search-icon',
|
|
164
|
-
placeholderId,
|
|
165
|
-
sectionId,
|
|
166
|
-
isDesigner,
|
|
167
|
-
blockInfo: {
|
|
168
|
-
id: blockId || 'header-search-icon',
|
|
169
|
-
type: 'icon-button',
|
|
170
|
-
label: 'Search Icon'
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const handleSearch = () => {
|
|
175
|
-
if (searchText.trim() !== '') {
|
|
176
|
-
router.push(
|
|
177
|
-
`${ROUTES.LIST}/?search_text=${encodeURIComponent(searchText)}`
|
|
178
|
-
);
|
|
179
|
-
setSearchText('');
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
// Handle click - in designer mode, select the block; otherwise do nothing on container
|
|
184
|
-
const handleContainerClick = useCallback(
|
|
185
|
-
(e: React.MouseEvent) => {
|
|
186
|
-
if (isDesigner) {
|
|
187
|
-
e.preventDefault();
|
|
188
|
-
e.stopPropagation();
|
|
189
|
-
handleDesignerClick(e);
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
[isDesigner, handleDesignerClick]
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
196
|
-
if (e.key === 'Enter') {
|
|
197
|
-
handleSearch();
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<div
|
|
203
|
-
data-block-id={blockId}
|
|
204
|
-
onClick={handleContainerClick}
|
|
205
|
-
className={clsx(
|
|
206
|
-
'flex items-center border px-3 flex-shrink-0',
|
|
207
|
-
// Only apply default Tailwind classes if no inline style override
|
|
208
|
-
!computedStyles.height && 'h-10',
|
|
209
|
-
!computedStyles.borderRadius && 'rounded-md',
|
|
210
|
-
!computedStyles.backgroundColor && 'bg-white',
|
|
211
|
-
!computedStyles.borderColor && 'border-gray-300',
|
|
212
|
-
isDesigner && 'cursor-pointer',
|
|
213
|
-
isSelected && 'ring-2 ring-blue-500 ring-offset-1',
|
|
214
|
-
className
|
|
215
|
-
)}
|
|
216
|
-
style={computedStyles}
|
|
217
|
-
>
|
|
218
|
-
<input
|
|
219
|
-
ref={inputRef}
|
|
220
|
-
type="text"
|
|
221
|
-
value={searchText}
|
|
222
|
-
onChange={(e) => setSearchText(e.target.value)}
|
|
223
|
-
onKeyDown={handleKeyDown}
|
|
224
|
-
placeholder={placeholder}
|
|
225
|
-
className={clsx(
|
|
226
|
-
'flex-1 text-sm text-black-750 bg-transparent border-none outline-none',
|
|
227
|
-
'placeholder:text-gray-400',
|
|
228
|
-
isDesigner && 'pointer-events-none'
|
|
229
|
-
)}
|
|
230
|
-
style={inputStyle}
|
|
231
|
-
/>
|
|
232
|
-
<button
|
|
233
|
-
type="button"
|
|
234
|
-
onClick={handleSearch}
|
|
235
|
-
className={clsx(
|
|
236
|
-
'flex items-center justify-center ml-2 p-1 hover:bg-gray-100 rounded transition-colors',
|
|
237
|
-
isDesigner && 'pointer-events-none'
|
|
238
|
-
)}
|
|
239
|
-
aria-label="Search"
|
|
240
|
-
>
|
|
241
|
-
{customIcon ? (
|
|
242
|
-
<div
|
|
243
|
-
className="flex items-center justify-center"
|
|
244
|
-
style={{
|
|
245
|
-
width: iconSize,
|
|
246
|
-
height: iconSize,
|
|
247
|
-
color: iconColor || undefined
|
|
248
|
-
}}
|
|
249
|
-
dangerouslySetInnerHTML={{ __html: customIcon }}
|
|
250
|
-
/>
|
|
251
|
-
) : (
|
|
252
|
-
<Icon
|
|
253
|
-
name="search"
|
|
254
|
-
size={iconSize}
|
|
255
|
-
className={iconColor ? undefined : 'text-gray-600'}
|
|
256
|
-
style={iconColor ? { color: iconColor } : undefined}
|
|
257
|
-
/>
|
|
258
|
-
)}
|
|
259
|
-
</button>
|
|
260
|
-
</div>
|
|
261
|
-
);
|
|
262
|
-
}
|