@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/README.md +80 -9
- package/dist/NavigationMenuItem.module.css +20 -0
- package/dist/SmartLink.module.css +3 -0
- package/dist/index.d.ts +58 -10
- package/dist/index.js +284 -329
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
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
|
|
52
|
-
if (
|
|
53
|
-
return
|
|
23
|
+
function normalizePath(path) {
|
|
24
|
+
if (path === "/") return "/";
|
|
25
|
+
return path.replace(/\/+$/, "");
|
|
54
26
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
130
|
-
if (
|
|
131
|
-
|
|
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
|
|
66
|
+
return null;
|
|
136
67
|
}
|
|
137
|
-
function
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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,
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
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 {
|
|
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
|
|
392
|
-
function NavigationMenuItem({
|
|
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__ */
|
|
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
|
-
|
|
405
|
+
SmartLink,
|
|
401
406
|
{
|
|
402
407
|
href: item.href,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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__ */
|
|
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
|
-
|
|
427
|
+
SmartLink,
|
|
423
428
|
{
|
|
424
429
|
href: item.href,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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__ */
|
|
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__ */
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
463
|
-
function NavigationMenu({
|
|
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
|
-
|
|
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__ */
|
|
492
|
-
/* @__PURE__ */
|
|
493
|
-
"div",
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
|
586
|
-
function MenuButton({
|
|
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__ */
|
|
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__ */
|
|
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 {
|
|
611
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
613
|
+
/* @__PURE__ */ jsx6(Search2, { size: 20, "aria-hidden": "true" }),
|
|
636
614
|
t("common.search")
|
|
637
615
|
]
|
|
638
616
|
}
|
|
639
617
|
)
|
|
640
618
|
] }),
|
|
641
|
-
/* @__PURE__ */
|
|
619
|
+
/* @__PURE__ */ jsx6(MenuButton, { appBasePath })
|
|
642
620
|
] })
|
|
643
621
|
] }),
|
|
644
|
-
/* @__PURE__ */
|
|
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
|
|
628
|
+
import clsx4 from "clsx";
|
|
651
629
|
import styles5 from "./GlobalFooter.module.css";
|
|
652
|
-
import { jsx as
|
|
630
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
653
631
|
function GlobalFooter({ className }) {
|
|
654
632
|
const { t } = useLayoutTranslation();
|
|
655
|
-
return /* @__PURE__ */
|
|
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 {
|
|
661
|
-
|
|
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
|
-
|
|
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:
|
|
687
|
-
/* @__PURE__ */
|
|
688
|
-
/* @__PURE__ */
|
|
689
|
-
/* @__PURE__ */
|
|
690
|
-
/* @__PURE__ */
|
|
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
|
|
655
|
+
import { useEffect as useEffect3 } from "react";
|
|
696
656
|
function useBreadcrumbs(breadcrumbs) {
|
|
697
657
|
const setBreadcrumbs = useGlobalStore((state) => state.setBreadcrumbs);
|
|
698
|
-
const
|
|
699
|
-
const breadcrumbsStringified = JSON.stringify(breadcrumbs);
|
|
700
|
-
const hasSetBreadcrumbs = useRef2(false);
|
|
658
|
+
const serialized = JSON.stringify(breadcrumbs);
|
|
701
659
|
useEffect3(() => {
|
|
702
|
-
|
|
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
|
-
}, [
|
|
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
|
};
|