Skip to content

Commit a216577

Browse files
feat: implement loading components and lazy loading functionality
1 parent a56cf4d commit a216577

File tree

7 files changed

+136
-9
lines changed

7 files changed

+136
-9
lines changed

src/components/loading.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { cn } from "@/lib/utils";
2+
3+
interface LoadingProps {
4+
size?: "sm" | "md" | "lg";
5+
text?: string;
6+
className?: string;
7+
}
8+
9+
export function Loading({ size = "md", text, className }: LoadingProps) {
10+
const sizeClasses = {
11+
sm: "w-4 h-4",
12+
md: "w-8 h-8",
13+
lg: "w-12 h-12",
14+
};
15+
16+
return (
17+
<div
18+
className={cn("flex flex-col items-center justify-center p-8", className)}
19+
>
20+
<div
21+
className={cn(
22+
"animate-spin rounded-full border-2 border-gray-300 border-t-blue-600",
23+
sizeClasses[size]
24+
)}
25+
/>
26+
{text && <p className="mt-2 text-sm text-gray-600">{text}</p>}
27+
</div>
28+
);
29+
}
30+
31+
export function PageLoading({ text = "Loading..." }: { text?: string }) {
32+
return (
33+
<div className="min-h-screen flex items-center justify-center bg-gray-50">
34+
<Loading size="lg" text={text} />
35+
</div>
36+
);
37+
}
38+
39+
export default Loading;

src/lib/utils/lazy-loader.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { lazy, Suspense } from "react";
2+
import type { ComponentType, ReactNode } from "react";
3+
import { PageLoading } from "@/components/loading";
4+
5+
// Type for the import function
6+
type ImportFunction = () => Promise<{ default: ComponentType<unknown> }>;
7+
8+
// Options for the lazy wrapper
9+
interface LazyOptions {
10+
fallback?: ReactNode;
11+
displayName?: string;
12+
}
13+
14+
/**
15+
* Higher-order function that wraps React.lazy with configurable fallback
16+
* and automatic component naming
17+
*
18+
* @param importFn - The dynamic import function
19+
* @param options - Configuration options for fallback and naming
20+
* @returns A React component wrapped with Suspense and lazy loading
21+
*/
22+
export function createLazyComponent(
23+
importFn: ImportFunction,
24+
options: LazyOptions = {}
25+
): ComponentType<unknown> {
26+
const { fallback, displayName } = options;
27+
28+
// Create the lazy component
29+
const LazyComponent = lazy(importFn);
30+
31+
// Create the wrapper component with Suspense
32+
const WrappedComponent: ComponentType<unknown> = (props: unknown) => {
33+
const defaultFallback = <PageLoading text="Loading page..." />;
34+
35+
return (
36+
<Suspense fallback={fallback || defaultFallback}>
37+
<LazyComponent {...(props as Record<string, unknown>)} />
38+
</Suspense>
39+
);
40+
};
41+
42+
// Set display name for better debugging
43+
if (displayName) {
44+
WrappedComponent.displayName = `Lazy(${displayName})`;
45+
} else {
46+
// Try to extract component name from import path
47+
const importStr = importFn.toString();
48+
const pathMatch = importStr.match(/import\(["'](.+)["']\)/);
49+
if (pathMatch) {
50+
const path = pathMatch[1];
51+
const componentName =
52+
path.split("/").pop()?.replace(/['"]/g, "") || "Component";
53+
WrappedComponent.displayName = `Lazy(${componentName})`;
54+
} else {
55+
WrappedComponent.displayName = "LazyComponent";
56+
}
57+
}
58+
59+
return WrappedComponent;
60+
}
61+
62+
/**
63+
* Convenience wrapper around createLazyComponent that always provides
64+
* a PageLoading fallback component, unless another fallback is explicitly
65+
* provided.
66+
*
67+
* @param importFn - The dynamic import function
68+
* @param options - Configuration options for fallback and naming
69+
* @returns A React component wrapped with Suspense and lazy loading
70+
*/
71+
export function withLazyLoading(
72+
importFn: ImportFunction,
73+
options: LazyOptions = {}
74+
): ComponentType<unknown> {
75+
return createLazyComponent(importFn, {
76+
fallback: options.fallback || <PageLoading />,
77+
});
78+
}

src/pages/admin/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export { default as AdminPage } from "./admin-page";
1+
import AdminPage from "./admin-page";
2+
3+
export default AdminPage;

src/pages/create-post/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export { default as CreatePostPage } from "./create-post-page";
1+
import CreatePostPage from "./create-post-page";
2+
3+
export default CreatePostPage;

src/pages/forbidden/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export { default as ForbiddenPage } from "./forbidden-page";
1+
import ForbiddenPage from "./forbidden-page";
2+
3+
export default ForbiddenPage;

src/pages/home/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export { default as HomePage } from "./home-page";
1+
import HomePage from "./home-page";
2+
3+
export default HomePage;

src/routes/index.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ import Layout from "../layout";
33

44
import ErrorElement from "@/components/error-element";
55
import NotFound from "@/components/not-found";
6-
import { ForbiddenPage } from "@/pages/forbidden";
76
import type { RouteObject } from "react-router";
87
import authRoutes from "./auth-routes";
98
import MainLayout from "@/components/layout/main-layout";
109
import withGuards from "@/lib/utils/with-guards";
11-
import { AdminPage } from "@/pages/admin";
12-
import { CreatePostPage } from "@/pages/create-post";
13-
import { HomePage } from "@/pages/home";
14-
import ThemeDemo from "@/components/theme-demo";
10+
import HomePage from "@/pages/home";
11+
import { withLazyLoading } from "@/lib/utils/lazy-loader";
12+
13+
const AdminPage = withLazyLoading(() => import("@/pages/admin"));
14+
const CreatePostPage = withLazyLoading(() => import("@/pages/create-post"));
15+
const ForbiddenPage = withLazyLoading(() => import("@/pages/forbidden"));
16+
const ThemeDemo = withLazyLoading(() => import("@/components/theme-demo"));
1517

1618
const routes: RouteObject[] = [
1719
{

0 commit comments

Comments
 (0)