@veiag/payload-enhanced-sidebar 0.1.0 → 0.1.2
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 +18 -2
- package/dist/components/EnhancedSidebar/SidebarContent.d.ts +1 -0
- package/dist/components/EnhancedSidebar/SidebarContent.js +15 -20
- package/dist/components/EnhancedSidebar/SidebarContent.js.map +1 -1
- package/dist/components/EnhancedSidebar/TabsBar/index.js +2 -1
- package/dist/components/EnhancedSidebar/TabsBar/index.js.map +1 -1
- package/dist/components/EnhancedSidebar/index.client.js +14 -8
- package/dist/components/EnhancedSidebar/index.client.js.map +1 -1
- package/dist/components/EnhancedSidebar/index.js +9 -0
- package/dist/components/EnhancedSidebar/index.js.map +1 -1
- package/dist/types.d.ts +26 -9
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/EnhancedSidebar/CustomTabContent.d.ts +0 -6
- package/dist/components/EnhancedSidebar/CustomTabContent.js +0 -35
- package/dist/components/EnhancedSidebar/CustomTabContent.js.map +0 -1
package/README.md
CHANGED
|
@@ -73,6 +73,15 @@ export default buildConfig({
|
|
|
73
73
|
label: { en: 'Content', uk: 'Контент' },
|
|
74
74
|
collections: ['posts', 'pages', 'categories'],
|
|
75
75
|
},
|
|
76
|
+
// Link to external documentation
|
|
77
|
+
{
|
|
78
|
+
id: 'docs',
|
|
79
|
+
type: 'link',
|
|
80
|
+
href: 'https://payloadcms.com/',
|
|
81
|
+
icon: 'BookOpen',
|
|
82
|
+
isExternal: true,
|
|
83
|
+
label: { en: 'Documentation', uk: 'Документація' },
|
|
84
|
+
},
|
|
76
85
|
// E-commerce tab with custom items
|
|
77
86
|
{
|
|
78
87
|
id: 'ecommerce',
|
|
@@ -104,6 +113,12 @@ export default buildConfig({
|
|
|
104
113
|
label: { en: 'API Keys', uk: 'API Ключі' },
|
|
105
114
|
// No group - will appear at the bottom
|
|
106
115
|
},
|
|
116
|
+
{
|
|
117
|
+
slug:'external-link',
|
|
118
|
+
href: 'https://example.com',
|
|
119
|
+
isExternal: true,
|
|
120
|
+
label: { en: 'External Link', uk: 'Зовнішнє Посилання'}
|
|
121
|
+
}
|
|
107
122
|
],
|
|
108
123
|
},
|
|
109
124
|
],
|
|
@@ -146,7 +161,8 @@ Array of tabs and links to show in the sidebar.
|
|
|
146
161
|
| `type` | `'link'` | Yes | Link type |
|
|
147
162
|
| `icon` | `string` | Yes | Lucide icon name |
|
|
148
163
|
| `label` | `LocalizedString` | Yes | Link tooltip/label |
|
|
149
|
-
| `href` | `string` | Yes | URL
|
|
164
|
+
| `href` | `string` | Yes | URL |
|
|
165
|
+
| `isExternal` | `boolean` | No | If true, `href` is absolute URL, if not, `href` is relative to admin route |
|
|
150
166
|
|
|
151
167
|
### `customItems`
|
|
152
168
|
|
|
@@ -155,7 +171,7 @@ Custom items can be added to any tab:
|
|
|
155
171
|
```typescript
|
|
156
172
|
{
|
|
157
173
|
slug: 'unique-slug', // Required: unique identifier
|
|
158
|
-
href: '/path', // Required: URL
|
|
174
|
+
href: '/path', // Required: URL
|
|
159
175
|
label: { en: 'Label' }, // Required: display label
|
|
160
176
|
group: { en: 'Group Name' }, // Optional: merge into existing group or create new
|
|
161
177
|
isExternal: true, // Optional: if true, href is absolute URL
|
|
@@ -1,33 +1,25 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useTranslation } from '@payloadcms/ui';
|
|
4
|
-
import React, {
|
|
4
|
+
import React, { useMemo, useState } from 'react';
|
|
5
5
|
import { extractLocalizedValue } from '../../utils';
|
|
6
6
|
import { EnhancedSidebarClient } from './index.client';
|
|
7
7
|
import { SidebarWrapper } from './SidebarWrapper';
|
|
8
8
|
import { TabsBar } from './TabsBar';
|
|
9
9
|
const baseClass = 'enhanced-sidebar';
|
|
10
|
-
const
|
|
11
|
-
|
|
10
|
+
const COOKIE_KEY = 'payload-enhanced-sidebar-active-tab';
|
|
11
|
+
const setTabCookie = (tabId)=>{
|
|
12
|
+
document.cookie = `${COOKIE_KEY}=${tabId}; path=/; max-age=31536000; SameSite=Lax`;
|
|
13
|
+
};
|
|
14
|
+
export const SidebarContent = ({ afterNavLinks, beforeNavLinks, groups, initialActiveTabId, navPreferences, sidebarConfig })=>{
|
|
12
15
|
const { i18n } = useTranslation();
|
|
13
16
|
const currentLang = i18n.language;
|
|
14
17
|
const tabs = sidebarConfig.tabs?.filter((t)=>t.type === 'tab') ?? [];
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
21
|
-
if (stored && tabs.some((t)=>t.id === stored)) {
|
|
22
|
-
setActiveTabId(stored);
|
|
23
|
-
}
|
|
24
|
-
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
25
|
-
// Persist to localStorage on change
|
|
26
|
-
useEffect(()=>{
|
|
27
|
-
localStorage.setItem(STORAGE_KEY, activeTabId);
|
|
28
|
-
}, [
|
|
29
|
-
activeTabId
|
|
30
|
-
]);
|
|
18
|
+
const [activeTabId, setActiveTabId] = useState(initialActiveTabId);
|
|
19
|
+
const handleTabChange = (tabId)=>{
|
|
20
|
+
setActiveTabId(tabId);
|
|
21
|
+
setTabCookie(tabId);
|
|
22
|
+
};
|
|
31
23
|
const activeTab = tabs.find((tab)=>tab.id === activeTabId);
|
|
32
24
|
// Build groups for the active tab
|
|
33
25
|
const filteredGroups = useMemo(()=>{
|
|
@@ -72,6 +64,7 @@ export const SidebarContent = ({ afterNavLinks, beforeNavLinks, groups, navPrefe
|
|
|
72
64
|
slug: item.slug,
|
|
73
65
|
type: 'custom',
|
|
74
66
|
href: item.href,
|
|
67
|
+
isExternal: item.isExternal,
|
|
75
68
|
label: item.label
|
|
76
69
|
});
|
|
77
70
|
} else {
|
|
@@ -82,6 +75,7 @@ export const SidebarContent = ({ afterNavLinks, beforeNavLinks, groups, navPrefe
|
|
|
82
75
|
slug: item.slug,
|
|
83
76
|
type: 'custom',
|
|
84
77
|
href: item.href,
|
|
78
|
+
isExternal: item.isExternal,
|
|
85
79
|
label: item.label
|
|
86
80
|
}
|
|
87
81
|
],
|
|
@@ -99,6 +93,7 @@ export const SidebarContent = ({ afterNavLinks, beforeNavLinks, groups, navPrefe
|
|
|
99
93
|
slug: item.slug,
|
|
100
94
|
type: 'custom',
|
|
101
95
|
href: item.href,
|
|
96
|
+
isExternal: item.isExternal,
|
|
102
97
|
label: item.label
|
|
103
98
|
})),
|
|
104
99
|
label: ''
|
|
@@ -116,7 +111,7 @@ export const SidebarContent = ({ afterNavLinks, beforeNavLinks, groups, navPrefe
|
|
|
116
111
|
children: [
|
|
117
112
|
/*#__PURE__*/ _jsx(TabsBar, {
|
|
118
113
|
activeTabId: activeTabId,
|
|
119
|
-
onTabChange:
|
|
114
|
+
onTabChange: handleTabChange,
|
|
120
115
|
sidebarConfig: sidebarConfig
|
|
121
116
|
}),
|
|
122
117
|
/*#__PURE__*/ _jsx("nav", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/EnhancedSidebar/SidebarContent.tsx"],"sourcesContent":["'use client'\nimport type { NavPreferences } from 'payload'\n\nimport { useTranslation } from '@payloadcms/ui'\nimport React, {
|
|
1
|
+
{"version":3,"sources":["../../../src/components/EnhancedSidebar/SidebarContent.tsx"],"sourcesContent":["'use client'\nimport type { NavPreferences } from 'payload'\n\nimport { useTranslation } from '@payloadcms/ui'\nimport React, { useMemo, useState } from 'react'\n\nimport type {\n EnhancedSidebarConfig,\n ExtendedEntity,\n ExtendedGroup,\n SidebarTabContent as SidebarTabContentType,\n SidebarTabItem,\n} from '../../types'\n\nimport { extractLocalizedValue } from '../../utils'\nimport { EnhancedSidebarClient } from './index.client'\nimport { SidebarWrapper } from './SidebarWrapper'\nimport { TabsBar } from './TabsBar'\n\nconst baseClass = 'enhanced-sidebar'\n\nexport type SidebarContentProps = {\n afterNavLinks?: React.ReactNode\n beforeNavLinks?: React.ReactNode\n groups: ExtendedGroup[]\n initialActiveTabId: string\n navPreferences: NavPreferences | null\n sidebarConfig: EnhancedSidebarConfig\n}\n\nconst COOKIE_KEY = 'payload-enhanced-sidebar-active-tab'\n\nconst setTabCookie = (tabId: string) => {\n document.cookie = `${COOKIE_KEY}=${tabId}; path=/; max-age=31536000; SameSite=Lax`\n}\n\nexport const SidebarContent: React.FC<SidebarContentProps> = ({\n afterNavLinks,\n beforeNavLinks,\n groups,\n initialActiveTabId,\n navPreferences,\n sidebarConfig,\n}) => {\n const { i18n } = useTranslation()\n const currentLang = i18n.language\n\n const tabs = sidebarConfig.tabs?.filter((t): t is SidebarTabContentType => t.type === 'tab') ?? []\n\n const [activeTabId, setActiveTabId] = useState(initialActiveTabId)\n\n const handleTabChange = (tabId: string) => {\n setActiveTabId(tabId)\n setTabCookie(tabId)\n }\n\n const activeTab = tabs.find((tab) => tab.id === activeTabId)\n\n // Build groups for the active tab\n const filteredGroups = useMemo(() => {\n if (!activeTab) {\n return groups\n }\n\n const { collections: tabCollections, customItems, globals: tabGlobals } = activeTab\n\n // If no specific collections/globals defined, show all\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 // Merge custom items into groups\n if (customItems && customItems.length > 0) {\n const ungroupedItems: SidebarTabItem[] = []\n\n for (const item of customItems) {\n if (item.group) {\n // Get localized group name for comparison\n const itemGroupLabel = extractLocalizedValue(item.group, currentLang)\n\n // Find existing group by comparing localized labels\n const existingGroup = result.find((g) => {\n const groupLabel = extractLocalizedValue(g.label, currentLang)\n return groupLabel === itemGroupLabel\n })\n\n if (existingGroup) {\n existingGroup.entities.push({\n slug: item.slug,\n type: 'custom',\n href: item.href,\n isExternal: item.isExternal,\n label: item.label,\n } as ExtendedEntity)\n } else {\n // Create new group\n result.push({\n entities: [\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 label: item.group,\n })\n }\n } else {\n ungroupedItems.push(item)\n }\n }\n\n // Add ungrouped items at the end\n if (ungroupedItems.length > 0) {\n result.push({\n entities: ungroupedItems.map((item) => ({\n slug: item.slug,\n type: 'custom',\n href: item.href,\n isExternal: item.isExternal,\n label: item.label,\n })) as ExtendedEntity[],\n label: '',\n })\n }\n }\n\n return result\n }, [activeTab, groups, currentLang])\n\n return (\n <SidebarWrapper baseClass={baseClass}>\n <TabsBar\n activeTabId={activeTabId}\n onTabChange={handleTabChange}\n sidebarConfig={sidebarConfig}\n />\n <nav className={`${baseClass}__content`}>\n <div className={`${baseClass}__content-scroll`}>\n {beforeNavLinks}\n <EnhancedSidebarClient groups={filteredGroups} navPreferences={navPreferences} />\n {afterNavLinks}\n </div>\n </nav>\n </SidebarWrapper>\n )\n}\n"],"names":["useTranslation","React","useMemo","useState","extractLocalizedValue","EnhancedSidebarClient","SidebarWrapper","TabsBar","baseClass","COOKIE_KEY","setTabCookie","tabId","document","cookie","SidebarContent","afterNavLinks","beforeNavLinks","groups","initialActiveTabId","navPreferences","sidebarConfig","i18n","currentLang","language","tabs","filter","t","type","activeTabId","setActiveTabId","handleTabChange","activeTab","find","tab","id","filteredGroups","collections","tabCollections","customItems","globals","tabGlobals","showAll","allowedSlugs","Set","result","map","g","entities","size","group","entity","has","slug","length","ungroupedItems","item","itemGroupLabel","existingGroup","groupLabel","label","push","href","isExternal","onTabChange","nav","className","div"],"mappings":"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"}
|
|
@@ -29,7 +29,7 @@ export const TabsBar = ({ activeTabId, onTabChange, sidebarConfig })=>{
|
|
|
29
29
|
};
|
|
30
30
|
const renderLink = (link)=>{
|
|
31
31
|
const label = getTranslation(link.label, i18n);
|
|
32
|
-
const href = formatAdminURL({
|
|
32
|
+
const href = link.isExternal ? link.href : formatAdminURL({
|
|
33
33
|
adminRoute,
|
|
34
34
|
path: link.href
|
|
35
35
|
});
|
|
@@ -38,6 +38,7 @@ export const TabsBar = ({ activeTabId, onTabChange, sidebarConfig })=>{
|
|
|
38
38
|
return /*#__PURE__*/ _jsx(Link, {
|
|
39
39
|
className: `${tabsBaseClass}__link ${isActive ? `${tabsBaseClass}__link--active` : ''}`,
|
|
40
40
|
href: href,
|
|
41
|
+
target: link.isExternal ? '_blank' : undefined,
|
|
41
42
|
title: label,
|
|
42
43
|
children: /*#__PURE__*/ _jsx(Icon, {
|
|
43
44
|
name: link.icon,
|
|
@@ -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'\nimport { formatAdminURL } from 'payload/shared'\nimport React from 'react'\n\nimport type { EnhancedSidebarConfig, SidebarTabContent, SidebarTabLink } from '../../../types'\n\nimport { Icon } from '../Icon'\nimport './index.scss'\n\nconst tabsBaseClass = 'tabs-bar'\n\nexport type TabsBarProps = {\n activeTabId: string\n onTabChange: (tabId: string) => void\n sidebarConfig: EnhancedSidebarConfig\n}\n\nexport const TabsBar: React.FC<TabsBarProps> = ({ activeTabId, onTabChange, sidebarConfig }) => {\n const { i18n } = useTranslation()\n const pathname = usePathname()\n\n const {\n config: {\n admin: {\n routes: { logout: logoutRoute },\n },\n routes: { admin: adminRoute },\n },\n } = useConfig()\n\n const showLogout = sidebarConfig.showLogout !== false\n\n const renderTab = (tab: SidebarTabContent) => {\n const label = getTranslation(tab.label, i18n)\n const isActive = activeTabId === tab.id\n\n return (\n <button\n className={`${tabsBaseClass}__tab ${isActive ? `${tabsBaseClass}__tab--active` : ''}`}\n key={tab.id}\n onClick={() => onTabChange(tab.id)}\n title={label}\n type=\"button\"\n >\n <Icon name={tab.icon} size={20} />\n </button>\n )\n }\n\n const renderLink = (link: SidebarTabLink) => {\n const label = getTranslation(link.label, i18n)\n const href = formatAdminURL({ adminRoute, path: link.href
|
|
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'\nimport { formatAdminURL } from 'payload/shared'\nimport React from 'react'\n\nimport type { EnhancedSidebarConfig, SidebarTabContent, SidebarTabLink } from '../../../types'\n\nimport { Icon } from '../Icon'\nimport './index.scss'\n\nconst tabsBaseClass = 'tabs-bar'\n\nexport type TabsBarProps = {\n activeTabId: string\n onTabChange: (tabId: string) => void\n sidebarConfig: EnhancedSidebarConfig\n}\n\nexport const TabsBar: React.FC<TabsBarProps> = ({ activeTabId, onTabChange, sidebarConfig }) => {\n const { i18n } = useTranslation()\n const pathname = usePathname()\n\n const {\n config: {\n admin: {\n routes: { logout: logoutRoute },\n },\n routes: { admin: adminRoute },\n },\n } = useConfig()\n\n const showLogout = sidebarConfig.showLogout !== false\n\n const renderTab = (tab: SidebarTabContent) => {\n const label = getTranslation(tab.label, i18n)\n const isActive = activeTabId === tab.id\n\n return (\n <button\n className={`${tabsBaseClass}__tab ${isActive ? `${tabsBaseClass}__tab--active` : ''}`}\n key={tab.id}\n onClick={() => onTabChange(tab.id)}\n title={label}\n type=\"button\"\n >\n <Icon name={tab.icon} size={20} />\n </button>\n )\n }\n\n const renderLink = (link: SidebarTabLink) => {\n const label = getTranslation(link.label, i18n)\n const href = link.isExternal ? link.href : formatAdminURL({ adminRoute, path: link.href })\n\n // Check if this link is active\n const isActive = pathname === href || (link.href === '/' && pathname === adminRoute)\n\n return (\n <Link\n className={`${tabsBaseClass}__link ${isActive ? `${tabsBaseClass}__link--active` : ''}`}\n href={href}\n key={link.id}\n target={link.isExternal ? '_blank' : undefined}\n title={label}\n >\n <Icon name={link.icon} size={20} />\n </Link>\n )\n }\n\n const renderTabItem = (item: SidebarTabContent | SidebarTabLink) => {\n if (item.type === 'tab') {\n return renderTab(item)\n }\n return renderLink(item)\n }\n\n const tabItems = sidebarConfig.tabs ?? []\n\n return (\n <div className={tabsBaseClass}>\n <div className={`${tabsBaseClass}__tabs`}>{tabItems.map(renderTabItem)}</div>\n\n {showLogout && (\n <div className={`${tabsBaseClass}__actions`}>\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 </div>\n )}\n </div>\n )\n}\n"],"names":["getTranslation","Link","useConfig","useTranslation","usePathname","formatAdminURL","React","Icon","tabsBaseClass","TabsBar","activeTabId","onTabChange","sidebarConfig","i18n","pathname","config","admin","routes","logout","logoutRoute","adminRoute","showLogout","renderTab","tab","label","isActive","id","button","className","onClick","title","type","name","icon","size","renderLink","link","href","isExternal","path","target","undefined","renderTabItem","item","tabItems","tabs","div","map","en","uk"],"mappings":"AAAA;;AAEA,SAASA,cAAc,QAAQ,2BAA0B;AACzD,SAASC,IAAI,EAAEC,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AAChE,SAASC,WAAW,QAAQ,kBAAiB;AAC7C,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,OAAOC,WAAW,QAAO;AAIzB,SAASC,IAAI,QAAQ,UAAS;AAC9B,OAAO,eAAc;AAErB,MAAMC,gBAAgB;AAQtB,OAAO,MAAMC,UAAkC,CAAC,EAAEC,WAAW,EAAEC,WAAW,EAAEC,aAAa,EAAE;IACzF,MAAM,EAAEC,IAAI,EAAE,GAAGV;IACjB,MAAMW,WAAWV;IAEjB,MAAM,EACJW,QAAQ,EACNC,OAAO,EACLC,QAAQ,EAAEC,QAAQC,WAAW,EAAE,EAChC,EACDF,QAAQ,EAAED,OAAOI,UAAU,EAAE,EAC9B,EACF,GAAGlB;IAEJ,MAAMmB,aAAaT,cAAcS,UAAU,KAAK;IAEhD,MAAMC,YAAY,CAACC;QACjB,MAAMC,QAAQxB,eAAeuB,IAAIC,KAAK,EAAEX;QACxC,MAAMY,WAAWf,gBAAgBa,IAAIG,EAAE;QAEvC,qBACE,KAACC;YACCC,WAAW,GAAGpB,cAAc,MAAM,EAAEiB,WAAW,GAAGjB,cAAc,aAAa,CAAC,GAAG,IAAI;YAErFqB,SAAS,IAAMlB,YAAYY,IAAIG,EAAE;YACjCI,OAAON;YACPO,MAAK;sBAEL,cAAA,KAACxB;gBAAKyB,MAAMT,IAAIU,IAAI;gBAAEC,MAAM;;WALvBX,IAAIG,EAAE;IAQjB;IAEA,MAAMS,aAAa,CAACC;QAClB,MAAMZ,QAAQxB,eAAeoC,KAAKZ,KAAK,EAAEX;QACzC,MAAMwB,OAAOD,KAAKE,UAAU,GAAGF,KAAKC,IAAI,GAAGhC,eAAe;YAAEe;YAAYmB,MAAMH,KAAKC,IAAI;QAAC;QAExF,+BAA+B;QAC/B,MAAMZ,WAAWX,aAAauB,QAASD,KAAKC,IAAI,KAAK,OAAOvB,aAAaM;QAEzE,qBACE,KAACnB;YACC2B,WAAW,GAAGpB,cAAc,OAAO,EAAEiB,WAAW,GAAGjB,cAAc,cAAc,CAAC,GAAG,IAAI;YACvF6B,MAAMA;YAENG,QAAQJ,KAAKE,UAAU,GAAG,WAAWG;YACrCX,OAAON;sBAEP,cAAA,KAACjB;gBAAKyB,MAAMI,KAAKH,IAAI;gBAAEC,MAAM;;WAJxBE,KAAKV,EAAE;IAOlB;IAEA,MAAMgB,gBAAgB,CAACC;QACrB,IAAIA,KAAKZ,IAAI,KAAK,OAAO;YACvB,OAAOT,UAAUqB;QACnB;QACA,OAAOR,WAAWQ;IACpB;IAEA,MAAMC,WAAWhC,cAAciC,IAAI,IAAI,EAAE;IAEzC,qBACE,MAACC;QAAIlB,WAAWpB;;0BACd,KAACsC;gBAAIlB,WAAW,GAAGpB,cAAc,MAAM,CAAC;0BAAGoC,SAASG,GAAG,CAACL;;YAEvDrB,4BACC,KAACyB;gBAAIlB,WAAW,GAAGpB,cAAc,SAAS,CAAC;0BACzC,cAAA,KAACP;oBACC2B,WAAW,GAAGpB,cAAc,QAAQ,CAAC;oBACrC6B,MAAMhC,eAAe;wBACnBe;wBACAmB,MAAMpB;oBACR;oBACAW,OAAO9B,eAAe;wBAAEgD,IAAI;wBAAUC,IAAI;oBAAQ,GAAGpC;oBACrDkB,MAAK;8BAEL,cAAA,KAACxB;wBAAKyB,MAAK;wBAASE,MAAM;;;;;;AAMtC,EAAC"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { getTranslation } from '@payloadcms/translations';
|
|
4
4
|
import { Link, NavGroup, useConfig, useTranslation } from '@payloadcms/ui';
|
|
5
|
+
import { EntityType } from '@payloadcms/ui/shared';
|
|
5
6
|
import { usePathname } from 'next/navigation.js';
|
|
6
7
|
import { formatAdminURL } from 'payload/shared';
|
|
7
8
|
import React, { Fragment } from 'react';
|
|
@@ -20,14 +21,16 @@ export const EnhancedSidebarClient = ({ groups, navPreferences })=>{
|
|
|
20
21
|
const entityType = entity.type;
|
|
21
22
|
let href;
|
|
22
23
|
let id;
|
|
23
|
-
// Check for collection
|
|
24
|
-
|
|
24
|
+
// Check for collection
|
|
25
|
+
//@ts-expect-error Idk why typescript is complaining here
|
|
26
|
+
if (entityType === EntityType.collection) {
|
|
25
27
|
href = formatAdminURL({
|
|
26
28
|
adminRoute,
|
|
27
29
|
path: `/collections/${slug}`
|
|
28
30
|
});
|
|
29
31
|
id = `nav-${slug}`;
|
|
30
|
-
|
|
32
|
+
//@ts-expect-error Idk why typescript is complaining here
|
|
33
|
+
} else if (entityType === EntityType.global) {
|
|
31
34
|
href = formatAdminURL({
|
|
32
35
|
adminRoute,
|
|
33
36
|
path: `/globals/${slug}`
|
|
@@ -35,12 +38,15 @@ export const EnhancedSidebarClient = ({ groups, navPreferences })=>{
|
|
|
35
38
|
id = `nav-global-${slug}`;
|
|
36
39
|
} else if (entityType === 'custom' && entity.href) {
|
|
37
40
|
// Custom item with href
|
|
38
|
-
const customHref = entity.href;
|
|
39
|
-
href = formatAdminURL({
|
|
40
|
-
adminRoute,
|
|
41
|
-
path: customHref
|
|
42
|
-
});
|
|
43
41
|
id = `nav-custom-${slug}`;
|
|
42
|
+
if (entity.isExternal) {
|
|
43
|
+
href = entity.href;
|
|
44
|
+
} else {
|
|
45
|
+
href = formatAdminURL({
|
|
46
|
+
adminRoute,
|
|
47
|
+
path: entity.href
|
|
48
|
+
});
|
|
49
|
+
}
|
|
44
50
|
} else {
|
|
45
51
|
return null;
|
|
46
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/EnhancedSidebar/index.client.tsx"],"sourcesContent":["'use client'\nimport type { NavPreferences } from 'payload'\nimport type { ExtendedGroup } from 'src/types'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { Link, NavGroup, useConfig, useTranslation } from '@payloadcms/ui'\nimport { usePathname } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport React, { Fragment } from 'react'\n\nconst baseClass = 'enhanced-sidebar'\n\nexport const EnhancedSidebarClient: React.FC<{\n groups: ExtendedGroup[]\n navPreferences: NavPreferences | null\n}> = ({ groups, navPreferences }) => {\n const pathname = usePathname()\n\n const {\n config: {\n routes: { admin: adminRoute },\n },\n } = useConfig()\n\n const { i18n } = useTranslation()\n\n return (\n <Fragment>\n {groups.map(({ entities, label }, key) => {\n // Handle empty label (ungrouped items)\n const groupLabel = label || ''\n const isUngrouped = !label || (typeof label === 'string' && label === '')\n\n const content = entities.map((entity, i) => {\n const { slug, label: entityLabel } = entity\n const entityType = entity.type\n let href: string\n let id: string\n\n // Check for collection
|
|
1
|
+
{"version":3,"sources":["../../../src/components/EnhancedSidebar/index.client.tsx"],"sourcesContent":["'use client'\nimport type { NavPreferences } from 'payload'\nimport type { ExtendedGroup } from 'src/types'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { Link, NavGroup, useConfig, useTranslation } from '@payloadcms/ui'\nimport { EntityType } from '@payloadcms/ui/shared'\nimport { usePathname } from 'next/navigation.js'\nimport { formatAdminURL } from 'payload/shared'\nimport React, { Fragment } from 'react'\n\nconst baseClass = 'enhanced-sidebar'\n\nexport const EnhancedSidebarClient: React.FC<{\n groups: ExtendedGroup[]\n navPreferences: NavPreferences | null\n}> = ({ groups, navPreferences }) => {\n const pathname = usePathname()\n\n const {\n config: {\n routes: { admin: adminRoute },\n },\n } = useConfig()\n\n const { i18n } = useTranslation()\n\n return (\n <Fragment>\n {groups.map(({ entities, label }, key) => {\n // Handle empty label (ungrouped items)\n const groupLabel = label || ''\n const isUngrouped = !label || (typeof label === 'string' && label === '')\n\n const content = entities.map((entity, i) => {\n const { slug, label: entityLabel } = entity\n const entityType = entity.type\n let href: string\n let id: string\n\n // Check for collection\n //@ts-expect-error Idk why typescript is complaining here\n if (entityType === EntityType.collection) {\n href = formatAdminURL({ adminRoute, path: `/collections/${slug}` })\n id = `nav-${slug}`\n //@ts-expect-error Idk why typescript is complaining here\n } else if (entityType === EntityType.global) {\n href = formatAdminURL({ adminRoute, path: `/globals/${slug}` })\n id = `nav-global-${slug}`\n } else if (entityType === 'custom' && entity.href) {\n // Custom item with href\n id = `nav-custom-${slug}`\n if (entity.isExternal) {\n href = entity.href\n } else {\n href = formatAdminURL({ adminRoute, path: entity.href })\n }\n } else {\n return null\n }\n\n const isActive =\n pathname.startsWith(href) && ['/', undefined].includes(pathname[href.length])\n\n const Label = (\n <>\n {isActive && <div className={`${baseClass}__link-indicator`} />}\n <span className={`${baseClass}__link-label`}>\n {getTranslation(entityLabel, i18n)}\n </span>\n </>\n )\n\n if (pathname === href) {\n return (\n <div className={`${baseClass}__link`} id={id} key={i}>\n {Label}\n </div>\n )\n }\n\n return (\n <Link className={`${baseClass}__link`} href={href} id={id} key={i} prefetch={false}>\n {Label}\n </Link>\n )\n })\n\n // For ungrouped items, render without NavGroup wrapper\n if (isUngrouped) {\n return <Fragment key={key}>{content}</Fragment>\n }\n\n // Get translated label for NavGroup\n const translatedLabel = getTranslation(groupLabel, i18n)\n\n return (\n <NavGroup\n isOpen={navPreferences?.groups?.[translatedLabel]?.open}\n key={key}\n label={translatedLabel}\n >\n {content}\n </NavGroup>\n )\n })}\n </Fragment>\n )\n}\n"],"names":["getTranslation","Link","NavGroup","useConfig","useTranslation","EntityType","usePathname","formatAdminURL","React","Fragment","baseClass","EnhancedSidebarClient","groups","navPreferences","pathname","config","routes","admin","adminRoute","i18n","map","entities","label","key","groupLabel","isUngrouped","content","entity","i","slug","entityLabel","entityType","type","href","id","collection","path","global","isExternal","isActive","startsWith","undefined","includes","length","Label","div","className","span","prefetch","translatedLabel","isOpen","open"],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent';
|
|
3
3
|
import { EntityType, groupNavItems } from '@payloadcms/ui/shared';
|
|
4
|
+
import { cookies } from 'next/headers';
|
|
4
5
|
import React from 'react';
|
|
6
|
+
const COOKIE_KEY = 'payload-enhanced-sidebar-active-tab';
|
|
5
7
|
import { getNavPrefs } from './getNavPrefs';
|
|
6
8
|
import { SidebarContent } from './SidebarContent';
|
|
7
9
|
import './index.scss';
|
|
@@ -52,10 +54,17 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
52
54
|
}
|
|
53
55
|
]
|
|
54
56
|
};
|
|
57
|
+
// Read active tab from cookie
|
|
58
|
+
const cookieStore = await cookies();
|
|
59
|
+
const storedTabId = cookieStore.get(COOKIE_KEY)?.value;
|
|
60
|
+
const tabs = config.tabs?.filter((t)=>t.type === 'tab') ?? [];
|
|
61
|
+
const defaultTabId = tabs[0]?.id ?? 'default';
|
|
62
|
+
const initialActiveTabId = storedTabId && tabs.some((t)=>t.id === storedTabId) ? storedTabId : defaultTabId;
|
|
55
63
|
return /*#__PURE__*/ _jsx(SidebarContent, {
|
|
56
64
|
afterNavLinks: afterNavLinksRendered,
|
|
57
65
|
beforeNavLinks: beforeNavLinksRendered,
|
|
58
66
|
groups: groups,
|
|
67
|
+
initialActiveTabId: initialActiveTabId,
|
|
59
68
|
navPreferences: navPreferences,
|
|
60
69
|
sidebarConfig: config
|
|
61
70
|
});
|
|
@@ -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 { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'\nimport { EntityType, groupNavItems } from '@payloadcms/ui/shared'\nimport React from 'react'\n\nimport type { EnhancedSidebarConfig, ExtendedGroup } from '../../types'\n\nimport { getNavPrefs } from './getNavPrefs'\nimport { SidebarContent } from './SidebarContent'\nimport './index.scss'\n\nexport type EnhancedSidebarProps = {\n req?: PayloadRequest\n sidebarConfig?: EnhancedSidebarConfig\n} & ServerProps\n\nexport const EnhancedSidebar: React.FC<EnhancedSidebarProps> = async (props) => {\n const {\n i18n,\n locale,\n params,\n payload,\n permissions,\n req,\n searchParams,\n sidebarConfig,\n user,\n visibleEntities,\n } = props\n\n if (!payload?.config) {\n return null\n }\n\n const {\n admin: {\n components: { afterNavLinks, beforeNavLinks },\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 beforeNavLinksRendered = RenderServerComponent({\n Component: beforeNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n const afterNavLinksRendered = RenderServerComponent({\n Component: afterNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n // Default config if not provided\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 return (\n <SidebarContent\n afterNavLinks={afterNavLinksRendered}\n beforeNavLinks={beforeNavLinksRendered}\n groups={groups}\n navPreferences={navPreferences}\n sidebarConfig={config}\n />\n )\n}\n\nexport default EnhancedSidebar\n"],"names":["RenderServerComponent","EntityType","groupNavItems","React","getNavPrefs","SidebarContent","EnhancedSidebar","props","i18n","locale","params","payload","permissions","req","searchParams","sidebarConfig","user","visibleEntities","config","admin","components","afterNavLinks","beforeNavLinks","collections","globals","groups","filter","slug","includes","map","collection","type","entity","global","navPreferences","serverProps","beforeNavLinksRendered","Component","importMap","afterNavLinksRendered","tabs","id","icon","label"],"mappings":";AAGA,SAASA,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,UAAU,EAAEC,aAAa,QAAQ,wBAAuB;AACjE,OAAOC,WAAW,QAAO;
|
|
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 { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'\nimport { EntityType, groupNavItems } from '@payloadcms/ui/shared'\nimport { cookies } from 'next/headers'\nimport React from 'react'\n\nconst COOKIE_KEY = 'payload-enhanced-sidebar-active-tab'\n\nimport type { EnhancedSidebarConfig, ExtendedGroup } from '../../types'\n\nimport { getNavPrefs } from './getNavPrefs'\nimport { SidebarContent } from './SidebarContent'\nimport './index.scss'\n\nexport type EnhancedSidebarProps = {\n req?: PayloadRequest\n sidebarConfig?: EnhancedSidebarConfig\n} & ServerProps\n\nexport const EnhancedSidebar: React.FC<EnhancedSidebarProps> = async (props) => {\n const {\n i18n,\n locale,\n params,\n payload,\n permissions,\n req,\n searchParams,\n sidebarConfig,\n user,\n visibleEntities,\n } = props\n\n if (!payload?.config) {\n return null\n }\n\n const {\n admin: {\n components: { afterNavLinks, beforeNavLinks },\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 beforeNavLinksRendered = RenderServerComponent({\n Component: beforeNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n const afterNavLinksRendered = RenderServerComponent({\n Component: afterNavLinks,\n importMap: payload.importMap,\n serverProps,\n })\n\n // Default config if not provided\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 // Read active tab from cookie\n const cookieStore = await cookies()\n const storedTabId = cookieStore.get(COOKIE_KEY)?.value\n const tabs = config.tabs?.filter((t) => t.type === 'tab') ?? []\n const defaultTabId = tabs[0]?.id ?? 'default'\n const initialActiveTabId =\n storedTabId && tabs.some((t) => t.id === storedTabId) ? storedTabId : defaultTabId\n\n return (\n <SidebarContent\n afterNavLinks={afterNavLinksRendered}\n beforeNavLinks={beforeNavLinksRendered}\n groups={groups}\n initialActiveTabId={initialActiveTabId}\n navPreferences={navPreferences}\n sidebarConfig={config}\n />\n )\n}\n\nexport default EnhancedSidebar\n"],"names":["RenderServerComponent","EntityType","groupNavItems","cookies","React","COOKIE_KEY","getNavPrefs","SidebarContent","EnhancedSidebar","props","i18n","locale","params","payload","permissions","req","searchParams","sidebarConfig","user","visibleEntities","config","admin","components","afterNavLinks","beforeNavLinks","collections","globals","groups","filter","slug","includes","map","collection","type","entity","global","navPreferences","serverProps","beforeNavLinksRendered","Component","importMap","afterNavLinksRendered","tabs","id","icon","label","cookieStore","storedTabId","get","value","t","defaultTabId","initialActiveTabId","some"],"mappings":";AAGA,SAASA,qBAAqB,QAAQ,gDAA+C;AACrF,SAASC,UAAU,EAAEC,aAAa,QAAQ,wBAAuB;AACjE,SAASC,OAAO,QAAQ,eAAc;AACtC,OAAOC,WAAW,QAAO;AAEzB,MAAMC,aAAa;AAInB,SAASC,WAAW,QAAQ,gBAAe;AAC3C,SAASC,cAAc,QAAQ,mBAAkB;AACjD,OAAO,eAAc;AAOrB,OAAO,MAAMC,kBAAkD,OAAOC;IACpE,MAAM,EACJC,IAAI,EACJC,MAAM,EACNC,MAAM,EACNC,OAAO,EACPC,WAAW,EACXC,GAAG,EACHC,YAAY,EACZC,aAAa,EACbC,IAAI,EACJC,eAAe,EAChB,GAAGV;IAEJ,IAAI,CAACI,SAASO,QAAQ;QACpB,OAAO;IACT;IAEA,MAAM,EACJC,OAAO,EACLC,YAAY,EAAEC,aAAa,EAAEC,cAAc,EAAE,EAC9C,EACDC,WAAW,EACXC,OAAO,EACR,GAAGb,QAAQO,MAAM;IAElB,MAAMO,SAASzB,cACb;WACKuB,YACAG,MAAM,CAAC,CAAC,EAAEC,IAAI,EAAE,GAAKV,iBAAiBM,YAAYK,SAASD,OAC3DE,GAAG,CACF,CAACC,aACE,CAAA;gBACCC,MAAMhC,WAAW+B,UAAU;gBAC3BE,QAAQF;YACV,CAAA;WAEHN,QACAE,MAAM,CAAC,CAAC,EAAEC,IAAI,EAAE,GAAKV,iBAAiBO,QAAQI,SAASD,OACvDE,GAAG,CACF,CAACI,SACE,CAAA;gBACCF,MAAMhC,WAAWkC,MAAM;gBACvBD,QAAQC;YACV,CAAA;KAEP,EACDrB,eAAe,CAAC,GAChBJ;IAGF,MAAM0B,iBAAiB,MAAM9B,YAAYS;IAEzC,MAAMsB,cAAc;QAClB3B;QACAC;QACAC;QACAC;QACAC;QACAE;QACAE;IACF;IAEA,MAAMoB,yBAAyBtC,sBAAsB;QACnDuC,WAAWf;QACXgB,WAAW3B,QAAQ2B,SAAS;QAC5BH;IACF;IAEA,MAAMI,wBAAwBzC,sBAAsB;QAClDuC,WAAWhB;QACXiB,WAAW3B,QAAQ2B,SAAS;QAC5BH;IACF;IAEA,iCAAiC;IACjC,MAAMjB,SAAgCH,iBAAiB;QACrDyB,MAAM;YACJ;gBACEC,IAAI;gBACJV,MAAM;gBACNW,MAAM;gBACNC,OAAO;YACT;SACD;IACH;IAEA,8BAA8B;IAC9B,MAAMC,cAAc,MAAM3C;IAC1B,MAAM4C,cAAcD,YAAYE,GAAG,CAAC3C,aAAa4C;IACjD,MAAMP,OAAOtB,OAAOsB,IAAI,EAAEd,OAAO,CAACsB,IAAMA,EAAEjB,IAAI,KAAK,UAAU,EAAE;IAC/D,MAAMkB,eAAeT,IAAI,CAAC,EAAE,EAAEC,MAAM;IACpC,MAAMS,qBACJL,eAAeL,KAAKW,IAAI,CAAC,CAACH,IAAMA,EAAEP,EAAE,KAAKI,eAAeA,cAAcI;IAExE,qBACE,KAAC5C;QACCgB,eAAekB;QACfjB,gBAAgBc;QAChBX,QAAQA;QACRyB,oBAAoBA;QACpBhB,gBAAgBA;QAChBnB,eAAeG;;AAGrB,EAAC;AAED,eAAeZ,gBAAe"}
|
package/dist/types.d.ts
CHANGED
|
@@ -35,12 +35,7 @@ export interface SidebarTabContent {
|
|
|
35
35
|
label: LocalizedString;
|
|
36
36
|
type: 'tab';
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
* Sidebar link that navigates to a URL (not a tab)
|
|
40
|
-
*/
|
|
41
|
-
export interface SidebarTabLink {
|
|
42
|
-
/** Link href (relative to admin route) */
|
|
43
|
-
href: string;
|
|
38
|
+
interface SidebarTabLinkBase {
|
|
44
39
|
/** Icon name from lucide-react */
|
|
45
40
|
icon: IconName;
|
|
46
41
|
/** Unique identifier */
|
|
@@ -49,6 +44,20 @@ export interface SidebarTabLink {
|
|
|
49
44
|
label: LocalizedString;
|
|
50
45
|
type: 'link';
|
|
51
46
|
}
|
|
47
|
+
interface SidebarTabLinkExternal extends SidebarTabLinkBase {
|
|
48
|
+
/** Link href (absolute URL) */
|
|
49
|
+
href: string;
|
|
50
|
+
isExternal: true;
|
|
51
|
+
}
|
|
52
|
+
interface SidebarTabLinkInternal extends SidebarTabLinkBase {
|
|
53
|
+
/** Link href (relative to admin route) */
|
|
54
|
+
href: '' | `/${string}`;
|
|
55
|
+
isExternal?: false;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sidebar link that navigates to a URL (not a tab)
|
|
59
|
+
*/
|
|
60
|
+
export type SidebarTabLink = SidebarTabLinkExternal | SidebarTabLinkInternal;
|
|
52
61
|
/**
|
|
53
62
|
* A tab or link in the sidebar tabs bar
|
|
54
63
|
*/
|
|
@@ -119,12 +128,20 @@ export type GenericCollectionDocument = {
|
|
|
119
128
|
[key: string]: number | string;
|
|
120
129
|
id: string;
|
|
121
130
|
};
|
|
122
|
-
|
|
123
|
-
href?: string;
|
|
131
|
+
interface BaseExtendedEntity {
|
|
124
132
|
label: Record<string, string> | string;
|
|
125
133
|
slug: string;
|
|
126
134
|
type: 'collection' | 'custom' | 'global';
|
|
127
|
-
}
|
|
135
|
+
}
|
|
136
|
+
interface InternalExtendedEntity extends BaseExtendedEntity {
|
|
137
|
+
href?: '' | `/${string}`;
|
|
138
|
+
isExternal?: false;
|
|
139
|
+
}
|
|
140
|
+
interface ExternalExtendedEntity extends BaseExtendedEntity {
|
|
141
|
+
href: string;
|
|
142
|
+
isExternal: true;
|
|
143
|
+
}
|
|
144
|
+
export type ExtendedEntity = ExternalExtendedEntity | InternalExtendedEntity;
|
|
128
145
|
export type ExtendedGroup = {
|
|
129
146
|
entities: ExtendedEntity[];
|
|
130
147
|
label: Record<string, string> | string;
|
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 } from 'payload'\n\nexport type IconName = keyof typeof icons\n\nexport type LocalizedString = { [locale: string]: string } | string\n\nexport type InternalIcon = IconName | LucideIcon\n\n// ============================================\n// Enhanced Sidebar Types\n// ============================================\n\n/**\n * Sidebar tab that shows content when selected\n */\nexport interface SidebarTabContent {\n /**\n * Collections to show in this tab.\n * If not specified, no collections are shown (unless items are specified).\n * Use collection slugs.\n */\n collections?: CollectionSlug[]\n /**\n * Custom items to add to this tab.\n * Items with `group` will be merged into matching collection groups.\n * Items without `group` will be shown at the bottom as a flat list.\n */\n customItems?: SidebarTabItem[]\n /**\n * Globals to show in this tab.\n * If not specified, no globals are shown.\n * Use global slugs.\n */\n globals?: GlobalSlug[]\n /** Icon name from lucide-react */\n icon: IconName\n /** Unique identifier for the tab */\n id: string\n /** Tooltip/label for the tab */\n label: LocalizedString\n type: 'tab'\n}\n\
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { icons, LucideIcon } from 'lucide-react'\nimport type { CollectionSlug, GlobalSlug } from 'payload'\n\nexport type IconName = keyof typeof icons\n\nexport type LocalizedString = { [locale: string]: string } | string\n\nexport type InternalIcon = IconName | LucideIcon\n\n// ============================================\n// Enhanced Sidebar Types\n// ============================================\n\n/**\n * Sidebar tab that shows content when selected\n */\nexport interface SidebarTabContent {\n /**\n * Collections to show in this tab.\n * If not specified, no collections are shown (unless items are specified).\n * Use collection slugs.\n */\n collections?: CollectionSlug[]\n /**\n * Custom items to add to this tab.\n * Items with `group` will be merged into matching collection groups.\n * Items without `group` will be shown at the bottom as a flat list.\n */\n customItems?: SidebarTabItem[]\n /**\n * Globals to show in this tab.\n * If not specified, no globals are shown.\n * Use global slugs.\n */\n globals?: GlobalSlug[]\n /** Icon name from lucide-react */\n icon: IconName\n /** Unique identifier for the tab */\n id: string\n /** Tooltip/label for the tab */\n label: LocalizedString\n type: 'tab'\n}\n\ninterface SidebarTabLinkBase {\n /** Icon name from lucide-react */\n icon: IconName\n /** Unique identifier */\n id: string\n /** Tooltip/label */\n label: LocalizedString\n type: 'link'\n}\ninterface SidebarTabLinkExternal extends SidebarTabLinkBase {\n /** Link href (absolute URL) */\n href: string\n isExternal: true\n}\ninterface SidebarTabLinkInternal extends SidebarTabLinkBase {\n /** Link href (relative to admin route) */\n href: '' | `/${string}`\n isExternal?: false\n}\n/**\n * Sidebar link that navigates to a URL (not a tab)\n */\nexport type SidebarTabLink = SidebarTabLinkExternal | SidebarTabLinkInternal\n/**\n * A tab or link in the sidebar tabs bar\n */\nexport type SidebarTab = SidebarTabContent | SidebarTabLink\n\ninterface BaseSidebarTabItem {\n /**\n * Group to add this item to.\n * If matches an existing collection group label, item will be merged into that group.\n * If no match found, a new group will be created.\n * If not specified, item will be shown at the bottom as ungrouped.\n */\n group?: LocalizedString\n /** Display label */\n label: LocalizedString\n /** Unique slug for the item */\n slug: string\n}\ninterface ExternalHrefItem extends BaseSidebarTabItem {\n /** Link href (absolute URL or relative to root) */\n href: string\n /** Whether the link is external, without admin route prefix. */\n isExternal: true\n}\n\ninterface InternalHrefItem extends BaseSidebarTabItem {\n /** Link href (relative to admin route) */\n href: '' | `/${string}`\n /** Whether the link is external, without admin route prefix. */\n isExternal?: false\n}\n/**\n * Custom item inside a sidebar tab\n */\nexport type SidebarTabItem = ExternalHrefItem | InternalHrefItem\n\n/**\n * Configuration for the enhanced sidebar\n */\nexport interface EnhancedSidebarConfig {\n /**\n * Disable the plugin\n * @default false\n */\n disabled?: boolean\n\n /**\n * Custom icons for collections and globals in the default tab.\n */\n icons?: {\n collections?: Partial<Record<CollectionSlug, IconName>>\n globals?: Partial<Record<GlobalSlug, IconName>>\n }\n\n /**\n * Show logout button at the bottom of the tabs bar.\n * @default true\n */\n showLogout?: boolean\n\n /**\n * Tabs and links to show in the sidebar tabs bar.\n * Order matters - items are rendered top to bottom.\n *\n * @default [{ type: 'tab', id: 'default', icon: 'LayoutGrid', label: 'Collections' }]\n */\n tabs?: SidebarTab[]\n}\n\n/**\n * Generic document type for collections, with dynamic keys.\n * We assume values are either string or number for simplicity, useAsTitle is making sure of that.\n */\nexport type GenericCollectionDocument = {\n [key: string]: number | string\n id: string\n}\n\ninterface BaseExtendedEntity {\n label: Record<string, string> | string\n slug: string\n type: 'collection' | 'custom' | 'global'\n}\n\ninterface InternalExtendedEntity extends BaseExtendedEntity {\n href?: '' | `/${string}`\n isExternal?: false\n}\n\ninterface ExternalExtendedEntity extends BaseExtendedEntity {\n href: string\n isExternal: true\n}\n\nexport type ExtendedEntity = ExternalExtendedEntity | InternalExtendedEntity\n\nexport type ExtendedGroup = {\n entities: ExtendedEntity[]\n label: Record<string, string> | string\n}\n"],"names":[],"mappings":"AAmKA,WAGC"}
|
package/package.json
CHANGED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { getTranslation } from '@payloadcms/translations';
|
|
4
|
-
import { Link, useConfig, useTranslation } from '@payloadcms/ui';
|
|
5
|
-
import { usePathname } from 'next/navigation.js';
|
|
6
|
-
import { formatAdminURL } from 'payload/shared';
|
|
7
|
-
import React from 'react';
|
|
8
|
-
const baseClass = 'enhanced-sidebar';
|
|
9
|
-
export const CustomTabContent = ({ items })=>{
|
|
10
|
-
const { i18n } = useTranslation();
|
|
11
|
-
const pathname = usePathname();
|
|
12
|
-
const { config: { routes: { admin: adminRoute } } } = useConfig();
|
|
13
|
-
return /*#__PURE__*/ _jsx("div", {
|
|
14
|
-
className: `${baseClass}__custom-items`,
|
|
15
|
-
children: items.map((item)=>{
|
|
16
|
-
const href = item.isExternal ? item.href : formatAdminURL({
|
|
17
|
-
adminRoute,
|
|
18
|
-
path: item.href
|
|
19
|
-
});
|
|
20
|
-
const label = getTranslation(item.label, i18n);
|
|
21
|
-
const isActive = pathname.startsWith(href);
|
|
22
|
-
return /*#__PURE__*/ _jsx(Link, {
|
|
23
|
-
className: `${baseClass}__link ${isActive ? `${baseClass}__link--active` : ''}`,
|
|
24
|
-
href: href,
|
|
25
|
-
prefetch: false,
|
|
26
|
-
children: /*#__PURE__*/ _jsx("span", {
|
|
27
|
-
className: `${baseClass}__link-label`,
|
|
28
|
-
children: label
|
|
29
|
-
})
|
|
30
|
-
}, item.slug);
|
|
31
|
-
})
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
//# sourceMappingURL=CustomTabContent.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/EnhancedSidebar/CustomTabContent.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 { SidebarTabItem } from '../../types'\n\nconst baseClass = 'enhanced-sidebar'\n\nexport type CustomTabContentProps = {\n items: SidebarTabItem[]\n}\n\nexport const CustomTabContent: React.FC<CustomTabContentProps> = ({ items }) => {\n const { i18n } = useTranslation()\n const pathname = usePathname()\n\n const {\n config: {\n routes: { admin: adminRoute },\n },\n } = useConfig()\n\n return (\n <div className={`${baseClass}__custom-items`}>\n {items.map((item) => {\n const href = item.isExternal ? item.href : formatAdminURL({ adminRoute, path: item.href })\n const label = getTranslation(item.label, i18n)\n const isActive = pathname.startsWith(href)\n\n return (\n <Link\n className={`${baseClass}__link ${isActive ? `${baseClass}__link--active` : ''}`}\n href={href}\n key={item.slug}\n prefetch={false}\n >\n <span className={`${baseClass}__link-label`}>{label}</span>\n </Link>\n )\n })}\n </div>\n )\n}\n"],"names":["getTranslation","Link","useConfig","useTranslation","usePathname","formatAdminURL","React","baseClass","CustomTabContent","items","i18n","pathname","config","routes","admin","adminRoute","div","className","map","item","href","isExternal","path","label","isActive","startsWith","prefetch","span","slug"],"mappings":"AAAA;;AAEA,SAASA,cAAc,QAAQ,2BAA0B;AACzD,SAASC,IAAI,EAAEC,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AAChE,SAASC,WAAW,QAAQ,qBAAoB;AAChD,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,OAAOC,WAAW,QAAO;AAIzB,MAAMC,YAAY;AAMlB,OAAO,MAAMC,mBAAoD,CAAC,EAAEC,KAAK,EAAE;IACzE,MAAM,EAAEC,IAAI,EAAE,GAAGP;IACjB,MAAMQ,WAAWP;IAEjB,MAAM,EACJQ,QAAQ,EACNC,QAAQ,EAAEC,OAAOC,UAAU,EAAE,EAC9B,EACF,GAAGb;IAEJ,qBACE,KAACc;QAAIC,WAAW,GAAGV,UAAU,cAAc,CAAC;kBACzCE,MAAMS,GAAG,CAAC,CAACC;YACV,MAAMC,OAAOD,KAAKE,UAAU,GAAGF,KAAKC,IAAI,GAAGf,eAAe;gBAAEU;gBAAYO,MAAMH,KAAKC,IAAI;YAAC;YACxF,MAAMG,QAAQvB,eAAemB,KAAKI,KAAK,EAAEb;YACzC,MAAMc,WAAWb,SAASc,UAAU,CAACL;YAErC,qBACE,KAACnB;gBACCgB,WAAW,GAAGV,UAAU,OAAO,EAAEiB,WAAW,GAAGjB,UAAU,cAAc,CAAC,GAAG,IAAI;gBAC/Ea,MAAMA;gBAENM,UAAU;0BAEV,cAAA,KAACC;oBAAKV,WAAW,GAAGV,UAAU,YAAY,CAAC;8BAAGgB;;eAHzCJ,KAAKS,IAAI;QAMpB;;AAGN,EAAC"}
|