@veiag/payload-enhanced-sidebar 0.3.0-beta.4 → 0.3.1
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 +1 -0
- package/dist/components/EnhancedSidebar/NavContent/index.js +5 -3
- package/dist/components/EnhancedSidebar/NavContent/index.js.map +1 -1
- package/dist/components/EnhancedSidebar/SidebarContent.d.ts +2 -0
- package/dist/components/EnhancedSidebar/SidebarContent.js +3 -1
- package/dist/components/EnhancedSidebar/SidebarContent.js.map +1 -1
- package/dist/components/EnhancedSidebar/TabsBar/TabItem.js +42 -20
- package/dist/components/EnhancedSidebar/TabsBar/TabItem.js.map +1 -1
- package/dist/components/EnhancedSidebar/TabsBar/index.scss +42 -0
- package/dist/components/EnhancedSidebar/index.js +31 -13
- package/dist/components/EnhancedSidebar/index.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -575,6 +575,7 @@ label: {
|
|
|
575
575
|
|
|
576
576
|
- **Browse by Folder Button** - Automatically shows folder view button when Payload folders are enabled (requires Payload v3.41.0+)
|
|
577
577
|
- **Settings Menu Items** - Integrates with Payload's SettingsMenu components (requires Payload v3.60.0+)
|
|
578
|
+
- **`beforeNav` / `afterNav` slots** - Supports Payload's `admin.components.beforeNav` and `admin.components.afterNav` slots (requires Payload v3.75.0+). Both slots are rendered inside the nav content area — `beforeNav` before `beforeNavLinks`, `afterNav` after `afterNavLinks`.
|
|
578
579
|
|
|
579
580
|
## Contributing
|
|
580
581
|
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { useTabState } from '../hooks/useTabState';
|
|
5
5
|
const baseClass = 'enhanced-sidebar';
|
|
6
|
-
const TabPanel = ({
|
|
6
|
+
const TabPanel = ({ id, children })=>{
|
|
7
7
|
const { isActive } = useTabState(id);
|
|
8
8
|
return /*#__PURE__*/ _jsx("div", {
|
|
9
9
|
"aria-hidden": !isActive,
|
|
@@ -13,19 +13,21 @@ const TabPanel = ({ children, id })=>{
|
|
|
13
13
|
children: children
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
|
-
export const NavContent = ({ afterNavLinks, allContent, beforeNavLinks, tabs, tabsContent })=>{
|
|
16
|
+
export const NavContent = ({ afterNav, afterNavLinks, allContent, beforeNav, beforeNavLinks, tabs, tabsContent })=>{
|
|
17
17
|
const hasTabs = tabs.length > 0;
|
|
18
18
|
return /*#__PURE__*/ _jsx("nav", {
|
|
19
19
|
className: `${baseClass}__content`,
|
|
20
20
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
21
21
|
className: `${baseClass}__content-scroll`,
|
|
22
22
|
children: [
|
|
23
|
+
beforeNav,
|
|
23
24
|
beforeNavLinks,
|
|
24
25
|
hasTabs ? tabs.map((tab)=>/*#__PURE__*/ _jsx(TabPanel, {
|
|
25
26
|
id: tab.id,
|
|
26
27
|
children: tabsContent[tab.id]
|
|
27
28
|
}, tab.id)) : allContent,
|
|
28
|
-
afterNavLinks
|
|
29
|
+
afterNavLinks,
|
|
30
|
+
afterNav
|
|
29
31
|
]
|
|
30
32
|
})
|
|
31
33
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/EnhancedSidebar/NavContent/index.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nimport type { CustomNavContentProps } from '../../../types'\n\nimport { useTabState } from '../hooks/useTabState'\n\nconst baseClass = 'enhanced-sidebar'\n\nconst TabPanel: React.FC<{
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/EnhancedSidebar/NavContent/index.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nimport type { CustomNavContentProps } from '../../../types'\n\nimport { useTabState } from '../hooks/useTabState'\n\nconst baseClass = 'enhanced-sidebar'\n\nconst TabPanel: React.FC<{ children: React.ReactNode; id: string }> = ({ id, children }) => {\n const { isActive } = useTabState(id)\n return (\n <div aria-hidden={!isActive} style={{ display: isActive ? undefined : 'none' }}>\n {children}\n </div>\n )\n}\n\nexport const NavContent: React.FC<CustomNavContentProps> = ({\n afterNav,\n afterNavLinks,\n allContent,\n beforeNav,\n beforeNavLinks,\n tabs,\n tabsContent,\n}) => {\n const hasTabs = tabs.length > 0\n\n return (\n <nav className={`${baseClass}__content`}>\n <div className={`${baseClass}__content-scroll`}>\n {beforeNav}\n {beforeNavLinks}\n {hasTabs\n ? tabs.map((tab) => (\n <TabPanel id={tab.id} key={tab.id}>\n {tabsContent[tab.id]}\n </TabPanel>\n ))\n : allContent}\n {afterNavLinks}\n {afterNav}\n </div>\n </nav>\n )\n}\n"],"names":["React","useTabState","baseClass","TabPanel","id","children","isActive","div","aria-hidden","style","display","undefined","NavContent","afterNav","afterNavLinks","allContent","beforeNav","beforeNavLinks","tabs","tabsContent","hasTabs","length","nav","className","map","tab"],"mappings":"AAAA;;AAEA,OAAOA,WAAW,QAAO;AAIzB,SAASC,WAAW,QAAQ,uBAAsB;AAElD,MAAMC,YAAY;AAElB,MAAMC,WAAgE,CAAC,EAAEC,EAAE,EAAEC,QAAQ,EAAE;IACrF,MAAM,EAAEC,QAAQ,EAAE,GAAGL,YAAYG;IACjC,qBACE,KAACG;QAAIC,eAAa,CAACF;QAAUG,OAAO;YAAEC,SAASJ,WAAWK,YAAY;QAAO;kBAC1EN;;AAGP;AAEA,OAAO,MAAMO,aAA8C,CAAC,EAC1DC,QAAQ,EACRC,aAAa,EACbC,UAAU,EACVC,SAAS,EACTC,cAAc,EACdC,IAAI,EACJC,WAAW,EACZ;IACC,MAAMC,UAAUF,KAAKG,MAAM,GAAG;IAE9B,qBACE,KAACC;QAAIC,WAAW,GAAGrB,UAAU,SAAS,CAAC;kBACrC,cAAA,MAACK;YAAIgB,WAAW,GAAGrB,UAAU,gBAAgB,CAAC;;gBAC3Cc;gBACAC;gBACAG,UACGF,KAAKM,GAAG,CAAC,CAACC,oBACR,KAACtB;wBAASC,IAAIqB,IAAIrB,EAAE;kCACjBe,WAAW,CAACM,IAAIrB,EAAE,CAAC;uBADKqB,IAAIrB,EAAE,KAInCW;gBACHD;gBACAD;;;;AAIT,EAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { EnhancedSidebarConfig } from '../../types';
|
|
3
3
|
export type SidebarContentProps = {
|
|
4
|
+
afterNav?: React.ReactNode;
|
|
4
5
|
afterNavLinks?: React.ReactNode;
|
|
5
6
|
allContent?: React.ReactNode;
|
|
7
|
+
beforeNav?: React.ReactNode;
|
|
6
8
|
beforeNavLinks?: React.ReactNode;
|
|
7
9
|
customNavContent?: React.ReactNode;
|
|
8
10
|
customTabComponents?: Record<string, React.ReactNode>;
|
|
@@ -10,7 +10,7 @@ const COOKIE_KEY = 'payload-enhanced-sidebar-active-tab';
|
|
|
10
10
|
const setTabCookie = (tabId)=>{
|
|
11
11
|
document.cookie = `${COOKIE_KEY}=${tabId}; path=/; max-age=31536000; SameSite=Lax`;
|
|
12
12
|
};
|
|
13
|
-
export const SidebarContent = ({ afterNavLinks, allContent, beforeNavLinks, customNavContent, customTabComponents, initialActiveTabId, renderedTabItems, settingsMenu, sidebarConfig, tabIcons, tabsContent })=>{
|
|
13
|
+
export const SidebarContent = ({ afterNav, afterNavLinks, allContent, beforeNav, beforeNavLinks, customNavContent, customTabComponents, initialActiveTabId, renderedTabItems, settingsMenu, sidebarConfig, tabIcons, tabsContent })=>{
|
|
14
14
|
const [activeTabId, setActiveTabId] = useState(initialActiveTabId);
|
|
15
15
|
const handleTabChange = useCallback((tabId)=>{
|
|
16
16
|
setActiveTabId(tabId);
|
|
@@ -39,8 +39,10 @@ export const SidebarContent = ({ afterNavLinks, allContent, beforeNavLinks, cust
|
|
|
39
39
|
tabIcons: tabIcons
|
|
40
40
|
}),
|
|
41
41
|
customNavContent ?? /*#__PURE__*/ _jsx(NavContent, {
|
|
42
|
+
afterNav: afterNav,
|
|
42
43
|
afterNavLinks: afterNavLinks,
|
|
43
44
|
allContent: allContent,
|
|
45
|
+
beforeNav: beforeNav,
|
|
44
46
|
beforeNavLinks: beforeNavLinks,
|
|
45
47
|
tabs: tabs,
|
|
46
48
|
tabsContent: tabsContent
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/EnhancedSidebar/SidebarContent.tsx"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useMemo, useState } from 'react'\n\nimport type { EnhancedSidebarConfig } from '../../types'\n\nimport { EnhancedSidebarContext } from './context'\nimport { NavContent } from './NavContent'\nimport { SidebarWrapper } from './SidebarWrapper'\nimport { TabsBar } from './TabsBar'\n\nconst baseClass = 'enhanced-sidebar'\n\nexport type SidebarContentProps = {\n afterNavLinks?: React.ReactNode\n allContent?: React.ReactNode\n beforeNavLinks?: React.ReactNode\n customNavContent?: React.ReactNode\n customTabComponents?: Record<string, React.ReactNode>\n initialActiveTabId: string\n renderedTabItems?: React.ReactNode[]\n settingsMenu?: React.ReactNode[]\n sidebarConfig: EnhancedSidebarConfig\n tabIcons?: Record<string, React.ReactNode>\n tabsContent: Record<string, React.ReactNode>\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 allContent,\n beforeNavLinks,\n customNavContent,\n customTabComponents,\n initialActiveTabId,\n renderedTabItems,\n settingsMenu,\n sidebarConfig,\n tabIcons,\n tabsContent,\n}) => {\n const [activeTabId, setActiveTabId] = useState(initialActiveTabId)\n\n const handleTabChange = useCallback((tabId: string) => {\n setActiveTabId(tabId)\n setTabCookie(tabId)\n }, [])\n\n const contextValue = useMemo(\n () => ({ activeTabId, onTabChange: handleTabChange }),\n [activeTabId, handleTabChange],\n )\n\n const tabs = sidebarConfig.tabs?.filter((t) => t.type === 'tab') ?? []\n\n return (\n <EnhancedSidebarContext.Provider value={contextValue}>\n <SidebarWrapper baseClass={baseClass}>\n <TabsBar\n activeTabId={activeTabId}\n customTabComponents={customTabComponents}\n onTabChange={handleTabChange}\n renderedTabItems={renderedTabItems}\n settingsMenu={settingsMenu}\n sidebarConfig={sidebarConfig}\n tabIcons={tabIcons}\n />\n {customNavContent ?? (\n <NavContent\n afterNavLinks={afterNavLinks}\n allContent={allContent}\n beforeNavLinks={beforeNavLinks}\n tabs={tabs}\n tabsContent={tabsContent}\n />\n )}\n </SidebarWrapper>\n </EnhancedSidebarContext.Provider>\n )\n}\n"],"names":["React","useCallback","useMemo","useState","EnhancedSidebarContext","NavContent","SidebarWrapper","TabsBar","baseClass","COOKIE_KEY","setTabCookie","tabId","document","cookie","SidebarContent","afterNavLinks","allContent","beforeNavLinks","customNavContent","customTabComponents","initialActiveTabId","renderedTabItems","settingsMenu","sidebarConfig","tabIcons","tabsContent","activeTabId","setActiveTabId","handleTabChange","contextValue","onTabChange","tabs","filter","t","type","Provider","value"],"mappings":"AAAA;;AAEA,OAAOA,SAASC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAI7D,SAASC,sBAAsB,QAAQ,YAAW;AAClD,SAASC,UAAU,QAAQ,eAAc;AACzC,SAASC,cAAc,QAAQ,mBAAkB;AACjD,SAASC,OAAO,QAAQ,YAAW;AAEnC,MAAMC,YAAY;
|
|
1
|
+
{"version":3,"sources":["../../../src/components/EnhancedSidebar/SidebarContent.tsx"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useMemo, useState } from 'react'\n\nimport type { EnhancedSidebarConfig } from '../../types'\n\nimport { EnhancedSidebarContext } from './context'\nimport { NavContent } from './NavContent'\nimport { SidebarWrapper } from './SidebarWrapper'\nimport { TabsBar } from './TabsBar'\n\nconst baseClass = 'enhanced-sidebar'\n\nexport type SidebarContentProps = {\n afterNav?: React.ReactNode\n afterNavLinks?: React.ReactNode\n allContent?: React.ReactNode\n beforeNav?: React.ReactNode\n beforeNavLinks?: React.ReactNode\n customNavContent?: React.ReactNode\n customTabComponents?: Record<string, React.ReactNode>\n initialActiveTabId: string\n renderedTabItems?: React.ReactNode[]\n settingsMenu?: React.ReactNode[]\n sidebarConfig: EnhancedSidebarConfig\n tabIcons?: Record<string, React.ReactNode>\n tabsContent: Record<string, React.ReactNode>\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 afterNav,\n afterNavLinks,\n allContent,\n beforeNav,\n beforeNavLinks,\n customNavContent,\n customTabComponents,\n initialActiveTabId,\n renderedTabItems,\n settingsMenu,\n sidebarConfig,\n tabIcons,\n tabsContent,\n}) => {\n const [activeTabId, setActiveTabId] = useState(initialActiveTabId)\n\n const handleTabChange = useCallback((tabId: string) => {\n setActiveTabId(tabId)\n setTabCookie(tabId)\n }, [])\n\n const contextValue = useMemo(\n () => ({ activeTabId, onTabChange: handleTabChange }),\n [activeTabId, handleTabChange],\n )\n\n const tabs = sidebarConfig.tabs?.filter((t) => t.type === 'tab') ?? []\n\n return (\n <EnhancedSidebarContext.Provider value={contextValue}>\n <SidebarWrapper baseClass={baseClass}>\n <TabsBar\n activeTabId={activeTabId}\n customTabComponents={customTabComponents}\n onTabChange={handleTabChange}\n renderedTabItems={renderedTabItems}\n settingsMenu={settingsMenu}\n sidebarConfig={sidebarConfig}\n tabIcons={tabIcons}\n />\n {customNavContent ?? (\n <NavContent\n afterNav={afterNav}\n afterNavLinks={afterNavLinks}\n allContent={allContent}\n beforeNav={beforeNav}\n beforeNavLinks={beforeNavLinks}\n tabs={tabs}\n tabsContent={tabsContent}\n />\n )}\n </SidebarWrapper>\n </EnhancedSidebarContext.Provider>\n )\n}\n"],"names":["React","useCallback","useMemo","useState","EnhancedSidebarContext","NavContent","SidebarWrapper","TabsBar","baseClass","COOKIE_KEY","setTabCookie","tabId","document","cookie","SidebarContent","afterNav","afterNavLinks","allContent","beforeNav","beforeNavLinks","customNavContent","customTabComponents","initialActiveTabId","renderedTabItems","settingsMenu","sidebarConfig","tabIcons","tabsContent","activeTabId","setActiveTabId","handleTabChange","contextValue","onTabChange","tabs","filter","t","type","Provider","value"],"mappings":"AAAA;;AAEA,OAAOA,SAASC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAI7D,SAASC,sBAAsB,QAAQ,YAAW;AAClD,SAASC,UAAU,QAAQ,eAAc;AACzC,SAASC,cAAc,QAAQ,mBAAkB;AACjD,SAASC,OAAO,QAAQ,YAAW;AAEnC,MAAMC,YAAY;AAkBlB,MAAMC,aAAa;AAEnB,MAAMC,eAAe,CAACC;IACpBC,SAASC,MAAM,GAAG,GAAGJ,WAAW,CAAC,EAAEE,MAAM,wCAAwC,CAAC;AACpF;AAEA,OAAO,MAAMG,iBAAgD,CAAC,EAC5DC,QAAQ,EACRC,aAAa,EACbC,UAAU,EACVC,SAAS,EACTC,cAAc,EACdC,gBAAgB,EAChBC,mBAAmB,EACnBC,kBAAkB,EAClBC,gBAAgB,EAChBC,YAAY,EACZC,aAAa,EACbC,QAAQ,EACRC,WAAW,EACZ;IACC,MAAM,CAACC,aAAaC,eAAe,GAAG1B,SAASmB;IAE/C,MAAMQ,kBAAkB7B,YAAY,CAACU;QACnCkB,eAAelB;QACfD,aAAaC;IACf,GAAG,EAAE;IAEL,MAAMoB,eAAe7B,QACnB,IAAO,CAAA;YAAE0B;YAAaI,aAAaF;QAAgB,CAAA,GACnD;QAACF;QAAaE;KAAgB;IAGhC,MAAMG,OAAOR,cAAcQ,IAAI,EAAEC,OAAO,CAACC,IAAMA,EAAEC,IAAI,KAAK,UAAU,EAAE;IAEtE,qBACE,KAAChC,uBAAuBiC,QAAQ;QAACC,OAAOP;kBACtC,cAAA,MAACzB;YAAeE,WAAWA;;8BACzB,KAACD;oBACCqB,aAAaA;oBACbP,qBAAqBA;oBACrBW,aAAaF;oBACbP,kBAAkBA;oBAClBC,cAAcA;oBACdC,eAAeA;oBACfC,UAAUA;;gBAEXN,kCACC,KAACf;oBACCU,UAAUA;oBACVC,eAAeA;oBACfC,YAAYA;oBACZC,WAAWA;oBACXC,gBAAgBA;oBAChBc,MAAMA;oBACNN,aAAaA;;;;;AAMzB,EAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { getTranslation } from '@payloadcms/translations';
|
|
4
|
-
import { Link, useTranslation } from '@payloadcms/ui';
|
|
5
|
-
import React from 'react';
|
|
4
|
+
import { Link, Tooltip, useTranslation } from '@payloadcms/ui';
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
6
|
import { Badge } from '../Badge';
|
|
7
7
|
import { useBadge } from '../hooks/useBadge';
|
|
8
8
|
import { Icon } from '../Icon';
|
|
@@ -11,34 +11,51 @@ export const TabButton = ({ icon, isActive, onTabChange, tab })=>{
|
|
|
11
11
|
const { i18n } = useTranslation();
|
|
12
12
|
const label = getTranslation(tab.label, i18n);
|
|
13
13
|
const { value } = useBadge(tab.badge, tab.id);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
const [hovered, setHovered] = useState(false);
|
|
15
|
+
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
16
|
+
children: /*#__PURE__*/ _jsxs("button", {
|
|
17
|
+
"aria-label": label,
|
|
18
|
+
className: `${tabsBaseClass}__tab ${isActive ? `${tabsBaseClass}__tab--active` : ''}`,
|
|
19
|
+
onBlur: ()=>setHovered(false),
|
|
20
|
+
onClick: ()=>onTabChange(tab.id),
|
|
21
|
+
onFocus: ()=>setHovered(true),
|
|
22
|
+
onMouseEnter: ()=>setHovered(true),
|
|
23
|
+
onMouseLeave: ()=>setHovered(false),
|
|
24
|
+
type: "button",
|
|
25
|
+
children: [
|
|
26
|
+
icon ?? /*#__PURE__*/ _jsx(Icon, {
|
|
27
|
+
name: tab.icon,
|
|
28
|
+
size: 20
|
|
29
|
+
}),
|
|
30
|
+
value !== undefined && /*#__PURE__*/ _jsx(Badge, {
|
|
31
|
+
color: tab.badge?.color,
|
|
32
|
+
position: "absolute",
|
|
33
|
+
value: value
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsx(Tooltip, {
|
|
36
|
+
alignCaret: "left",
|
|
37
|
+
show: hovered,
|
|
38
|
+
children: label
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
})
|
|
30
42
|
});
|
|
31
43
|
};
|
|
32
44
|
export const TabLink = ({ href, icon, isActive, link })=>{
|
|
33
45
|
const { i18n } = useTranslation();
|
|
34
46
|
const label = getTranslation(link.label, i18n);
|
|
35
47
|
const { value } = useBadge(link.badge, link.id);
|
|
48
|
+
const [hovered, setHovered] = useState(false);
|
|
36
49
|
return /*#__PURE__*/ _jsxs(Link, {
|
|
50
|
+
"aria-label": label,
|
|
37
51
|
className: `${tabsBaseClass}__link ${isActive ? `${tabsBaseClass}__link--active` : ''}`,
|
|
38
52
|
href: href,
|
|
53
|
+
onBlur: ()=>setHovered(false),
|
|
54
|
+
onFocus: ()=>setHovered(true),
|
|
55
|
+
onMouseEnter: ()=>setHovered(true),
|
|
56
|
+
onMouseLeave: ()=>setHovered(false),
|
|
39
57
|
rel: link.isExternal ? 'noopener noreferrer' : undefined,
|
|
40
58
|
target: link.isExternal ? '_blank' : undefined,
|
|
41
|
-
title: label,
|
|
42
59
|
children: [
|
|
43
60
|
icon ?? /*#__PURE__*/ _jsx(Icon, {
|
|
44
61
|
name: link.icon,
|
|
@@ -48,6 +65,11 @@ export const TabLink = ({ href, icon, isActive, link })=>{
|
|
|
48
65
|
color: link.badge?.color,
|
|
49
66
|
position: "absolute",
|
|
50
67
|
value: value
|
|
68
|
+
}),
|
|
69
|
+
/*#__PURE__*/ _jsx(Tooltip, {
|
|
70
|
+
alignCaret: "left",
|
|
71
|
+
show: hovered,
|
|
72
|
+
children: label
|
|
51
73
|
})
|
|
52
74
|
]
|
|
53
75
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/EnhancedSidebar/TabsBar/TabItem.tsx"],"sourcesContent":["'use client'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { Link, useTranslation } from '@payloadcms/ui'\nimport React from 'react'\n\nimport type { SidebarTabContent, SidebarTabLink } from '../../../types'\n\nimport { Badge } from '../Badge'\nimport { useBadge } from '../hooks/useBadge'\nimport { Icon } from '../Icon'\n\nconst tabsBaseClass = 'tabs-bar'\n\ntype TabButtonProps = {\n icon?: React.ReactNode\n isActive: boolean\n onTabChange: (tabId: string) => void\n tab: SidebarTabContent\n}\n\nexport const TabButton: React.FC<TabButtonProps> = ({ icon, isActive, onTabChange, tab }) => {\n const { i18n } = useTranslation()\n const label = getTranslation(tab.label, i18n)\n const { value } = useBadge(tab.badge, tab.id)\n\n return (\n <button\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/EnhancedSidebar/TabsBar/TabItem.tsx"],"sourcesContent":["'use client'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport { Link, Tooltip, useTranslation } from '@payloadcms/ui'\nimport React, { useState } from 'react'\n\nimport type { SidebarTabContent, SidebarTabLink } from '../../../types'\n\nimport { Badge } from '../Badge'\nimport { useBadge } from '../hooks/useBadge'\nimport { Icon } from '../Icon'\n\nconst tabsBaseClass = 'tabs-bar'\n\ntype TabButtonProps = {\n icon?: React.ReactNode\n isActive: boolean\n onTabChange: (tabId: string) => void\n tab: SidebarTabContent\n}\n\nexport const TabButton: React.FC<TabButtonProps> = ({ icon, isActive, onTabChange, tab }) => {\n const { i18n } = useTranslation()\n const label = getTranslation(tab.label, i18n)\n const { value } = useBadge(tab.badge, tab.id)\n const [hovered, setHovered] = useState(false)\n\n return (\n <>\n <button\n aria-label={label}\n className={`${tabsBaseClass}__tab ${isActive ? `${tabsBaseClass}__tab--active` : ''}`}\n onBlur={() => setHovered(false)}\n onClick={() => onTabChange(tab.id)}\n onFocus={() => setHovered(true)}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n type=\"button\"\n >\n {icon ?? <Icon name={tab.icon!} size={20} />}\n {value !== undefined && (\n <Badge color={tab.badge?.color} position=\"absolute\" value={value} />\n )}\n <Tooltip alignCaret=\"left\" show={hovered}>\n {label}\n </Tooltip>\n </button>\n </>\n )\n}\n\ntype TabLinkProps = {\n href: string\n icon?: React.ReactNode\n isActive: boolean\n link: SidebarTabLink\n}\n\nexport const TabLink: React.FC<TabLinkProps> = ({ href, icon, isActive, link }) => {\n const { i18n } = useTranslation()\n const label = getTranslation(link.label, i18n)\n const { value } = useBadge(link.badge, link.id)\n const [hovered, setHovered] = useState(false)\n\n return (\n <Link\n aria-label={label}\n className={`${tabsBaseClass}__link ${isActive ? `${tabsBaseClass}__link--active` : ''}`}\n href={href}\n onBlur={() => setHovered(false)}\n onFocus={() => setHovered(true)}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n rel={link.isExternal ? 'noopener noreferrer' : undefined}\n target={link.isExternal ? '_blank' : undefined}\n >\n {icon ?? <Icon name={link.icon!} size={20} />}\n {value !== undefined && <Badge color={link.badge?.color} position=\"absolute\" value={value} />}\n <Tooltip alignCaret=\"left\" show={hovered}>\n {label}\n </Tooltip>\n </Link>\n )\n}\n"],"names":["getTranslation","Link","Tooltip","useTranslation","React","useState","Badge","useBadge","Icon","tabsBaseClass","TabButton","icon","isActive","onTabChange","tab","i18n","label","value","badge","id","hovered","setHovered","button","aria-label","className","onBlur","onClick","onFocus","onMouseEnter","onMouseLeave","type","name","size","undefined","color","position","alignCaret","show","TabLink","href","link","rel","isExternal","target"],"mappings":"AAAA;;AAEA,SAASA,cAAc,QAAQ,2BAA0B;AACzD,SAASC,IAAI,EAAEC,OAAO,EAAEC,cAAc,QAAQ,iBAAgB;AAC9D,OAAOC,SAASC,QAAQ,QAAQ,QAAO;AAIvC,SAASC,KAAK,QAAQ,WAAU;AAChC,SAASC,QAAQ,QAAQ,oBAAmB;AAC5C,SAASC,IAAI,QAAQ,UAAS;AAE9B,MAAMC,gBAAgB;AAStB,OAAO,MAAMC,YAAsC,CAAC,EAAEC,IAAI,EAAEC,QAAQ,EAAEC,WAAW,EAAEC,GAAG,EAAE;IACtF,MAAM,EAAEC,IAAI,EAAE,GAAGZ;IACjB,MAAMa,QAAQhB,eAAec,IAAIE,KAAK,EAAED;IACxC,MAAM,EAAEE,KAAK,EAAE,GAAGV,SAASO,IAAII,KAAK,EAAEJ,IAAIK,EAAE;IAC5C,MAAM,CAACC,SAASC,WAAW,GAAGhB,SAAS;IAEvC,qBACE;kBACE,cAAA,MAACiB;YACCC,cAAYP;YACZQ,WAAW,GAAGf,cAAc,MAAM,EAAEG,WAAW,GAAGH,cAAc,aAAa,CAAC,GAAG,IAAI;YACrFgB,QAAQ,IAAMJ,WAAW;YACzBK,SAAS,IAAMb,YAAYC,IAAIK,EAAE;YACjCQ,SAAS,IAAMN,WAAW;YAC1BO,cAAc,IAAMP,WAAW;YAC/BQ,cAAc,IAAMR,WAAW;YAC/BS,MAAK;;gBAEJnB,sBAAQ,KAACH;oBAAKuB,MAAMjB,IAAIH,IAAI;oBAAGqB,MAAM;;gBACrCf,UAAUgB,2BACT,KAAC3B;oBAAM4B,OAAOpB,IAAII,KAAK,EAAEgB;oBAAOC,UAAS;oBAAWlB,OAAOA;;8BAE7D,KAACf;oBAAQkC,YAAW;oBAAOC,MAAMjB;8BAC9BJ;;;;;AAKX,EAAC;AASD,OAAO,MAAMsB,UAAkC,CAAC,EAAEC,IAAI,EAAE5B,IAAI,EAAEC,QAAQ,EAAE4B,IAAI,EAAE;IAC5E,MAAM,EAAEzB,IAAI,EAAE,GAAGZ;IACjB,MAAMa,QAAQhB,eAAewC,KAAKxB,KAAK,EAAED;IACzC,MAAM,EAAEE,KAAK,EAAE,GAAGV,SAASiC,KAAKtB,KAAK,EAAEsB,KAAKrB,EAAE;IAC9C,MAAM,CAACC,SAASC,WAAW,GAAGhB,SAAS;IAEvC,qBACE,MAACJ;QACCsB,cAAYP;QACZQ,WAAW,GAAGf,cAAc,OAAO,EAAEG,WAAW,GAAGH,cAAc,cAAc,CAAC,GAAG,IAAI;QACvF8B,MAAMA;QACNd,QAAQ,IAAMJ,WAAW;QACzBM,SAAS,IAAMN,WAAW;QAC1BO,cAAc,IAAMP,WAAW;QAC/BQ,cAAc,IAAMR,WAAW;QAC/BoB,KAAKD,KAAKE,UAAU,GAAG,wBAAwBT;QAC/CU,QAAQH,KAAKE,UAAU,GAAG,WAAWT;;YAEpCtB,sBAAQ,KAACH;gBAAKuB,MAAMS,KAAK7B,IAAI;gBAAGqB,MAAM;;YACtCf,UAAUgB,2BAAa,KAAC3B;gBAAM4B,OAAOM,KAAKtB,KAAK,EAAEgB;gBAAOC,UAAS;gBAAWlB,OAAOA;;0BACpF,KAACf;gBAAQkC,YAAW;gBAAOC,MAAMjB;0BAC9BJ;;;;AAIT,EAAC"}
|
|
@@ -25,6 +25,48 @@
|
|
|
25
25
|
padding-top: var(--app-header-height);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Override Payload Tooltip positioning — it defaults to top/bottom (for toolbars),
|
|
29
|
+
// but in a left sidebar we want it to appear to the right of the button.
|
|
30
|
+
&__tab,
|
|
31
|
+
&__link,
|
|
32
|
+
&__action {
|
|
33
|
+
.tooltip {
|
|
34
|
+
left: calc(100% + 8px);
|
|
35
|
+
top: 50%;
|
|
36
|
+
transform: translateY(-50%);
|
|
37
|
+
white-space: nowrap;
|
|
38
|
+
//Force z-index to very high value, to prevent it from being hidden by other elements
|
|
39
|
+
z-index: 1000;
|
|
40
|
+
|
|
41
|
+
// Neutralize the top/bottom transform that Payload applies
|
|
42
|
+
&--position-top,
|
|
43
|
+
&--position-bottom {
|
|
44
|
+
bottom: auto;
|
|
45
|
+
top: 50%;
|
|
46
|
+
transform: translateY(-50%);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Hide the caret — it doesn't make sense for right-side positioning
|
|
50
|
+
&::before,
|
|
51
|
+
&::after {
|
|
52
|
+
display: none;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// RTL: tooltip appears to the left of the button
|
|
57
|
+
[dir='rtl'] & .tooltip {
|
|
58
|
+
left: auto;
|
|
59
|
+
right: calc(100% + 8px);
|
|
60
|
+
|
|
61
|
+
&--position-top,
|
|
62
|
+
&--position-bottom {
|
|
63
|
+
bottom: auto;
|
|
64
|
+
top: 50%;
|
|
65
|
+
transform: translateY(-50%);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
28
70
|
&__tab,
|
|
29
71
|
&__link {
|
|
30
72
|
position: relative;
|
|
@@ -47,11 +47,11 @@ import './index.scss';
|
|
|
47
47
|
isExternal: item.isExternal,
|
|
48
48
|
label: item.label
|
|
49
49
|
});
|
|
50
|
-
// Filter custom items by access — fail-closed: missing req denies access
|
|
51
|
-
const accessResults = await Promise.all(customItems.map((item)=>item.access ? req ? item.access({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
// Filter custom items by access — fail-closed: missing req or thrown error denies access
|
|
51
|
+
const accessResults = await Promise.all(customItems.map((item)=>Promise.resolve().then(()=>item.access ? req ? item.access({
|
|
52
|
+
item,
|
|
53
|
+
req
|
|
54
|
+
}) : false : true).catch(()=>false)));
|
|
55
55
|
const visibleItems = customItems.filter((_, i)=>accessResults[i]);
|
|
56
56
|
for (const item of visibleItems){
|
|
57
57
|
if (item.group) {
|
|
@@ -109,7 +109,7 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
109
109
|
if (!payload?.config) {
|
|
110
110
|
return null;
|
|
111
111
|
}
|
|
112
|
-
const { admin: { components: { afterNavLinks, beforeNavLinks, settingsMenu } }, collections, globals } = payload.config;
|
|
112
|
+
const { admin: { components: { afterNav, afterNavLinks, beforeNav, beforeNavLinks, settingsMenu } }, collections, globals } = payload.config;
|
|
113
113
|
const groups = groupNavItems([
|
|
114
114
|
...collections.filter(({ slug })=>visibleEntities?.collections.includes(slug)).map((collection)=>({
|
|
115
115
|
type: EntityType.collection,
|
|
@@ -134,6 +134,12 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
134
134
|
documentSubViewType,
|
|
135
135
|
viewType
|
|
136
136
|
};
|
|
137
|
+
const beforeNavRendered = RenderServerComponent({
|
|
138
|
+
clientProps,
|
|
139
|
+
Component: beforeNav,
|
|
140
|
+
importMap: payload.importMap,
|
|
141
|
+
serverProps
|
|
142
|
+
});
|
|
137
143
|
const beforeNavLinksRendered = RenderServerComponent({
|
|
138
144
|
clientProps,
|
|
139
145
|
Component: beforeNavLinks,
|
|
@@ -146,6 +152,12 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
146
152
|
importMap: payload.importMap,
|
|
147
153
|
serverProps
|
|
148
154
|
});
|
|
155
|
+
const afterNavRendered = RenderServerComponent({
|
|
156
|
+
clientProps,
|
|
157
|
+
Component: afterNav,
|
|
158
|
+
importMap: payload.importMap,
|
|
159
|
+
serverProps
|
|
160
|
+
});
|
|
149
161
|
const renderedSettingsMenu = settingsMenu && Array.isArray(settingsMenu) ? settingsMenu.map((item, index)=>RenderServerComponent({
|
|
150
162
|
clientProps,
|
|
151
163
|
Component: item,
|
|
@@ -166,16 +178,16 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
166
178
|
// Filter all tab bar items by access — fail-closed: missing req denies access
|
|
167
179
|
// Note: req can be undefined on 404 pages due to a Payload bug (NotFound view omits req from props)
|
|
168
180
|
const allConfigTabs = config.tabs ?? [];
|
|
169
|
-
const tabAccessResults = await Promise.all(allConfigTabs.map((t)=>t.access ? req ? t.access({
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
const tabAccessResults = await Promise.all(allConfigTabs.map((t)=>Promise.resolve().then(()=>t.access ? req ? t.access({
|
|
182
|
+
item: t,
|
|
183
|
+
req
|
|
184
|
+
}) : false : true).catch(()=>false)));
|
|
173
185
|
const visibleTabItems = allConfigTabs.filter((_, i)=>tabAccessResults[i]);
|
|
174
186
|
// Read active tab from cookie
|
|
175
187
|
const cookieStore = await cookies();
|
|
176
188
|
const storedTabId = cookieStore.get(COOKIE_KEY)?.value;
|
|
177
189
|
const tabs = visibleTabItems.filter((t)=>t.type === 'tab');
|
|
178
|
-
const defaultTabId = tabs[0]?.id ?? '
|
|
190
|
+
const defaultTabId = tabs[0]?.id ?? '';
|
|
179
191
|
const initialActiveTabId = storedTabId && tabs.some((t)=>t.id === storedTabId) ? storedTabId : defaultTabId;
|
|
180
192
|
const adminRoute = payload.config.routes.admin;
|
|
181
193
|
const currentLang = i18n.language;
|
|
@@ -275,8 +287,10 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
275
287
|
children: tabGroups.map((group, j)=>renderGroup(group, `${tab.id}-${j}`))
|
|
276
288
|
});
|
|
277
289
|
}
|
|
278
|
-
// For the no-tabs fallback
|
|
279
|
-
|
|
290
|
+
// For the no-tabs fallback — only show all content when the config has no tab-type items at all.
|
|
291
|
+
// If tabs exist but are all hidden by access control, show nothing instead of the full nav.
|
|
292
|
+
const configuredTabsCount = (config.tabs ?? []).filter((t)=>t.type === 'tab').length;
|
|
293
|
+
const allContent = configuredTabsCount === 0 ? /*#__PURE__*/ _jsx(Fragment, {
|
|
280
294
|
children: groups.map((group, i)=>renderGroup(group, `all-${i}`))
|
|
281
295
|
}) : undefined;
|
|
282
296
|
// Build server-side icon and tab button rendering
|
|
@@ -371,8 +385,10 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
371
385
|
const { clientProps: extraProps, path } = resolveSidebarComponent(config.customComponents.NavContent);
|
|
372
386
|
return RenderServerComponent({
|
|
373
387
|
clientProps: {
|
|
388
|
+
afterNav: afterNavRendered,
|
|
374
389
|
afterNavLinks: afterNavLinksRendered,
|
|
375
390
|
allContent,
|
|
391
|
+
beforeNav: beforeNavRendered,
|
|
376
392
|
beforeNavLinks: beforeNavLinksRendered,
|
|
377
393
|
tabs: tabs.map((t)=>({
|
|
378
394
|
id: t.id
|
|
@@ -392,8 +408,10 @@ export const EnhancedSidebar = async (props)=>{
|
|
|
392
408
|
tabs: visibleTabItems
|
|
393
409
|
});
|
|
394
410
|
return /*#__PURE__*/ _jsx(SidebarContent, {
|
|
411
|
+
afterNav: afterNavRendered,
|
|
395
412
|
afterNavLinks: afterNavLinksRendered,
|
|
396
413
|
allContent: allContent,
|
|
414
|
+
beforeNav: beforeNavRendered,
|
|
397
415
|
beforeNavLinks: beforeNavLinksRendered,
|
|
398
416
|
customNavContent: customNavContent,
|
|
399
417
|
customTabComponents: Object.keys(customTabComponents).length > 0 ? customTabComponents : undefined,
|
|
@@ -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'\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'\n\nimport { extractLocalizedValue, resolveSidebarComponent, sanitizeSidebarConfig } from '../../utils'\nimport { getNavPrefs } from './getNavPrefs'\nimport { Icon } from './Icon'\nimport { NavItem } from './NavItem'\nimport { SidebarContent } from './SidebarContent'\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 denies access\n const accessResults = await Promise.all(\n customItems.map((item) => (item.access ? (req ? item.access({ item, req }) : false) : true)),\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: { afterNavLinks, 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 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 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) => (t.access ? (req ? t.access({ item: t, req }) : false) : true)),\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 ?? 'default'\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\n const allContent =\n tabs.length === 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 afterNavLinks: afterNavLinksRendered,\n allContent,\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 afterNavLinks={afterNavLinksRendered}\n allContent={allContent}\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","access","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","afterNavLinks","beforeNavLinks","settingsMenu","includes","collection","global","navPreferences","serverProps","clientProps","beforeNavLinksRendered","Component","importMap","afterNavLinksRendered","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","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'\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'\n\nimport { extractLocalizedValue, resolveSidebarComponent, sanitizeSidebarConfig } from '../../utils'\nimport { getNavPrefs } from './getNavPrefs'\nimport { Icon } from './Icon'\nimport { NavItem } from './NavItem'\nimport { SidebarContent } from './SidebarContent'\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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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -290,6 +290,7 @@ export type SidebarTabItem = ExternalHrefItem | InternalHrefItem;
|
|
|
290
290
|
* @example
|
|
291
291
|
* ```tsx
|
|
292
292
|
* 'use client'
|
|
293
|
+
* import React from 'react'
|
|
293
294
|
* import { useNavItemState } from '@veiag/payload-enhanced-sidebar'
|
|
294
295
|
* import type { CustomNavItemProps } from '@veiag/payload-enhanced-sidebar'
|
|
295
296
|
*
|
|
@@ -317,6 +318,7 @@ export type CustomNavItemProps = {
|
|
|
317
318
|
* @example
|
|
318
319
|
* ```tsx
|
|
319
320
|
* 'use client'
|
|
321
|
+
* import React from 'react'
|
|
320
322
|
* import type { CustomNavGroupProps } from '@veiag/payload-enhanced-sidebar'
|
|
321
323
|
*
|
|
322
324
|
* export const MyNavGroup: React.FC<CustomNavGroupProps> = ({ label, isOpen, children }) => {
|
|
@@ -338,6 +340,7 @@ export type CustomNavGroupProps = {
|
|
|
338
340
|
* @example
|
|
339
341
|
* ```tsx
|
|
340
342
|
* 'use client'
|
|
343
|
+
* import React from 'react'
|
|
341
344
|
* import type { CustomTabIconProps } from '@veiag/payload-enhanced-sidebar'
|
|
342
345
|
*
|
|
343
346
|
* export const MyIcon: React.FC<CustomTabIconProps> = ({ id, label }) => (
|
|
@@ -363,6 +366,7 @@ export type CustomTabIconProps = {
|
|
|
363
366
|
* @example
|
|
364
367
|
* ```tsx
|
|
365
368
|
* 'use client'
|
|
369
|
+
* import React from 'react'
|
|
366
370
|
* import type { CustomTabButtonProps } from '@veiag/payload-enhanced-sidebar'
|
|
367
371
|
* import { useTabState, useEnhancedSidebar } from '@veiag/payload-enhanced-sidebar'
|
|
368
372
|
*
|
|
@@ -421,10 +425,14 @@ export type CustomTabButtonProps = {
|
|
|
421
425
|
* ```
|
|
422
426
|
*/
|
|
423
427
|
export type CustomNavContentProps = {
|
|
428
|
+
/** Rendered afterNav from payload config (rendered after afterNavLinks) */
|
|
429
|
+
afterNav?: ReactNode;
|
|
424
430
|
/** Rendered afterNavLinks from payload config */
|
|
425
431
|
afterNavLinks?: ReactNode;
|
|
426
432
|
/** Content to show when no tabs are defined */
|
|
427
433
|
allContent?: ReactNode;
|
|
434
|
+
/** Rendered beforeNav from payload config (rendered before beforeNavLinks) */
|
|
435
|
+
beforeNav?: ReactNode;
|
|
428
436
|
/** Rendered beforeNavLinks from payload config */
|
|
429
437
|
beforeNavLinks?: ReactNode;
|
|
430
438
|
/** Tab definitions (id only) for mapping over */
|
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 { useNavItemState } from '@veiag/payload-enhanced-sidebar'\n * import type { CustomNavItemProps } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyNavItem: React.FC<CustomNavItemProps> = ({ entity, href, id, label, badgeConfig }) => {\n * const { isActive, isCurrentPage } = useNavItemState(href)\n * return <a href={href}>{label}</a>\n * }\n * ```\n */\nexport type CustomNavItemProps = {\n /** Badge configuration as defined in the plugin config */\n badgeConfig?: BadgeConfig\n /** The entity (collection, global, or custom item) */\n entity: ExtendedEntity\n /** Computed href with admin route prefix applied */\n href: string\n /** DOM element id */\n id: string\n /** Pre-translated label string */\n label: string\n}\n\n/**\n * Props received by a custom NavGroup component registered via `customComponents.NavGroup`.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomNavGroupProps } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyNavGroup: React.FC<CustomNavGroupProps> = ({ label, isOpen, children }) => {\n * return <div><strong>{label}</strong>{children}</div>\n * }\n * ```\n */\nexport type CustomNavGroupProps = {\n /** Nav items inside the group */\n children: ReactNode\n /** Initial open state from nav preferences */\n isOpen?: boolean\n /** Translated group label */\n label: string\n}\n\n/**\n * Props received by a custom tab icon component set via `iconComponent` on a tab or link.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomTabIconProps } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyIcon: React.FC<CustomTabIconProps> = ({ id, label }) => (\n * <img alt={label} src={`/icons/${id}.svg`} width={20} height={20} />\n * )\n * ```\n */\nexport type CustomTabIconProps = {\n /** Tab/link id */\n id: string\n /** Pre-translated label */\n label: string\n /** Whether this item is a tab or a link */\n type: 'link' | 'tab'\n}\n\n/**\n * Props received by a custom TabButton component registered via `customComponents.TabButton`.\n * Used for both `tab` and `link` type items in the tabs bar.\n *\n * Use `useTabState(id)` for tab active state, or `usePathname()` for link active state.\n * Use `useEnhancedSidebar().onTabChange` to trigger tab switches.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomTabButtonProps } from '@veiag/payload-enhanced-sidebar'\n * import { useTabState, useEnhancedSidebar } from '@veiag/payload-enhanced-sidebar'\n *\n * export const MyTabButton: React.FC<CustomTabButtonProps> = ({ id, type, icon, label, href }) => {\n * const { isActive } = useTabState(id)\n * const { onTabChange } = useEnhancedSidebar()\n * if (type === 'link') return <a href={href}>{icon}{label}</a>\n * return <button onClick={() => onTabChange(id)}>{icon}{label}</button>\n * }\n * ```\n */\nexport type CustomTabButtonProps = {\n /** Badge configuration as defined in the plugin config */\n badge?: BadgeConfig\n /** Computed href (for link type, with admin route prefix applied) */\n href?: string\n /** Pre-rendered icon — either from `iconComponent` or the default Lucide icon */\n icon: ReactNode\n /** Tab/link id */\n id: string\n /** Whether the link is external (only set for link type) */\n isExternal?: boolean\n /** Pre-translated label */\n label: string\n /** Item type — use to differentiate rendering */\n type: 'link' | 'tab'\n}\n\n/**\n * Props received by a custom NavContent component registered via `customComponents.NavContent`.\n *\n * Renders in place of the default `<nav>` content area. Use the `useTabState(id)` hook\n * to determine which tab is active and show/hide content accordingly.\n *\n * @example\n * ```tsx\n * 'use client'\n * import type { CustomNavContentProps } from '@veiag/payload-enhanced-sidebar'\n * import { useTabState } from '@veiag/payload-enhanced-sidebar'\n *\n * const TabPanel = ({ id, content }: { id: string; content: React.ReactNode }) => {\n * const { isActive } = useTabState(id)\n * return <div style={{ display: isActive ? undefined : 'none' }}>{content}</div>\n * }\n *\n * export const MyNavContent: React.FC<CustomNavContentProps> = ({\n * tabs, tabsContent, beforeNavLinks, afterNavLinks,\n * }) => {\n * return (\n * <nav>\n * {beforeNavLinks}\n * {tabs.map(tab => <TabPanel key={tab.id} id={tab.id} content={tabsContent[tab.id]} />)}\n * {afterNavLinks}\n * </nav>\n * )\n * }\n * ```\n */\nexport type CustomNavContentProps = {\n /** Rendered afterNavLinks from payload config */\n afterNavLinks?: ReactNode\n /** Content to show when no tabs are defined */\n allContent?: ReactNode\n /** Rendered beforeNavLinks from payload config */\n beforeNavLinks?: ReactNode\n /** Tab definitions (id only) for mapping over */\n tabs: Array<{ id: string }>\n /** Pre-rendered content per tab id */\n tabsContent: Record<string, ReactNode>\n}\n\n/**\n * Configuration for the enhanced sidebar\n */\nexport interface EnhancedSidebarConfig {\n /**\n * Badge configurations for sidebar items (collections, globals, custom items).\n * Key is the slug of the item.\n *\n * @example\n * ```typescript\n * badges: {\n * 'posts': { type: 'collection-count', color: 'primary' },\n * 'orders': { type: 'api', endpoint: '/api/orders/pending', responseKey: 'count' },\n * 'notifications': { type: 'provider', color: 'error' },\n * }\n * ```\n */\n badges?: Record<string, BadgeConfig>\n\n /**\n * Custom components to replace the default NavItem, NavGroup, and/or NavContent rendering.\n * Each field accepts a plain path string or a `SidebarComponent` object `{ path, clientProps }`\n * to forward additional props. The plugin registers them in the import map automatically.\n *\n * @example\n * ```typescript\n * customComponents: {\n * // Plain path\n * NavItem: './components/MySidebar#MyNavItem',\n * // With extra client props forwarded to the component\n * NavGroup: { path: './components/MySidebar#MyNavGroup', clientProps: { collapsible: true } },\n * }\n * ```\n */\n customComponents?: {\n /**\n * Custom NavContent component. Replaces the default `<nav>` content area.\n * Receives `CustomNavContentProps`. Use `useTabState(id)` to check if a tab is active.\n */\n NavContent?: SidebarComponent\n /** Custom NavGroup component. Receives `CustomNavGroupProps`. */\n NavGroup?: SidebarComponent\n /** Custom NavItem component. Receives `CustomNavItemProps`. */\n NavItem?: SidebarComponent\n /**\n * Custom tab button component. Used for both `tab` and `link` items in the tabs bar.\n * Receives `CustomTabButtonProps`. Use `useTabState(id)` and `useEnhancedSidebar()` for state.\n */\n TabButton?: SidebarComponent\n }\n\n /**\n * Disable the plugin\n * @default false\n */\n disabled?: boolean\n\n /**\n * 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":"AAikBA,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 */\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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veiag/payload-enhanced-sidebar",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "An enhanced sidebar plugin for Payload CMS with tabbed navigation to organize collections and globals.",
|
|
5
5
|
"author": "VeiaG",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
],
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@eslint/eslintrc": "^3.2.0",
|
|
32
|
-
"@payloadcms/db-mongodb": "3.
|
|
33
|
-
"@payloadcms/db-postgres": "3.
|
|
34
|
-
"@payloadcms/db-sqlite": "3.
|
|
32
|
+
"@payloadcms/db-mongodb": "3.75.0",
|
|
33
|
+
"@payloadcms/db-postgres": "3.75.0",
|
|
34
|
+
"@payloadcms/db-sqlite": "3.75.0",
|
|
35
35
|
"@payloadcms/eslint-config": "3.9.0",
|
|
36
|
-
"@payloadcms/next": "3.
|
|
37
|
-
"@payloadcms/plugin-multi-tenant": "3.
|
|
38
|
-
"@payloadcms/richtext-lexical": "3.
|
|
39
|
-
"@payloadcms/ui": "3.
|
|
36
|
+
"@payloadcms/next": "3.75.0",
|
|
37
|
+
"@payloadcms/plugin-multi-tenant": "3.75.0",
|
|
38
|
+
"@payloadcms/richtext-lexical": "3.75.0",
|
|
39
|
+
"@payloadcms/ui": "3.75.0",
|
|
40
40
|
"@playwright/test": "1.56.1",
|
|
41
41
|
"@swc-node/register": "1.10.9",
|
|
42
42
|
"@swc/cli": "0.6.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"mongodb-memory-server": "10.1.4",
|
|
52
52
|
"next": "15.4.8",
|
|
53
53
|
"open": "^10.1.0",
|
|
54
|
-
"payload": "3.
|
|
54
|
+
"payload": "3.75.0",
|
|
55
55
|
"prettier": "^3.4.2",
|
|
56
56
|
"qs-esm": "7.0.2",
|
|
57
57
|
"react": "19.2.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"vitest": "^3.1.2"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|
|
67
|
-
"payload": "^3.
|
|
67
|
+
"payload": "^3.75.0"
|
|
68
68
|
},
|
|
69
69
|
"engines": {
|
|
70
70
|
"node": "^18.20.2 || >=20.9.0",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"registry": "https://registry.npmjs.org/",
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@payloadcms/translations": "^3.
|
|
75
|
+
"@payloadcms/translations": "^3.75.0",
|
|
76
76
|
"lucide-react": "^0.556.0"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|