@djangocfg/layouts 1.2.18 → 1.2.20
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/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +15 -89
- package/src/layouts/UILayout/components/layout/Header/Header.tsx +25 -32
- package/src/layouts/UILayout/config/components/navigation.config.tsx +29 -8
- package/src/layouts/UILayout/context/ShowcaseContext.tsx +1 -11
- package/src/layouts/UILayout/core/UIGuideView.tsx +2 -2
- package/src/layouts/UILayout/core/UILayout.tsx +44 -47
- package/src/layouts/UILayout/hooks/index.ts +1 -1
- package/src/layouts/UILayout/types/layout.ts +1 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.20",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"check": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@djangocfg/api": "^1.2.
|
|
57
|
-
"@djangocfg/og-image": "^1.2.
|
|
58
|
-
"@djangocfg/ui": "^1.2.
|
|
56
|
+
"@djangocfg/api": "^1.2.20",
|
|
57
|
+
"@djangocfg/og-image": "^1.2.20",
|
|
58
|
+
"@djangocfg/ui": "^1.2.20",
|
|
59
59
|
"@hookform/resolvers": "^5.2.0",
|
|
60
60
|
"consola": "^3.4.2",
|
|
61
61
|
"lucide-react": "^0.468.0",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"vidstack": "0.6.15"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@djangocfg/typescript-config": "^1.2.
|
|
79
|
+
"@djangocfg/typescript-config": "^1.2.20",
|
|
80
80
|
"@types/node": "^24.7.2",
|
|
81
81
|
"@types/react": "19.2.2",
|
|
82
82
|
"@types/react-dom": "19.2.1",
|
|
@@ -16,36 +16,36 @@ export interface PackageInfo {
|
|
|
16
16
|
/**
|
|
17
17
|
* Package versions registry
|
|
18
18
|
* Auto-synced from package.json files
|
|
19
|
-
* Last updated: 2025-11-
|
|
19
|
+
* Last updated: 2025-11-02T09:59:18.371Z
|
|
20
20
|
*/
|
|
21
21
|
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
22
|
{
|
|
23
23
|
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.
|
|
24
|
+
"version": "1.2.20"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.
|
|
28
|
+
"version": "1.2.20"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.
|
|
32
|
+
"version": "1.2.20"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.20"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.
|
|
40
|
+
"version": "1.2.20"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.
|
|
44
|
+
"version": "1.2.20"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.
|
|
48
|
+
"version": "1.2.20"
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
|
@@ -94,13 +94,10 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
94
94
|
const [parentThemeMode, setParentThemeMode] = useState<string | null>(null);
|
|
95
95
|
|
|
96
96
|
useEffect(() => {
|
|
97
|
-
// Debug logging (uncomment for debugging)
|
|
98
|
-
// console.log('[useCfgApp] Hook initializing...');
|
|
99
97
|
setIsMounted(true);
|
|
100
98
|
|
|
101
99
|
// Check if running in iframe
|
|
102
100
|
const inIframe = window.self !== window.top;
|
|
103
|
-
// console.log('[useCfgApp] Iframe detection:', { inIframe });
|
|
104
101
|
setIsEmbedded(inIframe);
|
|
105
102
|
|
|
106
103
|
// Check if running as standalone PWA
|
|
@@ -109,85 +106,19 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
109
106
|
|
|
110
107
|
// Get referrer if embedded
|
|
111
108
|
if (inIframe && document.referrer) {
|
|
112
|
-
// console.log('[useCfgApp] Referrer:', document.referrer);
|
|
113
109
|
setReferrer(document.referrer);
|
|
114
110
|
}
|
|
115
111
|
|
|
116
|
-
// Setup resize observer and interval for iframe height updates
|
|
117
|
-
let resizeObserver: ResizeObserver | null = null;
|
|
118
|
-
let checkHeightInterval: NodeJS.Timeout | null = null;
|
|
119
|
-
|
|
120
|
-
// Notify parent window that iframe is ready
|
|
121
|
-
if (inIframe) {
|
|
122
|
-
try {
|
|
123
|
-
// console.log('[useCfgApp] Sending iframe-ready message to parent');
|
|
124
|
-
window.parent.postMessage({
|
|
125
|
-
type: 'iframe-ready',
|
|
126
|
-
data: {
|
|
127
|
-
url: window.location.href,
|
|
128
|
-
referrer: document.referrer
|
|
129
|
-
}
|
|
130
|
-
}, '*'); // Use '*' or specific origin for security
|
|
131
|
-
// console.log('[useCfgApp] iframe-ready message sent');
|
|
132
|
-
} catch (e) {
|
|
133
|
-
console.error('[useCfgApp] Failed to notify parent:', e);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Send current content height (both increases and decreases)
|
|
137
|
-
const sendHeight = () => {
|
|
138
|
-
try {
|
|
139
|
-
// Use body.scrollHeight to avoid iframe height feedback loop
|
|
140
|
-
// document.documentElement.scrollHeight includes iframe's own height!
|
|
141
|
-
const bodyScrollHeight = document.body.scrollHeight;
|
|
142
|
-
const bodyOffsetHeight = document.body.offsetHeight;
|
|
143
|
-
const height = Math.max(bodyScrollHeight, bodyOffsetHeight);
|
|
144
|
-
|
|
145
|
-
window.parent.postMessage({
|
|
146
|
-
type: 'iframe-resize',
|
|
147
|
-
data: { height }
|
|
148
|
-
}, '*');
|
|
149
|
-
} catch (e) {
|
|
150
|
-
console.error('[useCfgApp] Failed to send height:', e);
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// Send height immediately after mount
|
|
155
|
-
setTimeout(sendHeight, 100);
|
|
156
|
-
|
|
157
|
-
// Watch for content size changes using ResizeObserver
|
|
158
|
-
resizeObserver = new ResizeObserver(() => {
|
|
159
|
-
sendHeight();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Observe document body for size changes
|
|
163
|
-
resizeObserver.observe(document.body);
|
|
164
|
-
|
|
165
|
-
// Also observe on route changes and after data loads
|
|
166
|
-
checkHeightInterval = setInterval(sendHeight, 1000);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
112
|
// Listen for messages from parent window
|
|
170
113
|
const handleMessage = (event: MessageEvent) => {
|
|
171
|
-
// console.log('[useCfgApp] Message received:', {
|
|
172
|
-
// origin: event.origin,
|
|
173
|
-
// type: event.data?.type,
|
|
174
|
-
// data: event.data?.data
|
|
175
|
-
// });
|
|
176
|
-
|
|
177
|
-
// Verify origin for security (optional - adjust for your needs)
|
|
178
|
-
// if (event.origin !== window.location.origin) return;
|
|
179
|
-
|
|
180
114
|
const { type, data } = event.data || {};
|
|
181
115
|
|
|
182
116
|
switch (type) {
|
|
183
117
|
case 'parent-auth':
|
|
184
|
-
// console.log('[useCfgApp] Processing parent-auth message');
|
|
185
118
|
// Receive authentication tokens from parent
|
|
186
119
|
if (data?.authToken && options?.onAuthTokenReceived) {
|
|
187
120
|
try {
|
|
188
|
-
// console.log('[useCfgApp] Calling onAuthTokenReceived');
|
|
189
121
|
options.onAuthTokenReceived(data.authToken, data.refreshToken);
|
|
190
|
-
// console.log('[useCfgApp] Auth tokens received from parent');
|
|
191
122
|
} catch (e) {
|
|
192
123
|
console.error('[useCfgApp] Failed to process auth tokens:', e);
|
|
193
124
|
}
|
|
@@ -195,19 +126,13 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
195
126
|
break;
|
|
196
127
|
|
|
197
128
|
case 'parent-theme':
|
|
198
|
-
// console.log('[useCfgApp] Processing parent-theme message');
|
|
199
129
|
// Receive theme from parent
|
|
200
130
|
if (data?.theme) {
|
|
201
131
|
try {
|
|
202
|
-
// console.log('[useCfgApp] Setting theme:', {
|
|
203
|
-
// theme: data.theme,
|
|
204
|
-
// themeMode: data.themeMode
|
|
205
|
-
// });
|
|
206
132
|
setParentTheme(data.theme);
|
|
207
133
|
if (data.themeMode) {
|
|
208
134
|
setParentThemeMode(data.themeMode);
|
|
209
135
|
}
|
|
210
|
-
// console.log('[useCfgApp] Theme received from parent:', data.theme, 'mode:', data.themeMode);
|
|
211
136
|
} catch (e) {
|
|
212
137
|
console.error('[useCfgApp] Failed to process theme:', e);
|
|
213
138
|
}
|
|
@@ -216,31 +141,32 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
216
141
|
|
|
217
142
|
case 'parent-resize':
|
|
218
143
|
// Handle parent window resize (optional)
|
|
219
|
-
// console.log('[useCfgApp] Parent resized:', data);
|
|
220
144
|
break;
|
|
221
145
|
|
|
222
146
|
default:
|
|
223
|
-
// if (type) {
|
|
224
|
-
// console.log('[useCfgApp] Unknown message type:', type);
|
|
225
|
-
// }
|
|
226
147
|
break;
|
|
227
148
|
}
|
|
228
149
|
};
|
|
229
150
|
|
|
230
|
-
// console.log('[useCfgApp] Adding message event listener');
|
|
231
151
|
window.addEventListener('message', handleMessage);
|
|
232
152
|
|
|
153
|
+
// Send iframe-ready since listener is registered
|
|
154
|
+
if (inIframe) {
|
|
155
|
+
try {
|
|
156
|
+
window.parent.postMessage({
|
|
157
|
+
type: 'iframe-ready',
|
|
158
|
+
data: {
|
|
159
|
+
url: window.location.href,
|
|
160
|
+
referrer: document.referrer
|
|
161
|
+
}
|
|
162
|
+
}, '*');
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.error('[useCfgApp] Failed to notify parent about ready state:', e);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
233
168
|
return () => {
|
|
234
|
-
// console.log('[useCfgApp] Cleaning up message event listener');
|
|
235
169
|
window.removeEventListener('message', handleMessage);
|
|
236
|
-
|
|
237
|
-
// Cleanup resize observer and interval
|
|
238
|
-
if (resizeObserver) {
|
|
239
|
-
resizeObserver.disconnect();
|
|
240
|
-
}
|
|
241
|
-
if (checkHeightInterval) {
|
|
242
|
-
clearInterval(checkHeightInterval);
|
|
243
|
-
}
|
|
244
170
|
};
|
|
245
171
|
}, [options]);
|
|
246
172
|
|
|
@@ -1,60 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Header Component
|
|
3
|
-
*
|
|
3
|
+
* Simple unified header for UI documentation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use client';
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import {
|
|
10
|
-
import { HeaderMobile } from './HeaderMobile';
|
|
11
|
-
import { HeaderDesktop } from './HeaderDesktop';
|
|
9
|
+
import { CopyAIButton } from './CopyAIButton';
|
|
12
10
|
|
|
13
11
|
export interface HeaderProps {
|
|
14
|
-
/** Page title
|
|
12
|
+
/** Page title */
|
|
15
13
|
title?: string;
|
|
16
|
-
/** Project name
|
|
14
|
+
/** Project name */
|
|
17
15
|
projectName?: string;
|
|
18
|
-
/** Logo component
|
|
16
|
+
/** Logo component */
|
|
19
17
|
logo?: React.ReactNode;
|
|
20
|
-
/** Is sidebar open (mobile only) */
|
|
21
|
-
isSidebarOpen?: boolean;
|
|
22
|
-
/** Toggle sidebar callback (mobile only) */
|
|
23
|
-
onToggleSidebar?: () => void;
|
|
24
18
|
/** Copy for AI callback - must return string */
|
|
25
19
|
onCopyForAI?: () => string;
|
|
26
20
|
}
|
|
27
21
|
|
|
28
22
|
/**
|
|
29
23
|
* Header Component
|
|
30
|
-
*
|
|
24
|
+
* Displays title and Copy for AI button
|
|
31
25
|
*/
|
|
32
26
|
export function Header({
|
|
33
27
|
title = 'UI Component Library',
|
|
34
28
|
projectName = 'Django CFG',
|
|
35
29
|
logo,
|
|
36
|
-
isSidebarOpen = false,
|
|
37
|
-
onToggleSidebar,
|
|
38
30
|
onCopyForAI,
|
|
39
31
|
}: HeaderProps) {
|
|
40
|
-
const isMobile = useIsMobile();
|
|
41
|
-
|
|
42
|
-
if (isMobile) {
|
|
43
|
-
return (
|
|
44
|
-
<HeaderMobile
|
|
45
|
-
projectName={projectName}
|
|
46
|
-
logo={logo}
|
|
47
|
-
isSidebarOpen={isSidebarOpen}
|
|
48
|
-
onToggleSidebar={onToggleSidebar}
|
|
49
|
-
onCopyForAI={onCopyForAI}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
32
|
return (
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
33
|
+
<header className="w-full border-b bg-background">
|
|
34
|
+
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
35
|
+
<div className="flex items-center justify-between h-16">
|
|
36
|
+
{/* Left: Logo and Title */}
|
|
37
|
+
<div className="flex items-center gap-3">
|
|
38
|
+
{logo && <div className="flex-shrink-0">{logo}</div>}
|
|
39
|
+
<div className="flex flex-col">
|
|
40
|
+
<h1 className="text-lg font-semibold tracking-tight">{title}</h1>
|
|
41
|
+
<p className="text-xs text-muted-foreground hidden sm:block">{projectName}</p>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* Right: Copy for AI Button */}
|
|
46
|
+
{onCopyForAI && (
|
|
47
|
+
<CopyAIButton onCopyForAI={onCopyForAI} />
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</header>
|
|
59
52
|
);
|
|
60
53
|
}
|
|
@@ -147,12 +147,13 @@ export const NAVIGATION_COMPONENTS: ComponentConfig[] = [
|
|
|
147
147
|
{
|
|
148
148
|
name: 'Tabs',
|
|
149
149
|
category: 'navigation',
|
|
150
|
-
description: 'Tab navigation for switching between different views',
|
|
150
|
+
description: 'Tab navigation for switching between different views. Supports mobile sheet mode, sticky positioning, and auto-responsive behavior.',
|
|
151
151
|
importPath: `import { Tabs, TabsContent, TabsList, TabsTrigger } from '@djangocfg/ui';`,
|
|
152
|
-
example:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<TabsTrigger value="
|
|
152
|
+
example: `// Basic tabs with equal-width tabs
|
|
153
|
+
<Tabs defaultValue="account" className="w-[400px]">
|
|
154
|
+
<TabsList fullWidth>
|
|
155
|
+
<TabsTrigger value="account" flexEqual>Account</TabsTrigger>
|
|
156
|
+
<TabsTrigger value="password" flexEqual>Password</TabsTrigger>
|
|
156
157
|
</TabsList>
|
|
157
158
|
<TabsContent value="account">
|
|
158
159
|
<div className="p-4 border rounded-md">
|
|
@@ -164,12 +165,32 @@ export const NAVIGATION_COMPONENTS: ComponentConfig[] = [
|
|
|
164
165
|
<p className="text-sm">Change your password here.</p>
|
|
165
166
|
</div>
|
|
166
167
|
</TabsContent>
|
|
168
|
+
</Tabs>
|
|
169
|
+
|
|
170
|
+
// Mobile-responsive tabs with sticky positioning
|
|
171
|
+
<Tabs
|
|
172
|
+
defaultValue="account"
|
|
173
|
+
mobileSheet
|
|
174
|
+
mobileTitleText="Settings"
|
|
175
|
+
mobileSheetTitle="Navigation"
|
|
176
|
+
sticky
|
|
177
|
+
>
|
|
178
|
+
<TabsList fullWidth>
|
|
179
|
+
<TabsTrigger value="account" flexEqual>Account</TabsTrigger>
|
|
180
|
+
<TabsTrigger value="password" flexEqual>Password</TabsTrigger>
|
|
181
|
+
</TabsList>
|
|
182
|
+
<TabsContent value="account">
|
|
183
|
+
Account content
|
|
184
|
+
</TabsContent>
|
|
185
|
+
<TabsContent value="password">
|
|
186
|
+
Password content
|
|
187
|
+
</TabsContent>
|
|
167
188
|
</Tabs>`,
|
|
168
189
|
preview: (
|
|
169
190
|
<Tabs defaultValue="account" className="w-[400px]">
|
|
170
|
-
<TabsList
|
|
171
|
-
<TabsTrigger value="account">Account</TabsTrigger>
|
|
172
|
-
<TabsTrigger value="password">Password</TabsTrigger>
|
|
191
|
+
<TabsList fullWidth>
|
|
192
|
+
<TabsTrigger value="account" flexEqual>Account</TabsTrigger>
|
|
193
|
+
<TabsTrigger value="password" flexEqual>Password</TabsTrigger>
|
|
173
194
|
</TabsList>
|
|
174
195
|
<TabsContent value="account">
|
|
175
196
|
<div className="p-4 border rounded-md">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Showcase Context
|
|
3
|
-
* Manages navigation state for
|
|
3
|
+
* Manages navigation state for UILayout
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use client';
|
|
@@ -10,9 +10,6 @@ import React, { createContext, useContext, useState, ReactNode } from 'react';
|
|
|
10
10
|
interface ShowcaseContextValue {
|
|
11
11
|
currentCategory: string;
|
|
12
12
|
setCurrentCategory: (category: string) => void;
|
|
13
|
-
isSidebarOpen: boolean;
|
|
14
|
-
toggleSidebar: () => void;
|
|
15
|
-
closeSidebar: () => void;
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
const ShowcaseContext = createContext<ShowcaseContextValue | undefined>(undefined);
|
|
@@ -24,19 +21,12 @@ interface ShowcaseProviderProps {
|
|
|
24
21
|
|
|
25
22
|
export function ShowcaseProvider({ children, defaultCategory = 'overview' }: ShowcaseProviderProps) {
|
|
26
23
|
const [currentCategory, setCurrentCategory] = useState(defaultCategory);
|
|
27
|
-
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
|
28
|
-
|
|
29
|
-
const toggleSidebar = () => setIsSidebarOpen(prev => !prev);
|
|
30
|
-
const closeSidebar = () => setIsSidebarOpen(false);
|
|
31
24
|
|
|
32
25
|
return (
|
|
33
26
|
<ShowcaseContext.Provider
|
|
34
27
|
value={{
|
|
35
28
|
currentCategory,
|
|
36
29
|
setCurrentCategory,
|
|
37
|
-
isSidebarOpen,
|
|
38
|
-
toggleSidebar,
|
|
39
|
-
closeSidebar,
|
|
40
30
|
}}
|
|
41
31
|
>
|
|
42
32
|
{children}
|
|
@@ -17,7 +17,7 @@ import { TailwindGuideRenderer } from '../components/TailwindGuideRenderer';
|
|
|
17
17
|
function UIGuideContent() {
|
|
18
18
|
const { currentCategory, setCurrentCategory } = useShowcase();
|
|
19
19
|
|
|
20
|
-
// For overview, show
|
|
20
|
+
// For overview, show landing page without layout
|
|
21
21
|
if (currentCategory === 'overview') {
|
|
22
22
|
return <UIGuideLanding />;
|
|
23
23
|
}
|
|
@@ -44,7 +44,7 @@ function UIGuideContent() {
|
|
|
44
44
|
{currentCategory === 'tailwind4' && <TailwindGuideRenderer />}
|
|
45
45
|
|
|
46
46
|
{/* All other categories use CategoryRenderer */}
|
|
47
|
-
{currentCategory !== 'tailwind4' &&
|
|
47
|
+
{currentCategory !== 'tailwind4' && (
|
|
48
48
|
<CategoryRenderer categoryId={currentCategory} />
|
|
49
49
|
)}
|
|
50
50
|
</div>
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
'use client';
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import {
|
|
9
|
+
import { Tabs, TabsList, TabsTrigger } from '@djangocfg/ui';
|
|
10
10
|
import { Header } from '../components/layout/Header';
|
|
11
|
-
import { MobileOverlay } from '../components/layout/MobileOverlay';
|
|
12
11
|
import { generateAIContext } from '../config';
|
|
13
|
-
import { useShowcase } from '../context';
|
|
14
12
|
import type { UILayoutProps } from '../types';
|
|
15
13
|
|
|
16
14
|
|
|
@@ -20,7 +18,7 @@ import type { UILayoutProps } from '../types';
|
|
|
20
18
|
* Features:
|
|
21
19
|
* - Config-driven: All component data comes from centralized config
|
|
22
20
|
* - "Copy for AI": One-click export of all documentation
|
|
23
|
-
* - Responsive:
|
|
21
|
+
* - Responsive: Auto-converts to mobile sheet menu on mobile
|
|
24
22
|
* - Type-safe: Full TypeScript support
|
|
25
23
|
*
|
|
26
24
|
* @example
|
|
@@ -45,59 +43,58 @@ export function UILayout({
|
|
|
45
43
|
logo,
|
|
46
44
|
projectName = "Django CFG UI",
|
|
47
45
|
}: UILayoutProps) {
|
|
48
|
-
const { isSidebarOpen, toggleSidebar, closeSidebar } = useShowcase();
|
|
49
|
-
|
|
50
|
-
const handleCategoryChange = (categoryId: string) => {
|
|
51
|
-
onCategoryChange?.(categoryId);
|
|
52
|
-
closeSidebar();
|
|
53
|
-
};
|
|
54
|
-
|
|
55
46
|
const handleCopyForAI = () => {
|
|
56
47
|
return generateAIContext();
|
|
57
48
|
};
|
|
58
49
|
|
|
59
50
|
return (
|
|
60
|
-
<div className="flex bg-background">
|
|
61
|
-
{/*
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
51
|
+
<div className="flex flex-col min-h-screen bg-background">
|
|
52
|
+
{/* Header with Copy for AI button */}
|
|
53
|
+
<Header
|
|
54
|
+
title={title}
|
|
55
|
+
projectName={projectName}
|
|
56
|
+
logo={logo}
|
|
57
|
+
onCopyForAI={handleCopyForAI}
|
|
65
58
|
/>
|
|
66
59
|
|
|
67
|
-
{/*
|
|
68
|
-
<div className="
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
{/* Category Navigation Tabs */}
|
|
61
|
+
<div className="container mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-3">
|
|
62
|
+
<Tabs
|
|
63
|
+
value={currentCategory}
|
|
64
|
+
onValueChange={onCategoryChange}
|
|
65
|
+
mobileSheet
|
|
66
|
+
mobileTitleText="UI Library"
|
|
67
|
+
mobileSheetTitle="Categories"
|
|
68
|
+
sticky
|
|
69
|
+
>
|
|
70
|
+
<TabsList fullWidth className="h-auto flex-wrap">
|
|
71
|
+
{categories.map((category) => (
|
|
72
|
+
<TabsTrigger
|
|
73
|
+
key={category.id}
|
|
74
|
+
value={category.id}
|
|
75
|
+
flexEqual
|
|
76
|
+
className="gap-2 whitespace-nowrap"
|
|
77
|
+
>
|
|
78
|
+
{category.icon}
|
|
79
|
+
<span>{category.label}</span>
|
|
80
|
+
{category.count !== undefined && (
|
|
81
|
+
<span className="ml-1 text-xs opacity-60">({category.count})</span>
|
|
82
|
+
)}
|
|
83
|
+
</TabsTrigger>
|
|
84
|
+
))}
|
|
85
|
+
</TabsList>
|
|
86
|
+
</Tabs>
|
|
77
87
|
</div>
|
|
78
88
|
|
|
79
89
|
{/* Main Content */}
|
|
80
|
-
<main className="flex-1
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
onCopyForAI={handleCopyForAI}
|
|
89
|
-
/>
|
|
90
|
-
|
|
91
|
-
{/* Content Area */}
|
|
92
|
-
<div className="flex-1">
|
|
93
|
-
<div className="container max-w-7xl mx-auto p-6">
|
|
94
|
-
{description && (
|
|
95
|
-
<p className="text-sm text-muted-foreground mb-6">
|
|
96
|
-
{description}
|
|
97
|
-
</p>
|
|
98
|
-
)}
|
|
99
|
-
{children}
|
|
100
|
-
</div>
|
|
90
|
+
<main className="flex-1">
|
|
91
|
+
<div className="container max-w-7xl mx-auto p-6">
|
|
92
|
+
{description && (
|
|
93
|
+
<p className="text-sm text-muted-foreground mb-6">
|
|
94
|
+
{description}
|
|
95
|
+
</p>
|
|
96
|
+
)}
|
|
97
|
+
{children}
|
|
101
98
|
</div>
|
|
102
99
|
</main>
|
|
103
100
|
</div>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Custom hooks for UILayout functionality
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export { useSidebarState } from './useSidebarState';
|
|
7
6
|
export { useAIExport } from './useAIExport';
|
|
8
7
|
export { useCategoryNavigation } from './useCategoryNavigation';
|
|
9
8
|
export { useComponentSearch } from './useComponentSearch';
|
|
9
|
+
export { useSidebarState } from './useSidebarState';
|
|
@@ -33,13 +33,11 @@ export interface UILayoutProps {
|
|
|
33
33
|
* Defines layout dimensions and behavior
|
|
34
34
|
*/
|
|
35
35
|
export interface LayoutConfig {
|
|
36
|
-
/** Sidebar width in pixels */
|
|
37
|
-
sidebarWidth?: number;
|
|
38
36
|
/** Header height in pixels */
|
|
39
37
|
headerHeight?: number;
|
|
40
38
|
/** Container max width */
|
|
41
39
|
containerMaxWidth?: string;
|
|
42
|
-
/** Enable sticky
|
|
40
|
+
/** Enable sticky header */
|
|
43
41
|
stickyHeader?: boolean;
|
|
44
42
|
}
|
|
45
43
|
|