@djangocfg/layouts 2.1.107 → 2.1.109
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
CHANGED
|
@@ -138,7 +138,11 @@ import { PublicLayout, PrivateLayout, AuthLayout } from '@djangocfg/layouts';
|
|
|
138
138
|
|
|
139
139
|
// Private page
|
|
140
140
|
<PrivateLayout
|
|
141
|
-
sidebar={{
|
|
141
|
+
sidebar={{
|
|
142
|
+
groups: [
|
|
143
|
+
{ label: 'Main', items: menuItems }
|
|
144
|
+
]
|
|
145
|
+
}}
|
|
142
146
|
header={{ title: 'Dashboard' }}
|
|
143
147
|
>
|
|
144
148
|
{children}
|
|
@@ -669,15 +673,18 @@ export default function DashboardPage() {
|
|
|
669
673
|
return (
|
|
670
674
|
<PrivateLayout
|
|
671
675
|
sidebar={{
|
|
672
|
-
|
|
673
|
-
{
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
676
|
+
groups: [
|
|
677
|
+
{
|
|
678
|
+
label: 'Main',
|
|
679
|
+
items: [
|
|
680
|
+
{ label: 'Dashboard', href: '/dashboard', icon: 'LayoutDashboard' },
|
|
681
|
+
{ label: 'Settings', href: '/settings', icon: 'Settings' }
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
],
|
|
685
|
+
homeHref: '/dashboard'
|
|
680
686
|
}}
|
|
687
|
+
header={{ title: 'Dashboard' }}
|
|
681
688
|
>
|
|
682
689
|
<h1>Dashboard</h1>
|
|
683
690
|
</PrivateLayout>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.109",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -74,11 +74,11 @@
|
|
|
74
74
|
"check": "tsc --noEmit"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
|
-
"@djangocfg/api": "^2.1.
|
|
78
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
79
|
-
"@djangocfg/ui-core": "^2.1.
|
|
80
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
81
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
77
|
+
"@djangocfg/api": "^2.1.109",
|
|
78
|
+
"@djangocfg/centrifugo": "^2.1.109",
|
|
79
|
+
"@djangocfg/ui-core": "^2.1.109",
|
|
80
|
+
"@djangocfg/ui-nextjs": "^2.1.109",
|
|
81
|
+
"@djangocfg/ui-tools": "^2.1.109",
|
|
82
82
|
"@hookform/resolvers": "^5.2.0",
|
|
83
83
|
"consola": "^3.4.2",
|
|
84
84
|
"lucide-react": "^0.545.0",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"p-retry": "^7.0.0",
|
|
88
88
|
"react": "^19.1.0",
|
|
89
89
|
"react-dom": "^19.1.0",
|
|
90
|
-
"react-hook-form": "7.
|
|
90
|
+
"react-hook-form": "^7.69.0",
|
|
91
91
|
"sonner": "2.0.7",
|
|
92
92
|
"swr": "^2.3.7",
|
|
93
93
|
"tailwindcss": "^4.1.14",
|
|
@@ -101,12 +101,12 @@
|
|
|
101
101
|
"uuid": "^11.1.0"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@djangocfg/api": "^2.1.
|
|
105
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
106
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
107
|
-
"@djangocfg/ui-core": "^2.1.
|
|
108
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
109
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
104
|
+
"@djangocfg/api": "^2.1.109",
|
|
105
|
+
"@djangocfg/centrifugo": "^2.1.109",
|
|
106
|
+
"@djangocfg/typescript-config": "^2.1.109",
|
|
107
|
+
"@djangocfg/ui-core": "^2.1.109",
|
|
108
|
+
"@djangocfg/ui-nextjs": "^2.1.109",
|
|
109
|
+
"@djangocfg/ui-tools": "^2.1.109",
|
|
110
110
|
"@types/node": "^24.7.2",
|
|
111
111
|
"@types/react": "^19.1.0",
|
|
112
112
|
"@types/react-dom": "^19.1.0",
|
|
@@ -59,6 +59,7 @@ import { UserMenuConfig } from '../types';
|
|
|
59
59
|
import { PrivateContent, PrivateHeader, PrivateSidebar } from './components';
|
|
60
60
|
|
|
61
61
|
import type { LucideIcon as LucideIconType } from 'lucide-react';
|
|
62
|
+
|
|
62
63
|
export interface SidebarItem {
|
|
63
64
|
label: string;
|
|
64
65
|
href: string;
|
|
@@ -66,8 +67,19 @@ export interface SidebarItem {
|
|
|
66
67
|
badge?: string | number;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
export interface
|
|
70
|
+
export interface SidebarGroupConfig {
|
|
71
|
+
/** Group label displayed above items */
|
|
72
|
+
label: string;
|
|
73
|
+
/** Items in this group */
|
|
70
74
|
items: SidebarItem[];
|
|
75
|
+
/** If true, group is only shown when it has items (for dynamic groups like extensions) */
|
|
76
|
+
dynamic?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SidebarConfig {
|
|
80
|
+
/** Grouped items with labels */
|
|
81
|
+
groups: SidebarGroupConfig[];
|
|
82
|
+
/** Home link href */
|
|
71
83
|
homeHref?: string;
|
|
72
84
|
}
|
|
73
85
|
|
|
@@ -18,7 +18,7 @@ import { cn } from '@djangocfg/ui-core/lib';
|
|
|
18
18
|
|
|
19
19
|
import { LucideIcon } from '../../../components';
|
|
20
20
|
|
|
21
|
-
import type { SidebarItem, SidebarConfig } from '../PrivateLayout';
|
|
21
|
+
import type { SidebarItem, SidebarConfig, SidebarGroupConfig } from '../PrivateLayout';
|
|
22
22
|
|
|
23
23
|
interface PrivateSidebarProps {
|
|
24
24
|
sidebar: SidebarConfig;
|
|
@@ -29,18 +29,71 @@ export function PrivateSidebar({ sidebar }: PrivateSidebarProps) {
|
|
|
29
29
|
const { state, isMobile } = useSidebar();
|
|
30
30
|
const homeHref = sidebar.homeHref || '/';
|
|
31
31
|
|
|
32
|
+
// Get all items for active detection
|
|
33
|
+
const allItems = React.useMemo(() => {
|
|
34
|
+
return sidebar.groups.flatMap((g) => g.items);
|
|
35
|
+
}, [sidebar.groups]);
|
|
36
|
+
|
|
32
37
|
const isActive = (href: string) => {
|
|
33
38
|
const matches = pathname === href || pathname.startsWith(href + '/');
|
|
34
39
|
if (!matches) return false;
|
|
35
40
|
|
|
36
41
|
// Check if there's a more specific (longer) path that also matches
|
|
37
|
-
return !
|
|
38
|
-
otherItem
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
return !allItems.some(
|
|
43
|
+
(otherItem) =>
|
|
44
|
+
otherItem.href !== href &&
|
|
45
|
+
otherItem.href.startsWith(href + '/') &&
|
|
46
|
+
(pathname === otherItem.href ||
|
|
47
|
+
pathname.startsWith(otherItem.href + '/'))
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Render a single menu item
|
|
52
|
+
const renderMenuItem = (item: SidebarItem) => {
|
|
53
|
+
const active = isActive(item.href);
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<SidebarMenuItem key={item.href}>
|
|
57
|
+
<SidebarMenuButton
|
|
58
|
+
asChild
|
|
59
|
+
isActive={active}
|
|
60
|
+
tooltip={item.label}
|
|
61
|
+
size={isMobile ? 'lg' : 'default'}
|
|
62
|
+
>
|
|
63
|
+
<Link href={item.href}>
|
|
64
|
+
{item.icon && (
|
|
65
|
+
<LucideIcon
|
|
66
|
+
icon={typeof item.icon === 'string' ? item.icon : item.icon}
|
|
67
|
+
className={isMobile ? 'h-5 w-5' : 'h-4 w-4'}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
<span className={isMobile ? 'text-base' : ''}>{item.label}</span>
|
|
71
|
+
{item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>}
|
|
72
|
+
</Link>
|
|
73
|
+
</SidebarMenuButton>
|
|
74
|
+
</SidebarMenuItem>
|
|
41
75
|
);
|
|
42
76
|
};
|
|
43
77
|
|
|
78
|
+
// Render groups
|
|
79
|
+
const renderContent = () => {
|
|
80
|
+
return sidebar.groups.map((group) => {
|
|
81
|
+
// Skip dynamic groups with no items
|
|
82
|
+
if (group.dynamic && group.items.length === 0) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<SidebarGroup key={group.label}>
|
|
88
|
+
<SidebarGroupLabel className="font-medium text-[10px]">{group.label}</SidebarGroupLabel>
|
|
89
|
+
<SidebarGroupContent>
|
|
90
|
+
<SidebarMenu>{group.items.map(renderMenuItem)}</SidebarMenu>
|
|
91
|
+
</SidebarGroupContent>
|
|
92
|
+
</SidebarGroup>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
44
97
|
return (
|
|
45
98
|
<Sidebar collapsible="icon">
|
|
46
99
|
<SidebarHeader>
|
|
@@ -88,47 +141,7 @@ export function PrivateSidebar({ sidebar }: PrivateSidebarProps) {
|
|
|
88
141
|
</div>
|
|
89
142
|
</SidebarHeader>
|
|
90
143
|
|
|
91
|
-
<SidebarContent>
|
|
92
|
-
<SidebarGroup>
|
|
93
|
-
<SidebarGroupContent>
|
|
94
|
-
<SidebarMenu>
|
|
95
|
-
{sidebar.items.map((item) => {
|
|
96
|
-
const active = isActive(item.href);
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<SidebarMenuItem key={item.href}>
|
|
100
|
-
<SidebarMenuButton
|
|
101
|
-
asChild
|
|
102
|
-
isActive={active}
|
|
103
|
-
tooltip={item.label}
|
|
104
|
-
size={isMobile ? 'lg' : 'default'}
|
|
105
|
-
>
|
|
106
|
-
<Link href={item.href}>
|
|
107
|
-
{item.icon && (
|
|
108
|
-
<LucideIcon
|
|
109
|
-
icon={
|
|
110
|
-
typeof item.icon === 'string'
|
|
111
|
-
? item.icon
|
|
112
|
-
: item.icon
|
|
113
|
-
}
|
|
114
|
-
className={isMobile ? 'h-5 w-5' : 'h-4 w-4'}
|
|
115
|
-
/>
|
|
116
|
-
)}
|
|
117
|
-
<span className={isMobile ? 'text-base' : ''}>
|
|
118
|
-
{item.label}
|
|
119
|
-
</span>
|
|
120
|
-
{item.badge && (
|
|
121
|
-
<SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
|
|
122
|
-
)}
|
|
123
|
-
</Link>
|
|
124
|
-
</SidebarMenuButton>
|
|
125
|
-
</SidebarMenuItem>
|
|
126
|
-
);
|
|
127
|
-
})}
|
|
128
|
-
</SidebarMenu>
|
|
129
|
-
</SidebarGroupContent>
|
|
130
|
-
</SidebarGroup>
|
|
131
|
-
</SidebarContent>
|
|
144
|
+
<SidebarContent>{renderContent()}</SidebarContent>
|
|
132
145
|
</Sidebar>
|
|
133
146
|
);
|
|
134
147
|
}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { PrivateLayout } from './PrivateLayout';
|
|
6
|
-
export type { PrivateLayoutProps, SidebarItem, SidebarConfig, HeaderConfig } from './PrivateLayout';
|
|
6
|
+
export type { PrivateLayoutProps, SidebarItem, SidebarGroupConfig, SidebarConfig, HeaderConfig } from './PrivateLayout';
|
|
7
7
|
|