weg-shared-layout 0.0.23 → 0.0.25

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 (57) hide show
  1. package/LICENSE +21 -21
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/weg-footer_2.cjs.entry.js +307 -219
  4. package/dist/cjs/weg-shared-layout.cjs.js +1 -1
  5. package/dist/collection/collection-manifest.json +0 -1
  6. package/dist/collection/components/weg-footer/icons/instagram.svg +5 -5
  7. package/dist/collection/components/weg-footer/icons/linkedin.svg +3 -3
  8. package/dist/collection/components/weg-footer/icons/tiktok.svg +3 -3
  9. package/dist/collection/components/weg-footer/icons/youtube.svg +4 -4
  10. package/dist/collection/components/weg-footer/weg-footer.css +270 -201
  11. package/dist/collection/components/weg-footer/weg-footer.js +22 -26
  12. package/dist/collection/components/weg-header/weg-header.css +474 -365
  13. package/dist/collection/components/weg-header/weg-header.js +226 -166
  14. package/dist/collection/constants/breakpoints.js +6 -0
  15. package/dist/collection/styles/shared.css +72 -78
  16. package/dist/collection/utils/layout.js +57 -9
  17. package/dist/components/p-BnMIy-A5.js +1 -0
  18. package/dist/components/weg-footer.js +1 -1
  19. package/dist/components/weg-header.js +1 -1
  20. package/dist/esm/loader.js +1 -1
  21. package/dist/esm/weg-footer_2.entry.js +307 -219
  22. package/dist/esm/weg-shared-layout.js +1 -1
  23. package/dist/types/components/weg-footer/weg-footer.d.ts +2 -2
  24. package/dist/types/components/weg-header/weg-header.d.ts +32 -16
  25. package/dist/types/components.d.ts +6 -48
  26. package/dist/types/constants/breakpoints.d.ts +6 -0
  27. package/dist/types/types/layout-data.d.ts +13 -27
  28. package/dist/types/utils/layout.d.ts +9 -1
  29. package/dist/weg-shared-layout/p-d74a58cf.entry.js +1 -0
  30. package/dist/weg-shared-layout/weg-shared-layout.esm.js +1 -1
  31. package/docs/angular-integration-guide.md +1004 -0
  32. package/docs/angular.md +229 -138
  33. package/docs/nextjs.md +306 -305
  34. package/docs/publishing.md +48 -48
  35. package/docs/react.md +261 -261
  36. package/docs/vanilla.md +138 -140
  37. package/package.json +12 -8
  38. package/readme.md +82 -107
  39. package/src/assets/dummy-data.json +137 -63
  40. package/src/constants/breakpoints.ts +7 -0
  41. package/dist/cjs/my-component.cjs.entry.js +0 -33
  42. package/dist/collection/components/my-component/my-component.css +0 -3
  43. package/dist/collection/components/my-component/my-component.js +0 -95
  44. package/dist/collection/components/weg-header/signed-in-layout.js +0 -13
  45. package/dist/collection/constants/weg-urls.js +0 -18
  46. package/dist/components/my-component.d.ts +0 -11
  47. package/dist/components/my-component.js +0 -1
  48. package/dist/components/p-DbIEJ3IT.js +0 -1
  49. package/dist/esm/my-component.entry.js +0 -31
  50. package/dist/types/components/my-component/my-component.d.ts +0 -16
  51. package/dist/types/components/weg-header/signed-in-layout.d.ts +0 -18
  52. package/dist/types/constants/weg-urls.d.ts +0 -18
  53. package/dist/weg-shared-layout/p-80fa529c.entry.js +0 -1
  54. package/dist/weg-shared-layout/p-d61033bd.entry.js +0 -1
  55. package/src/constants/weg-urls.ts +0 -23
  56. /package/dist/types/Users/{tan/Documents/code → rakin/Documents/WEG/repos}/weg-shared-layout/.stencil/test-utils/axe.d.ts +0 -0
  57. /package/dist/types/Users/{tan/Documents/code → rakin/Documents/WEG/repos}/weg-shared-layout/.stencil/test-utils/viewport.d.ts +0 -0
@@ -1,64 +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
+ import { WEG_HOVER_CAPABLE_MEDIA_QUERY, WEG_MD_MEDIA_QUERY } from "../../constants/breakpoints";
3
4
  import { LOGO_SRC } from "./logo-data";
4
- 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
+ ];
5
16
  const EMPTY_HEADER = {
6
- logoHref: DEFAULT_LOGO_HREF,
7
- dropdowns: [],
8
- links: [],
9
- signIn: null,
10
- signOut: null,
17
+ menu: [],
11
18
  };
12
- function normalizeDropdowns(input) {
13
- if (!Array.isArray(input))
14
- return [];
15
- const result = [];
16
- for (const item of input) {
17
- if (!item || typeof item !== 'object')
18
- continue;
19
- const label = item.label;
20
- const items = normalizeLinks(item.items);
21
- if (!isNonEmptyString(label))
22
- continue;
23
- if (items.length === 0)
24
- continue;
25
- result.push({ label: label.trim(), items });
26
- }
27
- 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;
28
26
  }
29
- function normalizeSignIn(input) {
30
- if (!input || typeof input !== 'object')
31
- return null;
32
- const label = input.label;
33
- const href = input.href;
34
- if (!isNonEmptyString(label))
35
- return null;
36
- if (!isNonEmptyString(href))
37
- return null;
38
- return { label: label.trim(), href: href.trim() };
27
+ function isSignOutLink(link) {
28
+ return link.label.trim().toLowerCase() === 'sign out';
39
29
  }
40
- function normalizeSignOut(input) {
41
- if (!input || typeof input !== 'object')
42
- return null;
43
- const label = input.label;
44
- const href = input.href;
45
- if (!isNonEmptyString(label))
46
- return null;
47
- return {
48
- label: label.trim(),
49
- href: isNonEmptyString(href) ? href.trim() : undefined,
50
- };
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`;
51
44
  }
52
45
  function normalizeHeaderData(input) {
53
46
  const root = (input && typeof input === 'object' ? input : {});
54
47
  const header = root.header && typeof root.header === 'object' ? root.header : {};
55
- const logoHref = isNonEmptyString(header.logoHref) ? header.logoHref.trim() : DEFAULT_LOGO_HREF;
48
+ const logoSrc = isNonEmptyString(header.logoSrc) ? header.logoSrc.trim() : undefined;
56
49
  return {
57
- logoHref,
58
- dropdowns: normalizeDropdowns(header.dropdowns),
59
- links: normalizeLinks(header.links),
60
- signIn: normalizeSignIn(header.signIn),
61
- signOut: normalizeSignOut(header.signOut),
50
+ logoSrc,
51
+ menu: normalizeHeaderMenu(header.menu),
62
52
  };
63
53
  }
64
54
  function SignInIcon() {
@@ -67,6 +57,12 @@ function SignInIcon() {
67
57
  function SignOutIcon() {
68
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" })));
69
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
+ }
70
66
  function HamburgerIcon() {
71
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" })));
72
68
  }
@@ -76,8 +72,8 @@ function CloseIcon() {
76
72
  function ToggleIcon({ expanded }) {
77
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" })))));
78
74
  }
79
- function Logo({ href }) {
80
- 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" })));
81
77
  }
82
78
  export class WegHeader {
83
79
  /**
@@ -87,19 +83,18 @@ export class WegHeader {
87
83
  * ```json
88
84
  * {
89
85
  * "header": {
90
- * "logoHref": "https://www.warwickemploymentgroup.com/",
91
- * "dropdowns": [{ "label": "Find a job", "items": [{ "label": "...", "href": "..." }] }],
92
- * "links": [{ "label": "Career advice", "href": "/career-advice" }],
93
- * "signIn": { "label": "Sign in", "href": "/account/login" },
94
- * "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
+ * ]
95
91
  * }
96
92
  * }
97
93
  * ```
98
94
  */
99
95
  layout;
100
96
  /**
101
- * When true, the header shows the signed-in navigation (Find a job, Dashboard,
102
- * 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.
103
98
  */
104
99
  signedIn = false;
105
100
  /**
@@ -115,10 +110,13 @@ export class WegHeader {
115
110
  resolved = EMPTY_HEADER;
116
111
  menuOpen = false;
117
112
  openDropdown = null;
118
- expandedSection = null;
119
113
  boundHandleDocumentClick = this.handleDocumentClick.bind(this);
114
+ boundHandleViewportChange = this.handleViewportChange.bind(this);
115
+ boundHandleHoverCapabilityChange = this.handleHoverCapabilityChange.bind(this);
120
116
  hoverCloseTimer = null;
121
117
  hoverOpenEnabled = false;
118
+ desktopMediaQuery;
119
+ hoverCapableMediaQuery;
122
120
  resolve() {
123
121
  if (this.layout === undefined || this.layout === null) {
124
122
  this.resolved = EMPTY_HEADER;
@@ -128,15 +126,46 @@ export class WegHeader {
128
126
  }
129
127
  componentWillLoad() {
130
128
  this.resolve();
131
- this.hoverOpenEnabled = window.matchMedia('(hover: hover) and (pointer: fine)').matches;
129
+ this.hoverCapableMediaQuery = window.matchMedia(WEG_HOVER_CAPABLE_MEDIA_QUERY);
130
+ this.hoverCapableMediaQuery.addEventListener('change', this.boundHandleHoverCapabilityChange);
131
+ this.desktopMediaQuery = window.matchMedia(WEG_MD_MEDIA_QUERY);
132
+ this.desktopMediaQuery.addEventListener('change', this.boundHandleViewportChange);
133
+ this.updateHoverDropdownEnabled();
134
+ this.handleViewportChange();
132
135
  }
133
136
  connectedCallback() {
134
137
  document.addEventListener('click', this.boundHandleDocumentClick);
135
138
  }
136
139
  disconnectedCallback() {
137
140
  document.removeEventListener('click', this.boundHandleDocumentClick);
141
+ this.desktopMediaQuery?.removeEventListener('change', this.boundHandleViewportChange);
142
+ this.hoverCapableMediaQuery?.removeEventListener('change', this.boundHandleHoverCapabilityChange);
138
143
  this.cancelScheduledDropdownClose();
139
144
  }
145
+ /**
146
+ * Desktop dropdowns open on hover when viewport is md+ and the device reports hover support.
147
+ * Uses (hover: hover) only — not (pointer: fine), which breaks in DevTools and hybrid devices.
148
+ */
149
+ updateHoverDropdownEnabled() {
150
+ const enabled = Boolean(this.desktopMediaQuery?.matches && this.hoverCapableMediaQuery?.matches);
151
+ if (this.hoverOpenEnabled && !enabled) {
152
+ this.cancelScheduledDropdownClose();
153
+ this.closeDropdown();
154
+ }
155
+ this.hoverOpenEnabled = enabled;
156
+ }
157
+ handleHoverCapabilityChange() {
158
+ this.updateHoverDropdownEnabled();
159
+ }
160
+ handleViewportChange() {
161
+ if (this.desktopMediaQuery?.matches) {
162
+ this.closeMenu();
163
+ }
164
+ this.updateHoverDropdownEnabled();
165
+ }
166
+ isMobileMenuActive() {
167
+ return this.menuOpen && !this.desktopMediaQuery?.matches;
168
+ }
140
169
  watchLayout() {
141
170
  this.resolve();
142
171
  }
@@ -188,18 +217,40 @@ export class WegHeader {
188
217
  return;
189
218
  this.openDropdownMenu(label);
190
219
  }
191
- handleDropdownPointerLeave() {
220
+ handleDropdownPointerLeave(event) {
192
221
  if (!this.hoverOpenEnabled)
193
222
  return;
223
+ const related = event.relatedTarget;
224
+ const container = event.currentTarget;
225
+ if (related && container.contains(related))
226
+ return;
194
227
  this.scheduleDropdownClose();
195
228
  }
196
- handleDropdownFocusIn(label) {
229
+ handleDropdownPanelPointerEnter(label) {
230
+ if (!this.hoverOpenEnabled)
231
+ return;
232
+ this.cancelScheduledDropdownClose();
197
233
  this.openDropdownMenu(label);
198
234
  }
235
+ handleDropdownPanelPointerLeave(event) {
236
+ if (!this.hoverOpenEnabled)
237
+ return;
238
+ const related = event.relatedTarget;
239
+ const panel = event.currentTarget;
240
+ const container = panel.closest('.nav-dropdown');
241
+ if (related && container?.contains(related))
242
+ return;
243
+ this.scheduleDropdownClose();
244
+ }
245
+ handleDropdownTriggerClick(event, label) {
246
+ event.stopPropagation();
247
+ this.cancelScheduledDropdownClose();
248
+ this.toggleDropdown(label);
249
+ }
199
250
  handleDropdownFocusOut(event, label) {
200
251
  const related = event.relatedTarget;
201
- const currentTarget = event.currentTarget;
202
- if (related && currentTarget.contains(related))
252
+ const container = event.currentTarget;
253
+ if (related && container.contains(related))
203
254
  return;
204
255
  if (this.openDropdown === label) {
205
256
  this.closeDropdown();
@@ -214,63 +265,74 @@ export class WegHeader {
214
265
  }
215
266
  focusFirstDropdownLink(itemEl) {
216
267
  setTimeout(() => {
217
- const firstLink = itemEl.querySelector('.dropdown-panel__link');
268
+ const firstLink = itemEl.querySelector('.nav-dropdown__link');
218
269
  firstLink?.focus();
219
270
  }, 0);
220
271
  }
221
- toggleAccordion(label) {
222
- this.expandedSection = this.expandedSection === label ? null : label;
272
+ toggleMenu() {
273
+ if (this.menuOpen) {
274
+ this.closeMenu();
275
+ return;
276
+ }
277
+ this.openMenu();
223
278
  }
224
279
  openMenu() {
225
280
  this.menuOpen = true;
226
- this.expandedSection = null;
281
+ this.closeDropdown();
227
282
  }
228
283
  closeMenu() {
229
284
  this.menuOpen = false;
230
- this.expandedSection = null;
285
+ this.closeDropdown();
231
286
  }
232
- getActiveHeaderData() {
233
- if (!this.signedIn)
234
- return this.resolved;
235
- return {
236
- logoHref: SIGNED_IN_HEADER.logoHref,
237
- dropdowns: [],
238
- links: [...SIGNED_IN_HEADER.links],
239
- signIn: null,
240
- signOut: SIGNED_IN_HEADER.signOut,
241
- };
287
+ getActiveMenu() {
288
+ return this.signedIn ? SIGNED_IN_MENU : this.resolved.menu;
289
+ }
290
+ getLogoSrc() {
291
+ return this.resolved.logoSrc ?? LOGO_SRC;
242
292
  }
243
293
  getLogoHref() {
244
- 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;
245
302
  }
246
303
  getManageAccountLabel() {
247
304
  const name = this.userName?.trim();
248
305
  return name || 'Manage Account';
249
306
  }
250
- getAuthControl() {
251
- if (this.signedIn)
252
- return null;
253
- const signIn = this.resolved.signIn;
254
- if (!signIn)
255
- return null;
256
- return { label: signIn.label, href: signIn.href, action: 'sign-in' };
257
- }
258
- handleAuthClick(event, onNavigate) {
259
- const auth = this.getAuthControl() ?? {
260
- label: SIGNED_IN_HEADER.signOut.label,
261
- href: SIGNED_IN_HEADER.signOut.href,
262
- action: 'sign-out',
263
- };
264
- 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 });
265
327
  if (emitted.defaultPrevented) {
266
328
  event.preventDefault();
267
329
  onNavigate?.();
268
330
  return;
269
331
  }
270
- if (auth.action === 'sign-out') {
332
+ if (action === 'sign-out') {
271
333
  event.preventDefault();
272
- if (auth.href) {
273
- window.location.assign(auth.href);
334
+ if (href) {
335
+ window.location.assign(href);
274
336
  }
275
337
  onNavigate?.();
276
338
  return;
@@ -278,71 +340,70 @@ export class WegHeader {
278
340
  onNavigate?.();
279
341
  }
280
342
  renderAuthControl(options) {
281
- const auth = this.getAuthControl();
282
- if (!auth)
283
- return null;
284
- const sharedClass = {
285
- 'sign-in-link': !options.iconOnly,
286
- 'icon-button': !!options.iconOnly,
287
- [options.className || '']: !!options.className,
288
- };
289
- const content = [
290
- h(SignInIcon, { key: "icon" }),
291
- !options.iconOnly ? auth.label : null,
292
- options.iconOnly ? h("span", { class: "sr-only" }, auth.label) : null,
293
- ];
294
- return (h("a", { class: sharedClass, href: auth.href, "aria-label": auth.label, onClick: (event) => this.handleAuthClick(event, options.onNavigate) }, content));
295
- }
296
- renderSignedInAuthControls(options) {
297
- const manageAccountLabel = this.getManageAccountLabel();
298
- const signOut = SIGNED_IN_HEADER.signOut;
299
- if (options.iconOnly) {
300
- return (h("a", { class: "icon-button manage-account-link", href: SIGNED_IN_HEADER.manageAccount.href, "aria-label": manageAccountLabel, onClick: () => options.onNavigate?.() }, h(SignInIcon, null), h("span", { class: "sr-only" }, manageAccountLabel)));
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);
363
+ }
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);
380
+ }
381
+ renderNav(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)))));
383
+ }
384
+ renderCompactAuth(onNavigate) {
385
+ if (this.signedIn) {
386
+ return this.renderManageAccountAnchor(onNavigate, true);
301
387
  }
302
- return [
303
- h("li", { class: "desktop-nav__item", key: "manage-account" }, h("a", { class: "sign-in-link manage-account-link", href: SIGNED_IN_HEADER.manageAccount.href, "aria-label": manageAccountLabel, onClick: () => options.onNavigate?.() }, h(SignInIcon, null), manageAccountLabel)),
304
- h("li", { class: "desktop-nav__item", key: "sign-out" }, h("a", { class: "sign-in-link sign-out-link", href: signOut.href, "aria-label": signOut.label, onClick: (event) => this.handleAuthClick(event, options.onNavigate) }, h(SignOutIcon, null), signOut.label)),
305
- ];
306
- }
307
- renderDesktop() {
308
- const { dropdowns, links } = this.getActiveHeaderData();
309
- return (h("div", { class: "desktop" }, h(Logo, { href: this.getLogoHref() }), h("nav", { class: "desktop-nav", "aria-label": "Main" }, h("ul", { class: "desktop-nav__list" }, dropdowns.map((dropdown) => {
310
- const isOpen = this.openDropdown === dropdown.label;
311
- const panelId = this.getDropdownPanelId(dropdown.label);
312
- return (h("li", { class: "desktop-nav__item desktop-nav__item--dropdown", key: dropdown.label, onMouseEnter: () => this.handleDropdownPointerEnter(dropdown.label), onMouseLeave: () => this.handleDropdownPointerLeave(), onFocusin: () => this.handleDropdownFocusIn(dropdown.label), onFocusout: (event) => this.handleDropdownFocusOut(event, dropdown.label) }, h("button", { type: "button", class: {
313
- 'dropdown-trigger': true,
314
- 'dropdown-trigger--open': isOpen,
315
- }, "aria-expanded": isOpen ? 'true' : 'false', "aria-haspopup": "true", "aria-controls": panelId, id: `${panelId}-trigger`, onClick: (event) => {
316
- event.stopPropagation();
317
- this.toggleDropdown(dropdown.label);
318
- }, onKeyDown: (event) => {
319
- const itemEl = event.currentTarget.closest('.desktop-nav__item--dropdown');
320
- this.handleDropdownTriggerKeyDown(event, dropdown.label, itemEl);
321
- } }, dropdown.label, h(ToggleIcon, { expanded: isOpen })), isOpen ? (h("div", { class: "dropdown-panel", id: panelId, role: "region", "aria-labelledby": `${panelId}-trigger` }, h("div", { class: "dropdown-panel__accent" }, h("div", { class: "dropdown-panel__links" }, dropdown.items.map((item) => (h("a", { class: "dropdown-panel__link", href: item.href, key: `${dropdown.label}:${item.label}` }, item.label))))))) : null));
322
- }), links.map((link) => (h("li", { class: "desktop-nav__item", key: link.label }, h("a", { class: "nav-link", href: link.href }, link.label)))), this.signedIn
323
- ? this.renderSignedInAuthControls({})
324
- : this.getAuthControl()
325
- ? h("li", { class: "desktop-nav__item" }, this.renderAuthControl({}))
326
- : null))));
327
- }
328
- renderMobileBar() {
329
- return (h("div", { class: "mobile" }, h("button", { type: "button", class: "icon-button", "aria-label": "Open menu", onClick: () => this.openMenu() }, h(HamburgerIcon, null)), h(Logo, { href: this.getLogoHref() }), this.signedIn
330
- ? this.renderSignedInAuthControls({ iconOnly: true })
331
- : this.renderAuthControl({ iconOnly: true })));
332
- }
333
- renderMobileOverlay() {
334
- if (!this.menuOpen)
388
+ const signIn = this.findSignInLink(this.resolved.menu);
389
+ if (!signIn?.href)
335
390
  return null;
336
- const { dropdowns, links } = this.getActiveHeaderData();
337
- return (h("div", { class: "mobile-overlay", role: "dialog", "aria-modal": "true", "aria-label": "Menu" }, h("div", { class: "mobile-overlay__header" }, h("button", { type: "button", class: "icon-button", "aria-label": "Close menu", onClick: () => this.closeMenu() }, h(CloseIcon, null)), h("div", { class: "mobile-overlay__logo" }, h(Logo, { href: this.getLogoHref() })), this.signedIn
338
- ? this.renderSignedInAuthControls({ iconOnly: true, onNavigate: () => this.closeMenu() })
339
- : this.renderAuthControl({ iconOnly: true, onNavigate: () => this.closeMenu() })), h("nav", { class: "mobile-nav", "aria-label": "Main" }, dropdowns.map((dropdown) => {
340
- const isExpanded = this.expandedSection === dropdown.label;
341
- return (h("div", { class: "mobile-nav__section", key: dropdown.label }, h("button", { type: "button", class: "mobile-nav__row", "aria-expanded": isExpanded ? 'true' : 'false', onClick: () => this.toggleAccordion(dropdown.label) }, h("span", null, dropdown.label), h(ToggleIcon, { expanded: isExpanded })), isExpanded ? (h("div", { class: "mobile-nav__sub" }, dropdown.items.map((item) => (h("a", { class: "mobile-nav__sub-link", href: item.href, key: `${dropdown.label}:${item.label}`, onClick: () => this.closeMenu() }, item.label))))) : null));
342
- }), links.map((link) => (h("div", { class: "mobile-nav__section", key: link.label }, h("a", { class: "mobile-nav__row", href: link.href, onClick: () => this.closeMenu() }, link.label)))), this.signedIn ? (h("div", { class: "mobile-nav__section" }, h("a", { class: "mobile-nav__row manage-account-link", href: SIGNED_IN_HEADER.manageAccount.href, onClick: () => this.closeMenu() }, this.getManageAccountLabel()), h("a", { class: "mobile-nav__row sign-out-link", href: SIGNED_IN_HEADER.signOut.href, onClick: (event) => this.handleAuthClick(event, () => this.closeMenu()) }, h(SignOutIcon, null), SIGNED_IN_HEADER.signOut.label))) : null)));
391
+ return this.renderAuthControl({
392
+ link: signIn,
393
+ action: 'sign-in',
394
+ iconOnly: true,
395
+ onNavigate,
396
+ });
343
397
  }
344
398
  render() {
345
- return (h("header", { key: 'fc00e4afd015b291311939ba30f4ae3070957864', class: "header" }, h("div", { key: 'f2c8ed2c4e702e0a85acfbccd91165f5a2e1ff7a', class: "container" }, this.renderDesktop(), this.renderMobileBar()), this.renderMobileOverlay()));
399
+ const logoHref = this.getLogoHref();
400
+ const logoSrc = this.getLogoSrc();
401
+ const closeMenu = () => this.closeMenu();
402
+ const mobileMenuActive = this.isMobileMenuActive();
403
+ return (h("header", { key: 'ce1a217dd1eda9bb72b506c187a5c6a0848de3a6', class: {
404
+ header: true,
405
+ 'header--menu-open': mobileMenuActive,
406
+ } }, h("div", { key: '535d54f8eb512158c6048d748fccce4feb097cc0', class: "container header-inner" }, h("button", { key: '51a0fead049fadcbee17c8e622a35b1d2d8be99c', 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: '6f5693bfec5d8fe196ca3e4ff565506b6c3d3421', href: logoHref, src: logoSrc }), h("div", { key: '9f846e47cb68b6158e832fdffda5973e5e1554f4', class: "header-actions" }, this.renderCompactAuth(mobileMenuActive ? closeMenu : undefined)), h("div", { key: 'e18db3c24e934200595a1216dc74f3dcb8ad0e67', 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)))));
346
407
  }
347
408
  static get is() { return "weg-header"; }
348
409
  static get encapsulation() { return "shadow"; }
@@ -363,7 +424,7 @@ export class WegHeader {
363
424
  "mutable": false,
364
425
  "complexType": {
365
426
  "original": "LayoutData | string",
366
- "resolved": "string | { header?: Partial<LayoutHeaderData>; footer?: Partial<{ social: unknown; columns: unknown; credits: unknown; copyright: unknown; }>; }",
427
+ "resolved": "string | { header?: Partial<LayoutHeaderData>; footer?: Partial<LayoutFooterData>; }",
367
428
  "references": {
368
429
  "LayoutData": {
369
430
  "location": "import",
@@ -377,7 +438,7 @@ export class WegHeader {
377
438
  "optional": true,
378
439
  "docs": {
379
440
  "tags": [],
380
- "text": "Layout payload, supplied by the host application.\n\nExpected shape:\n```json\n{\n \"header\": {\n \"logoHref\": \"https://www.warwickemploymentgroup.com/\",\n \"dropdowns\": [{ \"label\": \"Find a job\", \"items\": [{ \"label\": \"...\", \"href\": \"...\" }] }],\n \"links\": [{ \"label\": \"Career advice\", \"href\": \"/career-advice\" }],\n \"signIn\": { \"label\": \"Sign in\", \"href\": \"/account/login\" },\n \"signOut\": { \"label\": \"Sign out\", \"href\": \"/account/login\" }\n }\n}\n```"
441
+ "text": "Layout payload, supplied by the host application.\r\n\r\nExpected shape:\r\n```json\r\n{\r\n \"header\": {\r\n \"menu\": [\r\n { \"label\": \"Find a job\", \"items\": [{ \"label\": \"Graduates\", \"href\": \"...\" }] },\r\n { \"label\": \"Career advice\", \"href\": \"/career-advice\" },\r\n { \"label\": \"Sign in\", \"href\": \"/account/login\" }\r\n ]\r\n }\r\n}\r\n```"
381
442
  },
382
443
  "getter": false,
383
444
  "setter": false,
@@ -396,7 +457,7 @@ export class WegHeader {
396
457
  "optional": false,
397
458
  "docs": {
398
459
  "tags": [],
399
- "text": "When true, the header shows the signed-in navigation (Find a job, Dashboard,\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."
400
461
  },
401
462
  "getter": false,
402
463
  "setter": false,
@@ -429,8 +490,7 @@ export class WegHeader {
429
490
  return {
430
491
  "resolved": {},
431
492
  "menuOpen": {},
432
- "openDropdown": {},
433
- "expandedSection": {}
493
+ "openDropdown": {}
434
494
  };
435
495
  }
436
496
  static get events() {
@@ -442,7 +502,7 @@ export class WegHeader {
442
502
  "composed": true,
443
503
  "docs": {
444
504
  "tags": [],
445
- "text": "Fired when the user clicks Sign in or Sign out.\nCall `event.preventDefault()` in the host to handle navigation/logout yourself."
505
+ "text": "Fired when the user clicks Sign in or Sign out.\r\nCall `event.preventDefault()` in the host to handle navigation/logout yourself."
446
506
  },
447
507
  "complexType": {
448
508
  "original": "{ action: LayoutHeaderAuthAction }",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * JS media-query strings aligned with `src/styles/breakpoints.css`.
3
+ */
4
+ export const WEG_MD_MEDIA_QUERY = '(width >= 960px)';
5
+ /** Primary input can hover (excludes most touch-only profiles). */
6
+ export const WEG_HOVER_CAPABLE_MEDIA_QUERY = '(hover: hover)';