@djangocfg/layouts 1.2.58 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/layouts/AppLayout/AppLayout.tsx +11 -7
- package/src/layouts/AppLayout/components/UpdateNotifier/UpdateNotifier.tsx +170 -0
- package/src/layouts/AppLayout/components/UpdateNotifier/index.ts +2 -0
- package/src/layouts/AppLayout/components/index.ts +2 -2
- package/src/layouts/AppLayout/context/AppContext.tsx +4 -4
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +1 -8
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +1 -8
- package/src/layouts/AppLayout/components/PackageVersions/PackageVersions.tsx +0 -101
- package/src/layouts/AppLayout/components/PackageVersions/index.ts +0 -7
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +0 -65
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"check": "tsc --noEmit"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@djangocfg/api": "^1.
|
|
67
|
-
"@djangocfg/og-image": "^1.
|
|
68
|
-
"@djangocfg/ui": "^1.
|
|
66
|
+
"@djangocfg/api": "^1.4.0",
|
|
67
|
+
"@djangocfg/og-image": "^1.4.0",
|
|
68
|
+
"@djangocfg/ui": "^1.4.0",
|
|
69
69
|
"@hookform/resolvers": "^5.2.0",
|
|
70
70
|
"consola": "^3.4.2",
|
|
71
71
|
"lucide-react": "^0.468.0",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"vidstack": "0.6.15"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@djangocfg/typescript-config": "^1.
|
|
89
|
+
"@djangocfg/typescript-config": "^1.4.0",
|
|
90
90
|
"@types/node": "^24.7.2",
|
|
91
91
|
"@types/react": "19.2.2",
|
|
92
92
|
"@types/react-dom": "19.2.1",
|
|
@@ -29,7 +29,7 @@ import { useRouter } from 'next/router';
|
|
|
29
29
|
import dynamic from 'next/dynamic';
|
|
30
30
|
import { AppContextProvider } from './context';
|
|
31
31
|
import { CoreProviders } from './providers';
|
|
32
|
-
import { Seo, PageProgress, ErrorBoundary } from './components';
|
|
32
|
+
import { Seo, PageProgress, ErrorBoundary, UpdateNotifier } from './components';
|
|
33
33
|
import { PublicLayout } from './layouts/PublicLayout';
|
|
34
34
|
import { PrivateLayout } from './layouts/PrivateLayout';
|
|
35
35
|
import { AuthLayout } from './layouts/AuthLayout';
|
|
@@ -40,6 +40,7 @@ import type { AppLayoutConfig } from './types';
|
|
|
40
40
|
import type { ValidationErrorConfig, CORSErrorConfig, NetworkErrorConfig } from '../../validation';
|
|
41
41
|
import type { PageWithConfig } from '../../types/pageConfig';
|
|
42
42
|
import { determinePageConfig } from '../../types/pageConfig';
|
|
43
|
+
import packageJson from '../../../package.json';
|
|
43
44
|
|
|
44
45
|
// Dynamic import for AdminLayout to prevent SSR hydration issues
|
|
45
46
|
const AdminLayout = dynamic(
|
|
@@ -84,11 +85,11 @@ export interface AppLayoutProps {
|
|
|
84
85
|
*/
|
|
85
86
|
fontFamily?: string;
|
|
86
87
|
/**
|
|
87
|
-
* Show
|
|
88
|
-
* @default
|
|
89
|
-
* @example
|
|
88
|
+
* Show update notifier (checks npm for new versions)
|
|
89
|
+
* @default true
|
|
90
|
+
* @example showUpdateNotifier={false}
|
|
90
91
|
*/
|
|
91
|
-
|
|
92
|
+
showUpdateNotifier?: boolean;
|
|
92
93
|
/**
|
|
93
94
|
* Validation error tracking configuration
|
|
94
95
|
* @default { enabled: true, showToast: true, maxErrors: 50 }
|
|
@@ -276,7 +277,7 @@ function LayoutRouter({
|
|
|
276
277
|
* </AppLayout>
|
|
277
278
|
* ```
|
|
278
279
|
*/
|
|
279
|
-
export function AppLayout({ children, config, component, pageProps, disableLayout = false, forceLayout, fontFamily,
|
|
280
|
+
export function AppLayout({ children, config, component, pageProps, disableLayout = false, forceLayout, fontFamily, showUpdateNotifier, validation, cors, network }: AppLayoutProps) {
|
|
280
281
|
const router = useRouter();
|
|
281
282
|
|
|
282
283
|
// Check if ErrorBoundary is enabled (default: true)
|
|
@@ -302,7 +303,7 @@ export function AppLayout({ children, config, component, pageProps, disableLayou
|
|
|
302
303
|
};
|
|
303
304
|
|
|
304
305
|
const appContent = (
|
|
305
|
-
<AppContextProvider config={config}
|
|
306
|
+
<AppContextProvider config={config} showUpdateNotifier={showUpdateNotifier}>
|
|
306
307
|
{/* SEO Meta Tags */}
|
|
307
308
|
<Seo
|
|
308
309
|
pageConfig={finalPageConfig}
|
|
@@ -310,6 +311,9 @@ export function AppLayout({ children, config, component, pageProps, disableLayou
|
|
|
310
311
|
siteUrl={config.app.siteUrl}
|
|
311
312
|
/>
|
|
312
313
|
|
|
314
|
+
{/* Update Notifier */}
|
|
315
|
+
<UpdateNotifier enabled={showUpdateNotifier} currentVersion={packageJson.version} />
|
|
316
|
+
|
|
313
317
|
{/* Loading Progress Bar */}
|
|
314
318
|
<PageProgress />
|
|
315
319
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Notifier Component
|
|
3
|
+
*
|
|
4
|
+
* Checks npm registry for @djangocfg package updates
|
|
5
|
+
* Shows toast notification when new version is available
|
|
6
|
+
* Uses localStorage to cache check and avoid spam
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use client';
|
|
10
|
+
|
|
11
|
+
import React, { useEffect, useState } from 'react';
|
|
12
|
+
import { toast } from '@djangocfg/ui/hooks';
|
|
13
|
+
|
|
14
|
+
const PACKAGE_NAME = '@djangocfg/layouts';
|
|
15
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
16
|
+
const CACHE_KEY = 'djangocfg_update_check';
|
|
17
|
+
|
|
18
|
+
interface UpdateCheckCache {
|
|
19
|
+
lastCheck: number;
|
|
20
|
+
latestVersion: string;
|
|
21
|
+
dismissed: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UpdateNotifierProps {
|
|
25
|
+
/**
|
|
26
|
+
* Enable update notifications
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Current package version (auto-injected from package.json)
|
|
32
|
+
*/
|
|
33
|
+
currentVersion?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compare semver versions
|
|
38
|
+
* Returns true if newVersion > currentVersion
|
|
39
|
+
*/
|
|
40
|
+
function isNewerVersion(current: string, latest: string): boolean {
|
|
41
|
+
const parseCurrent = current.split('.').map(Number);
|
|
42
|
+
const parseLatest = latest.split('.').map(Number);
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < 3; i++) {
|
|
45
|
+
if (parseLatest[i] > parseCurrent[i]) return true;
|
|
46
|
+
if (parseLatest[i] < parseCurrent[i]) return false;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fetch latest version from npm registry
|
|
53
|
+
*/
|
|
54
|
+
async function fetchLatestVersion(): Promise<string | null> {
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
57
|
+
method: 'GET',
|
|
58
|
+
headers: { 'Accept': 'application/json' },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!response.ok) return null;
|
|
62
|
+
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
return data.version || null;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.warn('[UpdateNotifier] Failed to check for updates:', error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get cached update check data
|
|
73
|
+
*/
|
|
74
|
+
function getCache(): UpdateCheckCache | null {
|
|
75
|
+
if (typeof window === 'undefined') return null;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const cached = localStorage.getItem(CACHE_KEY);
|
|
79
|
+
if (!cached) return null;
|
|
80
|
+
|
|
81
|
+
const data: UpdateCheckCache = JSON.parse(cached);
|
|
82
|
+
return data;
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Save update check data to cache
|
|
90
|
+
*/
|
|
91
|
+
function setCache(data: UpdateCheckCache): void {
|
|
92
|
+
if (typeof window === 'undefined') return;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.warn('[UpdateNotifier] Failed to cache update check:', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function UpdateNotifier({ enabled = false, currentVersion }: UpdateNotifierProps) {
|
|
102
|
+
const [checked, setChecked] = useState(false);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (!enabled || checked || typeof window === 'undefined') return;
|
|
106
|
+
if (!currentVersion) return;
|
|
107
|
+
|
|
108
|
+
const checkForUpdates = async () => {
|
|
109
|
+
// Check cache first
|
|
110
|
+
const cache = getCache();
|
|
111
|
+
const now = Date.now();
|
|
112
|
+
|
|
113
|
+
// If we checked recently, skip
|
|
114
|
+
if (cache && (now - cache.lastCheck) < CHECK_INTERVAL_MS) {
|
|
115
|
+
// Show notification if there's an update and it wasn't dismissed
|
|
116
|
+
if (cache.latestVersion && !cache.dismissed && isNewerVersion(currentVersion, cache.latestVersion)) {
|
|
117
|
+
showUpdateNotification(currentVersion, cache.latestVersion);
|
|
118
|
+
}
|
|
119
|
+
setChecked(true);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fetch latest version from npm
|
|
124
|
+
const latestVersion = await fetchLatestVersion();
|
|
125
|
+
|
|
126
|
+
if (!latestVersion) {
|
|
127
|
+
setChecked(true);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Update cache
|
|
132
|
+
setCache({
|
|
133
|
+
lastCheck: now,
|
|
134
|
+
latestVersion,
|
|
135
|
+
dismissed: false,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Show notification if newer version available
|
|
139
|
+
if (isNewerVersion(currentVersion, latestVersion)) {
|
|
140
|
+
showUpdateNotification(currentVersion, latestVersion);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setChecked(true);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// Check after a short delay to not block initial render
|
|
147
|
+
const timer = setTimeout(checkForUpdates, 2000);
|
|
148
|
+
|
|
149
|
+
return () => clearTimeout(timer);
|
|
150
|
+
}, [enabled, checked, currentVersion]);
|
|
151
|
+
|
|
152
|
+
return null; // This component doesn't render anything
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Show update notification toast
|
|
157
|
+
*/
|
|
158
|
+
function showUpdateNotification(currentVersion: string, latestVersion: string) {
|
|
159
|
+
toast({
|
|
160
|
+
title: `📦 Update Available`,
|
|
161
|
+
description: `New version ${latestVersion} of @djangocfg packages is available. You're using ${currentVersion}. Run: pnpm update @djangocfg/layouts@latest`,
|
|
162
|
+
duration: 10000,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Mark as dismissed in cache after showing
|
|
166
|
+
const cache = getCache();
|
|
167
|
+
if (cache) {
|
|
168
|
+
setCache({ ...cache, dismissed: true });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
export { default as Seo } from './Seo';
|
|
6
6
|
export { default as PageProgress } from './PageProgress';
|
|
7
7
|
export { ErrorBoundary } from './ErrorBoundary';
|
|
8
|
-
export {
|
|
8
|
+
export { UpdateNotifier } from './UpdateNotifier';
|
|
9
9
|
export { UserMenu } from './UserMenu';
|
|
10
|
-
export type {
|
|
10
|
+
export type { UpdateNotifierProps } from './UpdateNotifier';
|
|
11
11
|
export type { UserMenuProps } from './UserMenu';
|
|
@@ -43,7 +43,7 @@ interface AppContextValue {
|
|
|
43
43
|
toggleSidebar: () => void;
|
|
44
44
|
|
|
45
45
|
// Features
|
|
46
|
-
|
|
46
|
+
showUpdateNotifier?: boolean;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -59,7 +59,7 @@ const AppContext = createContext<AppContextValue | null>(null);
|
|
|
59
59
|
export interface AppContextProviderProps {
|
|
60
60
|
children: ReactNode;
|
|
61
61
|
config: AppLayoutConfig;
|
|
62
|
-
|
|
62
|
+
showUpdateNotifier?: boolean;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
@@ -68,7 +68,7 @@ export interface AppContextProviderProps {
|
|
|
68
68
|
* Provides unified application context to all child components
|
|
69
69
|
* Manages layout state and exposes configuration
|
|
70
70
|
*/
|
|
71
|
-
export function AppContextProvider({ children, config,
|
|
71
|
+
export function AppContextProvider({ children, config, showUpdateNotifier }: AppContextProviderProps) {
|
|
72
72
|
const router = useRouter();
|
|
73
73
|
|
|
74
74
|
// UI state
|
|
@@ -117,7 +117,7 @@ export function AppContextProvider({ children, config, showPackageVersions }: Ap
|
|
|
117
117
|
collapseSidebar,
|
|
118
118
|
expandSidebar,
|
|
119
119
|
toggleSidebar,
|
|
120
|
-
|
|
120
|
+
showUpdateNotifier,
|
|
121
121
|
};
|
|
122
122
|
|
|
123
123
|
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
} from '@djangocfg/ui/components';
|
|
30
30
|
import { useAppContext } from '../../../context';
|
|
31
31
|
import { useNavigation } from '../../../hooks';
|
|
32
|
-
import { PackageVersions } from '../../../components';
|
|
33
32
|
|
|
34
33
|
export interface DashboardSidebarProps {
|
|
35
34
|
isAdmin?: boolean;
|
|
@@ -49,7 +48,7 @@ export interface DashboardSidebarProps {
|
|
|
49
48
|
* All data from context!
|
|
50
49
|
*/
|
|
51
50
|
export function DashboardSidebar({ isAdmin = false }: DashboardSidebarProps) {
|
|
52
|
-
const { config
|
|
51
|
+
const { config } = useAppContext();
|
|
53
52
|
const { currentPath } = useNavigation();
|
|
54
53
|
const { state, isMobile } = useSidebar();
|
|
55
54
|
|
|
@@ -177,12 +176,6 @@ export function DashboardSidebar({ isAdmin = false }: DashboardSidebarProps) {
|
|
|
177
176
|
</SidebarGroup>
|
|
178
177
|
))}
|
|
179
178
|
</SidebarContent>
|
|
180
|
-
|
|
181
|
-
{showPackageVersions && (
|
|
182
|
-
<SidebarFooter>
|
|
183
|
-
<PackageVersions variant="sidebar" />
|
|
184
|
-
</SidebarFooter>
|
|
185
|
-
)}
|
|
186
179
|
</Sidebar>
|
|
187
180
|
);
|
|
188
181
|
}
|
|
@@ -11,7 +11,6 @@ import React from 'react';
|
|
|
11
11
|
import Link from 'next/link';
|
|
12
12
|
import { useIsMobile } from '@djangocfg/ui/hooks';
|
|
13
13
|
import { useAppContext } from '../../../context';
|
|
14
|
-
import { PackageVersions } from '../../../components';
|
|
15
14
|
|
|
16
15
|
/**
|
|
17
16
|
* Footer Component
|
|
@@ -28,7 +27,7 @@ import { PackageVersions } from '../../../components';
|
|
|
28
27
|
* All data from context!
|
|
29
28
|
*/
|
|
30
29
|
export function Footer() {
|
|
31
|
-
const { config
|
|
30
|
+
const { config } = useAppContext();
|
|
32
31
|
const isMobile = useIsMobile();
|
|
33
32
|
|
|
34
33
|
const { app, publicLayout } = config;
|
|
@@ -60,9 +59,6 @@ export function Footer() {
|
|
|
60
59
|
|
|
61
60
|
{/* Quick Links */}
|
|
62
61
|
<div className="flex flex-wrap justify-center gap-4 mb-6 items-center">
|
|
63
|
-
{showPackageVersions && (
|
|
64
|
-
<PackageVersions variant="footer-minimal" />
|
|
65
|
-
)}
|
|
66
62
|
{footer.links.docs && (
|
|
67
63
|
<a
|
|
68
64
|
href={footer.links.docs}
|
|
@@ -193,9 +189,6 @@ export function Footer() {
|
|
|
193
189
|
</a>
|
|
194
190
|
</div>
|
|
195
191
|
<div className="flex flex-wrap items-center gap-4">
|
|
196
|
-
{showPackageVersions && (
|
|
197
|
-
<PackageVersions variant="footer-minimal" />
|
|
198
|
-
)}
|
|
199
192
|
{footer.links.docs && (
|
|
200
193
|
<a
|
|
201
194
|
href={footer.links.docs}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package Versions Display
|
|
3
|
-
*
|
|
4
|
-
* Shows all @djangocfg packages versions in a popover
|
|
5
|
-
* Works in both sidebar (PrivateLayout) and footer (PublicLayout)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import React from 'react';
|
|
11
|
-
import { Info, Package } from 'lucide-react';
|
|
12
|
-
import {
|
|
13
|
-
Popover,
|
|
14
|
-
PopoverContent,
|
|
15
|
-
PopoverTrigger,
|
|
16
|
-
} from '@djangocfg/ui/components';
|
|
17
|
-
import { Button } from '@djangocfg/ui/components';
|
|
18
|
-
import { getPackageVersions } from './packageVersions.config';
|
|
19
|
-
|
|
20
|
-
export interface PackageVersionsProps {
|
|
21
|
-
/**
|
|
22
|
-
* Display variant
|
|
23
|
-
* - 'sidebar': Adapts to sidebar collapsed state (PrivateLayout)
|
|
24
|
-
* - 'footer': Simple button for footer (PublicLayout)
|
|
25
|
-
* - 'footer-minimal': Only icon, no text (for compact footer)
|
|
26
|
-
*/
|
|
27
|
-
variant?: 'sidebar' | 'footer' | 'footer-minimal';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function PackageVersions({ variant = 'footer' }: PackageVersionsProps) {
|
|
31
|
-
// Try to use sidebar state if available (only in PrivateLayout)
|
|
32
|
-
let isCollapsed = false;
|
|
33
|
-
if (variant === 'sidebar') {
|
|
34
|
-
try {
|
|
35
|
-
// Dynamic import to avoid errors in PublicLayout
|
|
36
|
-
const { useSidebar } = require('@djangocfg/ui/components');
|
|
37
|
-
const { state } = useSidebar();
|
|
38
|
-
isCollapsed = state === 'collapsed';
|
|
39
|
-
} catch (e) {
|
|
40
|
-
// Sidebar not available, use default
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const isSidebarVariant = variant === 'sidebar';
|
|
45
|
-
const isMinimalVariant = variant === 'footer-minimal';
|
|
46
|
-
const popoverAlign = isSidebarVariant ? 'start' : 'center';
|
|
47
|
-
const popoverSide = isSidebarVariant ? 'right' : 'top';
|
|
48
|
-
|
|
49
|
-
// Determine if we should show text
|
|
50
|
-
const showText = !isCollapsed && !isMinimalVariant;
|
|
51
|
-
|
|
52
|
-
// Get package versions dynamically
|
|
53
|
-
const packages = getPackageVersions();
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<Popover>
|
|
57
|
-
<PopoverTrigger asChild>
|
|
58
|
-
<Button
|
|
59
|
-
variant="ghost"
|
|
60
|
-
size={isCollapsed || isMinimalVariant ? "icon" : "sm"}
|
|
61
|
-
className={isSidebarVariant
|
|
62
|
-
? "w-full justify-start text-xs text-muted-foreground hover:text-foreground"
|
|
63
|
-
: isMinimalVariant
|
|
64
|
-
? "h-auto w-auto p-1 text-muted-foreground hover:text-primary transition-colors"
|
|
65
|
-
: "text-xs text-muted-foreground hover:text-foreground"
|
|
66
|
-
}
|
|
67
|
-
title={isMinimalVariant ? "Package Versions" : undefined}
|
|
68
|
-
>
|
|
69
|
-
<Info className={isMinimalVariant ? "h-3 w-3" : "h-3.5 w-3.5"} />
|
|
70
|
-
{showText && <span className="ml-2">Package Versions</span>}
|
|
71
|
-
</Button>
|
|
72
|
-
</PopoverTrigger>
|
|
73
|
-
<PopoverContent align={popoverAlign} side={popoverSide} className="w-80">
|
|
74
|
-
<div className="space-y-3">
|
|
75
|
-
<div className="flex items-center gap-2">
|
|
76
|
-
<Package className="h-4 w-4 text-primary" />
|
|
77
|
-
<h4 className="font-semibold text-sm">Package Versions</h4>
|
|
78
|
-
</div>
|
|
79
|
-
<div className="space-y-1.5">
|
|
80
|
-
{packages.map((pkg) => (
|
|
81
|
-
<a
|
|
82
|
-
key={pkg.name}
|
|
83
|
-
href={`https://www.npmjs.com/package/${pkg.name}`}
|
|
84
|
-
target="_blank"
|
|
85
|
-
rel="noopener noreferrer"
|
|
86
|
-
className="flex items-center justify-between py-1.5 px-2 rounded-sm hover:bg-accent/50 transition-colors cursor-pointer group"
|
|
87
|
-
>
|
|
88
|
-
<span className="text-xs font-mono text-muted-foreground group-hover:text-foreground transition-colors">
|
|
89
|
-
{pkg.name}
|
|
90
|
-
</span>
|
|
91
|
-
<span className="text-xs font-semibold bg-primary/10 text-primary px-2 py-0.5 rounded group-hover:bg-primary/20 transition-colors">
|
|
92
|
-
v{pkg.version}
|
|
93
|
-
</span>
|
|
94
|
-
</a>
|
|
95
|
-
))}
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
</PopoverContent>
|
|
99
|
-
</Popover>
|
|
100
|
-
);
|
|
101
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Package Versions Configuration
|
|
3
|
-
*
|
|
4
|
-
* NOTE: This file is auto-generated by packages/scripts/sync-package-versions.js
|
|
5
|
-
* Do not edit manually! Run 'make build' or 'pnpm sync-versions' to update.
|
|
6
|
-
*
|
|
7
|
-
* Versions are synced from actual package.json files in the monorepo.
|
|
8
|
-
* This ensures compatibility with both monorepo (dev) and npm (production).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export interface PackageInfo {
|
|
12
|
-
name: string;
|
|
13
|
-
version: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Package versions registry
|
|
18
|
-
* Auto-synced from package.json files
|
|
19
|
-
* Last updated: 2025-11-23T06:18:54.167Z
|
|
20
|
-
*/
|
|
21
|
-
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
|
-
{
|
|
23
|
-
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.58"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.58"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.58"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.58"
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.58"
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.58"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.58"
|
|
49
|
-
}
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get all package versions
|
|
54
|
-
*/
|
|
55
|
-
export function getPackageVersions(): PackageInfo[] {
|
|
56
|
-
return PACKAGE_VERSIONS;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get single package version by name
|
|
61
|
-
*/
|
|
62
|
-
export function getPackageVersion(packageName: string): string | undefined {
|
|
63
|
-
const packages = getPackageVersions();
|
|
64
|
-
return packages.find((pkg) => pkg.name === packageName)?.version;
|
|
65
|
-
}
|