@xyz/navigation 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,422 @@
1
+ # @xyz/navigation
2
+
3
+ A flexible, context-aware navigation module for Nuxt 4 with zero performance overhead.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@xyz/navigation.svg)](https://www.npmjs.com/package/@xyz/navigation)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Features
9
+
10
+ - 🎯 **Context-Aware Paths** - Dynamic path resolution using templates (`{org}`, `{slug}`, `{username}`)
11
+ - 🔐 **Role-Based Filtering** - Show/hide items based on user roles
12
+ - 🚩 **Feature Flags** - Conditional navigation based on enabled features
13
+ - ⚡ **Zero Performance Overhead** - SSR-safe with reactive computed properties
14
+ - 🎨 **Framework-Agnostic** - Works with any UI library (Nuxt UI, Tailwind, custom)
15
+ - 🔧 **Flexible Configuration** - Inline or external config file
16
+ - 📘 **Full TypeScript Support** - Complete type safety and inference
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pnpm add @xyz/navigation
22
+ # or
23
+ npm install @xyz/navigation
24
+ # or
25
+ yarn add @xyz/navigation
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Add Module to nuxt.config.ts
31
+
32
+ ```typescript
33
+ export default defineNuxtConfig({
34
+ modules: ['@xyz/navigation']
35
+ })
36
+ ```
37
+
38
+ ### 2. Configure Navigation
39
+
40
+ **Option A: Inline Configuration** (Recommended for small configs)
41
+
42
+ ```typescript
43
+ export default defineNuxtConfig({
44
+ modules: ['@xyz/navigation'],
45
+
46
+ navigation: {
47
+ items: {
48
+ sections: {
49
+ main: [
50
+ { label: 'Dashboard', to: '{org}', icon: 'i-heroicons-home' },
51
+ { label: 'Projects', to: '{org}/projects' },
52
+ { label: 'Team', to: '{org}/team', features: ['team'] }
53
+ ]
54
+ }
55
+ },
56
+
57
+ contextProvider: () => ({
58
+ user: useAuthUser(),
59
+ activeOrganization: useActiveOrg(),
60
+ features: useFeatureFlags()
61
+ })
62
+ }
63
+ })
64
+ ```
65
+
66
+ **Option B: External File** (Recommended for larger configs)
67
+
68
+ Create `navigation.config.ts` at your project root:
69
+
70
+ ```typescript
71
+ export default {
72
+ sections: {
73
+ main: [
74
+ {
75
+ label: 'Dashboard',
76
+ to: '{org}',
77
+ icon: 'i-heroicons-home'
78
+ },
79
+ {
80
+ label: 'Projects',
81
+ to: '{org}/projects',
82
+ icon: 'i-heroicons-folder'
83
+ },
84
+ {
85
+ label: 'Team',
86
+ to: '{org}/team',
87
+ icon: 'i-heroicons-user-group',
88
+ features: ['team'] // Only shown if 'team' feature is enabled
89
+ },
90
+ {
91
+ label: 'Settings',
92
+ to: '{org}/settings',
93
+ icon: 'i-heroicons-cog-6-tooth',
94
+ children: [
95
+ { label: 'General', to: '{org}/settings/general' },
96
+ {
97
+ label: 'Billing',
98
+ to: '{org}/settings/billing',
99
+ roles: ['admin', 'owner'] // Only for admins/owners
100
+ }
101
+ ]
102
+ }
103
+ ],
104
+
105
+ profile: [
106
+ { label: 'My Profile', to: '/profile' },
107
+ { label: 'Preferences', to: '/preferences' }
108
+ ]
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### 3. Use in Components
114
+
115
+ ```vue
116
+ <script setup lang="ts">
117
+ import navigationConfig from '~/navigation.config'
118
+
119
+ const { sections } = useNavigation(navigationConfig)
120
+ </script>
121
+
122
+ <template>
123
+ <nav>
124
+ <NuxtLink
125
+ v-for="item in sections.main"
126
+ :key="item.label"
127
+ :to="item.to"
128
+ >
129
+ {{ item.label }}
130
+ </NuxtLink>
131
+ </nav>
132
+ </template>
133
+ ```
134
+
135
+ ## Template Strings
136
+
137
+ Built-in template variables for dynamic path resolution:
138
+
139
+ | Template | Resolves To | Example |
140
+ |----------|-------------|---------|
141
+ | `{org}` | `/app` (personal) or `/app/:slug` (organization) | `{org}/projects` → `/app/acme/projects` |
142
+ | `{slug}` | Current organization slug | `{slug}` → `acme` |
143
+ | `{username}` | Current user's name or email | `/u/{username}` → `/u/john` |
144
+ | `{user.id}` | Current user ID | `/users/{user.id}` → `/users/123` |
145
+
146
+ ### Custom Templates
147
+
148
+ Define custom template resolvers in `nuxt.config.ts`:
149
+
150
+ ```typescript
151
+ export default defineNuxtConfig({
152
+ modules: ['@xyz/navigation'],
153
+
154
+ navigation: {
155
+ templates: {
156
+ '{workspace}': (ctx) => ctx.workspace?.slug ?? '',
157
+ '{tenant}': (ctx) => ctx.tenant?.id ?? 'default'
158
+ }
159
+ }
160
+ })
161
+ ```
162
+
163
+ ## Context Provider
164
+
165
+ Define how navigation context is resolved:
166
+
167
+ ```typescript
168
+ export default defineNuxtConfig({
169
+ modules: ['@xyz/navigation'],
170
+
171
+ navigation: {
172
+ contextProvider: () => ({
173
+ // User information
174
+ user: useAuthUser(),
175
+
176
+ // Active organization/tenant
177
+ activeOrganization: useActiveOrg(),
178
+
179
+ // Feature flags
180
+ features: useFeatureFlags(),
181
+
182
+ // Custom context
183
+ workspace: useWorkspace(),
184
+ permissions: usePermissions()
185
+ })
186
+ }
187
+ })
188
+ ```
189
+
190
+ ## Role-Based Access
191
+
192
+ Control navigation visibility based on user roles:
193
+
194
+ ```typescript
195
+ {
196
+ label: 'Admin Panel',
197
+ to: '/admin',
198
+ roles: ['admin', 'superadmin'] // Only shown to admins
199
+ }
200
+ ```
201
+
202
+ ## Feature Flags
203
+
204
+ Show/hide items based on enabled features:
205
+
206
+ ```typescript
207
+ {
208
+ label: 'Beta Feature',
209
+ to: '/beta',
210
+ features: ['betaAccess'] // Only shown if feature is enabled
211
+ }
212
+
213
+ // Multiple features (all must be enabled)
214
+ {
215
+ label: 'Advanced Analytics',
216
+ to: '/analytics',
217
+ features: ['analytics', 'premium']
218
+ }
219
+ ```
220
+
221
+ ## Conditional Display
222
+
223
+ Use custom conditions for complex logic:
224
+
225
+ ```typescript
226
+ {
227
+ label: 'Upgrade',
228
+ to: '/upgrade',
229
+ if: (ctx) => !ctx.user?.isPremium // Show only to non-premium users
230
+ }
231
+ ```
232
+
233
+ ## Nested Navigation
234
+
235
+ Create multi-level navigation structures:
236
+
237
+ ```typescript
238
+ {
239
+ label: 'Settings',
240
+ to: '{org}/settings',
241
+ children: [
242
+ { label: 'General', to: '{org}/settings/general' },
243
+ { label: 'Team', to: '{org}/settings/team' },
244
+ {
245
+ label: 'Billing',
246
+ to: '{org}/settings/billing',
247
+ roles: ['admin']
248
+ }
249
+ ]
250
+ }
251
+ ```
252
+
253
+ ## Sidebar State Management
254
+
255
+ Built-in composable for managing sidebar state:
256
+
257
+ ```typescript
258
+ const sidebar = useSidebarState({
259
+ initialView: 'main',
260
+ persist: true // Persists to localStorage
261
+ })
262
+
263
+ // Switch views
264
+ await sidebar.switchToView('settings')
265
+
266
+ // Go back
267
+ sidebar.backToPrevious()
268
+
269
+ // Reset to initial
270
+ sidebar.resetView()
271
+
272
+ // Check current view
273
+ if (sidebar.isView('settings')) {
274
+ // ...
275
+ }
276
+ ```
277
+
278
+ ## Framework Integration
279
+
280
+ Works seamlessly with any UI library:
281
+
282
+ ### Nuxt UI
283
+
284
+ ```vue
285
+ <template>
286
+ <UNavigationMenu :items="sections.main.value" orientation="vertical" />
287
+ </template>
288
+ ```
289
+
290
+ ### Headless UI / Custom
291
+
292
+ ```vue
293
+ <template>
294
+ <nav class="flex flex-col gap-2">
295
+ <NuxtLink
296
+ v-for="item in sections.main.value"
297
+ :key="item.label"
298
+ :to="item.to"
299
+ class="nav-link"
300
+ >
301
+ <Icon :name="item.icon" />
302
+ {{ item.label }}
303
+ </NuxtLink>
304
+ </nav>
305
+ </template>
306
+ ```
307
+
308
+ ## API Reference
309
+
310
+ ### `useNavigation(config, options?)`
311
+
312
+ Main composable for processing navigation configuration.
313
+
314
+ **Parameters:**
315
+ - `config` - Navigation configuration object
316
+ - `options` (optional) - Processing options
317
+
318
+ **Returns:**
319
+ ```typescript
320
+ {
321
+ sections: Record<string, ComputedRef<NavigationItem[]>>,
322
+ context: ComputedRef<NavigationContext>,
323
+ refresh: () => void
324
+ }
325
+ ```
326
+
327
+ ### `useNavigationContext(override?)`
328
+
329
+ Access current navigation context.
330
+
331
+ **Parameters:**
332
+ - `override` (optional) - Override context values
333
+
334
+ **Returns:**
335
+ ```typescript
336
+ ComputedRef<NavigationContext>
337
+ ```
338
+
339
+ ### `useSidebarState(options?)`
340
+
341
+ Manage sidebar view state.
342
+
343
+ **Parameters:**
344
+ ```typescript
345
+ {
346
+ initialView?: string,
347
+ persist?: boolean // Default: false
348
+ }
349
+ ```
350
+
351
+ **Returns:**
352
+ ```typescript
353
+ {
354
+ currentView: Ref<string>,
355
+ history: Ref<string[]>,
356
+ switchToView: (view: string) => Promise<void>,
357
+ backToPrevious: () => void,
358
+ resetView: () => void,
359
+ isView: (view: string) => boolean
360
+ }
361
+ ```
362
+
363
+ ## TypeScript Support
364
+
365
+ Full type safety with automatic inference:
366
+
367
+ ```typescript
368
+ import type { NavigationConfig, NavigationItem } from '@xyz/navigation'
369
+
370
+ const config: NavigationConfig = {
371
+ sections: {
372
+ main: [
373
+ // Fully typed items
374
+ ]
375
+ }
376
+ }
377
+ ```
378
+
379
+ ## Examples
380
+
381
+ ### Multi-Tenant SaaS
382
+
383
+ ```typescript
384
+ export default {
385
+ sections: {
386
+ main: [
387
+ { label: 'Dashboard', to: '{org}' },
388
+ { label: 'Users', to: '{org}/users', roles: ['admin'] },
389
+ { label: 'Billing', to: '{org}/billing', features: ['billing'] }
390
+ ]
391
+ }
392
+ }
393
+ ```
394
+
395
+ ### Context-Based Routing
396
+
397
+ ```typescript
398
+ {
399
+ label: 'Profile',
400
+ to: (ctx) => ctx.activeOrganization
401
+ ? `/${ctx.activeOrganization.slug}/profile`
402
+ : `/u/${ctx.user?.username}/profile`
403
+ }
404
+ ```
405
+
406
+ ### Progressive Disclosure
407
+
408
+ ```typescript
409
+ {
410
+ label: 'Advanced',
411
+ to: '/advanced',
412
+ if: (ctx) => ctx.user?.role === 'developer' && ctx.features?.advanced
413
+ }
414
+ ```
415
+
416
+ ## License
417
+
418
+ MIT License - see [LICENSE](./LICENSE) for details.
419
+
420
+ ## Contributing
421
+
422
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const kit = require('@nuxt/kit');
4
+ const defu = require('defu');
5
+
6
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
7
+ const module$1 = kit.defineNuxtModule({
8
+ meta: {
9
+ name: "xyz-navigation",
10
+ configKey: "navigation",
11
+ compatibility: {
12
+ nuxt: "^3.0.0 || ^4.0.0"
13
+ }
14
+ },
15
+ defaults: {
16
+ enabled: true
17
+ },
18
+ async setup(options, nuxt) {
19
+ if (options.enabled === false) {
20
+ return;
21
+ }
22
+ const resolver = kit.createResolver((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('module.cjs', document.baseURI).href)));
23
+ nuxt.options.alias["#xyz-navigation"] = resolver.resolve("./runtime");
24
+ kit.addImports([
25
+ {
26
+ name: "useNavigation",
27
+ from: resolver.resolve("./runtime/composables/useNavigation")
28
+ },
29
+ {
30
+ name: "useNavigationContext",
31
+ from: resolver.resolve("./runtime/composables/useNavigationContext")
32
+ },
33
+ {
34
+ name: "useSidebarState",
35
+ from: resolver.resolve("./runtime/composables/useSidebarState")
36
+ }
37
+ ]);
38
+ kit.addPlugin(resolver.resolve("./runtime/plugins/navigation"));
39
+ kit.addTemplate({
40
+ filename: "types/xyz-navigation.d.ts",
41
+ getContents: () => `
42
+ declare module '#app' {
43
+ interface NuxtApp {
44
+ $navigation: {
45
+ context: import('vue').ComputedRef<import('#xyz-navigation/types').NavigationContext>
46
+ }
47
+ }
48
+ }
49
+
50
+ export {}
51
+ `.trim()
52
+ });
53
+ nuxt.options.runtimeConfig.public.navigation = defu.defu(
54
+ nuxt.options.runtimeConfig.public.navigation,
55
+ {
56
+ enabled: options.enabled,
57
+ contextProvider: options.contextProvider
58
+ }
59
+ );
60
+ if (options.items) {
61
+ kit.addTemplate({
62
+ filename: "navigation-config.mjs",
63
+ getContents: () => `export default ${JSON.stringify(options.items, null, 2)}`
64
+ });
65
+ kit.addImports({
66
+ name: "default",
67
+ as: "navigationConfig",
68
+ from: "#build/navigation-config.mjs"
69
+ });
70
+ if (nuxt.options.dev) {
71
+ console.log("[xyz-navigation] Using inline navigation configuration from nuxt.config.ts");
72
+ }
73
+ } else {
74
+ try {
75
+ const { existsSync } = await import('fs');
76
+ const navConfigPath = resolver.resolve(nuxt.options.rootDir, "navigation.config.ts");
77
+ if (existsSync(navConfigPath)) {
78
+ kit.addImports({
79
+ name: "default",
80
+ as: "navigationConfig",
81
+ from: navConfigPath
82
+ });
83
+ if (nuxt.options.dev) {
84
+ console.log("[xyz-navigation] Found navigation.config.ts at project root - auto-importing");
85
+ }
86
+ } else if (nuxt.options.dev) {
87
+ console.warn("[xyz-navigation] No navigation configuration found. Add navigation.config.ts or use inline config in nuxt.config.ts");
88
+ }
89
+ } catch (error) {
90
+ if (nuxt.options.dev) {
91
+ console.warn("[xyz-navigation] Error checking for navigation.config.ts:", error);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ });
97
+
98
+ module.exports = module$1;
@@ -0,0 +1,8 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ModuleOptions } from '../dist/runtime/types/config.js';
3
+ export { ModuleOptions } from '../dist/runtime/types/config.js';
4
+ export * from '../dist/runtime/types/index.js';
5
+
6
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
7
+
8
+ export { _default as default };
@@ -0,0 +1,8 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ModuleOptions } from '../dist/runtime/types/config.js';
3
+ export { ModuleOptions } from '../dist/runtime/types/config.js';
4
+ export * from '../dist/runtime/types/index.js';
5
+
6
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
7
+
8
+ export { _default as default };
@@ -0,0 +1,8 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { ModuleOptions } from '../dist/runtime/types/config.js';
3
+ export { ModuleOptions } from '../dist/runtime/types/config.js';
4
+ export * from '../dist/runtime/types/index.js';
5
+
6
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
7
+
8
+ export { _default as default };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "xyz-navigation",
3
+ "configKey": "navigation",
4
+ "compatibility": {
5
+ "nuxt": "^3.0.0 || ^4.0.0"
6
+ },
7
+ "version": "1.0.0",
8
+ "builder": {
9
+ "@nuxt/module-builder": "0.8.4",
10
+ "unbuild": "2.0.0"
11
+ }
12
+ }
@@ -0,0 +1,95 @@
1
+ import { defineNuxtModule, createResolver, addImports, addPlugin, addTemplate } from '@nuxt/kit';
2
+ import { defu } from 'defu';
3
+
4
+ const module = defineNuxtModule({
5
+ meta: {
6
+ name: "xyz-navigation",
7
+ configKey: "navigation",
8
+ compatibility: {
9
+ nuxt: "^3.0.0 || ^4.0.0"
10
+ }
11
+ },
12
+ defaults: {
13
+ enabled: true
14
+ },
15
+ async setup(options, nuxt) {
16
+ if (options.enabled === false) {
17
+ return;
18
+ }
19
+ const resolver = createResolver(import.meta.url);
20
+ nuxt.options.alias["#xyz-navigation"] = resolver.resolve("./runtime");
21
+ addImports([
22
+ {
23
+ name: "useNavigation",
24
+ from: resolver.resolve("./runtime/composables/useNavigation")
25
+ },
26
+ {
27
+ name: "useNavigationContext",
28
+ from: resolver.resolve("./runtime/composables/useNavigationContext")
29
+ },
30
+ {
31
+ name: "useSidebarState",
32
+ from: resolver.resolve("./runtime/composables/useSidebarState")
33
+ }
34
+ ]);
35
+ addPlugin(resolver.resolve("./runtime/plugins/navigation"));
36
+ addTemplate({
37
+ filename: "types/xyz-navigation.d.ts",
38
+ getContents: () => `
39
+ declare module '#app' {
40
+ interface NuxtApp {
41
+ $navigation: {
42
+ context: import('vue').ComputedRef<import('#xyz-navigation/types').NavigationContext>
43
+ }
44
+ }
45
+ }
46
+
47
+ export {}
48
+ `.trim()
49
+ });
50
+ nuxt.options.runtimeConfig.public.navigation = defu(
51
+ nuxt.options.runtimeConfig.public.navigation,
52
+ {
53
+ enabled: options.enabled,
54
+ contextProvider: options.contextProvider
55
+ }
56
+ );
57
+ if (options.items) {
58
+ addTemplate({
59
+ filename: "navigation-config.mjs",
60
+ getContents: () => `export default ${JSON.stringify(options.items, null, 2)}`
61
+ });
62
+ addImports({
63
+ name: "default",
64
+ as: "navigationConfig",
65
+ from: "#build/navigation-config.mjs"
66
+ });
67
+ if (nuxt.options.dev) {
68
+ console.log("[xyz-navigation] Using inline navigation configuration from nuxt.config.ts");
69
+ }
70
+ } else {
71
+ try {
72
+ const { existsSync } = await import('fs');
73
+ const navConfigPath = resolver.resolve(nuxt.options.rootDir, "navigation.config.ts");
74
+ if (existsSync(navConfigPath)) {
75
+ addImports({
76
+ name: "default",
77
+ as: "navigationConfig",
78
+ from: navConfigPath
79
+ });
80
+ if (nuxt.options.dev) {
81
+ console.log("[xyz-navigation] Found navigation.config.ts at project root - auto-importing");
82
+ }
83
+ } else if (nuxt.options.dev) {
84
+ console.warn("[xyz-navigation] No navigation configuration found. Add navigation.config.ts or use inline config in nuxt.config.ts");
85
+ }
86
+ } catch (error) {
87
+ if (nuxt.options.dev) {
88
+ console.warn("[xyz-navigation] Error checking for navigation.config.ts:", error);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ });
94
+
95
+ export { module as default };
@@ -0,0 +1,19 @@
1
+ import type { NavigationConfig, NavigationResult, UseNavigationOptions } from '../types/index.js';
2
+ /**
3
+ * Main navigation composable
4
+ * Processes navigation configuration and returns resolved items
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const config = {
9
+ * app: [
10
+ * { label: 'Dashboard', to: '{org}', icon: 'i-lucide-layout-dashboard' },
11
+ * { label: 'Settings', to: '{org}/settings', icon: 'i-lucide-settings' }
12
+ * ]
13
+ * }
14
+ *
15
+ * const { sections } = useNavigation(config)
16
+ * // sections.app.value → processed items
17
+ * ```
18
+ */
19
+ export declare function useNavigation<T extends NavigationConfig>(config: T, options?: UseNavigationOptions): NavigationResult<T>;