@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/README.md +80 -9
- package/dist/NavigationMenuItem.module.css +20 -0
- package/dist/index.d.ts +58 -10
- package/dist/index.js +307 -336
- 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
|
-
import { Button as Button2, Link as
|
|
9
|
-
import
|
|
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
|
-
|
|
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
|
-
});
|
|
48
|
+
return { externalAncestors: ancestors, appRoot: null };
|
|
97
49
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
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) {
|
|
@@ -225,37 +178,61 @@ function useLayoutTranslation() {
|
|
|
225
178
|
return { t, locale };
|
|
226
179
|
}
|
|
227
180
|
|
|
228
|
-
// src/components/
|
|
181
|
+
// src/components/BreadcrumbLink/BreadcrumbLink.tsx
|
|
182
|
+
import Link from "next/link";
|
|
229
183
|
import { jsx } from "react/jsx-runtime";
|
|
230
|
-
function
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
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__ */
|
|
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__ */
|
|
225
|
+
children: /* @__PURE__ */ jsx2(Breadcrumbs.List, { children: breadcrumbs.map((crumb, index) => {
|
|
248
226
|
const isLast = index === breadcrumbs.length - 1;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
Breadcrumbs.Link,
|
|
227
|
+
return /* @__PURE__ */ jsx2(Breadcrumbs.Item, { children: /* @__PURE__ */ jsx2(
|
|
228
|
+
BreadcrumbLink,
|
|
252
229
|
{
|
|
253
|
-
href,
|
|
254
|
-
|
|
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 {
|
|
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
|
|
392
|
-
function NavigationMenuItem({
|
|
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__ */
|
|
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
|
-
|
|
421
|
+
SmartLink,
|
|
401
422
|
{
|
|
402
423
|
href: item.href,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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__ */
|
|
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
|
-
|
|
443
|
+
SmartLink,
|
|
423
444
|
{
|
|
424
445
|
href: item.href,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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__ */
|
|
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__ */
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
463
|
-
function NavigationMenu({
|
|
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
|
-
|
|
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__ */
|
|
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",
|
|
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
|
-
|
|
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
|
-
)
|
|
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
|
|
586
|
-
function MenuButton({
|
|
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__ */
|
|
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__ */
|
|
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 {
|
|
611
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
629
|
+
/* @__PURE__ */ jsx7(Search2, { size: 20, "aria-hidden": "true" }),
|
|
636
630
|
t("common.search")
|
|
637
631
|
]
|
|
638
632
|
}
|
|
639
633
|
)
|
|
640
634
|
] }),
|
|
641
|
-
/* @__PURE__ */
|
|
635
|
+
/* @__PURE__ */ jsx7(MenuButton, { appBasePath })
|
|
642
636
|
] })
|
|
643
637
|
] }),
|
|
644
|
-
/* @__PURE__ */
|
|
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
|
|
644
|
+
import clsx4 from "clsx";
|
|
651
645
|
import styles5 from "./GlobalFooter.module.css";
|
|
652
|
-
import { jsx as
|
|
646
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
653
647
|
function GlobalFooter({ className }) {
|
|
654
648
|
const { t } = useLayoutTranslation();
|
|
655
|
-
return /* @__PURE__ */
|
|
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 {
|
|
661
|
-
|
|
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
|
-
|
|
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:
|
|
687
|
-
/* @__PURE__ */
|
|
688
|
-
/* @__PURE__ */
|
|
689
|
-
/* @__PURE__ */
|
|
690
|
-
/* @__PURE__ */
|
|
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
|
|
671
|
+
import { useEffect as useEffect3 } from "react";
|
|
696
672
|
function useBreadcrumbs(breadcrumbs) {
|
|
697
673
|
const setBreadcrumbs = useGlobalStore((state) => state.setBreadcrumbs);
|
|
698
|
-
const
|
|
699
|
-
const breadcrumbsStringified = JSON.stringify(breadcrumbs);
|
|
700
|
-
const hasSetBreadcrumbs = useRef2(false);
|
|
674
|
+
const serialized = JSON.stringify(breadcrumbs);
|
|
701
675
|
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;
|
|
676
|
+
setBreadcrumbs(JSON.parse(serialized));
|
|
708
677
|
return () => {
|
|
709
678
|
setBreadcrumbs(null);
|
|
710
|
-
hasSetBreadcrumbs.current = false;
|
|
711
679
|
};
|
|
712
|
-
}, [
|
|
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
|
};
|