@consilioweb/admin-nav 0.4.3

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.

Potentially problematic release.


This version of @consilioweb/admin-nav might be problematic. Click here for more details.

package/README.md ADDED
@@ -0,0 +1,774 @@
1
+ <!-- Header Banner -->
2
+ <div align="center">
3
+
4
+ <a href="https://git.io/typing-svg">
5
+ <img src="https://readme-typing-svg.demolab.com?font=Fira+Code&weight=700&size=32&duration=3000&pause=1000&color=3B82F6&center=true&vCenter=true&width=700&lines=%40consilioweb%2Fadmin-nav;Payload+CMS+Sidebar+Plugin;Drag+%26+Drop+%7C+Per-User+Prefs;70%2B+Icons+%7C+i18n+Ready;Zero-Config+%7C+Instant+Render" alt="Typing SVG" />
6
+ </a>
7
+
8
+ <br><br>
9
+
10
+ <!-- Badges -->
11
+ <a href="https://www.npmjs.com/package/@consilioweb/admin-nav"><img src="https://img.shields.io/npm/v/@consilioweb/admin-nav?style=for-the-badge&logo=npm&logoColor=white&color=CB3837" alt="npm version"></a>
12
+ <a href="https://www.npmjs.com/package/@consilioweb/admin-nav"><img src="https://img.shields.io/npm/dw/@consilioweb/admin-nav?style=for-the-badge&logo=npm&logoColor=white&color=CB3837" alt="npm downloads"></a>
13
+ <img src="https://img.shields.io/badge/Payload%20CMS-3.x-0F172A?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMkw0IDdWMTdMMTIgMjJMMjAgMTdWN0wxMiAyWiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4=&logoColor=white" alt="Payload CMS 3">
14
+ <img src="https://img.shields.io/badge/Drag%20%26%20Drop-dnd--kit-8B5CF6?style=for-the-badge" alt="dnd-kit">
15
+ <img src="https://img.shields.io/badge/Icons-70%2B%20SVG-10B981?style=for-the-badge" alt="70+ Icons">
16
+ <img src="https://img.shields.io/badge/i18n-FR%20%7C%20EN-F59E0B?style=for-the-badge&logo=translate&logoColor=white" alt="i18n FR | EN">
17
+ <a href="https://github.com/pOwn3d/payload-nav-studio/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-7C3AED?style=for-the-badge" alt="MIT License"></a>
18
+ <img src="https://img.shields.io/badge/TypeScript-5.x-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript">
19
+ <a href="https://github.com/pOwn3d/payload-nav-studio/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/pOwn3d/payload-nav-studio/ci.yml?branch=main&style=for-the-badge&logo=github-actions&logoColor=white" alt="CI"></a>
20
+ <a href="https://github.com/pOwn3d/payload-nav-studio"><img src="https://img.shields.io/github/stars/pOwn3d/payload-nav-studio?style=for-the-badge&logo=github&color=181717" alt="GitHub stars"></a>
21
+
22
+ </div>
23
+
24
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
25
+
26
+ ## About
27
+
28
+ > **@consilioweb/admin-nav** — A fully customizable admin sidebar navigation plugin for Payload CMS 3. Drag & drop reordering, per-user preferences stored in the database, 70+ built-in SVG icons, nested sub-items, i18n support (FR/EN with multi-language labels), and a dedicated admin view to customize everything visually.
29
+
30
+ <table>
31
+ <tr>
32
+ <td align="center" width="25%">
33
+ <img src="https://img.icons8.com/color/96/drag-and-drop.png" width="50"/><br>
34
+ <b>Drag & Drop</b><br>
35
+ <sub>Reorder groups & items</sub>
36
+ </td>
37
+ <td align="center" width="25%">
38
+ <img src="https://img.icons8.com/color/96/user-group-man-man.png" width="50"/><br>
39
+ <b>Per-User Prefs</b><br>
40
+ <sub>Stored in database</sub>
41
+ </td>
42
+ <td align="center" width="25%">
43
+ <img src="https://img.icons8.com/color/96/paint-palette.png" width="50"/><br>
44
+ <b>70+ Icons</b><br>
45
+ <sub>Lucide-compatible SVG</sub>
46
+ </td>
47
+ <td align="center" width="25%">
48
+ <img src="https://img.icons8.com/color/96/api-settings.png" width="50"/><br>
49
+ <b>REST API</b><br>
50
+ <sub>Preferences CRUD</sub>
51
+ </td>
52
+ </tr>
53
+ <tr>
54
+ <td align="center" width="25%">
55
+ <img src="https://img.icons8.com/color/96/language.png" width="50"/><br>
56
+ <b>i18n Ready</b><br>
57
+ <sub>FR & EN out of the box</sub>
58
+ </td>
59
+ <td align="center" width="25%">
60
+ <img src="https://img.icons8.com/color/96/translate-text.png" width="50"/><br>
61
+ <b>Multi-lang Labels</b><br>
62
+ <sub>Per-language nav labels</sub>
63
+ </td>
64
+ <td align="center" width="25%">
65
+ <img src="https://img.icons8.com/color/96/automation.png" width="50"/><br>
66
+ <b>Auto-Discovery</b><br>
67
+ <sub>Zero-config mode</sub>
68
+ </td>
69
+ <td align="center" width="25%">
70
+ <img src="https://img.icons8.com/color/96/speed.png" width="50"/><br>
71
+ <b>Instant Render</b><br>
72
+ <sub>Two-tier cache, no flash</sub>
73
+ </td>
74
+ </tr>
75
+ </table>
76
+
77
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
78
+
79
+ ## Overview
80
+
81
+ `@consilioweb/admin-nav` replaces the default Payload CMS admin sidebar with a fully customizable navigation system. Each admin user gets their own layout preferences, persisted in the database. The plugin provides a visual editor (`/admin/nav-customizer`) where users can drag & drop groups and items, toggle visibility, edit labels and icons, create custom entries, and organize nested sub-menus — all without touching code.
82
+
83
+ ### Screenshots
84
+
85
+ | Nav Customizer | Drag & Drop |
86
+ |:---:|:---:|
87
+ | ![Nav Customizer](https://raw.githubusercontent.com/pOwn3d/payload-nav-studio/main/docs/screenshots/nav-customizer.png) | ![Drag & Drop](https://raw.githubusercontent.com/pOwn3d/payload-nav-studio/main/docs/screenshots/drag-drop.png) |
88
+
89
+ | Item Editor (with sub-menus) | Group Editor |
90
+ |:---:|:---:|
91
+ | ![Item Editor](https://raw.githubusercontent.com/pOwn3d/payload-nav-studio/main/docs/screenshots/item-editor.png) | ![Group Editor](https://raw.githubusercontent.com/pOwn3d/payload-nav-studio/main/docs/screenshots/group-editor.png) |
92
+
93
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
94
+
95
+ ## Table of Contents
96
+
97
+ - [Features](#features)
98
+ - [Installation](#installation)
99
+ - [Quick Start](#quick-start)
100
+ - [Zero-Config Mode](#zero-config-mode)
101
+ - [Configuration](#configuration)
102
+ - [Internationalization (i18n)](#internationalization-i18n)
103
+ - [Built-in Icons](#built-in-icons)
104
+ - [API Endpoints](#api-endpoints)
105
+ - [Components](#components)
106
+ - [Package Exports](#package-exports)
107
+ - [Requirements](#requirements)
108
+ - [Uninstall](#uninstall)
109
+ - [License](#license)
110
+
111
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
112
+
113
+ ## Features
114
+
115
+ ### Drag & Drop Navigation
116
+
117
+ Powered by [@dnd-kit](https://dndkit.com/), the navigation supports:
118
+
119
+ - **Group reordering** — drag entire sections up and down
120
+ - **Item reordering** — move items within or across groups
121
+ - **Touch & keyboard support** — accessible on all devices
122
+ - **Visual drag overlay** — see what you're moving in real-time
123
+
124
+ ### Per-User Preferences
125
+
126
+ - Each admin user has their own navigation layout
127
+ - Preferences are stored in a dedicated Payload collection
128
+ - Changes persist across sessions and devices
129
+ - One-click reset to defaults
130
+
131
+ ### Visual Customization
132
+
133
+ - **Show/hide** any group or item
134
+ - **Edit labels** — rename anything in the sidebar
135
+ - **Edit URLs** — change where items link to
136
+ - **Custom icons** — pick from 70+ SVG icons or use color dots
137
+ - **Create groups** — add new sections to organize your nav
138
+ - **Create items** — add custom links to any group
139
+ - **Nested sub-items** — create dropdown sub-menus with their own icons
140
+ - **Collapse groups** — set groups to start collapsed by default
141
+
142
+ ### 70+ Built-in Icons
143
+
144
+ Inline SVG icons (Lucide-compatible, 24x24 viewBox) — zero external dependencies:
145
+
146
+ - **Navigation** — home, layout-dashboard, settings, menu, compass
147
+ - **Content** — file-text, newspaper, image, tag, calendar
148
+ - **Support** — ticket, message-square, users, shield-check, mail-search
149
+ - **Management** — receipt, briefcase, wallet, credit-card, truck
150
+ - **Config** — database, globe, palette, key, monitor
151
+ - **SEO** — search, trending-up, bar-chart, link, award
152
+ - **Misc** — heart, star, bell, zap, gift, rocket, and many more
153
+
154
+ ### Internationalization (i18n)
155
+
156
+ - **UI translations** — all plugin strings are translated (French & English included)
157
+ - **Multi-language labels** — nav item labels and group titles support `string | Record<string, string>`
158
+ - **Toggle in editor** — switch between single-language and multi-language mode per item
159
+ - **Auto-detection** — existing `Record<string, string>` labels open in multi-lang mode automatically
160
+ - **Fallback chain** — resolves: exact language → fallback language → first available value
161
+ - **Extensible** — follows Payload's `deepMergeSimple` pattern, so you can override any translation key
162
+
163
+ ### Auto-Discovery (Zero-Config)
164
+
165
+ - **No config needed** — call `adminNavPlugin()` with no arguments
166
+ - **Collections** — grouped by `admin.group`, hidden collections excluded
167
+ - **Globals** — added to a "Configuration" group
168
+ - **Custom views** — detected and added to a "Views" group
169
+ - **Smart icons** — 40+ slug-to-icon mappings (fallback: `box`)
170
+ - **Fully customizable** — use `autoDiscoverNav()` to generate a base, then modify
171
+
172
+ ### Instant Rendering
173
+
174
+ - **Two-tier cache** — module-level (survives SPA navigation) + sessionStorage (survives page reload)
175
+ - **No loading flash** — nav renders instantly on page transitions
176
+ - **Background sync** — always fetches fresh data from the server after rendering the cache
177
+ - **SSR-safe** — module cache is `null` on the server, matching client initial state (no hydration mismatch)
178
+
179
+ ### Admin View
180
+
181
+ The plugin adds a dedicated view at `/admin/nav-customizer` with:
182
+
183
+ - Full drag & drop interface
184
+ - Group editor modal (title, ID, collapse state)
185
+ - Item editor modal (label, URL, icon, prefix match, sub-items)
186
+ - Icon picker with search and color mode
187
+ - Toast notifications for save/reset feedback
188
+ - Responsive layout
189
+
190
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
191
+
192
+ ## Installation
193
+
194
+ ```bash
195
+ pnpm add @consilioweb/admin-nav
196
+ ```
197
+
198
+ Or with npm/yarn:
199
+
200
+ ```bash
201
+ npm install @consilioweb/admin-nav
202
+ yarn add @consilioweb/admin-nav
203
+ ```
204
+
205
+ > **Note:** `@dnd-kit` is bundled into the client bundle — you do **not** need to install it separately.
206
+
207
+ ### Peer Dependencies
208
+
209
+ | Package | Version | Required |
210
+ |---------|---------|----------|
211
+ | `payload` | `^3.0.0` | **Yes** |
212
+ | `@payloadcms/next` | `^3.0.0` | Optional (admin views) |
213
+ | `@payloadcms/ui` | `^3.0.0` | Optional (admin UI) |
214
+ | `@payloadcms/translations` | `^3.0.0` | Optional (i18n) |
215
+ | `next` | `^14.0.0 \|\| ^15.0.0` | Optional (admin UI) |
216
+ | `react` | `^18.0.0 \|\| ^19.0.0` | Optional (admin UI) |
217
+ | `react-dom` | `^18.0.0 \|\| ^19.0.0` | Optional (admin UI) |
218
+
219
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
220
+
221
+ ## Quick Start
222
+
223
+ Add the plugin to your `payload.config.ts`:
224
+
225
+ ```ts
226
+ import { buildConfig } from 'payload'
227
+ import { adminNavPlugin } from '@consilioweb/admin-nav'
228
+
229
+ export default buildConfig({
230
+ // ... your existing config
231
+ plugins: [
232
+ adminNavPlugin({
233
+ defaultNav: [
234
+ {
235
+ id: 'content',
236
+ title: 'Content',
237
+ items: [
238
+ { id: 'pages', href: '/admin/collections/pages', label: 'Pages', icon: 'file-text' },
239
+ { id: 'posts', href: '/admin/collections/posts', label: 'Posts', icon: 'newspaper' },
240
+ { id: 'media', href: '/admin/collections/media', label: 'Media', icon: 'image' },
241
+ ],
242
+ },
243
+ {
244
+ id: 'settings',
245
+ title: 'Settings',
246
+ items: [
247
+ { id: 'users', href: '/admin/collections/users', label: 'Users', icon: 'users' },
248
+ { id: 'settings', href: '/admin/globals/settings', label: 'Settings', icon: 'settings' },
249
+ ],
250
+ },
251
+ ],
252
+ }),
253
+ ],
254
+ })
255
+ ```
256
+
257
+ That's it. The plugin will automatically:
258
+
259
+ 1. Add a `admin-nav-preferences` collection to store per-user layouts
260
+ 2. Register API endpoints under `/api/admin-nav/`
261
+ 3. Inject the `AdminNav` component into the sidebar via `beforeNavLinks`
262
+ 4. Add the Nav Customizer view at `/admin/nav-customizer`
263
+
264
+ > **Important:** After installing, run `pnpm generate:importmap` to register the new components.
265
+
266
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
267
+
268
+ ## Zero-Config Mode
269
+
270
+ If you don't provide a `defaultNav`, the plugin **auto-discovers** your Payload config and builds the navigation automatically:
271
+
272
+ ```ts
273
+ import { adminNavPlugin } from '@consilioweb/admin-nav'
274
+
275
+ export default buildConfig({
276
+ plugins: [
277
+ adminNavPlugin(), // No config needed — collections, globals & views are auto-detected
278
+ ],
279
+ })
280
+ ```
281
+
282
+ Auto-discovery will:
283
+
284
+ - Group collections by their `admin.group` value (or "Collections" as fallback)
285
+ - Add globals to a "Configuration" group
286
+ - Add custom admin views to a "Views" group
287
+ - Guess icons from 40+ slug mappings (e.g. `pages` → `file-text`, `users` → `users`, `media` → `image`)
288
+ - Skip hidden collections (`admin.hidden: true`)
289
+
290
+ You can also use `autoDiscoverNav` programmatically to generate a base config, then customize it:
291
+
292
+ ```ts
293
+ import { adminNavPlugin, autoDiscoverNav } from '@consilioweb/admin-nav'
294
+
295
+ const baseNav = autoDiscoverNav(config)
296
+ // Modify baseNav as needed...
297
+
298
+ export default buildConfig({
299
+ plugins: [
300
+ adminNavPlugin({ defaultNav: baseNav }),
301
+ ],
302
+ })
303
+ ```
304
+
305
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
306
+
307
+ ## Configuration
308
+
309
+ ### `AdminNavPluginConfig`
310
+
311
+ ```ts
312
+ adminNavPlugin({
313
+ defaultNav: [], // Optional — auto-discovered if omitted
314
+ afterNav: [], // Component paths to render after the nav
315
+ collectionSlug: 'admin-nav-preferences', // Preferences collection slug
316
+ userCollectionSlug: 'users', // User collection for the relationship
317
+ endpointBasePath: '/admin-nav', // API endpoint prefix
318
+ addCustomizerView: true, // Add /admin/nav-customizer view
319
+ navComponentPath: undefined, // Override AdminNav component path
320
+ })
321
+ ```
322
+
323
+ | Option | Type | Default | Description |
324
+ |--------|------|---------|-------------|
325
+ | `defaultNav` | `NavGroupConfig[]` | Auto-discovered | Initial sidebar structure. If omitted, the plugin auto-discovers collections, globals, and views from your Payload config |
326
+ | `afterNav` | `string[]` | `[]` | Component paths to render after the navigation |
327
+ | `collectionSlug` | `string` | `'admin-nav-preferences'` | Collection slug for preferences storage |
328
+ | `userCollectionSlug` | `string` | `'users'` | User collection slug for the relationship |
329
+ | `endpointBasePath` | `string` | `'/admin-nav'` | Base path for API endpoints |
330
+ | `addCustomizerView` | `boolean` | `true` | Whether to add the Nav Customizer admin view |
331
+ | `navComponentPath` | `string` | `'@consilioweb/admin-nav/client#AdminNav'` | Override the AdminNav component path (for `file:` or `link:` installs) |
332
+
333
+ ### Types
334
+
335
+ #### `LocalizedString`
336
+
337
+ ```ts
338
+ type LocalizedString = string | Record<string, string>
339
+ ```
340
+
341
+ Labels and titles accept either a plain string or a per-language record. Both are fully backward-compatible.
342
+
343
+ #### `NavGroupConfig`
344
+
345
+ ```ts
346
+ interface NavGroupConfig {
347
+ id: string // Unique group ID
348
+ title: LocalizedString // Section header label (string or per-language)
349
+ items: NavItemConfig[] // Items in this group
350
+ visible?: boolean // Whether the group is visible (default: true)
351
+ defaultCollapsed?: boolean // Start collapsed (default: false)
352
+ }
353
+ ```
354
+
355
+ #### `NavItemConfig`
356
+
357
+ ```ts
358
+ interface NavItemConfig {
359
+ id: string // Unique item ID
360
+ href: string // Admin URL path
361
+ label: LocalizedString // Display label (string or per-language)
362
+ icon: string // Icon name or '#RRGGBB' for color dot
363
+ matchPrefix?: boolean // Activate on pathname.startsWith(href)
364
+ children?: NavItemConfig[] // Nested sub-items
365
+ visible?: boolean // Whether visible (default: true)
366
+ }
367
+ ```
368
+
369
+ #### `NavLayout`
370
+
371
+ ```ts
372
+ interface NavLayout {
373
+ groups: NavGroupConfig[] // Ordered list of groups
374
+ version: number // Schema version for future migrations
375
+ }
376
+ ```
377
+
378
+ ### Advanced Example
379
+
380
+ ```ts
381
+ adminNavPlugin({
382
+ defaultNav: [
383
+ {
384
+ id: 'content',
385
+ title: { fr: 'Contenu', en: 'Content' }, // Multi-language group title
386
+ items: [
387
+ { id: 'pages', href: '/admin/collections/pages', label: 'Pages', icon: 'file-text' },
388
+ { id: 'posts', href: '/admin/collections/posts', label: { fr: 'Articles', en: 'Blog' }, icon: 'newspaper' },
389
+ { id: 'media', href: '/admin/collections/media', label: { fr: 'Médias', en: 'Media' }, icon: 'image' },
390
+ ],
391
+ },
392
+ {
393
+ id: 'seo',
394
+ title: 'SEO', // Plain string still works
395
+ defaultCollapsed: true,
396
+ items: [
397
+ { id: 'seo-dashboard', href: '/admin/seo', label: 'Dashboard', icon: 'trending-up', matchPrefix: true },
398
+ { id: 'sitemap', href: '/admin/sitemap-audit', label: 'Sitemap', icon: 'globe' },
399
+ { id: 'redirects', href: '/admin/redirects', label: 'Redirects', icon: 'corner-down-right' },
400
+ ],
401
+ },
402
+ {
403
+ id: 'support',
404
+ title: 'Support',
405
+ items: [
406
+ {
407
+ id: 'tickets',
408
+ href: '/admin/collections/tickets',
409
+ label: 'Tickets',
410
+ icon: 'ticket',
411
+ matchPrefix: true,
412
+ children: [
413
+ { id: 'open', href: '/admin/collections/tickets?status=open', label: 'Open', icon: '#EF4444' },
414
+ { id: 'closed', href: '/admin/collections/tickets?status=closed', label: 'Closed', icon: '#22C55E' },
415
+ ],
416
+ },
417
+ ],
418
+ },
419
+ {
420
+ id: 'admin',
421
+ title: 'Administration',
422
+ items: [
423
+ { id: 'users', href: '/admin/collections/users', label: 'Users', icon: 'users' },
424
+ { id: 'nav-customizer', href: '/admin/nav-customizer', label: 'Customize Nav', icon: 'palette' },
425
+ ],
426
+ },
427
+ ],
428
+ // For file: or link: protocol installs, override the component path:
429
+ // navComponentPath: '@/components/admin/AdminNavWrapper#AdminNav',
430
+ })
431
+ ```
432
+
433
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
434
+
435
+ ## Internationalization (i18n)
436
+
437
+ ### Plugin UI Translations
438
+
439
+ The plugin ships with French and English translations for all UI strings (buttons, labels, toasts, confirmations, etc.). Translations are automatically merged into Payload's i18n system using `deepMergeSimple`.
440
+
441
+ All keys are namespaced under `plugin-admin-nav`:
442
+
443
+ ```ts
444
+ t('plugin-admin-nav:save') // "Sauvegarder" (FR) / "Save" (EN)
445
+ t('plugin-admin-nav:editItem') // "Modifier l'item" (FR) / "Edit item" (EN)
446
+ ```
447
+
448
+ To override or add a language, merge your translations in `payload.config.ts`:
449
+
450
+ ```ts
451
+ import { buildConfig } from 'payload'
452
+
453
+ export default buildConfig({
454
+ i18n: {
455
+ translations: {
456
+ de: {
457
+ 'plugin-admin-nav': {
458
+ save: 'Speichern',
459
+ cancel: 'Abbrechen',
460
+ // ... override any key
461
+ },
462
+ },
463
+ },
464
+ },
465
+ })
466
+ ```
467
+
468
+ ### Multi-Language Nav Labels
469
+
470
+ Item labels and group titles accept `string | Record<string, string>`:
471
+
472
+ ```ts
473
+ adminNavPlugin({
474
+ defaultNav: [
475
+ {
476
+ id: 'content',
477
+ title: { fr: 'Contenu', en: 'Content' }, // Multi-language title
478
+ items: [
479
+ {
480
+ id: 'pages',
481
+ href: '/admin/collections/pages',
482
+ label: { fr: 'Pages', en: 'Pages' }, // Multi-language label
483
+ icon: 'file-text',
484
+ },
485
+ {
486
+ id: 'posts',
487
+ href: '/admin/collections/posts',
488
+ label: 'Blog', // Simple string still works
489
+ icon: 'newspaper',
490
+ },
491
+ ],
492
+ },
493
+ ],
494
+ })
495
+ ```
496
+
497
+ The item and group editors include a **Multi-lang** toggle that lets users switch between single-language and multi-language mode. When a label is already a `Record<string, string>`, the editor opens in multi-lang mode automatically.
498
+
499
+ ### Utilities
500
+
501
+ ```ts
502
+ import { resolveLabel, isMultiLang } from '@consilioweb/admin-nav'
503
+
504
+ // Resolve a label to the current language
505
+ resolveLabel({ fr: 'Pages', en: 'Pages' }, 'fr') // 'Pages'
506
+ resolveLabel({ fr: 'Contenu', en: 'Content' }, 'en') // 'Content'
507
+ resolveLabel('Simple string', 'fr') // 'Simple string'
508
+
509
+ // Type guard
510
+ isMultiLang({ fr: 'Oui', en: 'Yes' }) // true
511
+ isMultiLang('plain') // false
512
+ ```
513
+
514
+ ### `usePluginTranslation` Hook
515
+
516
+ Type-safe wrapper around Payload's `useTranslation` that accepts plugin custom keys:
517
+
518
+ ```ts
519
+ import { usePluginTranslation } from '@consilioweb/admin-nav/client'
520
+
521
+ function MyComponent() {
522
+ const { t, i18n } = usePluginTranslation()
523
+ return <span>{t('plugin-admin-nav:save')}</span>
524
+ }
525
+ ```
526
+
527
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
528
+
529
+ ## Built-in Icons
530
+
531
+ The plugin includes 70+ inline SVG icons with zero external dependencies. All icons use a 24x24 viewBox and are Lucide-compatible.
532
+
533
+ Use any icon name in `NavItemConfig.icon`:
534
+
535
+ ```ts
536
+ { id: 'pages', href: '/admin/collections/pages', label: 'Pages', icon: 'file-text' }
537
+ ```
538
+
539
+ For color dots instead of icons, use a hex color:
540
+
541
+ ```ts
542
+ { id: 'urgent', href: '...', label: 'Urgent', icon: '#EF4444' }
543
+ ```
544
+
545
+ <details>
546
+ <summary><strong>Full icon list (70+)</strong></summary>
547
+
548
+ | Category | Icons |
549
+ |----------|-------|
550
+ | **Navigation** | `home`, `layout-dashboard`, `settings`, `menu`, `compass`, `map`, `navigation` |
551
+ | **Content** | `file-text`, `newspaper`, `image`, `tag`, `calendar`, `bookmark`, `clipboard` |
552
+ | **Support** | `ticket`, `message-square`, `users`, `shield-check`, `mail-search`, `folder-kanban`, `file-up` |
553
+ | **Management** | `receipt`, `briefcase`, `wallet`, `credit-card`, `truck`, `package`, `shopping-cart` |
554
+ | **Config** | `database`, `globe`, `palette`, `key`, `monitor`, `server`, `cloud` |
555
+ | **SEO** | `search`, `trending-up`, `bar-chart`, `link`, `award`, `target`, `activity` |
556
+ | **Misc** | `heart`, `star`, `bell`, `zap`, `gift`, `rocket`, `flag`, `coffee`, `music`, `camera` |
557
+
558
+ </details>
559
+
560
+ ### Programmatic Access
561
+
562
+ ```ts
563
+ import { getIconNames, getIconPath, iconPaths } from '@consilioweb/admin-nav'
564
+
565
+ // Get all available icon names
566
+ const names = getIconNames() // ['home', 'file-text', ...]
567
+
568
+ // Get the SVG path data for an icon
569
+ const path = getIconPath('home') // 'M3 9l9-7 9 7v11a2...'
570
+
571
+ // Access the full registry
572
+ console.log(Object.keys(iconPaths).length) // 70+
573
+ ```
574
+
575
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
576
+
577
+ ## API Endpoints
578
+
579
+ All endpoints are prefixed with the configured `endpointBasePath` (default: `/admin-nav`). All endpoints require an authenticated admin user.
580
+
581
+ | Method | Path | Description |
582
+ |--------|------|-------------|
583
+ | `GET` | `/admin-nav/preferences` | Get the current user's navigation preferences |
584
+ | `PATCH` | `/admin-nav/preferences` | Save the current user's navigation layout |
585
+ | `DELETE` | `/admin-nav/preferences` | Reset to default navigation |
586
+ | `GET` | `/admin-nav/default-nav` | Get the plugin's default nav config |
587
+
588
+ ### `useNavPreferences` Hook
589
+
590
+ For custom components that need to interact with nav preferences:
591
+
592
+ ```ts
593
+ import { useNavPreferences } from '@consilioweb/admin-nav/client'
594
+
595
+ function MyComponent() {
596
+ const { layout, isLoaded, isSaving, isCustom, save, reset, reload } = useNavPreferences()
597
+
598
+ if (!isLoaded) return <p>Loading...</p>
599
+
600
+ return (
601
+ <div>
602
+ <p>Groups: {layout.groups.length}</p>
603
+ <p>Custom layout: {isCustom ? 'Yes' : 'No'}</p>
604
+ <button onClick={reset}>Reset to defaults</button>
605
+ </div>
606
+ )
607
+ }
608
+ ```
609
+
610
+ | Property | Type | Description |
611
+ |----------|------|-------------|
612
+ | `layout` | `NavLayout` | Current navigation layout (user's or default) |
613
+ | `isLoaded` | `boolean` | Whether preferences have been fetched |
614
+ | `isSaving` | `boolean` | Whether a save operation is in progress |
615
+ | `isCustom` | `boolean` | Whether the user has customized their nav |
616
+ | `save` | `(layout: NavLayout) => Promise<void>` | Save a new layout |
617
+ | `reset` | `() => Promise<void>` | Reset to default navigation |
618
+ | `reload` | `() => Promise<void>` | Reload preferences from server |
619
+
620
+ ### Caching Strategy
621
+
622
+ The hook uses a **two-tier cache** for instant rendering with zero flash:
623
+
624
+ 1. **Module-level cache** — JavaScript variables at the module scope survive React component re-mounts during SPA navigation. The layout is available immediately in `useState()`, so subsequent page navigations render the nav instantly with no loading state.
625
+
626
+ 2. **sessionStorage cache** — Persists across full page reloads. Read in `useEffect` (post-hydration) to avoid React hydration mismatch errors.
627
+
628
+ On the server, module variables are always `null`, matching the empty initial state on the client — no hydration mismatch. After the first successful fetch, both caches are populated and all subsequent renders are instant.
629
+
630
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
631
+
632
+ ## Components
633
+
634
+ ### Client Components
635
+
636
+ | Component / Hook | Description |
637
+ |------------------|-------------|
638
+ | `AdminNav` | Main sidebar navigation component (injected via `beforeNavLinks`) |
639
+ | `NavCustomizer` | Full drag & drop editor for the navigation layout |
640
+ | `SortableGroup` | Draggable group component (used by NavCustomizer) |
641
+ | `SortableItem` | Draggable item component (used by NavCustomizer) |
642
+ | `GroupEditor` | Modal for editing group properties (with multi-lang toggle) |
643
+ | `NavItemEditor` | Modal for editing item properties, sub-items, and multi-lang labels |
644
+ | `IconPicker` | Icon selection dropdown with search and color mode |
645
+ | `useNavPreferences` | Hook for reading/saving nav preferences |
646
+ | `usePluginTranslation` | Type-safe i18n hook with plugin translation keys |
647
+
648
+ ### Server Views
649
+
650
+ | Component | Path | Description |
651
+ |-----------|------|-------------|
652
+ | `NavCustomizerView` | `/admin/nav-customizer` | Admin view wrapping the NavCustomizer in Payload's DefaultTemplate |
653
+
654
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
655
+
656
+ ## Package Exports
657
+
658
+ ```ts
659
+ // Main entry — plugin, collection, endpoints, icons, types, i18n utilities
660
+ import {
661
+ adminNavPlugin,
662
+ autoDiscoverNav,
663
+ createAdminNavPreferencesCollection,
664
+ createGetPreferencesHandler,
665
+ createSavePreferencesHandler,
666
+ createResetPreferencesHandler,
667
+ getIconNames,
668
+ getIconPath,
669
+ iconPaths,
670
+ resolveLabel,
671
+ isMultiLang,
672
+ } from '@consilioweb/admin-nav'
673
+ import type {
674
+ NavItemConfig,
675
+ NavGroupConfig,
676
+ NavLayout,
677
+ AdminNavPluginConfig,
678
+ LocalizedString,
679
+ } from '@consilioweb/admin-nav'
680
+
681
+ // Client components — React components for Payload admin UI
682
+ import {
683
+ AdminNav,
684
+ NavCustomizer,
685
+ SortableGroup,
686
+ SortableItem,
687
+ GroupEditor,
688
+ NavItemEditor,
689
+ IconPicker,
690
+ useNavPreferences,
691
+ usePluginTranslation,
692
+ resolveLabel,
693
+ isMultiLang,
694
+ } from '@consilioweb/admin-nav/client'
695
+ import type { PluginAdminNavTranslationKeys } from '@consilioweb/admin-nav/client'
696
+
697
+ // Server views — admin views wrapped in DefaultTemplate
698
+ import { NavCustomizerView } from '@consilioweb/admin-nav/views'
699
+ ```
700
+
701
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
702
+
703
+ ## Requirements
704
+
705
+ - **Node.js** >= 18
706
+ - **Payload CMS** 3.x
707
+ - **React** 18.x or 19.x (for admin UI components)
708
+ - **Database**: Any Payload-supported adapter (SQLite, PostgreSQL, MongoDB)
709
+
710
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
711
+
712
+ ## Uninstall
713
+
714
+ 1. Remove the plugin from your `payload.config.ts`
715
+ 2. Uninstall the package:
716
+
717
+ ```bash
718
+ pnpm remove @consilioweb/admin-nav
719
+ ```
720
+
721
+ 3. Regenerate the importmap:
722
+
723
+ ```bash
724
+ pnpm generate:importmap
725
+ ```
726
+
727
+ ### Data cleanup (optional)
728
+
729
+ The `admin-nav-preferences` collection data remains in your database after uninstall. To remove it:
730
+
731
+ **SQLite:**
732
+ ```sql
733
+ DROP TABLE IF EXISTS admin_nav_preferences;
734
+ ```
735
+
736
+ **PostgreSQL:**
737
+ ```sql
738
+ DROP TABLE IF EXISTS "admin-nav-preferences" CASCADE;
739
+ ```
740
+
741
+ **MongoDB:**
742
+ ```js
743
+ db.getCollection('admin-nav-preferences').drop()
744
+ ```
745
+
746
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
747
+
748
+ ## License
749
+
750
+ [MIT](LICENSE)
751
+
752
+ <img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="line">
753
+
754
+ <div align="center">
755
+
756
+ ### Author
757
+
758
+ **Made with passion by [ConsilioWEB](https://consilioweb.fr)**
759
+
760
+ <a href="https://www.linkedin.com/in/christophe-lopez/">
761
+ <img src="https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" alt="LinkedIn">
762
+ </a>
763
+ <a href="https://github.com/pOwn3d">
764
+ <img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white" alt="GitHub">
765
+ </a>
766
+ <a href="https://consilioweb.fr">
767
+ <img src="https://img.shields.io/badge/Website-consilioweb.fr-3B82F6?style=for-the-badge&logo=google-chrome&logoColor=white" alt="Website">
768
+ </a>
769
+
770
+ <br><br>
771
+
772
+ <img src="https://capsule-render.vercel.app/api?type=waving&color=gradient&customColorList=6,11,20&height=100&section=footer" width="100%"/>
773
+
774
+ </div>