@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/README.md
CHANGED
|
@@ -63,26 +63,97 @@ src/
|
|
|
63
63
|
|
|
64
64
|
## Usage
|
|
65
65
|
|
|
66
|
-
###
|
|
66
|
+
### 1. Defining Your Route Tree
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
The routing system uses **URL-Driven Logic**. You define a single `RouteNode` tree passed to `RootLayout`.
|
|
69
|
+
|
|
70
|
+
- **External Links**: Any path starting with `http` or `https` is treated as an external parent.
|
|
71
|
+
- **Internal Links**: Any relative path (e.g., `/`, `/about`) is treated as internal to your application (relative to your `basePath`).
|
|
72
|
+
|
|
73
|
+
#### Scenario A: Standard Application
|
|
74
|
+
*This app sits directly under the main Statsbygg site.*
|
|
69
75
|
|
|
70
76
|
```tsx
|
|
71
|
-
|
|
72
|
-
import '@
|
|
73
|
-
|
|
77
|
+
// app/layout.tsx
|
|
78
|
+
import { RootLayout, RouteNode } from '@statsbygg/layout';
|
|
79
|
+
|
|
80
|
+
const ROUTES: RouteNode = {
|
|
81
|
+
label: 'Hjem',
|
|
82
|
+
path: 'https://statsbygg.no', // External Parent
|
|
83
|
+
children: [
|
|
84
|
+
{
|
|
85
|
+
label: 'My App',
|
|
86
|
+
path: '/', // App Root (relative to basePath)
|
|
87
|
+
children: [
|
|
88
|
+
{ label: 'Page 1', path: '/page-1' },
|
|
89
|
+
{ label: 'Page 2', path: '/page-2' },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
74
94
|
|
|
75
95
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
76
96
|
return (
|
|
77
97
|
<html lang="no">
|
|
78
98
|
<body>
|
|
79
|
-
<RootLayout
|
|
80
|
-
{children}
|
|
81
|
-
</RootLayout>
|
|
99
|
+
<RootLayout routes={ROUTES}>{children}</RootLayout>
|
|
82
100
|
</body>
|
|
83
101
|
</html>
|
|
84
102
|
);
|
|
85
103
|
}
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Scenario B: Deeply Nested Microfrontend
|
|
108
|
+
|
|
109
|
+
*This app sits deep within a hierarchy of other applications.*
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// app/layout.tsx
|
|
113
|
+
const ROUTES: RouteNode = {
|
|
114
|
+
label: 'Hjem',
|
|
115
|
+
path: 'https://statsbygg.no',
|
|
116
|
+
children: [
|
|
117
|
+
{
|
|
118
|
+
label: 'Parent Route',
|
|
119
|
+
path: 'https://statsbygg.no/parent-route', // External Parent 2
|
|
120
|
+
children: [
|
|
121
|
+
{
|
|
122
|
+
label: 'Your App Name',
|
|
123
|
+
path: '/', // Your App Root
|
|
124
|
+
children: [
|
|
125
|
+
{ label: 'Your App Route', path: '/your-app-route' }
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 2. Handling Dynamic Routes
|
|
136
|
+
|
|
137
|
+
For pages with dynamic IDs (e.g., `/properties/[id]`), use the `useBreadcrumbs` hook.
|
|
138
|
+
|
|
139
|
+
**Note:** You only need to define the *dynamic* portion of the path. The layout package automatically prepends the static parents (Home, App Root, etc.) defined in your `ROUTES` tree.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
// app/properties/[id]/page.tsx
|
|
143
|
+
'use client';
|
|
144
|
+
import { useBreadcrumbs } from '@statsbygg/layout';
|
|
145
|
+
|
|
146
|
+
export default function PropertyPage({ params, propertyName }) {
|
|
147
|
+
|
|
148
|
+
useBreadcrumbs([
|
|
149
|
+
{ label: 'Properties', href: '/properties' },
|
|
150
|
+
{ label: propertyName, href: `/properties/${params.id}` },
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
return <div>...</div>;
|
|
154
|
+
}
|
|
155
|
+
// Resulting Breadcrumbs: Home > My App > Properties > [Property Name]
|
|
156
|
+
|
|
86
157
|
```
|
|
87
158
|
|
|
88
159
|
### Using the Global Store
|
|
@@ -121,7 +192,7 @@ Main layout component that orchestrates the entire layout structure.
|
|
|
121
192
|
```tsx
|
|
122
193
|
interface RootLayoutProps {
|
|
123
194
|
children: React.ReactNode;
|
|
124
|
-
|
|
195
|
+
routes: RouteNode;
|
|
125
196
|
className?: string;
|
|
126
197
|
}
|
|
127
198
|
```
|
|
@@ -69,6 +69,26 @@
|
|
|
69
69
|
color: var(--ds-color-accent-text-default);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
.parentLink.active {
|
|
73
|
+
color: var(--ds-color-accent-text-default);
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
text-decoration: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.childLink.active {
|
|
79
|
+
color: var(--ds-color-accent-text-default);
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
border-left: 2px solid var(--ds-color-accent-text-default);
|
|
83
|
+
padding-left: calc(var(--ds-size-2) - 2px); /* Adjust padding to prevent layout shift */
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.compactLink.active {
|
|
87
|
+
color: var(--ds-color-accent-text-default);
|
|
88
|
+
font-weight: 600;
|
|
89
|
+
text-decoration: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
72
92
|
@keyframes fadeIn {
|
|
73
93
|
from {
|
|
74
94
|
opacity: 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as zustand_middleware from 'zustand/middleware';
|
|
3
3
|
import * as zustand from 'zustand';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
5
|
+
|
|
6
|
+
type RouteNode = {
|
|
7
|
+
path: string;
|
|
8
|
+
label: string;
|
|
9
|
+
children?: RouteNode[];
|
|
10
|
+
};
|
|
11
|
+
type BreadcrumbItem = {
|
|
12
|
+
label: string;
|
|
13
|
+
href: string;
|
|
14
|
+
};
|
|
15
|
+
type AncestorChain = {
|
|
16
|
+
externalAncestors: RouteNode[];
|
|
17
|
+
appRoot: RouteNode | null;
|
|
18
|
+
};
|
|
19
|
+
declare function findAppRootWithAncestors(node: RouteNode, ancestors?: RouteNode[]): AncestorChain;
|
|
20
|
+
declare function getBreadcrumbs(routes: RouteNode, pathname: string): BreadcrumbItem[];
|
|
4
21
|
|
|
5
22
|
type RootLayoutProps = {
|
|
6
23
|
children: React.ReactNode;
|
|
7
|
-
|
|
24
|
+
routes: RouteNode;
|
|
25
|
+
appBasePath?: string;
|
|
8
26
|
className?: string;
|
|
9
27
|
};
|
|
10
28
|
|
|
11
|
-
declare function RootLayout({ children,
|
|
29
|
+
declare function RootLayout({ children, routes, appBasePath, className }: RootLayoutProps): react_jsx_runtime.JSX.Element;
|
|
12
30
|
|
|
13
31
|
type Theme = 'light' | 'dark' | 'system';
|
|
14
|
-
type BreadcrumbItem = {
|
|
15
|
-
label: string;
|
|
16
|
-
href: string;
|
|
17
|
-
};
|
|
18
32
|
type GlobalState = {
|
|
19
33
|
user?: {
|
|
20
34
|
id: string;
|
|
@@ -23,16 +37,14 @@ type GlobalState = {
|
|
|
23
37
|
theme: Theme;
|
|
24
38
|
locale: string;
|
|
25
39
|
isMenuOpen: boolean;
|
|
26
|
-
zone: string | null;
|
|
27
40
|
breadcrumbs: BreadcrumbItem[] | null;
|
|
28
41
|
setUser: (user: GlobalState['user']) => void;
|
|
29
42
|
setTheme: (theme: Theme) => void;
|
|
30
43
|
setLocale: (locale: string) => void;
|
|
31
44
|
setIsMenuOpen: (isOpen: boolean) => void;
|
|
32
45
|
toggleMenu: () => void;
|
|
33
|
-
setZone: (zone: string) => void;
|
|
34
46
|
setBreadcrumbs: (breadcrumbs: BreadcrumbItem[] | null) => void;
|
|
35
|
-
initialize: () => void
|
|
47
|
+
initialize: () => void;
|
|
36
48
|
};
|
|
37
49
|
declare const useGlobalStore: zustand.UseBoundStore<Omit<zustand.StoreApi<GlobalState>, "setState" | "persist"> & {
|
|
38
50
|
setState(partial: GlobalState | Partial<GlobalState> | ((state: GlobalState) => GlobalState | Partial<GlobalState>), replace?: false | undefined): unknown;
|
|
@@ -48,6 +60,42 @@ declare const useGlobalStore: zustand.UseBoundStore<Omit<zustand.StoreApi<Global
|
|
|
48
60
|
};
|
|
49
61
|
}>;
|
|
50
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Hook for manually setting breadcrumbs on dynamic routes.
|
|
65
|
+
* Overrides automatic breadcrumb generation, preserving external ancestors.
|
|
66
|
+
*
|
|
67
|
+
* @param breadcrumbs - Array of breadcrumb items for the current dynamic route
|
|
68
|
+
*/
|
|
51
69
|
declare function useBreadcrumbs(breadcrumbs: BreadcrumbItem[]): void;
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
type SmartLinkProps = {
|
|
72
|
+
href: string;
|
|
73
|
+
appBasePath?: string;
|
|
74
|
+
external?: boolean;
|
|
75
|
+
className?: string;
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Determines if a given href belongs to the current zone, another zone or should be external
|
|
81
|
+
*/
|
|
82
|
+
declare function SmartLink({ href, appBasePath, external, className, children, }: SmartLinkProps): react_jsx_runtime.JSX.Element;
|
|
83
|
+
|
|
84
|
+
type MenuItem = {
|
|
85
|
+
label: string;
|
|
86
|
+
href: string;
|
|
87
|
+
external?: boolean;
|
|
88
|
+
children?: MenuItem[];
|
|
89
|
+
};
|
|
90
|
+
type MenuSubsection = {
|
|
91
|
+
title: string;
|
|
92
|
+
items: MenuItem[];
|
|
93
|
+
};
|
|
94
|
+
type MenuSection = {
|
|
95
|
+
title: string;
|
|
96
|
+
layout?: 'columns' | 'subsections';
|
|
97
|
+
items?: MenuItem[];
|
|
98
|
+
subsections?: MenuSubsection[];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export { type BreadcrumbItem, type GlobalState, type MenuItem, type MenuSection, type MenuSubsection, RootLayout, type RootLayoutProps, type RouteNode, SmartLink, type SmartLinkProps, findAppRootWithAncestors, getBreadcrumbs, useBreadcrumbs, useGlobalStore };
|