@akinon/pz-theme 2.0.115-rc.0 → 2.0.116-beta.0
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 +15 -35
- package/package.json +4 -4
- package/src/blocks/__tests__/menu-query-args.test.tsx +239 -0
- package/src/blocks/__tests__/nav-menu-ssr.test.tsx +260 -0
- package/src/blocks/menu-query-args.ts +105 -0
- package/src/blocks/nav-menu-block.tsx +21 -10
- package/src/chrome/__tests__/chrome-mode.test.ts +35 -2
- package/src/chrome/__tests__/menu-seed-source.test.ts +186 -0
- package/src/chrome/__tests__/use-chrome-mode.test.tsx +162 -0
- package/src/chrome/chrome-mode.ts +33 -2
- package/src/chrome/menu-seed-source.ts +89 -0
- package/src/chrome/use-chrome-mode.ts +44 -0
- package/src/custom/README-sections.md +32 -15
- package/src/custom/__tests__/custom-entry-exports.test.tsx +50 -0
- package/src/custom/index.ts +22 -6
- package/src/index.ts +2 -0
- package/src/menu-seed-context.tsx +38 -0
- package/src/theme-placeholder-wrapper.tsx +103 -91
- package/src/theme-placeholder.tsx +5 -0
- package/src/utils/__tests__/docked-state-styles.test.ts +88 -19
- package/src/utils/part-styles.ts +36 -4
|
@@ -310,7 +310,7 @@ way a composition learns something it cannot see from where it sits.
|
|
|
310
310
|
|---|---|---|---|
|
|
311
311
|
| `scrolled` | the page has moved under the chrome | the chrome slot, `data-scroll-state` | pz-theme (`chrome/scroll-state.tsx`) |
|
|
312
312
|
| `overlay-open` | some overlay is open | `body`, `data-overlay-state` | pz-theme (`hooks/use-overlay.ts`) |
|
|
313
|
-
| `docked` | the chrome sits **above** the page, not over it | `body`, `data-chrome-mode` | **you**, via `
|
|
313
|
+
| `docked` | the chrome sits **above** the page, not over it | your header's root element and `body`, `data-chrome-mode` | **you**, via `useChromeMode` |
|
|
314
314
|
|
|
315
315
|
All three work as a root state on the section itself, `:<state>/<property>`, and
|
|
316
316
|
as a part state, `<part>/<state>/<property>`.
|
|
@@ -318,7 +318,7 @@ as a part state, `<part>/<state>/<property>`.
|
|
|
318
318
|
**The one rule: pz-theme owns the vocabulary, the brand owns the signal.** The
|
|
319
319
|
attribute names, the tokens and the selectors that read them live here. Nothing
|
|
320
320
|
in pz-theme can know which of your routes carry a hero, so nothing in pz-theme
|
|
321
|
-
stamps `docked`. You do — and through `
|
|
321
|
+
stamps `docked`. You do — and through `useChromeMode`, never by hand-writing the
|
|
322
322
|
attribute, because a typo there raises no error and simply produces a header
|
|
323
323
|
that never changes appearance.
|
|
324
324
|
|
|
@@ -335,18 +335,13 @@ and lose the second appearance.
|
|
|
335
335
|
'use client';
|
|
336
336
|
|
|
337
337
|
import { usePathname } from 'next/navigation';
|
|
338
|
-
import {
|
|
339
|
-
import {
|
|
340
|
-
CHROME_MODE_DOCKED,
|
|
341
|
-
setChromeMode
|
|
342
|
-
} from '@akinon/pz-theme/src/custom';
|
|
338
|
+
import { CHROME_MODE_DOCKED, useChromeMode } from '@akinon/pz-theme/src/custom';
|
|
343
339
|
|
|
344
340
|
function StoreHeader() {
|
|
345
341
|
const isHome = usePathname() === '/';
|
|
342
|
+
const attrs = useChromeMode(isHome ? null : CHROME_MODE_DOCKED);
|
|
346
343
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// ...the header markup
|
|
344
|
+
return <header {...attrs}>{/* ...the header markup */}</header>;
|
|
350
345
|
}
|
|
351
346
|
```
|
|
352
347
|
|
|
@@ -354,11 +349,32 @@ Import both from `@akinon/pz-theme/src/custom`, like the rest of the section
|
|
|
354
349
|
API. The package root exports them too, but it pulls in `server-only`, so a
|
|
355
350
|
client header cannot import them from there.
|
|
356
351
|
|
|
357
|
-
|
|
358
|
-
|
|
352
|
+
**Spread what the hook returns on the header's root element.** That attribute is
|
|
353
|
+
what the first paint uses: it is in the HTML the server sends, and the `docked`
|
|
354
|
+
selector reads it through `body:has(…)`, so an inner page arrives docked instead
|
|
355
|
+
of showing the transparent overlay header until hydration. The hook also stamps
|
|
356
|
+
`body` from a layout effect — the editor's docked preview, browsers without
|
|
357
|
+
`:has` and any CSS of your own written against `body[data-chrome-mode]` all rely
|
|
358
|
+
on that half. Leave the spread out and the header still docks, but only after
|
|
359
|
+
hydration, which is exactly the flash this exists to remove.
|
|
360
|
+
|
|
361
|
+
**Compute the mode from render-time inputs only** — `usePathname()`, settings,
|
|
362
|
+
props. Never from `window`, `localStorage` or state an effect sets. The server
|
|
363
|
+
renders the attribute from what it knows, and the client's first render has to
|
|
364
|
+
produce the same markup: React does not promise to patch a mismatched attribute
|
|
365
|
+
while hydrating, so a mode only the browser could compute can leave the server's
|
|
366
|
+
answer on the header while `body` says the opposite.
|
|
367
|
+
|
|
368
|
+
**Give the signal one owner.** The hook's cleanup restores the value its stamp
|
|
359
369
|
displaced — a snapshot, not a counted stack — so it unwinds correctly as one
|
|
360
|
-
component mounts,
|
|
361
|
-
components both
|
|
370
|
+
component mounts, changes mode and unmounts, and does *not* compose if two
|
|
371
|
+
components both declare a mode during a route transition.
|
|
372
|
+
|
|
373
|
+
The two halves are exported too, for a header that cannot call a hook:
|
|
374
|
+
`chromeModeAttrs(mode)` returns the attribute and is pure, so a server-rendered
|
|
375
|
+
header can call it, and `setChromeMode(mode)` is the `body` stamp, called from an
|
|
376
|
+
effect with the cleanup it returns handed straight back. Prefer the hook — it is
|
|
377
|
+
both, in the right order.
|
|
362
378
|
|
|
363
379
|
The section then styles all three appearances, and no descriptor changes:
|
|
364
380
|
|
|
@@ -386,7 +402,8 @@ did.
|
|
|
386
402
|
**`docked` outranks `scrolled` on purpose.** On an inner page both are true at
|
|
387
403
|
once — a docked header is scrolled the moment the shopper moves — and both set
|
|
388
404
|
the same property on the same band. The `docked` selector is qualified with
|
|
389
|
-
`body
|
|
405
|
+
`body` — both of its branches, the `body` stamp and `body:has(…)`, weigh the
|
|
406
|
+
same — which makes it (0,2,1) against `scrolled` at (0,2,0), so the mode wins
|
|
390
407
|
on specificity rather than on whichever rule happened to be emitted last. That
|
|
391
408
|
is the right answer and not an arbitrary one: `scrolled` describes how far the
|
|
392
409
|
page has slid under a bar that floats over it, and once the bar is docked the
|
|
@@ -4,6 +4,7 @@ import React from 'react';
|
|
|
4
4
|
import { render, screen } from '@testing-library/react';
|
|
5
5
|
|
|
6
6
|
import * as chromeMode from '../../chrome/chrome-mode';
|
|
7
|
+
import * as chromeModeHook from '../../chrome/use-chrome-mode';
|
|
7
8
|
import * as blockContext from '../block-context';
|
|
8
9
|
import * as overlay from '../../hooks/use-overlay';
|
|
9
10
|
import { resetCustomCollection } from '../collect';
|
|
@@ -15,8 +16,10 @@ import type { Section } from '../../theme-section';
|
|
|
15
16
|
// `server-only`, so a client module cannot reach them there.
|
|
16
17
|
import {
|
|
17
18
|
CHROME_MODE_DOCKED,
|
|
19
|
+
chromeModeAttrs,
|
|
18
20
|
defineCustomSection,
|
|
19
21
|
setChromeMode,
|
|
22
|
+
useChromeMode,
|
|
20
23
|
useCustomBlockContext,
|
|
21
24
|
useOverlay
|
|
22
25
|
} from '../index';
|
|
@@ -147,6 +150,53 @@ describe('useCustomBlockContext', () => {
|
|
|
147
150
|
});
|
|
148
151
|
});
|
|
149
152
|
|
|
153
|
+
describe('the chrome mode exports for first paint', () => {
|
|
154
|
+
it('re-exports the same bindings rather than second copies', () => {
|
|
155
|
+
expect(chromeModeAttrs).toBe(chromeMode.chromeModeAttrs);
|
|
156
|
+
expect(useChromeMode).toBe(chromeModeHook.useChromeMode);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('keeps use-chrome-mode.ts client-safe: a client directive, and react and chrome-mode as its only imports', () => {
|
|
160
|
+
const source = fs.readFileSync(
|
|
161
|
+
path.resolve(__dirname, '../../chrome/use-chrome-mode.ts'),
|
|
162
|
+
'utf8'
|
|
163
|
+
);
|
|
164
|
+
const sources = Array.from(
|
|
165
|
+
source.matchAll(/^import[\s\S]*?from\s+['"]([^'"]+)['"]/gm)
|
|
166
|
+
).map((match) => match[1]);
|
|
167
|
+
|
|
168
|
+
expect(source.trimStart()).toMatch(/^['"]use client['"]/);
|
|
169
|
+
expect(source).not.toMatch(/server-only/);
|
|
170
|
+
expect(sources.length).toBeGreaterThan(0);
|
|
171
|
+
sources.forEach((from) => {
|
|
172
|
+
expect(['react', './chrome-mode']).toContain(from);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('useChromeMode from the custom entry', () => {
|
|
178
|
+
function StoreHeader({ mode }: { mode: ChromeMode }) {
|
|
179
|
+
return <header data-testid="store-header" {...useChromeMode(mode)} />;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
afterEach(() => {
|
|
183
|
+
document.body.removeAttribute('data-chrome-mode');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('puts the mode on the header root and on body, and takes body back on unmount', () => {
|
|
187
|
+
const { unmount } = render(<StoreHeader mode={CHROME_MODE_DOCKED} />);
|
|
188
|
+
|
|
189
|
+
expect(
|
|
190
|
+
screen.getByTestId('store-header').getAttribute('data-chrome-mode')
|
|
191
|
+
).toBe('docked');
|
|
192
|
+
expect(document.body.getAttribute('data-chrome-mode')).toBe('docked');
|
|
193
|
+
|
|
194
|
+
unmount();
|
|
195
|
+
|
|
196
|
+
expect(document.body.hasAttribute('data-chrome-mode')).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
150
200
|
describe('setChromeMode from the custom entry', () => {
|
|
151
201
|
afterEach(() => {
|
|
152
202
|
document.body.removeAttribute('data-chrome-mode');
|
package/src/custom/index.ts
CHANGED
|
@@ -239,12 +239,28 @@ export { findRegionChild, isRegionNode, regionNodeName } from './region-nodes';
|
|
|
239
239
|
export { partAttrs } from '../utils/part-styles';
|
|
240
240
|
|
|
241
241
|
/**
|
|
242
|
-
* The chrome mode
|
|
243
|
-
*
|
|
242
|
+
* The chrome mode, re-exported so a custom header section can declare its
|
|
243
|
+
* docked appearance from this module. The package root exports the same
|
|
244
244
|
* symbols but pulls in `server-only`, which a client module cannot import.
|
|
245
|
-
*
|
|
246
|
-
* is
|
|
247
|
-
*
|
|
245
|
+
*
|
|
246
|
+
* `useChromeMode` is the one to reach for: call it in the header with a mode
|
|
247
|
+
* computed from render-time inputs (`usePathname()`, settings, props) and
|
|
248
|
+
* spread what it returns on the header's root element. That attribute is the
|
|
249
|
+
* first-paint signal; the hook also stamps `body` from a layout effect.
|
|
250
|
+
* `chrome/use-chrome-mode.ts` is 'use client' and imports only react and
|
|
251
|
+
* `chrome-mode.ts`, so it is safe to re-export here.
|
|
252
|
+
*
|
|
253
|
+
* `chromeModeAttrs` and `setChromeMode` are its two halves, for a header that
|
|
254
|
+
* cannot use the hook — `chromeModeAttrs` is pure, so a server-rendered header
|
|
255
|
+
* can call it. `chrome/chrome-mode.ts` has no imports and no directive, and
|
|
256
|
+
* `setChromeMode` is a no-op without a `document`, so both are safe on either
|
|
257
|
+
* graph. Call `setChromeMode` from an effect and return the cleanup it hands
|
|
258
|
+
* back.
|
|
248
259
|
*/
|
|
249
|
-
export {
|
|
260
|
+
export {
|
|
261
|
+
CHROME_MODE_DOCKED,
|
|
262
|
+
chromeModeAttrs,
|
|
263
|
+
setChromeMode
|
|
264
|
+
} from '../chrome/chrome-mode';
|
|
250
265
|
export type { ChromeMode } from '../chrome/chrome-mode';
|
|
266
|
+
export { useChromeMode } from '../chrome/use-chrome-mode';
|
package/src/index.ts
CHANGED
|
@@ -66,9 +66,11 @@ export {
|
|
|
66
66
|
export {
|
|
67
67
|
CHROME_MODE_ATTRIBUTE,
|
|
68
68
|
CHROME_MODE_DOCKED,
|
|
69
|
+
chromeModeAttrs,
|
|
69
70
|
setChromeMode
|
|
70
71
|
} from './chrome/chrome-mode';
|
|
71
72
|
export type { ChromeMode } from './chrome/chrome-mode';
|
|
73
|
+
export { useChromeMode } from './chrome/use-chrome-mode';
|
|
72
74
|
export { PreviewAuthStateReporter } from './chrome/preview-auth-state';
|
|
73
75
|
export { LiveBasketPageContext } from './chrome/live-basket-page-context';
|
|
74
76
|
export { LiveWishlistPageContext } from './chrome/live-wishlist-page-context';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, ReactNode, useContext } from 'react';
|
|
4
|
+
import { menuSeedKey } from './blocks/menu-query-args';
|
|
5
|
+
import type { MenuQueryArgs, MenuSeeds } from './blocks/menu-query-args';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Server-fetched menus for the nav-menu blocks of one placeholder
|
|
9
|
+
* (chrome/menu-seed-source.ts). `null` wherever no server answered: the
|
|
10
|
+
* designer's modal preview host, a brand that mounts the wrapper itself.
|
|
11
|
+
* Blocks then fetch on the client exactly as they always have.
|
|
12
|
+
*
|
|
13
|
+
* Read straight from the prop, no state: the server and the first client
|
|
14
|
+
* render must see the same value or hydration would not match.
|
|
15
|
+
*/
|
|
16
|
+
export const MenuSeedContext = createContext<MenuSeeds | null>(null);
|
|
17
|
+
|
|
18
|
+
export function MenuSeedProvider({
|
|
19
|
+
value,
|
|
20
|
+
children
|
|
21
|
+
}: {
|
|
22
|
+
value: MenuSeeds | null;
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<MenuSeedContext.Provider value={value}>
|
|
27
|
+
{children}
|
|
28
|
+
</MenuSeedContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The seeded entries for these args, or `undefined` when none were seeded. */
|
|
33
|
+
export function useMenuSeed(args: MenuQueryArgs): unknown[] | undefined {
|
|
34
|
+
const seeds = useContext(MenuSeedContext);
|
|
35
|
+
const seeded = seeds?.[menuSeedKey(args)];
|
|
36
|
+
|
|
37
|
+
return Array.isArray(seeded) ? seeded : undefined;
|
|
38
|
+
}
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
import { useThemePageContext } from './theme-page-context';
|
|
17
17
|
import { ThemePageContextProvider } from './theme-page-context';
|
|
18
18
|
import { ThemeSettingsProvider } from './theme-settings-context';
|
|
19
|
+
import { MenuSeedProvider } from './menu-seed-context';
|
|
20
|
+
import type { MenuSeeds } from './blocks/menu-query-args';
|
|
19
21
|
import { generateThemeCSS, readThemeTypographyFont } from './utils';
|
|
20
22
|
import { breakpointForWidth } from './utils/breakpoints';
|
|
21
23
|
import { PartStatePreview } from './components/part-state-preview';
|
|
@@ -221,6 +223,13 @@ interface ThemePlaceholderWrapperProps {
|
|
|
221
223
|
dataSources?: any[];
|
|
222
224
|
initialThemeSettings?: Record<string, unknown> | null;
|
|
223
225
|
initialPageContext?: Record<string, unknown> | null;
|
|
226
|
+
/**
|
|
227
|
+
* Menus the server fetched for this placeholder's nav-menu blocks
|
|
228
|
+
* (chrome/menu-seed-source.ts), so the category row is in the served HTML.
|
|
229
|
+
* Absent for callers without a server render; the blocks then fetch on the
|
|
230
|
+
* client as before.
|
|
231
|
+
*/
|
|
232
|
+
initialMenuSeeds?: MenuSeeds | null;
|
|
224
233
|
}
|
|
225
234
|
|
|
226
235
|
declare global {
|
|
@@ -357,7 +366,8 @@ export default function ThemePlaceholderWrapper({
|
|
|
357
366
|
isDesignMode,
|
|
358
367
|
dataSources: initialDataSources = [],
|
|
359
368
|
initialThemeSettings = null,
|
|
360
|
-
initialPageContext = null
|
|
369
|
+
initialPageContext = null,
|
|
370
|
+
initialMenuSeeds = null
|
|
361
371
|
}: ThemePlaceholderWrapperProps) {
|
|
362
372
|
const inheritedPageContext = useThemePageContext();
|
|
363
373
|
const [sections, setSections] = useState<Section[]>(initialSections);
|
|
@@ -1058,96 +1068,98 @@ export default function ThemePlaceholderWrapper({
|
|
|
1058
1068
|
// falls back to its empty state.
|
|
1059
1069
|
<ThemePageContextProvider value={resolvedPageContext}>
|
|
1060
1070
|
<ThemeSettingsProvider value={themeSettings}>
|
|
1061
|
-
{
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
{
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
(
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
<
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1071
|
+
<MenuSeedProvider value={initialMenuSeeds}>
|
|
1072
|
+
{/*
|
|
1073
|
+
Pins are released while browsing — a button frozen in its hover state
|
|
1074
|
+
is not what the live site looks like. The editor re-sends the pinned
|
|
1075
|
+
state when the designer comes back, so the panel and the canvas agree.
|
|
1076
|
+
*/}
|
|
1077
|
+
<PartStatePreview isDesigner={isEditing} />
|
|
1078
|
+
{/*
|
|
1079
|
+
Tells the editor which languages this storefront has and follows its
|
|
1080
|
+
language switch. Every placeholder renders one; they share a single
|
|
1081
|
+
listener and a single announcement (see editor-locale-bridge.tsx).
|
|
1082
|
+
*/}
|
|
1083
|
+
<EditorLocaleBridge />
|
|
1084
|
+
{themeBackgroundCSS && (
|
|
1085
|
+
<style dangerouslySetInnerHTML={{ __html: themeBackgroundCSS }} />
|
|
1086
|
+
)}
|
|
1087
|
+
{dynamicCSS && (
|
|
1088
|
+
<style dangerouslySetInnerHTML={{ __html: dynamicCSS }} />
|
|
1089
|
+
)}
|
|
1090
|
+
{/*
|
|
1091
|
+
Deliberately unpainted. This wrapper sits behind every section of the
|
|
1092
|
+
slot, so any colour on it showed through a section that asked for
|
|
1093
|
+
`background-color: transparent` — the shipped default. That is why a
|
|
1094
|
+
composed header could not sit over a hero: the section was transparent
|
|
1095
|
+
and the wrapper behind it was not. The page colour belongs to `body`
|
|
1096
|
+
(see buildThemeBackgroundCss), which paints the same pixels everywhere
|
|
1097
|
+
the slot does not overlap something else.
|
|
1098
|
+
*/}
|
|
1099
|
+
<div data-placeholder={slug} className="theme-placeholder">
|
|
1100
|
+
{renderedSections
|
|
1101
|
+
.sort((a, b) => a.order - b.order)
|
|
1102
|
+
.filter(
|
|
1103
|
+
(section) =>
|
|
1104
|
+
!section.hidden &&
|
|
1105
|
+
(isEditing ||
|
|
1106
|
+
isPublishWindowVisible(
|
|
1107
|
+
section.properties?.['publish-window']
|
|
1108
|
+
))
|
|
1109
|
+
)
|
|
1110
|
+
.map((section) => (
|
|
1111
|
+
<SectionErrorBoundary key={section.id} sectionId={section.id}>
|
|
1112
|
+
<ThemeSection
|
|
1113
|
+
section={section}
|
|
1114
|
+
placeholderId={placeholderSlug}
|
|
1115
|
+
pageContext={resolvedPageContext}
|
|
1116
|
+
isDesigner={isEditing}
|
|
1117
|
+
isSelected={selectedSectionId === section.id}
|
|
1118
|
+
selectedBlockId={selectedBlockId}
|
|
1119
|
+
currentBreakpoint={currentBreakpoint}
|
|
1120
|
+
onSelect={setSelectedSectionId}
|
|
1121
|
+
onMoveUp={() =>
|
|
1122
|
+
sendAction('MOVE_SECTION_UP', {
|
|
1123
|
+
placeholderId: placeholderSlug,
|
|
1124
|
+
sectionId: section.id
|
|
1125
|
+
})
|
|
1126
|
+
}
|
|
1127
|
+
onMoveDown={() =>
|
|
1128
|
+
sendAction('MOVE_SECTION_DOWN', {
|
|
1129
|
+
placeholderId: placeholderSlug,
|
|
1130
|
+
sectionId: section.id
|
|
1131
|
+
})
|
|
1132
|
+
}
|
|
1133
|
+
onDuplicate={() =>
|
|
1134
|
+
sendAction('DUPLICATE_SECTION', {
|
|
1135
|
+
placeholderId: placeholderSlug,
|
|
1136
|
+
sectionId: section.id
|
|
1137
|
+
})
|
|
1138
|
+
}
|
|
1139
|
+
onToggleVisibility={() =>
|
|
1140
|
+
sendAction('TOGGLE_SECTION_VISIBILITY', {
|
|
1141
|
+
placeholderId: placeholderSlug,
|
|
1142
|
+
sectionId: section.id
|
|
1143
|
+
})
|
|
1144
|
+
}
|
|
1145
|
+
onDelete={() =>
|
|
1146
|
+
sendAction('DELETE_SECTION', {
|
|
1147
|
+
placeholderId: placeholderSlug,
|
|
1148
|
+
sectionId: section.id
|
|
1149
|
+
})
|
|
1150
|
+
}
|
|
1151
|
+
onRename={(newLabel) =>
|
|
1152
|
+
sendAction('RENAME_SECTION', {
|
|
1153
|
+
placeholderId: placeholderSlug,
|
|
1154
|
+
sectionId: section.id,
|
|
1155
|
+
label: newLabel
|
|
1156
|
+
})
|
|
1157
|
+
}
|
|
1158
|
+
/>
|
|
1159
|
+
</SectionErrorBoundary>
|
|
1160
|
+
))}
|
|
1161
|
+
</div>
|
|
1162
|
+
</MenuSeedProvider>
|
|
1151
1163
|
</ThemeSettingsProvider>
|
|
1152
1164
|
</ThemePageContextProvider>
|
|
1153
1165
|
);
|
|
@@ -4,6 +4,7 @@ import ThemePlaceholderWrapper from './theme-placeholder-wrapper';
|
|
|
4
4
|
import { ThemeFontsLink } from './chrome/theme-fonts-link';
|
|
5
5
|
import { ThemeGoogleFontsLinks } from './chrome/theme-google-fonts-links';
|
|
6
6
|
import { loadPlaceholderSections } from './load-placeholder-sections';
|
|
7
|
+
import { loadMenuSeeds } from './chrome/menu-seed-source';
|
|
7
8
|
import { generateThemeCSS } from './utils';
|
|
8
9
|
|
|
9
10
|
interface ThemePlaceholderProps {
|
|
@@ -23,6 +24,9 @@ export default async function ThemePlaceholder({
|
|
|
23
24
|
|
|
24
25
|
const { sections, placeholderId, dataSources, themeSettings } = loaded;
|
|
25
26
|
const themeCSS = generateThemeCSS(sections);
|
|
27
|
+
// The live and the preview-snapshot loads both arrive here, so both get the
|
|
28
|
+
// menus their nav-menu blocks would otherwise fetch after hydration.
|
|
29
|
+
const menuSeeds = await loadMenuSeeds(sections);
|
|
26
30
|
|
|
27
31
|
return (
|
|
28
32
|
<>
|
|
@@ -37,6 +41,7 @@ export default async function ThemePlaceholder({
|
|
|
37
41
|
dataSources={dataSources}
|
|
38
42
|
initialThemeSettings={themeSettings}
|
|
39
43
|
initialPageContext={pageContext}
|
|
44
|
+
initialMenuSeeds={menuSeeds}
|
|
40
45
|
/>
|
|
41
46
|
</>
|
|
42
47
|
);
|