@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "1.2.18",
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.18",
57
- "@djangocfg/og-image": "^1.2.18",
58
- "@djangocfg/ui": "^1.2.18",
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.18",
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-01T07:48:08.580Z
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.18"
24
+ "version": "1.2.20"
25
25
  },
26
26
  {
27
27
  "name": "@djangocfg/api",
28
- "version": "1.2.18"
28
+ "version": "1.2.20"
29
29
  },
30
30
  {
31
31
  "name": "@djangocfg/layouts",
32
- "version": "1.2.18"
32
+ "version": "1.2.20"
33
33
  },
34
34
  {
35
35
  "name": "@djangocfg/markdown",
36
- "version": "1.2.18"
36
+ "version": "1.2.20"
37
37
  },
38
38
  {
39
39
  "name": "@djangocfg/og-image",
40
- "version": "1.2.18"
40
+ "version": "1.2.20"
41
41
  },
42
42
  {
43
43
  "name": "@djangocfg/eslint-config",
44
- "version": "1.2.18"
44
+ "version": "1.2.20"
45
45
  },
46
46
  {
47
47
  "name": "@djangocfg/typescript-config",
48
- "version": "1.2.18"
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
- * Adaptive header that renders mobile or desktop version
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 { useIsMobile } from '@djangocfg/ui';
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 (desktop only) */
12
+ /** Page title */
15
13
  title?: string;
16
- /** Project name (mobile only) */
14
+ /** Project name */
17
15
  projectName?: string;
18
- /** Logo component (mobile only) */
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
- * Automatically renders mobile or desktop version based on screen size
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
- <HeaderDesktop
56
- title={title}
57
- onCopyForAI={onCopyForAI}
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: `<Tabs defaultValue="account" className="w-[400px]">
153
- <TabsList className="grid w-full grid-cols-2">
154
- <TabsTrigger value="account">Account</TabsTrigger>
155
- <TabsTrigger value="password">Password</TabsTrigger>
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 className="grid w-full grid-cols-2">
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 ComponentShowcaseLayout
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 only landing page without layout
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' && currentCategory !== 'overview' && (
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 { Sidebar } from '../components/layout/Sidebar';
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: Mobile-first design with sidebar
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
- {/* Mobile Overlay - outside flex container for proper z-index stacking */}
62
- <MobileOverlay
63
- isOpen={isSidebarOpen}
64
- onClose={closeSidebar}
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
- {/* Sidebar with Sticky - parent must be scrollable for react-sticky-box */}
68
- <div className="h-full sticky top-0">
69
- <Sidebar
70
- categories={categories}
71
- currentCategory={currentCategory}
72
- onCategoryChange={handleCategoryChange}
73
- isOpen={isSidebarOpen}
74
- projectName={projectName}
75
- logo={logo}
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 flex flex-col min-w-0">
81
- {/* Header with Copy for AI button */}
82
- <Header
83
- title={title}
84
- projectName={projectName}
85
- logo={logo}
86
- isSidebarOpen={isSidebarOpen}
87
- onToggleSidebar={toggleSidebar}
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 sidebar */
40
+ /** Enable sticky header */
43
41
  stickyHeader?: boolean;
44
42
  }
45
43