@djangocfg/layouts 1.0.6 → 1.1.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.
Files changed (50) hide show
  1. package/package.json +5 -5
  2. package/src/layouts/AppLayout/AppLayout.tsx +126 -28
  3. package/src/layouts/AppLayout/components/ErrorBoundary.tsx +99 -0
  4. package/src/layouts/AppLayout/components/PageProgress.tsx +28 -11
  5. package/src/layouts/AppLayout/components/index.ts +1 -0
  6. package/src/layouts/AppLayout/layouts/AuthLayout/AuthContext.tsx +1 -1
  7. package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +5 -5
  8. package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +2 -2
  9. package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +2 -2
  10. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +1 -1
  11. package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +1 -1
  12. package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +53 -36
  13. package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +64 -52
  14. package/src/layouts/AppLayout/types/config.ts +22 -0
  15. package/src/layouts/ErrorLayout/ErrorLayout.tsx +169 -0
  16. package/src/layouts/ErrorLayout/errorConfig.tsx +152 -0
  17. package/src/layouts/ErrorLayout/index.ts +8 -0
  18. package/src/layouts/UILayout/README.md +267 -0
  19. package/src/layouts/UILayout/REFACTORING.md +331 -0
  20. package/src/layouts/UILayout/UIGuideApp.tsx +18 -0
  21. package/src/layouts/UILayout/UIGuideLanding.tsx +198 -0
  22. package/src/layouts/UILayout/UIGuideView.tsx +61 -0
  23. package/src/layouts/UILayout/UILayout.tsx +122 -0
  24. package/src/layouts/UILayout/components/AutoComponentDemo.tsx +77 -0
  25. package/src/layouts/UILayout/components/CategoryRenderer.tsx +45 -0
  26. package/src/layouts/UILayout/components/Header.tsx +114 -0
  27. package/src/layouts/UILayout/components/MobileOverlay.tsx +33 -0
  28. package/src/layouts/UILayout/components/Sidebar.tsx +195 -0
  29. package/src/layouts/UILayout/components/TailwindGuideRenderer.tsx +138 -0
  30. package/src/layouts/UILayout/config/ai-export.config.ts +80 -0
  31. package/src/layouts/UILayout/config/categories.config.tsx +114 -0
  32. package/src/layouts/UILayout/config/components/blocks.config.tsx +233 -0
  33. package/src/layouts/UILayout/config/components/data.config.tsx +308 -0
  34. package/src/layouts/UILayout/config/components/feedback.config.tsx +246 -0
  35. package/src/layouts/UILayout/config/components/forms.config.tsx +171 -0
  36. package/src/layouts/UILayout/config/components/hooks.config.tsx +131 -0
  37. package/src/layouts/UILayout/config/components/index.ts +69 -0
  38. package/src/layouts/UILayout/config/components/layout.config.tsx +133 -0
  39. package/src/layouts/UILayout/config/components/navigation.config.tsx +244 -0
  40. package/src/layouts/UILayout/config/components/overlay.config.tsx +561 -0
  41. package/src/layouts/UILayout/config/components/specialized.config.tsx +125 -0
  42. package/src/layouts/UILayout/config/components/types.ts +14 -0
  43. package/src/layouts/UILayout/config/index.ts +42 -0
  44. package/src/layouts/UILayout/config/tailwind.config.ts +77 -0
  45. package/src/layouts/UILayout/constants.ts +23 -0
  46. package/src/layouts/UILayout/context/ShowcaseContext.tsx +53 -0
  47. package/src/layouts/UILayout/context/index.ts +1 -0
  48. package/src/layouts/UILayout/index.ts +64 -0
  49. package/src/layouts/UILayout/types.ts +13 -0
  50. package/src/layouts/index.ts +5 -1
@@ -9,15 +9,12 @@
9
9
 
10
10
  import React from 'react';
11
11
  import Link from 'next/link';
12
- import { Menu } from 'lucide-react';
12
+ import { Menu, ChevronDown } from 'lucide-react';
13
13
  import {
14
- NavigationMenu,
15
- NavigationMenuContent,
16
- NavigationMenuItem,
17
- NavigationMenuLink,
18
- NavigationMenuList,
19
- NavigationMenuTrigger,
20
- navigationMenuTriggerStyle,
14
+ DropdownMenu,
15
+ DropdownMenuTrigger,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
21
18
  } from '@djangocfg/ui/components';
22
19
  import { ThemeToggle } from '@djangocfg/ui/theme';
23
20
  import { cn } from '@djangocfg/ui/lib';
@@ -45,15 +42,16 @@ export function Navigation() {
45
42
  const { user, isAuthenticated, logout } = useAuth();
46
43
  const { isActive } = useNavigation();
47
44
  const isMobile = useIsMobile();
45
+ const [openDropdown, setOpenDropdown] = React.useState<string | null>(null);
48
46
 
49
47
  const { app, publicLayout } = config;
50
48
 
51
49
  return (
52
- <nav className="sticky top-0 w-full border-b backdrop-blur-xl z-[100] bg-background/80 border-border/30">
50
+ <nav className="sticky top-0 w-full border-b backdrop-blur-xl z-10 bg-background/80 border-border/30">
53
51
  <div className="w-full px-4 sm:px-6 lg:px-8">
54
52
  <div className="flex items-center justify-between h-16">
55
53
  {/* Left side - Logo and Navigation Menu */}
56
- <div className="flex items-center gap-6 flex-1">
54
+ <div className="flex items-center gap-6">
57
55
  {/* Logo */}
58
56
  <Link
59
57
  href={publicLayout.navigation.homePath}
@@ -69,11 +67,9 @@ export function Navigation() {
69
67
  </span>
70
68
  </Link>
71
69
 
72
- {/* Desktop Navigation Menu using @djangocfg/ui NavigationMenu */}
70
+ {/* Desktop Navigation Menu */}
73
71
  {!isMobile && (
74
- <div>
75
- <NavigationMenu>
76
- <NavigationMenuList>
72
+ <div className="flex items-center gap-1">
77
73
  {publicLayout.navigation.menuSections.map((section) => {
78
74
  // Single item section - render as direct link
79
75
  if (section.items.length === 1) {
@@ -81,53 +77,69 @@ export function Navigation() {
81
77
  if (!item) return null;
82
78
 
83
79
  return (
84
- <NavigationMenuItem key={section.title}>
85
- <NavigationMenuLink asChild>
86
- <Link
87
- href={item.path}
88
- className={cn(
89
- navigationMenuTriggerStyle(),
90
- isActive(item.path) && 'bg-accent text-accent-foreground'
91
- )}
92
- >
93
- {item.label}
94
- </Link>
95
- </NavigationMenuLink>
96
- </NavigationMenuItem>
80
+ <Link
81
+ key={section.title}
82
+ href={item.path}
83
+ className={cn(
84
+ 'inline-flex h-9 items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50',
85
+ isActive(item.path) && 'bg-accent text-accent-foreground'
86
+ )}
87
+ >
88
+ {item.label}
89
+ </Link>
97
90
  );
98
91
  }
99
92
 
100
93
  // Multiple items - render as dropdown menu
101
94
  return (
102
- <NavigationMenuItem key={section.title}>
103
- <NavigationMenuTrigger>{section.title}</NavigationMenuTrigger>
104
- <NavigationMenuContent>
105
- <ul className="grid gap-3 p-4" style={{ width: '400px' }}>
95
+ <div
96
+ key={section.title}
97
+ onMouseEnter={() => setOpenDropdown(section.title)}
98
+ onMouseLeave={() => setOpenDropdown(null)}
99
+ >
100
+ <DropdownMenu
101
+ open={openDropdown === section.title}
102
+ onOpenChange={(open) => setOpenDropdown(open ? section.title : null)}
103
+ modal={false}
104
+ >
105
+ <DropdownMenuTrigger
106
+ className={cn(
107
+ "inline-flex h-9 items-center justify-center gap-1 rounded-md px-4 py-2 text-sm font-medium transition-colors focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50",
108
+ openDropdown === section.title ? "bg-accent text-accent-foreground" : "hover:bg-accent hover:text-accent-foreground"
109
+ )}
110
+ >
111
+ {section.title}
112
+ <ChevronDown className="h-3 w-3 transition-transform duration-200 group-data-[state=open]:rotate-180" />
113
+ </DropdownMenuTrigger>
114
+ <DropdownMenuContent
115
+ align="start"
116
+ sideOffset={0}
117
+ className="p-2"
118
+ style={{
119
+ minWidth: '250px',
120
+ backdropFilter: 'blur(24px)',
121
+ WebkitBackdropFilter: 'blur(24px)'
122
+ }}
123
+ >
106
124
  {section.items.map((item) => (
107
- <li key={item.path}>
108
- <NavigationMenuLink asChild>
109
- <Link
110
- href={item.path}
111
- className={cn(
112
- 'block select-none rounded-md p-3 leading-none no-underline outline-none transition-colors whitespace-nowrap hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground',
113
- isActive(item.path) && 'bg-accent/50'
114
- )}
115
- >
116
- <div className="text-sm font-medium leading-none">
117
- {item.label}
118
- </div>
119
- </Link>
120
- </NavigationMenuLink>
121
- </li>
125
+ <DropdownMenuItem key={item.path} asChild>
126
+ <Link
127
+ href={item.path}
128
+ className={cn(
129
+ 'cursor-pointer w-full hover:bg-accent hover:text-accent-foreground transition-colors text-base px-4 py-3 rounded-md',
130
+ isActive(item.path) && 'bg-accent/50'
131
+ )}
132
+ >
133
+ {item.label}
134
+ </Link>
135
+ </DropdownMenuItem>
122
136
  ))}
123
- </ul>
124
- </NavigationMenuContent>
125
- </NavigationMenuItem>
137
+ </DropdownMenuContent>
138
+ </DropdownMenu>
139
+ </div>
126
140
  );
127
141
  })}
128
- </NavigationMenuList>
129
- </NavigationMenu>
130
- </div>
142
+ </div>
131
143
  )}
132
144
  </div>
133
145
 
@@ -37,4 +37,26 @@ export interface AppLayoutConfig {
37
37
 
38
38
  /** Private layout configuration */
39
39
  privateLayout: PrivateLayoutConfig;
40
+
41
+ /** Error handling configuration */
42
+ errors?: {
43
+ /** Enable automatic error boundary (default: true) */
44
+ enableErrorBoundary?: boolean;
45
+ /** Support email for error pages */
46
+ supportEmail?: string;
47
+ /** Custom error handler callback */
48
+ onError?: (error: Error, errorInfo?: React.ErrorInfo) => void;
49
+ };
50
+
51
+ /** Auth configuration */
52
+ auth?: {
53
+ /** Terms of Service URL */
54
+ termsUrl?: string;
55
+ /** Privacy Policy URL */
56
+ privacyUrl?: string;
57
+ /** Support URL for auth help */
58
+ supportUrl?: string;
59
+ /** Enable phone authentication (default: false) */
60
+ enablePhoneAuth?: boolean;
61
+ };
40
62
  }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * ErrorLayout - Universal Error Display
3
+ *
4
+ * Minimalist error page with customizable content
5
+ * Works with Next.js error pages (404.tsx, 500.tsx, error.tsx)
6
+ *
7
+ * Usage:
8
+ * ```tsx
9
+ * // pages/404.tsx
10
+ * import { ErrorLayout } from '@djangocfg/layouts/AppLayout';
11
+ *
12
+ * export default function NotFound() {
13
+ * return (
14
+ * <ErrorLayout
15
+ * code="404"
16
+ * title="Page Not Found"
17
+ * description="The page you're looking for doesn't exist."
18
+ * />
19
+ * );
20
+ * }
21
+ * ```
22
+ */
23
+
24
+ 'use client';
25
+
26
+ import React from 'react';
27
+ import { useRouter } from 'next/router';
28
+ import { Button } from '@djangocfg/ui/components';
29
+ import { getErrorContent } from './errorConfig';
30
+
31
+ export interface ErrorLayoutProps {
32
+ /** Error code (e.g., "404", "500", "403") - if provided, auto-configures title/description/icon */
33
+ code?: string | number;
34
+ /** Error title (auto-generated from code if not provided) */
35
+ title?: string;
36
+ /** Error description (auto-generated from code if not provided) */
37
+ description?: string;
38
+ /** Custom action buttons */
39
+ actions?: React.ReactNode;
40
+ /** Show default actions (back, home) */
41
+ showDefaultActions?: boolean;
42
+ /** Custom illustration/icon (auto-generated from code if not provided) */
43
+ illustration?: React.ReactNode;
44
+ /** Support email for contact link */
45
+ supportEmail?: string;
46
+ }
47
+
48
+ /**
49
+ * ErrorLayout Component
50
+ *
51
+ * Clean, minimal error display with semantic colors
52
+ * Standalone layout - doesn't depend on AppLayout context
53
+ *
54
+ * Smart auto-configuration:
55
+ * - Pass only `code` prop and everything is configured automatically
56
+ * - Or override with custom title/description/illustration
57
+ */
58
+ export function ErrorLayout({
59
+ code,
60
+ title,
61
+ description,
62
+ actions,
63
+ showDefaultActions = true,
64
+ illustration,
65
+ supportEmail = 'support@example.com',
66
+ }: ErrorLayoutProps) {
67
+ const router = useRouter();
68
+
69
+ // Auto-configure content from error code if not provided
70
+ const autoContent = code && (!title || !description || !illustration)
71
+ ? getErrorContent(code)
72
+ : null;
73
+
74
+ // Use provided values or fall back to auto-generated
75
+ const finalTitle = title || autoContent?.title || 'Error';
76
+ const finalDescription = description || autoContent?.description;
77
+ const finalIllustration = illustration || autoContent?.icon;
78
+
79
+ const handleGoBack = () => {
80
+ if (window.history.length > 1) {
81
+ router.back();
82
+ } else {
83
+ router.push('/');
84
+ }
85
+ };
86
+
87
+ const handleGoHome = () => {
88
+ router.push('/');
89
+ };
90
+
91
+ return (
92
+ <div className="min-h-screen flex items-center justify-center px-4">
93
+ <div className="max-w-2xl w-full text-center space-y-8">
94
+ {/* Error Code */}
95
+ {code && (
96
+ <div className="relative">
97
+ <h1
98
+ className="text-[12rem] font-bold leading-none text-muted/20 select-none"
99
+ aria-hidden="true"
100
+ >
101
+ {code}
102
+ </h1>
103
+ </div>
104
+ )}
105
+
106
+ {/* Illustration */}
107
+ {finalIllustration && (
108
+ <div className="flex justify-center py-8">
109
+ {finalIllustration}
110
+ </div>
111
+ )}
112
+
113
+ {/* Error Content */}
114
+ <div className="space-y-4">
115
+ <h2 className="text-4xl font-bold text-foreground">
116
+ {finalTitle}
117
+ </h2>
118
+
119
+ {finalDescription && (
120
+ <p className="text-lg text-muted-foreground max-w-md mx-auto">
121
+ {finalDescription}
122
+ </p>
123
+ )}
124
+ </div>
125
+
126
+ {/* Actions */}
127
+ <div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
128
+ {/* Custom actions */}
129
+ {actions}
130
+
131
+ {/* Default actions */}
132
+ {showDefaultActions && !actions && (
133
+ <>
134
+ <Button
135
+ variant="outline"
136
+ size="lg"
137
+ onClick={handleGoBack}
138
+ className="min-w-[140px]"
139
+ >
140
+ Go Back
141
+ </Button>
142
+ <Button
143
+ variant="default"
144
+ size="lg"
145
+ onClick={handleGoHome}
146
+ className="min-w-[140px]"
147
+ >
148
+ Go Home
149
+ </Button>
150
+ </>
151
+ )}
152
+ </div>
153
+
154
+ {/* Additional Info */}
155
+ <div className="pt-8 text-sm text-muted-foreground">
156
+ <p>
157
+ Need help? Contact{' '}
158
+ <a
159
+ href={`mailto:${supportEmail}`}
160
+ className="text-primary hover:underline"
161
+ >
162
+ support
163
+ </a>
164
+ </p>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ );
169
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Universal Error Configuration
3
+ *
4
+ * Provides standard error content for common HTTP status codes
5
+ * Use this to maintain consistency across error pages
6
+ */
7
+
8
+ import React from 'react';
9
+ import { FileQuestion, ServerCrash, ShieldAlert, Clock, AlertTriangle, Ban } from 'lucide-react';
10
+
11
+ export interface ErrorContent {
12
+ title: string;
13
+ description: string;
14
+ icon: React.ReactNode;
15
+ }
16
+
17
+ /**
18
+ * Get standardized error content based on status code
19
+ *
20
+ * @param statusCode - HTTP status code or custom error type
21
+ * @returns Error content configuration
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * const { title, description, icon } = getErrorContent(404);
26
+ * <ErrorLayout title={title} description={description} illustration={icon} />
27
+ * ```
28
+ */
29
+ export function getErrorContent(statusCode?: number | string): ErrorContent {
30
+ const code = typeof statusCode === 'string' ? parseInt(statusCode, 10) : statusCode;
31
+
32
+ switch (code) {
33
+ // 400 Bad Request
34
+ case 400:
35
+ return {
36
+ title: 'Bad Request',
37
+ description: 'The request could not be understood. Please check your input and try again.',
38
+ icon: <AlertTriangle className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
39
+ };
40
+
41
+ // 401 Unauthorized
42
+ case 401:
43
+ return {
44
+ title: 'Authentication Required',
45
+ description: 'You need to sign in to access this page.',
46
+ icon: <ShieldAlert className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
47
+ };
48
+
49
+ // 403 Forbidden
50
+ case 403:
51
+ return {
52
+ title: 'Access Denied',
53
+ description: "You don't have permission to access this resource.",
54
+ icon: <Ban className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
55
+ };
56
+
57
+ // 404 Not Found
58
+ case 404:
59
+ return {
60
+ title: 'Page Not Found',
61
+ description: "The page you're looking for doesn't exist or has been moved.",
62
+ icon: <FileQuestion className="w-24 h-24 text-muted-foreground/50" strokeWidth={1.5} />,
63
+ };
64
+
65
+ // 408 Request Timeout
66
+ case 408:
67
+ return {
68
+ title: 'Request Timeout',
69
+ description: 'The request took too long to process. Please try again.',
70
+ icon: <Clock className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
71
+ };
72
+
73
+ // 500 Internal Server Error
74
+ case 500:
75
+ return {
76
+ title: 'Server Error',
77
+ description: "Something went wrong on our end. We're working to fix it.",
78
+ icon: <ServerCrash className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
79
+ };
80
+
81
+ // 502 Bad Gateway
82
+ case 502:
83
+ return {
84
+ title: 'Bad Gateway',
85
+ description: 'The server received an invalid response. Please try again later.',
86
+ icon: <ServerCrash className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
87
+ };
88
+
89
+ // 503 Service Unavailable
90
+ case 503:
91
+ return {
92
+ title: 'Service Unavailable',
93
+ description: 'The service is temporarily unavailable. Please try again later.',
94
+ icon: <ServerCrash className="w-24 h-24 text-destructive/50" strokeWidth={1.5} />,
95
+ };
96
+
97
+ // 504 Gateway Timeout
98
+ case 504:
99
+ return {
100
+ title: 'Gateway Timeout',
101
+ description: 'The server took too long to respond. Please try again.',
102
+ icon: <Clock className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
103
+ };
104
+
105
+ // Default / Unknown Error
106
+ default:
107
+ return {
108
+ title: 'Something Went Wrong',
109
+ description: 'An unexpected error occurred. Please try again or contact support.',
110
+ icon: <AlertTriangle className="w-24 h-24 text-warning/50" strokeWidth={1.5} />,
111
+ };
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Common error codes as constants
117
+ */
118
+ export const ERROR_CODES = {
119
+ BAD_REQUEST: 400,
120
+ UNAUTHORIZED: 401,
121
+ FORBIDDEN: 403,
122
+ NOT_FOUND: 404,
123
+ TIMEOUT: 408,
124
+ SERVER_ERROR: 500,
125
+ BAD_GATEWAY: 502,
126
+ SERVICE_UNAVAILABLE: 503,
127
+ GATEWAY_TIMEOUT: 504,
128
+ } as const;
129
+
130
+ /**
131
+ * Ready-to-use getInitialProps for Next.js _error.tsx page
132
+ *
133
+ * Extracts status code from response or error automatically
134
+ * Works for both server-side and client-side errors
135
+ *
136
+ * @example
137
+ * ```tsx
138
+ * // pages/_error.tsx
139
+ * import { ErrorLayout, errorPageGetInitialProps } from '@djangocfg/layouts';
140
+ *
141
+ * function ErrorPage({ statusCode }) {
142
+ * return <ErrorLayout code={statusCode} />;
143
+ * }
144
+ *
145
+ * ErrorPage.getInitialProps = errorPageGetInitialProps;
146
+ * export default ErrorPage;
147
+ * ```
148
+ */
149
+ export const errorPageGetInitialProps = ({ res, err }: any) => {
150
+ const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
151
+ return { statusCode };
152
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * ErrorLayout Module
3
+ */
4
+
5
+ export { ErrorLayout } from './ErrorLayout';
6
+ export type { ErrorLayoutProps } from './ErrorLayout';
7
+ export { getErrorContent, ERROR_CODES, errorPageGetInitialProps } from './errorConfig';
8
+ export type { ErrorContent } from './errorConfig';