@djangocfg/layouts 1.2.2 → 1.2.4

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.2",
3
+ "version": "1.2.4",
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.2",
57
- "@djangocfg/og-image": "^1.2.2",
58
- "@djangocfg/ui": "^1.2.2",
56
+ "@djangocfg/api": "^1.2.4",
57
+ "@djangocfg/og-image": "^1.2.4",
58
+ "@djangocfg/ui": "^1.2.4",
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.2",
79
+ "@djangocfg/typescript-config": "^1.2.4",
80
80
  "@types/node": "^24.7.2",
81
81
  "@types/react": "19.2.2",
82
82
  "@types/react-dom": "19.2.1",
@@ -32,7 +32,18 @@ const hasValidTokens = (): boolean => {
32
32
  // Internal provider that uses AccountsContext
33
33
  const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config }) => {
34
34
  const accounts = useAccountsContext();
35
- const [isLoading, setIsLoading] = useState(true);
35
+
36
+ // Smart initial loading state: only true if we don't have tokens yet
37
+ const [isLoading, setIsLoading] = useState(() => {
38
+ // If we already have tokens and profile, don't show loading
39
+ if (typeof window !== 'undefined') {
40
+ const hasTokens = hasValidTokens();
41
+ // Only show loading on initial mount if no tokens
42
+ return !hasTokens;
43
+ }
44
+ return true;
45
+ });
46
+
36
47
  const [initialized, setInitialized] = useState(false);
37
48
  const router = useRouter();
38
49
 
@@ -113,7 +124,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
113
124
 
114
125
  const initializeAuth = async () => {
115
126
  authLogger.info('Initializing auth...');
116
- setIsLoading(true);
117
127
 
118
128
  // Debug token state
119
129
  const token = api.getToken();
@@ -125,7 +135,16 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
125
135
  const hasTokens = hasValidTokens();
126
136
  authLogger.info('Has tokens:', hasTokens);
127
137
 
138
+ // If profile is already loaded (from SWR cache), we're initialized
139
+ if (user) {
140
+ authLogger.info('Profile already loaded from cache, skipping initialization');
141
+ setInitialized(true);
142
+ setIsLoading(false);
143
+ return;
144
+ }
145
+
128
146
  if (hasTokens) {
147
+ setIsLoading(true);
129
148
  try {
130
149
  await loadCurrentProfile();
131
150
  } catch (error) {
@@ -133,15 +152,15 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
133
152
  // If profile loading fails, clear auth state
134
153
  clearAuthState('initializeAuth:loadProfileFailed');
135
154
  }
155
+ setIsLoading(false);
136
156
  } else {
137
157
  setInitialized(true);
158
+ setIsLoading(false);
138
159
  }
139
-
140
- setIsLoading(false);
141
160
  };
142
161
 
143
162
  initializeAuth();
144
- }, [initialized, loadCurrentProfile, clearAuthState]);
163
+ }, [initialized, user, loadCurrentProfile, clearAuthState]);
145
164
 
146
165
  // Redirect logic - only for unauthenticated users on protected pages
147
166
  useEffect(() => {
@@ -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-10-23T06:57:54.947Z
19
+ * Last updated: 2025-10-24T04:54:59.911Z
20
20
  */
21
21
  const PACKAGE_VERSIONS: PackageInfo[] = [
22
22
  {
23
23
  "name": "@djangocfg/ui",
24
- "version": "1.2.2"
24
+ "version": "1.2.4"
25
25
  },
26
26
  {
27
27
  "name": "@djangocfg/api",
28
- "version": "1.2.2"
28
+ "version": "1.2.4"
29
29
  },
30
30
  {
31
31
  "name": "@djangocfg/layouts",
32
- "version": "1.2.2"
32
+ "version": "1.2.4"
33
33
  },
34
34
  {
35
35
  "name": "@djangocfg/markdown",
36
- "version": "1.2.2"
36
+ "version": "1.2.4"
37
37
  },
38
38
  {
39
39
  "name": "@djangocfg/og-image",
40
- "version": "1.2.2"
40
+ "version": "1.2.4"
41
41
  },
42
42
  {
43
43
  "name": "@djangocfg/eslint-config",
44
- "version": "1.2.2"
44
+ "version": "1.2.4"
45
45
  },
46
46
  {
47
47
  "name": "@djangocfg/typescript-config",
48
- "version": "1.2.2"
48
+ "version": "1.2.4"
49
49
  }
50
50
  ];
51
51
 
@@ -10,7 +10,7 @@
10
10
  import React from 'react';
11
11
  import { useRouter } from 'next/router';
12
12
  import { User, ChevronDown, Crown, LogOut, Settings } from 'lucide-react';
13
- import { Button, ButtonLink, Card, CardContent } from '@djangocfg/ui/components';
13
+ import { Button, ButtonLink, Card, CardContent, Avatar, AvatarImage, AvatarFallback } from '@djangocfg/ui/components';
14
14
  import { ThemeToggle } from '@djangocfg/ui/theme';
15
15
  import { useAppContext } from '../context';
16
16
  import { useAuth } from '../../../auth';
@@ -46,6 +46,14 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
46
46
 
47
47
  // === DATA PREPARATION (before rendering) ===
48
48
 
49
+ // Prepare user data
50
+ const userAvatar = user?.avatar || '';
51
+ const userEmail = user?.email || '';
52
+ const displayName = user?.display_username || userEmail || 'User';
53
+ const userInitial = displayName.charAt(0).toUpperCase();
54
+ const dashboardPath = publicLayout.userMenu.dashboardPath;
55
+ const profilePath = publicLayout.userMenu.profilePath;
56
+
49
57
  // Handle logout
50
58
  const handleLogout = React.useCallback(() => {
51
59
  logout();
@@ -66,7 +74,7 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
66
74
  type: 'link',
67
75
  icon: <Settings className="size-4" />,
68
76
  label: 'Profile',
69
- href: publicLayout.userMenu.profilePath,
77
+ href: profilePath,
70
78
  variant: 'ghost',
71
79
  });
72
80
 
@@ -82,7 +90,7 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
82
90
  });
83
91
 
84
92
  return items;
85
- }, [publicLayout.userMenu.profilePath, handleLogout]);
93
+ }, [profilePath, handleLogout]);
86
94
 
87
95
  // === UNIFIED MENU ITEM RENDERER ===
88
96
 
@@ -174,9 +182,9 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
174
182
  {isAuthenticated ? (
175
183
  <div className="flex items-center gap-3">
176
184
  {/* Dashboard button (only if not on dashboard) */}
177
- {publicLayout.userMenu.dashboardPath && !isDashboard && (
185
+ {dashboardPath && !isDashboard && (
178
186
  <ButtonLink
179
- href={publicLayout.userMenu.dashboardPath}
187
+ href={dashboardPath}
180
188
  variant="default"
181
189
  size="sm"
182
190
  className="gap-2"
@@ -189,13 +197,23 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
189
197
  {/* User Dropdown */}
190
198
  <div className="relative">
191
199
  <button
192
- className="flex items-center gap-2 px-3 py-2 rounded-sm text-sm font-medium transition-colors text-foreground hover:text-primary hover:bg-accent/50"
200
+ className="flex items-center gap-2 px-2 py-1.5 rounded-sm text-sm font-medium transition-colors text-foreground hover:text-primary hover:bg-accent/50 cursor-pointer"
193
201
  onClick={toggleUserMenu}
194
202
  aria-haspopup="true"
195
203
  aria-expanded={userMenuOpen}
196
204
  >
197
- <User className="size-4" />
198
- <span className="max-w-[120px] truncate">{user?.email}</span>
205
+ <Avatar className="h-7 w-7 ring-1 ring-primary/20">
206
+ <AvatarImage
207
+ src={userAvatar}
208
+ alt={displayName}
209
+ />
210
+ <AvatarFallback className="bg-primary/10 text-primary text-xs">
211
+ {userInitial}
212
+ </AvatarFallback>
213
+ </Avatar>
214
+ <span className="max-w-[120px] truncate">
215
+ {displayName}
216
+ </span>
199
217
  <ChevronDown
200
218
  className={`size-4 transition-transform ${
201
219
  userMenuOpen ? 'rotate-180' : ''
@@ -207,13 +225,14 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
207
225
  <>
208
226
  {/* Backdrop */}
209
227
  <div
210
- className="fixed inset-0 z-[9995]"
228
+ className="fixed inset-0 z-[9995] bg-transparent"
211
229
  onClick={closeUserMenu}
212
230
  aria-hidden="true"
231
+ style={{ cursor: 'default' }}
213
232
  />
214
233
  {/* Dropdown */}
215
234
  <div
216
- className="absolute top-full right-0 mt-2 w-48 rounded-sm shadow-sm backdrop-blur-xl z-[9996] bg-popover border border-border"
235
+ className="absolute top-full right-0 mt-2 w-52 rounded-sm shadow-sm backdrop-blur-xl z-[9996] bg-popover border border-border"
217
236
  role="menu"
218
237
  aria-label="User menu"
219
238
  >
@@ -222,7 +241,7 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
222
241
  <div className="px-3 py-2 text-sm mb-2 border-b border-border">
223
242
  <div className="text-muted-foreground">Signed in as:</div>
224
243
  <div className="font-medium truncate text-popover-foreground mt-1">
225
- {user?.email}
244
+ {userEmail}
226
245
  </div>
227
246
  </div>
228
247
 
@@ -253,10 +272,10 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
253
272
  {/* User Info Header */}
254
273
  <div className="flex items-center gap-3 mb-4 p-3 rounded-sm border border-border bg-accent/70">
255
274
  <div className="w-10 h-10 rounded-full flex items-center justify-center bg-primary flex-shrink-0 overflow-hidden relative">
256
- {user?.avatar ? (
275
+ {userAvatar ? (
257
276
  <img
258
- src={user.avatar}
259
- alt={user?.email || 'User'}
277
+ src={userAvatar}
278
+ alt={displayName}
260
279
  className="w-10 h-10 rounded-full object-cover"
261
280
  />
262
281
  ) : (
@@ -270,7 +289,7 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
270
289
  Signed in as
271
290
  </p>
272
291
  <p className="text-sm font-semibold truncate text-foreground">
273
- {user?.email}
292
+ {displayName}
274
293
  </p>
275
294
  </div>
276
295
  </div>
@@ -278,9 +297,9 @@ export function UserMenu({ variant, onNavigate }: UserMenuProps) {
278
297
  {/* Action Buttons */}
279
298
  <div className="space-y-3">
280
299
  {/* Dashboard link */}
281
- {publicLayout.userMenu.dashboardPath && (
300
+ {dashboardPath && (
282
301
  <ButtonLink
283
- href={publicLayout.userMenu.dashboardPath}
302
+ href={dashboardPath}
284
303
  variant="default"
285
304
  size="sm"
286
305
  className="w-full h-9 gap-2"
@@ -38,7 +38,7 @@ export function PrivateLayout({ children }: PrivateLayoutProps) {
38
38
 
39
39
  const { privateLayout, routes } = config;
40
40
 
41
- // Redirect unauthenticated users
41
+ // Redirect unauthenticated users (only when not loading)
42
42
  useEffect(() => {
43
43
  if (!isLoading && !isAuthenticated) {
44
44
  const redirect = routes.detectors.getUnauthenticatedRedirect(
@@ -50,19 +50,8 @@ export function PrivateLayout({ children }: PrivateLayoutProps) {
50
50
  }
51
51
  }, [isLoading, isAuthenticated, routes]);
52
52
 
53
- // Show loading state while checking auth
54
- if (isLoading) {
55
- return (
56
- <div className="min-h-screen bg-background flex items-center justify-center">
57
- <div className="flex flex-col items-center gap-4">
58
- <div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin" />
59
- <p className="text-sm text-muted-foreground">Loading...</p>
60
- </div>
61
- </div>
62
- );
63
- }
64
-
65
53
  // Don't render content if user is not authenticated
54
+ // Note: Loading state is now handled in AppLayout, so we don't show it here
66
55
  if (!isAuthenticated) {
67
56
  return null;
68
57
  }
@@ -7,29 +7,18 @@
7
7
 
8
8
  'use client';
9
9
 
10
- import { Bell, LogIn, LogOut, User } from 'lucide-react';
10
+ import { Bell } from 'lucide-react';
11
11
  import React from 'react';
12
12
 
13
13
  import {
14
- Avatar,
15
- AvatarFallback,
16
- AvatarImage,
17
- Badge,
18
14
  Button,
19
- DropdownMenu,
20
- DropdownMenuContent,
21
- DropdownMenuItem,
22
- DropdownMenuLabel,
23
- DropdownMenuSeparator,
24
- DropdownMenuTrigger,
25
15
  Separator,
26
16
  SidebarTrigger,
27
17
  } from '@djangocfg/ui';
28
18
  import { ThemeToggle } from '@djangocfg/ui/theme';
29
- import { useAuthDialog } from '../../../../../snippets';
30
19
  import { useAppContext } from '../../../context';
31
- import { useAuth } from '../../../../../auth';
32
20
  import { useNavigation } from '../../../hooks';
21
+ import { UserMenu } from '../../../components';
33
22
 
34
23
  /**
35
24
  * Dashboard Header Component
@@ -47,10 +36,7 @@ import { useNavigation } from '../../../hooks';
47
36
  */
48
37
  export function DashboardHeader() {
49
38
  const { config } = useAppContext();
50
- const { user } = useAuth();
51
39
  const { getPageTitle } = useNavigation();
52
- const { openAuthDialog } = useAuthDialog();
53
- const { logout } = useAuth();
54
40
 
55
41
  const { privateLayout } = config;
56
42
  const pageTitle = getPageTitle();
@@ -60,10 +46,6 @@ export function DashboardHeader() {
60
46
  console.log('Notifications clicked');
61
47
  };
62
48
 
63
- const handleLogin = () => {
64
- openAuthDialog();
65
- };
66
-
67
49
  return (
68
50
  <header className="sticky top-0 py-2 z-10 h-16 flex items-center justify-between px-4 shrink-0 bg-background border-b border-border">
69
51
  {/* Left side */}
@@ -95,75 +77,8 @@ export function DashboardHeader() {
95
77
  {/* Theme Toggle */}
96
78
  <ThemeToggle />
97
79
 
98
- {/* User menu or Login button */}
99
- {user ? (
100
- <DropdownMenu>
101
- <DropdownMenuTrigger asChild>
102
- <Button variant="ghost" className="flex items-center gap-2 p-2">
103
- <Avatar className="h-8 w-8">
104
- <AvatarImage
105
- src={user.avatar || ''}
106
- alt={user.display_username || user.email || ''}
107
- />
108
- <AvatarFallback className="bg-primary/10 text-primary">
109
- {user.display_username?.charAt(0)?.toUpperCase() || 'U'}
110
- </AvatarFallback>
111
- </Avatar>
112
- <span className="hidden md:block text-sm font-medium">
113
- {user.display_username}
114
- </span>
115
- </Button>
116
- </DropdownMenuTrigger>
117
-
118
- <DropdownMenuContent align="end" className="w-48">
119
- <DropdownMenuLabel className="p-0 font-normal">
120
- <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
121
- <Avatar className="h-8 w-8">
122
- <AvatarImage
123
- src={user.avatar || ''}
124
- alt={user.display_username || user.email || ''}
125
- />
126
- <AvatarFallback className="bg-primary/10 text-primary">
127
- {user.display_username?.charAt(0)?.toUpperCase() || 'U'}
128
- </AvatarFallback>
129
- </Avatar>
130
- <div className="grid flex-1 text-left text-sm leading-tight">
131
- <span className="truncate font-semibold">
132
- {user.display_username || user.full_name || user.email}
133
- </span>
134
- <span className="truncate text-xs text-muted-foreground">
135
- {user.email}
136
- </span>
137
- </div>
138
- </div>
139
- </DropdownMenuLabel>
140
-
141
- <DropdownMenuSeparator />
142
-
143
- <DropdownMenuItem asChild>
144
- <a
145
- href={privateLayout.profileHref}
146
- className="flex items-center gap-2"
147
- >
148
- <User className="h-4 w-4" />
149
- Profile
150
- </a>
151
- </DropdownMenuItem>
152
-
153
- <DropdownMenuSeparator />
154
-
155
- <DropdownMenuItem onClick={logout} className="gap-2">
156
- <LogOut className="h-4 w-4" />
157
- Logout
158
- </DropdownMenuItem>
159
- </DropdownMenuContent>
160
- </DropdownMenu>
161
- ) : (
162
- <Button onClick={handleLogin} size="sm" className="gap-2">
163
- <LogIn className="h-4 w-4" />
164
- <span className="hidden sm:inline">Sign In</span>
165
- </Button>
166
- )}
80
+ {/* User menu - unified component */}
81
+ <UserMenu variant="desktop" />
167
82
  </div>
168
83
  </header>
169
84
  );
@@ -41,7 +41,7 @@ export function Footer() {
41
41
  <div className="w-full px-4 py-8">
42
42
  {/* Project Info */}
43
43
  <div className="text-center space-y-4 mb-6">
44
- <div className="flex items-center justify-center space-x-2">
44
+ <div className="flex items-center justify-center gap-2">
45
45
  <div className="w-6 h-6 flex items-center justify-center">
46
46
  <img
47
47
  src={app.logoPath}
@@ -95,10 +95,10 @@ export function Footer() {
95
95
  {/* Bottom Section */}
96
96
  <div className="border-t border-border pt-4">
97
97
  <div className="text-center space-y-2">
98
- <div className="text-sm text-muted-foreground">
98
+ <div className="text-xs text-muted-foreground">
99
99
  © {currentYear} {app.name}. All rights reserved.
100
100
  </div>
101
- <div className="text-sm text-muted-foreground">
101
+ <div className="text-xs text-muted-foreground flex items-center justify-center gap-1">
102
102
  Made with ❤️ by{' '}
103
103
  <a
104
104
  href="https://reforms.ai"
@@ -152,46 +152,26 @@ export function Footer() {
152
152
  </div>
153
153
 
154
154
  {/* Right Column - Footer Menu Sections */}
155
- <div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8 flex-1">
156
- {footer.menuSections.map((section) => {
157
- // Single item section - render as direct link
158
- if (section.items.length === 1) {
159
- const item = section.items[0];
160
- if (!item) return null;
161
-
162
- return (
163
- <div key={section.title}>
164
- <Link
165
- href={item.path}
166
- className="text-muted-foreground hover:text-primary text-sm transition-colors"
167
- >
168
- {item.label}
169
- </Link>
170
- </div>
171
- );
172
- }
173
-
174
- // Multiple items - render as section
175
- return (
176
- <div key={section.title}>
177
- <h3 className="text-lg font-semibold text-foreground mb-4">
178
- {section.title}
179
- </h3>
180
- <ul className="space-y-2">
181
- {section.items.map((item) => (
182
- <li key={item.path}>
183
- <Link
184
- href={item.path}
185
- className="text-muted-foreground hover:text-primary text-sm transition-colors"
186
- >
187
- {item.label}
188
- </Link>
189
- </li>
190
- ))}
191
- </ul>
192
- </div>
193
- );
194
- })}
155
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-8 flex-1">
156
+ {footer.menuSections.map((section) => (
157
+ <div key={section.title}>
158
+ <h3 className="text-sm font-semibold text-foreground mb-3">
159
+ {section.title}
160
+ </h3>
161
+ <ul className="space-y-2">
162
+ {section.items.map((item) => (
163
+ <li key={item.path}>
164
+ <Link
165
+ href={item.path}
166
+ className="text-muted-foreground hover:text-primary text-sm transition-colors"
167
+ >
168
+ {item.label}
169
+ </Link>
170
+ </li>
171
+ ))}
172
+ </ul>
173
+ </div>
174
+ ))}
195
175
  </div>
196
176
  </div>
197
177
 
@@ -45,9 +45,9 @@ export function Navigation() {
45
45
  const { app, publicLayout } = config;
46
46
 
47
47
  return (
48
- <nav className="sticky top-0 w-full border-b backdrop-blur-xl z-10 bg-background/80 border-border/30">
48
+ <nav className="sticky top-0 w-full border-b backdrop-blur-xl z-[100] bg-background/80 border-border/30 isolate">
49
49
  <div className="w-full px-4 sm:px-6 lg:px-8">
50
- <div className="flex items-center justify-between h-16">
50
+ <div className="flex items-center justify-between py-2 min-h-[40px]">
51
51
  {/* Left side - Logo and Navigation Menu */}
52
52
  <div className="flex items-center gap-6">
53
53
  {/* Logo */}
@@ -153,10 +153,10 @@ export function Navigation() {
153
153
  {isMobile && (
154
154
  <button
155
155
  onClick={toggleMobileDrawer}
156
- className="p-3 rounded-sm border shadow-sm transition-all duration-200 bg-card/50 hover:bg-card border-border/50 hover:border-primary/50 text-foreground hover:text-primary"
156
+ className="p-2 rounded-md transition-colors hover:bg-accent text-foreground hover:text-primary"
157
157
  aria-label="Toggle mobile menu"
158
158
  >
159
- <Menu className="size-5" />
159
+ <Menu className="size-6" />
160
160
  </button>
161
161
  )}
162
162
  </div>
@@ -72,18 +72,23 @@ export const AvatarSection = () => {
72
72
  return (
73
73
  <div className="flex flex-col items-center mb-4">
74
74
  <div className="relative group">
75
- <Avatar className="w-24 h-24 transition-transform group-hover:scale-105">
75
+ <Avatar
76
+ className="aspect-square rounded-full overflow-hidden ring-1 ring-foreground/20 transition-transform group-hover:scale-105"
77
+ style={{ width: '80px', height: '80px' }}
78
+ >
76
79
  {avatarPreview ? (
77
80
  <img
78
81
  src={avatarPreview}
79
82
  alt="Avatar preview"
80
- className="w-full h-full object-cover rounded-full"
83
+ className="w-full h-full object-cover"
84
+ style={{ borderRadius: '50%' }}
81
85
  />
82
86
  ) : user?.avatar ? (
83
87
  <img
84
88
  src={user.avatar}
85
89
  alt="User avatar"
86
- className="w-full h-full object-cover rounded-full"
90
+ className="w-full h-full object-cover"
91
+ style={{ borderRadius: '50%' }}
87
92
  />
88
93
  ) : (
89
94
  <AvatarFallback className="text-2xl font-semibold bg-gradient-to-br from-primary to-primary/80 text-primary-foreground">
@@ -18,6 +18,8 @@ import {
18
18
  Blocks,
19
19
  Github,
20
20
  BookOpen,
21
+ Layout,
22
+ CheckCircle2,
21
23
  } from 'lucide-react';
22
24
  import {
23
25
  SuperHero,
@@ -75,6 +77,7 @@ export function UIGuideLanding({
75
77
  { number: "11", label: "Hooks" },
76
78
  { number: "100%", label: "Type Safe" },
77
79
  ]}
80
+ showBackgroundSwitcher={true}
78
81
  />
79
82
 
80
83
  {/* Features Section using FeatureSection block */}
@@ -124,21 +127,25 @@ export function UIGuideLanding({
124
127
  columns={4}
125
128
  stats={[
126
129
  {
130
+ icon: <Layout className="w-6 h-6" />,
127
131
  number: "56+",
128
132
  label: "UI Components",
129
133
  description: "Production-ready",
130
134
  },
131
135
  {
136
+ icon: <Blocks className="w-6 h-6" />,
132
137
  number: "7",
133
138
  label: "Landing Blocks",
134
139
  description: "Pre-built sections",
135
140
  },
136
141
  {
142
+ icon: <Code2 className="w-6 h-6" />,
137
143
  number: "11",
138
144
  label: "React Hooks",
139
145
  description: "Custom utilities",
140
146
  },
141
147
  {
148
+ icon: <CheckCircle2 className="w-6 h-6" />,
142
149
  number: "100%",
143
150
  label: "Type Safe",
144
151
  description: "Full TypeScript",
@@ -160,39 +167,6 @@ export function UIGuideLanding({
160
167
  }}
161
168
  background="gradient"
162
169
  />
163
-
164
- {/* Simple Footer */}
165
- <footer className="border-t py-8 sm:py-10 md:py-12 bg-background">
166
- <div className="container max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
167
- <div className="flex flex-col md:flex-row items-center justify-between gap-4">
168
- <div className="flex items-center gap-2 text-sm text-muted-foreground">
169
- <Package className="w-4 h-4" />
170
- <span>Built with Radix UI + Tailwind CSS</span>
171
- </div>
172
- <div className="flex items-center gap-6">
173
- <button
174
- onClick={handleBrowseComponents}
175
- className="text-sm text-muted-foreground hover:text-foreground transition-colors"
176
- >
177
- Components
178
- </button>
179
- <button
180
- onClick={handleViewBlocks}
181
- className="text-sm text-muted-foreground hover:text-foreground transition-colors"
182
- >
183
- Blocks
184
- </button>
185
- <button
186
- onClick={() => window.open(githubUrl, '_blank')}
187
- className="text-sm text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
188
- >
189
- <Github className="w-4 h-4" />
190
- GitHub
191
- </button>
192
- </div>
193
- </div>
194
- </div>
195
- </footer>
196
170
  </div>
197
171
  );
198
172
  }
@@ -123,13 +123,6 @@ function SidebarContent({
123
123
  </div>
124
124
  </div>
125
125
  </div>
126
-
127
- {/* Footer */}
128
- <div className="border-t p-4">
129
- <p className="text-xs text-muted-foreground">
130
- Built with Radix UI + Tailwind CSS v4
131
- </p>
132
- </div>
133
126
  </div>
134
127
  );
135
128
  }
@@ -12,7 +12,7 @@ import {
12
12
  StatsSection,
13
13
  TestimonialSection,
14
14
  } from '@djangocfg/ui/blocks';
15
- import { Sparkles, BookOpen, Zap, Shield, Code } from 'lucide-react';
15
+ import { Sparkles, BookOpen, Zap, Shield, Code, Users, Building2, TrendingUp, Headphones } from 'lucide-react';
16
16
  import type { ComponentConfig } from './types';
17
17
 
18
18
  export const BLOCKS: ComponentConfig[] = [
@@ -60,7 +60,9 @@ export const BLOCKS: ComponentConfig[] = [
60
60
  { number: "6", label: "Hooks" },
61
61
  { number: "100%", label: "Type Safe" }
62
62
  ]}
63
- scrollIndicator={false}
63
+ backgroundVariant="waves"
64
+ backgroundIntensity="medium"
65
+ showBackgroundSwitcher={true}
64
66
  />`,
65
67
  preview: (
66
68
  <SuperHero
@@ -82,7 +84,9 @@ export const BLOCKS: ComponentConfig[] = [
82
84
  { number: "6", label: "Hooks" },
83
85
  { number: "100%", label: "Type Safe" },
84
86
  ]}
85
- scrollIndicator={false}
87
+ backgroundVariant="waves"
88
+ backgroundIntensity="medium"
89
+ showBackgroundSwitcher={true}
86
90
  />
87
91
  ),
88
92
  },
@@ -181,18 +185,20 @@ export const BLOCKS: ComponentConfig[] = [
181
185
  example: `<StatsSection
182
186
  title="Our Impact"
183
187
  stats={[
184
- { number: "10K+", label: "Active Users" },
185
- { number: "99.9%", label: "Uptime" }
188
+ { icon: <Users className="w-6 h-6" />, number: "10K+", label: "Active Users" },
189
+ { icon: <Building2 className="w-6 h-6" />, number: "500+", label: "Companies" },
190
+ { icon: <TrendingUp className="w-6 h-6" />, number: "99.9%", label: "Uptime" },
191
+ { icon: <Headphones className="w-6 h-6" />, number: "24/7", label: "Support" }
186
192
  ]}
187
193
  />`,
188
194
  preview: (
189
195
  <StatsSection
190
196
  title="Our Impact"
191
197
  stats={[
192
- { number: "10K+", label: "Active Users" },
193
- { number: "500+", label: "Companies" },
194
- { number: "99.9%", label: "Uptime" },
195
- { number: "24/7", label: "Support" }
198
+ { icon: <Users className="w-6 h-6" />, number: "10K+", label: "Active Users" },
199
+ { icon: <Building2 className="w-6 h-6" />, number: "500+", label: "Companies" },
200
+ { icon: <TrendingUp className="w-6 h-6" />, number: "99.9%", label: "Uptime" },
201
+ { icon: <Headphones className="w-6 h-6" />, number: "24/7", label: "Support" }
196
202
  ]}
197
203
  />
198
204
  ),
@@ -30,6 +30,12 @@ export const TAILWIND_GUIDE: TailwindGuide = {
30
30
  "Use standard Tailwind classes only: py-16 sm:py-20 md:py-24 lg:py-32",
31
31
  "Responsive patterns: px-4 sm:px-6 lg:px-8",
32
32
  "Container pattern: container max-w-7xl mx-auto",
33
+ "IMPORTANT: Arbitrary values like h-[80px] may NOT work in v4 due to changed syntax",
34
+ "For fixed sizes: Use inline styles style={{ width: '80px', height: '80px' }} - always reliable",
35
+ "Spacing utilities (h-20, p-4, etc.) require --spacing-* variables defined in @theme block before Tailwind import",
36
+ "Import order is critical but hard to fix: changing order breaks other styles",
37
+ "Use aspect-square for maintaining 1:1 ratio (circles, squares)",
38
+ "Use overflow-hidden with rounded-full for perfect circles",
33
39
  "Avoid custom utilities like: section-padding, animate-*, shadow-brand",
34
40
  "Mobile-first approach with breakpoints: sm: (640px), md: (768px), lg: (1024px), xl: (1280px)",
35
41
  "Use CSS variables: var(--color-primary), var(--font-family-sans)"
@@ -53,6 +59,7 @@ export const TAILWIND_GUIDE: TailwindGuide = {
53
59
  --color-primary: #3b82f6;
54
60
  --color-secondary: #8b5cf6;
55
61
  --font-family-sans: ui-sans-serif, system-ui, sans-serif;
62
+ --spacing-20: 5rem; /* Required for h-20, w-20 to work */
56
63
  }`
57
64
  },
58
65
  {
@@ -65,6 +72,16 @@ export const TAILWIND_GUIDE: TailwindGuide = {
65
72
  </h1>
66
73
  </div>
67
74
  </section>`
75
+ },
76
+ {
77
+ title: "Fixed Sizes with Inline Styles",
78
+ description: "Most reliable way to set exact dimensions in v4",
79
+ code: `<Avatar
80
+ className="aspect-square rounded-full overflow-hidden ring-1 ring-foreground/20"
81
+ style={{ width: '80px', height: '80px' }}
82
+ >
83
+ <AvatarImage src={avatar} alt="User" />
84
+ </Avatar>`
68
85
  },
69
86
  {
70
87
  title: "Component Styling",