weg-shared-layout 0.0.39 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/LICENSE +21 -21
  2. package/dist/cjs/weg-footer_2.cjs.entry.js +9 -10
  3. package/dist/collection/components/weg-footer/icons/instagram.svg +5 -5
  4. package/dist/collection/components/weg-footer/icons/linkedin.svg +3 -3
  5. package/dist/collection/components/weg-footer/icons/tiktok.svg +3 -3
  6. package/dist/collection/components/weg-footer/icons/youtube.svg +4 -4
  7. package/dist/collection/components/weg-footer/weg-footer.css +263 -263
  8. package/dist/collection/components/weg-footer/weg-footer.js +2 -2
  9. package/dist/collection/components/weg-header/weg-header.css +483 -483
  10. package/dist/collection/components/weg-header/weg-header.js +9 -10
  11. package/dist/collection/styles/shared.css +103 -103
  12. package/dist/components/weg-footer.js +1 -1
  13. package/dist/components/weg-header.js +1 -1
  14. package/dist/esm/weg-footer_2.entry.js +9 -10
  15. package/dist/weg-shared-layout/p-6f83b24a.entry.js +1 -0
  16. package/dist/weg-shared-layout/weg-shared-layout.esm.js +1 -1
  17. package/docs/README.md +49 -49
  18. package/docs/angular.md +400 -395
  19. package/docs/nextjs.md +302 -307
  20. package/docs/publishing.md +65 -65
  21. package/docs/react.md +257 -262
  22. package/docs/vanilla.md +124 -125
  23. package/package.json +81 -81
  24. package/readme.md +97 -96
  25. package/src/constants/breakpoints.ts +7 -7
  26. package/src/styles/container.css +60 -60
  27. package/dist/weg-shared-layout/p-18d9facc.entry.js +0 -1
  28. /package/dist/types/Users/{rakin/Documents/WEG/repos → tan/Documents/code}/weg-shared-layout/.stencil/test-utils/axe.d.ts +0 -0
  29. /package/dist/types/Users/{rakin/Documents/WEG/repos → tan/Documents/code}/weg-shared-layout/.stencil/test-utils/viewport.d.ts +0 -0
package/docs/angular.md CHANGED
@@ -1,395 +1,400 @@
1
- # Angular 16 integration
2
-
3
- Step-by-step guide for **Angular 16** (standalone or NgModule). Uses plain component properties — no signals.
4
-
5
- Runnable reference: **[demo/angular16](../demo/angular16/README.md)** (`npm run demo:angular16` from repo root).
6
-
7
- Newer Angular (signals): [weg-angular-demo](https://github.com/jobsac/weg-angular-demo) — same layout ideas, different syntax.
8
-
9
- ---
10
-
11
- ## Checklist
12
-
13
- Copy this while integrating:
14
-
15
- - [ ] **Step 1** — `npm i weg-shared-layout`
16
- - [ ] **Step 2** — `tsconfig`: `skipLibCheck`, `resolveJsonModule`, `moduleResolution`
17
- - [ ] **Step 3** — `defineCustomElements()` in `main.ts` **before** bootstrap
18
- - [ ] **Step 4** — `layout.types.ts` from package fixture
19
- - [ ] **Step 5** — `auth.ts` with sign-out URL (optional; only if handling `wegAuthClick`)
20
- - [ ] **Step 6** — `CUSTOM_ELEMENTS_SCHEMA` on the shell component
21
- - [ ] **Step 7** — Template with **`[layout]`** property binding
22
- - [ ] **Step 8** — Header: `[signedIn]`, `[userName]`, `[accountBaseUrl]` (optional), `(wegAuthClick)`
23
- - [ ] **Step 9** — Verify in DevTools: `$0.layout` is an object on `<weg-header>`
24
-
25
- ---
26
-
27
- ## Step 1 — Install
28
-
29
- ```bash
30
- npm i weg-shared-layout
31
- ```
32
-
33
- ---
34
-
35
- ## Step 2 — TypeScript config
36
-
37
- In **`tsconfig.json`** and/or **`tsconfig.app.json`**:
38
-
39
- ```json
40
- {
41
- "compilerOptions": {
42
- "skipLibCheck": true,
43
- "resolveJsonModule": true,
44
- "moduleResolution": "bundler"
45
- },
46
- "include": ["src/**/*.ts"]
47
- }
48
- ```
49
-
50
- | Option | Why |
51
- | --- | --- |
52
- | `skipLibCheck` | Skips Stencil `stencil-public-runtime.d.ts` (TS 5 syntax) when on TS 4.9 |
53
- | `resolveJsonModule` | Allows `import … from 'weg-shared-layout/dummy-data.json'` |
54
- | `moduleResolution: "bundler"` | Resolves package `exports` (`loader`, `dummy-data.json`) |
55
-
56
- Use **`node16`** instead of **`bundler`** if you must stay on TypeScript 4.9. Classic **`node`** does not resolve subpaths reliably.
57
-
58
- ---
59
-
60
- ## Step 3 — Register custom elements
61
-
62
- Call the Stencil loader in **`src/main.ts`** once, **before** bootstrap:
63
-
64
- ```ts
65
- import { bootstrapApplication } from '@angular/platform-browser';
66
- import { defineCustomElements } from 'weg-shared-layout/loader';
67
-
68
- import { AppComponent } from './app/app.component';
69
- import { appConfig } from './app/app.config';
70
-
71
- defineCustomElements();
72
-
73
- bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
74
- ```
75
-
76
- If header/footer tags appear but nav is empty, registration did not run or ran too late.
77
-
78
- **NgModule apps:** same `defineCustomElements()` call, then `platformBrowserDynamic().bootstrapModule(AppModule)`.
79
-
80
- ---
81
-
82
- ## Step 4 — Layout types
83
-
84
- ```ts
85
- // src/app/layout.types.ts
86
- import layoutFixture from 'weg-shared-layout/dummy-data.json';
87
-
88
- export type LayoutData = typeof layoutFixture;
89
- ```
90
-
91
- Alternative (types only, no JSON import):
92
-
93
- ```ts
94
- import type { LayoutData } from 'weg-shared-layout/layout-data';
95
- ```
96
-
97
- ---
98
-
99
- ## Step 5 — Sign-out URL (optional)
100
-
101
- Sign-in is a normal nav link in your CMS layout — no host constants needed. Only define a sign-out redirect if you handle `wegAuthClick`:
102
-
103
- ```ts
104
- // src/app/auth.ts
105
- export const ACCOUNT_SIGN_OUT_HREF = 'https://account.warwickemploymentgroup.com/account/login';
106
- ```
107
-
108
- ---
109
-
110
- ## Step 6 — Shell component
111
-
112
- Add `CUSTOM_ELEMENTS_SCHEMA` to the component whose template contains `<weg-header>` / `<weg-footer>`. It does **not** cascade to routed children.
113
-
114
- ```ts
115
- // src/app/app.component.ts
116
- import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
117
- import { RouterOutlet } from '@angular/router';
118
- import layoutFixture from 'weg-shared-layout/dummy-data.json';
119
-
120
- import { ACCOUNT_SIGN_OUT_HREF } from './auth';
121
- import type { LayoutData } from './layout.types';
122
-
123
- @Component({
124
- selector: 'app-root',
125
- standalone: true,
126
- imports: [RouterOutlet],
127
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
128
- templateUrl: './app.component.html',
129
- })
130
- export class AppComponent {
131
- layoutData: LayoutData = layoutFixture;
132
-
133
- signedIn = false;
134
- userName?: string;
135
-
136
- onAuthClick(event: Event): void {
137
- const customEvent = event as CustomEvent<{ action: 'sign-in' | 'sign-out' }>;
138
- if (customEvent.detail.action !== 'sign-out') return;
139
-
140
- customEvent.preventDefault();
141
- this.signedIn = false;
142
- window.location.href = ACCOUNT_SIGN_OUT_HREF;
143
- }
144
- }
145
- ```
146
-
147
- ---
148
-
149
- ## Step 7 — Template
150
-
151
- Use **`[layout]="..."`** so Angular sets the JavaScript property, not an HTML attribute.
152
-
153
- ```html
154
- <!-- src/app/app.component.html -->
155
- <weg-header
156
- [layout]="layoutData"
157
- [signedIn]="signedIn"
158
- [userName]="userName"
159
- (wegAuthClick)="onAuthClick($event)"
160
- ></weg-header>
161
-
162
- <main>
163
- <router-outlet></router-outlet>
164
- </main>
165
-
166
- <weg-footer [layout]="layoutData"></weg-footer>
167
- ```
168
-
169
- | Binding | Purpose |
170
- | --- | --- |
171
- | `[layout]` | CMS nav when signed out; same object for header and footer |
172
- | `[signedIn]` | `true` → built-in signed-in nav (ignores CMS `header.menu`) |
173
- | `[userName]` | First name on Manage Account when signed in |
174
- | `[accountBaseUrl]` | Account portal origin for signed-in links (optional; production default when omitted) |
175
- | `(wegAuthClick)` | Optional — handle `sign-out` only; sign-in follows the link `href` |
176
-
177
- Run the app — you should see header nav and footer.
178
-
179
- ---
180
-
181
- ## Step 8 — Load layout from your API (optional)
182
-
183
- When ready, replace the static fixture with HTTP. Register `provideHttpClient()` in `app.config.ts`.
184
-
185
- **`src/app/layout.service.ts`:**
186
-
187
- ```ts
188
- import { Injectable } from '@angular/core';
189
- import { HttpClient } from '@angular/common/http';
190
- import { map, Observable } from 'rxjs';
191
-
192
- import type { LayoutData } from './layout.types';
193
-
194
- const DEFAULT_SIGN_IN = {
195
- label: 'Sign in',
196
- href: 'https://account.warwickemploymentgroup.com/account/login',
197
- };
198
-
199
- @Injectable({ providedIn: 'root' })
200
- export class LayoutService {
201
- constructor(private http: HttpClient) {}
202
-
203
- loadLayout(): Observable<LayoutData> {
204
- return this.http.get<LayoutData>('/api/layout').pipe(
205
- map((data) => this.ensureSignInMenu(data)),
206
- );
207
- }
208
-
209
- private ensureSignInMenu(data: LayoutData): LayoutData {
210
- const menu = data.header?.menu ?? [];
211
- const hasSignIn = menu.some(
212
- (item) =>
213
- !item.items?.length &&
214
- item.label?.trim().toLowerCase() === 'sign in',
215
- );
216
- if (hasSignIn) return data;
217
- return {
218
- ...data,
219
- header: { ...data.header, menu: [...menu, DEFAULT_SIGN_IN] },
220
- };
221
- }
222
- }
223
- ```
224
-
225
- **`src/app/app.component.ts`** (HTTP variant):
226
-
227
- ```ts
228
- import { AsyncPipe } from '@angular/common';
229
- import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
230
- import { RouterOutlet } from '@angular/router';
231
- import { Observable } from 'rxjs';
232
-
233
- import { ACCOUNT_SIGN_OUT_HREF } from './auth';
234
- import type { LayoutData } from './layout.types';
235
- import { LayoutService } from './layout.service';
236
-
237
- @Component({
238
- selector: 'app-root',
239
- standalone: true,
240
- imports: [AsyncPipe, RouterOutlet],
241
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
242
- templateUrl: './app.component.html',
243
- })
244
- export class AppComponent {
245
- layout$: Observable<LayoutData>;
246
- signedIn = false;
247
- userName?: string;
248
-
249
- constructor(layoutService: LayoutService) {
250
- this.layout$ = layoutService.loadLayout();
251
- }
252
-
253
- onAuthClick(event: Event): void {
254
- const customEvent = event as CustomEvent<{ action: 'sign-in' | 'sign-out' }>;
255
- if (customEvent.detail.action !== 'sign-out') return;
256
-
257
- customEvent.preventDefault();
258
- this.signedIn = false;
259
- window.location.href = ACCOUNT_SIGN_OUT_HREF;
260
- }
261
- }
262
- ```
263
-
264
- **`src/app/app.component.html`** (Angular 16 — use `*ngIf`, not `@if`):
265
-
266
- ```html
267
- <ng-container *ngIf="layout$ | async as layout">
268
- <weg-header
269
- [layout]="layout"
270
- [signedIn]="signedIn"
271
- [userName]="userName"
272
- (wegAuthClick)="onAuthClick($event)"
273
- ></weg-header>
274
-
275
- <main>
276
- <router-outlet></router-outlet>
277
- </main>
278
-
279
- <weg-footer [layout]="layout"></weg-footer>
280
- </ng-container>
281
- ```
282
-
283
- Add `CommonModule` or `NgIf` + `AsyncPipe` to `imports` if not already present.
284
-
285
- ---
286
-
287
- ## Step 9 — Verify
288
-
289
- 1. Open DevTools → Elements → select `<weg-header>`.
290
- 2. In the console:
291
-
292
- ```js
293
- $0.layout // must be a plain object, not undefined
294
- $0.signedIn // boolean
295
- $0.userName // string or undefined
296
- ```
297
-
298
- If `layout` looks like `"[object Object]"`, you used attribute binding instead of `[layout]`.
299
-
300
- ---
301
-
302
- ## Layout shape
303
-
304
- Both tags accept one **`layout`** object with `header` and `footer` keys — **not** a bare array at the top level.
305
-
306
- Full reference: [`dummy-data.json`](../src/assets/dummy-data.json) or [`GET /api/layout`](https://weg-payload-test.vercel.app/api/layout).
307
-
308
- ```json
309
- {
310
- "header": {
311
- "menu": [
312
- { "label": "Find a job", "items": [{ "label": "Graduates", "href": "…" }] },
313
- { "label": "Career advice", "href": "…" },
314
- { "label": "Sign in", "href": "…" }
315
- ]
316
- },
317
- "footer": {
318
- "menu": [[{ "label": "About WEG", "href": "…" }]],
319
- "social": [{ "platform": "LinkedIn", "href": "…" }],
320
- "credits": "…",
321
- "copyright": "…"
322
- }
323
- }
324
- ```
325
-
326
- ### API returns separate arrays?
327
-
328
- If your API does not return one `{ header, footer }` object, merge in a service:
329
-
330
- ```ts
331
- forkJoin({
332
- menuGroups: this.http.get<LayoutData['header']>('/api/nav/menu'),
333
- flatLinks: this.http.get<LayoutData['header']>('/api/nav/links'),
334
- footer: this.http.get<LayoutData['footer']>('/api/footer'),
335
- }).pipe(
336
- map(({ menuGroups, flatLinks, footer }) => ({
337
- header: { menu: [...menuGroups, ...flatLinks, DEFAULT_SIGN_IN] },
338
- footer,
339
- })),
340
- );
341
- ```
342
-
343
- ---
344
-
345
- ## Troubleshooting
346
-
347
- | Symptom | Fix |
348
- | --- | --- |
349
- | `'weg-header'` is not a known element | `CUSTOM_ELEMENTS_SCHEMA` on the component that owns the template |
350
- | Header/footer empty | `defineCustomElements()` before bootstrap |
351
- | Nav empty after passing data | `[layout]` must be `{ header, footer }`, not a bare array |
352
- | Auth always Sign in | Bind `[signedIn]` from session state |
353
- | Generic Manage Account | Pass `[userName]` when signed in |
354
- | Staging account portal | Pass `[accountBaseUrl]` (e.g. `https://account-staging.example.com`) |
355
- | `Mixin<const …>` / TS1139 in `stencil-public-runtime.d.ts` | Add `skipLibCheck: true` and/or upgrade to TypeScript 5.0+ |
356
- | Cannot resolve `weg-shared-layout/loader` | Set `moduleResolution` to `bundler` or `node16` |
357
- | SSR: `document is not defined` | Guard: `if (typeof window !== 'undefined') defineCustomElements()` |
358
-
359
- ### TypeScript 4.9 + Stencil errors
360
-
361
- Stencil ships `.d.ts` files with TypeScript 5 syntax. On Angular 16 with TS 4.9:
362
-
363
- 1. Add **`skipLibCheck: true`** (recommended), or
364
- 2. Upgrade to **TypeScript 5.0–5.1** (supported by Angular 16.1+): `npm i -D typescript@~5.1.6`
365
-
366
- For typing only, avoid pulling Stencil runtime declarations:
367
-
368
- ```ts
369
- import type { LayoutData } from 'weg-shared-layout/layout-data';
370
- // or
371
- export type LayoutData = typeof layoutFixture; // from dummy-data.json
372
- ```
373
-
374
- ---
375
-
376
- ## File map
377
-
378
- | File | Role |
379
- | --- | --- |
380
- | `src/main.ts` | `defineCustomElements()` then bootstrap |
381
- | `tsconfig.json` | `skipLibCheck`, `resolveJsonModule`, `moduleResolution` |
382
- | `src/app/layout.types.ts` | `export type LayoutData = typeof layoutFixture` |
383
- | `src/app/auth.ts` | `ACCOUNT_SIGN_OUT_HREF` (optional) |
384
- | `src/app/layout.service.ts` | HTTP / API mapping (optional) |
385
- | `src/app/app.component.ts` | Shell: schema, layout, auth handler |
386
- | `src/app/app.component.html` | `<weg-header>`, `<router-outlet>`, `<weg-footer>` |
387
-
388
- ---
389
-
390
- ## See also
391
-
392
- - [Documentation index](./README.md)
393
- - [In-repo Angular 16 demo](../demo/angular16/README.md)
394
- - [Package readme](../readme.md)
395
- - [React SPA](./react.md) · [Next.js](./nextjs.md) · [Vanilla JS](./vanilla.md)
1
+ # Angular 16 integration
2
+
3
+ Step-by-step guide for **Angular 16** (standalone or NgModule). Uses plain component properties — no signals.
4
+
5
+ Runnable reference: **[demo/angular16](../demo/angular16/README.md)** (`npm run demo:angular16` from repo root).
6
+
7
+ Newer Angular (signals): [weg-angular-demo](https://github.com/jobsac/weg-angular-demo) — same layout ideas, different syntax.
8
+
9
+ ---
10
+
11
+ ## Checklist
12
+
13
+ Copy this while integrating:
14
+
15
+ - [ ] **Step 1** — `npm i weg-shared-layout`
16
+ - [ ] **Step 2** — `tsconfig`: `skipLibCheck`, `resolveJsonModule`, `moduleResolution`
17
+ - [ ] **Step 3** — `defineCustomElements()` in `main.ts` **before** bootstrap
18
+ - [ ] **Step 4** — `layout.types.ts` from package fixture
19
+ - [ ] **Step 5** — `auth.ts` with sign-out URL (optional; only if handling `wegAuthClick`)
20
+ - [ ] **Step 6** — `CUSTOM_ELEMENTS_SCHEMA` on the shell component
21
+ - [ ] **Step 7** — Template with **`[layout]`** property binding
22
+ - [ ] **Step 8** — Header: `[signedIn]`, `[userName]`, `[accountBaseUrl]` (optional), `(wegAuthClick)`
23
+ - [ ] **Step 9** — Verify in DevTools: `$0.layout` is an object on `<weg-header>`
24
+
25
+ ---
26
+
27
+ ## Step 1 — Install
28
+
29
+ ```bash
30
+ npm i weg-shared-layout
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Step 2 — TypeScript config
36
+
37
+ In **`tsconfig.json`** and/or **`tsconfig.app.json`**:
38
+
39
+ ```json
40
+ {
41
+ "compilerOptions": {
42
+ "skipLibCheck": true,
43
+ "resolveJsonModule": true,
44
+ "moduleResolution": "bundler"
45
+ },
46
+ "include": ["src/**/*.ts"]
47
+ }
48
+ ```
49
+
50
+ | Option | Why |
51
+ | --- | --- |
52
+ | `skipLibCheck` | Skips Stencil `stencil-public-runtime.d.ts` (TS 5 syntax) when on TS 4.9 |
53
+ | `resolveJsonModule` | Allows `import … from 'weg-shared-layout/dummy-data.json'` |
54
+ | `moduleResolution: "bundler"` | Resolves package `exports` (`loader`, `dummy-data.json`) |
55
+
56
+ Use **`node16`** instead of **`bundler`** if you must stay on TypeScript 4.9. Classic **`node`** does not resolve subpaths reliably.
57
+
58
+ ---
59
+
60
+ ## Step 3 — Register custom elements
61
+
62
+ Call the Stencil loader in **`src/main.ts`** once, **before** bootstrap:
63
+
64
+ ```ts
65
+ import { bootstrapApplication } from '@angular/platform-browser';
66
+ import { defineCustomElements } from 'weg-shared-layout/loader';
67
+
68
+ import { AppComponent } from './app/app.component';
69
+ import { appConfig } from './app/app.config';
70
+
71
+ defineCustomElements();
72
+
73
+ bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
74
+ ```
75
+
76
+ If header/footer tags appear but nav is empty, registration did not run or ran too late.
77
+
78
+ **NgModule apps:** same `defineCustomElements()` call, then `platformBrowserDynamic().bootstrapModule(AppModule)`.
79
+
80
+ ---
81
+
82
+ ## Step 4 — Layout types
83
+
84
+ ```ts
85
+ // src/app/layout.types.ts
86
+ import layoutFixture from 'weg-shared-layout/dummy-data.json';
87
+
88
+ export type LayoutData = typeof layoutFixture;
89
+ ```
90
+
91
+ Alternative (types only, no JSON import):
92
+
93
+ ```ts
94
+ import type { LayoutData } from 'weg-shared-layout/layout-data';
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Step 5 — Sign-out handling
100
+
101
+ Sign-in is a normal nav link in your CMS layout. Handle sign-out via **`wegAuthClick`** — call your logout API, then redirect (e.g. to `/`).
102
+
103
+ ---
104
+
105
+ ## Step 6 — Shell component
106
+
107
+ Add `CUSTOM_ELEMENTS_SCHEMA` to the component whose template contains `<weg-header>` / `<weg-footer>`. It does **not** cascade to routed children.
108
+
109
+ ```ts
110
+ // src/app/app.component.ts
111
+ import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
112
+ import { RouterOutlet } from '@angular/router';
113
+ import layoutFixture from 'weg-shared-layout/dummy-data.json';
114
+
115
+ import type { LayoutData } from './layout.types';
116
+
117
+ @Component({
118
+ selector: 'app-root',
119
+ standalone: true,
120
+ imports: [RouterOutlet],
121
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
122
+ templateUrl: './app.component.html',
123
+ })
124
+ export class AppComponent {
125
+ layoutData: LayoutData = layoutFixture;
126
+
127
+ signedIn = false;
128
+ userName?: string;
129
+
130
+ onAuthClick(event: Event): void {
131
+ const customEvent = event as CustomEvent<{ action: 'sign-in' | 'sign-out' }>;
132
+ if (customEvent.detail.action !== 'sign-out') return;
133
+
134
+ customEvent.preventDefault();
135
+ this.signedIn = false;
136
+ void this.logout().then(() => {
137
+ window.location.assign('/');
138
+ });
139
+ }
140
+
141
+ private async logout(): Promise<void> {
142
+ // Call your account API logout endpoint
143
+ }
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Step 7 — Template
150
+
151
+ Use **`[layout]="..."`** so Angular sets the JavaScript property, not an HTML attribute.
152
+
153
+ ```html
154
+ <!-- src/app/app.component.html -->
155
+ <weg-header
156
+ [layout]="layoutData"
157
+ [signedIn]="signedIn"
158
+ [userName]="userName"
159
+ (wegAuthClick)="onAuthClick($event)"
160
+ ></weg-header>
161
+
162
+ <main>
163
+ <router-outlet></router-outlet>
164
+ </main>
165
+
166
+ <weg-footer [layout]="layoutData"></weg-footer>
167
+ ```
168
+
169
+ | Binding | Purpose |
170
+ | --- | --- |
171
+ | `[layout]` | CMS nav when signed out; same object for header and footer |
172
+ | `[signedIn]` | `true` → built-in signed-in nav (ignores CMS `header.menu`) |
173
+ | `[userName]` | First name on Manage Account when signed in |
174
+ | `[accountBaseUrl]` | Account portal origin for signed-in links (optional; production default when omitted) |
175
+ | `(wegAuthClick)` | Required for sign-out — call logout API and redirect; sign-in follows the link `href` |
176
+
177
+ Run the app — you should see header nav and footer.
178
+
179
+ ---
180
+
181
+ ## Step 8 — Load layout from your API (optional)
182
+
183
+ When ready, replace the static fixture with HTTP. Register `provideHttpClient()` in `app.config.ts`.
184
+
185
+ **`src/app/layout.service.ts`:**
186
+
187
+ ```ts
188
+ import { Injectable } from '@angular/core';
189
+ import { HttpClient } from '@angular/common/http';
190
+ import { map, Observable } from 'rxjs';
191
+
192
+ import type { LayoutData } from './layout.types';
193
+
194
+ const DEFAULT_SIGN_IN = {
195
+ label: 'Sign in',
196
+ href: 'https://account.warwickemploymentgroup.com/account/login',
197
+ };
198
+
199
+ @Injectable({ providedIn: 'root' })
200
+ export class LayoutService {
201
+ constructor(private http: HttpClient) {}
202
+
203
+ loadLayout(): Observable<LayoutData> {
204
+ return this.http.get<LayoutData>('/api/layout').pipe(
205
+ map((data) => this.ensureSignInMenu(data)),
206
+ );
207
+ }
208
+
209
+ private ensureSignInMenu(data: LayoutData): LayoutData {
210
+ const menu = data.header?.menu ?? [];
211
+ const hasSignIn = menu.some(
212
+ (item) =>
213
+ !item.items?.length &&
214
+ item.label?.trim().toLowerCase() === 'sign in',
215
+ );
216
+ if (hasSignIn) return data;
217
+ return {
218
+ ...data,
219
+ header: { ...data.header, menu: [...menu, DEFAULT_SIGN_IN] },
220
+ };
221
+ }
222
+ }
223
+ ```
224
+
225
+ **`src/app/app.component.ts`** (HTTP variant):
226
+
227
+ ```ts
228
+ import { AsyncPipe } from '@angular/common';
229
+ import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
230
+ import { RouterOutlet } from '@angular/router';
231
+ import { Observable } from 'rxjs';
232
+
233
+ import type { LayoutData } from './layout.types';
234
+ import { LayoutService } from './layout.service';
235
+
236
+ @Component({
237
+ selector: 'app-root',
238
+ standalone: true,
239
+ imports: [AsyncPipe, RouterOutlet],
240
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
241
+ templateUrl: './app.component.html',
242
+ })
243
+ export class AppComponent {
244
+ layout$: Observable<LayoutData>;
245
+ signedIn = false;
246
+ userName?: string;
247
+
248
+ constructor(layoutService: LayoutService) {
249
+ this.layout$ = layoutService.loadLayout();
250
+ }
251
+
252
+ onAuthClick(event: Event): void {
253
+ const customEvent = event as CustomEvent<{ action: 'sign-in' | 'sign-out' }>;
254
+ if (customEvent.detail.action !== 'sign-out') return;
255
+
256
+ customEvent.preventDefault();
257
+ this.signedIn = false;
258
+ void this.logout().then(() => {
259
+ window.location.assign('/');
260
+ });
261
+ }
262
+
263
+ private async logout(): Promise<void> {
264
+ // Call your account API logout endpoint
265
+ }
266
+ }
267
+ ```
268
+
269
+ **`src/app/app.component.html`** (Angular 16 — use `*ngIf`, not `@if`):
270
+
271
+ ```html
272
+ <ng-container *ngIf="layout$ | async as layout">
273
+ <weg-header
274
+ [layout]="layout"
275
+ [signedIn]="signedIn"
276
+ [userName]="userName"
277
+ (wegAuthClick)="onAuthClick($event)"
278
+ ></weg-header>
279
+
280
+ <main>
281
+ <router-outlet></router-outlet>
282
+ </main>
283
+
284
+ <weg-footer [layout]="layout"></weg-footer>
285
+ </ng-container>
286
+ ```
287
+
288
+ Add `CommonModule` or `NgIf` + `AsyncPipe` to `imports` if not already present.
289
+
290
+ ---
291
+
292
+ ## Step 9 — Verify
293
+
294
+ 1. Open DevTools → Elements → select `<weg-header>`.
295
+ 2. In the console:
296
+
297
+ ```js
298
+ $0.layout // must be a plain object, not undefined
299
+ $0.signedIn // boolean
300
+ $0.userName // string or undefined
301
+ ```
302
+
303
+ If `layout` looks like `"[object Object]"`, you used attribute binding instead of `[layout]`.
304
+
305
+ ---
306
+
307
+ ## Layout shape
308
+
309
+ Both tags accept one **`layout`** object with `header` and `footer` keys — **not** a bare array at the top level.
310
+
311
+ Full reference: [`dummy-data.json`](../src/assets/dummy-data.json) or [`GET /api/layout`](https://weg-payload-test.vercel.app/api/layout).
312
+
313
+ ```json
314
+ {
315
+ "header": {
316
+ "menu": [
317
+ { "label": "Find a job", "items": [{ "label": "Graduates", "href": "…" }] },
318
+ { "label": "Career advice", "href": "…" },
319
+ { "label": "Sign in", "href": "…" }
320
+ ]
321
+ },
322
+ "footer": {
323
+ "menu": [[{ "label": "About WEG", "href": "…" }]],
324
+ "social": [{ "platform": "LinkedIn", "href": "…" }],
325
+ "credits": "…",
326
+ "copyright": "…"
327
+ }
328
+ }
329
+ ```
330
+
331
+ ### API returns separate arrays?
332
+
333
+ If your API does not return one `{ header, footer }` object, merge in a service:
334
+
335
+ ```ts
336
+ forkJoin({
337
+ menuGroups: this.http.get<LayoutData['header']>('/api/nav/menu'),
338
+ flatLinks: this.http.get<LayoutData['header']>('/api/nav/links'),
339
+ footer: this.http.get<LayoutData['footer']>('/api/footer'),
340
+ }).pipe(
341
+ map(({ menuGroups, flatLinks, footer }) => ({
342
+ header: { menu: [...menuGroups, ...flatLinks, DEFAULT_SIGN_IN] },
343
+ footer,
344
+ })),
345
+ );
346
+ ```
347
+
348
+ ---
349
+
350
+ ## Troubleshooting
351
+
352
+ | Symptom | Fix |
353
+ | --- | --- |
354
+ | `'weg-header'` is not a known element | `CUSTOM_ELEMENTS_SCHEMA` on the component that owns the template |
355
+ | Header/footer empty | `defineCustomElements()` before bootstrap |
356
+ | Nav empty after passing data | `[layout]` must be `{ header, footer }`, not a bare array |
357
+ | Auth always Sign in | Bind `[signedIn]` from session state |
358
+ | Generic Manage Account | Pass `[userName]` when signed in |
359
+ | Staging account portal | Pass `[accountBaseUrl]` (e.g. `https://account-staging.example.com`) |
360
+ | `Mixin<const …>` / TS1139 in `stencil-public-runtime.d.ts` | Add `skipLibCheck: true` and/or upgrade to TypeScript 5.0+ |
361
+ | Cannot resolve `weg-shared-layout/loader` | Set `moduleResolution` to `bundler` or `node16` |
362
+ | SSR: `document is not defined` | Guard: `if (typeof window !== 'undefined') defineCustomElements()` |
363
+
364
+ ### TypeScript 4.9 + Stencil errors
365
+
366
+ Stencil ships `.d.ts` files with TypeScript 5 syntax. On Angular 16 with TS 4.9:
367
+
368
+ 1. Add **`skipLibCheck: true`** (recommended), or
369
+ 2. Upgrade to **TypeScript 5.0–5.1** (supported by Angular 16.1+): `npm i -D typescript@~5.1.6`
370
+
371
+ For typing only, avoid pulling Stencil runtime declarations:
372
+
373
+ ```ts
374
+ import type { LayoutData } from 'weg-shared-layout/layout-data';
375
+ // or
376
+ export type LayoutData = typeof layoutFixture; // from dummy-data.json
377
+ ```
378
+
379
+ ---
380
+
381
+ ## File map
382
+
383
+ | File | Role |
384
+ | --- | --- |
385
+ | `src/main.ts` | `defineCustomElements()` then bootstrap |
386
+ | `tsconfig.json` | `skipLibCheck`, `resolveJsonModule`, `moduleResolution` |
387
+ | `src/app/layout.types.ts` | `export type LayoutData = typeof layoutFixture` |
388
+ | `src/app/auth.ts` | `POST_LOGOUT_HREF` (optional) |
389
+ | `src/app/layout.service.ts` | HTTP / API mapping (optional) |
390
+ | `src/app/app.component.ts` | Shell: schema, layout, auth handler |
391
+ | `src/app/app.component.html` | `<weg-header>`, `<router-outlet>`, `<weg-footer>` |
392
+
393
+ ---
394
+
395
+ ## See also
396
+
397
+ - [Documentation index](./README.md)
398
+ - [In-repo Angular 16 demo](../demo/angular16/README.md)
399
+ - [Package readme](../readme.md)
400
+ - [React SPA](./react.md) · [Next.js](./nextjs.md) · [Vanilla JS](./vanilla.md)