@statsbygg/layout 0.1.12 → 0.1.14

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
- import { Button as Button2, Link as Link2 } from "@digdir/designsystemet-react";
9
- import clsx2 from "clsx";
9
+ import { Button as Button2, Link as Link3 } from "@digdir/designsystemet-react";
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
- });
48
+ return { externalAncestors: ancestors, appRoot: null };
97
49
  }
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;
50
+ function findInternalMatch(node, pathname, ancestors = []) {
51
+ const nodePath = normalizePath(node.path);
52
+ if (pathname === nodePath) {
53
+ return { node, ancestors };
108
54
  }
109
- }
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;
124
- }
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) {
@@ -225,37 +178,61 @@ function useLayoutTranslation() {
225
178
  return { t, locale };
226
179
  }
227
180
 
228
- // src/components/Breadcrumbs/Breadcrumbs.tsx
181
+ // src/components/BreadcrumbLink/BreadcrumbLink.tsx
182
+ import Link from "next/link";
229
183
  import { jsx } from "react/jsx-runtime";
230
- function SbBreadcrumbs({ className, zone }) {
184
+ function BreadcrumbLink({
185
+ href,
186
+ className,
187
+ ariaCurrent,
188
+ children
189
+ }) {
190
+ const isExternal = href.startsWith("http://") || href.startsWith("https://");
191
+ if (isExternal) {
192
+ return /* @__PURE__ */ jsx("a", { href, className, "aria-current": ariaCurrent, children });
193
+ }
194
+ return /* @__PURE__ */ jsx(Link, { href, className, "aria-current": ariaCurrent, children });
195
+ }
196
+
197
+ // src/components/Breadcrumbs/Breadcrumbs.tsx
198
+ import styles from "./Breadcrumbs.module.css";
199
+ import { jsx as jsx2 } from "react/jsx-runtime";
200
+ function SbBreadcrumbs({ className, routes, appBasePath }) {
231
201
  const pathname = usePathname();
232
202
  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
203
  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) {
204
+ const breadcrumbs = useMemo(() => {
205
+ if (manualBreadcrumbs) {
206
+ const { externalAncestors } = findAppRootWithAncestors(routes);
207
+ const baseBreadcrumbs = externalAncestors.map((node) => ({
208
+ label: node.label,
209
+ href: node.path
210
+ }));
211
+ const manualHrefs = new Set(manualBreadcrumbs.map((b) => b.href));
212
+ const uniqueBase = baseBreadcrumbs.filter((b) => !manualHrefs.has(b.href));
213
+ return [...uniqueBase, ...manualBreadcrumbs];
214
+ }
215
+ return getBreadcrumbs(routes, pathname);
216
+ }, [routes, pathname, manualBreadcrumbs]);
217
+ if (breadcrumbs.length === 0) {
240
218
  return null;
241
219
  }
242
- return /* @__PURE__ */ jsx(
220
+ return /* @__PURE__ */ jsx2(
243
221
  Breadcrumbs,
244
222
  {
245
223
  "aria-label": t("common.youAreHere"),
246
224
  className: clsx(styles.breadcrumbs, className),
247
- children: /* @__PURE__ */ jsx(Breadcrumbs.List, { children: breadcrumbs.map((crumb, index) => {
225
+ children: /* @__PURE__ */ jsx2(Breadcrumbs.List, { children: breadcrumbs.map((crumb, index) => {
248
226
  const isLast = index === breadcrumbs.length - 1;
249
- const href = manualBreadcrumbs ? crumb.href : transformHrefForZone(crumb.href, zone, { isDev, prodUrl });
250
- return /* @__PURE__ */ jsx(Breadcrumbs.Item, { children: /* @__PURE__ */ jsx(
251
- Breadcrumbs.Link,
227
+ return /* @__PURE__ */ jsx2(Breadcrumbs.Item, { children: /* @__PURE__ */ jsx2(
228
+ BreadcrumbLink,
252
229
  {
253
- href,
254
- "aria-current": isLast ? "page" : void 0,
230
+ href: crumb.href,
231
+ ariaCurrent: isLast ? "page" : void 0,
255
232
  className: isLast ? styles.currentLink : styles.link,
256
233
  children: crumb.label
257
234
  }
258
- ) }, crumb.href);
235
+ ) }, `${crumb.href}-${index}`);
259
236
  }) })
260
237
  }
261
238
  );
@@ -290,9 +267,7 @@ var NAVIGATION_MENU = [
290
267
  label: "For leietakere",
291
268
  href: "/for-leietakere",
292
269
  children: [
293
- { label: "Leieveileder", href: "/leietakere/leieveileder" },
294
- { label: "Lenke til underside", href: "/leietakere/lenke1" },
295
- { label: "Lenke til underside", href: "/leietakere/lenke2" }
270
+ { label: "Leieveileder", href: "/for-leietakere/leieveileder" }
296
271
  ]
297
272
  },
298
273
  {
@@ -301,8 +276,7 @@ var NAVIGATION_MENU = [
301
276
  children: [
302
277
  { label: "V\xE5re krav", href: "/byggebransjen/vare-krav" },
303
278
  { label: "BIM", href: "/byggebransjen/bim" },
304
- { label: "ByggBoks", href: "/byggebransjen/byggboks" },
305
- { label: "Lenke til underside", href: "/byggebransjen/lenke" }
279
+ { label: "ByggBoks", href: "/byggebransjen/byggboks" }
306
280
  ]
307
281
  },
308
282
  {
@@ -385,27 +359,74 @@ var NAVIGATION_MENU = [
385
359
  ];
386
360
 
387
361
  // src/components/NavigationMenuItem/NavigationMenuItem.tsx
388
- import { Link, List } from "@digdir/designsystemet-react";
362
+ import { List } from "@digdir/designsystemet-react";
389
363
  import { ExternalLink } from "lucide-react";
364
+ import { usePathname as usePathname2 } from "next/navigation";
365
+ import clsx2 from "clsx";
366
+
367
+ // src/components/SmartLink/SmartLink.tsx
368
+ import Link2 from "next/link";
369
+ import { Link as DsLink } from "@digdir/designsystemet-react";
370
+ import { jsx as jsx3 } from "react/jsx-runtime";
371
+ function SmartLink({
372
+ href,
373
+ appBasePath,
374
+ external = false,
375
+ className,
376
+ children
377
+ }) {
378
+ const isExternalUrl = href.startsWith("http://") || href.startsWith("https://");
379
+ const isLocalRoute = appBasePath && !isExternalUrl && (href === appBasePath || href.startsWith(`${appBasePath}/`));
380
+ if (isExternalUrl || external) {
381
+ return /* @__PURE__ */ jsx3(
382
+ DsLink,
383
+ {
384
+ href,
385
+ className,
386
+ target: "_blank",
387
+ rel: "noopener noreferrer",
388
+ children
389
+ }
390
+ );
391
+ }
392
+ if (isLocalRoute) {
393
+ const localHref = href.replace(appBasePath, "") || "/";
394
+ return /* @__PURE__ */ jsx3(Link2, { href: localHref, className, children });
395
+ }
396
+ return /* @__PURE__ */ jsx3(DsLink, { href, className, children });
397
+ }
398
+
399
+ // src/components/NavigationMenuItem/NavigationMenuItem.tsx
390
400
  import styles2 from "./NavigationMenuItem.module.css";
391
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
392
- function NavigationMenuItem({ item, animationDelay, compact = false }) {
401
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
402
+ function NavigationMenuItem({
403
+ item,
404
+ animationDelay,
405
+ compact = false,
406
+ appBasePath
407
+ }) {
408
+ const pathname = usePathname2();
409
+ const isActive = (href) => {
410
+ if (href === "/") return pathname === "/";
411
+ return pathname == null ? void 0 : pathname.startsWith(href);
412
+ };
413
+ const isParentActive = isActive(item.href);
393
414
  if (compact) {
394
- return /* @__PURE__ */ jsx2(
415
+ return /* @__PURE__ */ jsx4(
395
416
  "div",
396
417
  {
397
418
  className: styles2.itemColumn,
398
419
  style: { animationDelay: `${animationDelay}ms` },
399
420
  children: /* @__PURE__ */ jsxs(
400
- Link,
421
+ SmartLink,
401
422
  {
402
423
  href: item.href,
403
- className: styles2.compactLink,
404
- target: item.external ? "_blank" : void 0,
405
- rel: item.external ? "noopener noreferrer" : void 0,
424
+ appBasePath,
425
+ external: item.external,
426
+ className: clsx2(styles2.compactLink, isParentActive && styles2.active),
406
427
  children: [
407
428
  item.label,
408
- item.external && /* @__PURE__ */ jsx2(ExternalLink, { size: 16, "aria-hidden": "true" })
429
+ item.external && /* @__PURE__ */ jsx4(ExternalLink, { size: 16, "aria-hidden": "true" })
409
430
  ]
410
431
  }
411
432
  )
@@ -419,39 +440,42 @@ function NavigationMenuItem({ item, animationDelay, compact = false }) {
419
440
  style: { animationDelay: `${animationDelay}ms` },
420
441
  children: [
421
442
  /* @__PURE__ */ jsxs(
422
- Link,
443
+ SmartLink,
423
444
  {
424
445
  href: item.href,
425
- className: styles2.parentLink,
426
- target: item.external ? "_blank" : void 0,
427
- rel: item.external ? "noopener noreferrer" : void 0,
446
+ appBasePath,
447
+ external: item.external,
448
+ className: clsx2(styles2.parentLink, isParentActive && styles2.active),
428
449
  children: [
429
450
  item.label,
430
- item.external && /* @__PURE__ */ jsx2(ExternalLink, { size: 20, "aria-hidden": "true" })
451
+ item.external && /* @__PURE__ */ jsx4(ExternalLink, { size: 20, "aria-hidden": "true" })
431
452
  ]
432
453
  }
433
454
  ),
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
- )) })
455
+ item.children && item.children.length > 0 && /* @__PURE__ */ jsx4(List.Unordered, { className: styles2.childItems, children: item.children.map((child, childIndex) => {
456
+ const isChildActive = isActive(child.href);
457
+ return /* @__PURE__ */ jsx4(
458
+ List.Item,
459
+ {
460
+ className: styles2.childItem,
461
+ style: { animationDelay: `${animationDelay + 30 + childIndex * 15}ms` },
462
+ children: /* @__PURE__ */ jsxs(
463
+ SmartLink,
464
+ {
465
+ href: child.href,
466
+ appBasePath,
467
+ external: child.external,
468
+ className: clsx2(styles2.childLink, isChildActive && styles2.active),
469
+ children: [
470
+ child.label,
471
+ child.external && /* @__PURE__ */ jsx4(ExternalLink, { size: 16, "aria-hidden": "true" })
472
+ ]
473
+ }
474
+ )
475
+ },
476
+ childIndex
477
+ );
478
+ }) })
455
479
  ]
456
480
  }
457
481
  );
@@ -459,8 +483,8 @@ function NavigationMenuItem({ item, animationDelay, compact = false }) {
459
483
 
460
484
  // src/components/NavigationMenu/NavigationMenu.tsx
461
485
  import styles3 from "./NavigationMenu.module.css";
462
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
463
- function NavigationMenu({ zone }) {
486
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
487
+ function NavigationMenu({ className, appBasePath }) {
464
488
  const [searchValue, setSearchValue] = useState("");
465
489
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
466
490
  const setIsMenuOpen = useGlobalStore((state) => state.setIsMenuOpen);
@@ -468,19 +492,17 @@ function NavigationMenu({ zone }) {
468
492
  const { t } = useLayoutTranslation();
469
493
  useEffect(() => {
470
494
  if (!isMenuOpen) return;
471
- const handleEscape = (event) => {
495
+ function handleEscape(event) {
472
496
  if (event.key === "Escape") {
473
497
  setIsMenuOpen(false);
474
498
  }
475
- };
499
+ }
476
500
  setTimeout(() => {
477
501
  var _a;
478
502
  (_a = searchInputRef.current) == null ? void 0 : _a.focus();
479
503
  }, 100);
480
504
  document.addEventListener("keydown", handleEscape);
481
- return () => {
482
- document.removeEventListener("keydown", handleEscape);
483
- };
505
+ return () => document.removeEventListener("keydown", handleEscape);
484
506
  }, [setIsMenuOpen, isMenuOpen]);
485
507
  function handleBackdropClick(e) {
486
508
  if (e.target === e.currentTarget) {
@@ -488,102 +510,67 @@ function NavigationMenu({ zone }) {
488
510
  }
489
511
  }
490
512
  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",
513
+ isMenuOpen && /* @__PURE__ */ jsx5("div", { className: styles3.backdrop, onClick: handleBackdropClick }),
514
+ /* @__PURE__ */ jsx5("div", { className: `${styles3.menuOverlay} ${!isMenuOpen ? styles3.hidden : ""} ${className != null ? className : ""}`, children: /* @__PURE__ */ jsxs2("div", { className: styles3.container, children: [
515
+ /* @__PURE__ */ jsxs2("div", { className: styles3.searchSection, children: [
516
+ /* @__PURE__ */ jsx5(Heading, { level: 1, children: t("menu.helpTitle") }),
517
+ /* @__PURE__ */ jsxs2(Search, { children: [
518
+ /* @__PURE__ */ jsx5(
519
+ Search.Input,
516
520
  {
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
- )
521
+ "aria-label": t("common.search"),
522
+ ref: searchInputRef,
523
+ value: searchValue,
524
+ onChange: (e) => setSearchValue(e.target.value),
525
+ placeholder: t("common.search"),
526
+ className: styles3.searchField
576
527
  }
577
- )
528
+ ),
529
+ /* @__PURE__ */ jsx5(Search.Clear, {})
578
530
  ] })
579
- }
580
- )
531
+ ] }),
532
+ /* @__PURE__ */ jsx5("nav", { className: styles3.menuSections, "aria-label": t("menu.mainMenuLabel"), children: NAVIGATION_MENU.map((section, sectionIndex) => {
533
+ var _a;
534
+ return /* @__PURE__ */ jsxs2(
535
+ "section",
536
+ {
537
+ className: styles3.section,
538
+ style: { animationDelay: `${0.15 + sectionIndex * 0.06}s` },
539
+ children: [
540
+ section.layout !== "subsections" && /* @__PURE__ */ jsx5(Heading, { level: 2, className: styles3.sectionHeader, children: section.title }),
541
+ section.layout === "subsections" && section.subsections ? /* @__PURE__ */ jsx5("div", { className: styles3.subsectionsGrid, children: section.subsections.map((subsection, subsectionIndex) => /* @__PURE__ */ jsxs2("div", { className: styles3.subsection, children: [
542
+ /* @__PURE__ */ jsx5(Heading, { level: 2, className: styles3.subsectionHeader, children: subsection.title }),
543
+ /* @__PURE__ */ jsx5("div", { className: styles3.subsectionItems, children: subsection.items.map((item, itemIndex) => /* @__PURE__ */ jsx5(
544
+ NavigationMenuItem,
545
+ {
546
+ item,
547
+ animationDelay: 150 + sectionIndex * 60 + subsectionIndex * 40 + itemIndex * 25,
548
+ appBasePath,
549
+ compact: true
550
+ },
551
+ itemIndex
552
+ )) })
553
+ ] }, subsectionIndex)) }) : /* @__PURE__ */ jsx5("div", { className: sectionIndex === 2 ? styles3.itemsGridThreeCol : styles3.itemsGrid, children: (_a = section.items) == null ? void 0 : _a.map((item, itemIndex) => /* @__PURE__ */ jsx5(
554
+ NavigationMenuItem,
555
+ {
556
+ item,
557
+ animationDelay: 150 + sectionIndex * 60 + itemIndex * 25,
558
+ appBasePath
559
+ },
560
+ itemIndex
561
+ )) })
562
+ ]
563
+ },
564
+ sectionIndex
565
+ );
566
+ }) })
567
+ ] }) })
581
568
  ] });
582
569
  }
583
570
 
584
571
  // src/components/MenuButton/MenuButton.tsx
585
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
586
- function MenuButton({ zone }) {
572
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
573
+ function MenuButton({ className, appBasePath }) {
587
574
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
588
575
  const toggleMenu = useGlobalStore((state) => state.toggleMenu);
589
576
  const { t } = useLayoutTranslation();
@@ -595,21 +582,21 @@ function MenuButton({ zone }) {
595
582
  onClick: toggleMenu,
596
583
  "aria-expanded": isMenuOpen,
597
584
  "aria-label": isMenuOpen ? t("menu.closeMenu") : t("menu.openMenu"),
585
+ className,
598
586
  children: [
599
- isMenuOpen ? /* @__PURE__ */ jsx4(X, { size: 20, "aria-hidden": "true" }) : /* @__PURE__ */ jsx4(Menu, { size: 20, "aria-hidden": "true" }),
587
+ isMenuOpen ? /* @__PURE__ */ jsx6(X, { size: 20, "aria-hidden": "true" }) : /* @__PURE__ */ jsx6(Menu, { size: 20, "aria-hidden": "true" }),
600
588
  isMenuOpen ? t("common.close") : t("common.menu")
601
589
  ]
602
590
  }
603
591
  ),
604
- /* @__PURE__ */ jsx4(NavigationMenu, { zone })
592
+ /* @__PURE__ */ jsx6(NavigationMenu, { appBasePath })
605
593
  ] });
606
594
  }
607
595
 
608
596
  // src/components/GlobalHeader/GlobalHeader.tsx
609
597
  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 }) {
598
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
599
+ function GlobalHeader({ className, routes, appBasePath }) {
613
600
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
614
601
  const setIsMenuOpen = useGlobalStore((state) => state.setIsMenuOpen);
615
602
  const setTheme = useGlobalStore((state) => state.setTheme);
@@ -618,12 +605,19 @@ function GlobalHeader({ className, zone }) {
618
605
  function toggleDarkMode() {
619
606
  setTheme(theme === "dark" ? "light" : "dark");
620
607
  }
621
- return /* @__PURE__ */ jsx5("header", { className: clsx2(styles4.header, isMenuOpen && styles4.menuOpen, className), children: /* @__PURE__ */ jsxs4("div", { className: styles4.headerContainer, children: [
608
+ return /* @__PURE__ */ jsx7("header", { className: clsx3(styles4.header, isMenuOpen && styles4.menuOpen, className), children: /* @__PURE__ */ jsxs4("div", { className: styles4.headerContainer, children: [
622
609
  /* @__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 }) }),
610
+ /* @__PURE__ */ jsx7(Link3, { href: "https://www.statsbygg.no", children: /* @__PURE__ */ jsx7(
611
+ "img",
612
+ {
613
+ src: "https://dok.statsbygg.no/wp-content/uploads/2025/11/Statsbygg_logo.svg",
614
+ alt: "Logo",
615
+ className: styles4.logo
616
+ }
617
+ ) }),
624
618
  /* @__PURE__ */ jsxs4("div", { className: styles4.actionsContainer, children: [
625
619
  !isMenuOpen && /* @__PURE__ */ jsxs4(Fragment3, { children: [
626
- ALLOW_DARK_THEME && /* @__PURE__ */ jsx5(Button2, { onClick: toggleDarkMode, className: styles4.themeToggleButton, children: theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}" }),
620
+ ALLOW_DARK_THEME && /* @__PURE__ */ jsx7(Button2, { onClick: toggleDarkMode, className: styles4.themeToggleButton, children: theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}" }),
627
621
  /* @__PURE__ */ jsxs4(
628
622
  Button2,
629
623
  {
@@ -632,87 +626,64 @@ function GlobalHeader({ className, zone }) {
632
626
  className: styles4.searchButton,
633
627
  "aria-label": t("common.search"),
634
628
  children: [
635
- /* @__PURE__ */ jsx5(Search2, { size: 20, "aria-hidden": "true" }),
629
+ /* @__PURE__ */ jsx7(Search2, { size: 20, "aria-hidden": "true" }),
636
630
  t("common.search")
637
631
  ]
638
632
  }
639
633
  )
640
634
  ] }),
641
- /* @__PURE__ */ jsx5(MenuButton, { zone })
635
+ /* @__PURE__ */ jsx7(MenuButton, { appBasePath })
642
636
  ] })
643
637
  ] }),
644
- /* @__PURE__ */ jsx5(SbBreadcrumbs, { zone })
638
+ /* @__PURE__ */ jsx7(SbBreadcrumbs, { routes, appBasePath })
645
639
  ] }) });
646
640
  }
647
641
 
648
642
  // src/components/GlobalFooter/GlobalFooter.tsx
649
643
  import { Paragraph } from "@digdir/designsystemet-react";
650
- import clsx3 from "clsx";
644
+ import clsx4 from "clsx";
651
645
  import styles5 from "./GlobalFooter.module.css";
652
- import { jsx as jsx6 } from "react/jsx-runtime";
646
+ import { jsx as jsx8 } from "react/jsx-runtime";
653
647
  function GlobalFooter({ className }) {
654
648
  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") }) }) }) });
649
+ return /* @__PURE__ */ jsx8("footer", { className: clsx4(styles5.footer, className), children: /* @__PURE__ */ jsx8("div", { className: styles5.container, children: /* @__PURE__ */ jsx8("div", { className: styles5.content, children: /* @__PURE__ */ jsx8(Paragraph, { children: t("footer.content") }) }) }) });
656
650
  }
657
651
 
658
652
  // src/components/RootLayout/RootLayout.tsx
659
653
  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
- }) {
654
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
655
+ function RootLayout({ children, routes, appBasePath, className }) {
667
656
  const initialize = useGlobalStore((state) => state.initialize);
668
- const setZone = useGlobalStore((state) => state.setZone);
669
657
  const isMenuOpen = useGlobalStore((state) => state.isMenuOpen);
670
658
  const { t } = useLayoutTranslation();
671
659
  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
- }
660
+ initialize();
685
661
  }, [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, {})
662
+ return /* @__PURE__ */ jsxs5("div", { className: clsx5(styles6.root, isMenuOpen && styles6.menuOpen, className), children: [
663
+ /* @__PURE__ */ jsx9(SkipLink, { className: styles6.skipLink, href: "#main-content", children: t("common.skipLink") }),
664
+ /* @__PURE__ */ jsx9(GlobalHeader, { routes, appBasePath }),
665
+ /* @__PURE__ */ jsx9("main", { id: "main-content", tabIndex: -1, className: styles6.main, children }),
666
+ /* @__PURE__ */ jsx9(GlobalFooter, {})
691
667
  ] });
692
668
  }
693
669
 
694
670
  // src/hooks/use-breadcrumbs.ts
695
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
671
+ import { useEffect as useEffect3 } from "react";
696
672
  function useBreadcrumbs(breadcrumbs) {
697
673
  const setBreadcrumbs = useGlobalStore((state) => state.setBreadcrumbs);
698
- const zone = useGlobalStore((state) => state.zone);
699
- const breadcrumbsStringified = JSON.stringify(breadcrumbs);
700
- const hasSetBreadcrumbs = useRef2(false);
674
+ const serialized = JSON.stringify(breadcrumbs);
701
675
  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;
676
+ setBreadcrumbs(JSON.parse(serialized));
708
677
  return () => {
709
678
  setBreadcrumbs(null);
710
- hasSetBreadcrumbs.current = false;
711
679
  };
712
- }, [zone, breadcrumbsStringified, setBreadcrumbs]);
680
+ }, [serialized, setBreadcrumbs]);
713
681
  }
714
682
  export {
715
683
  RootLayout,
684
+ SmartLink,
685
+ findAppRootWithAncestors,
686
+ getBreadcrumbs,
716
687
  useBreadcrumbs,
717
688
  useGlobalStore
718
689
  };