@veiag/payload-cmdk 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.
Files changed (38) hide show
  1. package/README.md +594 -0
  2. package/dist/components/CommandMenuContext.d.ts +15 -0
  3. package/dist/components/CommandMenuContext.js +430 -0
  4. package/dist/components/CommandMenuContext.js.map +1 -0
  5. package/dist/components/SearchButton.d.ts +8 -0
  6. package/dist/components/SearchButton.js +106 -0
  7. package/dist/components/SearchButton.js.map +1 -0
  8. package/dist/components/SearchButton.scss +133 -0
  9. package/dist/components/cmdk/command.scss +334 -0
  10. package/dist/components/cmdk/index.d.ts +12 -0
  11. package/dist/components/cmdk/index.js +77 -0
  12. package/dist/components/cmdk/index.js.map +1 -0
  13. package/dist/components/modal.scss +94 -0
  14. package/dist/endpoints/customEndpointHandler.d.ts +2 -0
  15. package/dist/endpoints/customEndpointHandler.js +7 -0
  16. package/dist/endpoints/customEndpointHandler.js.map +1 -0
  17. package/dist/exports/client.d.ts +2 -0
  18. package/dist/exports/client.js +4 -0
  19. package/dist/exports/client.js.map +1 -0
  20. package/dist/exports/rsc.d.ts +0 -0
  21. package/dist/exports/rsc.js +2 -0
  22. package/dist/exports/rsc.js.map +1 -0
  23. package/dist/hooks/useMutationObserver.d.ts +1 -0
  24. package/dist/hooks/useMutationObserver.js +21 -0
  25. package/dist/hooks/useMutationObserver.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.js +74 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/translations/index.d.ts +32 -0
  30. package/dist/translations/index.js +38 -0
  31. package/dist/translations/index.js.map +1 -0
  32. package/dist/types.d.ts +223 -0
  33. package/dist/types.js +6 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/utils/index.d.ts +30 -0
  36. package/dist/utils/index.js +191 -0
  37. package/dist/utils/index.js.map +1 -0
  38. package/package.json +126 -0
package/README.md ADDED
@@ -0,0 +1,594 @@
1
+ # Payload CMDK
2
+
3
+ A powerful command menu plugin for [Payload CMS](https://payloadcms.com) that enhances navigation and accessibility within the admin panel. Quickly search and navigate through collections, globals, and custom actions using keyboard shortcuts.
4
+
5
+ ![Demo of opening command menu, searching for a collection, and navigating](docs/usage.gif)
6
+
7
+ ## Features
8
+
9
+ ✨ **Quick Search** - Instantly search across all collections and globals
10
+ ⌨️ **Keyboard Shortcuts** - Fully customizable keyboard shortcuts powered by [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro)
11
+ 🔍 **Collection Submenu** - Search within collection documents by their title field
12
+ 🎨 **Custom Icons** - Use any [Lucide icon](https://lucide.dev/icons) for collections and globals
13
+ 🎯 **Custom Items** - Add custom actions and menu groups
14
+ 🌍 **i18n Support** - Built-in English and Ukrainian translations, easily add your own
15
+ 🖥️ **Cross-platform** - Optimized shortcuts for both macOS and Windows/Linux
16
+
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @veiag/payload-cmdk
22
+ # or
23
+ yarn add @veiag/payload-cmdk
24
+ # or
25
+ pnpm add @veiag/payload-cmdk
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ The plugin works out of the box with minimal configuration:
31
+
32
+ ```typescript
33
+ import { payloadCmdk } from '@veiag/payload-cmdk'
34
+ import { buildConfig } from 'payload'
35
+
36
+ export default buildConfig({
37
+ // ... your config
38
+ plugins: [
39
+ payloadCmdk({
40
+ // Plugin works without any options!
41
+ }),
42
+ ],
43
+ })
44
+ ```
45
+
46
+ This will:
47
+ - Add a search button to the admin panel
48
+ - Enable `⌘K` (Mac) / `Ctrl+K` (Windows/Linux) keyboard shortcut
49
+ - List all collections and globals in the command menu
50
+ - Enable collection submenu search
51
+
52
+ ## Configuration
53
+
54
+ ### Full Configuration Example
55
+
56
+ ```typescript
57
+ import { payloadCmdk } from '@veiag/payload-cmdk'
58
+ import { buildConfig } from 'payload'
59
+
60
+ export default buildConfig({
61
+ plugins: [
62
+ payloadCmdk({
63
+ // Keyboard shortcut to open the menu
64
+ shortcut: ['meta+k', 'ctrl+k'], // Default
65
+
66
+ // Search button configuration
67
+ searchButton: {
68
+ position: 'actions', // 'actions' | 'nav'
69
+ },
70
+
71
+ // Backdrop blur effect
72
+ blurBg: true, // Default
73
+
74
+ // Collection submenu configuration
75
+ submenu: {
76
+ enabled: true, // Default
77
+ shortcut: 'shift+enter', // 'shift+enter' | 'enter'
78
+ icons: {
79
+ posts: 'file-text',
80
+ users: 'user',
81
+ },
82
+ },
83
+
84
+ // Custom icons for collections and globals
85
+ icons: {
86
+ collections: {
87
+ posts: 'file-text',
88
+ pages: 'file',
89
+ media: 'image',
90
+ users: 'users',
91
+ },
92
+ globals: {
93
+ settings: 'settings',
94
+ navigation: 'menu',
95
+ },
96
+ },
97
+
98
+ // Collections/globals to ignore
99
+ slugsToIgnore: ['payload-migrations', 'payload-preferences'],
100
+
101
+ // Custom menu items
102
+ customItems: [
103
+ {
104
+ type: 'group',
105
+ title: 'Quick Actions',
106
+ items: [
107
+ {
108
+ type: 'item',
109
+ slug: 'view-site',
110
+ label: 'View Site',
111
+ icon: 'external-link',
112
+ action: {
113
+ type: 'link',
114
+ href: 'https://your-site.com',
115
+ },
116
+ },
117
+ {
118
+ type: 'item',
119
+ slug: 'clear-cache',
120
+ label: 'Clear Cache',
121
+ icon: 'trash-2',
122
+ action: {
123
+ type: 'api',
124
+ method: 'POST',
125
+ href: '/api/cache/clear',
126
+ },
127
+ },
128
+ ],
129
+ },
130
+ ],
131
+
132
+ // Disable the plugin
133
+ disabled: false, // Default
134
+ }),
135
+ ],
136
+ })
137
+ ```
138
+
139
+ ## Configuration Options
140
+
141
+ ### `shortcut`
142
+
143
+ Keyboard shortcut to open the command menu. Powered by [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro).
144
+
145
+ - **Type:** `string | string[]`
146
+ - **Default:** `['meta+k', 'ctrl+k']`
147
+
148
+ The default provides cross-platform support:
149
+ - `meta+k` - Works on macOS (⌘K)
150
+ - `ctrl+k` - Works on Windows/Linux (Ctrl+K)
151
+
152
+ **Examples:**
153
+
154
+ ```typescript
155
+ // Single shortcut
156
+ shortcut: 'ctrl+shift+k'
157
+
158
+ // Multiple shortcuts for cross-platform support
159
+ shortcut: ['meta+k', 'ctrl+k']
160
+
161
+ // Custom combinations
162
+ shortcut: ['meta+/', 'ctrl+/']
163
+ ```
164
+
165
+
166
+ ### `searchButton`
167
+
168
+ Configuration for the search button displayed in the admin panel.
169
+
170
+ - **Type:** `{ position?: 'actions' | 'nav' } | false`
171
+ - **Default:** `{ position: 'actions' }`
172
+
173
+ **Options:**
174
+ - `position: 'actions'` - Display in the action buttons area (default)
175
+ - `position: 'nav'` - Display in the navigation sidebar
176
+ - `false` - Hide the search button completely (keyboard shortcut still works)
177
+
178
+ **Examples:**
179
+
180
+ ```typescript
181
+ // Display in navigation
182
+ searchButton: {
183
+ position: 'nav'
184
+ }
185
+
186
+ // Hide search button
187
+ searchButton: false
188
+ ```
189
+ Actions button position:
190
+
191
+ ![Actions button](/docs/position_actions.png)
192
+
193
+
194
+ Navigation button position:
195
+
196
+ ![Navigation button](/docs/position_nav.png)
197
+
198
+
199
+ ### `blurBg`
200
+
201
+ Enable backdrop blur effect when the command menu is open.
202
+
203
+ - **Type:** `boolean`
204
+ - **Default:** `true`
205
+
206
+ ```typescript
207
+ blurBg: false // Disable blur effect
208
+ ```
209
+
210
+ ### `submenu`
211
+
212
+ Configure submenu behavior for searching within collection documents.
213
+
214
+ - **Type:** `object`
215
+ - **Default:** `{ enabled: true, shortcut: 'shift+enter' }`
216
+
217
+ **Options:**
218
+
219
+ | Property | Type | Default | Description |
220
+ |----------|------|---------|-------------|
221
+ | `enabled` | `boolean` | `true` | Enable/disable submenu functionality |
222
+ | `shortcut` | `'shift+enter'` \| `'enter'` | `'shift+enter'` | Keyboard shortcut to open submenu |
223
+ | `icons` | `object` | `undefined` | Custom icons for collection submenus |
224
+
225
+ **Shortcut behavior:**
226
+ - `shift+enter`: Shift+Enter opens submenu, Enter navigates to collection list
227
+ - `enter`: Enter opens submenu, Shift+Enter navigates to collection list
228
+
229
+ ![Searching within a collection submenu](docs/searching_in_collection.png)
230
+
231
+ **Example:**
232
+
233
+ ```typescript
234
+ submenu: {
235
+ enabled: true,
236
+ shortcut: 'enter',
237
+ icons: {
238
+ posts: 'file-text',
239
+ products: 'shopping-cart',
240
+ }
241
+ }
242
+ ```
243
+
244
+ The submenu searches documents by their `useAsTitle` field (or `id` if not specified). You can configure this in your collection:
245
+
246
+ ```typescript
247
+ {
248
+ slug: 'posts',
249
+ admin: {
250
+ useAsTitle: 'title' // Submenu will search by this field
251
+ }
252
+ }
253
+ ```
254
+
255
+ ### `icons`
256
+
257
+ Customize icons for collections and globals using [Lucide icon names](https://lucide.dev/icons).
258
+
259
+ - **Type:** `object`
260
+ - **Default:** `{ collections: {}, globals: {} }`
261
+
262
+ **Default icons:**
263
+ - Collections: `Files` icon
264
+ - Globals: `Globe` icon
265
+
266
+ **Example:**
267
+
268
+ ```typescript
269
+ icons: {
270
+ collections: {
271
+ posts: 'file-text',
272
+ pages: 'file',
273
+ media: 'image',
274
+ users: 'users',
275
+ categories: 'folder',
276
+ },
277
+ globals: {
278
+ settings: 'settings',
279
+ navigation: 'menu',
280
+ footer: 'layout',
281
+ }
282
+ }
283
+ ```
284
+
285
+ Browse all available icons at [lucide.dev/icons](https://lucide.dev/icons).
286
+
287
+ ![Icons preview](/docs/icons.png)
288
+
289
+ ### `customItems`
290
+
291
+ Add custom menu items and groups to the command menu.
292
+
293
+ - **Type:** `Array<CustomMenuItem | CustomMenuGroup>`
294
+ - **Default:** `[]`
295
+
296
+ #### Custom Menu Item
297
+
298
+ ```typescript
299
+ {
300
+ type: 'item',
301
+ slug: 'unique-slug',
302
+ label: 'Item Label', // Can be localized
303
+ icon: 'lucide-icon-name', // Optional, from lucide.dev/icons
304
+ action: {
305
+ type: 'link' | 'api',
306
+ href: '/path/or/url',
307
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE', // For API actions
308
+ body?: { key: 'value' } // For API actions
309
+ }
310
+ }
311
+ ```
312
+
313
+ #### Custom Menu Group
314
+
315
+ ```typescript
316
+ {
317
+ type: 'group',
318
+ title: 'Group Title', // Can be localized
319
+ items: [
320
+ // Array of CustomMenuItem
321
+ ]
322
+ }
323
+ ```
324
+
325
+ **Example with localization:**
326
+
327
+ ```typescript
328
+ customItems: [
329
+ {
330
+ type: 'group',
331
+ title: {
332
+ en: 'Quick Actions',
333
+ uk: 'Швидкі дії',
334
+ },
335
+ items: [
336
+ {
337
+ type: 'item',
338
+ slug: 'view-site',
339
+ label: {
340
+ en: 'View Site',
341
+ uk: 'Переглянути сайт',
342
+ },
343
+ icon: 'external-link',
344
+ action: {
345
+ type: 'link',
346
+ href: 'https://your-site.com',
347
+ },
348
+ },
349
+ {
350
+ type: 'item',
351
+ slug: 'regenerate',
352
+ label: 'Regenerate Cache',
353
+ icon: 'refresh-cw',
354
+ action: {
355
+ type: 'api',
356
+ method: 'POST',
357
+ href: '/api/cache/regenerate',
358
+ },
359
+ },
360
+ ],
361
+ },
362
+ ]
363
+ ```
364
+
365
+ ### `slugsToIgnore`
366
+
367
+ Specify which collection/global slugs to exclude from the command menu.
368
+
369
+ - **Type:** `CollectionSlug[] | { ignoreList: CollectionSlug[], replaceDefaults?: boolean }`
370
+ - **Default:** `['payload-migrations', 'payload-preferences', 'payload-locked-documents']`
371
+
372
+ **Examples:**
373
+
374
+ ```typescript
375
+ // Add to default ignore list
376
+ slugsToIgnore: ['internal-collection', 'test-data']
377
+
378
+ // Replace default ignore list completely
379
+ slugsToIgnore: {
380
+ ignoreList: ['my-hidden-collection'],
381
+ replaceDefaults: true
382
+ }
383
+ ```
384
+
385
+ ### `disabled`
386
+
387
+ Completely disable the plugin.
388
+
389
+ - **Type:** `boolean`
390
+ - **Default:** `false`
391
+
392
+ ```typescript
393
+ disabled: process.env.DISABLE_COMMAND_MENU === 'true'
394
+ ```
395
+
396
+ ## Custom Translations
397
+
398
+ The plugin includes built-in translations for:
399
+ - 🇬🇧 English (`en`)
400
+ - 🇺🇦 Ukrainian (`uk`)
401
+
402
+ You can add translations for other languages using Payload's i18n configuration:
403
+
404
+ ```typescript
405
+ import { buildConfig } from 'payload'
406
+
407
+ export default buildConfig({
408
+ i18n: {
409
+ supportedLanguages: {
410
+ //You can learn more about adding languages in the Payload docs
411
+ en,
412
+ uk,
413
+ de,
414
+ fr,
415
+ },
416
+ translations: {
417
+ de: {
418
+ cmdkPlugin: {
419
+ loading: 'Lädt...',
420
+ navigate: 'zum Navigieren',
421
+ noResults: 'Keine Ergebnisse gefunden',
422
+ open: 'zum Öffnen',
423
+ search: 'Sammlungen, Globals durchsuchen...',
424
+ searchIn: 'Suchen in {{label}}',
425
+ searchInCollection: 'in Sammlung suchen',
426
+ searchShort: 'Suchen',
427
+ },
428
+ },
429
+ fr: {
430
+ cmdkPlugin: {
431
+ loading: 'Chargement...',
432
+ navigate: 'pour naviguer',
433
+ noResults: 'Aucun résultat trouvé',
434
+ open: 'pour ouvrir',
435
+ search: 'Rechercher collections, globals...',
436
+ searchIn: 'Rechercher dans {{label}}',
437
+ searchInCollection: 'pour rechercher dans la collection',
438
+ searchShort: 'Rechercher',
439
+ },
440
+ },
441
+ },
442
+ },
443
+ plugins: [
444
+ payloadCmdk({
445
+ // Your config
446
+ }),
447
+ ],
448
+ })
449
+ ```
450
+
451
+ ### Available Translation Keys
452
+
453
+ All translation keys are under the `cmdkPlugin` namespace:
454
+
455
+ | Key | Description | Example (EN) |
456
+ |-----|-------------|--------------|
457
+ | `search` | Main search placeholder | "Search collections, globals..." |
458
+ | `searchShort` | Short search label | "Search" |
459
+ | `searchIn` | Submenu search placeholder | "Search in {{label}}" |
460
+ | `loading` | Loading state | "Loading..." |
461
+ | `noResults` | No results state | "No results found" |
462
+ | `navigate` | Footer hint for navigation | "to navigate" |
463
+ | `searchInCollection` | Footer hint for collection search | "to search in collection" |
464
+ | `open` | Footer hint for opening documents | "to open" |
465
+
466
+
467
+ ## Keyboard Shortcuts
468
+
469
+ ### Global Shortcuts
470
+
471
+ | Shortcut | Action |
472
+ |----------|--------|
473
+ | `⌘K` / `Ctrl+K` | Open/close command menu |
474
+ | `Esc` | Close menu or go back in submenu |
475
+ | `↑` `↓` | Navigate items |
476
+ | `Enter` | Select item or navigate to collection |
477
+ | `Shift+Enter` | Search within collection (default) |
478
+
479
+ ### In Submenu
480
+
481
+ | Shortcut | Action |
482
+ |----------|--------|
483
+ | `Esc` | Go back to main menu |
484
+ | `Enter` | Open selected document |
485
+
486
+ ## Examples
487
+
488
+ ### Minimal Setup
489
+
490
+ ```typescript
491
+ export default buildConfig({
492
+ plugins: [payloadCmdk()],
493
+ })
494
+ ```
495
+
496
+ ### Custom Shortcuts Only
497
+
498
+ ```typescript
499
+ export default buildConfig({
500
+ plugins: [
501
+ payloadCmdk({
502
+ shortcut: ['meta+/', 'ctrl+/'],
503
+ searchButton: false, // Hide button, only use keyboard
504
+ }),
505
+ ],
506
+ })
507
+ ```
508
+
509
+ ### With Custom Actions
510
+
511
+ ```typescript
512
+ export default buildConfig({
513
+ plugins: [
514
+ payloadCmdk({
515
+ customItems: [
516
+ {
517
+ type: 'item',
518
+ slug: 'documentation',
519
+ label: 'View Documentation',
520
+ icon: 'book-open',
521
+ action: {
522
+ type: 'link',
523
+ href: 'https://docs.your-site.com',
524
+ },
525
+ },
526
+ ],
527
+ }),
528
+ ],
529
+ })
530
+ ```
531
+
532
+ ### Full Custom Theme
533
+
534
+ ```typescript
535
+ export default buildConfig({
536
+ plugins: [
537
+ payloadCmdk({
538
+ icons: {
539
+ collections: {
540
+ posts: 'newspaper',
541
+ pages: 'file-text',
542
+ media: 'image',
543
+ categories: 'folder-tree',
544
+ tags: 'tag',
545
+ users: 'user-circle',
546
+ comments: 'message-circle',
547
+ },
548
+ globals: {
549
+ header: 'layout-template',
550
+ footer: 'layout',
551
+ settings: 'settings-2',
552
+ navigation: 'navigation',
553
+ seo: 'search',
554
+ },
555
+ },
556
+ submenu: {
557
+ enabled: true,
558
+ icons: {
559
+ posts: 'file-text',
560
+ pages: 'file',
561
+ media: 'image',
562
+ },
563
+ },
564
+ }),
565
+ ],
566
+ })
567
+ ```
568
+
569
+ ## Contributing
570
+
571
+ Contributions are welcome! Please feel free to submit a Pull Request.
572
+
573
+ 1. Fork the repository
574
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
575
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
576
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
577
+ 5. Open a Pull Request
578
+
579
+ ## Issues
580
+
581
+ Found a bug or have a feature request? Please open an issue on [GitHub](https://github.com/VeiaG/payload-cmdk/issues).
582
+
583
+ ## License
584
+
585
+ MIT © [VeiaG](https://github.com/VeiaG)
586
+
587
+ ## Links
588
+
589
+ - [GitHub Repository](https://github.com/VeiaG/payload-cmdk/tree/main)
590
+ - [Payload CMS](https://payloadcms.com)
591
+ - [Lucide Icons](https://lucide.dev/icons)
592
+ - [react-hotkeys-hook Documentation](https://react-hotkeys-hook.vercel.app/docs/intro)
593
+
594
+ # More plugins and payload resources at [PayloadCMS Extensions](https://payload.veiag.dev/)
@@ -0,0 +1,15 @@
1
+ import './modal.scss';
2
+ import type { CommandMenuContextProps, CommandMenuGroup, CommandMenuItem, CommandMenuPage } from 'src/types';
3
+ interface CommandMenuContextType {
4
+ closeMenu: () => void;
5
+ currentPage: CommandMenuPage;
6
+ groups: CommandMenuGroup[];
7
+ isOpen: boolean;
8
+ items: CommandMenuItem[];
9
+ openMenu: () => void;
10
+ setPage: (page: CommandMenuPage) => void;
11
+ toggleMenu: () => void;
12
+ }
13
+ export declare const useCommandMenu: () => CommandMenuContextType;
14
+ export declare const CommandMenuProvider: React.FC<CommandMenuContextProps>;
15
+ export {};