weg-shared-layout 0.0.24 → 0.0.26

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 (43) hide show
  1. package/LICENSE +21 -21
  2. package/dist/cjs/weg-footer_2.cjs.entry.js +218 -193
  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 +270 -218
  8. package/dist/collection/components/weg-footer/weg-footer.js +22 -26
  9. package/dist/collection/components/weg-header/weg-header.css +474 -473
  10. package/dist/collection/components/weg-header/weg-header.js +143 -139
  11. package/dist/collection/styles/shared.css +72 -72
  12. package/dist/collection/utils/layout.js +57 -9
  13. package/dist/components/p-BnMIy-A5.js +1 -0
  14. package/dist/components/weg-footer.js +1 -1
  15. package/dist/components/weg-header.js +1 -1
  16. package/dist/esm/weg-footer_2.entry.js +218 -193
  17. package/dist/types/components/weg-footer/weg-footer.d.ts +2 -2
  18. package/dist/types/components/weg-header/weg-header.d.ts +14 -11
  19. package/dist/types/components.d.ts +6 -6
  20. package/dist/types/types/layout-data.d.ts +13 -27
  21. package/dist/types/utils/layout.d.ts +9 -1
  22. package/dist/weg-shared-layout/p-e6eadfa1.entry.js +1 -0
  23. package/dist/weg-shared-layout/weg-shared-layout.esm.js +1 -1
  24. package/docs/angular-16-compatibility.md +235 -0
  25. package/docs/angular-integration-guide.md +1008 -1026
  26. package/docs/angular.md +232 -247
  27. package/docs/nextjs.md +306 -305
  28. package/docs/publishing.md +48 -48
  29. package/docs/react.md +261 -261
  30. package/docs/vanilla.md +138 -140
  31. package/package.json +5 -2
  32. package/readme.md +83 -108
  33. package/src/assets/dummy-data.json +221 -147
  34. package/src/constants/breakpoints.ts +7 -7
  35. package/dist/collection/components/weg-header/signed-in-layout.js +0 -13
  36. package/dist/collection/constants/weg-urls.js +0 -18
  37. package/dist/components/p-DbIEJ3IT.js +0 -1
  38. package/dist/types/components/weg-header/signed-in-layout.d.ts +0 -18
  39. package/dist/types/constants/weg-urls.d.ts +0 -18
  40. package/dist/weg-shared-layout/p-c6ec3888.entry.js +0 -1
  41. package/src/constants/weg-urls.ts +0 -23
  42. /package/dist/types/Users/{rakin/Documents/WEG/repos → tan/Documents/code}/weg-shared-layout/.stencil/test-utils/axe.d.ts +0 -0
  43. /package/dist/types/Users/{rakin/Documents/WEG/repos → tan/Documents/code}/weg-shared-layout/.stencil/test-utils/viewport.d.ts +0 -0
@@ -1,65 +1,54 @@
1
1
  import { h } from "@stencil/core";
2
- import { normalizeLinks, parseJsonProp, isNonEmptyString } from "../../utils/layout";
2
+ import { isMenuGroup, isNonEmptyString, normalizeHeaderMenu, parseJsonProp, } from "../../utils/layout";
3
3
  import { WEG_HOVER_CAPABLE_MEDIA_QUERY, WEG_MD_MEDIA_QUERY } from "../../constants/breakpoints";
4
4
  import { LOGO_SRC } from "./logo-data";
5
- import { DEFAULT_LOGO_HREF, SIGNED_IN_HEADER } from "./signed-in-layout";
5
+ const WEG_HOME = 'https://www.warwickemploymentgroup.com';
6
+ const ACCOUNT_HOME = 'https://account.warwickemploymentgroup.com';
7
+ const DEFAULT_LOGO_HREF = `${WEG_HOME}/`;
8
+ const ACCOUNT_LOGIN_HREF = `${ACCOUNT_HOME}/account/login`;
9
+ const ACCOUNT_MANAGE_HREF = `${ACCOUNT_HOME}/account/manage`;
10
+ const SIGNED_IN_MENU = [
11
+ { label: 'Find a job', href: `${WEG_HOME}/find-a-job` },
12
+ { label: 'Dashboard', href: `${ACCOUNT_HOME}/dashboard` },
13
+ { label: 'Manage Account', href: `${ACCOUNT_HOME}/account/manage` },
14
+ { label: 'Sign out', href: `${ACCOUNT_HOME}/account/login` },
15
+ ];
6
16
  const EMPTY_HEADER = {
7
- logoHref: DEFAULT_LOGO_HREF,
8
- dropdowns: [],
9
- links: [],
10
- signIn: null,
11
- signOut: null,
17
+ menu: [],
12
18
  };
13
- function normalizeDropdowns(input) {
14
- if (!Array.isArray(input))
15
- return [];
16
- const result = [];
17
- for (const item of input) {
18
- if (!item || typeof item !== 'object')
19
- continue;
20
- const label = item.label;
21
- const items = normalizeLinks(item.items);
22
- if (!isNonEmptyString(label))
23
- continue;
24
- if (items.length === 0)
25
- continue;
26
- result.push({ label: label.trim(), items });
27
- }
28
- return result;
19
+ function isSignInLink(link) {
20
+ const label = link.label.trim().toLowerCase();
21
+ if (label === 'sign out')
22
+ return false;
23
+ if (label === 'sign in')
24
+ return true;
25
+ return link.href === ACCOUNT_LOGIN_HREF;
29
26
  }
30
- function normalizeSignIn(input) {
31
- if (!input || typeof input !== 'object')
32
- return null;
33
- const label = input.label;
34
- const href = input.href;
35
- if (!isNonEmptyString(label))
36
- return null;
37
- if (!isNonEmptyString(href))
38
- return null;
39
- return { label: label.trim(), href: href.trim() };
27
+ function isSignOutLink(link) {
28
+ return link.label.trim().toLowerCase() === 'sign out';
40
29
  }
41
- function normalizeSignOut(input) {
42
- if (!input || typeof input !== 'object')
43
- return null;
44
- const label = input.label;
45
- const href = input.href;
46
- if (!isNonEmptyString(label))
47
- return null;
48
- return {
49
- label: label.trim(),
50
- href: isNonEmptyString(href) ? href.trim() : undefined,
51
- };
30
+ function isManageAccountLink(link) {
31
+ if (link.label.trim().toLowerCase() === 'manage account')
32
+ return true;
33
+ return link.href === ACCOUNT_MANAGE_HREF;
34
+ }
35
+ function isFindJobLink(link) {
36
+ if (link.label.trim().toLowerCase() === 'find a job')
37
+ return true;
38
+ return link.href === `${WEG_HOME}/find-a-job`;
39
+ }
40
+ function isDashboardLink(link) {
41
+ if (link.label.trim().toLowerCase() === 'dashboard')
42
+ return true;
43
+ return link.href === `${ACCOUNT_HOME}/dashboard`;
52
44
  }
53
45
  function normalizeHeaderData(input) {
54
46
  const root = (input && typeof input === 'object' ? input : {});
55
47
  const header = root.header && typeof root.header === 'object' ? root.header : {};
56
- const logoHref = isNonEmptyString(header.logoHref) ? header.logoHref.trim() : DEFAULT_LOGO_HREF;
48
+ const logoSrc = isNonEmptyString(header.logoSrc) ? header.logoSrc.trim() : undefined;
57
49
  return {
58
- logoHref,
59
- dropdowns: normalizeDropdowns(header.dropdowns),
60
- links: normalizeLinks(header.links),
61
- signIn: normalizeSignIn(header.signIn),
62
- signOut: normalizeSignOut(header.signOut),
50
+ logoSrc,
51
+ menu: normalizeHeaderMenu(header.menu),
63
52
  };
64
53
  }
65
54
  function SignInIcon() {
@@ -68,6 +57,12 @@ function SignInIcon() {
68
57
  function SignOutIcon() {
69
58
  return (h("svg", { viewBox: "0 0 26 24", width: "26", height: "24", "aria-hidden": "true", focusable: "false", fill: "none" }, h("path", { d: "M19.6667 6.33333L25 11.6667L19.6667 17M25 11.6667H6.33333M14.3333 17V18.3333C14.3333 19.3942 13.9119 20.4116 13.1618 21.1618C12.4116 21.9119 11.3942 22.3333 10.3333 22.3333H5C3.93913 22.3333 2.92172 21.9119 2.17157 21.1618C1.42143 20.4116 1 19.3942 1 18.3333V5C1 3.93913 1.42143 2.92172 2.17157 2.17157C2.92172 1.42143 3.93913 1 5 1H10.3333C11.3942 1 12.4116 1.42143 13.1618 2.17157C13.9119 2.92172 14.3333 3.93913 14.3333 5V6.33333", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" })));
70
59
  }
60
+ function FindJobIcon() {
61
+ return (h("svg", { viewBox: "0 0 24 24", width: "20", height: "20", "aria-hidden": "true", focusable: "false", fill: "none" }, h("path", { d: "M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("rect", { x: "2", y: "6", width: "20", height: "14", rx: "2", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" })));
62
+ }
63
+ function DashboardIcon() {
64
+ return (h("svg", { viewBox: "0 0 24 24", width: "20", height: "20", "aria-hidden": "true", focusable: "false", fill: "none" }, h("rect", { x: "3", y: "3", width: "7", height: "9", rx: "1", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("rect", { x: "14", y: "3", width: "7", height: "5", rx: "1", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("rect", { x: "14", y: "12", width: "7", height: "9", rx: "1", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("rect", { x: "3", y: "16", width: "7", height: "5", rx: "1", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" })));
65
+ }
71
66
  function HamburgerIcon() {
72
67
  return (h("svg", { viewBox: "0 0 24 24", width: "28", height: "28", "aria-hidden": "true", focusable: "false", fill: "none" }, h("path", { d: "M4 7h16M4 12h16M4 17h16", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" })));
73
68
  }
@@ -77,8 +72,8 @@ function CloseIcon() {
77
72
  function ToggleIcon({ expanded }) {
78
73
  return (h("span", { class: "toggle-icon", "aria-hidden": "true" }, expanded ? (h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none" }, h("path", { d: "M6 12h12", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round" }))) : (h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none" }, h("path", { d: "M12 6v12M6 12h12", stroke: "currentColor", "stroke-width": "2.5", "stroke-linecap": "round" })))));
79
74
  }
80
- function Logo({ href }) {
81
- return (h("a", { class: "logo-link", href: href, "aria-label": "Home" }, h("img", { class: "logo", src: LOGO_SRC, alt: "WEG", width: "225", height: "83" })));
75
+ function Logo({ href, src }) {
76
+ return (h("a", { class: "logo-link", href: href, "aria-label": "Home" }, h("img", { class: "logo", src: src, alt: "WEG", width: "225", height: "83" })));
82
77
  }
83
78
  export class WegHeader {
84
79
  /**
@@ -88,19 +83,18 @@ export class WegHeader {
88
83
  * ```json
89
84
  * {
90
85
  * "header": {
91
- * "logoHref": "https://www.warwickemploymentgroup.com/",
92
- * "dropdowns": [{ "label": "Find a job", "items": [{ "label": "...", "href": "..." }] }],
93
- * "links": [{ "label": "Career advice", "href": "/career-advice" }],
94
- * "signIn": { "label": "Sign in", "href": "/account/login" },
95
- * "signOut": { "label": "Sign out", "href": "/account/login" }
86
+ * "menu": [
87
+ * { "label": "Find a job", "items": [{ "label": "Graduates", "href": "..." }] },
88
+ * { "label": "Career advice", "href": "/career-advice" },
89
+ * { "label": "Sign in", "href": "/account/login" }
90
+ * ]
96
91
  * }
97
92
  * }
98
93
  * ```
99
94
  */
100
95
  layout;
101
96
  /**
102
- * When true, the header shows the signed-in navigation (Find a job, Dashboard,
103
- * Manage Account, Sign out) instead of the CMS layout.
97
+ * When true, the header uses the built-in signed-in menu instead of the CMS layout.
104
98
  */
105
99
  signedIn = false;
106
100
  /**
@@ -290,48 +284,55 @@ export class WegHeader {
290
284
  this.menuOpen = false;
291
285
  this.closeDropdown();
292
286
  }
293
- getActiveHeaderData() {
294
- if (!this.signedIn)
295
- return this.resolved;
296
- return {
297
- logoHref: SIGNED_IN_HEADER.logoHref,
298
- dropdowns: [],
299
- links: [...SIGNED_IN_HEADER.links],
300
- signIn: null,
301
- signOut: SIGNED_IN_HEADER.signOut,
302
- };
287
+ getActiveMenu() {
288
+ return this.signedIn ? SIGNED_IN_MENU : this.resolved.menu;
289
+ }
290
+ getLogoSrc() {
291
+ return this.resolved.logoSrc ?? LOGO_SRC;
303
292
  }
304
293
  getLogoHref() {
305
- return this.getActiveHeaderData().logoHref;
294
+ return DEFAULT_LOGO_HREF;
295
+ }
296
+ findSignInLink(menu) {
297
+ for (const item of menu) {
298
+ if (!isMenuGroup(item) && isSignInLink(item))
299
+ return item;
300
+ }
301
+ return null;
306
302
  }
307
303
  getManageAccountLabel() {
308
304
  const name = this.userName?.trim();
309
305
  return name || 'Manage Account';
310
306
  }
311
- getAuthControl() {
312
- if (this.signedIn)
313
- return null;
314
- const signIn = this.resolved.signIn;
315
- if (!signIn)
316
- return null;
317
- return { label: signIn.label, href: signIn.href, action: 'sign-in' };
318
- }
319
- handleAuthClick(event, onNavigate) {
320
- const auth = this.getAuthControl() ?? {
321
- label: SIGNED_IN_HEADER.signOut.label,
322
- href: SIGNED_IN_HEADER.signOut.href,
323
- action: 'sign-out',
324
- };
325
- const emitted = this.wegAuthClick.emit({ action: auth.action });
307
+ renderIconNavLink(link, Icon, className, onNavigate) {
308
+ return (h("li", { class: "main-nav__item main-nav__item--auth", key: link.label }, h("a", { class: { 'sign-in-link': true, [className]: true }, href: link.href, onClick: () => onNavigate?.() }, h(Icon, null), link.label)));
309
+ }
310
+ renderFlatNavLink(link, onNavigate) {
311
+ return (h("li", { class: "main-nav__item", key: link.label }, h("a", { class: "nav-link", href: link.href, onClick: () => onNavigate?.() }, link.label)));
312
+ }
313
+ renderMenuGroup(item, onNavigate) {
314
+ const items = item.items ?? [];
315
+ const isOpen = this.openDropdown === item.label;
316
+ const panelId = this.getDropdownPanelId(item.label);
317
+ return (h("li", { class: "main-nav__item main-nav__item--dropdown", key: item.label }, h("div", { class: "nav-dropdown", onMouseEnter: () => this.handleDropdownPointerEnter(item.label), onMouseLeave: (event) => this.handleDropdownPointerLeave(event), onFocusout: (event) => this.handleDropdownFocusOut(event, item.label) }, h("button", { type: "button", class: {
318
+ 'nav-dropdown__trigger': true,
319
+ 'nav-dropdown__trigger--open': isOpen,
320
+ }, "aria-expanded": isOpen ? 'true' : 'false', "aria-haspopup": "true", "aria-controls": panelId, id: `${panelId}-trigger`, onClick: (event) => this.handleDropdownTriggerClick(event, item.label), onKeyDown: (event) => {
321
+ const container = event.currentTarget.closest('.nav-dropdown');
322
+ this.handleDropdownTriggerKeyDown(event, item.label, container);
323
+ } }, h("span", { class: "nav-dropdown__label" }, item.label), h(ToggleIcon, { expanded: isOpen })), isOpen ? (h("div", { class: "nav-dropdown__panel", id: panelId, role: "region", "aria-labelledby": `${panelId}-trigger`, onMouseEnter: () => this.handleDropdownPanelPointerEnter(item.label), onMouseLeave: (event) => this.handleDropdownPanelPointerLeave(event) }, h("div", { class: "nav-dropdown__accent" }, h("div", { class: "nav-dropdown__links" }, items.map((child) => (h("a", { class: "nav-dropdown__link", href: child.href, key: `${item.label}:${child.label}`, onClick: () => onNavigate?.() }, child.label))))))) : null)));
324
+ }
325
+ handleAuthClick(event, action, href, onNavigate) {
326
+ const emitted = this.wegAuthClick.emit({ action });
326
327
  if (emitted.defaultPrevented) {
327
328
  event.preventDefault();
328
329
  onNavigate?.();
329
330
  return;
330
331
  }
331
- if (auth.action === 'sign-out') {
332
+ if (action === 'sign-out') {
332
333
  event.preventDefault();
333
- if (auth.href) {
334
- window.location.assign(auth.href);
334
+ if (href) {
335
+ window.location.assign(href);
335
336
  }
336
337
  onNavigate?.();
337
338
  return;
@@ -339,67 +340,70 @@ export class WegHeader {
339
340
  onNavigate?.();
340
341
  }
341
342
  renderAuthControl(options) {
342
- const auth = this.getAuthControl();
343
- if (!auth)
344
- return null;
345
- const sharedClass = {
346
- 'sign-in-link': !options.iconOnly,
347
- 'icon-button': !!options.iconOnly,
348
- [options.className || '']: !!options.className,
349
- };
350
- const content = [
351
- h(SignInIcon, { key: "icon" }),
352
- !options.iconOnly ? auth.label : null,
353
- options.iconOnly ? h("span", { class: "sr-only" }, auth.label) : null,
354
- ];
355
- return (h("a", { class: sharedClass, href: auth.href, "aria-label": auth.label, onClick: (event) => this.handleAuthClick(event, options.onNavigate) }, content));
356
- }
357
- renderSignedInCompactAuth(onNavigate) {
358
- const manageAccountLabel = this.getManageAccountLabel();
359
- return (h("a", { class: "icon-button manage-account-link", href: SIGNED_IN_HEADER.manageAccount.href, "aria-label": manageAccountLabel, onClick: () => onNavigate?.() }, h(SignInIcon, null), h("span", { class: "sr-only" }, manageAccountLabel)));
360
- }
361
- renderNavAuthItems(onNavigate) {
362
- if (this.signedIn) {
363
- const manageAccountLabel = this.getManageAccountLabel();
364
- const signOut = SIGNED_IN_HEADER.signOut;
365
- return [
366
- h("li", { class: "main-nav__item main-nav__item--auth", key: "manage-account" }, h("a", { class: "sign-in-link manage-account-link", href: SIGNED_IN_HEADER.manageAccount.href, "aria-label": manageAccountLabel, onClick: () => onNavigate?.() }, h(SignInIcon, null), manageAccountLabel)),
367
- h("li", { class: "main-nav__item main-nav__item--auth", key: "sign-out" }, h("a", { class: "sign-in-link sign-out-link", href: signOut.href, "aria-label": signOut.label, onClick: (event) => this.handleAuthClick(event, onNavigate) }, h(SignOutIcon, null), signOut.label)),
368
- ];
343
+ const { link, action, iconOnly, onNavigate } = options;
344
+ const Icon = action === 'sign-out' ? SignOutIcon : SignInIcon;
345
+ const classSuffix = action === 'sign-out' ? 'sign-out-link' : 'sign-in-link';
346
+ return (h("a", { class: {
347
+ [classSuffix]: !iconOnly,
348
+ 'icon-button': !!iconOnly,
349
+ [options.className || '']: !!options.className,
350
+ }, href: link.href, "aria-label": link.label, onClick: (event) => this.handleAuthClick(event, action, link.href, onNavigate) }, h(Icon, null), !iconOnly ? link.label : null, iconOnly ? h("span", { class: "sr-only" }, link.label) : null));
351
+ }
352
+ renderManageAccountAnchor(onNavigate, iconOnly = false) {
353
+ const label = this.getManageAccountLabel();
354
+ return (h("a", { class: {
355
+ 'sign-in-link': !iconOnly,
356
+ 'manage-account-link': true,
357
+ 'icon-button': iconOnly,
358
+ }, href: ACCOUNT_MANAGE_HREF, "aria-label": label, onClick: () => onNavigate?.() }, h(SignInIcon, null), !iconOnly ? label : null, iconOnly ? h("span", { class: "sr-only" }, label) : null));
359
+ }
360
+ renderNavItem(item, onNavigate) {
361
+ if (isMenuGroup(item)) {
362
+ return this.renderMenuGroup(item, onNavigate);
369
363
  }
370
- const auth = this.getAuthControl();
371
- if (!auth)
372
- return null;
373
- return (h("li", { class: "main-nav__item main-nav__item--auth", key: "sign-in" }, this.renderAuthControl({ onNavigate })));
364
+ if (isSignOutLink(item)) {
365
+ return (h("li", { class: "main-nav__item main-nav__item--auth", key: "sign-out" }, this.renderAuthControl({ link: item, action: 'sign-out', onNavigate })));
366
+ }
367
+ if (isSignInLink(item)) {
368
+ return (h("li", { class: "main-nav__item main-nav__item--auth", key: "sign-in" }, this.renderAuthControl({ link: item, action: 'sign-in', onNavigate })));
369
+ }
370
+ if (isManageAccountLink(item)) {
371
+ return (h("li", { class: "main-nav__item main-nav__item--auth", key: "manage-account" }, this.renderManageAccountAnchor(onNavigate)));
372
+ }
373
+ if (this.signedIn && isFindJobLink(item)) {
374
+ return this.renderIconNavLink(item, FindJobIcon, 'find-a-job-link', onNavigate);
375
+ }
376
+ if (this.signedIn && isDashboardLink(item)) {
377
+ return this.renderIconNavLink(item, DashboardIcon, 'dashboard-link', onNavigate);
378
+ }
379
+ return this.renderFlatNavLink(item, onNavigate);
374
380
  }
375
381
  renderNav(onNavigate) {
376
- const { dropdowns, links } = this.getActiveHeaderData();
377
- return (h("nav", { class: "main-nav", "aria-label": "Main" }, h("ul", { class: "main-nav__list" }, dropdowns.map((dropdown) => {
378
- const isOpen = this.openDropdown === dropdown.label;
379
- const panelId = this.getDropdownPanelId(dropdown.label);
380
- return (h("li", { class: "main-nav__item main-nav__item--dropdown", key: dropdown.label }, h("div", { class: "nav-dropdown", onMouseEnter: () => this.handleDropdownPointerEnter(dropdown.label), onMouseLeave: (event) => this.handleDropdownPointerLeave(event), onFocusout: (event) => this.handleDropdownFocusOut(event, dropdown.label) }, h("button", { type: "button", class: {
381
- 'nav-dropdown__trigger': true,
382
- 'nav-dropdown__trigger--open': isOpen,
383
- }, "aria-expanded": isOpen ? 'true' : 'false', "aria-haspopup": "true", "aria-controls": panelId, id: `${panelId}-trigger`, onClick: (event) => this.handleDropdownTriggerClick(event, dropdown.label), onKeyDown: (event) => {
384
- const container = event.currentTarget.closest('.nav-dropdown');
385
- this.handleDropdownTriggerKeyDown(event, dropdown.label, container);
386
- } }, h("span", { class: "nav-dropdown__label" }, dropdown.label), h(ToggleIcon, { expanded: isOpen })), isOpen ? (h("div", { class: "nav-dropdown__panel", id: panelId, role: "region", "aria-labelledby": `${panelId}-trigger`, onMouseEnter: () => this.handleDropdownPanelPointerEnter(dropdown.label), onMouseLeave: (event) => this.handleDropdownPanelPointerLeave(event) }, h("div", { class: "nav-dropdown__accent" }, h("div", { class: "nav-dropdown__links" }, dropdown.items.map((item) => (h("a", { class: "nav-dropdown__link", href: item.href, key: `${dropdown.label}:${item.label}`, onClick: () => onNavigate?.() }, item.label))))))) : null)));
387
- }), links.map((link) => (h("li", { class: "main-nav__item", key: link.label }, h("a", { class: "nav-link", href: link.href, onClick: () => onNavigate?.() }, link.label)))), this.renderNavAuthItems(onNavigate))));
382
+ return (h("nav", { class: "main-nav", "aria-label": "Main" }, h("ul", { class: "main-nav__list" }, this.getActiveMenu().map((item) => this.renderNavItem(item, onNavigate)))));
388
383
  }
389
384
  renderCompactAuth(onNavigate) {
390
385
  if (this.signedIn) {
391
- return this.renderSignedInCompactAuth(onNavigate);
386
+ return this.renderManageAccountAnchor(onNavigate, true);
392
387
  }
393
- return this.renderAuthControl({ iconOnly: true, onNavigate });
388
+ const signIn = this.findSignInLink(this.resolved.menu);
389
+ if (!signIn?.href)
390
+ return null;
391
+ return this.renderAuthControl({
392
+ link: signIn,
393
+ action: 'sign-in',
394
+ iconOnly: true,
395
+ onNavigate,
396
+ });
394
397
  }
395
398
  render() {
396
399
  const logoHref = this.getLogoHref();
400
+ const logoSrc = this.getLogoSrc();
397
401
  const closeMenu = () => this.closeMenu();
398
402
  const mobileMenuActive = this.isMobileMenuActive();
399
- return (h("header", { key: 'cf1c3532a5fe2b089f7947aacbe521632cfcf835', class: {
403
+ return (h("header", { key: '6b1f9929c75b61f3afd01651a57b5923ba8b32f6', class: {
400
404
  header: true,
401
405
  'header--menu-open': mobileMenuActive,
402
- } }, h("div", { key: '269f6a6ad820e7c3e307655fa95e28872d8259de', class: "container header-inner" }, h("button", { key: 'e46fc072a1023358d78f72ee4d12152b783a8471', type: "button", class: "menu-toggle icon-button", "aria-label": mobileMenuActive ? 'Close menu' : 'Open menu', "aria-expanded": mobileMenuActive ? 'true' : 'false', "aria-controls": "weg-header-main-nav", onClick: () => this.toggleMenu() }, mobileMenuActive ? h(CloseIcon, null) : h(HamburgerIcon, null)), h(Logo, { key: 'f228835a0941cdfcf11a4c59e03e80dac74df3b8', href: logoHref }), h("div", { key: '692d57fa6592b9a8995af26d235dcfc58962daf4', class: "header-actions" }, this.renderCompactAuth(mobileMenuActive ? closeMenu : undefined)), h("div", { key: '6609fea3c7b6293f342d63eb18b8219152ede883', class: "main-nav-panel", id: "weg-header-main-nav", role: mobileMenuActive ? 'dialog' : undefined, "aria-modal": mobileMenuActive ? 'true' : undefined, "aria-label": mobileMenuActive ? 'Menu' : undefined }, this.renderNav(mobileMenuActive ? closeMenu : undefined)))));
406
+ } }, h("div", { key: 'a4ebb7b276d22a60fb163dfe27cfb58fef28dc52', class: "container header-inner" }, h("button", { key: '5d52fc517e5d078c209d9fd3a096bdc3a88d3bcd', type: "button", class: "menu-toggle icon-button", "aria-label": mobileMenuActive ? 'Close menu' : 'Open menu', "aria-expanded": mobileMenuActive ? 'true' : 'false', "aria-controls": "weg-header-main-nav", onClick: () => this.toggleMenu() }, mobileMenuActive ? h(CloseIcon, null) : h(HamburgerIcon, null)), h(Logo, { key: '2a94c8178c404eeac500d5014747c2df0135db1e', href: logoHref, src: logoSrc }), h("div", { key: '2d042f19d01f3bfa29735bb878189db290530429', class: "header-actions" }, this.renderCompactAuth(mobileMenuActive ? closeMenu : undefined)), h("div", { key: 'd2f001b813d443560263a09c527bba287157ad85', class: "main-nav-panel", id: "weg-header-main-nav", role: mobileMenuActive ? 'dialog' : undefined, "aria-modal": mobileMenuActive ? 'true' : undefined, "aria-label": mobileMenuActive ? 'Menu' : undefined }, this.renderNav(mobileMenuActive ? closeMenu : undefined)))));
403
407
  }
404
408
  static get is() { return "weg-header"; }
405
409
  static get encapsulation() { return "shadow"; }
@@ -420,7 +424,7 @@ export class WegHeader {
420
424
  "mutable": false,
421
425
  "complexType": {
422
426
  "original": "LayoutData | string",
423
- "resolved": "string | { header?: Partial<LayoutHeaderData>; footer?: Partial<{ social: unknown; columns: unknown; credits: unknown; copyright: unknown; }>; }",
427
+ "resolved": "string | { header?: Partial<LayoutHeaderData>; footer?: Partial<LayoutFooterData>; }",
424
428
  "references": {
425
429
  "LayoutData": {
426
430
  "location": "import",
@@ -434,7 +438,7 @@ export class WegHeader {
434
438
  "optional": true,
435
439
  "docs": {
436
440
  "tags": [],
437
- "text": "Layout payload, supplied by the host application.\r\n\r\nExpected shape:\r\n```json\r\n{\r\n \"header\": {\r\n \"logoHref\": \"https://www.warwickemploymentgroup.com/\",\r\n \"dropdowns\": [{ \"label\": \"Find a job\", \"items\": [{ \"label\": \"...\", \"href\": \"...\" }] }],\r\n \"links\": [{ \"label\": \"Career advice\", \"href\": \"/career-advice\" }],\r\n \"signIn\": { \"label\": \"Sign in\", \"href\": \"/account/login\" },\r\n \"signOut\": { \"label\": \"Sign out\", \"href\": \"/account/login\" }\r\n }\r\n}\r\n```"
441
+ "text": "Layout payload, supplied by the host application.\n\nExpected shape:\n```json\n{\n \"header\": {\n \"menu\": [\n { \"label\": \"Find a job\", \"items\": [{ \"label\": \"Graduates\", \"href\": \"...\" }] },\n { \"label\": \"Career advice\", \"href\": \"/career-advice\" },\n { \"label\": \"Sign in\", \"href\": \"/account/login\" }\n ]\n }\n}\n```"
438
442
  },
439
443
  "getter": false,
440
444
  "setter": false,
@@ -453,7 +457,7 @@ export class WegHeader {
453
457
  "optional": false,
454
458
  "docs": {
455
459
  "tags": [],
456
- "text": "When true, the header shows the signed-in navigation (Find a job, Dashboard,\r\nManage Account, Sign out) instead of the CMS layout."
460
+ "text": "When true, the header uses the built-in signed-in menu instead of the CMS layout."
457
461
  },
458
462
  "getter": false,
459
463
  "setter": false,
@@ -498,7 +502,7 @@ export class WegHeader {
498
502
  "composed": true,
499
503
  "docs": {
500
504
  "tags": [],
501
- "text": "Fired when the user clicks Sign in or Sign out.\r\nCall `event.preventDefault()` in the host to handle navigation/logout yourself."
505
+ "text": "Fired when the user clicks Sign in or Sign out.\nCall `event.preventDefault()` in the host to handle navigation/logout yourself."
502
506
  },
503
507
  "complexType": {
504
508
  "original": "{ action: LayoutHeaderAuthAction }",
@@ -1,72 +1,72 @@
1
- /**
2
- * Breakpoint tokens (mobile-first min-width).
3
- *
4
- * - `@custom-media` names: e.g. `@media (--weg-md)` applies from 960px up.
5
- * Compiled by PostCSS in stencil.config.ts.
6
- * - `--weg-breakpoint-*` variables: use in property values, e.g. `max-width: var(--weg-breakpoint-md)`.
7
- *
8
- * Import this file in component stylesheets, or rely on shared.css which already imports it.
9
- */
10
-
11
- :host {
12
- --weg-breakpoint-sm: 640px;
13
- --weg-breakpoint-md: 960px;
14
- --weg-breakpoint-lg: 1280px;
15
- --weg-breakpoint-xl: 1440px;
16
- }
17
-
18
-
19
- *,
20
- *::before,
21
- *::after {
22
- box-sizing: border-box;
23
- }
24
-
25
- :host {
26
- display: block;
27
- --weg-font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, 'Apple Color Emoji',
28
- 'Segoe UI Emoji';
29
- --weg-color-accent: #948eeb;
30
- }
31
-
32
- /**
33
- * Container widths and horizontal padding match wegcms-v2 globals.css.
34
- * @see wegcms-v2/src/app/(frontend)/globals.css
35
- */
36
- .container {
37
- width: 100%;
38
- margin-inline: auto;
39
- padding-block: 12px;
40
- }
41
-
42
- @media (width >= 960px) {
43
- .container {
44
- padding-inline: 1rem;
45
- padding-block: 20px;
46
- }
47
- }
48
-
49
- @media (width >= 1280px) {
50
- .container {
51
- padding-inline: 2rem;
52
- }
53
- }
54
-
55
-
56
- @media (width >= 1440px) {
57
- .container {
58
- padding-block: 28px;
59
- }
60
- }
61
-
62
- .sr-only {
63
- position: absolute;
64
- width: 1px;
65
- height: 1px;
66
- padding: 0;
67
- margin: -1px;
68
- overflow: hidden;
69
- clip: rect(0, 0, 0, 0);
70
- white-space: nowrap;
71
- border: 0;
72
- }
1
+ /**
2
+ * Breakpoint tokens (mobile-first min-width).
3
+ *
4
+ * - `@custom-media` names: e.g. `@media (--weg-md)` applies from 960px up.
5
+ * Compiled by PostCSS in stencil.config.ts.
6
+ * - `--weg-breakpoint-*` variables: use in property values, e.g. `max-width: var(--weg-breakpoint-md)`.
7
+ *
8
+ * Import this file in component stylesheets, or rely on shared.css which already imports it.
9
+ */
10
+
11
+ :host {
12
+ --weg-breakpoint-sm: 640px;
13
+ --weg-breakpoint-md: 960px;
14
+ --weg-breakpoint-lg: 1280px;
15
+ --weg-breakpoint-xl: 1440px;
16
+ }
17
+
18
+
19
+ *,
20
+ *::before,
21
+ *::after {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ :host {
26
+ display: block;
27
+ --weg-font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, 'Apple Color Emoji',
28
+ 'Segoe UI Emoji';
29
+ --weg-color-accent: #948eeb;
30
+ }
31
+
32
+ /**
33
+ * Container widths and horizontal padding match wegcms-v2 globals.css.
34
+ * @see wegcms-v2/src/app/(frontend)/globals.css
35
+ */
36
+ .container {
37
+ width: 100%;
38
+ margin-inline: auto;
39
+ padding-block: 0.75rem;
40
+ }
41
+
42
+ @media (width >= 960px) {
43
+ .container {
44
+ padding-inline: 1rem;
45
+ padding-block: 1.25rem;
46
+ }
47
+ }
48
+
49
+ @media (width >= 1280px) {
50
+ .container {
51
+ padding-inline: 2rem;
52
+ }
53
+ }
54
+
55
+
56
+ @media (width >= 1440px) {
57
+ .container {
58
+ padding-block: 1.75rem;
59
+ }
60
+ }
61
+
62
+ .sr-only {
63
+ position: absolute;
64
+ width: 1px;
65
+ height: 1px;
66
+ padding: 0;
67
+ margin: -1px;
68
+ overflow: hidden;
69
+ clip: rect(0, 0, 0, 0);
70
+ white-space: nowrap;
71
+ border: 0;
72
+ }
@@ -1,3 +1,4 @@
1
+ const LINK_TARGETS = new Set(['_blank', '_self', '_parent', '_top']);
1
2
  export function parseJsonProp(value) {
2
3
  if (typeof value !== 'string')
3
4
  return value;
@@ -14,20 +15,67 @@ export function parseJsonProp(value) {
14
15
  export function isNonEmptyString(value) {
15
16
  return typeof value === 'string' && value.trim().length > 0;
16
17
  }
17
- export function normalizeLinks(input) {
18
+ export function isMenuGroup(link) {
19
+ return Boolean(link.items?.length);
20
+ }
21
+ function readNestedItems(raw) {
22
+ return raw.items ?? raw.children;
23
+ }
24
+ function readLinkTarget(raw) {
25
+ const target = raw.target;
26
+ if (typeof target === 'string' && LINK_TARGETS.has(target)) {
27
+ return target;
28
+ }
29
+ return undefined;
30
+ }
31
+ export function normalizeLayoutLink(input, options) {
32
+ if (!input || typeof input !== 'object')
33
+ return null;
34
+ const raw = input;
35
+ const label = raw.label;
36
+ if (!isNonEmptyString(label))
37
+ return null;
38
+ const nested = readNestedItems(raw);
39
+ if (Array.isArray(nested)) {
40
+ const items = normalizeLinks(nested, { requireHref: true });
41
+ if (items.length > 0) {
42
+ return { label: label.trim(), items };
43
+ }
44
+ }
45
+ const href = raw.href;
46
+ if (isNonEmptyString(href)) {
47
+ const link = { label: label.trim(), href: href.trim() };
48
+ const target = readLinkTarget(raw);
49
+ if (target)
50
+ link.target = target;
51
+ return link;
52
+ }
53
+ return options?.requireHref === false ? null : null;
54
+ }
55
+ export function normalizeLinks(input, options) {
18
56
  if (!Array.isArray(input))
19
57
  return [];
58
+ const requireHref = options?.requireHref ?? true;
20
59
  const result = [];
21
60
  for (const item of input) {
22
- if (!item || typeof item !== 'object')
23
- continue;
24
- const label = item.label;
25
- const href = item.href;
26
- if (!isNonEmptyString(label))
27
- continue;
28
- if (!isNonEmptyString(href))
61
+ const link = normalizeLayoutLink(item, { requireHref });
62
+ if (link)
63
+ result.push(link);
64
+ }
65
+ return result;
66
+ }
67
+ export function normalizeHeaderMenu(input) {
68
+ return normalizeLinks(input, { requireHref: false });
69
+ }
70
+ export function normalizeMenu(input) {
71
+ if (!Array.isArray(input))
72
+ return [];
73
+ const result = [];
74
+ for (const column of input) {
75
+ const links = normalizeLinks(column, { requireHref: true });
76
+ if (links.length === 0)
29
77
  continue;
30
- result.push({ label: label.trim(), href: href.trim() });
78
+ result.push(links);
31
79
  }
32
80
  return result;
33
81
  }
@@ -0,0 +1 @@
1
+ const n=new Set(["_blank","_self","_parent","_top"]);function t(n){if("string"!=typeof n)return n;const t=n.trim();if(t)try{return JSON.parse(t)}catch{return}}function r(n){return"string"==typeof n&&n.trim().length>0}function o(n){return Boolean(n.items?.length)}function e(t){if(!t||"object"!=typeof t)return null;const o=t,e=o.label;if(!r(e))return null;const u=function(n){return n.items??n.children}(o);if(Array.isArray(u)){const n=f(u);if(n.length>0)return{label:e.trim(),items:n}}const c=o.href;if(r(c)){const t={label:e.trim(),href:c.trim()},r=function(t){const r=t.target;if("string"==typeof r&&n.has(r))return r}(o);return r&&(t.target=r),t}return null}function f(n){if(!Array.isArray(n))return[];const t=[];for(const r of n){const n=e(r);n&&t.push(n)}return t}function u(n){return f(n)}function c(n){if(!Array.isArray(n))return[];const t=[];for(const r of n){const n=f(r);0!==n.length&&t.push(n)}return t}export{o as a,u as b,r as i,c as n,t as p}