@statsbygg/layout 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,158 +2,113 @@
2
2
 
3
3
  // src/components/RootLayout/RootLayout.tsx
4
4
  import { useEffect as useEffect2 } from "react";
5
- import clsx4 from "clsx";
5
+ import clsx5 from "clsx";
6
+ import { SkipLink } from "@digdir/designsystemet-react";
6
7
 
7
8
  // src/components/GlobalHeader/GlobalHeader.tsx
8
9
  import { Button as Button2, Link as Link2 } from "@digdir/designsystemet-react";
9
- import clsx2 from "clsx";
10
+ import { Search as Search2 } from "lucide-react";
11
+ import clsx3 from "clsx";
10
12
 
11
13
  // src/components/Breadcrumbs/Breadcrumbs.tsx
14
+ import { useMemo } from "react";
12
15
  import { usePathname } from "next/navigation";
13
16
  import { Breadcrumbs } from "@digdir/designsystemet-react";
14
17
  import clsx from "clsx";
15
- import styles from "./Breadcrumbs.module.css";
16
18
 
17
19
  // src/routes.ts
18
- var ZONE_TREES_INPUT = {
19
- sbno: {
20
- segment: "",
21
- // primary root route, no breadcrumb
22
- label: "Hjem",
23
- children: [
24
- { segment: "nyheter", label: "Nyheter" }
25
- ]
26
- },
27
- lokaler: {
28
- segment: "lokaler",
29
- label: "Statens eide og leide lokaler",
30
- children: [
31
- {
32
- segment: "lokalbruk",
33
- label: "Lokalbruk"
34
- },
35
- { segment: "veiledning", label: "Veiledning" },
36
- { segment: "statlige-eiendommer", label: "Statlige eiendommer" },
37
- { segment: "ledig-for-fremleie", label: "Ledig for fremleie" },
38
- { segment: "statistikk", label: "Statistikk" }
39
- ]
40
- }
41
- };
42
- var ROUTES_INDEX = {};
43
- var PARENT_INDEX = /* @__PURE__ */ new Map();
44
- var ZONE_TREES = {};
45
- function isSegmentNode(n) {
46
- return n.segment !== void 0 && n.path === void 0;
47
- }
48
- function isPathNode(n) {
49
- return n.path !== void 0;
20
+ function isExternalPath(path) {
21
+ return path.startsWith("http://") || path.startsWith("https://");
50
22
  }
51
- function joinPath(base, seg) {
52
- if (!base) return seg ? `/${seg}` : "/";
53
- return seg ? `${base.replace(/\/+$/, "")}/${seg.replace(/^\/+/, "")}` : base || "/";
23
+ function normalizePath(path) {
24
+ if (path === "/") return "/";
25
+ return path.replace(/\/+$/, "");
54
26
  }
55
- function normalizeToAbsolute(node, base = "") {
56
- var _a;
57
- const path = isSegmentNode(node) ? joinPath(base, node.segment) : isPathNode(node) ? node.path === "" ? "/" : node.path : "/";
58
- const children = ((_a = node.children) != null ? _a : []).map((c) => normalizeToAbsolute(c, path));
59
- return { path, label: node.label, children: children.length ? children : void 0 };
27
+ function formatSegmentLabel(segment) {
28
+ try {
29
+ const decoded = decodeURIComponent(segment).replace(/[-_]+/g, " ").trim();
30
+ return decoded ? decoded.charAt(0).toUpperCase() + decoded.slice(1) : segment;
31
+ } catch (e) {
32
+ return segment;
33
+ }
60
34
  }
61
- (function buildAll() {
62
- Object.keys(ZONE_TREES_INPUT).forEach((zone) => {
63
- ZONE_TREES[zone] = normalizeToAbsolute(ZONE_TREES_INPUT[zone]);
64
- });
65
- function walk(zone, node, parentKey) {
66
- var _a;
67
- const def = { zone, path: node.path, label: node.label };
68
- const key = `${zone}:${node.path}`;
69
- ROUTES_INDEX[zone].push(def);
70
- PARENT_INDEX.set(key, parentKey);
71
- for (const child of (_a = node.children) != null ? _a : []) {
72
- walk(zone, child, key);
73
- }
35
+ function findAppRootWithAncestors(node, ancestors = []) {
36
+ if (!isExternalPath(node.path)) {
37
+ return {
38
+ externalAncestors: ancestors,
39
+ appRoot: node
40
+ };
74
41
  }
75
- Object.keys(ZONE_TREES).forEach((zone) => {
76
- ROUTES_INDEX[zone] = [];
77
- walk(zone, ZONE_TREES[zone], null);
78
- });
79
- })();
80
- function findBestMatch(zone, pathname) {
81
- var _a;
82
- const list = (_a = ROUTES_INDEX[zone]) != null ? _a : [];
83
- let best;
84
- for (const r of list) {
85
- if (pathname === r.path) {
86
- best = r;
87
- break;
88
- }
89
- if (pathname.startsWith(r.path.endsWith("/") ? r.path : r.path + "/")) {
90
- if (!best || r.path.length > best.path.length) best = r;
42
+ if (node.children) {
43
+ for (const child of node.children) {
44
+ const result = findAppRootWithAncestors(child, [...ancestors, node]);
45
+ if (result.appRoot) return result;
91
46
  }
92
47
  }
93
- return best != null ? best : list.find((r) => {
94
- var _a2;
95
- return r.path === ((_a2 = ZONE_TREES[zone]) == null ? void 0 : _a2.path);
96
- });
97
- }
98
- function getZoneRoot(zone) {
99
- var _a;
100
- return ((_a = ZONE_TREES[zone]) == null ? void 0 : _a.path) || "/";
101
- }
102
- function labelFromSlug(slug) {
103
- try {
104
- const s = decodeURIComponent(slug).replace(/[-_]+/g, " ").trim();
105
- return s ? s.charAt(0).toUpperCase() + s.slice(1) : slug;
106
- } catch (e) {
107
- return slug;
108
- }
48
+ return { externalAncestors: ancestors, appRoot: null };
109
49
  }
110
- function getBreadcrumbs(zone, pathname) {
111
- var _a, _b;
112
- const normalizedPathname = pathname === "/" ? pathname : pathname.replace(/\/+$/, "");
113
- if (zone === "sbno" && normalizedPathname === "/") return [];
114
- const match = findBestMatch(zone, normalizedPathname);
115
- if (!match) return [];
116
- const chain = [];
117
- let key = `${zone}:${match.path}`;
118
- while (key) {
119
- const parentKey = PARENT_INDEX.get(key);
120
- const [z, p] = key.split(":");
121
- const def = ((_a = ROUTES_INDEX[z]) != null ? _a : []).find((r) => r.path === p);
122
- if (def) chain.unshift(def);
123
- key = parentKey != null ? parentKey : void 0;
50
+ function findInternalMatch(node, pathname, ancestors = []) {
51
+ const nodePath = normalizePath(node.path);
52
+ if (pathname === nodePath) {
53
+ return { node, ancestors };
124
54
  }
125
- const homeRoute = ((_b = ROUTES_INDEX.sbno) != null ? _b : []).find((r) => r.path === "/");
126
- if (homeRoute && !(chain.length === 1 && chain[0].zone === "sbno" && chain[0].path === "/") && (chain.length === 0 || chain[0].path !== homeRoute.path)) {
127
- chain.unshift(homeRoute);
55
+ if (node.children) {
56
+ for (const child of node.children) {
57
+ if (isExternalPath(child.path)) continue;
58
+ const result = findInternalMatch(child, pathname, [...ancestors, node]);
59
+ if (result) return result;
60
+ }
128
61
  }
129
- const last = chain[chain.length - 1];
130
- if (last && normalizedPathname !== last.path && normalizedPathname.startsWith(last.path.endsWith("/") ? last.path : last.path + "/")) {
131
- const segments = normalizedPathname.split("/").filter(Boolean);
132
- const tail = segments[segments.length - 1];
133
- chain.push({ zone, path: normalizedPathname, label: labelFromSlug(tail) });
62
+ const pathPrefix = nodePath === "/" ? "/" : nodePath + "/";
63
+ if (pathname.startsWith(pathPrefix) || pathname === nodePath) {
64
+ return { node, ancestors };
134
65
  }
135
- return chain.map((r) => ({ label: r.label, href: r.path }));
66
+ return null;
136
67
  }
137
- function transformHrefForZone(targetPath, currentZone, options) {
138
- const { isDev, prodUrl } = options;
139
- const targetZone = getZoneFromPathname(targetPath);
140
- const isCrossZone = targetZone !== currentZone;
141
- if (isCrossZone && isDev && prodUrl) {
142
- return `${prodUrl}${targetPath}`;
68
+ function getBreadcrumbs(routes, pathname) {
69
+ const normalizedPathname = normalizePath(pathname);
70
+ const { externalAncestors, appRoot } = findAppRootWithAncestors(routes);
71
+ if (!appRoot) {
72
+ return externalAncestors.map((node) => ({
73
+ label: node.label,
74
+ href: node.path
75
+ }));
143
76
  }
144
- const currentZoneRoot = getZoneRoot(currentZone);
145
- if (!isCrossZone && isDev && currentZoneRoot !== "/") {
146
- return targetPath.replace(currentZoneRoot, "") || "/";
77
+ const breadcrumbs = externalAncestors.map((node) => ({
78
+ label: node.label,
79
+ href: node.path
80
+ }));
81
+ const internalMatch = findInternalMatch(appRoot, normalizedPathname);
82
+ if (!internalMatch) {
83
+ breadcrumbs.push({ label: appRoot.label, href: appRoot.path });
84
+ return breadcrumbs;
147
85
  }
148
- return targetPath;
149
- }
150
- function getZoneFromPathname(pathname) {
151
- if (pathname.startsWith("/lokaler")) return "lokaler";
152
- return "sbno";
153
- }
154
- function getZonePrefixBreadcrumbs(zone) {
155
- const zoneRoot = getZoneRoot(zone);
156
- return getBreadcrumbs(zone, zoneRoot);
86
+ for (const ancestor of internalMatch.ancestors) {
87
+ breadcrumbs.push({
88
+ label: ancestor.label,
89
+ href: ancestor.path
90
+ });
91
+ }
92
+ breadcrumbs.push({
93
+ label: internalMatch.node.label,
94
+ href: internalMatch.node.path
95
+ });
96
+ const matchedPath = normalizePath(internalMatch.node.path);
97
+ if (normalizedPathname !== matchedPath) {
98
+ const remainingPath = normalizedPathname.slice(
99
+ matchedPath === "/" ? 1 : matchedPath.length + 1
100
+ );
101
+ const dynamicSegments = remainingPath.split("/").filter(Boolean);
102
+ let accumulatedPath = matchedPath;
103
+ for (const segment of dynamicSegments) {
104
+ accumulatedPath = accumulatedPath === "/" ? `/${segment}` : `${accumulatedPath}/${segment}`;
105
+ breadcrumbs.push({
106
+ label: formatSegmentLabel(segment),
107
+ href: accumulatedPath
108
+ });
109
+ }
110
+ }
111
+ return breadcrumbs;
157
112
  }
158
113
 
159
114
  // src/store/globalState.ts
@@ -165,7 +120,6 @@ var creator = (set, get) => ({
165
120
  theme: "light",
166
121
  locale: "no",
167
122
  isMenuOpen: false,
168
- zone: null,
169
123
  breadcrumbs: null,
170
124
  setUser: (user) => set({ user }),
171
125
  setTheme: (theme) => {
@@ -177,7 +131,6 @@ var creator = (set, get) => ({
177
131
  setLocale: (locale) => set({ locale }),
178
132
  setIsMenuOpen: (isMenuOpen) => set({ isMenuOpen }),
179
133
  toggleMenu: () => set((state) => ({ isMenuOpen: !state.isMenuOpen })),
180
- setZone: (zone) => set({ zone }),
181
134
  setBreadcrumbs: (breadcrumbs) => set({ breadcrumbs }),
182
135
  initialize: () => {
183
136
  if (typeof document !== "undefined" && ALLOW_DARK_THEME) {
@@ -226,17 +179,26 @@ function useLayoutTranslation() {
226
179
  }
227
180
 
228
181
  // src/components/Breadcrumbs/Breadcrumbs.tsx
182
+ import styles from "./Breadcrumbs.module.css";
229
183
  import { jsx } from "react/jsx-runtime";
230
- function SbBreadcrumbs({ className, zone }) {
184
+ function SbBreadcrumbs({ className, routes }) {
231
185
  const pathname = usePathname();
232
186
  const manualBreadcrumbs = useGlobalStore((state) => state.breadcrumbs);
233
- const isDev = process.env.NODE_ENV === "development";
234
- const prodUrl = "https://www.statsbygg.no";
235
- const zoneRoot = getZoneRoot(zone);
236
187
  const { t } = useLayoutTranslation();
237
- const fullPath = isDev && zoneRoot !== "/" && !pathname.startsWith(zoneRoot) ? `${zoneRoot}${pathname}` : pathname;
238
- const breadcrumbs = manualBreadcrumbs != null ? manualBreadcrumbs : getBreadcrumbs(zone, fullPath);
239
- if (breadcrumbs.length <= 1) {
188
+ const breadcrumbs = useMemo(() => {
189
+ if (manualBreadcrumbs) {
190
+ const { externalAncestors } = findAppRootWithAncestors(routes);
191
+ const baseBreadcrumbs = externalAncestors.map((node) => ({
192
+ label: node.label,
193
+ href: node.path
194
+ }));
195
+ const manualHrefs = new Set(manualBreadcrumbs.map((b) => b.href));
196
+ const uniqueBase = baseBreadcrumbs.filter((b) => !manualHrefs.has(b.href));
197
+ return [...uniqueBase, ...manualBreadcrumbs];
198
+ }
199
+ return getBreadcrumbs(routes, pathname);
200
+ }, [routes, pathname, manualBreadcrumbs]);
201
+ if (breadcrumbs.length === 0) {
240
202
  return null;
241
203
  }
242
204
  return /* @__PURE__ */ jsx(
@@ -246,16 +208,15 @@ function SbBreadcrumbs({ className, zone }) {
246
208
  className: clsx(styles.breadcrumbs, className),
247
209
  children: /* @__PURE__ */ jsx(Breadcrumbs.List, { children: breadcrumbs.map((crumb, index) => {
248
210
  const isLast = index === breadcrumbs.length - 1;
249
- const href = manualBreadcrumbs ? crumb.href : transformHrefForZone(crumb.href, zone, { isDev, prodUrl });
250
211
  return /* @__PURE__ */ jsx(Breadcrumbs.Item, { children: /* @__PURE__ */ jsx(
251
212
  Breadcrumbs.Link,
252
213
  {
253
- href,
214
+ href: crumb.href,
254
215
  "aria-current": isLast ? "page" : void 0,
255
216
  className: isLast ? styles.currentLink : styles.link,
256
217
  children: crumb.label
257
218
  }
258
- ) }, crumb.href);
219
+ ) }, `${crumb.href}-${index}`);
259
220
  }) })
260
221
  }
261
222
  );
@@ -290,9 +251,7 @@ var NAVIGATION_MENU = [
290
251
  label: "For leietakere",
291
252
  href: "/for-leietakere",
292
253
  children: [
293
- { label: "Leieveileder", href: "/leietakere/leieveileder" },
294
- { label: "Lenke til underside", href: "/leietakere/lenke1" },
295
- { label: "Lenke til underside", href: "/leietakere/lenke2" }
254
+ { label: "Leieveileder", href: "/for-leietakere/leieveileder" }
296
255
  ]
297
256
  },
298
257
  {
@@ -301,8 +260,7 @@ var NAVIGATION_MENU = [
301
260
  children: [
302
261
  { label: "V\xE5re krav", href: "/byggebransjen/vare-krav" },
303
262
  { label: "BIM", href: "/byggebransjen/bim" },
304
- { label: "ByggBoks", href: "/byggebransjen/byggboks" },
305
- { label: "Lenke til underside", href: "/byggebransjen/lenke" }
263
+ { label: "ByggBoks", href: "/byggebransjen/byggboks" }
306
264
  ]
307
265
  },
308
266
  {
@@ -385,27 +343,74 @@ var NAVIGATION_MENU = [
385
343
  ];
386
344
 
387
345
  // src/components/NavigationMenuItem/NavigationMenuItem.tsx
388
- import { Link, List } from "@digdir/designsystemet-react";
346
+ import { List } from "@digdir/designsystemet-react";
389
347
  import { ExternalLink } from "lucide-react";
348
+ import { usePathname as usePathname2 } from "next/navigation";
349
+ import clsx2 from "clsx";
350
+
351
+ // src/components/SmartLink/SmartLink.tsx
352
+ import Link from "next/link";
353
+ import { Link as DsLink } from "@digdir/designsystemet-react";
354
+ import { jsx as jsx2 } from "react/jsx-runtime";
355
+ function SmartLink({
356
+ href,
357
+ appBasePath,
358
+ external = false,
359
+ className,
360
+ children
361
+ }) {
362
+ const isExternalUrl = href.startsWith("http://") || href.startsWith("https://");
363
+ const isLocalRoute = appBasePath && !isExternalUrl && (href === appBasePath || href.startsWith(`${appBasePath}/`));
364
+ if (isExternalUrl || external) {
365
+ return /* @__PURE__ */ jsx2(
366
+ DsLink,
367
+ {
368
+ href,
369
+ className,
370
+ target: "_blank",
371
+ rel: "noopener noreferrer",
372
+ children
373
+ }
374
+ );
375
+ }
376
+ if (isLocalRoute) {
377
+ const localHref = href.replace(appBasePath, "") || "/";
378
+ return /* @__PURE__ */ jsx2(Link, { href: localHref, className, children });
379
+ }
380
+ return /* @__PURE__ */ jsx2(DsLink, { href, className, children });
381
+ }
382
+
383
+ // src/components/NavigationMenuItem/NavigationMenuItem.tsx
390
384
  import styles2 from "./NavigationMenuItem.module.css";
391
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
392
- function NavigationMenuItem({ item, animationDelay, compact = false }) {
385
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
386
+ function NavigationMenuItem({
387
+ item,
388
+ animationDelay,
389
+ compact = false,
390
+ appBasePath
391
+ }) {
392
+ const pathname = usePathname2();
393
+ const isActive = (href) => {
394
+ if (href === "/") return pathname === "/";
395
+ return pathname == null ? void 0 : pathname.startsWith(href);
396
+ };
397
+ const isParentActive = isActive(item.href);
393
398
  if (compact) {
394
- return /* @__PURE__ */ jsx2(
399
+ return /* @__PURE__ */ jsx3(
395
400
  "div",
396
401
  {
397
402
  className: styles2.itemColumn,
398
403
  style: { animationDelay: `${animationDelay}ms` },
399
404
  children: /* @__PURE__ */ jsxs(
400
- Link,
405
+ SmartLink,
401
406
  {
402
407
  href: item.href,
403
- className: styles2.compactLink,
404
- target: item.external ? "_blank" : void 0,
405
- rel: item.external ? "noopener noreferrer" : void 0,
408
+ appBasePath,
409
+ external: item.external,
410
+ className: clsx2(styles2.compactLink, isParentActive && styles2.active),
406
411
  children: [
407
412
  item.label,
408
- item.external && /* @__PURE__ */ jsx2(ExternalLink, { size: 16, "aria-hidden": "true" })
413
+ item.external && /* @__PURE__ */ jsx3(ExternalLink, { size: 16, "aria-hidden": "true" })
409
414
  ]
410
415
  }
411
416
  )
@@ -419,39 +424,42 @@ function NavigationMenuItem({ item, animationDelay, compact = false }) {
419
424
  style: { animationDelay: `${animationDelay}ms` },
420
425
  children: [
421
426
  /* @__PURE__ */ jsxs(
422
- Link,
427
+ SmartLink,
423
428
  {
424
429
  href: item.href,
425
- className: styles2.parentLink,
426
- target: item.external ? "_blank" : void 0,
427
- rel: item.external ? "noopener noreferrer" : void 0,
430
+ appBasePath,
431
+ external: item.external,
432
+ className: clsx2(styles2.parentLink, isParentActive && styles2.active),
428
433
  children: [
429
434
  item.label,
430
- item.external && /* @__PURE__ */ jsx2(ExternalLink, { size: 20, "aria-hidden": "true" })
435
+ item.external && /* @__PURE__ */ jsx3(ExternalLink, { size: 20, "aria-hidden": "true" })
431
436
  ]
432
437
  }
433
438
  ),
434
- item.children && item.children.length > 0 && /* @__PURE__ */ jsx2(List.Unordered, { className: styles2.childItems, children: item.children.map((child, childIndex) => /* @__PURE__ */ jsx2(
435
- List.Item,
436
- {
437
- className: styles2.childItem,
438
- style: { animationDelay: `${animationDelay + 30 + childIndex * 15}ms` },
439
- children: /* @__PURE__ */ jsxs(
440
- Link,
441
- {
442
- href: child.href,
443
- className: styles2.childLink,
444
- target: child.external ? "_blank" : void 0,
445
- rel: child.external ? "noopener noreferrer" : void 0,
446
- children: [
447
- child.label,
448
- child.external && /* @__PURE__ */ jsx2(ExternalLink, { size: 20, "aria-hidden": "true" })
449
- ]
450
- }
451
- )
452
- },
453
- childIndex
454
- )) })
439
+ item.children && item.children.length > 0 && /* @__PURE__ */ jsx3(List.Unordered, { className: styles2.childItems, children: item.children.map((child, childIndex) => {
440
+ const isChildActive = isActive(child.href);
441
+ return /* @__PURE__ */ jsx3(
442
+ List.Item,
443
+ {
444
+ className: styles2.childItem,
445
+ style: { animationDelay: `${animationDelay + 30 + childIndex * 15}ms` },
446
+ children: /* @__PURE__ */ jsxs(
447
+ SmartLink,
448
+ {
449
+ href: child.href,
450
+ appBasePath,
451
+ external: child.external,
452
+ className: clsx2(styles2.childLink, isChildActive && styles2.active),
453
+ children: [
454
+ child.label,
455
+ child.external && /* @__PURE__ */ jsx3(ExternalLink, { size: 16, "aria-hidden": "true" })
456
+ ]
457
+ }
458
+ )
459
+ },
460
+ childIndex
461
+ );
462
+ }) })
455
463
  ]
456
464
  }
457
465
  );
@@ -459,8 +467,8 @@ function NavigationMenuItem({ item, animationDelay, compact = false }) {
459
467
 
460
468
  // src/components/NavigationMenu/NavigationMenu.tsx
461
469
  import styles3 from "./NavigationMenu.module.css";
462
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
463
- function NavigationMenu({ zone }) {
470
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
471
+ function NavigationMenu({ className, appBasePath }) {
464
472
  const [searchValue, setSearchValue] = useState("");
465
473
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
466
474
  const setIsMenuOpen = useGlobalStore((state) => state.setIsMenuOpen);
@@ -468,19 +476,17 @@ function NavigationMenu({ zone }) {
468
476
  const { t } = useLayoutTranslation();
469
477
  useEffect(() => {
470
478
  if (!isMenuOpen) return;
471
- const handleEscape = (event) => {
479
+ function handleEscape(event) {
472
480
  if (event.key === "Escape") {
473
481
  setIsMenuOpen(false);
474
482
  }
475
- };
483
+ }
476
484
  setTimeout(() => {
477
485
  var _a;
478
486
  (_a = searchInputRef.current) == null ? void 0 : _a.focus();
479
487
  }, 100);
480
488
  document.addEventListener("keydown", handleEscape);
481
- return () => {
482
- document.removeEventListener("keydown", handleEscape);
483
- };
489
+ return () => document.removeEventListener("keydown", handleEscape);
484
490
  }, [setIsMenuOpen, isMenuOpen]);
485
491
  function handleBackdropClick(e) {
486
492
  if (e.target === e.currentTarget) {
@@ -488,102 +494,67 @@ function NavigationMenu({ zone }) {
488
494
  }
489
495
  }
490
496
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
491
- isMenuOpen && /* @__PURE__ */ jsx3("div", { className: styles3.backdrop, onClick: handleBackdropClick }),
492
- /* @__PURE__ */ jsx3(
493
- "div",
494
- {
495
- className: `${styles3.menuOverlay} ${!isMenuOpen ? styles3.hidden : ""}`,
496
- children: /* @__PURE__ */ jsxs2("div", { className: styles3.container, children: [
497
- /* @__PURE__ */ jsxs2("div", { className: styles3.searchSection, children: [
498
- /* @__PURE__ */ jsx3(Heading, { level: 1, children: t("menu.helpTitle") }),
499
- /* @__PURE__ */ jsxs2(Search, { children: [
500
- /* @__PURE__ */ jsx3(
501
- Search.Input,
502
- {
503
- "aria-label": t("common.search"),
504
- ref: searchInputRef,
505
- value: searchValue,
506
- onChange: (e) => setSearchValue(e.target.value),
507
- placeholder: t("common.search"),
508
- className: styles3.searchField
509
- }
510
- ),
511
- /* @__PURE__ */ jsx3(Search.Clear, {})
512
- ] })
513
- ] }),
514
- /* @__PURE__ */ jsx3(
515
- "nav",
497
+ isMenuOpen && /* @__PURE__ */ jsx4("div", { className: styles3.backdrop, onClick: handleBackdropClick }),
498
+ /* @__PURE__ */ jsx4("div", { className: `${styles3.menuOverlay} ${!isMenuOpen ? styles3.hidden : ""} ${className != null ? className : ""}`, children: /* @__PURE__ */ jsxs2("div", { className: styles3.container, children: [
499
+ /* @__PURE__ */ jsxs2("div", { className: styles3.searchSection, children: [
500
+ /* @__PURE__ */ jsx4(Heading, { level: 1, children: t("menu.helpTitle") }),
501
+ /* @__PURE__ */ jsxs2(Search, { children: [
502
+ /* @__PURE__ */ jsx4(
503
+ Search.Input,
516
504
  {
517
- className: styles3.menuSections,
518
- "aria-label": t("menu.mainMenuLabel"),
519
- children: NAVIGATION_MENU.map(
520
- (section, sectionIndex) => {
521
- var _a;
522
- return /* @__PURE__ */ jsxs2(
523
- "section",
524
- {
525
- className: styles3.section,
526
- style: { animationDelay: `${0.15 + sectionIndex * 0.06}s` },
527
- children: [
528
- section.layout !== "subsections" && /* @__PURE__ */ jsx3(Heading, { level: 2, className: styles3.sectionHeader, children: section.title }),
529
- section.layout === "subsections" && section.subsections ? /* @__PURE__ */ jsx3("div", { className: styles3.subsectionsGrid, children: section.subsections.map(
530
- (subsection, subsectionIndex) => /* @__PURE__ */ jsxs2(
531
- "div",
532
- {
533
- className: styles3.subsection,
534
- children: [
535
- /* @__PURE__ */ jsx3(
536
- Heading,
537
- {
538
- level: 2,
539
- className: styles3.subsectionHeader,
540
- children: subsection.title
541
- }
542
- ),
543
- /* @__PURE__ */ jsx3("div", { className: styles3.subsectionItems, children: subsection.items.map((item, itemIndex) => /* @__PURE__ */ jsx3(
544
- NavigationMenuItem,
545
- {
546
- item,
547
- animationDelay: 150 + sectionIndex * 60 + subsectionIndex * 40 + itemIndex * 25,
548
- compact: true
549
- },
550
- itemIndex
551
- )) })
552
- ]
553
- },
554
- subsectionIndex
555
- )
556
- ) }) : /* @__PURE__ */ jsx3(
557
- "div",
558
- {
559
- className: sectionIndex === 2 ? styles3.itemsGridThreeCol : styles3.itemsGrid,
560
- children: (_a = section.items) == null ? void 0 : _a.map((item, itemIndex) => /* @__PURE__ */ jsx3(
561
- NavigationMenuItem,
562
- {
563
- item,
564
- animationDelay: 150 + sectionIndex * 60 + itemIndex * 25
565
- },
566
- itemIndex
567
- ))
568
- }
569
- )
570
- ]
571
- },
572
- sectionIndex
573
- );
574
- }
575
- )
505
+ "aria-label": t("common.search"),
506
+ ref: searchInputRef,
507
+ value: searchValue,
508
+ onChange: (e) => setSearchValue(e.target.value),
509
+ placeholder: t("common.search"),
510
+ className: styles3.searchField
576
511
  }
577
- )
512
+ ),
513
+ /* @__PURE__ */ jsx4(Search.Clear, {})
578
514
  ] })
579
- }
580
- )
515
+ ] }),
516
+ /* @__PURE__ */ jsx4("nav", { className: styles3.menuSections, "aria-label": t("menu.mainMenuLabel"), children: NAVIGATION_MENU.map((section, sectionIndex) => {
517
+ var _a;
518
+ return /* @__PURE__ */ jsxs2(
519
+ "section",
520
+ {
521
+ className: styles3.section,
522
+ style: { animationDelay: `${0.15 + sectionIndex * 0.06}s` },
523
+ children: [
524
+ section.layout !== "subsections" && /* @__PURE__ */ jsx4(Heading, { level: 2, className: styles3.sectionHeader, children: section.title }),
525
+ section.layout === "subsections" && section.subsections ? /* @__PURE__ */ jsx4("div", { className: styles3.subsectionsGrid, children: section.subsections.map((subsection, subsectionIndex) => /* @__PURE__ */ jsxs2("div", { className: styles3.subsection, children: [
526
+ /* @__PURE__ */ jsx4(Heading, { level: 2, className: styles3.subsectionHeader, children: subsection.title }),
527
+ /* @__PURE__ */ jsx4("div", { className: styles3.subsectionItems, children: subsection.items.map((item, itemIndex) => /* @__PURE__ */ jsx4(
528
+ NavigationMenuItem,
529
+ {
530
+ item,
531
+ animationDelay: 150 + sectionIndex * 60 + subsectionIndex * 40 + itemIndex * 25,
532
+ appBasePath,
533
+ compact: true
534
+ },
535
+ itemIndex
536
+ )) })
537
+ ] }, subsectionIndex)) }) : /* @__PURE__ */ jsx4("div", { className: sectionIndex === 2 ? styles3.itemsGridThreeCol : styles3.itemsGrid, children: (_a = section.items) == null ? void 0 : _a.map((item, itemIndex) => /* @__PURE__ */ jsx4(
538
+ NavigationMenuItem,
539
+ {
540
+ item,
541
+ animationDelay: 150 + sectionIndex * 60 + itemIndex * 25,
542
+ appBasePath
543
+ },
544
+ itemIndex
545
+ )) })
546
+ ]
547
+ },
548
+ sectionIndex
549
+ );
550
+ }) })
551
+ ] }) })
581
552
  ] });
582
553
  }
583
554
 
584
555
  // src/components/MenuButton/MenuButton.tsx
585
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
586
- function MenuButton({ zone }) {
556
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
557
+ function MenuButton({ className, appBasePath }) {
587
558
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
588
559
  const toggleMenu = useGlobalStore((state) => state.toggleMenu);
589
560
  const { t } = useLayoutTranslation();
@@ -595,21 +566,21 @@ function MenuButton({ zone }) {
595
566
  onClick: toggleMenu,
596
567
  "aria-expanded": isMenuOpen,
597
568
  "aria-label": isMenuOpen ? t("menu.closeMenu") : t("menu.openMenu"),
569
+ className,
598
570
  children: [
599
- isMenuOpen ? /* @__PURE__ */ jsx4(X, { size: 20, "aria-hidden": "true" }) : /* @__PURE__ */ jsx4(Menu, { size: 20, "aria-hidden": "true" }),
571
+ isMenuOpen ? /* @__PURE__ */ jsx5(X, { size: 20, "aria-hidden": "true" }) : /* @__PURE__ */ jsx5(Menu, { size: 20, "aria-hidden": "true" }),
600
572
  isMenuOpen ? t("common.close") : t("common.menu")
601
573
  ]
602
574
  }
603
575
  ),
604
- /* @__PURE__ */ jsx4(NavigationMenu, { zone })
576
+ /* @__PURE__ */ jsx5(NavigationMenu, { appBasePath })
605
577
  ] });
606
578
  }
607
579
 
608
580
  // src/components/GlobalHeader/GlobalHeader.tsx
609
581
  import styles4 from "./GlobalHeader.module.css";
610
- import { Search as Search2 } from "lucide-react";
611
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
612
- function GlobalHeader({ className, zone }) {
582
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
583
+ function GlobalHeader({ className, routes, appBasePath }) {
613
584
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
614
585
  const setIsMenuOpen = useGlobalStore((state) => state.setIsMenuOpen);
615
586
  const setTheme = useGlobalStore((state) => state.setTheme);
@@ -618,12 +589,19 @@ function GlobalHeader({ className, zone }) {
618
589
  function toggleDarkMode() {
619
590
  setTheme(theme === "dark" ? "light" : "dark");
620
591
  }
621
- return /* @__PURE__ */ jsx5("header", { className: clsx2(styles4.header, isMenuOpen && styles4.menuOpen, className), children: /* @__PURE__ */ jsxs4("div", { className: styles4.headerContainer, children: [
592
+ return /* @__PURE__ */ jsx6("header", { className: clsx3(styles4.header, isMenuOpen && styles4.menuOpen, className), children: /* @__PURE__ */ jsxs4("div", { className: styles4.headerContainer, children: [
622
593
  /* @__PURE__ */ jsxs4("div", { className: styles4.topBarContainer, children: [
623
- /* @__PURE__ */ jsx5(Link2, { href: "https://www.statsbygg.no", children: /* @__PURE__ */ jsx5("img", { src: "https://dok.statsbygg.no/wp-content/uploads/2025/11/Statsbygg_logo.svg", alt: "Logo", className: styles4.logo }) }),
594
+ /* @__PURE__ */ jsx6(Link2, { href: "https://www.statsbygg.no", children: /* @__PURE__ */ jsx6(
595
+ "img",
596
+ {
597
+ src: "https://dok.statsbygg.no/wp-content/uploads/2025/11/Statsbygg_logo.svg",
598
+ alt: "Logo",
599
+ className: styles4.logo
600
+ }
601
+ ) }),
624
602
  /* @__PURE__ */ jsxs4("div", { className: styles4.actionsContainer, children: [
625
603
  !isMenuOpen && /* @__PURE__ */ jsxs4(Fragment3, { children: [
626
- ALLOW_DARK_THEME && /* @__PURE__ */ jsx5(Button2, { onClick: toggleDarkMode, className: styles4.themeToggleButton, children: theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}" }),
604
+ ALLOW_DARK_THEME && /* @__PURE__ */ jsx6(Button2, { onClick: toggleDarkMode, className: styles4.themeToggleButton, children: theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}" }),
627
605
  /* @__PURE__ */ jsxs4(
628
606
  Button2,
629
607
  {
@@ -632,87 +610,64 @@ function GlobalHeader({ className, zone }) {
632
610
  className: styles4.searchButton,
633
611
  "aria-label": t("common.search"),
634
612
  children: [
635
- /* @__PURE__ */ jsx5(Search2, { size: 20, "aria-hidden": "true" }),
613
+ /* @__PURE__ */ jsx6(Search2, { size: 20, "aria-hidden": "true" }),
636
614
  t("common.search")
637
615
  ]
638
616
  }
639
617
  )
640
618
  ] }),
641
- /* @__PURE__ */ jsx5(MenuButton, { zone })
619
+ /* @__PURE__ */ jsx6(MenuButton, { appBasePath })
642
620
  ] })
643
621
  ] }),
644
- /* @__PURE__ */ jsx5(SbBreadcrumbs, { zone })
622
+ /* @__PURE__ */ jsx6(SbBreadcrumbs, { routes })
645
623
  ] }) });
646
624
  }
647
625
 
648
626
  // src/components/GlobalFooter/GlobalFooter.tsx
649
627
  import { Paragraph } from "@digdir/designsystemet-react";
650
- import clsx3 from "clsx";
628
+ import clsx4 from "clsx";
651
629
  import styles5 from "./GlobalFooter.module.css";
652
- import { jsx as jsx6 } from "react/jsx-runtime";
630
+ import { jsx as jsx7 } from "react/jsx-runtime";
653
631
  function GlobalFooter({ className }) {
654
632
  const { t } = useLayoutTranslation();
655
- return /* @__PURE__ */ jsx6("footer", { className: clsx3(styles5.footer, className), children: /* @__PURE__ */ jsx6("div", { className: styles5.container, children: /* @__PURE__ */ jsx6("div", { className: styles5.content, children: /* @__PURE__ */ jsx6(Paragraph, { children: t("footer.content") }) }) }) });
633
+ return /* @__PURE__ */ jsx7("footer", { className: clsx4(styles5.footer, className), children: /* @__PURE__ */ jsx7("div", { className: styles5.container, children: /* @__PURE__ */ jsx7("div", { className: styles5.content, children: /* @__PURE__ */ jsx7(Paragraph, { children: t("footer.content") }) }) }) });
656
634
  }
657
635
 
658
636
  // src/components/RootLayout/RootLayout.tsx
659
637
  import styles6 from "./RootLayout.module.css";
660
- import { SkipLink } from "@digdir/designsystemet-react";
661
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
662
- function RootLayout({
663
- children,
664
- zone,
665
- className
666
- }) {
638
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
639
+ function RootLayout({ children, routes, appBasePath, className }) {
667
640
  const initialize = useGlobalStore((state) => state.initialize);
668
- const setZone = useGlobalStore((state) => state.setZone);
669
641
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
670
642
  const { t } = useLayoutTranslation();
671
643
  useEffect2(() => {
672
- setZone(zone);
673
- }, [zone, setZone]);
674
- useEffect2(() => {
675
- try {
676
- const maybe = initialize();
677
- if (maybe && typeof maybe.then === "function") {
678
- maybe.catch((error) => {
679
- console.error("Failed to initialize global state:", error);
680
- });
681
- }
682
- } catch (error) {
683
- console.error("Failed to initialize global state:", error);
684
- }
644
+ initialize();
685
645
  }, [initialize]);
686
- return /* @__PURE__ */ jsxs5("div", { className: clsx4(styles6.root, isMenuOpen && styles6.menuOpen, className), "data-zone": zone, children: [
687
- /* @__PURE__ */ jsx7(SkipLink, { className: styles6.skipLink, href: "#main-content", children: t("common.skipLink") }),
688
- /* @__PURE__ */ jsx7(GlobalHeader, { zone }),
689
- /* @__PURE__ */ jsx7("main", { id: "main-content", tabIndex: -1, className: styles6.main, children }),
690
- /* @__PURE__ */ jsx7(GlobalFooter, {})
646
+ return /* @__PURE__ */ jsxs5("div", { className: clsx5(styles6.root, isMenuOpen && styles6.menuOpen, className), children: [
647
+ /* @__PURE__ */ jsx8(SkipLink, { className: styles6.skipLink, href: "#main-content", children: t("common.skipLink") }),
648
+ /* @__PURE__ */ jsx8(GlobalHeader, { routes, appBasePath }),
649
+ /* @__PURE__ */ jsx8("main", { id: "main-content", tabIndex: -1, className: styles6.main, children }),
650
+ /* @__PURE__ */ jsx8(GlobalFooter, {})
691
651
  ] });
692
652
  }
693
653
 
694
654
  // src/hooks/use-breadcrumbs.ts
695
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
655
+ import { useEffect as useEffect3 } from "react";
696
656
  function useBreadcrumbs(breadcrumbs) {
697
657
  const setBreadcrumbs = useGlobalStore((state) => state.setBreadcrumbs);
698
- const zone = useGlobalStore((state) => state.zone);
699
- const breadcrumbsStringified = JSON.stringify(breadcrumbs);
700
- const hasSetBreadcrumbs = useRef2(false);
658
+ const serialized = JSON.stringify(breadcrumbs);
701
659
  useEffect3(() => {
702
- if (!zone || hasSetBreadcrumbs.current) return;
703
- const zonePrefixBreadcrumbs = getZonePrefixBreadcrumbs(zone);
704
- const parsedBreadcrumbs = JSON.parse(breadcrumbsStringified);
705
- const fullBreadcrumbs = [...zonePrefixBreadcrumbs, ...parsedBreadcrumbs];
706
- setBreadcrumbs(fullBreadcrumbs);
707
- hasSetBreadcrumbs.current = true;
660
+ setBreadcrumbs(JSON.parse(serialized));
708
661
  return () => {
709
662
  setBreadcrumbs(null);
710
- hasSetBreadcrumbs.current = false;
711
663
  };
712
- }, [zone, breadcrumbsStringified, setBreadcrumbs]);
664
+ }, [serialized, setBreadcrumbs]);
713
665
  }
714
666
  export {
715
667
  RootLayout,
668
+ SmartLink,
669
+ findAppRootWithAncestors,
670
+ getBreadcrumbs,
716
671
  useBreadcrumbs,
717
672
  useGlobalStore
718
673
  };