@akinon/pz-theme 2.0.38-beta.0 → 2.0.38
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 +4 -4
- package/package.json +4 -4
- package/src/__tests__/chrome-takeover-gate.test.tsx +252 -0
- package/src/__tests__/registry-parity.test.ts +14 -1
- package/src/__tests__/route-slug.test.ts +116 -0
- package/src/blocks/__tests__/overlay-sweep.test.tsx +592 -0
- package/src/blocks/account-button-block.tsx +133 -0
- package/src/blocks/announcement-bar-block.tsx +407 -0
- package/src/blocks/block-renderer-registry.tsx +26 -0
- package/src/blocks/currency-selector-block.tsx +177 -0
- package/src/blocks/hamburger-menu-block.tsx +461 -0
- package/src/blocks/language-selector-block.tsx +172 -0
- package/src/blocks/mega-menu-block.tsx +712 -0
- package/src/blocks/mini-basket-block.tsx +173 -0
- package/src/blocks/nav-menu-block.tsx +309 -0
- package/src/blocks/newsletter-form-block.tsx +184 -0
- package/src/blocks/payment-badges-block.tsx +393 -0
- package/src/blocks/search-box-block.tsx +277 -0
- package/src/blocks/social-links-block.tsx +391 -0
- package/src/blocks/wishlist-button-block.tsx +115 -0
- package/src/chrome/chrome-fallback.tsx +147 -0
- package/src/chrome/chrome-slot.tsx +68 -0
- package/src/chrome/composition-state.ts +92 -0
- package/src/chrome/page-context.ts +387 -0
- package/src/chrome/placeholder-slugs.ts +86 -0
- package/src/chrome/route-slug.ts +159 -0
- package/src/index.ts +20 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { PLACEHOLDER_SLUGS } from './placeholder-slugs';
|
|
2
|
+
|
|
3
|
+
export type RouteSlugRule = [RegExp, string];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Maps a storefront path to the placeholder slug that composes its body.
|
|
7
|
+
*
|
|
8
|
+
* A brand mounts one `ChromeSlot` per *layout* rather than per page — the
|
|
9
|
+
* account layout alone covers sixteen screens — and a layout only knows the
|
|
10
|
+
* current path. Resolving here keeps that lookup out of brand code and keeps
|
|
11
|
+
* the editor's route map and the storefront agreeing on one table.
|
|
12
|
+
*
|
|
13
|
+
* Brands rename these paths freely (`/users/orders` on one, `/account/orders`
|
|
14
|
+
* on the next, `/baskets/basket` on a third), so the defaults accept the
|
|
15
|
+
* variants that actually ship, and anything else is registered with
|
|
16
|
+
* `registerRouteSlugRules` instead of forking this file.
|
|
17
|
+
*
|
|
18
|
+
* Order matters: the most specific pattern wins, so `/users/orders/123`
|
|
19
|
+
* resolves to the order detail body and not to the order list.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_RULES: RouteSlugRule[] = [
|
|
22
|
+
// Account — deepest first
|
|
23
|
+
[
|
|
24
|
+
/^\/(account|users)\/orders\/[^/]+/,
|
|
25
|
+
PLACEHOLDER_SLUGS.accountOrderDetail
|
|
26
|
+
],
|
|
27
|
+
[/^\/(account|users)\/orders\/?$/, PLACEHOLDER_SLUGS.accountOrders],
|
|
28
|
+
[/^\/account\/address/, PLACEHOLDER_SLUGS.accountAddresses],
|
|
29
|
+
[/^\/account\/profile/, PLACEHOLDER_SLUGS.accountProfile],
|
|
30
|
+
[/^\/account\/change-password/, PLACEHOLDER_SLUGS.accountChangePassword],
|
|
31
|
+
[/^\/account\/change-email/, PLACEHOLDER_SLUGS.accountChangeEmail],
|
|
32
|
+
[/^\/account\/coupons/, PLACEHOLDER_SLUGS.accountCoupons],
|
|
33
|
+
[
|
|
34
|
+
/^\/account\/(favourite-products|favorites|wishlist)/,
|
|
35
|
+
PLACEHOLDER_SLUGS.accountFavourites
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
/^\/account\/stock-(waiting-products|alerts)/,
|
|
39
|
+
PLACEHOLDER_SLUGS.accountStockAlerts
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
/^\/account\/notification-settings/,
|
|
43
|
+
PLACEHOLDER_SLUGS.accountNotifications
|
|
44
|
+
],
|
|
45
|
+
[/^\/account\/faq/, PLACEHOLDER_SLUGS.accountFaq],
|
|
46
|
+
// Any other account screen composes the shell body, which is what the
|
|
47
|
+
// account layout mounts for it.
|
|
48
|
+
[/^\/account(\/|$)/, PLACEHOLDER_SLUGS.accountShell],
|
|
49
|
+
|
|
50
|
+
// Auth
|
|
51
|
+
[/^\/(users\/)?auth\/register/, PLACEHOLDER_SLUGS.register],
|
|
52
|
+
[/^\/(users\/)?register/, PLACEHOLDER_SLUGS.register],
|
|
53
|
+
[/^\/(users\/)?auth\/forgot-password/, PLACEHOLDER_SLUGS.forgotPassword],
|
|
54
|
+
[/^\/users\/password\/reset/, PLACEHOLDER_SLUGS.forgotPassword],
|
|
55
|
+
[/^\/(users\/)?auth\/guest/, PLACEHOLDER_SLUGS.guestLogin],
|
|
56
|
+
[/^\/(users\/)?auth/, PLACEHOLDER_SLUGS.auth],
|
|
57
|
+
|
|
58
|
+
// Order flow
|
|
59
|
+
[/^\/orders\/completed/, PLACEHOLDER_SLUGS.orderSuccess],
|
|
60
|
+
// Checkout is code-owned; the designer composes the zone above the flow
|
|
61
|
+
// (the zone below shares the page and is added by the editor's scope).
|
|
62
|
+
[/^\/orders\/checkout/, PLACEHOLDER_SLUGS.checkoutTop],
|
|
63
|
+
[/^\/anonymous-(orders|tracking)/, PLACEHOLDER_SLUGS.orderTracking],
|
|
64
|
+
|
|
65
|
+
// Commerce
|
|
66
|
+
[/^\/(baskets\/basket|basket|sepet)/, PLACEHOLDER_SLUGS.basket],
|
|
67
|
+
[/^\/(group-)?product\//, PLACEHOLDER_SLUGS.productDetail],
|
|
68
|
+
[/^\/category\//, PLACEHOLDER_SLUGS.productListing],
|
|
69
|
+
[/^\/special-page\//, PLACEHOLDER_SLUGS.productListing],
|
|
70
|
+
[/^\/list\/?$/, PLACEHOLDER_SLUGS.productListing],
|
|
71
|
+
|
|
72
|
+
// Content
|
|
73
|
+
[/^\/(address\/)?stores/, PLACEHOLDER_SLUGS.stores],
|
|
74
|
+
[/^\/faq/, PLACEHOLDER_SLUGS.faq],
|
|
75
|
+
[/^\/sitemap/, PLACEHOLDER_SLUGS.sitemap],
|
|
76
|
+
[/^\/(markalar|brands)/, PLACEHOLDER_SLUGS.brands],
|
|
77
|
+
[/^\/flat-page\//, PLACEHOLDER_SLUGS.flatPage],
|
|
78
|
+
[/^\/?$/, PLACEHOLDER_SLUGS.homepage]
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Slugs whose *page* mounts the slot itself, because the blocks there need
|
|
83
|
+
* commerce data the page already fetched (product, listing, basket, account,
|
|
84
|
+
* order recap). Everything else can be mounted once in the root layout with
|
|
85
|
+
* `children` as the fallback — which is also the only way to compose a page
|
|
86
|
+
* that is a client component, since a slot is an async server component.
|
|
87
|
+
*/
|
|
88
|
+
const PAGE_MOUNTED_SLUGS = new Set<string>([
|
|
89
|
+
PLACEHOLDER_SLUGS.productDetail,
|
|
90
|
+
PLACEHOLDER_SLUGS.productListing,
|
|
91
|
+
PLACEHOLDER_SLUGS.basket,
|
|
92
|
+
PLACEHOLDER_SLUGS.orderSuccess,
|
|
93
|
+
PLACEHOLDER_SLUGS.accountShell,
|
|
94
|
+
PLACEHOLDER_SLUGS.accountMenu,
|
|
95
|
+
PLACEHOLDER_SLUGS.accountOrders,
|
|
96
|
+
PLACEHOLDER_SLUGS.accountOrderDetail,
|
|
97
|
+
PLACEHOLDER_SLUGS.accountAddresses,
|
|
98
|
+
PLACEHOLDER_SLUGS.accountProfile,
|
|
99
|
+
PLACEHOLDER_SLUGS.accountChangePassword,
|
|
100
|
+
PLACEHOLDER_SLUGS.accountChangeEmail,
|
|
101
|
+
PLACEHOLDER_SLUGS.accountCoupons,
|
|
102
|
+
PLACEHOLDER_SLUGS.accountFavourites,
|
|
103
|
+
PLACEHOLDER_SLUGS.accountStockAlerts,
|
|
104
|
+
PLACEHOLDER_SLUGS.accountNotifications,
|
|
105
|
+
PLACEHOLDER_SLUGS.accountFaq,
|
|
106
|
+
PLACEHOLDER_SLUGS.flatPage
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
/** True when a deeper page/layout already mounts this slug's slot. */
|
|
110
|
+
export const isPageMountedSlug = (slug: string | null): boolean =>
|
|
111
|
+
slug !== null && PAGE_MOUNTED_SLUGS.has(slug);
|
|
112
|
+
|
|
113
|
+
let extraRules: RouteSlugRule[] = [];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Adds brand-specific paths. Registered rules are matched before the
|
|
117
|
+
* defaults, so a brand can redirect any path to any placeholder.
|
|
118
|
+
*/
|
|
119
|
+
export const registerRouteSlugRules = (rules: RouteSlugRule[]) => {
|
|
120
|
+
extraRules = [...rules, ...extraRules];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** Test helper: drops everything `registerRouteSlugRules` added. */
|
|
124
|
+
export const resetRouteSlugRules = () => {
|
|
125
|
+
extraRules = [];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** Strips query/hash and any trailing slash, keeping the leading one. */
|
|
129
|
+
export const normalizePath = (pathname: string): string => {
|
|
130
|
+
const withoutQuery = (pathname || '/').split('?')[0].split('#')[0];
|
|
131
|
+
const prefixed = withoutQuery.startsWith('/')
|
|
132
|
+
? withoutQuery
|
|
133
|
+
: `/${withoutQuery}`;
|
|
134
|
+
return prefixed.length > 1 ? prefixed.replace(/\/+$/, '') : '/';
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns the body placeholder slug for a path, or `null` when the path has
|
|
139
|
+
* no dedicated placeholder (the brand's own page then renders untouched).
|
|
140
|
+
*/
|
|
141
|
+
export const resolveBodySlug = (pathname: string): string | null => {
|
|
142
|
+
const path = normalizePath(pathname);
|
|
143
|
+
const match = [...extraRules, ...DEFAULT_RULES].find(([pattern]) =>
|
|
144
|
+
pattern.test(path)
|
|
145
|
+
);
|
|
146
|
+
return match ? match[1] : null;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Same as `resolveBodySlug` but accepts the full URL a route segment carries
|
|
151
|
+
* (`params.url` on brands built with commerce/locale/currency segments).
|
|
152
|
+
*/
|
|
153
|
+
export const resolveBodySlugFromUrl = (url: string): string | null => {
|
|
154
|
+
try {
|
|
155
|
+
return resolveBodySlug(new URL(decodeURIComponent(url)).pathname);
|
|
156
|
+
} catch {
|
|
157
|
+
return resolveBodySlug(url);
|
|
158
|
+
}
|
|
159
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -19,3 +19,23 @@ export { WithDesignerFeatures } from './components/with-designer-features';
|
|
|
19
19
|
|
|
20
20
|
export type { Section } from './theme-section';
|
|
21
21
|
export type { Block } from './theme-block';
|
|
22
|
+
|
|
23
|
+
export { default as ChromeSlot } from './chrome/chrome-slot';
|
|
24
|
+
export { ChromeFallback } from './chrome/chrome-fallback';
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
PLACEHOLDER_SLUGS,
|
|
28
|
+
customPageSlug,
|
|
29
|
+
customPageSlugFromPath
|
|
30
|
+
} from './chrome/placeholder-slugs';
|
|
31
|
+
export type { PlaceholderSlug } from './chrome/placeholder-slugs';
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
resolveBodySlug,
|
|
35
|
+
resolveBodySlugFromUrl,
|
|
36
|
+
registerRouteSlugRules,
|
|
37
|
+
resetRouteSlugRules,
|
|
38
|
+
isPageMountedSlug,
|
|
39
|
+
normalizePath
|
|
40
|
+
} from './chrome/route-slug';
|
|
41
|
+
export type { RouteSlugRule } from './chrome/route-slug';
|