@veiag/payload-enhanced-sidebar 0.3.2 → 0.4.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 +78 -0
- package/dist/components/EnhancedSidebar/TabsBar/index.js +55 -31
- package/dist/components/EnhancedSidebar/TabsBar/index.js.map +1 -1
- package/dist/components/EnhancedSidebar/TabsBar/index.scss +18 -0
- package/dist/components/EnhancedSidebar/index.js +28 -4
- package/dist/components/EnhancedSidebar/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +163 -6
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/index.d.ts +10 -1
- package/dist/utils/index.js +92 -3
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,6 +166,7 @@ Array of tabs and links to show in the sidebar.
|
|
|
166
166
|
| `globals` | `GlobalSlug[]` | No | Globals to show in this tab |
|
|
167
167
|
| `customItems` | `SidebarTabItem[]` | No | Custom navigation items (see below) |
|
|
168
168
|
| `badge` | `BadgeConfig` | No | Badge configuration for the tab icon |
|
|
169
|
+
| `position` | `'top' \| 'bottom'` | No | `'bottom'` pins the tab to the bottom of the bar, above the actions (default `'top'`) |
|
|
169
170
|
| `access` | `TabAccessFunction` | No | Server-side access control — return `false` to hide |
|
|
170
171
|
|
|
171
172
|
> \* Exactly one of `icon` or `iconComponent` is required — they are mutually exclusive.
|
|
@@ -184,6 +185,7 @@ Array of tabs and links to show in the sidebar.
|
|
|
184
185
|
| `href` | `string` | Yes | URL |
|
|
185
186
|
| `isExternal` | `boolean` | No | If true, `href` is absolute URL, if not, `href` is relative to admin route |
|
|
186
187
|
| `badge` | `BadgeConfig` | No | Badge configuration for the link icon |
|
|
188
|
+
| `position` | `'top' \| 'bottom'` | No | `'bottom'` pins the link to the bottom of the bar, above the actions (default `'top'`) |
|
|
187
189
|
| `access` | `TabAccessFunction` | No | Server-side access control — return `false` to hide |
|
|
188
190
|
|
|
189
191
|
> \* Exactly one of `icon` or `iconComponent` is required — they are mutually exclusive.
|
|
@@ -197,6 +199,7 @@ Renders an arbitrary component in the tabs bar — useful for spacers, separator
|
|
|
197
199
|
| `id` | `string` | Yes | Unique identifier |
|
|
198
200
|
| `type` | `'custom'` | Yes | Custom slot type |
|
|
199
201
|
| `component` | `SidebarComponent` | Yes | Component to render (string path or `{ path, clientProps }`) |
|
|
202
|
+
| `position` | `'top' \| 'bottom'` | No | `'bottom'` pins the slot to the bottom of the bar, above the actions (default `'top'`) |
|
|
200
203
|
| `access` | `TabAccessFunction` | No | Server-side access control — return `false` to hide |
|
|
201
204
|
|
|
202
205
|
The component receives `{ id }` plus any `clientProps` you pass. See [Custom Components](docs/custom-components.md) for details.
|
|
@@ -211,6 +214,21 @@ The component receives `{ id }` plus any `clientProps` you pass. See [Custom Com
|
|
|
211
214
|
|
|
212
215
|

|
|
213
216
|
|
|
217
|
+
**Tab bar position (`position`)**
|
|
218
|
+
|
|
219
|
+
Any tab, link, or custom slot can be pinned to the bottom of the tabs bar with `position: 'bottom'`. Bottom items render in config order, just **above** the actions area (folders / settings / logout). Items default to `'top'`.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
tabs: [
|
|
223
|
+
{ id: 'dashboard', type: 'link', href: '/', icon: 'House', label: 'Dashboard' },
|
|
224
|
+
{ id: 'content', type: 'tab', icon: 'FileText', label: 'Content', collections: ['posts'] },
|
|
225
|
+
// pinned to the bottom, above the logout button
|
|
226
|
+
{ id: 'settings', type: 'tab', icon: 'Settings', label: 'Settings', globals: ['site-settings'], position: 'bottom' },
|
|
227
|
+
]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
> The tab bar is not virtualized/scrolled — if you have a very large number of top tabs they may collide with the bottom group. `position` is intended for a small number of pinned items (settings, help, etc.).
|
|
231
|
+
|
|
214
232
|
### `customItems`
|
|
215
233
|
|
|
216
234
|
Custom items can be added to any tab:
|
|
@@ -236,6 +254,66 @@ Custom items can be added to any tab:
|
|
|
236
254
|
- `position: 'bottom'` — appears below all groups (default)
|
|
237
255
|
- Has no effect on items that merge into an existing collection group via `group`
|
|
238
256
|
|
|
257
|
+
**Custom component item:**
|
|
258
|
+
|
|
259
|
+
Instead of `href`/`label`, a custom item can render your own component (`slug` + `component`). It renders in the nav row via Payload's component system — both server and client components are supported — and honors the same `group`/`position` rules.
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
{
|
|
263
|
+
slug: 'storage-meter', // Required: unique identifier
|
|
264
|
+
component: './components/Sidebar#StorageMeter', // Required: string path or { path, clientProps }
|
|
265
|
+
position: 'top', // Optional
|
|
266
|
+
group: 'Content', // Optional
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The component receives `CustomNavItemComponentProps` (`{ slug }`) plus any `clientProps`. See [Custom Components](docs/custom-components.md#per-item-custom-component-customitemscomponent) for details.
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
## Ordering groups and items (`sort`)
|
|
274
|
+
|
|
275
|
+
By default, groups and items follow Payload's default order plus your `customItems` `position`/`group` rules. The `sort` option lets you take **full control of the order** per tab, without changing that default for tabs you don't configure.
|
|
276
|
+
|
|
277
|
+
`sort` is keyed by tab `id`. Each entry has two optional functions that return a **sort key** (`number | string | undefined`):
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
payloadEnhancedSidebar({
|
|
281
|
+
tabs: [
|
|
282
|
+
{ id: 'shop', type: 'tab', icon: 'ShoppingCart', label: 'Shop', collections: ['products', 'orders'] },
|
|
283
|
+
],
|
|
284
|
+
sort: {
|
|
285
|
+
shop: {
|
|
286
|
+
// Orders the top-level groups within the tab
|
|
287
|
+
groups: (group, ctx) => {
|
|
288
|
+
// ungrouped items become individual units you can place anywhere
|
|
289
|
+
if (group.isUngrouped && group.entities[0]?.slug === 'banner') return -100
|
|
290
|
+
if (typeof group.label !== 'string' && group.label.en === 'Featured') return 0
|
|
291
|
+
// everything else → default position
|
|
292
|
+
},
|
|
293
|
+
// Orders the entities inside each group
|
|
294
|
+
items: (item, group, ctx) => {
|
|
295
|
+
if (item.type === 'custom-component') return 10 // place after collections
|
|
296
|
+
if (item.slug === 'products') return -10 // pin products first
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**How sort keys work (like CSS `order`):**
|
|
304
|
+
- `number` — explicit order index; lower comes first.
|
|
305
|
+
- `string` — sorted lexicographically (`localeCompare`).
|
|
306
|
+
- `undefined` — treated as `0`, i.e. keeps the default position.
|
|
307
|
+
|
|
308
|
+
Sorting is **stable** — anything you don't assign a key to stays where the default logic placed it. Avoid mixing numbers and strings in the same scope (numbers sort before strings).
|
|
309
|
+
|
|
310
|
+
**Notes:**
|
|
311
|
+
- Labels are passed **raw** (not translated) — use `group.label.en` etc., or translate via `ctx.locale`.
|
|
312
|
+
- `items` sorts **only within a group** — it can't move an item to a different group (use `group` for that).
|
|
313
|
+
- When a `groups` function is set, ungrouped items become **individual single-item units** so you can interleave them between real groups (e.g. a banner above one group, a CTA below another).
|
|
314
|
+
- `sort` is a final pass that overrides `position`.
|
|
315
|
+
- Tab bar order (the icons on the left) is simply the order of the `tabs` array — `sort` only affects nav content.
|
|
316
|
+
|
|
239
317
|
|
|
240
318
|
## Badges
|
|
241
319
|
|
|
@@ -47,46 +47,70 @@ export const TabsBar = ({ activeTabId, customTabComponents, onTabChange, rendere
|
|
|
47
47
|
}, item.id);
|
|
48
48
|
};
|
|
49
49
|
const tabItems = sidebarConfig.tabs ?? [];
|
|
50
|
+
// `renderedTabItems` (set when a custom TabButton is used) is built server-side in the
|
|
51
|
+
// same order as `tabItems`, so we can zip by index. Otherwise we render the default item.
|
|
52
|
+
const topNodes = [];
|
|
53
|
+
const bottomNodes = [];
|
|
54
|
+
tabItems.forEach((item, index)=>{
|
|
55
|
+
const node = renderedTabItems ? renderedTabItems[index] : renderTabItem(item);
|
|
56
|
+
const wrapped = /*#__PURE__*/ _jsx(React.Fragment, {
|
|
57
|
+
children: node
|
|
58
|
+
}, item.id);
|
|
59
|
+
if (item.position === 'bottom') {
|
|
60
|
+
bottomNodes.push(wrapped);
|
|
61
|
+
} else {
|
|
62
|
+
topNodes.push(wrapped);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
50
65
|
return /*#__PURE__*/ _jsxs("div", {
|
|
51
66
|
className: tabsBaseClass,
|
|
52
67
|
children: [
|
|
53
68
|
/*#__PURE__*/ _jsx("div", {
|
|
54
69
|
className: `${tabsBaseClass}__tabs`,
|
|
55
|
-
children:
|
|
70
|
+
children: topNodes
|
|
56
71
|
}),
|
|
57
72
|
/*#__PURE__*/ _jsxs("div", {
|
|
58
|
-
className: `${tabsBaseClass}
|
|
73
|
+
className: `${tabsBaseClass}__bottom`,
|
|
59
74
|
children: [
|
|
60
|
-
|
|
61
|
-
className: `${tabsBaseClass}
|
|
62
|
-
|
|
63
|
-
title: getTranslation({
|
|
64
|
-
en: 'Browse by Folder',
|
|
65
|
-
uk: 'Переглянути по папках'
|
|
66
|
-
}, i18n),
|
|
67
|
-
children: /*#__PURE__*/ _jsx(Icon, {
|
|
68
|
-
name: "Folder",
|
|
69
|
-
size: 20
|
|
70
|
-
})
|
|
71
|
-
}),
|
|
72
|
-
/*#__PURE__*/ _jsx(SettingsMenuButton, {
|
|
73
|
-
settingsMenu: settingsMenu
|
|
75
|
+
bottomNodes.length > 0 && /*#__PURE__*/ _jsx("div", {
|
|
76
|
+
className: `${tabsBaseClass}__tabs-bottom`,
|
|
77
|
+
children: bottomNodes
|
|
74
78
|
}),
|
|
75
|
-
|
|
76
|
-
className: `${tabsBaseClass}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
79
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
80
|
+
className: `${tabsBaseClass}__actions`,
|
|
81
|
+
children: [
|
|
82
|
+
showFolders && /*#__PURE__*/ _jsx(Link, {
|
|
83
|
+
className: `${tabsBaseClass}__action ${isFoldersActive ? `${tabsBaseClass}__link--active` : ''}`,
|
|
84
|
+
href: folderURL,
|
|
85
|
+
title: getTranslation({
|
|
86
|
+
en: 'Browse by Folder',
|
|
87
|
+
uk: 'Переглянути по папках'
|
|
88
|
+
}, i18n),
|
|
89
|
+
children: /*#__PURE__*/ _jsx(Icon, {
|
|
90
|
+
name: "Folder",
|
|
91
|
+
size: 20
|
|
92
|
+
})
|
|
93
|
+
}),
|
|
94
|
+
/*#__PURE__*/ _jsx(SettingsMenuButton, {
|
|
95
|
+
settingsMenu: settingsMenu
|
|
96
|
+
}),
|
|
97
|
+
showLogout && /*#__PURE__*/ _jsx(Link, {
|
|
98
|
+
className: `${tabsBaseClass}__action`,
|
|
99
|
+
href: formatAdminURL({
|
|
100
|
+
adminRoute,
|
|
101
|
+
path: logoutRoute
|
|
102
|
+
}),
|
|
103
|
+
title: getTranslation({
|
|
104
|
+
en: 'Logout',
|
|
105
|
+
uk: 'Вийти'
|
|
106
|
+
}, i18n),
|
|
107
|
+
type: "button",
|
|
108
|
+
children: /*#__PURE__*/ _jsx(Icon, {
|
|
109
|
+
name: "LogOut",
|
|
110
|
+
size: 20
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
]
|
|
90
114
|
})
|
|
91
115
|
]
|
|
92
116
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/EnhancedSidebar/TabsBar/index.tsx"],"sourcesContent":["'use client'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { Link, useConfig, useTranslation } from '@payloadcms/ui'\nimport { usePathname } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport React from 'react'\n\nimport type { EnhancedSidebarConfig, SidebarTab
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/EnhancedSidebar/TabsBar/index.tsx"],"sourcesContent":["'use client'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { Link, useConfig, useTranslation } from '@payloadcms/ui'\nimport { usePathname } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport React from 'react'\n\nimport type { EnhancedSidebarConfig, SidebarTab } from '../../../types.js'\n\nimport { Icon } from '../Icon.js'\nimport { SettingsMenuButton } from '../SettingsMenuButton/index.js'\nimport { TabButton, TabLink } from './TabItem.js'\nimport './index.scss'\n\nconst tabsBaseClass = 'tabs-bar'\n\nexport type TabsBarProps = {\n activeTabId: string\n customTabComponents?: Record<string, React.ReactNode>\n onTabChange: (tabId: string) => void\n renderedTabItems?: React.ReactNode[]\n settingsMenu?: React.ReactNode[]\n sidebarConfig: EnhancedSidebarConfig\n tabIcons?: Record<string, React.ReactNode>\n}\n\nexport const TabsBar: React.FC<TabsBarProps> = ({\n activeTabId,\n customTabComponents,\n onTabChange,\n renderedTabItems,\n settingsMenu,\n sidebarConfig,\n tabIcons,\n}) => {\n const { i18n } = useTranslation()\n const pathname = usePathname()\n\n const {\n config: {\n admin: {\n routes: { browseByFolder: foldersRoute, logout: logoutRoute },\n },\n folders,\n routes: { admin: adminRoute },\n },\n } = useConfig()\n\n const showLogout = sidebarConfig.showLogout !== false\n const showFolders = folders && folders.browseByFolder\n\n const folderURL = formatAdminURL({\n adminRoute,\n path: foldersRoute,\n })\n const isFoldersActive = pathname.startsWith(folderURL)\n\n const renderTabItem = (item: SidebarTab) => {\n if (item.type === 'custom') {\n return customTabComponents?.[item.id] ?? null\n }\n\n if (item.type === 'tab') {\n return (\n <TabButton\n icon={tabIcons?.[item.id]}\n isActive={activeTabId === item.id}\n key={item.id}\n onTabChange={onTabChange}\n tab={item}\n />\n )\n }\n\n // Check if link is active\n const href = item.isExternal\n ? item.href\n : formatAdminURL({ adminRoute, path: item.href })\n const isActive = pathname === href || (item.href === '/' && pathname === adminRoute)\n\n return <TabLink href={href} icon={tabIcons?.[item.id]} isActive={isActive} key={item.id} link={item} />\n }\n\n const tabItems = sidebarConfig.tabs ?? []\n\n // `renderedTabItems` (set when a custom TabButton is used) is built server-side in the\n // same order as `tabItems`, so we can zip by index. Otherwise we render the default item.\n const topNodes: React.ReactNode[] = []\n const bottomNodes: React.ReactNode[] = []\n tabItems.forEach((item, index) => {\n const node = renderedTabItems ? renderedTabItems[index] : renderTabItem(item)\n const wrapped = <React.Fragment key={item.id}>{node}</React.Fragment>\n if (item.position === 'bottom') {\n bottomNodes.push(wrapped)\n } else {\n topNodes.push(wrapped)\n }\n })\n\n return (\n <div className={tabsBaseClass}>\n <div className={`${tabsBaseClass}__tabs`}>{topNodes}</div>\n\n <div className={`${tabsBaseClass}__bottom`}>\n {bottomNodes.length > 0 && (\n <div className={`${tabsBaseClass}__tabs-bottom`}>{bottomNodes}</div>\n )}\n\n <div className={`${tabsBaseClass}__actions`}>\n {showFolders && (\n <Link\n className={`${tabsBaseClass}__action ${isFoldersActive ? `${tabsBaseClass}__link--active` : ''}`}\n href={folderURL}\n title={getTranslation({ en: 'Browse by Folder', uk: 'Переглянути по папках' }, i18n)}\n >\n <Icon name=\"Folder\" size={20} />\n </Link>\n )}\n <SettingsMenuButton settingsMenu={settingsMenu} />\n {showLogout && (\n <Link\n className={`${tabsBaseClass}__action`}\n href={formatAdminURL({\n adminRoute,\n path: logoutRoute,\n })}\n title={getTranslation({ en: 'Logout', uk: 'Вийти' }, i18n)}\n type=\"button\"\n >\n <Icon name=\"LogOut\" size={20} />\n </Link>\n )}\n </div>\n </div>\n </div>\n )\n}\n"],"names":["getTranslation","Link","useConfig","useTranslation","usePathname","formatAdminURL","React","Icon","SettingsMenuButton","TabButton","TabLink","tabsBaseClass","TabsBar","activeTabId","customTabComponents","onTabChange","renderedTabItems","settingsMenu","sidebarConfig","tabIcons","i18n","pathname","config","admin","routes","browseByFolder","foldersRoute","logout","logoutRoute","folders","adminRoute","showLogout","showFolders","folderURL","path","isFoldersActive","startsWith","renderTabItem","item","type","id","icon","isActive","tab","href","isExternal","link","tabItems","tabs","topNodes","bottomNodes","forEach","index","node","wrapped","Fragment","position","push","div","className","length","title","en","uk","name","size"],"mappings":"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"}
|
|
@@ -118,6 +118,24 @@
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// Wrapper that keeps bottom-positioned tabs and the actions pinned together
|
|
122
|
+
// at the bottom of the bar (the `.tabs-bar` uses `justify-content: space-between`).
|
|
123
|
+
&__bottom {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: base(0.5);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Tabs explicitly positioned at the bottom (`position: 'bottom'`).
|
|
131
|
+
// Same look as the main `__tabs` group but without the header offset.
|
|
132
|
+
&__tabs-bottom {
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: base(0.25);
|
|
137
|
+
}
|
|
138
|
+
|
|
121
139
|
&__actions {
|
|
122
140
|
display: flex;
|
|
123
141
|
flex-direction: column;
|
|
@@ -7,7 +7,7 @@ import { cookies } from 'next/headers.js';
|
|
|
7
7
|
import { formatAdminURL } from 'payload/shared';
|
|
8
8
|
import React, { Fragment } from 'react';
|
|
9
9
|
const COOKIE_KEY = 'payload-enhanced-sidebar-active-tab';
|
|
10
|
-
import { extractLocalizedValue, resolveSidebarComponent, sanitizeSidebarConfig } from '../../utils/index.js';
|
|
10
|
+
import { extractLocalizedValue, resolveSidebarComponent, sanitizeSidebarConfig, sortTabGroups } from '../../utils/index.js';
|
|
11
11
|
import { getNavPrefs } from './getNavPrefs.js';
|
|
12
12
|
import { Icon } from './Icon.js';
|
|
13
13
|
import { NavItem } from './NavItem/index.js';
|
|
@@ -40,13 +40,23 @@ import './index.scss';
|
|
|
40
40
|
const topAdditions = [];
|
|
41
41
|
const topUngrouped = [];
|
|
42
42
|
const bottomUngrouped = [];
|
|
43
|
-
const toEntity = (item)=>
|
|
43
|
+
const toEntity = (item)=>{
|
|
44
|
+
if ('component' in item && item.component) {
|
|
45
|
+
return {
|
|
46
|
+
slug: item.slug,
|
|
47
|
+
type: 'custom-component',
|
|
48
|
+
component: item.component,
|
|
49
|
+
label: item.label ?? item.slug
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
44
53
|
slug: item.slug,
|
|
45
54
|
type: 'custom',
|
|
46
55
|
href: item.href,
|
|
47
56
|
isExternal: item.isExternal,
|
|
48
57
|
label: item.label
|
|
49
|
-
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
50
60
|
// Filter custom items by access — fail-closed: missing req or thrown error denies access
|
|
51
61
|
const accessResults = await Promise.all(customItems.map((item)=>Promise.resolve().then(()=>item.access ? req ? item.access({
|
|
52
62
|
item,
|
|
@@ -195,6 +205,20 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
195
205
|
* Renders a single entity as a NavItem (default or custom).
|
|
196
206
|
*/ const renderEntity = (entity, key)=>{
|
|
197
207
|
const { slug, type } = entity;
|
|
208
|
+
// Per-item custom component — render its own component, bypassing the NavItem override.
|
|
209
|
+
if (type === 'custom-component') {
|
|
210
|
+
const { clientProps: extraProps, path } = resolveSidebarComponent(entity.component);
|
|
211
|
+
return RenderServerComponent({
|
|
212
|
+
clientProps: {
|
|
213
|
+
slug,
|
|
214
|
+
...extraProps
|
|
215
|
+
},
|
|
216
|
+
Component: path,
|
|
217
|
+
importMap: payload.importMap,
|
|
218
|
+
key,
|
|
219
|
+
serverProps
|
|
220
|
+
});
|
|
221
|
+
}
|
|
198
222
|
let href;
|
|
199
223
|
let id;
|
|
200
224
|
if (type === EntityType.collection) {
|
|
@@ -282,7 +306,7 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
282
306
|
const tabsContent = {};
|
|
283
307
|
for(let i = 0; i < tabs.length; i++){
|
|
284
308
|
const tab = tabs[i];
|
|
285
|
-
const tabGroups = tabGroupResults[i];
|
|
309
|
+
const tabGroups = sortTabGroups(tabGroupResults[i], config.sort?.[tab.id], currentLang);
|
|
286
310
|
tabsContent[tab.id] = /*#__PURE__*/ _jsx(Fragment, {
|
|
287
311
|
children: tabGroups.map((group, j)=>renderGroup(group, `${tab.id}-${j}`))
|
|
288
312
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/EnhancedSidebar/index.tsx"],"sourcesContent":["import type { EntityToGroup } from '@payloadcms/ui/shared'\nimport type { PayloadRequest, ServerProps } from 'payload'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { NavGroup } from '@payloadcms/ui'\nimport { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'\nimport { EntityType, groupNavItems } from '@payloadcms/ui/shared'\nimport { cookies } from 'next/headers.js'\nimport { formatAdminURL } from 'payload/shared'\nimport React, { Fragment } from 'react'\n\nconst COOKIE_KEY = 'payload-enhanced-sidebar-active-tab'\n\nimport type {\n EnhancedSidebarConfig,\n ExtendedEntity,\n ExtendedGroup,\n SidebarTab,\n SidebarTabContent as SidebarTabContentType,\n SidebarTabItem,\n} from '../../types.js'\n\nimport { extractLocalizedValue, resolveSidebarComponent, sanitizeSidebarConfig } from '../../utils/index.js'\nimport { getNavPrefs } from './getNavPrefs.js'\nimport { Icon } from './Icon.js'\nimport { NavItem } from './NavItem/index.js'\nimport { SidebarContent } from './SidebarContent.js'\nimport './index.scss'\n\nexport type EnhancedSidebarProps = {\n req?: PayloadRequest\n sidebarConfig?: EnhancedSidebarConfig\n} & ServerProps\n\n/**\n * Computes filtered and merged groups for a specific tab (server-side).\n */\nconst computeGroupsForTab = async (\n tab: SidebarTabContentType,\n groups: ExtendedGroup[],\n currentLang: string,\n req: PayloadRequest | undefined,\n): Promise<ExtendedGroup[]> => {\n const { collections: tabCollections, customItems, globals: tabGlobals } = tab\n\n const showAll = !tabCollections && !tabGlobals\n const allowedSlugs = new Set([...(tabCollections ?? []), ...(tabGlobals ?? [])])\n\n let result: ExtendedGroup[] = []\n\n if (showAll) {\n result = groups.map((g) => ({ ...g, entities: [...g.entities] }))\n } else if (allowedSlugs.size > 0) {\n result = groups\n .map((group) => ({\n ...group,\n entities: group.entities.filter((entity) => allowedSlugs.has(entity.slug)),\n }))\n .filter((group) => group.entities.length > 0)\n }\n\n if (customItems && customItems.length > 0) {\n const topAdditions: ExtendedGroup[] = []\n const topUngrouped: SidebarTabItem[] = []\n const bottomUngrouped: SidebarTabItem[] = []\n\n const toEntity = (item: SidebarTabItem): ExtendedEntity =>\n ({\n slug: item.slug,\n type: 'custom',\n href: item.href,\n isExternal: item.isExternal,\n label: item.label,\n }) as ExtendedEntity\n\n // Filter custom items by access — fail-closed: missing req or thrown error denies access\n const accessResults = await Promise.all(\n customItems.map((item) =>\n Promise.resolve()\n .then(() => (item.access ? (req ? item.access({ item, req }) : false) : true))\n .catch(() => false),\n ),\n )\n const visibleItems = customItems.filter((_, i) => accessResults[i])\n\n for (const item of visibleItems) {\n if (item.group) {\n const itemGroupLabel = extractLocalizedValue(item.group, currentLang)\n const existingGroup = result.find((g) => {\n const groupLabel = extractLocalizedValue(g.label, currentLang)\n return groupLabel === itemGroupLabel\n })\n\n if (existingGroup) {\n // Merged into existing collection group — position has no effect here\n existingGroup.entities.push(toEntity(item))\n } else {\n // New custom group — position controls top vs bottom\n const newGroup: ExtendedGroup = { entities: [toEntity(item)], label: item.group }\n if (item.position === 'top') {\n topAdditions.push(newGroup)\n } else {\n result.push(newGroup)\n }\n }\n } else {\n if (item.position === 'top') {\n topUngrouped.push(item)\n } else {\n bottomUngrouped.push(item)\n }\n }\n }\n\n if (topUngrouped.length > 0) {\n topAdditions.unshift({ entities: topUngrouped.map(toEntity), label: '' })\n }\n\n if (bottomUngrouped.length > 0) {\n result.push({ entities: bottomUngrouped.map(toEntity), label: '' })\n }\n\n result = [...topAdditions, ...result]\n }\n\n return result\n}\n\nexport const EnhancedSidebar: React.FC<EnhancedSidebarProps> = async (props) => {\n const {\n documentSubViewType,\n i18n,\n locale,\n params,\n payload,\n permissions,\n req,\n searchParams,\n sidebarConfig,\n user,\n viewType,\n visibleEntities,\n } = props\n\n if (!payload?.config) {\n return null\n }\n\n const {\n admin: {\n components: { afterNav, afterNavLinks, beforeNav, beforeNavLinks, settingsMenu },\n },\n collections,\n globals,\n } = payload.config\n\n const groups = groupNavItems(\n [\n ...collections\n .filter(({ slug }) => visibleEntities?.collections.includes(slug))\n .map(\n (collection) =>\n ({\n type: EntityType.collection,\n entity: collection,\n }) satisfies EntityToGroup,\n ),\n ...globals\n .filter(({ slug }) => visibleEntities?.globals.includes(slug))\n .map(\n (global) =>\n ({\n type: EntityType.global,\n entity: global,\n }) satisfies EntityToGroup,\n ),\n ],\n permissions || {},\n i18n,\n ) as unknown as ExtendedGroup[]\n\n const navPreferences = await getNavPrefs(req)\n\n const serverProps = {\n i18n,\n locale,\n params,\n payload,\n permissions,\n searchParams,\n user,\n }\n\n const clientProps = {\n documentSubViewType,\n viewType,\n }\n\n const beforeNavRendered = RenderServerComponent({\n clientProps,\n Component: beforeNav,\n importMap: payload.importMap,\n serverProps,\n })\n\n const beforeNavLinksRendered = RenderServerComponent({\n clientProps,\n Component: beforeNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n const afterNavLinksRendered = RenderServerComponent({\n clientProps,\n Component: afterNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n const afterNavRendered = RenderServerComponent({\n clientProps,\n Component: afterNav,\n importMap: payload.importMap,\n serverProps,\n })\n\n const renderedSettingsMenu =\n settingsMenu && Array.isArray(settingsMenu)\n ? settingsMenu.map((item, index) =>\n RenderServerComponent({\n clientProps,\n Component: item,\n importMap: payload.importMap,\n key: `settings-menu-item-${index}`,\n serverProps,\n }),\n )\n : []\n\n const config: EnhancedSidebarConfig = sidebarConfig ?? {\n tabs: [\n {\n id: 'default',\n type: 'tab',\n icon: 'LayoutGrid',\n label: 'Collections',\n },\n ],\n }\n\n // Filter all tab bar items by access — fail-closed: missing req denies access\n // Note: req can be undefined on 404 pages due to a Payload bug (NotFound view omits req from props)\n const allConfigTabs = config.tabs ?? []\n const tabAccessResults = await Promise.all(\n allConfigTabs.map((t) =>\n Promise.resolve()\n .then(() => (t.access ? (req ? t.access({ item: t, req }) : false) : true))\n .catch(() => false),\n ),\n )\n const visibleTabItems = allConfigTabs.filter((_, i) => tabAccessResults[i])\n\n // Read active tab from cookie\n const cookieStore = await cookies()\n const storedTabId = cookieStore.get(COOKIE_KEY)?.value\n const tabs = visibleTabItems.filter((t) => t.type === 'tab') as SidebarTabContentType[]\n const defaultTabId = tabs[0]?.id ?? ''\n const initialActiveTabId =\n storedTabId && tabs.some((t) => t.id === storedTabId) ? storedTabId : defaultTabId\n\n const adminRoute = payload.config.routes.admin\n const currentLang = i18n.language\n\n /**\n * Renders a single entity as a NavItem (default or custom).\n */\n const renderEntity = (entity: ExtendedEntity, key: string): React.ReactNode => {\n const { slug, type } = entity\n let href: string\n let id: string\n\n if (type === EntityType.collection) {\n href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })\n id = `nav-${slug}`\n } else if (type === EntityType.global) {\n href = formatAdminURL({ adminRoute, path: `/globals/${slug}` })\n id = `nav-global-${slug}`\n } else if (type === 'custom' && entity.href) {\n id = `nav-custom-${slug}`\n href = entity.isExternal ? entity.href : formatAdminURL({ adminRoute, path: entity.href })\n } else {\n return null\n }\n\n const badgeConfig = config.badges?.[slug]\n\n if (config.customComponents?.NavItem) {\n const label = getTranslation(entity.label, i18n)\n const { clientProps: extraProps, path } = resolveSidebarComponent(\n config.customComponents.NavItem,\n )\n return RenderServerComponent({\n clientProps: { id, badgeConfig, entity, href, label, ...extraProps },\n Component: path,\n importMap: payload.importMap,\n key,\n serverProps,\n })\n }\n\n return <NavItem badgeConfig={badgeConfig} entity={entity} href={href} id={id} key={key} />\n }\n\n /**\n * Renders a group with its entities (default NavGroup or custom).\n */\n const renderGroup = (group: ExtendedGroup, key: string): React.ReactNode => {\n const { entities, label } = group\n const isUngrouped = !label || (typeof label === 'string' && label === '')\n const translatedLabel = getTranslation(label || '', i18n)\n\n const items = entities.map((entity, i) => renderEntity(entity, `${key}-${i}`))\n\n if (isUngrouped) {\n return <Fragment key={key}>{items}</Fragment>\n }\n\n if (config.customComponents?.NavGroup) {\n const { clientProps: extraProps, path } = resolveSidebarComponent(\n config.customComponents.NavGroup,\n )\n return RenderServerComponent({\n clientProps: {\n children: items,\n isOpen: navPreferences?.groups?.[translatedLabel]?.open,\n label: translatedLabel,\n ...extraProps,\n },\n Component: path,\n importMap: payload.importMap,\n key,\n serverProps,\n })\n }\n\n return (\n <NavGroup\n isOpen={navPreferences?.groups?.[translatedLabel]?.open}\n key={key}\n label={translatedLabel}\n >\n {items}\n </NavGroup>\n )\n }\n\n // Pre-render content for every tab on the server (computed in parallel)\n const tabGroupResults = await Promise.all(\n tabs.map((tab) => computeGroupsForTab(tab, groups, currentLang, req)),\n )\n const tabsContent: Record<string, React.ReactNode> = {}\n for (let i = 0; i < tabs.length; i++) {\n const tab = tabs[i]\n const tabGroups = tabGroupResults[i]\n tabsContent[tab.id] = (\n <Fragment>{tabGroups.map((group, j) => renderGroup(group, `${tab.id}-${j}`))}</Fragment>\n )\n }\n\n // For the no-tabs fallback — only show all content when the config has no tab-type items at all.\n // If tabs exist but are all hidden by access control, show nothing instead of the full nav.\n const configuredTabsCount = (config.tabs ?? []).filter((t) => t.type === 'tab').length\n const allContent =\n configuredTabsCount === 0 ? (\n <Fragment>{groups.map((group, i) => renderGroup(group, `all-${i}`))}</Fragment>\n ) : undefined\n\n // Build server-side icon and tab button rendering\n const allTabItems = visibleTabItems\n const hasCustomTabButton = !!config.customComponents?.TabButton\n const hasAnyIconComponent = allTabItems.some((t) => t.type !== 'custom' && t.iconComponent)\n\n // tabIcons: per-id icon node (only built when no custom TabButton, just iconComponent overrides)\n const tabIcons: Record<string, React.ReactNode> = {}\n // renderedTabItems: fully custom tab button nodes (built when customComponents.TabButton is set)\n const renderedTabItems: React.ReactNode[] = []\n // customTabComponents: server-rendered components for type:'custom' tab bar slots\n const customTabComponents: Record<string, React.ReactNode> = {}\n\n // Pre-render all type:'custom' items\n for (const item of allTabItems) {\n if (item.type === 'custom') {\n const { clientProps: extraProps, path } = resolveSidebarComponent(item.component)\n customTabComponents[item.id] = RenderServerComponent({\n clientProps: { id: item.id, ...extraProps },\n Component: path,\n importMap: payload.importMap,\n key: item.id,\n serverProps,\n })\n }\n }\n\n if (hasCustomTabButton || hasAnyIconComponent) {\n for (const item of allTabItems) {\n if (item.type === 'custom') {\n // Include pre-rendered custom slot in renderedTabItems when using custom TabButton\n if (hasCustomTabButton) {\n renderedTabItems.push(customTabComponents[item.id])\n }\n continue\n }\n\n const label = getTranslation(item.label, i18n)\n\n // Resolve icon: custom iconComponent > default Lucide\n let iconNode: React.ReactNode\n if (item.iconComponent) {\n const { clientProps: iconExtraProps, path: iconPath } = resolveSidebarComponent(\n item.iconComponent,\n )\n iconNode = RenderServerComponent({\n clientProps: { id: item.id, type: item.type, label, ...iconExtraProps },\n Component: iconPath,\n importMap: payload.importMap,\n serverProps,\n })\n } else {\n iconNode = item.icon ? <Icon name={item.icon} size={20} /> : null\n }\n\n if (hasCustomTabButton) {\n // Compute href for links\n let href: string | undefined\n if (item.type === 'link') {\n href = item.isExternal ? item.href : formatAdminURL({ adminRoute, path: item.href })\n }\n\n const { clientProps: tabBtnExtraProps, path: tabBtnPath } = resolveSidebarComponent(\n config.customComponents!.TabButton!,\n )\n renderedTabItems.push(\n RenderServerComponent({\n clientProps: {\n id: item.id,\n type: item.type,\n badge: item.badge,\n href,\n icon: iconNode,\n isExternal: item.type === 'link' ? item.isExternal : undefined,\n label,\n ...tabBtnExtraProps,\n },\n Component: tabBtnPath,\n importMap: payload.importMap,\n key: item.id,\n serverProps,\n }),\n )\n } else if (item.iconComponent) {\n tabIcons[item.id] = iconNode\n }\n }\n }\n\n const customNavContent = config.customComponents?.NavContent\n ? (() => {\n const { clientProps: extraProps, path } = resolveSidebarComponent(\n config.customComponents.NavContent,\n )\n return RenderServerComponent({\n clientProps: {\n afterNav: afterNavRendered,\n afterNavLinks: afterNavLinksRendered,\n allContent,\n beforeNav: beforeNavRendered,\n beforeNavLinks: beforeNavLinksRendered,\n tabs: tabs.map((t) => ({ id: t.id })),\n tabsContent,\n ...extraProps,\n },\n Component: path,\n importMap: payload.importMap,\n key: 'enhanced-sidebar-nav-content',\n serverProps,\n })\n })()\n : undefined\n\n // Strip access functions and use only visible tabs before passing to client component\n const clientSidebarConfig = sanitizeSidebarConfig({ ...config, tabs: visibleTabItems })\n\n return (\n <SidebarContent\n afterNav={afterNavRendered}\n afterNavLinks={afterNavLinksRendered}\n allContent={allContent}\n beforeNav={beforeNavRendered}\n beforeNavLinks={beforeNavLinksRendered}\n customNavContent={customNavContent}\n customTabComponents={Object.keys(customTabComponents).length > 0 ? customTabComponents : undefined}\n initialActiveTabId={initialActiveTabId}\n renderedTabItems={renderedTabItems.length > 0 ? renderedTabItems : undefined}\n settingsMenu={renderedSettingsMenu}\n sidebarConfig={clientSidebarConfig}\n tabIcons={Object.keys(tabIcons).length > 0 ? tabIcons : undefined}\n tabsContent={tabsContent}\n />\n )\n}\n\nexport default EnhancedSidebar\n"],"names":["getTranslation","NavGroup","RenderServerComponent","EntityType","groupNavItems","cookies","formatAdminURL","React","Fragment","COOKIE_KEY","extractLocalizedValue","resolveSidebarComponent","sanitizeSidebarConfig","getNavPrefs","Icon","NavItem","SidebarContent","computeGroupsForTab","tab","groups","currentLang","req","collections","tabCollections","customItems","globals","tabGlobals","showAll","allowedSlugs","Set","result","map","g","entities","size","group","filter","entity","has","slug","length","topAdditions","topUngrouped","bottomUngrouped","toEntity","item","type","href","isExternal","label","accessResults","Promise","all","resolve","then","access","catch","visibleItems","_","i","itemGroupLabel","existingGroup","find","groupLabel","push","newGroup","position","unshift","EnhancedSidebar","props","documentSubViewType","i18n","locale","params","payload","permissions","searchParams","sidebarConfig","user","viewType","visibleEntities","config","admin","components","afterNav","afterNavLinks","beforeNav","beforeNavLinks","settingsMenu","includes","collection","global","navPreferences","serverProps","clientProps","beforeNavRendered","Component","importMap","beforeNavLinksRendered","afterNavLinksRendered","afterNavRendered","renderedSettingsMenu","Array","isArray","index","key","tabs","id","icon","allConfigTabs","tabAccessResults","t","visibleTabItems","cookieStore","storedTabId","get","value","defaultTabId","initialActiveTabId","some","adminRoute","routes","language","renderEntity","path","badgeConfig","badges","customComponents","extraProps","renderGroup","isUngrouped","translatedLabel","items","children","isOpen","open","tabGroupResults","tabsContent","tabGroups","j","configuredTabsCount","allContent","undefined","allTabItems","hasCustomTabButton","TabButton","hasAnyIconComponent","iconComponent","tabIcons","renderedTabItems","customTabComponents","component","iconNode","iconExtraProps","iconPath","name","tabBtnExtraProps","tabBtnPath","badge","customNavContent","NavContent","clientSidebarConfig","Object","keys"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/EnhancedSidebar/index.tsx"],"sourcesContent":["import type { EntityToGroup } from '@payloadcms/ui/shared'\nimport type { PayloadRequest, ServerProps } from 'payload'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { NavGroup } from '@payloadcms/ui'\nimport { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'\nimport { EntityType, groupNavItems } from '@payloadcms/ui/shared'\nimport { cookies } from 'next/headers.js'\nimport { formatAdminURL } from 'payload/shared'\nimport React, { Fragment } from 'react'\n\nconst COOKIE_KEY = 'payload-enhanced-sidebar-active-tab'\n\nimport type {\n EnhancedSidebarConfig,\n ExtendedEntity,\n ExtendedGroup,\n SidebarTab,\n SidebarTabContent as SidebarTabContentType,\n SidebarTabItem,\n} from '../../types.js'\n\nimport {\n extractLocalizedValue,\n resolveSidebarComponent,\n sanitizeSidebarConfig,\n sortTabGroups,\n} from '../../utils/index.js'\nimport { getNavPrefs } from './getNavPrefs.js'\nimport { Icon } from './Icon.js'\nimport { NavItem } from './NavItem/index.js'\nimport { SidebarContent } from './SidebarContent.js'\nimport './index.scss'\n\nexport type EnhancedSidebarProps = {\n req?: PayloadRequest\n sidebarConfig?: EnhancedSidebarConfig\n} & ServerProps\n\n/**\n * Computes filtered and merged groups for a specific tab (server-side).\n */\nconst computeGroupsForTab = async (\n tab: SidebarTabContentType,\n groups: ExtendedGroup[],\n currentLang: string,\n req: PayloadRequest | undefined,\n): Promise<ExtendedGroup[]> => {\n const { collections: tabCollections, customItems, globals: tabGlobals } = tab\n\n const showAll = !tabCollections && !tabGlobals\n const allowedSlugs = new Set([...(tabCollections ?? []), ...(tabGlobals ?? [])])\n\n let result: ExtendedGroup[] = []\n\n if (showAll) {\n result = groups.map((g) => ({ ...g, entities: [...g.entities] }))\n } else if (allowedSlugs.size > 0) {\n result = groups\n .map((group) => ({\n ...group,\n entities: group.entities.filter((entity) => allowedSlugs.has(entity.slug)),\n }))\n .filter((group) => group.entities.length > 0)\n }\n\n if (customItems && customItems.length > 0) {\n const topAdditions: ExtendedGroup[] = []\n const topUngrouped: SidebarTabItem[] = []\n const bottomUngrouped: SidebarTabItem[] = []\n\n const toEntity = (item: SidebarTabItem): ExtendedEntity => {\n if ('component' in item && item.component) {\n return {\n slug: item.slug,\n type: 'custom-component',\n component: item.component,\n label: item.label ?? item.slug,\n }\n }\n return {\n slug: item.slug,\n type: 'custom',\n href: item.href,\n isExternal: item.isExternal,\n label: item.label,\n } as ExtendedEntity\n }\n\n // Filter custom items by access — fail-closed: missing req or thrown error denies access\n const accessResults = await Promise.all(\n customItems.map((item) =>\n Promise.resolve()\n .then(() => (item.access ? (req ? item.access({ item, req }) : false) : true))\n .catch(() => false),\n ),\n )\n const visibleItems = customItems.filter((_, i) => accessResults[i])\n\n for (const item of visibleItems) {\n if (item.group) {\n const itemGroupLabel = extractLocalizedValue(item.group, currentLang)\n const existingGroup = result.find((g) => {\n const groupLabel = extractLocalizedValue(g.label, currentLang)\n return groupLabel === itemGroupLabel\n })\n\n if (existingGroup) {\n // Merged into existing collection group — position has no effect here\n existingGroup.entities.push(toEntity(item))\n } else {\n // New custom group — position controls top vs bottom\n const newGroup: ExtendedGroup = { entities: [toEntity(item)], label: item.group }\n if (item.position === 'top') {\n topAdditions.push(newGroup)\n } else {\n result.push(newGroup)\n }\n }\n } else {\n if (item.position === 'top') {\n topUngrouped.push(item)\n } else {\n bottomUngrouped.push(item)\n }\n }\n }\n\n if (topUngrouped.length > 0) {\n topAdditions.unshift({ entities: topUngrouped.map(toEntity), label: '' })\n }\n\n if (bottomUngrouped.length > 0) {\n result.push({ entities: bottomUngrouped.map(toEntity), label: '' })\n }\n\n result = [...topAdditions, ...result]\n }\n\n return result\n}\n\nexport const EnhancedSidebar: React.FC<EnhancedSidebarProps> = async (props) => {\n const {\n documentSubViewType,\n i18n,\n locale,\n params,\n payload,\n permissions,\n req,\n searchParams,\n sidebarConfig,\n user,\n viewType,\n visibleEntities,\n } = props\n\n if (!payload?.config) {\n return null\n }\n\n const {\n admin: {\n components: { afterNav, afterNavLinks, beforeNav, beforeNavLinks, settingsMenu },\n },\n collections,\n globals,\n } = payload.config\n\n const groups = groupNavItems(\n [\n ...collections\n .filter(({ slug }) => visibleEntities?.collections.includes(slug))\n .map(\n (collection) =>\n ({\n type: EntityType.collection,\n entity: collection,\n }) satisfies EntityToGroup,\n ),\n ...globals\n .filter(({ slug }) => visibleEntities?.globals.includes(slug))\n .map(\n (global) =>\n ({\n type: EntityType.global,\n entity: global,\n }) satisfies EntityToGroup,\n ),\n ],\n permissions || {},\n i18n,\n ) as unknown as ExtendedGroup[]\n\n const navPreferences = await getNavPrefs(req)\n\n const serverProps = {\n i18n,\n locale,\n params,\n payload,\n permissions,\n searchParams,\n user,\n }\n\n const clientProps = {\n documentSubViewType,\n viewType,\n }\n\n const beforeNavRendered = RenderServerComponent({\n clientProps,\n Component: beforeNav,\n importMap: payload.importMap,\n serverProps,\n })\n\n const beforeNavLinksRendered = RenderServerComponent({\n clientProps,\n Component: beforeNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n const afterNavLinksRendered = RenderServerComponent({\n clientProps,\n Component: afterNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n const afterNavRendered = RenderServerComponent({\n clientProps,\n Component: afterNav,\n importMap: payload.importMap,\n serverProps,\n })\n\n const renderedSettingsMenu =\n settingsMenu && Array.isArray(settingsMenu)\n ? settingsMenu.map((item, index) =>\n RenderServerComponent({\n clientProps,\n Component: item,\n importMap: payload.importMap,\n key: `settings-menu-item-${index}`,\n serverProps,\n }),\n )\n : []\n\n const config: EnhancedSidebarConfig = sidebarConfig ?? {\n tabs: [\n {\n id: 'default',\n type: 'tab',\n icon: 'LayoutGrid',\n label: 'Collections',\n },\n ],\n }\n\n // Filter all tab bar items by access — fail-closed: missing req denies access\n // Note: req can be undefined on 404 pages due to a Payload bug (NotFound view omits req from props)\n const allConfigTabs = config.tabs ?? []\n const tabAccessResults = await Promise.all(\n allConfigTabs.map((t) =>\n Promise.resolve()\n .then(() => (t.access ? (req ? t.access({ item: t, req }) : false) : true))\n .catch(() => false),\n ),\n )\n const visibleTabItems = allConfigTabs.filter((_, i) => tabAccessResults[i])\n\n // Read active tab from cookie\n const cookieStore = await cookies()\n const storedTabId = cookieStore.get(COOKIE_KEY)?.value\n const tabs = visibleTabItems.filter((t) => t.type === 'tab') as SidebarTabContentType[]\n const defaultTabId = tabs[0]?.id ?? ''\n const initialActiveTabId =\n storedTabId && tabs.some((t) => t.id === storedTabId) ? storedTabId : defaultTabId\n\n const adminRoute = payload.config.routes.admin\n const currentLang = i18n.language\n\n /**\n * Renders a single entity as a NavItem (default or custom).\n */\n const renderEntity = (entity: ExtendedEntity, key: string): React.ReactNode => {\n const { slug, type } = entity\n\n // Per-item custom component — render its own component, bypassing the NavItem override.\n if (type === 'custom-component') {\n const { clientProps: extraProps, path } = resolveSidebarComponent(entity.component)\n return RenderServerComponent({\n clientProps: { slug, ...extraProps },\n Component: path,\n importMap: payload.importMap,\n key,\n serverProps,\n })\n }\n\n let href: string\n let id: string\n\n if (type === EntityType.collection) {\n href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })\n id = `nav-${slug}`\n } else if (type === EntityType.global) {\n href = formatAdminURL({ adminRoute, path: `/globals/${slug}` })\n id = `nav-global-${slug}`\n } else if (type === 'custom' && entity.href) {\n id = `nav-custom-${slug}`\n href = entity.isExternal ? entity.href : formatAdminURL({ adminRoute, path: entity.href })\n } else {\n return null\n }\n\n const badgeConfig = config.badges?.[slug]\n\n if (config.customComponents?.NavItem) {\n const label = getTranslation(entity.label, i18n)\n const { clientProps: extraProps, path } = resolveSidebarComponent(\n config.customComponents.NavItem,\n )\n return RenderServerComponent({\n clientProps: { id, badgeConfig, entity, href, label, ...extraProps },\n Component: path,\n importMap: payload.importMap,\n key,\n serverProps,\n })\n }\n\n return <NavItem badgeConfig={badgeConfig} entity={entity} href={href} id={id} key={key} />\n }\n\n /**\n * Renders a group with its entities (default NavGroup or custom).\n */\n const renderGroup = (group: ExtendedGroup, key: string): React.ReactNode => {\n const { entities, label } = group\n const isUngrouped = !label || (typeof label === 'string' && label === '')\n const translatedLabel = getTranslation(label || '', i18n)\n\n const items = entities.map((entity, i) => renderEntity(entity, `${key}-${i}`))\n\n if (isUngrouped) {\n return <Fragment key={key}>{items}</Fragment>\n }\n\n if (config.customComponents?.NavGroup) {\n const { clientProps: extraProps, path } = resolveSidebarComponent(\n config.customComponents.NavGroup,\n )\n return RenderServerComponent({\n clientProps: {\n children: items,\n isOpen: navPreferences?.groups?.[translatedLabel]?.open,\n label: translatedLabel,\n ...extraProps,\n },\n Component: path,\n importMap: payload.importMap,\n key,\n serverProps,\n })\n }\n\n return (\n <NavGroup\n isOpen={navPreferences?.groups?.[translatedLabel]?.open}\n key={key}\n label={translatedLabel}\n >\n {items}\n </NavGroup>\n )\n }\n\n // Pre-render content for every tab on the server (computed in parallel)\n const tabGroupResults = await Promise.all(\n tabs.map((tab) => computeGroupsForTab(tab, groups, currentLang, req)),\n )\n const tabsContent: Record<string, React.ReactNode> = {}\n for (let i = 0; i < tabs.length; i++) {\n const tab = tabs[i]\n const tabGroups = sortTabGroups(tabGroupResults[i], config.sort?.[tab.id], currentLang)\n tabsContent[tab.id] = (\n <Fragment>{tabGroups.map((group, j) => renderGroup(group, `${tab.id}-${j}`))}</Fragment>\n )\n }\n\n // For the no-tabs fallback — only show all content when the config has no tab-type items at all.\n // If tabs exist but are all hidden by access control, show nothing instead of the full nav.\n const configuredTabsCount = (config.tabs ?? []).filter((t) => t.type === 'tab').length\n const allContent =\n configuredTabsCount === 0 ? (\n <Fragment>{groups.map((group, i) => renderGroup(group, `all-${i}`))}</Fragment>\n ) : undefined\n\n // Build server-side icon and tab button rendering\n const allTabItems = visibleTabItems\n const hasCustomTabButton = !!config.customComponents?.TabButton\n const hasAnyIconComponent = allTabItems.some((t) => t.type !== 'custom' && t.iconComponent)\n\n // tabIcons: per-id icon node (only built when no custom TabButton, just iconComponent overrides)\n const tabIcons: Record<string, React.ReactNode> = {}\n // renderedTabItems: fully custom tab button nodes (built when customComponents.TabButton is set)\n const renderedTabItems: React.ReactNode[] = []\n // customTabComponents: server-rendered components for type:'custom' tab bar slots\n const customTabComponents: Record<string, React.ReactNode> = {}\n\n // Pre-render all type:'custom' items\n for (const item of allTabItems) {\n if (item.type === 'custom') {\n const { clientProps: extraProps, path } = resolveSidebarComponent(item.component)\n customTabComponents[item.id] = RenderServerComponent({\n clientProps: { id: item.id, ...extraProps },\n Component: path,\n importMap: payload.importMap,\n key: item.id,\n serverProps,\n })\n }\n }\n\n if (hasCustomTabButton || hasAnyIconComponent) {\n for (const item of allTabItems) {\n if (item.type === 'custom') {\n // Include pre-rendered custom slot in renderedTabItems when using custom TabButton\n if (hasCustomTabButton) {\n renderedTabItems.push(customTabComponents[item.id])\n }\n continue\n }\n\n const label = getTranslation(item.label, i18n)\n\n // Resolve icon: custom iconComponent > default Lucide\n let iconNode: React.ReactNode\n if (item.iconComponent) {\n const { clientProps: iconExtraProps, path: iconPath } = resolveSidebarComponent(\n item.iconComponent,\n )\n iconNode = RenderServerComponent({\n clientProps: { id: item.id, type: item.type, label, ...iconExtraProps },\n Component: iconPath,\n importMap: payload.importMap,\n serverProps,\n })\n } else {\n iconNode = item.icon ? <Icon name={item.icon} size={20} /> : null\n }\n\n if (hasCustomTabButton) {\n // Compute href for links\n let href: string | undefined\n if (item.type === 'link') {\n href = item.isExternal ? item.href : formatAdminURL({ adminRoute, path: item.href })\n }\n\n const { clientProps: tabBtnExtraProps, path: tabBtnPath } = resolveSidebarComponent(\n config.customComponents!.TabButton!,\n )\n renderedTabItems.push(\n RenderServerComponent({\n clientProps: {\n id: item.id,\n type: item.type,\n badge: item.badge,\n href,\n icon: iconNode,\n isExternal: item.type === 'link' ? item.isExternal : undefined,\n label,\n ...tabBtnExtraProps,\n },\n Component: tabBtnPath,\n importMap: payload.importMap,\n key: item.id,\n serverProps,\n }),\n )\n } else if (item.iconComponent) {\n tabIcons[item.id] = iconNode\n }\n }\n }\n\n const customNavContent = config.customComponents?.NavContent\n ? (() => {\n const { clientProps: extraProps, path } = resolveSidebarComponent(\n config.customComponents.NavContent,\n )\n return RenderServerComponent({\n clientProps: {\n afterNav: afterNavRendered,\n afterNavLinks: afterNavLinksRendered,\n allContent,\n beforeNav: beforeNavRendered,\n beforeNavLinks: beforeNavLinksRendered,\n tabs: tabs.map((t) => ({ id: t.id })),\n tabsContent,\n ...extraProps,\n },\n Component: path,\n importMap: payload.importMap,\n key: 'enhanced-sidebar-nav-content',\n serverProps,\n })\n })()\n : undefined\n\n // Strip access functions and use only visible tabs before passing to client component\n const clientSidebarConfig = sanitizeSidebarConfig({ ...config, tabs: visibleTabItems })\n\n return (\n <SidebarContent\n afterNav={afterNavRendered}\n afterNavLinks={afterNavLinksRendered}\n allContent={allContent}\n beforeNav={beforeNavRendered}\n beforeNavLinks={beforeNavLinksRendered}\n customNavContent={customNavContent}\n customTabComponents={Object.keys(customTabComponents).length > 0 ? customTabComponents : undefined}\n initialActiveTabId={initialActiveTabId}\n renderedTabItems={renderedTabItems.length > 0 ? renderedTabItems : undefined}\n settingsMenu={renderedSettingsMenu}\n sidebarConfig={clientSidebarConfig}\n tabIcons={Object.keys(tabIcons).length > 0 ? tabIcons : undefined}\n tabsContent={tabsContent}\n />\n )\n}\n\nexport default EnhancedSidebar\n"],"names":["getTranslation","NavGroup","RenderServerComponent","EntityType","groupNavItems","cookies","formatAdminURL","React","Fragment","COOKIE_KEY","extractLocalizedValue","resolveSidebarComponent","sanitizeSidebarConfig","sortTabGroups","getNavPrefs","Icon","NavItem","SidebarContent","computeGroupsForTab","tab","groups","currentLang","req","collections","tabCollections","customItems","globals","tabGlobals","showAll","allowedSlugs","Set","result","map","g","entities","size","group","filter","entity","has","slug","length","topAdditions","topUngrouped","bottomUngrouped","toEntity","item","component","type","label","href","isExternal","accessResults","Promise","all","resolve","then","access","catch","visibleItems","_","i","itemGroupLabel","existingGroup","find","groupLabel","push","newGroup","position","unshift","EnhancedSidebar","props","documentSubViewType","i18n","locale","params","payload","permissions","searchParams","sidebarConfig","user","viewType","visibleEntities","config","admin","components","afterNav","afterNavLinks","beforeNav","beforeNavLinks","settingsMenu","includes","collection","global","navPreferences","serverProps","clientProps","beforeNavRendered","Component","importMap","beforeNavLinksRendered","afterNavLinksRendered","afterNavRendered","renderedSettingsMenu","Array","isArray","index","key","tabs","id","icon","allConfigTabs","tabAccessResults","t","visibleTabItems","cookieStore","storedTabId","get","value","defaultTabId","initialActiveTabId","some","adminRoute","routes","language","renderEntity","extraProps","path","badgeConfig","badges","customComponents","renderGroup","isUngrouped","translatedLabel","items","children","isOpen","open","tabGroupResults","tabsContent","tabGroups","sort","j","configuredTabsCount","allContent","undefined","allTabItems","hasCustomTabButton","TabButton","hasAnyIconComponent","iconComponent","tabIcons","renderedTabItems","customTabComponents","iconNode","iconExtraProps","iconPath","name","tabBtnExtraProps","tabBtnPath","badge","customNavContent","NavContent","clientSidebarConfig","Object","keys"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { BadgeProvider, useBadgeContext, useBadgeValue, } from './components/Enh
|
|
|
5
5
|
export { useEnhancedSidebar } from './components/EnhancedSidebar/context.js';
|
|
6
6
|
export { useNavItemState } from './components/EnhancedSidebar/hooks/useNavItemState.js';
|
|
7
7
|
export { useTabState } from './components/EnhancedSidebar/hooks/useTabState.js';
|
|
8
|
-
export type { BadgeColor, BadgeConfig, BadgeConfigApi, BadgeConfigCollectionCount, BadgeConfigProvider, BadgeValues, CustomNavContentProps, CustomNavGroupProps, CustomNavItemProps, CustomTabButtonProps, CustomTabIconProps, CustomTabsBarComponentProps, EnhancedSidebarConfig, ItemAccessFunction, SidebarComponent, SidebarTabCustom, TabAccessFunction, } from './types.js';
|
|
8
|
+
export type { BadgeColor, BadgeConfig, BadgeConfigApi, BadgeConfigCollectionCount, BadgeConfigProvider, BadgeValues, CustomNavContentProps, CustomNavGroupProps, CustomNavItemComponentProps, CustomNavItemProps, CustomTabButtonProps, CustomTabIconProps, CustomTabsBarComponentProps, EnhancedSidebarConfig, ExtendedEntity, ExtendedGroup, GroupSortFunction, IconName, ItemAccessFunction, ItemSortFunction, SidebarComponent, SidebarSortContext, SidebarSortKey, SidebarTabContent, SidebarTabCustom, SidebarTabItem, SidebarTabLink, SortableGroup, TabAccessFunction, TabIconConfig, TabSortConfig, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -88,6 +88,18 @@ export const payloadEnhancedSidebar = (pluginOptions = {})=>(config)=>{
|
|
|
88
88
|
path
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
// Register per-item custom components from a tab's customItems
|
|
92
|
+
if (tab.type === 'tab' && tab.customItems) {
|
|
93
|
+
for (const item of tab.customItems){
|
|
94
|
+
if ('component' in item && item.component) {
|
|
95
|
+
const { path } = resolveSidebarComponent(item.component);
|
|
96
|
+
config.admin.dependencies[`enhanced-sidebar-custom-item-${tab.id}-${item.slug}`] = {
|
|
97
|
+
type: 'component',
|
|
98
|
+
path
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
// Check if we have any badges to fetch (api or collection-count)
|
|
93
105
|
const hasBadgesToFetch = sidebarConfig.badges || sidebarConfig.tabs?.some((tab)=>tab.type !== 'custom' && tab.badge && tab.badge.type !== 'provider');
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { type Config, deepMerge } from 'payload'\n\nimport type { EnhancedSidebarConfig } from './types.js'\n\nimport { sidebarTranslations } from './translations/index.js'\nimport { resolveSidebarComponent, sanitizeSidebarConfig } from './utils/index.js'\n\n/**\n * Default configuration for the enhanced sidebar\n */\nconst defaultConfig: EnhancedSidebarConfig = {\n showLogout: true,\n tabs: [\n {\n id: 'dashboard',\n type: 'link',\n href: '/',\n icon: 'House',\n label: { en: 'Dashboard', uk: 'Головна' },\n },\n {\n id: 'default',\n type: 'tab',\n icon: 'LayoutGrid',\n label: { en: 'Collections', uk: 'Колекції' },\n },\n ],\n}\n\nexport const payloadEnhancedSidebar =\n (pluginOptions: EnhancedSidebarConfig = {}) =>\n (config: Config): Config => {\n if (pluginOptions.disabled) {\n return config\n }\n\n const sidebarConfig: EnhancedSidebarConfig = {\n ...defaultConfig,\n ...pluginOptions,\n tabs: pluginOptions.tabs ?? defaultConfig.tabs,\n }\n\n if (!config.admin) {\n config.admin = {}\n }\n\n if (!config.admin.components) {\n config.admin.components = {}\n }\n\n // Always override Nav — user-defined Nav in config should use customComponents instead\n config.admin.components.Nav = {\n path: '@veiag/payload-enhanced-sidebar/rsc#EnhancedSidebar',\n serverProps: {\n sidebarConfig,\n },\n }\n\n // Register custom components and per-tab icons in the import map\n if (!config.admin.dependencies) {\n config.admin.dependencies = {}\n }\n\n const customComponentSlots = ['NavContent', 'NavGroup', 'NavItem', 'TabButton'] as const\n for (const slot of customComponentSlots) {\n const component = sidebarConfig.customComponents?.[slot]\n if (component) {\n const { path } = resolveSidebarComponent(component)\n config.admin.dependencies[`enhanced-sidebar-${slot.toLowerCase()}`] = {\n type: 'component',\n path,\n }\n }\n }\n\n const seenTabIds = new Set<string>()\n for (const tab of sidebarConfig.tabs ?? []) {\n if (seenTabIds.has(tab.id)) {\n throw new Error(\n `[payload-enhanced-sidebar] Duplicate tab id \"${tab.id}\". Each tab must have a unique id.`,\n )\n }\n seenTabIds.add(tab.id)\n\n if (tab.type === 'custom') {\n const { path } = resolveSidebarComponent(tab.component)\n config.admin.dependencies[`enhanced-sidebar-custom-tab-${tab.id}`] = {\n type: 'component',\n path,\n }\n } else if (tab.iconComponent) {\n const { path } = resolveSidebarComponent(tab.iconComponent)\n config.admin.dependencies[`enhanced-sidebar-icon-${tab.id}`] = {\n type: 'component',\n path,\n }\n }\n }\n\n // Check if we have any badges to fetch (api or collection-count)\n const hasBadgesToFetch =\n sidebarConfig.badges ||\n sidebarConfig.tabs?.some(\n (tab) => tab.type !== 'custom' && tab.badge && tab.badge.type !== 'provider',\n )\n\n // Add InternalBadgeProvider if we have badges to fetch\n if (hasBadgesToFetch) {\n if (!config.admin.components.providers) {\n config.admin.components.providers = []\n }\n\n // Add our internal provider at the beginning (so user providers can override)\n config.admin.components.providers.unshift({\n clientProps: {\n sidebarConfig: sanitizeSidebarConfig(sidebarConfig),\n },\n path: '@veiag/payload-enhanced-sidebar/client#InternalBadgeProvider',\n })\n }\n\n // Adding translations\n if (!config.i18n) {\n config.i18n = {}\n }\n if (!config.i18n.translations) {\n config.i18n.translations = {}\n }\n\n config.i18n.translations = deepMerge(config.i18n.translations, sidebarTranslations)\n\n return config\n }\n\nexport {\n BadgeProvider,\n useBadgeContext,\n useBadgeValue,\n} from './components/EnhancedSidebar/BadgeProvider/index.js'\n\nexport { useEnhancedSidebar } from './components/EnhancedSidebar/context.js'\nexport { useNavItemState } from './components/EnhancedSidebar/hooks/useNavItemState.js'\nexport { useTabState } from './components/EnhancedSidebar/hooks/useTabState.js'\n\nexport type {\n BadgeColor,\n BadgeConfig,\n BadgeConfigApi,\n BadgeConfigCollectionCount,\n BadgeConfigProvider,\n BadgeValues,\n CustomNavContentProps,\n CustomNavGroupProps,\n CustomNavItemProps,\n CustomTabButtonProps,\n CustomTabIconProps,\n CustomTabsBarComponentProps,\n EnhancedSidebarConfig,\n ItemAccessFunction,\n SidebarComponent,\n SidebarTabCustom,\n TabAccessFunction,\n} from './types.js'\n"],"names":["deepMerge","sidebarTranslations","resolveSidebarComponent","sanitizeSidebarConfig","defaultConfig","showLogout","tabs","id","type","href","icon","label","en","uk","payloadEnhancedSidebar","pluginOptions","config","disabled","sidebarConfig","admin","components","Nav","path","serverProps","dependencies","customComponentSlots","slot","component","customComponents","toLowerCase","seenTabIds","Set","tab","has","Error","add","iconComponent","hasBadgesToFetch","badges","some","badge","providers","unshift","clientProps","i18n","translations","BadgeProvider","useBadgeContext","useBadgeValue","useEnhancedSidebar","useNavItemState","useTabState"],"mappings":"AAAA,SAAsBA,SAAS,QAAQ,UAAS;AAIhD,SAASC,mBAAmB,QAAQ,0BAAyB;AAC7D,SAASC,uBAAuB,EAAEC,qBAAqB,QAAQ,mBAAkB;AAEjF;;CAEC,GACD,MAAMC,gBAAuC;IAC3CC,YAAY;IACZC,MAAM;QACJ;YACEC,IAAI;YACJC,MAAM;YACNC,MAAM;YACNC,MAAM;YACNC,OAAO;gBAAEC,IAAI;gBAAaC,IAAI;YAAU;QAC1C;QACA;YACEN,IAAI;YACJC,MAAM;YACNE,MAAM;YACNC,OAAO;gBAAEC,IAAI;gBAAeC,IAAI;YAAW;QAC7C;KACD;AACH;AAEA,OAAO,MAAMC,yBACX,CAACC,gBAAuC,CAAC,CAAC,GAC1C,CAACC;QACC,IAAID,cAAcE,QAAQ,EAAE;YAC1B,OAAOD;QACT;QAEA,MAAME,gBAAuC;YAC3C,GAAGd,aAAa;YAChB,GAAGW,aAAa;YAChBT,MAAMS,cAAcT,IAAI,IAAIF,cAAcE,IAAI;QAChD;QAEA,IAAI,CAACU,OAAOG,KAAK,EAAE;YACjBH,OAAOG,KAAK,GAAG,CAAC;QAClB;QAEA,IAAI,CAACH,OAAOG,KAAK,CAACC,UAAU,EAAE;YAC5BJ,OAAOG,KAAK,CAACC,UAAU,GAAG,CAAC;QAC7B;QAEA,uFAAuF;QACvFJ,OAAOG,KAAK,CAACC,UAAU,CAACC,GAAG,GAAG;YAC5BC,MAAM;YACNC,aAAa;gBACXL;YACF;QACF;QAEA,iEAAiE;QACjE,IAAI,CAACF,OAAOG,KAAK,CAACK,YAAY,EAAE;YAC9BR,OAAOG,KAAK,CAACK,YAAY,GAAG,CAAC;QAC/B;QAEA,MAAMC,uBAAuB;YAAC;YAAc;YAAY;YAAW;SAAY;QAC/E,KAAK,MAAMC,QAAQD,qBAAsB;YACvC,MAAME,YAAYT,cAAcU,gBAAgB,EAAE,CAACF,KAAK;YACxD,IAAIC,WAAW;gBACb,MAAM,EAAEL,IAAI,EAAE,GAAGpB,wBAAwByB;gBACzCX,OAAOG,KAAK,CAACK,YAAY,CAAC,CAAC,iBAAiB,EAAEE,KAAKG,WAAW,IAAI,CAAC,GAAG;oBACpErB,MAAM;oBACNc;gBACF;YACF;QACF;QAEA,MAAMQ,aAAa,IAAIC;QACvB,KAAK,MAAMC,OAAOd,cAAcZ,IAAI,IAAI,EAAE,CAAE;YAC1C,IAAIwB,WAAWG,GAAG,CAACD,IAAIzB,EAAE,GAAG;gBAC1B,MAAM,IAAI2B,MACR,CAAC,6CAA6C,EAAEF,IAAIzB,EAAE,CAAC,kCAAkC,CAAC;YAE9F;YACAuB,WAAWK,GAAG,CAACH,IAAIzB,EAAE;YAErB,IAAIyB,IAAIxB,IAAI,KAAK,UAAU;gBACzB,MAAM,EAAEc,IAAI,EAAE,GAAGpB,wBAAwB8B,IAAIL,SAAS;gBACtDX,OAAOG,KAAK,CAACK,YAAY,CAAC,CAAC,4BAA4B,EAAEQ,IAAIzB,EAAE,EAAE,CAAC,GAAG;oBACnEC,MAAM;oBACNc;gBACF;YACF,OAAO,IAAIU,IAAII,aAAa,EAAE;gBAC5B,MAAM,EAAEd,IAAI,EAAE,GAAGpB,wBAAwB8B,IAAII,aAAa;gBAC1DpB,OAAOG,KAAK,CAACK,YAAY,CAAC,CAAC,sBAAsB,EAAEQ,IAAIzB,EAAE,EAAE,CAAC,GAAG;oBAC7DC,MAAM;oBACNc;gBACF;YACF;QACF;QAEA,iEAAiE;QACjE,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { type Config, deepMerge } from 'payload'\n\nimport type { EnhancedSidebarConfig } from './types.js'\n\nimport { sidebarTranslations } from './translations/index.js'\nimport { resolveSidebarComponent, sanitizeSidebarConfig } from './utils/index.js'\n\n/**\n * Default configuration for the enhanced sidebar\n */\nconst defaultConfig: EnhancedSidebarConfig = {\n showLogout: true,\n tabs: [\n {\n id: 'dashboard',\n type: 'link',\n href: '/',\n icon: 'House',\n label: { en: 'Dashboard', uk: 'Головна' },\n },\n {\n id: 'default',\n type: 'tab',\n icon: 'LayoutGrid',\n label: { en: 'Collections', uk: 'Колекції' },\n },\n ],\n}\n\nexport const payloadEnhancedSidebar =\n (pluginOptions: EnhancedSidebarConfig = {}) =>\n (config: Config): Config => {\n if (pluginOptions.disabled) {\n return config\n }\n\n const sidebarConfig: EnhancedSidebarConfig = {\n ...defaultConfig,\n ...pluginOptions,\n tabs: pluginOptions.tabs ?? defaultConfig.tabs,\n }\n\n if (!config.admin) {\n config.admin = {}\n }\n\n if (!config.admin.components) {\n config.admin.components = {}\n }\n\n // Always override Nav — user-defined Nav in config should use customComponents instead\n config.admin.components.Nav = {\n path: '@veiag/payload-enhanced-sidebar/rsc#EnhancedSidebar',\n serverProps: {\n sidebarConfig,\n },\n }\n\n // Register custom components and per-tab icons in the import map\n if (!config.admin.dependencies) {\n config.admin.dependencies = {}\n }\n\n const customComponentSlots = ['NavContent', 'NavGroup', 'NavItem', 'TabButton'] as const\n for (const slot of customComponentSlots) {\n const component = sidebarConfig.customComponents?.[slot]\n if (component) {\n const { path } = resolveSidebarComponent(component)\n config.admin.dependencies[`enhanced-sidebar-${slot.toLowerCase()}`] = {\n type: 'component',\n path,\n }\n }\n }\n\n const seenTabIds = new Set<string>()\n for (const tab of sidebarConfig.tabs ?? []) {\n if (seenTabIds.has(tab.id)) {\n throw new Error(\n `[payload-enhanced-sidebar] Duplicate tab id \"${tab.id}\". Each tab must have a unique id.`,\n )\n }\n seenTabIds.add(tab.id)\n\n if (tab.type === 'custom') {\n const { path } = resolveSidebarComponent(tab.component)\n config.admin.dependencies[`enhanced-sidebar-custom-tab-${tab.id}`] = {\n type: 'component',\n path,\n }\n } else if (tab.iconComponent) {\n const { path } = resolveSidebarComponent(tab.iconComponent)\n config.admin.dependencies[`enhanced-sidebar-icon-${tab.id}`] = {\n type: 'component',\n path,\n }\n }\n\n // Register per-item custom components from a tab's customItems\n if (tab.type === 'tab' && tab.customItems) {\n for (const item of tab.customItems) {\n if ('component' in item && item.component) {\n const { path } = resolveSidebarComponent(item.component)\n config.admin.dependencies[`enhanced-sidebar-custom-item-${tab.id}-${item.slug}`] = {\n type: 'component',\n path,\n }\n }\n }\n }\n }\n\n // Check if we have any badges to fetch (api or collection-count)\n const hasBadgesToFetch =\n sidebarConfig.badges ||\n sidebarConfig.tabs?.some(\n (tab) => tab.type !== 'custom' && tab.badge && tab.badge.type !== 'provider',\n )\n\n // Add InternalBadgeProvider if we have badges to fetch\n if (hasBadgesToFetch) {\n if (!config.admin.components.providers) {\n config.admin.components.providers = []\n }\n\n // Add our internal provider at the beginning (so user providers can override)\n config.admin.components.providers.unshift({\n clientProps: {\n sidebarConfig: sanitizeSidebarConfig(sidebarConfig),\n },\n path: '@veiag/payload-enhanced-sidebar/client#InternalBadgeProvider',\n })\n }\n\n // Adding translations\n if (!config.i18n) {\n config.i18n = {}\n }\n if (!config.i18n.translations) {\n config.i18n.translations = {}\n }\n\n config.i18n.translations = deepMerge(config.i18n.translations, sidebarTranslations)\n\n return config\n }\n\nexport {\n BadgeProvider,\n useBadgeContext,\n useBadgeValue,\n} from './components/EnhancedSidebar/BadgeProvider/index.js'\n\nexport { useEnhancedSidebar } from './components/EnhancedSidebar/context.js'\nexport { useNavItemState } from './components/EnhancedSidebar/hooks/useNavItemState.js'\nexport { useTabState } from './components/EnhancedSidebar/hooks/useTabState.js'\n\nexport type {\n BadgeColor,\n BadgeConfig,\n BadgeConfigApi,\n BadgeConfigCollectionCount,\n BadgeConfigProvider,\n BadgeValues,\n CustomNavContentProps,\n CustomNavGroupProps,\n CustomNavItemComponentProps,\n CustomNavItemProps,\n CustomTabButtonProps,\n CustomTabIconProps,\n CustomTabsBarComponentProps,\n EnhancedSidebarConfig,\n ExtendedEntity,\n ExtendedGroup,\n GroupSortFunction,\n IconName,\n ItemAccessFunction,\n ItemSortFunction,\n SidebarComponent,\n SidebarSortContext,\n SidebarSortKey,\n SidebarTabContent,\n SidebarTabCustom,\n SidebarTabItem,\n SidebarTabLink,\n SortableGroup,\n TabAccessFunction,\n TabIconConfig,\n TabSortConfig,\n} from './types.js'\n"],"names":["deepMerge","sidebarTranslations","resolveSidebarComponent","sanitizeSidebarConfig","defaultConfig","showLogout","tabs","id","type","href","icon","label","en","uk","payloadEnhancedSidebar","pluginOptions","config","disabled","sidebarConfig","admin","components","Nav","path","serverProps","dependencies","customComponentSlots","slot","component","customComponents","toLowerCase","seenTabIds","Set","tab","has","Error","add","iconComponent","customItems","item","slug","hasBadgesToFetch","badges","some","badge","providers","unshift","clientProps","i18n","translations","BadgeProvider","useBadgeContext","useBadgeValue","useEnhancedSidebar","useNavItemState","useTabState"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -125,7 +125,7 @@ export type SidebarComponent = {
|
|
|
125
125
|
/**
|
|
126
126
|
* Mutually exclusive icon config: either a Lucide icon name OR a custom icon component path.
|
|
127
127
|
*/
|
|
128
|
-
type TabIconConfig = {
|
|
128
|
+
export type TabIconConfig = {
|
|
129
129
|
/** Icon name from lucide-react */
|
|
130
130
|
icon: IconName;
|
|
131
131
|
iconComponent?: never;
|
|
@@ -174,6 +174,14 @@ export type SidebarTabContent = {
|
|
|
174
174
|
id: string;
|
|
175
175
|
/** Tooltip/label for the tab */
|
|
176
176
|
label: LocalizedString;
|
|
177
|
+
/**
|
|
178
|
+
* Where to render this item in the tabs bar:
|
|
179
|
+
* - `'top'` — with the main tabs at the top (default)
|
|
180
|
+
* - `'bottom'` — pinned to the bottom, just above the actions (folders/settings/logout)
|
|
181
|
+
*
|
|
182
|
+
* @default 'top'
|
|
183
|
+
*/
|
|
184
|
+
position?: 'bottom' | 'top';
|
|
177
185
|
type: 'tab';
|
|
178
186
|
} & TabIconConfig;
|
|
179
187
|
type SidebarTabLinkBase = {
|
|
@@ -191,6 +199,14 @@ type SidebarTabLinkBase = {
|
|
|
191
199
|
id: string;
|
|
192
200
|
/** Tooltip/label */
|
|
193
201
|
label: LocalizedString;
|
|
202
|
+
/**
|
|
203
|
+
* Where to render this item in the tabs bar:
|
|
204
|
+
* - `'top'` — with the main tabs at the top (default)
|
|
205
|
+
* - `'bottom'` — pinned to the bottom, just above the actions (folders/settings/logout)
|
|
206
|
+
*
|
|
207
|
+
* @default 'top'
|
|
208
|
+
*/
|
|
209
|
+
position?: 'bottom' | 'top';
|
|
194
210
|
type: 'link';
|
|
195
211
|
} & TabIconConfig;
|
|
196
212
|
type SidebarTabLinkExternal = {
|
|
@@ -224,6 +240,14 @@ export type SidebarTabCustom = {
|
|
|
224
240
|
component: SidebarComponent;
|
|
225
241
|
/** Unique identifier */
|
|
226
242
|
id: string;
|
|
243
|
+
/**
|
|
244
|
+
* Where to render this item in the tabs bar:
|
|
245
|
+
* - `'top'` — with the main tabs at the top (default)
|
|
246
|
+
* - `'bottom'` — pinned to the bottom, just above the actions (folders/settings/logout)
|
|
247
|
+
*
|
|
248
|
+
* @default 'top'
|
|
249
|
+
*/
|
|
250
|
+
position?: 'bottom' | 'top';
|
|
227
251
|
type: 'custom';
|
|
228
252
|
};
|
|
229
253
|
/**
|
|
@@ -232,6 +256,27 @@ export type SidebarTabCustom = {
|
|
|
232
256
|
export type CustomTabsBarComponentProps = {
|
|
233
257
|
id: string;
|
|
234
258
|
};
|
|
259
|
+
/**
|
|
260
|
+
* Props passed to a per-item custom component set via `component` on a `customItems` entry.
|
|
261
|
+
* Rendered in the nav content area in place of a default link row.
|
|
262
|
+
*
|
|
263
|
+
* Deliberately minimal — only the item's `slug`. Anything else should be passed via
|
|
264
|
+
* `clientProps`. (For rich, shared link rendering use `customComponents.NavItem` instead.)
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```tsx
|
|
268
|
+
* 'use client'
|
|
269
|
+
* import type { CustomNavItemComponentProps } from '@veiag/payload-enhanced-sidebar'
|
|
270
|
+
*
|
|
271
|
+
* export const Banner: React.FC<CustomNavItemComponentProps> = ({ slug }) => (
|
|
272
|
+
* <div id={`nav-${slug}`}>Custom content</div>
|
|
273
|
+
* )
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
export type CustomNavItemComponentProps = {
|
|
277
|
+
/** The item's slug from config */
|
|
278
|
+
slug: string;
|
|
279
|
+
};
|
|
235
280
|
/**
|
|
236
281
|
* A tab, link, or custom component in the sidebar tabs bar
|
|
237
282
|
*/
|
|
@@ -249,8 +294,6 @@ interface BaseSidebarTabItem {
|
|
|
249
294
|
* If not specified, item will be shown as ungrouped (position controlled by `position`).
|
|
250
295
|
*/
|
|
251
296
|
group?: LocalizedString;
|
|
252
|
-
/** Display label */
|
|
253
|
-
label: LocalizedString;
|
|
254
297
|
/**
|
|
255
298
|
* Where to place this item relative to collection groups in the tab.
|
|
256
299
|
* - `'top'` — appears above all collection/global groups
|
|
@@ -267,21 +310,50 @@ interface BaseSidebarTabItem {
|
|
|
267
310
|
slug: string;
|
|
268
311
|
}
|
|
269
312
|
interface ExternalHrefItem extends BaseSidebarTabItem {
|
|
313
|
+
component?: never;
|
|
270
314
|
/** Link href (absolute URL or relative to root) */
|
|
271
315
|
href: string;
|
|
272
316
|
/** Whether the link is external, without admin route prefix. */
|
|
273
317
|
isExternal: true;
|
|
318
|
+
/** Display label */
|
|
319
|
+
label: LocalizedString;
|
|
274
320
|
}
|
|
275
321
|
interface InternalHrefItem extends BaseSidebarTabItem {
|
|
322
|
+
component?: never;
|
|
276
323
|
/** Link href (relative to admin route) */
|
|
277
324
|
href: '' | `/${string}`;
|
|
278
325
|
/** Whether the link is external, without admin route prefix. */
|
|
279
326
|
isExternal?: false;
|
|
327
|
+
/** Display label */
|
|
328
|
+
label: LocalizedString;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* A custom item that renders an arbitrary component in place of a link row.
|
|
332
|
+
* The component is rendered server-side via Payload's component system, so both
|
|
333
|
+
* server and client components are supported. It receives `CustomNavItemComponentProps`
|
|
334
|
+
* (`{ slug }`) plus any `clientProps` you pass.
|
|
335
|
+
*
|
|
336
|
+
* Supports the same `group` / `position` placement rules as link items.
|
|
337
|
+
*/
|
|
338
|
+
interface CustomComponentItem extends BaseSidebarTabItem {
|
|
339
|
+
/**
|
|
340
|
+
* Component to render in place of a default link row.
|
|
341
|
+
* Supports a plain string path or `{ path, clientProps }`.
|
|
342
|
+
* Receives `{ slug }` plus any `clientProps`.
|
|
343
|
+
*/
|
|
344
|
+
component: SidebarComponent;
|
|
345
|
+
href?: never;
|
|
346
|
+
isExternal?: never;
|
|
347
|
+
/**
|
|
348
|
+
* Optional label. Unused for rendering — the component controls its own output —
|
|
349
|
+
* but kept for parity with link items (e.g. if you read it from `clientProps`).
|
|
350
|
+
*/
|
|
351
|
+
label?: LocalizedString;
|
|
280
352
|
}
|
|
281
353
|
/**
|
|
282
|
-
* Custom item inside a sidebar tab
|
|
354
|
+
* Custom item inside a sidebar tab — either a link (`href`) or a custom component (`component`).
|
|
283
355
|
*/
|
|
284
|
-
export type SidebarTabItem = ExternalHrefItem | InternalHrefItem;
|
|
356
|
+
export type SidebarTabItem = CustomComponentItem | ExternalHrefItem | InternalHrefItem;
|
|
285
357
|
/**
|
|
286
358
|
* Props received by a custom NavItem component registered via `customComponents.NavItem`.
|
|
287
359
|
*
|
|
@@ -501,6 +573,28 @@ export interface EnhancedSidebarConfig {
|
|
|
501
573
|
* @default true
|
|
502
574
|
*/
|
|
503
575
|
showLogout?: boolean;
|
|
576
|
+
/**
|
|
577
|
+
* Per-tab control over the order of groups and items in the nav content area.
|
|
578
|
+
* Keyed by tab `id`. Functions run server-side and are applied as a final pass
|
|
579
|
+
* after the default ordering — so anything you don't assign a key to keeps its
|
|
580
|
+
* default position. Has no effect on tabs not listed here.
|
|
581
|
+
*
|
|
582
|
+
* @example
|
|
583
|
+
* ```typescript
|
|
584
|
+
* sort: {
|
|
585
|
+
* shop: {
|
|
586
|
+
* groups: (group) => {
|
|
587
|
+
* if (group.isUngrouped && group.entities[0]?.slug === 'banner') return -100
|
|
588
|
+
* if (typeof group.label !== 'string' && group.label.en === 'Featured') return 0
|
|
589
|
+
* },
|
|
590
|
+
* items: (item, group) => {
|
|
591
|
+
* if (item.type === 'custom-component') return 10 // after collections
|
|
592
|
+
* },
|
|
593
|
+
* },
|
|
594
|
+
* }
|
|
595
|
+
* ```
|
|
596
|
+
*/
|
|
597
|
+
sort?: Record<string, TabSortConfig>;
|
|
504
598
|
/**
|
|
505
599
|
* Tabs and links to show in the sidebar tabs bar.
|
|
506
600
|
* Order matters - items are rendered top to bottom.
|
|
@@ -530,9 +624,72 @@ interface ExternalExtendedEntity extends BaseExtendedEntity {
|
|
|
530
624
|
href: string;
|
|
531
625
|
isExternal: true;
|
|
532
626
|
}
|
|
533
|
-
|
|
627
|
+
/**
|
|
628
|
+
* A custom item that renders its own component instead of a link row.
|
|
629
|
+
* Carries the component reference through to server-side rendering.
|
|
630
|
+
*/
|
|
631
|
+
interface CustomComponentExtendedEntity {
|
|
632
|
+
component: SidebarComponent;
|
|
633
|
+
href?: never;
|
|
634
|
+
isExternal?: never;
|
|
635
|
+
label: Record<string, string> | string;
|
|
636
|
+
slug: string;
|
|
637
|
+
type: 'custom-component';
|
|
638
|
+
}
|
|
639
|
+
export type ExtendedEntity = CustomComponentExtendedEntity | ExternalExtendedEntity | InternalExtendedEntity;
|
|
534
640
|
export type ExtendedGroup = {
|
|
535
641
|
entities: ExtendedEntity[];
|
|
536
642
|
label: Record<string, string> | string;
|
|
537
643
|
};
|
|
644
|
+
/**
|
|
645
|
+
* A sort key returned by a sort function.
|
|
646
|
+
* - `number` — explicit order index (CSS `order`-like; lower comes first).
|
|
647
|
+
* - `string` — sorted lexicographically (`localeCompare`).
|
|
648
|
+
* - `undefined` — treated as `0`, i.e. keep the default position.
|
|
649
|
+
*
|
|
650
|
+
* Sorting is stable: elements with equal keys keep their original relative order,
|
|
651
|
+
* so anything you don't assign a key to stays where the default logic placed it.
|
|
652
|
+
* Avoid mixing numbers and strings in the same scope — numbers sort before strings.
|
|
653
|
+
*/
|
|
654
|
+
export type SidebarSortKey = number | string | undefined;
|
|
655
|
+
/**
|
|
656
|
+
* Context passed to sort functions. The tab id is implicit (it's the `sort` map key).
|
|
657
|
+
*/
|
|
658
|
+
export type SidebarSortContext = {
|
|
659
|
+
/** Current admin locale (i18n language) */
|
|
660
|
+
locale: string;
|
|
661
|
+
};
|
|
662
|
+
/**
|
|
663
|
+
* A group as seen by sort functions. Labels are passed raw (not translated) —
|
|
664
|
+
* translate via `ctx.locale` yourself if you need to.
|
|
665
|
+
*/
|
|
666
|
+
export type SortableGroup = {
|
|
667
|
+
/** Group entities, in their current (pre-sort) order */
|
|
668
|
+
entities: ExtendedEntity[];
|
|
669
|
+
/** True for the synthetic group holding ungrouped items (no label) */
|
|
670
|
+
isUngrouped: boolean;
|
|
671
|
+
/** Raw, non-translated group label */
|
|
672
|
+
label: LocalizedString;
|
|
673
|
+
};
|
|
674
|
+
/**
|
|
675
|
+
* Orders the top-level groups within a tab. Return a {@link SidebarSortKey}.
|
|
676
|
+
*
|
|
677
|
+
* When a `groups` function is set, ungrouped items are treated as individual
|
|
678
|
+
* single-item units, so you can interleave them between real groups.
|
|
679
|
+
*/
|
|
680
|
+
export type GroupSortFunction = (group: SortableGroup, ctx: SidebarSortContext) => SidebarSortKey;
|
|
681
|
+
/**
|
|
682
|
+
* Orders the entities inside a single group. Return a {@link SidebarSortKey}.
|
|
683
|
+
* Sorts only within the group — it cannot move an item to a different group.
|
|
684
|
+
*/
|
|
685
|
+
export type ItemSortFunction = (item: ExtendedEntity, group: SortableGroup, ctx: SidebarSortContext) => SidebarSortKey;
|
|
686
|
+
/**
|
|
687
|
+
* Per-tab sort configuration. Both functions are optional and run server-side.
|
|
688
|
+
*/
|
|
689
|
+
export type TabSortConfig = {
|
|
690
|
+
/** Orders the top-level groups within the tab */
|
|
691
|
+
groups?: GroupSortFunction;
|
|
692
|
+
/** Orders the entities inside each group */
|
|
693
|
+
items?: ItemSortFunction;
|
|
694
|
+
};
|
|
538
695
|
export {};
|
package/dist/types.js
CHANGED
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, PayloadRequest, 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 For 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// Access Control Types\n// ============================================\n\n/**\n * Access function for a tab or link in the tabs bar.\n * Return `false` to hide the item entirely (both the button and its content).\n */\nexport type TabAccessFunction = (args: {\n item: SidebarTab\n req: PayloadRequest\n}) => boolean | Promise<boolean>\n\n/**\n * Access function for a custom item inside a tab.\n * Return `false` to hide the item from the nav.\n */\nexport type ItemAccessFunction = (args: {\n item: SidebarTabItem\n req: PayloadRequest\n}) => boolean | Promise<boolean>\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 * Access control function. Called server-side with the current request.\n * Return `false` to hide this tab (button + content) entirely.\n */\n access?: TabAccessFunction\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 * Access control function. Called server-side with the current request.\n * Return `false` to hide this link entirely.\n */\n access?: TabAccessFunction\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 custom component rendered in the tabs bar (spacer, separator, badge, etc.).\n * Does not open any content — it's purely a visual slot in the tabs column.\n */\nexport type SidebarTabCustom = {\n /**\n * Access control function. Called server-side with the current request.\n * Return `false` to hide this item entirely.\n */\n access?: TabAccessFunction\n /**\n * Component to render. Receives `{ id }` plus any `clientProps` you pass.\n * Supports a plain string path or `{ path, clientProps }`.\n */\n component: SidebarComponent\n /** Unique identifier */\n id: string\n type: 'custom'\n}\n\n/**\n * Props passed to a custom tabs bar component (spacer, separator, etc.).\n */\nexport type CustomTabsBarComponentProps = {\n id: string\n}\n\n/**\n * A tab, link, or custom component in the sidebar tabs bar\n */\nexport type SidebarTab = SidebarTabContent | SidebarTabCustom | SidebarTabLink\n\ninterface BaseSidebarTabItem {\n /**\n * Access control function. Called server-side with the current request.\n * Return `false` to hide this item from the nav.\n */\n access?: ItemAccessFunction\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 React from 'react'\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 React from 'react'\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 React from 'react'\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 React from 'react'\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 afterNav from payload config (rendered after afterNavLinks) */\n afterNav?: ReactNode\n /** Rendered afterNavLinks from payload config */\n afterNavLinks?: ReactNode\n /** Content to show when no tabs are defined */\n allContent?: ReactNode\n /** Rendered beforeNav from payload config (rendered before beforeNavLinks) */\n beforeNav?: 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 * 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 // 'collections' | 'globals' are EntityType enum values from groupNavItems output; 'custom' is ours\n type: 'collections' | 'custom' | 'globals'\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":"AAykBA,WAGC"}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { icons, LucideIcon } from 'lucide-react'\nimport type { CollectionSlug, GlobalSlug, PayloadRequest, 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 For 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// Access Control Types\n// ============================================\n\n/**\n * Access function for a tab or link in the tabs bar.\n * Return `false` to hide the item entirely (both the button and its content).\n */\nexport type TabAccessFunction = (args: {\n item: SidebarTab\n req: PayloadRequest\n}) => boolean | Promise<boolean>\n\n/**\n * Access function for a custom item inside a tab.\n * Return `false` to hide the item from the nav.\n */\nexport type ItemAccessFunction = (args: {\n item: SidebarTabItem\n req: PayloadRequest\n}) => boolean | Promise<boolean>\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 */\nexport type 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 * Access control function. Called server-side with the current request.\n * Return `false` to hide this tab (button + content) entirely.\n */\n access?: TabAccessFunction\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 /**\n * Where to render this item in the tabs bar:\n * - `'top'` — with the main tabs at the top (default)\n * - `'bottom'` — pinned to the bottom, just above the actions (folders/settings/logout)\n *\n * @default 'top'\n */\n position?: 'bottom' | 'top'\n type: 'tab'\n} & TabIconConfig\n\ntype SidebarTabLinkBase = {\n /**\n * Access control function. Called server-side with the current request.\n * Return `false` to hide this link entirely.\n */\n access?: TabAccessFunction\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 /**\n * Where to render this item in the tabs bar:\n * - `'top'` — with the main tabs at the top (default)\n * - `'bottom'` — pinned to the bottom, just above the actions (folders/settings/logout)\n *\n * @default 'top'\n */\n position?: 'bottom' | 'top'\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 custom component rendered in the tabs bar (spacer, separator, badge, etc.).\n * Does not open any content — it's purely a visual slot in the tabs column.\n */\nexport type SidebarTabCustom = {\n /**\n * Access control function. Called server-side with the current request.\n * Return `false` to hide this item entirely.\n */\n access?: TabAccessFunction\n /**\n * Component to render. Receives `{ id }` plus any `clientProps` you pass.\n * Supports a plain string path or `{ path, clientProps }`.\n */\n component: SidebarComponent\n /** Unique identifier */\n id: string\n /**\n * Where to render this item in the tabs bar:\n * - `'top'` — with the main tabs at the top (default)\n * - `'bottom'` — pinned to the bottom, just above the actions (folders/settings/logout)\n *\n * @default 'top'\n */\n position?: 'bottom' | 'top'\n type: 'custom'\n}\n\n/**\n * Props passed to a custom tabs bar component (spacer, separator, etc.).\n */\nexport type CustomTabsBarComponentProps = {\n id: string\n}\n\n/**\n * Props passed to a per-item custom component set via `component` on a `customItems` entry.\n * Rendered in the nav content area in place of a default link row.\n *\n * Deliberately minimal — only the item's `slug`. Anything else should be passed via\n * `clientProps`. (For rich, shared link rendering use `customComponents.NavItem` instead.)\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomNavItemComponentProps } from '@veiag/payload-enhanced-sidebar'\n *\n * export const Banner: React.FC<CustomNavItemComponentProps> = ({ slug }) => (\n * <div id={`nav-${slug}`}>Custom content</div>\n * )\n * ```\n */\nexport type CustomNavItemComponentProps = {\n /** The item's slug from config */\n slug: string\n}\n\n/**\n * A tab, link, or custom component in the sidebar tabs bar\n */\nexport type SidebarTab = SidebarTabContent | SidebarTabCustom | SidebarTabLink\n\ninterface BaseSidebarTabItem {\n /**\n * Access control function. Called server-side with the current request.\n * Return `false` to hide this item from the nav.\n */\n access?: ItemAccessFunction\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 /**\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 component?: never\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 /** Display label */\n label: LocalizedString\n}\n\ninterface InternalHrefItem extends BaseSidebarTabItem {\n component?: never\n /** Link href (relative to admin route) */\n href: '' | `/${string}`\n /** Whether the link is external, without admin route prefix. */\n isExternal?: false\n /** Display label */\n label: LocalizedString\n}\n\n/**\n * A custom item that renders an arbitrary component in place of a link row.\n * The component is rendered server-side via Payload's component system, so both\n * server and client components are supported. It receives `CustomNavItemComponentProps`\n * (`{ slug }`) plus any `clientProps` you pass.\n *\n * Supports the same `group` / `position` placement rules as link items.\n */\ninterface CustomComponentItem extends BaseSidebarTabItem {\n /**\n * Component to render in place of a default link row.\n * Supports a plain string path or `{ path, clientProps }`.\n * Receives `{ slug }` plus any `clientProps`.\n */\n component: SidebarComponent\n href?: never\n isExternal?: never\n /**\n * Optional label. Unused for rendering — the component controls its own output —\n * but kept for parity with link items (e.g. if you read it from `clientProps`).\n */\n label?: LocalizedString\n}\n/**\n * Custom item inside a sidebar tab — either a link (`href`) or a custom component (`component`).\n */\nexport type SidebarTabItem = CustomComponentItem | 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 React from 'react'\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 React from 'react'\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 React from 'react'\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 React from 'react'\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 afterNav from payload config (rendered after afterNavLinks) */\n afterNav?: ReactNode\n /** Rendered afterNavLinks from payload config */\n afterNavLinks?: ReactNode\n /** Content to show when no tabs are defined */\n allContent?: ReactNode\n /** Rendered beforeNav from payload config (rendered before beforeNavLinks) */\n beforeNav?: 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 * Show logout button at the bottom of the tabs bar.\n * @default true\n */\n showLogout?: boolean\n\n /**\n * Per-tab control over the order of groups and items in the nav content area.\n * Keyed by tab `id`. Functions run server-side and are applied as a final pass\n * after the default ordering — so anything you don't assign a key to keeps its\n * default position. Has no effect on tabs not listed here.\n *\n * @example\n * ```typescript\n * sort: {\n * shop: {\n * groups: (group) => {\n * if (group.isUngrouped && group.entities[0]?.slug === 'banner') return -100\n * if (typeof group.label !== 'string' && group.label.en === 'Featured') return 0\n * },\n * items: (item, group) => {\n * if (item.type === 'custom-component') return 10 // after collections\n * },\n * },\n * }\n * ```\n */\n sort?: Record<string, TabSortConfig>\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 // 'collections' | 'globals' are EntityType enum values from groupNavItems output; 'custom' is ours\n type: 'collections' | 'custom' | 'globals'\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\n/**\n * A custom item that renders its own component instead of a link row.\n * Carries the component reference through to server-side rendering.\n */\ninterface CustomComponentExtendedEntity {\n component: SidebarComponent\n href?: never\n isExternal?: never\n label: Record<string, string> | string\n slug: string\n type: 'custom-component'\n}\n\nexport type ExtendedEntity =\n | CustomComponentExtendedEntity\n | ExternalExtendedEntity\n | InternalExtendedEntity\n\nexport type ExtendedGroup = {\n entities: ExtendedEntity[]\n label: Record<string, string> | string\n}\n\n// ============================================\n// Sort Types\n// ============================================\n\n/**\n * A sort key returned by a sort function.\n * - `number` — explicit order index (CSS `order`-like; lower comes first).\n * - `string` — sorted lexicographically (`localeCompare`).\n * - `undefined` — treated as `0`, i.e. keep the default position.\n *\n * Sorting is stable: elements with equal keys keep their original relative order,\n * so anything you don't assign a key to stays where the default logic placed it.\n * Avoid mixing numbers and strings in the same scope — numbers sort before strings.\n */\nexport type SidebarSortKey = number | string | undefined\n\n/**\n * Context passed to sort functions. The tab id is implicit (it's the `sort` map key).\n */\nexport type SidebarSortContext = {\n /** Current admin locale (i18n language) */\n locale: string\n}\n\n/**\n * A group as seen by sort functions. Labels are passed raw (not translated) —\n * translate via `ctx.locale` yourself if you need to.\n */\nexport type SortableGroup = {\n /** Group entities, in their current (pre-sort) order */\n entities: ExtendedEntity[]\n /** True for the synthetic group holding ungrouped items (no label) */\n isUngrouped: boolean\n /** Raw, non-translated group label */\n label: LocalizedString\n}\n\n/**\n * Orders the top-level groups within a tab. Return a {@link SidebarSortKey}.\n *\n * When a `groups` function is set, ungrouped items are treated as individual\n * single-item units, so you can interleave them between real groups.\n */\nexport type GroupSortFunction = (group: SortableGroup, ctx: SidebarSortContext) => SidebarSortKey\n\n/**\n * Orders the entities inside a single group. Return a {@link SidebarSortKey}.\n * Sorts only within the group — it cannot move an item to a different group.\n */\nexport type ItemSortFunction = (\n item: ExtendedEntity,\n group: SortableGroup,\n ctx: SidebarSortContext,\n) => SidebarSortKey\n\n/**\n * Per-tab sort configuration. Both functions are optional and run server-side.\n */\nexport type TabSortConfig = {\n /** Orders the top-level groups within the tab */\n groups?: GroupSortFunction\n /** Orders the entities inside each group */\n items?: ItemSortFunction\n}\n"],"names":[],"mappings":"AAsvBA;;CAEC,GACD,WAKC"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EnhancedSidebarConfig, LocalizedString, SidebarComponent } from '../types.js';
|
|
1
|
+
import type { EnhancedSidebarConfig, ExtendedGroup, LocalizedString, SidebarComponent, TabSortConfig } from '../types.js';
|
|
2
2
|
export declare const convertSlugToTitle: (slug: string) => string;
|
|
3
3
|
/**
|
|
4
4
|
* Extracts path and clientProps from a SidebarComponent (string or object).
|
|
@@ -12,4 +12,13 @@ export declare const resolveSidebarComponent: (component: SidebarComponent) => {
|
|
|
12
12
|
* before passing it to client components.
|
|
13
13
|
*/
|
|
14
14
|
export declare const sanitizeSidebarConfig: (config: EnhancedSidebarConfig) => EnhancedSidebarConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Applies a tab's sort configuration to its computed groups, as a final pass
|
|
17
|
+
* over the default ordering. Returns the input untouched when no sort is given.
|
|
18
|
+
*
|
|
19
|
+
* - When `groups` is set, multi-item ungrouped blocks are split into individual
|
|
20
|
+
* single-item units so they can be interleaved between real groups.
|
|
21
|
+
* - `items` sorts entities within each group only.
|
|
22
|
+
*/
|
|
23
|
+
export declare const sortTabGroups: (groups: ExtendedGroup[], sort: TabSortConfig | undefined, locale: string) => ExtendedGroup[];
|
|
15
24
|
export declare const extractLocalizedValue: (value: LocalizedString | undefined, locale: string, fallbackSlug?: string) => string;
|
package/dist/utils/index.js
CHANGED
|
@@ -18,9 +18,12 @@ export const convertSlugToTitle = (slug)=>{
|
|
|
18
18
|
/**
|
|
19
19
|
* Strips all non-serializable values (functions) from the sidebar config
|
|
20
20
|
* before passing it to client components.
|
|
21
|
-
*/ export const sanitizeSidebarConfig = (config)=>
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
*/ export const sanitizeSidebarConfig = (config)=>{
|
|
22
|
+
// `sort` holds server-side functions — drop it entirely before sending to the client.
|
|
23
|
+
const { sort: _sort, ...rest } = config;
|
|
24
|
+
return {
|
|
25
|
+
...rest,
|
|
26
|
+
tabs: rest.tabs?.map((tab)=>{
|
|
24
27
|
const { access: _, ...tabRest } = tab;
|
|
25
28
|
if (tabRest.type === 'tab' && tabRest.customItems) {
|
|
26
29
|
return {
|
|
@@ -30,7 +33,93 @@ export const convertSlugToTitle = (slug)=>{
|
|
|
30
33
|
}
|
|
31
34
|
return tabRest;
|
|
32
35
|
})
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Compares two sort keys. Numbers sort ascending and before strings;
|
|
40
|
+
* strings sort via `localeCompare`. `undefined` is treated as `0`.
|
|
41
|
+
*/ const compareSortKeys = (a, b)=>{
|
|
42
|
+
const ka = a ?? 0;
|
|
43
|
+
const kb = b ?? 0;
|
|
44
|
+
const aIsNum = typeof ka === 'number';
|
|
45
|
+
const bIsNum = typeof kb === 'number';
|
|
46
|
+
if (aIsNum && bIsNum) {
|
|
47
|
+
return ka - kb;
|
|
48
|
+
}
|
|
49
|
+
if (aIsNum) {
|
|
50
|
+
return -1;
|
|
51
|
+
}
|
|
52
|
+
if (bIsNum) {
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
return String(ka).localeCompare(String(kb));
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Stable sort by a key function (decorate–sort–undecorate so equal keys keep
|
|
59
|
+
* their original relative order).
|
|
60
|
+
*/ const stableSortBy = (arr, keyFn)=>arr.map((item, index)=>({
|
|
61
|
+
index,
|
|
62
|
+
item,
|
|
63
|
+
key: keyFn(item)
|
|
64
|
+
})).sort((a, b)=>compareSortKeys(a.key, b.key) || a.index - b.index).map(({ item })=>item);
|
|
65
|
+
const isUngroupedGroup = (group)=>!group.label || typeof group.label === 'string' && group.label === '';
|
|
66
|
+
const toSortableGroup = (group)=>({
|
|
67
|
+
entities: group.entities,
|
|
68
|
+
isUngrouped: isUngroupedGroup(group),
|
|
69
|
+
label: group.label
|
|
33
70
|
});
|
|
71
|
+
/**
|
|
72
|
+
* Applies a tab's sort configuration to its computed groups, as a final pass
|
|
73
|
+
* over the default ordering. Returns the input untouched when no sort is given.
|
|
74
|
+
*
|
|
75
|
+
* - When `groups` is set, multi-item ungrouped blocks are split into individual
|
|
76
|
+
* single-item units so they can be interleaved between real groups.
|
|
77
|
+
* - `items` sorts entities within each group only.
|
|
78
|
+
*/ export const sortTabGroups = (groups, sort, locale)=>{
|
|
79
|
+
if (!sort || !sort.groups && !sort.items) {
|
|
80
|
+
return groups;
|
|
81
|
+
}
|
|
82
|
+
const ctx = {
|
|
83
|
+
locale
|
|
84
|
+
};
|
|
85
|
+
let result = groups;
|
|
86
|
+
// Split ungrouped blocks into single-item units so each can be ordered independently.
|
|
87
|
+
if (sort.groups) {
|
|
88
|
+
const expanded = [];
|
|
89
|
+
for (const group of result){
|
|
90
|
+
if (isUngroupedGroup(group) && group.entities.length > 1) {
|
|
91
|
+
for (const entity of group.entities){
|
|
92
|
+
expanded.push({
|
|
93
|
+
entities: [
|
|
94
|
+
entity
|
|
95
|
+
],
|
|
96
|
+
label: ''
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
expanded.push(group);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
result = expanded;
|
|
104
|
+
}
|
|
105
|
+
// Sort entities within each group.
|
|
106
|
+
if (sort.items) {
|
|
107
|
+
const itemSort = sort.items;
|
|
108
|
+
result = result.map((group)=>{
|
|
109
|
+
const sortableGroup = toSortableGroup(group);
|
|
110
|
+
return {
|
|
111
|
+
...group,
|
|
112
|
+
entities: stableSortBy(group.entities, (entity)=>itemSort(entity, sortableGroup, ctx))
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// Sort the top-level groups.
|
|
117
|
+
if (sort.groups) {
|
|
118
|
+
const groupSort = sort.groups;
|
|
119
|
+
result = stableSortBy(result, (group)=>groupSort(toSortableGroup(group), ctx));
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
};
|
|
34
123
|
export const extractLocalizedValue = (value, locale, fallbackSlug)=>{
|
|
35
124
|
if (!value) {
|
|
36
125
|
return fallbackSlug ? convertSlugToTitle(fallbackSlug) : '';
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["import type {\n EnhancedSidebarConfig,\n ExtendedGroup,\n LocalizedString,\n SidebarComponent,\n SidebarSortKey,\n SortableGroup,\n TabSortConfig,\n} from '../types.js'\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\n/**\n * Strips all non-serializable values (functions) from the sidebar config\n * before passing it to client components.\n */\nexport const sanitizeSidebarConfig = (config: EnhancedSidebarConfig): EnhancedSidebarConfig => {\n // `sort` holds server-side functions — drop it entirely before sending to the client.\n const { sort: _sort, ...rest } = config\n return {\n ...rest,\n tabs: rest.tabs?.map((tab) => {\n const { access: _, ...tabRest } = tab\n if (tabRest.type === 'tab' && tabRest.customItems) {\n return {\n ...tabRest,\n customItems: tabRest.customItems.map(({ access: __, ...item }) => item),\n }\n }\n return tabRest\n }),\n }\n}\n\n/**\n * Compares two sort keys. Numbers sort ascending and before strings;\n * strings sort via `localeCompare`. `undefined` is treated as `0`.\n */\nconst compareSortKeys = (a: SidebarSortKey, b: SidebarSortKey): number => {\n const ka = a ?? 0\n const kb = b ?? 0\n const aIsNum = typeof ka === 'number'\n const bIsNum = typeof kb === 'number'\n if (aIsNum && bIsNum) {\n return ka - kb\n }\n if (aIsNum) {\n return -1\n }\n if (bIsNum) {\n return 1\n }\n return String(ka).localeCompare(String(kb))\n}\n\n/**\n * Stable sort by a key function (decorate–sort–undecorate so equal keys keep\n * their original relative order).\n */\nconst stableSortBy = <T>(arr: T[], keyFn: (item: T) => SidebarSortKey): T[] =>\n arr\n .map((item, index) => ({ index, item, key: keyFn(item) }))\n .sort((a, b) => compareSortKeys(a.key, b.key) || a.index - b.index)\n .map(({ item }) => item)\n\nconst isUngroupedGroup = (group: ExtendedGroup): boolean =>\n !group.label || (typeof group.label === 'string' && group.label === '')\n\nconst toSortableGroup = (group: ExtendedGroup): SortableGroup => ({\n entities: group.entities,\n isUngrouped: isUngroupedGroup(group),\n label: group.label,\n})\n\n/**\n * Applies a tab's sort configuration to its computed groups, as a final pass\n * over the default ordering. Returns the input untouched when no sort is given.\n *\n * - When `groups` is set, multi-item ungrouped blocks are split into individual\n * single-item units so they can be interleaved between real groups.\n * - `items` sorts entities within each group only.\n */\nexport const sortTabGroups = (\n groups: ExtendedGroup[],\n sort: TabSortConfig | undefined,\n locale: string,\n): ExtendedGroup[] => {\n if (!sort || (!sort.groups && !sort.items)) {\n return groups\n }\n\n const ctx = { locale }\n let result = groups\n\n // Split ungrouped blocks into single-item units so each can be ordered independently.\n if (sort.groups) {\n const expanded: ExtendedGroup[] = []\n for (const group of result) {\n if (isUngroupedGroup(group) && group.entities.length > 1) {\n for (const entity of group.entities) {\n expanded.push({ entities: [entity], label: '' })\n }\n } else {\n expanded.push(group)\n }\n }\n result = expanded\n }\n\n // Sort entities within each group.\n if (sort.items) {\n const itemSort = sort.items\n result = result.map((group) => {\n const sortableGroup = toSortableGroup(group)\n return {\n ...group,\n entities: stableSortBy(group.entities, (entity) =>\n itemSort(entity, sortableGroup, ctx),\n ),\n }\n })\n }\n\n // Sort the top-level groups.\n if (sort.groups) {\n const groupSort = sort.groups\n result = stableSortBy(result, (group) => groupSort(toSortableGroup(group), ctx))\n }\n\n return result\n}\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","sanitizeSidebarConfig","config","sort","_sort","rest","tabs","map","tab","access","_","tabRest","type","customItems","__","item","compareSortKeys","a","b","ka","kb","aIsNum","bIsNum","String","localeCompare","stableSortBy","arr","keyFn","index","key","isUngroupedGroup","group","label","toSortableGroup","entities","isUngrouped","sortTabGroups","groups","locale","items","ctx","result","expanded","length","entity","push","itemSort","sortableGroup","groupSort","extractLocalizedValue","value","fallbackSlug","Object","values"],"mappings":"AAUA,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;;;CAGC,GACD,OAAO,MAAMC,wBAAwB,CAACC;IACpC,sFAAsF;IACtF,MAAM,EAAEC,MAAMC,KAAK,EAAE,GAAGC,MAAM,GAAGH;IACjC,OAAO;QACL,GAAGG,IAAI;QACPC,MAAMD,KAAKC,IAAI,EAAEC,IAAI,CAACC;YACpB,MAAM,EAAEC,QAAQC,CAAC,EAAE,GAAGC,SAAS,GAAGH;YAClC,IAAIG,QAAQC,IAAI,KAAK,SAASD,QAAQE,WAAW,EAAE;gBACjD,OAAO;oBACL,GAAGF,OAAO;oBACVE,aAAaF,QAAQE,WAAW,CAACN,GAAG,CAAC,CAAC,EAAEE,QAAQK,EAAE,EAAE,GAAGC,MAAM,GAAKA;gBACpE;YACF;YACA,OAAOJ;QACT;IACF;AACF,EAAC;AAED;;;CAGC,GACD,MAAMK,kBAAkB,CAACC,GAAmBC;IAC1C,MAAMC,KAAKF,KAAK;IAChB,MAAMG,KAAKF,KAAK;IAChB,MAAMG,SAAS,OAAOF,OAAO;IAC7B,MAAMG,SAAS,OAAOF,OAAO;IAC7B,IAAIC,UAAUC,QAAQ;QACpB,OAAOH,KAAKC;IACd;IACA,IAAIC,QAAQ;QACV,OAAO,CAAC;IACV;IACA,IAAIC,QAAQ;QACV,OAAO;IACT;IACA,OAAOC,OAAOJ,IAAIK,aAAa,CAACD,OAAOH;AACzC;AAEA;;;CAGC,GACD,MAAMK,eAAe,CAAIC,KAAUC,QACjCD,IACGnB,GAAG,CAAC,CAACQ,MAAMa,QAAW,CAAA;YAAEA;YAAOb;YAAMc,KAAKF,MAAMZ;QAAM,CAAA,GACtDZ,IAAI,CAAC,CAACc,GAAGC,IAAMF,gBAAgBC,EAAEY,GAAG,EAAEX,EAAEW,GAAG,KAAKZ,EAAEW,KAAK,GAAGV,EAAEU,KAAK,EACjErB,GAAG,CAAC,CAAC,EAAEQ,IAAI,EAAE,GAAKA;AAEvB,MAAMe,mBAAmB,CAACC,QACxB,CAACA,MAAMC,KAAK,IAAK,OAAOD,MAAMC,KAAK,KAAK,YAAYD,MAAMC,KAAK,KAAK;AAEtE,MAAMC,kBAAkB,CAACF,QAAyC,CAAA;QAChEG,UAAUH,MAAMG,QAAQ;QACxBC,aAAaL,iBAAiBC;QAC9BC,OAAOD,MAAMC,KAAK;IACpB,CAAA;AAEA;;;;;;;CAOC,GACD,OAAO,MAAMI,gBAAgB,CAC3BC,QACAlC,MACAmC;IAEA,IAAI,CAACnC,QAAS,CAACA,KAAKkC,MAAM,IAAI,CAAClC,KAAKoC,KAAK,EAAG;QAC1C,OAAOF;IACT;IAEA,MAAMG,MAAM;QAAEF;IAAO;IACrB,IAAIG,SAASJ;IAEb,sFAAsF;IACtF,IAAIlC,KAAKkC,MAAM,EAAE;QACf,MAAMK,WAA4B,EAAE;QACpC,KAAK,MAAMX,SAASU,OAAQ;YAC1B,IAAIX,iBAAiBC,UAAUA,MAAMG,QAAQ,CAACS,MAAM,GAAG,GAAG;gBACxD,KAAK,MAAMC,UAAUb,MAAMG,QAAQ,CAAE;oBACnCQ,SAASG,IAAI,CAAC;wBAAEX,UAAU;4BAACU;yBAAO;wBAAEZ,OAAO;oBAAG;gBAChD;YACF,OAAO;gBACLU,SAASG,IAAI,CAACd;YAChB;QACF;QACAU,SAASC;IACX;IAEA,mCAAmC;IACnC,IAAIvC,KAAKoC,KAAK,EAAE;QACd,MAAMO,WAAW3C,KAAKoC,KAAK;QAC3BE,SAASA,OAAOlC,GAAG,CAAC,CAACwB;YACnB,MAAMgB,gBAAgBd,gBAAgBF;YACtC,OAAO;gBACL,GAAGA,KAAK;gBACRG,UAAUT,aAAaM,MAAMG,QAAQ,EAAE,CAACU,SACtCE,SAASF,QAAQG,eAAeP;YAEpC;QACF;IACF;IAEA,6BAA6B;IAC7B,IAAIrC,KAAKkC,MAAM,EAAE;QACf,MAAMW,YAAY7C,KAAKkC,MAAM;QAC7BI,SAAShB,aAAagB,QAAQ,CAACV,QAAUiB,UAAUf,gBAAgBF,QAAQS;IAC7E;IAEA,OAAOC;AACT,EAAC;AAGD,OAAO,MAAMQ,wBAAwB,CACnCC,OACAZ,QACAa;IAEA,IAAI,CAACD,OAAO;QACV,OAAOC,eAAe3D,mBAAmB2D,gBAAgB;IAC3D;IACA,IAAI,OAAOD,UAAU,UAAU;QAC7B,OAAOA;IACT;IACA,OAAOA,KAAK,CAACZ,OAAO,IAAIc,OAAOC,MAAM,CAACH,MAAM,CAAC,EAAE,IAAKC,CAAAA,eAAe3D,mBAAmB2D,gBAAgB,EAAC;AACzG,EAAC"}
|
package/package.json
CHANGED