weg-shared-layout 0.0.48 → 0.0.50
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/dist/cjs/weg-footer_2.cjs.entry.js +3 -3
- package/dist/collection/components/weg-footer/weg-footer.css +2 -26
- package/dist/collection/components/weg-footer/weg-footer.js +2 -2
- package/dist/components/weg-footer.js +1 -1
- package/dist/esm/weg-footer_2.entry.js +3 -3
- package/dist/weg-shared-layout/p-21d4edfc.entry.js +1 -0
- package/dist/weg-shared-layout/weg-shared-layout.esm.js +1 -1
- package/docs/README.md +54 -54
- package/docs/angular.md +440 -440
- package/docs/nextjs.md +313 -313
- package/docs/publishing.md +65 -65
- package/docs/react.md +291 -291
- package/docs/vanilla.md +159 -159
- package/package.json +1 -1
- package/readme.md +121 -121
- package/src/types/menus-data.ts +44 -44
- package/src/utils/menus.ts +65 -65
- package/dist/weg-shared-layout/p-16f87e9e.entry.js +0 -1
package/docs/nextjs.md
CHANGED
|
@@ -1,313 +1,313 @@
|
|
|
1
|
-
# Next.js (App Router)
|
|
2
|
-
|
|
3
|
-
Guide for **Next.js 13+ App Router** (`app/` directory). For Vite/CRA-style client-only React, see **[React SPA](./react.md)**.
|
|
4
|
-
|
|
5
|
-
## Components
|
|
6
|
-
|
|
7
|
-
| Tag | Purpose |
|
|
8
|
-
| --- | --- |
|
|
9
|
-
| `<weg-header>` | Site header — bundled logo, CMS nav (signed out), built-in nav (signed in), Sign in / Manage Account / Sign out |
|
|
10
|
-
| `<weg-footer>` | Site footer — social, menu, legal text |
|
|
11
|
-
|
|
12
|
-
Both accept **`layout`** (JSON string recommended in Next). `<weg-header>` also accepts **`signed-in`**, **`user-name`**, **`account-base-url`**, **`current-path`**, and emits **`wegAuthClick`**.
|
|
13
|
-
|
|
14
|
-
## Why Next.js is different
|
|
15
|
-
|
|
16
|
-
| Topic | SPA (React 19) | Next.js App Router |
|
|
17
|
-
| --- | --- | --- |
|
|
18
|
-
| **Where components run** | Client only | Must be in **Client Components** (`"use client"`) |
|
|
19
|
-
| **Server Components** | N/A | Default in `app/` — cannot call `defineCustomElements()` |
|
|
20
|
-
| **SSR output** | N/A | Often empty custom-element tags until client hydrates |
|
|
21
|
-
| **`layout={object}` on CE** | Works on React 19+ | **Unreliable** — use **`JSON.stringify(layout)`** |
|
|
22
|
-
| **Auth state** | `signed-in` prop | Set in client wrapper from session hook / context |
|
|
23
|
-
|
|
24
|
-
## Requirements
|
|
25
|
-
|
|
26
|
-
| Requirement | Notes |
|
|
27
|
-
| --- | --- |
|
|
28
|
-
| **Next.js 13+** | App Router (`app/layout.tsx`) |
|
|
29
|
-
| **`transpilePackages`** | Next must compile `weg-shared-layout` |
|
|
30
|
-
| **`suppressHydrationWarning`** | Recommended on both tags (SSR placeholder vs client shadow DOM) |
|
|
31
|
-
| **TypeScript** | `resolveJsonModule` if importing `weg-shared-layout/weg21-*.json` on the server |
|
|
32
|
-
|
|
33
|
-
## Install
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm i weg-shared-layout
|
|
37
|
-
# or
|
|
38
|
-
pnpm add weg-shared-layout
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## 1. Transpile the package
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
// next.config.ts
|
|
45
|
-
import type { NextConfig } from 'next';
|
|
46
|
-
|
|
47
|
-
const nextConfig: NextConfig = {
|
|
48
|
-
transpilePackages: ['weg-shared-layout'],
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export default nextConfig;
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## 2. Sign-out handling
|
|
55
|
-
|
|
56
|
-
Sign-in comes from your CMS `header.menu` like any other link. Handle sign-out via **`wegAuthClick`** — call your logout API, then redirect (e.g. to `/`).
|
|
57
|
-
|
|
58
|
-
## 3. Client layout components
|
|
59
|
-
|
|
60
|
-
```tsx
|
|
61
|
-
// src/components/layout/Header.tsx
|
|
62
|
-
'use client';
|
|
63
|
-
|
|
64
|
-
import { useCallback } from 'react';
|
|
65
|
-
import { usePathname } from 'next/navigation';
|
|
66
|
-
import { defineCustomElements } from 'weg-shared-layout/loader';
|
|
67
|
-
import 'weg-shared-layout/weg-header';
|
|
68
|
-
|
|
69
|
-
defineCustomElements();
|
|
70
|
-
|
|
71
|
-
import type { LayoutData } from 'weg-shared-layout/layout-data';
|
|
72
|
-
// Or: import { dummyWeg21LayoutData } from 'weg-shared-layout/menus';
|
|
73
|
-
// type LayoutData = typeof layoutFixture;
|
|
74
|
-
|
|
75
|
-
export function Header({
|
|
76
|
-
layout,
|
|
77
|
-
signedIn,
|
|
78
|
-
userName,
|
|
79
|
-
onSignedInChange,
|
|
80
|
-
}: {
|
|
81
|
-
layout: LayoutData;
|
|
82
|
-
signedIn: boolean;
|
|
83
|
-
userName?: string;
|
|
84
|
-
onSignedInChange?: (signedIn: boolean) => void;
|
|
85
|
-
}) {
|
|
86
|
-
const pathname = usePathname();
|
|
87
|
-
|
|
88
|
-
const onAuthClick = useCallback(
|
|
89
|
-
async (event: CustomEvent<{ action: 'sign-in' | 'sign-out' }>) => {
|
|
90
|
-
if (event.detail.action !== 'sign-out') return;
|
|
91
|
-
|
|
92
|
-
event.preventDefault();
|
|
93
|
-
await logout();
|
|
94
|
-
onSignedInChange?.(false);
|
|
95
|
-
window.location.assign('/');
|
|
96
|
-
},
|
|
97
|
-
[onSignedInChange],
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<weg-header
|
|
102
|
-
layout={JSON.stringify(layout)}
|
|
103
|
-
signedIn={signedIn}
|
|
104
|
-
currentPath={pathname}
|
|
105
|
-
{...(userName ? { userName } : {})}
|
|
106
|
-
suppressHydrationWarning
|
|
107
|
-
onWegAuthClick={onAuthClick}
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
```tsx
|
|
114
|
-
// src/components/layout/Footer.tsx
|
|
115
|
-
'use client';
|
|
116
|
-
|
|
117
|
-
import { defineCustomElements } from 'weg-shared-layout/loader';
|
|
118
|
-
import 'weg-shared-layout/weg-footer';
|
|
119
|
-
|
|
120
|
-
defineCustomElements();
|
|
121
|
-
|
|
122
|
-
export function Footer({ layout }: { layout: unknown }) {
|
|
123
|
-
return <weg-footer layout={JSON.stringify(layout)} suppressHydrationWarning />;
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
```tsx
|
|
128
|
-
// src/components/layout/SiteChrome.tsx — optional wrapper
|
|
129
|
-
'use client';
|
|
130
|
-
|
|
131
|
-
import { useState } from 'react';
|
|
132
|
-
import { Header } from './Header';
|
|
133
|
-
import { Footer } from './Footer';
|
|
134
|
-
|
|
135
|
-
export function SiteChrome({
|
|
136
|
-
layout,
|
|
137
|
-
children,
|
|
138
|
-
}: {
|
|
139
|
-
layout: unknown;
|
|
140
|
-
children: React.ReactNode;
|
|
141
|
-
}) {
|
|
142
|
-
const [signedIn, setSignedIn] = useState(false);
|
|
143
|
-
const userName = signedIn ? 'Alex' : undefined; // from your session
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<>
|
|
147
|
-
<Header
|
|
148
|
-
layout={layout}
|
|
149
|
-
signedIn={signedIn}
|
|
150
|
-
userName={userName}
|
|
151
|
-
onSignedInChange={setSignedIn}
|
|
152
|
-
/>
|
|
153
|
-
{children}
|
|
154
|
-
<Footer layout={layout} />
|
|
155
|
-
</>
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Why `JSON.stringify` and `suppressHydrationWarning`
|
|
161
|
-
|
|
162
|
-
| Line | Purpose |
|
|
163
|
-
| --- | --- |
|
|
164
|
-
| `layout={JSON.stringify(layout)}` | Serializable across RSC → client; component parses JSON |
|
|
165
|
-
| `suppressHydrationWarning` | Server renders empty tag; client fills shadow DOM |
|
|
166
|
-
| `'use client'` | Registration and auth handlers need the browser |
|
|
167
|
-
|
|
168
|
-
Do **not** pass `layout={layoutObject}` directly on the custom elements in Next.
|
|
169
|
-
|
|
170
|
-
## 4. Server layout
|
|
171
|
-
|
|
172
|
-
```tsx
|
|
173
|
-
// app/layout.tsx (Server Component)
|
|
174
|
-
import { SiteChrome } from '@/components/layout/SiteChrome';
|
|
175
|
-
import { dummyWeg21LayoutData } from 'weg-shared-layout/menus';
|
|
176
|
-
const layout = dummyWeg21LayoutData();
|
|
177
|
-
|
|
178
|
-
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
179
|
-
return (
|
|
180
|
-
<html lang="en">
|
|
181
|
-
<body>
|
|
182
|
-
<SiteChrome layout={layout}>{children}</SiteChrome>
|
|
183
|
-
</body>
|
|
184
|
-
</html>
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## 5. Production: fetch layout on the server
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
// app/layout.tsx
|
|
193
|
-
import { SiteChrome } from '@/components/layout/SiteChrome';
|
|
194
|
-
|
|
195
|
-
const WEG21_API = 'https://warwickemploymentgroup.com/api/v1/weg21';
|
|
196
|
-
|
|
197
|
-
async function getLayout() {
|
|
198
|
-
const res = await fetch(LAYOUT_URL, { next: { revalidate: 60 } });
|
|
199
|
-
if (!res.ok) throw new Error('Failed to load layout');
|
|
200
|
-
return res.json();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
204
|
-
const layout = await getLayout();
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<html lang="en">
|
|
208
|
-
<body>
|
|
209
|
-
<SiteChrome layout={layout}>{children}</SiteChrome>
|
|
210
|
-
</body>
|
|
211
|
-
</html>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Pass the plain object into the Client Component; **stringify inside** the header/footer wrappers.
|
|
217
|
-
|
|
218
|
-
Wire **`signedIn`** and **`userName`** from your auth provider (session hook / context in a client wrapper). Pass **`currentPath`** from `usePathname()` (and `useSearchParams()` when dropdown links use query strings).
|
|
219
|
-
|
|
220
|
-
## Nav active state
|
|
221
|
-
|
|
222
|
-
Pass **`current-path`** from the client wrapper. Flat links use prefix matching; dropdown children use exact pathname (+ query when the href includes `?`). Dropdown parents keep the active background when a child matches. Active styling uses **`weg-purple-200`** (`#CDCFF8`).
|
|
223
|
-
|
|
224
|
-
## Header auth reference
|
|
225
|
-
|
|
226
|
-
| API | Signed out | Signed in |
|
|
227
|
-
| --- | --- | --- |
|
|
228
|
-
| `layout.header.menu` | CMS nav rendered (groups + flat links incl. sign-in) | Ignored — built-in nav used |
|
|
229
|
-
| `layout.header.logoSrc` | Logo image URL (bundled if omitted) | Built-in bundled logo |
|
|
230
|
-
| `layout.header.logoHref` | Logo link URL | WEG homepage |
|
|
231
|
-
| `signed-in` prop | `false` | `true` — session flag from host app |
|
|
232
|
-
| `user-name` prop | — | User's first name on Manage Account |
|
|
233
|
-
| `account-base-url` prop | — | Account portal origin for signed-in links (optional) |
|
|
234
|
-
| `current-path` prop | Current pathname (optional query) | Highlights the active nav link; e.g. `/career-advice/my-article` |
|
|
235
|
-
| `wegAuthClick` event | Sign-in follows link `href` | `'sign-out'` — host handles logout and redirect |
|
|
236
|
-
| `event.preventDefault()` | Skip default navigation | Required — sign-out has no built-in redirect |
|
|
237
|
-
|
|
238
|
-
**Signed-in nav (built into the component):** Find a job, Dashboard, Manage Account, Sign out.
|
|
239
|
-
|
|
240
|
-
The logo **image** uses `layout.header.logoSrc` when signed out (bundled if omitted). The logo **link** uses `layout.header.logoHref` when provided; otherwise it defaults to the WEG homepage.
|
|
241
|
-
|
|
242
|
-
## Passing `layout` — quick reference
|
|
243
|
-
|
|
244
|
-
| Approach | Works in Next? |
|
|
245
|
-
| --- | --- |
|
|
246
|
-
| `layout={object}` on `<weg-header>` / `<weg-footer>` | **No** |
|
|
247
|
-
| `layout={JSON.stringify(obj)}` in client wrapper | **Yes** (recommended) |
|
|
248
|
-
| Pass object to `<SiteChrome layout={...} />`, stringify in child | **Yes** |
|
|
249
|
-
| `ref` + `el.layout = obj` in `useEffect` | Yes (escape hatch) |
|
|
250
|
-
|
|
251
|
-
## TypeScript
|
|
252
|
-
|
|
253
|
-
### Server / client JSON imports
|
|
254
|
-
|
|
255
|
-
```json
|
|
256
|
-
{
|
|
257
|
-
"compilerOptions": {
|
|
258
|
-
"resolveJsonModule": true,
|
|
259
|
-
"moduleResolution": "bundler"
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### JSX module augmentation
|
|
265
|
-
|
|
266
|
-
```ts
|
|
267
|
-
// src/types/weg-shared-layout-jsx.d.ts
|
|
268
|
-
import type { JSX as WegSharedLayoutJSX } from 'weg-shared-layout';
|
|
269
|
-
import type { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
270
|
-
|
|
271
|
-
declare module 'react' {
|
|
272
|
-
namespace JSX {
|
|
273
|
-
interface IntrinsicElements extends WegSharedLayoutJSX.IntrinsicElements {
|
|
274
|
-
'weg-header': WegSharedLayoutJSX.IntrinsicElements['weg-header'] &
|
|
275
|
-
DetailedHTMLProps<HTMLAttributes<HTMLWegHeaderElement>, HTMLWegHeaderElement>;
|
|
276
|
-
'weg-footer': WegSharedLayoutJSX.IntrinsicElements['weg-footer'] &
|
|
277
|
-
DetailedHTMLProps<HTMLAttributes<HTMLWegFooterElement>, HTMLWegFooterElement>;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Troubleshooting
|
|
284
|
-
|
|
285
|
-
| Symptom | Likely cause | Fix |
|
|
286
|
-
| --- | --- | --- |
|
|
287
|
-
| `document is not defined` | Loader in Server Component | Keep registration in `"use client"` files only. |
|
|
288
|
-
| Empty after hydration | Object passed to CE without stringify | `JSON.stringify` in client wrapper. |
|
|
289
|
-
| Logo missing | Stale package | Logo is inlined in `logo-data.ts`; rebuild / upgrade. |
|
|
290
|
-
| Header not clickable | Overlapping page content | `:host` uses `z-index: 20`; upgrade package if missing. |
|
|
291
|
-
| Auth not updating | `signed-in` only on server | Manage session in client state / context. |
|
|
292
|
-
| Nav never highlights | `current-path` not passed | Pass `usePathname()` (+ search params when needed) from client wrapper. |
|
|
293
|
-
| Hydration warning | Shadow DOM mismatch | `suppressHydrationWarning` on both tags. |
|
|
294
|
-
| Build error | Package not transpiled | `transpilePackages: ['weg-shared-layout']`. |
|
|
295
|
-
|
|
296
|
-
## Integration checklist
|
|
297
|
-
|
|
298
|
-
- [ ] `weg-shared-layout` installed
|
|
299
|
-
- [ ] `transpilePackages` in `next.config`
|
|
300
|
-
- [ ] Client `Header.tsx` / `Footer.tsx` with loader, tag imports, `JSON.stringify`, `suppressHydrationWarning`
|
|
301
|
-
- [ ] Auth: `signed-in`, `user-name`, `account-base-url` (optional), `current-path` from `usePathname()`, and `wegAuthClick` handler on header
|
|
302
|
-
- [ ] Server `layout.tsx` imports client chrome only (no `defineCustomElements` on server)
|
|
303
|
-
- [ ] Layout fetched or imported server-side, passed as serializable props
|
|
304
|
-
- [ ] TypeScript augmentation for `'weg-header'` and `'weg-footer'`
|
|
305
|
-
- [ ] Verified in browser after hydration
|
|
306
|
-
|
|
307
|
-
## See also
|
|
308
|
-
|
|
309
|
-
- **[Documentation index](./README.md)**
|
|
310
|
-
- **[React SPA](./react.md)**
|
|
311
|
-
- **[Angular](./angular.md)** · **[Angular 16 demo](../demo/angular16/README.md)**
|
|
312
|
-
- **[Plain HTML / vanilla JS](./vanilla.md)**
|
|
313
|
-
- **[Package readme](../readme.md)**
|
|
1
|
+
# Next.js (App Router)
|
|
2
|
+
|
|
3
|
+
Guide for **Next.js 13+ App Router** (`app/` directory). For Vite/CRA-style client-only React, see **[React SPA](./react.md)**.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
| Tag | Purpose |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `<weg-header>` | Site header — bundled logo, CMS nav (signed out), built-in nav (signed in), Sign in / Manage Account / Sign out |
|
|
10
|
+
| `<weg-footer>` | Site footer — social, menu, legal text |
|
|
11
|
+
|
|
12
|
+
Both accept **`layout`** (JSON string recommended in Next). `<weg-header>` also accepts **`signed-in`**, **`user-name`**, **`account-base-url`**, **`current-path`**, and emits **`wegAuthClick`**.
|
|
13
|
+
|
|
14
|
+
## Why Next.js is different
|
|
15
|
+
|
|
16
|
+
| Topic | SPA (React 19) | Next.js App Router |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| **Where components run** | Client only | Must be in **Client Components** (`"use client"`) |
|
|
19
|
+
| **Server Components** | N/A | Default in `app/` — cannot call `defineCustomElements()` |
|
|
20
|
+
| **SSR output** | N/A | Often empty custom-element tags until client hydrates |
|
|
21
|
+
| **`layout={object}` on CE** | Works on React 19+ | **Unreliable** — use **`JSON.stringify(layout)`** |
|
|
22
|
+
| **Auth state** | `signed-in` prop | Set in client wrapper from session hook / context |
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
| Requirement | Notes |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| **Next.js 13+** | App Router (`app/layout.tsx`) |
|
|
29
|
+
| **`transpilePackages`** | Next must compile `weg-shared-layout` |
|
|
30
|
+
| **`suppressHydrationWarning`** | Recommended on both tags (SSR placeholder vs client shadow DOM) |
|
|
31
|
+
| **TypeScript** | `resolveJsonModule` if importing `weg-shared-layout/weg21-*.json` on the server |
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm i weg-shared-layout
|
|
37
|
+
# or
|
|
38
|
+
pnpm add weg-shared-layout
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 1. Transpile the package
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// next.config.ts
|
|
45
|
+
import type { NextConfig } from 'next';
|
|
46
|
+
|
|
47
|
+
const nextConfig: NextConfig = {
|
|
48
|
+
transpilePackages: ['weg-shared-layout'],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default nextConfig;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 2. Sign-out handling
|
|
55
|
+
|
|
56
|
+
Sign-in comes from your CMS `header.menu` like any other link. Handle sign-out via **`wegAuthClick`** — call your logout API, then redirect (e.g. to `/`).
|
|
57
|
+
|
|
58
|
+
## 3. Client layout components
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// src/components/layout/Header.tsx
|
|
62
|
+
'use client';
|
|
63
|
+
|
|
64
|
+
import { useCallback } from 'react';
|
|
65
|
+
import { usePathname } from 'next/navigation';
|
|
66
|
+
import { defineCustomElements } from 'weg-shared-layout/loader';
|
|
67
|
+
import 'weg-shared-layout/weg-header';
|
|
68
|
+
|
|
69
|
+
defineCustomElements();
|
|
70
|
+
|
|
71
|
+
import type { LayoutData } from 'weg-shared-layout/layout-data';
|
|
72
|
+
// Or: import { dummyWeg21LayoutData } from 'weg-shared-layout/menus';
|
|
73
|
+
// type LayoutData = typeof layoutFixture;
|
|
74
|
+
|
|
75
|
+
export function Header({
|
|
76
|
+
layout,
|
|
77
|
+
signedIn,
|
|
78
|
+
userName,
|
|
79
|
+
onSignedInChange,
|
|
80
|
+
}: {
|
|
81
|
+
layout: LayoutData;
|
|
82
|
+
signedIn: boolean;
|
|
83
|
+
userName?: string;
|
|
84
|
+
onSignedInChange?: (signedIn: boolean) => void;
|
|
85
|
+
}) {
|
|
86
|
+
const pathname = usePathname();
|
|
87
|
+
|
|
88
|
+
const onAuthClick = useCallback(
|
|
89
|
+
async (event: CustomEvent<{ action: 'sign-in' | 'sign-out' }>) => {
|
|
90
|
+
if (event.detail.action !== 'sign-out') return;
|
|
91
|
+
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
await logout();
|
|
94
|
+
onSignedInChange?.(false);
|
|
95
|
+
window.location.assign('/');
|
|
96
|
+
},
|
|
97
|
+
[onSignedInChange],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<weg-header
|
|
102
|
+
layout={JSON.stringify(layout)}
|
|
103
|
+
signedIn={signedIn}
|
|
104
|
+
currentPath={pathname}
|
|
105
|
+
{...(userName ? { userName } : {})}
|
|
106
|
+
suppressHydrationWarning
|
|
107
|
+
onWegAuthClick={onAuthClick}
|
|
108
|
+
/>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// src/components/layout/Footer.tsx
|
|
115
|
+
'use client';
|
|
116
|
+
|
|
117
|
+
import { defineCustomElements } from 'weg-shared-layout/loader';
|
|
118
|
+
import 'weg-shared-layout/weg-footer';
|
|
119
|
+
|
|
120
|
+
defineCustomElements();
|
|
121
|
+
|
|
122
|
+
export function Footer({ layout }: { layout: unknown }) {
|
|
123
|
+
return <weg-footer layout={JSON.stringify(layout)} suppressHydrationWarning />;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
// src/components/layout/SiteChrome.tsx — optional wrapper
|
|
129
|
+
'use client';
|
|
130
|
+
|
|
131
|
+
import { useState } from 'react';
|
|
132
|
+
import { Header } from './Header';
|
|
133
|
+
import { Footer } from './Footer';
|
|
134
|
+
|
|
135
|
+
export function SiteChrome({
|
|
136
|
+
layout,
|
|
137
|
+
children,
|
|
138
|
+
}: {
|
|
139
|
+
layout: unknown;
|
|
140
|
+
children: React.ReactNode;
|
|
141
|
+
}) {
|
|
142
|
+
const [signedIn, setSignedIn] = useState(false);
|
|
143
|
+
const userName = signedIn ? 'Alex' : undefined; // from your session
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
<Header
|
|
148
|
+
layout={layout}
|
|
149
|
+
signedIn={signedIn}
|
|
150
|
+
userName={userName}
|
|
151
|
+
onSignedInChange={setSignedIn}
|
|
152
|
+
/>
|
|
153
|
+
{children}
|
|
154
|
+
<Footer layout={layout} />
|
|
155
|
+
</>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Why `JSON.stringify` and `suppressHydrationWarning`
|
|
161
|
+
|
|
162
|
+
| Line | Purpose |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `layout={JSON.stringify(layout)}` | Serializable across RSC → client; component parses JSON |
|
|
165
|
+
| `suppressHydrationWarning` | Server renders empty tag; client fills shadow DOM |
|
|
166
|
+
| `'use client'` | Registration and auth handlers need the browser |
|
|
167
|
+
|
|
168
|
+
Do **not** pass `layout={layoutObject}` directly on the custom elements in Next.
|
|
169
|
+
|
|
170
|
+
## 4. Server layout
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// app/layout.tsx (Server Component)
|
|
174
|
+
import { SiteChrome } from '@/components/layout/SiteChrome';
|
|
175
|
+
import { dummyWeg21LayoutData } from 'weg-shared-layout/menus';
|
|
176
|
+
const layout = dummyWeg21LayoutData();
|
|
177
|
+
|
|
178
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
179
|
+
return (
|
|
180
|
+
<html lang="en">
|
|
181
|
+
<body>
|
|
182
|
+
<SiteChrome layout={layout}>{children}</SiteChrome>
|
|
183
|
+
</body>
|
|
184
|
+
</html>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 5. Production: fetch layout on the server
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
// app/layout.tsx
|
|
193
|
+
import { SiteChrome } from '@/components/layout/SiteChrome';
|
|
194
|
+
|
|
195
|
+
const WEG21_API = 'https://warwickemploymentgroup.com/api/v1/weg21';
|
|
196
|
+
|
|
197
|
+
async function getLayout() {
|
|
198
|
+
const res = await fetch(LAYOUT_URL, { next: { revalidate: 60 } });
|
|
199
|
+
if (!res.ok) throw new Error('Failed to load layout');
|
|
200
|
+
return res.json();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
204
|
+
const layout = await getLayout();
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<html lang="en">
|
|
208
|
+
<body>
|
|
209
|
+
<SiteChrome layout={layout}>{children}</SiteChrome>
|
|
210
|
+
</body>
|
|
211
|
+
</html>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Pass the plain object into the Client Component; **stringify inside** the header/footer wrappers.
|
|
217
|
+
|
|
218
|
+
Wire **`signedIn`** and **`userName`** from your auth provider (session hook / context in a client wrapper). Pass **`currentPath`** from `usePathname()` (and `useSearchParams()` when dropdown links use query strings).
|
|
219
|
+
|
|
220
|
+
## Nav active state
|
|
221
|
+
|
|
222
|
+
Pass **`current-path`** from the client wrapper. Flat links use prefix matching; dropdown children use exact pathname (+ query when the href includes `?`). Dropdown parents keep the active background when a child matches. Active styling uses **`weg-purple-200`** (`#CDCFF8`).
|
|
223
|
+
|
|
224
|
+
## Header auth reference
|
|
225
|
+
|
|
226
|
+
| API | Signed out | Signed in |
|
|
227
|
+
| --- | --- | --- |
|
|
228
|
+
| `layout.header.menu` | CMS nav rendered (groups + flat links incl. sign-in) | Ignored — built-in nav used |
|
|
229
|
+
| `layout.header.logoSrc` | Logo image URL (bundled if omitted) | Built-in bundled logo |
|
|
230
|
+
| `layout.header.logoHref` | Logo link URL | WEG homepage |
|
|
231
|
+
| `signed-in` prop | `false` | `true` — session flag from host app |
|
|
232
|
+
| `user-name` prop | — | User's first name on Manage Account |
|
|
233
|
+
| `account-base-url` prop | — | Account portal origin for signed-in links (optional) |
|
|
234
|
+
| `current-path` prop | Current pathname (optional query) | Highlights the active nav link; e.g. `/career-advice/my-article` |
|
|
235
|
+
| `wegAuthClick` event | Sign-in follows link `href` | `'sign-out'` — host handles logout and redirect |
|
|
236
|
+
| `event.preventDefault()` | Skip default navigation | Required — sign-out has no built-in redirect |
|
|
237
|
+
|
|
238
|
+
**Signed-in nav (built into the component):** Find a job, Dashboard, Manage Account, Sign out.
|
|
239
|
+
|
|
240
|
+
The logo **image** uses `layout.header.logoSrc` when signed out (bundled if omitted). The logo **link** uses `layout.header.logoHref` when provided; otherwise it defaults to the WEG homepage.
|
|
241
|
+
|
|
242
|
+
## Passing `layout` — quick reference
|
|
243
|
+
|
|
244
|
+
| Approach | Works in Next? |
|
|
245
|
+
| --- | --- |
|
|
246
|
+
| `layout={object}` on `<weg-header>` / `<weg-footer>` | **No** |
|
|
247
|
+
| `layout={JSON.stringify(obj)}` in client wrapper | **Yes** (recommended) |
|
|
248
|
+
| Pass object to `<SiteChrome layout={...} />`, stringify in child | **Yes** |
|
|
249
|
+
| `ref` + `el.layout = obj` in `useEffect` | Yes (escape hatch) |
|
|
250
|
+
|
|
251
|
+
## TypeScript
|
|
252
|
+
|
|
253
|
+
### Server / client JSON imports
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"compilerOptions": {
|
|
258
|
+
"resolveJsonModule": true,
|
|
259
|
+
"moduleResolution": "bundler"
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### JSX module augmentation
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
// src/types/weg-shared-layout-jsx.d.ts
|
|
268
|
+
import type { JSX as WegSharedLayoutJSX } from 'weg-shared-layout';
|
|
269
|
+
import type { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
270
|
+
|
|
271
|
+
declare module 'react' {
|
|
272
|
+
namespace JSX {
|
|
273
|
+
interface IntrinsicElements extends WegSharedLayoutJSX.IntrinsicElements {
|
|
274
|
+
'weg-header': WegSharedLayoutJSX.IntrinsicElements['weg-header'] &
|
|
275
|
+
DetailedHTMLProps<HTMLAttributes<HTMLWegHeaderElement>, HTMLWegHeaderElement>;
|
|
276
|
+
'weg-footer': WegSharedLayoutJSX.IntrinsicElements['weg-footer'] &
|
|
277
|
+
DetailedHTMLProps<HTMLAttributes<HTMLWegFooterElement>, HTMLWegFooterElement>;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Troubleshooting
|
|
284
|
+
|
|
285
|
+
| Symptom | Likely cause | Fix |
|
|
286
|
+
| --- | --- | --- |
|
|
287
|
+
| `document is not defined` | Loader in Server Component | Keep registration in `"use client"` files only. |
|
|
288
|
+
| Empty after hydration | Object passed to CE without stringify | `JSON.stringify` in client wrapper. |
|
|
289
|
+
| Logo missing | Stale package | Logo is inlined in `logo-data.ts`; rebuild / upgrade. |
|
|
290
|
+
| Header not clickable | Overlapping page content | `:host` uses `z-index: 20`; upgrade package if missing. |
|
|
291
|
+
| Auth not updating | `signed-in` only on server | Manage session in client state / context. |
|
|
292
|
+
| Nav never highlights | `current-path` not passed | Pass `usePathname()` (+ search params when needed) from client wrapper. |
|
|
293
|
+
| Hydration warning | Shadow DOM mismatch | `suppressHydrationWarning` on both tags. |
|
|
294
|
+
| Build error | Package not transpiled | `transpilePackages: ['weg-shared-layout']`. |
|
|
295
|
+
|
|
296
|
+
## Integration checklist
|
|
297
|
+
|
|
298
|
+
- [ ] `weg-shared-layout` installed
|
|
299
|
+
- [ ] `transpilePackages` in `next.config`
|
|
300
|
+
- [ ] Client `Header.tsx` / `Footer.tsx` with loader, tag imports, `JSON.stringify`, `suppressHydrationWarning`
|
|
301
|
+
- [ ] Auth: `signed-in`, `user-name`, `account-base-url` (optional), `current-path` from `usePathname()`, and `wegAuthClick` handler on header
|
|
302
|
+
- [ ] Server `layout.tsx` imports client chrome only (no `defineCustomElements` on server)
|
|
303
|
+
- [ ] Layout fetched or imported server-side, passed as serializable props
|
|
304
|
+
- [ ] TypeScript augmentation for `'weg-header'` and `'weg-footer'`
|
|
305
|
+
- [ ] Verified in browser after hydration
|
|
306
|
+
|
|
307
|
+
## See also
|
|
308
|
+
|
|
309
|
+
- **[Documentation index](./README.md)**
|
|
310
|
+
- **[React SPA](./react.md)**
|
|
311
|
+
- **[Angular](./angular.md)** · **[Angular 16 demo](../demo/angular16/README.md)**
|
|
312
|
+
- **[Plain HTML / vanilla JS](./vanilla.md)**
|
|
313
|
+
- **[Package readme](../readme.md)**
|