@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/LICENSE +21 -0
- package/README.md +774 -0
- package/dist/client.cjs +6119 -0
- package/dist/client.d.cts +147 -0
- package/dist/client.d.ts +147 -0
- package/dist/client.js +6102 -0
- package/dist/index.cjs +587 -0
- package/dist/index.d.cts +164 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +575 -0
- package/dist/views.cjs +32 -0
- package/dist/views.d.cts +11 -0
- package/dist/views.d.ts +11 -0
- package/dist/views.js +30 -0
- package/package.json +128 -0
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¢er=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
|
+
|  |  |
|
|
88
|
+
|
|
89
|
+
| Item Editor (with sub-menus) | Group Editor |
|
|
90
|
+
|:---:|:---:|
|
|
91
|
+
|  |  |
|
|
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§ion=footer" width="100%"/>
|
|
773
|
+
|
|
774
|
+
</div>
|