@veiag/payload-enhanced-sidebar 0.2.0 → 0.3.0-beta.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/README.md +62 -5
- package/dist/components/EnhancedSidebar/NavContent/index.d.ts +3 -0
- package/dist/components/EnhancedSidebar/NavContent/index.js +34 -0
- package/dist/components/EnhancedSidebar/NavContent/index.js.map +1 -0
- package/dist/components/EnhancedSidebar/NavItem/index.d.ts +0 -2
- package/dist/components/EnhancedSidebar/NavItem/index.js +3 -1
- package/dist/components/EnhancedSidebar/NavItem/index.js.map +1 -1
- package/dist/components/EnhancedSidebar/SidebarContent.d.ts +6 -4
- package/dist/components/EnhancedSidebar/SidebarContent.js +31 -140
- package/dist/components/EnhancedSidebar/SidebarContent.js.map +1 -1
- package/dist/components/EnhancedSidebar/TabsBar/TabItem.d.ts +2 -0
- package/dist/components/EnhancedSidebar/TabsBar/TabItem.js +4 -4
- package/dist/components/EnhancedSidebar/TabsBar/TabItem.js.map +1 -1
- package/dist/components/EnhancedSidebar/TabsBar/index.d.ts +2 -0
- package/dist/components/EnhancedSidebar/TabsBar/index.js +4 -2
- package/dist/components/EnhancedSidebar/TabsBar/index.js.map +1 -1
- package/dist/components/EnhancedSidebar/context.d.ts +6 -0
- package/dist/components/EnhancedSidebar/context.js +9 -0
- package/dist/components/EnhancedSidebar/context.js.map +1 -0
- package/dist/components/EnhancedSidebar/hooks/useNavItemState.d.ts +23 -0
- package/dist/components/EnhancedSidebar/hooks/useNavItemState.js +32 -0
- package/dist/components/EnhancedSidebar/hooks/useNavItemState.js.map +1 -0
- package/dist/components/EnhancedSidebar/hooks/useTabState.d.ts +13 -0
- package/dist/components/EnhancedSidebar/hooks/useTabState.js +19 -0
- package/dist/components/EnhancedSidebar/hooks/useTabState.js.map +1 -0
- package/dist/components/EnhancedSidebar/index.js +286 -9
- package/dist/components/EnhancedSidebar/index.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +243 -14
- package/dist/types.js.map +1 -1
- package/dist/utils/index.d.ts +8 -1
- package/dist/utils/index.js +14 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/components/EnhancedSidebar/index.client.d.ts +0 -8
- package/dist/components/EnhancedSidebar/index.client.js +0 -87
- package/dist/components/EnhancedSidebar/index.client.js.map +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -84,10 +84,48 @@ export type BadgeConfig = BadgeConfigApi | BadgeConfigCollectionCount | BadgeCon
|
|
|
84
84
|
* Badge values provided via BadgeProvider context
|
|
85
85
|
*/
|
|
86
86
|
export type BadgeValues = Record<string, number | ReactNode>;
|
|
87
|
+
/**
|
|
88
|
+
* Path to a component, either as a plain string or as an object with a path and
|
|
89
|
+
* additional client props to forward to the component. Follows Payload's component format.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* // Simple path
|
|
94
|
+
* iconComponent: './components/Icons#TabIcon'
|
|
95
|
+
*
|
|
96
|
+
* // With client props — useful for a single component shared across all tabs
|
|
97
|
+
* iconComponent: {
|
|
98
|
+
* path: './components/Icons#TabIcon',
|
|
99
|
+
* clientProps: { icon: 'house' },
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export type SidebarComponent = {
|
|
104
|
+
/** Additional props forwarded to the component on the client */
|
|
105
|
+
clientProps?: Record<string, unknown>;
|
|
106
|
+
/** Component path in Payload's format: `'./path/to/file#ExportName'` */
|
|
107
|
+
path: string;
|
|
108
|
+
} | string;
|
|
109
|
+
/**
|
|
110
|
+
* Mutually exclusive icon config: either a Lucide icon name OR a custom icon component path.
|
|
111
|
+
*/
|
|
112
|
+
type TabIconConfig = {
|
|
113
|
+
/** Icon name from lucide-react */
|
|
114
|
+
icon: IconName;
|
|
115
|
+
iconComponent?: never;
|
|
116
|
+
} | {
|
|
117
|
+
icon?: never;
|
|
118
|
+
/**
|
|
119
|
+
* Path to a custom icon component. Receives `CustomTabIconProps`.
|
|
120
|
+
* Supports a plain string path or `{ path, clientProps }` to forward extra props.
|
|
121
|
+
* Registered automatically in the import map.
|
|
122
|
+
*/
|
|
123
|
+
iconComponent: SidebarComponent;
|
|
124
|
+
};
|
|
87
125
|
/**
|
|
88
126
|
* Sidebar tab that shows content when selected
|
|
89
127
|
*/
|
|
90
|
-
export
|
|
128
|
+
export type SidebarTabContent = {
|
|
91
129
|
/**
|
|
92
130
|
* Badge configuration for this tab.
|
|
93
131
|
* Shows a badge on the tab icon in the tabs bar.
|
|
@@ -111,38 +149,34 @@ export interface SidebarTabContent {
|
|
|
111
149
|
* Use global slugs.
|
|
112
150
|
*/
|
|
113
151
|
globals?: GlobalSlug[];
|
|
114
|
-
/** Icon name from lucide-react */
|
|
115
|
-
icon: IconName;
|
|
116
152
|
/** Unique identifier for the tab */
|
|
117
153
|
id: string;
|
|
118
154
|
/** Tooltip/label for the tab */
|
|
119
155
|
label: LocalizedString;
|
|
120
156
|
type: 'tab';
|
|
121
|
-
}
|
|
122
|
-
|
|
157
|
+
} & TabIconConfig;
|
|
158
|
+
type SidebarTabLinkBase = {
|
|
123
159
|
/**
|
|
124
160
|
* Badge configuration for this link.
|
|
125
161
|
* Shows a badge on the link icon in the tabs bar.
|
|
126
162
|
*/
|
|
127
163
|
badge?: BadgeConfig;
|
|
128
|
-
/** Icon name from lucide-react */
|
|
129
|
-
icon: IconName;
|
|
130
164
|
/** Unique identifier */
|
|
131
165
|
id: string;
|
|
132
166
|
/** Tooltip/label */
|
|
133
167
|
label: LocalizedString;
|
|
134
168
|
type: 'link';
|
|
135
|
-
}
|
|
136
|
-
|
|
169
|
+
} & TabIconConfig;
|
|
170
|
+
type SidebarTabLinkExternal = {
|
|
137
171
|
/** Link href (absolute URL) */
|
|
138
172
|
href: string;
|
|
139
173
|
isExternal: true;
|
|
140
|
-
}
|
|
141
|
-
|
|
174
|
+
} & SidebarTabLinkBase;
|
|
175
|
+
type SidebarTabLinkInternal = {
|
|
142
176
|
/** Link href (relative to admin route) */
|
|
143
177
|
href: '' | `/${string}`;
|
|
144
178
|
isExternal?: false;
|
|
145
|
-
}
|
|
179
|
+
} & SidebarTabLinkBase;
|
|
146
180
|
/**
|
|
147
181
|
* Sidebar link that navigates to a URL (not a tab)
|
|
148
182
|
*/
|
|
@@ -155,12 +189,24 @@ interface BaseSidebarTabItem {
|
|
|
155
189
|
/**
|
|
156
190
|
* Group to add this item to.
|
|
157
191
|
* If matches an existing collection group label, item will be merged into that group.
|
|
158
|
-
* If no match found, a new group will be created.
|
|
159
|
-
* If not specified, item will be shown
|
|
192
|
+
* If no match found, a new group will be created with this label.
|
|
193
|
+
* If not specified, item will be shown as ungrouped (position controlled by `position`).
|
|
160
194
|
*/
|
|
161
195
|
group?: LocalizedString;
|
|
162
196
|
/** Display label */
|
|
163
197
|
label: LocalizedString;
|
|
198
|
+
/**
|
|
199
|
+
* Where to place this item relative to collection groups in the tab.
|
|
200
|
+
* - `'top'` — appears above all collection/global groups
|
|
201
|
+
* - `'bottom'` — appears below all groups (default)
|
|
202
|
+
*
|
|
203
|
+
* Has no effect on items that are merged into an existing collection group via `group`.
|
|
204
|
+
* For new custom groups (unmatched `group` label), controls whether the group appears
|
|
205
|
+
* at the top or bottom of the nav.
|
|
206
|
+
*
|
|
207
|
+
* @default 'bottom'
|
|
208
|
+
*/
|
|
209
|
+
position?: 'bottom' | 'top';
|
|
164
210
|
/** Unique slug for the item */
|
|
165
211
|
slug: string;
|
|
166
212
|
}
|
|
@@ -180,6 +226,158 @@ interface InternalHrefItem extends BaseSidebarTabItem {
|
|
|
180
226
|
* Custom item inside a sidebar tab
|
|
181
227
|
*/
|
|
182
228
|
export type SidebarTabItem = ExternalHrefItem | InternalHrefItem;
|
|
229
|
+
/**
|
|
230
|
+
* Props received by a custom NavItem component registered via `customComponents.NavItem`.
|
|
231
|
+
*
|
|
232
|
+
* Use the `useNavItemState(href)` hook to get reactive `isActive` / `isCurrentPage` values.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```tsx
|
|
236
|
+
* 'use client'
|
|
237
|
+
* import { useNavItemState } from '@veiag/payload-enhanced-sidebar'
|
|
238
|
+
* import type { CustomNavItemProps } from '@veiag/payload-enhanced-sidebar'
|
|
239
|
+
*
|
|
240
|
+
* export const MyNavItem: React.FC<CustomNavItemProps> = ({ entity, href, id, label, badgeConfig }) => {
|
|
241
|
+
* const { isActive, isCurrentPage } = useNavItemState(href)
|
|
242
|
+
* return <a href={href}>{label}</a>
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export type CustomNavItemProps = {
|
|
247
|
+
/** Badge configuration as defined in the plugin config */
|
|
248
|
+
badgeConfig?: BadgeConfig;
|
|
249
|
+
/** The entity (collection, global, or custom item) */
|
|
250
|
+
entity: ExtendedEntity;
|
|
251
|
+
/** Computed href with admin route prefix applied */
|
|
252
|
+
href: string;
|
|
253
|
+
/** DOM element id */
|
|
254
|
+
id: string;
|
|
255
|
+
/** Pre-translated label string */
|
|
256
|
+
label: string;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Props received by a custom NavGroup component registered via `customComponents.NavGroup`.
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```tsx
|
|
263
|
+
* 'use client'
|
|
264
|
+
* import type { CustomNavGroupProps } from '@veiag/payload-enhanced-sidebar'
|
|
265
|
+
*
|
|
266
|
+
* export const MyNavGroup: React.FC<CustomNavGroupProps> = ({ label, isOpen, children }) => {
|
|
267
|
+
* return <div><strong>{label}</strong>{children}</div>
|
|
268
|
+
* }
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
export type CustomNavGroupProps = {
|
|
272
|
+
/** Nav items inside the group */
|
|
273
|
+
children: ReactNode;
|
|
274
|
+
/** Initial open state from nav preferences */
|
|
275
|
+
isOpen?: boolean;
|
|
276
|
+
/** Translated group label */
|
|
277
|
+
label: string;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* Props received by a custom tab icon component set via `iconComponent` on a tab or link.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```tsx
|
|
284
|
+
* 'use client'
|
|
285
|
+
* import type { CustomTabIconProps } from '@veiag/payload-enhanced-sidebar'
|
|
286
|
+
*
|
|
287
|
+
* export const MyIcon: React.FC<CustomTabIconProps> = ({ id, label }) => (
|
|
288
|
+
* <img alt={label} src={`/icons/${id}.svg`} width={20} height={20} />
|
|
289
|
+
* )
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
export type CustomTabIconProps = {
|
|
293
|
+
/** Tab/link id */
|
|
294
|
+
id: string;
|
|
295
|
+
/** Pre-translated label */
|
|
296
|
+
label: string;
|
|
297
|
+
/** Whether this item is a tab or a link */
|
|
298
|
+
type: 'link' | 'tab';
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Props received by a custom TabButton component registered via `customComponents.TabButton`.
|
|
302
|
+
* Used for both `tab` and `link` type items in the tabs bar.
|
|
303
|
+
*
|
|
304
|
+
* Use `useTabState(id)` for tab active state, or `usePathname()` for link active state.
|
|
305
|
+
* Use `useEnhancedSidebar().onTabChange` to trigger tab switches.
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* ```tsx
|
|
309
|
+
* 'use client'
|
|
310
|
+
* import type { CustomTabButtonProps } from '@veiag/payload-enhanced-sidebar'
|
|
311
|
+
* import { useTabState, useEnhancedSidebar } from '@veiag/payload-enhanced-sidebar'
|
|
312
|
+
*
|
|
313
|
+
* export const MyTabButton: React.FC<CustomTabButtonProps> = ({ id, type, icon, label, href }) => {
|
|
314
|
+
* const { isActive } = useTabState(id)
|
|
315
|
+
* const { onTabChange } = useEnhancedSidebar()
|
|
316
|
+
* if (type === 'link') return <a href={href}>{icon}{label}</a>
|
|
317
|
+
* return <button onClick={() => onTabChange(id)}>{icon}{label}</button>
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export type CustomTabButtonProps = {
|
|
322
|
+
/** Badge configuration as defined in the plugin config */
|
|
323
|
+
badge?: BadgeConfig;
|
|
324
|
+
/** Computed href (for link type, with admin route prefix applied) */
|
|
325
|
+
href?: string;
|
|
326
|
+
/** Pre-rendered icon — either from `iconComponent` or the default Lucide icon */
|
|
327
|
+
icon: ReactNode;
|
|
328
|
+
/** Tab/link id */
|
|
329
|
+
id: string;
|
|
330
|
+
/** Whether the link is external (only set for link type) */
|
|
331
|
+
isExternal?: boolean;
|
|
332
|
+
/** Pre-translated label */
|
|
333
|
+
label: string;
|
|
334
|
+
/** Item type — use to differentiate rendering */
|
|
335
|
+
type: 'link' | 'tab';
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Props received by a custom NavContent component registered via `customComponents.NavContent`.
|
|
339
|
+
*
|
|
340
|
+
* Renders in place of the default `<nav>` content area. Use the `useTabState(id)` hook
|
|
341
|
+
* to determine which tab is active and show/hide content accordingly.
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```tsx
|
|
345
|
+
* 'use client'
|
|
346
|
+
* import type { CustomNavContentProps } from '@veiag/payload-enhanced-sidebar'
|
|
347
|
+
* import { useTabState } from '@veiag/payload-enhanced-sidebar'
|
|
348
|
+
*
|
|
349
|
+
* const TabPanel = ({ id, content }: { id: string; content: React.ReactNode }) => {
|
|
350
|
+
* const { isActive } = useTabState(id)
|
|
351
|
+
* return <div style={{ display: isActive ? undefined : 'none' }}>{content}</div>
|
|
352
|
+
* }
|
|
353
|
+
*
|
|
354
|
+
* export const MyNavContent: React.FC<CustomNavContentProps> = ({
|
|
355
|
+
* tabs, tabsContent, beforeNavLinks, afterNavLinks,
|
|
356
|
+
* }) => {
|
|
357
|
+
* return (
|
|
358
|
+
* <nav>
|
|
359
|
+
* {beforeNavLinks}
|
|
360
|
+
* {tabs.map(tab => <TabPanel key={tab.id} id={tab.id} content={tabsContent[tab.id]} />)}
|
|
361
|
+
* {afterNavLinks}
|
|
362
|
+
* </nav>
|
|
363
|
+
* )
|
|
364
|
+
* }
|
|
365
|
+
* ```
|
|
366
|
+
*/
|
|
367
|
+
export type CustomNavContentProps = {
|
|
368
|
+
/** Rendered afterNavLinks from payload config */
|
|
369
|
+
afterNavLinks?: ReactNode;
|
|
370
|
+
/** Content to show when no tabs are defined */
|
|
371
|
+
allContent?: ReactNode;
|
|
372
|
+
/** Rendered beforeNavLinks from payload config */
|
|
373
|
+
beforeNavLinks?: ReactNode;
|
|
374
|
+
/** Tab definitions (id only) for mapping over */
|
|
375
|
+
tabs: Array<{
|
|
376
|
+
id: string;
|
|
377
|
+
}>;
|
|
378
|
+
/** Pre-rendered content per tab id */
|
|
379
|
+
tabsContent: Record<string, ReactNode>;
|
|
380
|
+
};
|
|
183
381
|
/**
|
|
184
382
|
* Configuration for the enhanced sidebar
|
|
185
383
|
*/
|
|
@@ -198,6 +396,37 @@ export interface EnhancedSidebarConfig {
|
|
|
198
396
|
* ```
|
|
199
397
|
*/
|
|
200
398
|
badges?: Record<string, BadgeConfig>;
|
|
399
|
+
/**
|
|
400
|
+
* Custom components to replace the default NavItem, NavGroup, and/or NavContent rendering.
|
|
401
|
+
* Each field accepts a plain path string or a `SidebarComponent` object `{ path, clientProps }`
|
|
402
|
+
* to forward additional props. The plugin registers them in the import map automatically.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```typescript
|
|
406
|
+
* customComponents: {
|
|
407
|
+
* // Plain path
|
|
408
|
+
* NavItem: './components/MySidebar#MyNavItem',
|
|
409
|
+
* // With extra client props forwarded to the component
|
|
410
|
+
* NavGroup: { path: './components/MySidebar#MyNavGroup', clientProps: { collapsible: true } },
|
|
411
|
+
* }
|
|
412
|
+
* ```
|
|
413
|
+
*/
|
|
414
|
+
customComponents?: {
|
|
415
|
+
/**
|
|
416
|
+
* Custom NavContent component. Replaces the default `<nav>` content area.
|
|
417
|
+
* Receives `CustomNavContentProps`. Use `useTabState(id)` to check if a tab is active.
|
|
418
|
+
*/
|
|
419
|
+
NavContent?: SidebarComponent;
|
|
420
|
+
/** Custom NavGroup component. Receives `CustomNavGroupProps`. */
|
|
421
|
+
NavGroup?: SidebarComponent;
|
|
422
|
+
/** Custom NavItem component. Receives `CustomNavItemProps`. */
|
|
423
|
+
NavItem?: SidebarComponent;
|
|
424
|
+
/**
|
|
425
|
+
* Custom tab button component. Used for both `tab` and `link` items in the tabs bar.
|
|
426
|
+
* Receives `CustomTabButtonProps`. Use `useTabState(id)` and `useEnhancedSidebar()` for state.
|
|
427
|
+
*/
|
|
428
|
+
TabButton?: SidebarComponent;
|
|
429
|
+
};
|
|
201
430
|
/**
|
|
202
431
|
* Disable the plugin
|
|
203
432
|
* @default false
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { icons, LucideIcon } from 'lucide-react'\nimport type { CollectionSlug, GlobalSlug, Where } from 'payload'\nimport type { ReactNode } from 'react'\n\nexport type IconName = keyof typeof icons\n\nexport type LocalizedString = { [locale: string]: string } | string\n\nexport type InternalIcon = IconName | LucideIcon\n\n// ============================================\n// Badge Types\n// ============================================\n\n/**\n * Available badge color variants\n */\nexport type BadgeColor = 'default' | 'error' | 'primary' | 'success' | 'warning'\n\n/**\n * Badge configuration Trr API-based fetching\n */\nexport interface BadgeConfigApi {\n /**\n * Badge color variant\n * @default 'default'\n */\n color?: BadgeColor\n /**\n * API endpoint to fetch badge data from.\n * Can be relative (to current origin) or absolute URL.\n */\n endpoint: string\n /**\n * HTTP method for the request\n * @default 'GET'\n */\n method?: 'GET' | 'POST'\n /**\n * Key in the response object to extract the count from\n * @default 'count'\n */\n responseKey?: string\n type: 'api'\n}\n\n/**\n * Badge configuration for provider-based values.\n * Values are provided via BadgeProvider context.\n */\nexport interface BadgeConfigProvider {\n /**\n * Badge color variant\n * @default 'default'\n */\n color?: BadgeColor\n /**\n * Slug to look up in the BadgeProvider values.\n * If not specified, defaults to the item's id/slug.\n */\n slug?: string\n type: 'provider'\n}\n\n/**\n * Badge configuration for automatic collection document count.\n * Fetches from /api/{collectionSlug}?limit=0 and uses totalDocs.\n */\nexport interface BadgeConfigCollectionCount {\n /**\n * Collection slug to count documents from.\n * If not specified, defaults to the item's slug.\n */\n collectionSlug?: CollectionSlug\n /**\n * Badge color variant\n * @default 'default'\n */\n color?: BadgeColor\n type: 'collection-count'\n /**\n * Optional where query to filter documents.\n * Will be serialized as query string.\n * @example { status: { equals: 'draft' } }\n */\n where?: Where\n}\n\n/**\n * Badge configuration - union of all badge types\n */\nexport type BadgeConfig = BadgeConfigApi | BadgeConfigCollectionCount | BadgeConfigProvider\n\n/**\n * Badge values provided via BadgeProvider context\n */\nexport type BadgeValues = Record<string, number | ReactNode>\n\n// ============================================\n// Enhanced Sidebar Types\n// ============================================\n\n/**\n * Sidebar tab that shows content when selected\n */\nexport interface SidebarTabContent {\n /**\n * Badge configuration for this tab.\n * Shows a badge on the tab icon in the tabs bar.\n */\n badge?: BadgeConfig\n /**\n * Collections to show in this tab.\n * If not specified, no collections are shown (unless items are specified).\n * Use collection slugs.\n */\n collections?: CollectionSlug[]\n /**\n * Custom items to add to this tab.\n * Items with `group` will be merged into matching collection groups.\n * Items without `group` will be shown at the bottom as a flat list.\n */\n customItems?: SidebarTabItem[]\n /**\n * Globals to show in this tab.\n * If not specified, no globals are shown.\n * Use global slugs.\n */\n globals?: GlobalSlug[]\n /** Icon name from lucide-react */\n icon: IconName\n /** Unique identifier for the tab */\n id: string\n /** Tooltip/label for the tab */\n label: LocalizedString\n type: 'tab'\n}\n\ninterface SidebarTabLinkBase {\n /**\n * Badge configuration for this link.\n * Shows a badge on the link icon in the tabs bar.\n */\n badge?: BadgeConfig\n /** Icon name from lucide-react */\n icon: IconName\n /** Unique identifier */\n id: string\n /** Tooltip/label */\n label: LocalizedString\n type: 'link'\n}\ninterface SidebarTabLinkExternal extends SidebarTabLinkBase {\n /** Link href (absolute URL) */\n href: string\n isExternal: true\n}\ninterface SidebarTabLinkInternal extends SidebarTabLinkBase {\n /** Link href (relative to admin route) */\n href: '' | `/${string}`\n isExternal?: false\n}\n/**\n * Sidebar link that navigates to a URL (not a tab)\n */\nexport type SidebarTabLink = SidebarTabLinkExternal | SidebarTabLinkInternal\n/**\n * A tab or link in the sidebar tabs bar\n */\nexport type SidebarTab = SidebarTabContent | SidebarTabLink\n\ninterface BaseSidebarTabItem {\n /**\n * Group to add this item to.\n * If matches an existing collection group label, item will be merged into that group.\n * If no match found, a new group will be created.\n * If not specified, item will be shown at the bottom as ungrouped.\n */\n group?: LocalizedString\n /** Display label */\n label: LocalizedString\n /** Unique slug for the item */\n slug: string\n}\ninterface ExternalHrefItem extends BaseSidebarTabItem {\n /** Link href (absolute URL or relative to root) */\n href: string\n /** Whether the link is external, without admin route prefix. */\n isExternal: true\n}\n\ninterface InternalHrefItem extends BaseSidebarTabItem {\n /** Link href (relative to admin route) */\n href: '' | `/${string}`\n /** Whether the link is external, without admin route prefix. */\n isExternal?: false\n}\n/**\n * Custom item inside a sidebar tab\n */\nexport type SidebarTabItem = ExternalHrefItem | InternalHrefItem\n\n/**\n * Configuration for the enhanced sidebar\n */\nexport interface EnhancedSidebarConfig {\n /**\n * Badge configurations for sidebar items (collections, globals, custom items).\n * Key is the slug of the item.\n *\n * @example\n * ```typescript\n * badges: {\n * 'posts': { type: 'collection-count', color: 'primary' },\n * 'orders': { type: 'api', endpoint: '/api/orders/pending', responseKey: 'count' },\n * 'notifications': { type: 'provider', color: 'error' },\n * }\n * ```\n */\n badges?: Record<string, BadgeConfig>\n\n /**\n * Disable the plugin\n * @default false\n */\n disabled?: boolean\n\n /**\n * Custom icons for collections and globals in the default tab.\n */\n icons?: {\n collections?: Partial<Record<CollectionSlug, IconName>>\n globals?: Partial<Record<GlobalSlug, IconName>>\n }\n\n /**\n * Show logout button at the bottom of the tabs bar.\n * @default true\n */\n showLogout?: boolean\n\n /**\n * Tabs and links to show in the sidebar tabs bar.\n * Order matters - items are rendered top to bottom.\n *\n * @default [{ type: 'tab', id: 'default', icon: 'LayoutGrid', label: 'Collections' }]\n */\n tabs?: SidebarTab[]\n}\n\n/**\n * Generic document type for collections, with dynamic keys.\n * We assume values are either string or number for simplicity, useAsTitle is making sure of that.\n */\nexport type GenericCollectionDocument = {\n [key: string]: number | string\n id: string\n}\n\ninterface BaseExtendedEntity {\n label: Record<string, string> | string\n slug: string\n type: 'collection' | 'custom' | 'global'\n}\n\ninterface InternalExtendedEntity extends BaseExtendedEntity {\n href?: '' | `/${string}`\n isExternal?: false\n}\n\ninterface ExternalExtendedEntity extends BaseExtendedEntity {\n href: string\n isExternal: true\n}\n\nexport type ExtendedEntity = ExternalExtendedEntity | InternalExtendedEntity\n\nexport type ExtendedGroup = {\n entities: ExtendedEntity[]\n label: Record<string, string> | string\n}\n"],"names":[],"mappings":"AAqRA,WAGC"}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { icons, LucideIcon } from 'lucide-react'\nimport type { CollectionSlug, GlobalSlug, Where } from 'payload'\nimport type { ReactNode } from 'react'\n\nexport type IconName = keyof typeof icons\n\nexport type LocalizedString = { [locale: string]: string } | string\n\nexport type InternalIcon = IconName | LucideIcon\n\n// ============================================\n// Badge Types\n// ============================================\n\n/**\n * Available badge color variants\n */\nexport type BadgeColor = 'default' | 'error' | 'primary' | 'success' | 'warning'\n\n/**\n * Badge configuration Trr API-based fetching\n */\nexport interface BadgeConfigApi {\n /**\n * Badge color variant\n * @default 'default'\n */\n color?: BadgeColor\n /**\n * API endpoint to fetch badge data from.\n * Can be relative (to current origin) or absolute URL.\n */\n endpoint: string\n /**\n * HTTP method for the request\n * @default 'GET'\n */\n method?: 'GET' | 'POST'\n /**\n * Key in the response object to extract the count from\n * @default 'count'\n */\n responseKey?: string\n type: 'api'\n}\n\n/**\n * Badge configuration for provider-based values.\n * Values are provided via BadgeProvider context.\n */\nexport interface BadgeConfigProvider {\n /**\n * Badge color variant\n * @default 'default'\n */\n color?: BadgeColor\n /**\n * Slug to look up in the BadgeProvider values.\n * If not specified, defaults to the item's id/slug.\n */\n slug?: string\n type: 'provider'\n}\n\n/**\n * Badge configuration for automatic collection document count.\n * Fetches from /api/{collectionSlug}?limit=0 and uses totalDocs.\n */\nexport interface BadgeConfigCollectionCount {\n /**\n * Collection slug to count documents from.\n * If not specified, defaults to the item's slug.\n */\n collectionSlug?: CollectionSlug\n /**\n * Badge color variant\n * @default 'default'\n */\n color?: BadgeColor\n type: 'collection-count'\n /**\n * Optional where query to filter documents.\n * Will be serialized as query string.\n * @example { status: { equals: 'draft' } }\n */\n where?: Where\n}\n\n/**\n * Badge configuration - union of all badge types\n */\nexport type BadgeConfig = BadgeConfigApi | BadgeConfigCollectionCount | BadgeConfigProvider\n\n/**\n * Badge values provided via BadgeProvider context\n */\nexport type BadgeValues = Record<string, number | ReactNode>\n\n// ============================================\n// Enhanced Sidebar Types\n// ============================================\n\n/**\n * Path to a component, either as a plain string or as an object with a path and\n * additional client props to forward to the component. Follows Payload's component format.\n *\n * @example\n * ```typescript\n * // Simple path\n * iconComponent: './components/Icons#TabIcon'\n *\n * // With client props — useful for a single component shared across all tabs\n * iconComponent: {\n * path: './components/Icons#TabIcon',\n * clientProps: { icon: 'house' },\n * }\n * ```\n */\nexport type SidebarComponent =\n | {\n /** Additional props forwarded to the component on the client */\n clientProps?: Record<string, unknown>\n /** Component path in Payload's format: `'./path/to/file#ExportName'` */\n path: string\n }\n | string\n\n/**\n * Mutually exclusive icon config: either a Lucide icon name OR a custom icon component path.\n */\ntype TabIconConfig =\n | {\n /** Icon name from lucide-react */\n icon: IconName\n iconComponent?: never\n }\n | {\n icon?: never\n /**\n * Path to a custom icon component. Receives `CustomTabIconProps`.\n * Supports a plain string path or `{ path, clientProps }` to forward extra props.\n * Registered automatically in the import map.\n */\n iconComponent: SidebarComponent\n }\n\n/**\n * Sidebar tab that shows content when selected\n */\nexport type SidebarTabContent = {\n /**\n * Badge configuration for this tab.\n * Shows a badge on the tab icon in the tabs bar.\n */\n badge?: BadgeConfig\n /**\n * Collections to show in this tab.\n * If not specified, no collections are shown (unless items are specified).\n * Use collection slugs.\n */\n collections?: CollectionSlug[]\n /**\n * Custom items to add to this tab.\n * Items with `group` will be merged into matching collection groups.\n * Items without `group` will be shown at the bottom as a flat list.\n */\n customItems?: SidebarTabItem[]\n /**\n * Globals to show in this tab.\n * If not specified, no globals are shown.\n * Use global slugs.\n */\n globals?: GlobalSlug[]\n /** Unique identifier for the tab */\n id: string\n /** Tooltip/label for the tab */\n label: LocalizedString\n type: 'tab'\n} & TabIconConfig\n\ntype SidebarTabLinkBase = {\n /**\n * Badge configuration for this link.\n * Shows a badge on the link icon in the tabs bar.\n */\n badge?: BadgeConfig\n /** Unique identifier */\n id: string\n /** Tooltip/label */\n label: LocalizedString\n type: 'link'\n} & TabIconConfig\ntype SidebarTabLinkExternal = {\n /** Link href (absolute URL) */\n href: string\n isExternal: true\n} & SidebarTabLinkBase\ntype SidebarTabLinkInternal = {\n /** Link href (relative to admin route) */\n href: '' | `/${string}`\n isExternal?: false\n} & SidebarTabLinkBase\n/**\n * Sidebar link that navigates to a URL (not a tab)\n */\nexport type SidebarTabLink = SidebarTabLinkExternal | SidebarTabLinkInternal\n/**\n * A tab or link in the sidebar tabs bar\n */\nexport type SidebarTab = SidebarTabContent | SidebarTabLink\n\ninterface BaseSidebarTabItem {\n /**\n * Group to add this item to.\n * If matches an existing collection group label, item will be merged into that group.\n * If no match found, a new group will be created with this label.\n * If not specified, item will be shown as ungrouped (position controlled by `position`).\n */\n group?: LocalizedString\n /** Display label */\n label: LocalizedString\n /**\n * Where to place this item relative to collection groups in the tab.\n * - `'top'` — appears above all collection/global groups\n * - `'bottom'` — appears below all groups (default)\n *\n * Has no effect on items that are merged into an existing collection group via `group`.\n * For new custom groups (unmatched `group` label), controls whether the group appears\n * at the top or bottom of the nav.\n *\n * @default 'bottom'\n */\n position?: 'bottom' | 'top'\n /** Unique slug for the item */\n slug: string\n}\ninterface ExternalHrefItem extends BaseSidebarTabItem {\n /** Link href (absolute URL or relative to root) */\n href: string\n /** Whether the link is external, without admin route prefix. */\n isExternal: true\n}\n\ninterface InternalHrefItem extends BaseSidebarTabItem {\n /** Link href (relative to admin route) */\n href: '' | `/${string}`\n /** Whether the link is external, without admin route prefix. */\n isExternal?: false\n}\n/**\n * Custom item inside a sidebar tab\n */\nexport type SidebarTabItem = ExternalHrefItem | InternalHrefItem\n\n// ============================================\n// Custom Component Types\n// ============================================\n\n/**\n * Props received by a custom NavItem component registered via `customComponents.NavItem`.\n *\n * Use the `useNavItemState(href)` hook to get reactive `isActive` / `isCurrentPage` values.\n *\n * @example\n * ```tsx\n * 'use client'\n * import { useNavItemState } from '@veiag/payload-enhanced-sidebar'\n * import type { CustomNavItemProps } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyNavItem: React.FC<CustomNavItemProps> = ({ entity, href, id, label, badgeConfig }) => {\n * const { isActive, isCurrentPage } = useNavItemState(href)\n * return <a href={href}>{label}</a>\n * }\n * ```\n */\nexport type CustomNavItemProps = {\n /** Badge configuration as defined in the plugin config */\n badgeConfig?: BadgeConfig\n /** The entity (collection, global, or custom item) */\n entity: ExtendedEntity\n /** Computed href with admin route prefix applied */\n href: string\n /** DOM element id */\n id: string\n /** Pre-translated label string */\n label: string\n}\n\n/**\n * Props received by a custom NavGroup component registered via `customComponents.NavGroup`.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomNavGroupProps } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyNavGroup: React.FC<CustomNavGroupProps> = ({ label, isOpen, children }) => {\n * return <div><strong>{label}</strong>{children}</div>\n * }\n * ```\n */\nexport type CustomNavGroupProps = {\n /** Nav items inside the group */\n children: ReactNode\n /** Initial open state from nav preferences */\n isOpen?: boolean\n /** Translated group label */\n label: string\n}\n\n/**\n * Props received by a custom tab icon component set via `iconComponent` on a tab or link.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomTabIconProps } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyIcon: React.FC<CustomTabIconProps> = ({ id, label }) => (\n * <img alt={label} src={`/icons/${id}.svg`} width={20} height={20} />\n * )\n * ```\n */\nexport type CustomTabIconProps = {\n /** Tab/link id */\n id: string\n /** Pre-translated label */\n label: string\n /** Whether this item is a tab or a link */\n type: 'link' | 'tab'\n}\n\n/**\n * Props received by a custom TabButton component registered via `customComponents.TabButton`.\n * Used for both `tab` and `link` type items in the tabs bar.\n *\n * Use `useTabState(id)` for tab active state, or `usePathname()` for link active state.\n * Use `useEnhancedSidebar().onTabChange` to trigger tab switches.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomTabButtonProps } from '@veiag/payload-enhanced-sidebar'\n * import { useTabState, useEnhancedSidebar } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyTabButton: React.FC<CustomTabButtonProps> = ({ id, type, icon, label, href }) => {\n * const { isActive } = useTabState(id)\n * const { onTabChange } = useEnhancedSidebar()\n * if (type === 'link') return <a href={href}>{icon}{label}</a>\n * return <button onClick={() => onTabChange(id)}>{icon}{label}</button>\n * }\n * ```\n */\nexport type CustomTabButtonProps = {\n /** Badge configuration as defined in the plugin config */\n badge?: BadgeConfig\n /** Computed href (for link type, with admin route prefix applied) */\n href?: string\n /** Pre-rendered icon — either from `iconComponent` or the default Lucide icon */\n icon: ReactNode\n /** Tab/link id */\n id: string\n /** Whether the link is external (only set for link type) */\n isExternal?: boolean\n /** Pre-translated label */\n label: string\n /** Item type — use to differentiate rendering */\n type: 'link' | 'tab'\n}\n\n/**\n * Props received by a custom NavContent component registered via `customComponents.NavContent`.\n *\n * Renders in place of the default `<nav>` content area. Use the `useTabState(id)` hook\n * to determine which tab is active and show/hide content accordingly.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomNavContentProps } from '@veiag/payload-enhanced-sidebar'\n * import { useTabState } from '@veiag/payload-enhanced-sidebar'\n *\n * const TabPanel = ({ id, content }: { id: string; content: React.ReactNode }) => {\n * const { isActive } = useTabState(id)\n * return <div style={{ display: isActive ? undefined : 'none' }}>{content}</div>\n * }\n *\n * export const MyNavContent: React.FC<CustomNavContentProps> = ({\n * tabs, tabsContent, beforeNavLinks, afterNavLinks,\n * }) => {\n * return (\n * <nav>\n * {beforeNavLinks}\n * {tabs.map(tab => <TabPanel key={tab.id} id={tab.id} content={tabsContent[tab.id]} />)}\n * {afterNavLinks}\n * </nav>\n * )\n * }\n * ```\n */\nexport type CustomNavContentProps = {\n /** Rendered afterNavLinks from payload config */\n afterNavLinks?: ReactNode\n /** Content to show when no tabs are defined */\n allContent?: ReactNode\n /** Rendered beforeNavLinks from payload config */\n beforeNavLinks?: ReactNode\n /** Tab definitions (id only) for mapping over */\n tabs: Array<{ id: string }>\n /** Pre-rendered content per tab id */\n tabsContent: Record<string, ReactNode>\n}\n\n/**\n * Configuration for the enhanced sidebar\n */\nexport interface EnhancedSidebarConfig {\n /**\n * Badge configurations for sidebar items (collections, globals, custom items).\n * Key is the slug of the item.\n *\n * @example\n * ```typescript\n * badges: {\n * 'posts': { type: 'collection-count', color: 'primary' },\n * 'orders': { type: 'api', endpoint: '/api/orders/pending', responseKey: 'count' },\n * 'notifications': { type: 'provider', color: 'error' },\n * }\n * ```\n */\n badges?: Record<string, BadgeConfig>\n\n /**\n * Custom components to replace the default NavItem, NavGroup, and/or NavContent rendering.\n * Each field accepts a plain path string or a `SidebarComponent` object `{ path, clientProps }`\n * to forward additional props. The plugin registers them in the import map automatically.\n *\n * @example\n * ```typescript\n * customComponents: {\n * // Plain path\n * NavItem: './components/MySidebar#MyNavItem',\n * // With extra client props forwarded to the component\n * NavGroup: { path: './components/MySidebar#MyNavGroup', clientProps: { collapsible: true } },\n * }\n * ```\n */\n customComponents?: {\n /**\n * Custom NavContent component. Replaces the default `<nav>` content area.\n * Receives `CustomNavContentProps`. Use `useTabState(id)` to check if a tab is active.\n */\n NavContent?: SidebarComponent\n /** Custom NavGroup component. Receives `CustomNavGroupProps`. */\n NavGroup?: SidebarComponent\n /** Custom NavItem component. Receives `CustomNavItemProps`. */\n NavItem?: SidebarComponent\n /**\n * Custom tab button component. Used for both `tab` and `link` items in the tabs bar.\n * Receives `CustomTabButtonProps`. Use `useTabState(id)` and `useEnhancedSidebar()` for state.\n */\n TabButton?: SidebarComponent\n }\n\n /**\n * Disable the plugin\n * @default false\n */\n disabled?: boolean\n\n /**\n * Custom icons for collections and globals in the default tab.\n */\n icons?: {\n collections?: Partial<Record<CollectionSlug, IconName>>\n globals?: Partial<Record<GlobalSlug, IconName>>\n }\n\n /**\n * Show logout button at the bottom of the tabs bar.\n * @default true\n */\n showLogout?: boolean\n\n /**\n * Tabs and links to show in the sidebar tabs bar.\n * Order matters - items are rendered top to bottom.\n *\n * @default [{ type: 'tab', id: 'default', icon: 'LayoutGrid', label: 'Collections' }]\n */\n tabs?: SidebarTab[]\n}\n\n/**\n * Generic document type for collections, with dynamic keys.\n * We assume values are either string or number for simplicity, useAsTitle is making sure of that.\n */\nexport type GenericCollectionDocument = {\n [key: string]: number | string\n id: string\n}\n\ninterface BaseExtendedEntity {\n label: Record<string, string> | string\n slug: string\n type: 'collection' | 'custom' | 'global'\n}\n\ninterface InternalExtendedEntity extends BaseExtendedEntity {\n href?: '' | `/${string}`\n isExternal?: false\n}\n\ninterface ExternalExtendedEntity extends BaseExtendedEntity {\n href: string\n isExternal: true\n}\n\nexport type ExtendedEntity = ExternalExtendedEntity | InternalExtendedEntity\n\nexport type ExtendedGroup = {\n entities: ExtendedEntity[]\n label: Record<string, string> | string\n}\n"],"names":[],"mappings":"AAwgBA,WAGC"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
import type { LocalizedString } from '../types';
|
|
1
|
+
import type { LocalizedString, SidebarComponent } from '../types';
|
|
2
2
|
export declare const convertSlugToTitle: (slug: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Extracts path and clientProps from a SidebarComponent (string or object).
|
|
5
|
+
*/
|
|
6
|
+
export declare const resolveSidebarComponent: (component: SidebarComponent) => {
|
|
7
|
+
clientProps: Record<string, unknown>;
|
|
8
|
+
path: string;
|
|
9
|
+
};
|
|
3
10
|
export declare const extractLocalizedValue: (value: LocalizedString | undefined, locale: string, fallbackSlug?: string) => string;
|
package/dist/utils/index.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
export const convertSlugToTitle = (slug)=>{
|
|
2
2
|
return slug.replace(/-/g, ' ').replace(/\b\w/g, (char)=>char.toUpperCase());
|
|
3
3
|
};
|
|
4
|
+
/**
|
|
5
|
+
* Extracts path and clientProps from a SidebarComponent (string or object).
|
|
6
|
+
*/ export const resolveSidebarComponent = (component)=>{
|
|
7
|
+
if (typeof component === 'string') {
|
|
8
|
+
return {
|
|
9
|
+
clientProps: {},
|
|
10
|
+
path: component
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
clientProps: component.clientProps ?? {},
|
|
15
|
+
path: component.path
|
|
16
|
+
};
|
|
17
|
+
};
|
|
4
18
|
export const extractLocalizedValue = (value, locale, fallbackSlug)=>{
|
|
5
19
|
if (!value) {
|
|
6
20
|
return fallbackSlug ? convertSlugToTitle(fallbackSlug) : '';
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["import type { LocalizedString } from '../types'\n\nexport const convertSlugToTitle = (slug: string): string => {\n return slug.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\nexport const extractLocalizedValue = (\n value: LocalizedString | undefined,\n locale: string,\n fallbackSlug?: string,\n): string => {\n if (!value) {\n return fallbackSlug ? convertSlugToTitle(fallbackSlug) : ''\n }\n if (typeof value === 'string') {\n return value\n }\n return value[locale] || Object.values(value)[0] || (fallbackSlug ? convertSlugToTitle(fallbackSlug) : '')\n}\n"],"names":["convertSlugToTitle","slug","replace","char","toUpperCase","extractLocalizedValue","value","locale","fallbackSlug","Object","values"],"mappings":"AAEA,OAAO,MAAMA,qBAAqB,CAACC;IACjC,OAAOA,KAAKC,OAAO,CAAC,MAAM,KAAKA,OAAO,CAAC,SAAS,CAACC,OAASA,KAAKC,WAAW;AAC5E,EAAC;AAED,OAAO,MAAMC,wBAAwB,CACnCC,OACAC,QACAC;IAEA,IAAI,CAACF,OAAO;QACV,OAAOE,
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["import type { LocalizedString, SidebarComponent } from '../types'\n\nexport const convertSlugToTitle = (slug: string): string => {\n return slug.replace(/-/g, ' ').replace(/\\b\\w/g, (char) => char.toUpperCase())\n}\n\n/**\n * Extracts path and clientProps from a SidebarComponent (string or object).\n */\nexport const resolveSidebarComponent = (\n component: SidebarComponent,\n): { clientProps: Record<string, unknown>; path: string } => {\n if (typeof component === 'string') {\n return { clientProps: {}, path: component }\n }\n return { clientProps: component.clientProps ?? {}, path: component.path }\n}\n\nexport const extractLocalizedValue = (\n value: LocalizedString | undefined,\n locale: string,\n fallbackSlug?: string,\n): string => {\n if (!value) {\n return fallbackSlug ? convertSlugToTitle(fallbackSlug) : ''\n }\n if (typeof value === 'string') {\n return value\n }\n return value[locale] || Object.values(value)[0] || (fallbackSlug ? convertSlugToTitle(fallbackSlug) : '')\n}\n"],"names":["convertSlugToTitle","slug","replace","char","toUpperCase","resolveSidebarComponent","component","clientProps","path","extractLocalizedValue","value","locale","fallbackSlug","Object","values"],"mappings":"AAEA,OAAO,MAAMA,qBAAqB,CAACC;IACjC,OAAOA,KAAKC,OAAO,CAAC,MAAM,KAAKA,OAAO,CAAC,SAAS,CAACC,OAASA,KAAKC,WAAW;AAC5E,EAAC;AAED;;CAEC,GACD,OAAO,MAAMC,0BAA0B,CACrCC;IAEA,IAAI,OAAOA,cAAc,UAAU;QACjC,OAAO;YAAEC,aAAa,CAAC;YAAGC,MAAMF;QAAU;IAC5C;IACA,OAAO;QAAEC,aAAaD,UAAUC,WAAW,IAAI,CAAC;QAAGC,MAAMF,UAAUE,IAAI;IAAC;AAC1E,EAAC;AAED,OAAO,MAAMC,wBAAwB,CACnCC,OACAC,QACAC;IAEA,IAAI,CAACF,OAAO;QACV,OAAOE,eAAeZ,mBAAmBY,gBAAgB;IAC3D;IACA,IAAI,OAAOF,UAAU,UAAU;QAC7B,OAAOA;IACT;IACA,OAAOA,KAAK,CAACC,OAAO,IAAIE,OAAOC,MAAM,CAACJ,MAAM,CAAC,EAAE,IAAKE,CAAAA,eAAeZ,mBAAmBY,gBAAgB,EAAC;AACzG,EAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veiag/payload-enhanced-sidebar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.0",
|
|
4
4
|
"description": "An enhanced sidebar plugin for Payload CMS with tabbed navigation to organize collections and globals.",
|
|
5
5
|
"author": "VeiaG",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@payloadcms/db-sqlite": "3.60.0",
|
|
35
35
|
"@payloadcms/eslint-config": "3.9.0",
|
|
36
36
|
"@payloadcms/next": "3.60.0",
|
|
37
|
+
"@payloadcms/plugin-multi-tenant": "3.60.0",
|
|
37
38
|
"@payloadcms/richtext-lexical": "3.60.0",
|
|
38
39
|
"@payloadcms/ui": "3.60.0",
|
|
39
40
|
"@playwright/test": "1.56.1",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { NavPreferences } from 'payload';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import type { BadgeConfig, ExtendedGroup } from '../../types';
|
|
4
|
-
export declare const EnhancedSidebarClient: React.FC<{
|
|
5
|
-
badges?: Record<string, BadgeConfig>;
|
|
6
|
-
groups: ExtendedGroup[];
|
|
7
|
-
navPreferences: NavPreferences | null;
|
|
8
|
-
}>;
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { getTranslation } from '@payloadcms/translations';
|
|
4
|
-
import { NavGroup, useConfig, useTranslation } from '@payloadcms/ui';
|
|
5
|
-
import { EntityType } from '@payloadcms/ui/shared';
|
|
6
|
-
import { usePathname } from 'next/navigation.js';
|
|
7
|
-
import { formatAdminURL } from 'payload/shared';
|
|
8
|
-
import React, { Fragment } from 'react';
|
|
9
|
-
import { NavItem } from './NavItem';
|
|
10
|
-
export const EnhancedSidebarClient = ({ badges, groups, navPreferences })=>{
|
|
11
|
-
const pathname = usePathname();
|
|
12
|
-
const { config: { routes: { admin: adminRoute } } } = useConfig();
|
|
13
|
-
const { i18n } = useTranslation();
|
|
14
|
-
return /*#__PURE__*/ _jsx(Fragment, {
|
|
15
|
-
children: groups.map(({ entities, label }, key)=>{
|
|
16
|
-
// Handle empty label (ungrouped items)
|
|
17
|
-
const groupLabel = label || '';
|
|
18
|
-
const isUngrouped = !label || typeof label === 'string' && label === '';
|
|
19
|
-
const translatedLabel = getTranslation(groupLabel, i18n);
|
|
20
|
-
const properKey = `${translatedLabel}-${key}`;
|
|
21
|
-
const content = entities.map((entity, i)=>{
|
|
22
|
-
const { slug } = entity;
|
|
23
|
-
const entityType = entity.type;
|
|
24
|
-
let href;
|
|
25
|
-
let id;
|
|
26
|
-
// Check for collection
|
|
27
|
-
//@ts-expect-error Idk why typescript is complaining here
|
|
28
|
-
if (entityType === EntityType.collection) {
|
|
29
|
-
href = formatAdminURL({
|
|
30
|
-
adminRoute,
|
|
31
|
-
path: `/collections/${slug}`
|
|
32
|
-
});
|
|
33
|
-
id = `nav-${slug}`;
|
|
34
|
-
//@ts-expect-error Idk why typescript is complaining here
|
|
35
|
-
} else if (entityType === EntityType.global) {
|
|
36
|
-
href = formatAdminURL({
|
|
37
|
-
adminRoute,
|
|
38
|
-
path: `/globals/${slug}`
|
|
39
|
-
});
|
|
40
|
-
id = `nav-global-${slug}`;
|
|
41
|
-
} else if (entityType === 'custom' && entity.href) {
|
|
42
|
-
// Custom item with href
|
|
43
|
-
id = `nav-custom-${slug}`;
|
|
44
|
-
if (entity.isExternal) {
|
|
45
|
-
href = entity.href;
|
|
46
|
-
} else {
|
|
47
|
-
href = formatAdminURL({
|
|
48
|
-
adminRoute,
|
|
49
|
-
path: entity.href
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
} else {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
const isActive = pathname.startsWith(href) && [
|
|
56
|
-
'/',
|
|
57
|
-
undefined
|
|
58
|
-
].includes(pathname[href.length]);
|
|
59
|
-
const isCurrentPage = pathname === href;
|
|
60
|
-
// Get badge config for this entity
|
|
61
|
-
const badgeConfig = badges?.[slug];
|
|
62
|
-
return /*#__PURE__*/ _jsx(NavItem, {
|
|
63
|
-
badgeConfig: badgeConfig,
|
|
64
|
-
entity: entity,
|
|
65
|
-
href: href,
|
|
66
|
-
id: id,
|
|
67
|
-
isActive: isActive,
|
|
68
|
-
isCurrentPage: isCurrentPage
|
|
69
|
-
}, i);
|
|
70
|
-
});
|
|
71
|
-
// For ungrouped items, render without NavGroup wrapper
|
|
72
|
-
if (isUngrouped) {
|
|
73
|
-
return /*#__PURE__*/ _jsx(Fragment, {
|
|
74
|
-
children: content
|
|
75
|
-
}, properKey);
|
|
76
|
-
}
|
|
77
|
-
//TODO:
|
|
78
|
-
return /*#__PURE__*/ _jsx(NavGroup, {
|
|
79
|
-
isOpen: navPreferences?.groups?.[translatedLabel]?.open,
|
|
80
|
-
label: translatedLabel,
|
|
81
|
-
children: content
|
|
82
|
-
}, properKey);
|
|
83
|
-
})
|
|
84
|
-
});
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
//# sourceMappingURL=index.client.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/EnhancedSidebar/index.client.tsx"],"sourcesContent":["'use client'\nimport type { NavPreferences } from 'payload'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { NavGroup, useConfig, useTranslation } from '@payloadcms/ui'\nimport { EntityType } from '@payloadcms/ui/shared'\nimport { usePathname } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport React, { Fragment } from 'react'\n\nimport type { BadgeConfig, ExtendedGroup } from '../../types'\n\nimport { NavItem } from './NavItem'\n\nexport const EnhancedSidebarClient: React.FC<{\n badges?: Record<string, BadgeConfig>\n groups: ExtendedGroup[]\n navPreferences: NavPreferences | null\n}> = ({ badges, groups, navPreferences }) => {\n const pathname = usePathname()\n\n const {\n config: {\n routes: { admin: adminRoute },\n },\n } = useConfig()\n\n const { i18n } = useTranslation()\n\n return (\n <Fragment>\n {groups.map(({ entities, label }, key) => {\n // Handle empty label (ungrouped items)\n const groupLabel = label || ''\n const isUngrouped = !label || (typeof label === 'string' && label === '')\n const translatedLabel = getTranslation(groupLabel, i18n)\n\n const properKey = `${translatedLabel}-${key}`\n\n const content = entities.map((entity, i) => {\n const { slug } = entity\n const entityType = entity.type\n let href: string\n let id: string\n\n // Check for collection\n //@ts-expect-error Idk why typescript is complaining here\n if (entityType === EntityType.collection) {\n href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })\n id = `nav-${slug}`\n //@ts-expect-error Idk why typescript is complaining here\n } else if (entityType === EntityType.global) {\n href = formatAdminURL({ adminRoute, path: `/globals/${slug}` })\n id = `nav-global-${slug}`\n } else if (entityType === 'custom' && entity.href) {\n // Custom item with href\n id = `nav-custom-${slug}`\n if (entity.isExternal) {\n href = entity.href\n } else {\n href = formatAdminURL({ adminRoute, path: entity.href })\n }\n } else {\n return null\n }\n\n const isActive =\n pathname.startsWith(href) && ['/', undefined].includes(pathname[href.length])\n const isCurrentPage = pathname === href\n\n // Get badge config for this entity\n const badgeConfig = badges?.[slug]\n\n return (\n <NavItem\n badgeConfig={badgeConfig}\n entity={entity}\n href={href}\n id={id}\n isActive={isActive}\n isCurrentPage={isCurrentPage}\n key={i}\n />\n )\n })\n\n // For ungrouped items, render without NavGroup wrapper\n if (isUngrouped) {\n return <Fragment key={properKey}>{content}</Fragment>\n }\n //TODO:\n return (\n <NavGroup\n isOpen={navPreferences?.groups?.[translatedLabel]?.open}\n key={properKey}\n label={translatedLabel}\n >\n {content}\n </NavGroup>\n )\n })}\n </Fragment>\n )\n}\n"],"names":["getTranslation","NavGroup","useConfig","useTranslation","EntityType","usePathname","formatAdminURL","React","Fragment","NavItem","EnhancedSidebarClient","badges","groups","navPreferences","pathname","config","routes","admin","adminRoute","i18n","map","entities","label","key","groupLabel","isUngrouped","translatedLabel","properKey","content","entity","i","slug","entityType","type","href","id","collection","path","global","isExternal","isActive","startsWith","undefined","includes","length","isCurrentPage","badgeConfig","isOpen","open"],"mappings":"AAAA;;AAGA,SAASA,cAAc,QAAQ,2BAA0B;AACzD,SAASC,QAAQ,EAAEC,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AACpE,SAASC,UAAU,QAAQ,wBAAuB;AAClD,SAASC,WAAW,QAAQ,qBAAoB;AAChD,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,OAAOC,SAASC,QAAQ,QAAQ,QAAO;AAIvC,SAASC,OAAO,QAAQ,YAAW;AAEnC,OAAO,MAAMC,wBAIR,CAAC,EAAEC,MAAM,EAAEC,MAAM,EAAEC,cAAc,EAAE;IACtC,MAAMC,WAAWT;IAEjB,MAAM,EACJU,QAAQ,EACNC,QAAQ,EAAEC,OAAOC,UAAU,EAAE,EAC9B,EACF,GAAGhB;IAEJ,MAAM,EAAEiB,IAAI,EAAE,GAAGhB;IAEjB,qBACE,KAACK;kBACEI,OAAOQ,GAAG,CAAC,CAAC,EAAEC,QAAQ,EAAEC,KAAK,EAAE,EAAEC;YAChC,uCAAuC;YACvC,MAAMC,aAAaF,SAAS;YAC5B,MAAMG,cAAc,CAACH,SAAU,OAAOA,UAAU,YAAYA,UAAU;YACtE,MAAMI,kBAAkB1B,eAAewB,YAAYL;YAEnD,MAAMQ,YAAY,GAAGD,gBAAgB,CAAC,EAAEH,KAAK;YAE7C,MAAMK,UAAUP,SAASD,GAAG,CAAC,CAACS,QAAQC;gBACpC,MAAM,EAAEC,IAAI,EAAE,GAAGF;gBACjB,MAAMG,aAAaH,OAAOI,IAAI;gBAC9B,IAAIC;gBACJ,IAAIC;gBAEJ,uBAAuB;gBACvB,yDAAyD;gBACzD,IAAIH,eAAe5B,WAAWgC,UAAU,EAAE;oBACxCF,OAAO5B,eAAe;wBAAEY;wBAAYmB,MAAM,CAAC,aAAa,EAAEN,MAAM;oBAAC;oBACjEI,KAAK,CAAC,IAAI,EAAEJ,MAAM;gBAClB,yDAAyD;gBAC3D,OAAO,IAAIC,eAAe5B,WAAWkC,MAAM,EAAE;oBAC3CJ,OAAO5B,eAAe;wBAAEY;wBAAYmB,MAAM,CAAC,SAAS,EAAEN,MAAM;oBAAC;oBAC7DI,KAAK,CAAC,WAAW,EAAEJ,MAAM;gBAC3B,OAAO,IAAIC,eAAe,YAAYH,OAAOK,IAAI,EAAE;oBACjD,wBAAwB;oBACxBC,KAAK,CAAC,WAAW,EAAEJ,MAAM;oBACzB,IAAIF,OAAOU,UAAU,EAAE;wBACrBL,OAAOL,OAAOK,IAAI;oBACpB,OAAO;wBACLA,OAAO5B,eAAe;4BAAEY;4BAAYmB,MAAMR,OAAOK,IAAI;wBAAC;oBACxD;gBACF,OAAO;oBACL,OAAO;gBACT;gBAEA,MAAMM,WACJ1B,SAAS2B,UAAU,CAACP,SAAS;oBAAC;oBAAKQ;iBAAU,CAACC,QAAQ,CAAC7B,QAAQ,CAACoB,KAAKU,MAAM,CAAC;gBAC9E,MAAMC,gBAAgB/B,aAAaoB;gBAEnC,mCAAmC;gBACnC,MAAMY,cAAcnC,QAAQ,CAACoB,KAAK;gBAElC,qBACE,KAACtB;oBACCqC,aAAaA;oBACbjB,QAAQA;oBACRK,MAAMA;oBACNC,IAAIA;oBACJK,UAAUA;oBACVK,eAAeA;mBACVf;YAGX;YAEA,uDAAuD;YACvD,IAAIL,aAAa;gBACf,qBAAO,KAACjB;8BAA0BoB;mBAAZD;YACxB;YACA,OAAO;YACP,qBACE,KAAC1B;gBACC8C,QAAQlC,gBAAgBD,QAAQ,CAACc,gBAAgB,EAAEsB;gBAEnD1B,OAAOI;0BAENE;eAHID;QAMX;;AAGN,EAAC"}
|