From 2031d5c4ea0461784067319fb392660fb54a452d Mon Sep 17 00:00:00 2001 From: blegodwin Date: Mon, 27 Jul 2026 12:21:40 +0100 Subject: [PATCH] feat: [FE-80] Add loading skeletons for data-heavy routes --- .../LoadingSkeletonsComponent.spec.tsx | 10 ++++++++++ .../LoadingSkeletons/LoadingSkeletonsComponent.tsx | 13 +++++++++++++ .../LoadingSkeletons/LoadingSkeletonsStore.ts | 8 ++++++++ .../LoadingSkeletons/LoadingSkeletonsTypes.ts | 8 ++++++++ .../LoadingSkeletons/useLoadingSkeletons.ts | 7 +++++++ 5 files changed, 46 insertions(+) create mode 100644 frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.spec.tsx create mode 100644 frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.tsx create mode 100644 frontend/features/LoadingSkeletons/LoadingSkeletonsStore.ts create mode 100644 frontend/features/LoadingSkeletons/LoadingSkeletonsTypes.ts create mode 100644 frontend/features/LoadingSkeletons/useLoadingSkeletons.ts diff --git a/frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.spec.tsx b/frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.spec.tsx new file mode 100644 index 00000000..40318fdd --- /dev/null +++ b/frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.spec.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import { describe, it, expect } from "vitest"; +import { render } from "@testing-library/react"; +import { LoadingSkeletonsComponent } from "./LoadingSkeletonsComponent"; +describe("LoadingSkeletonsComponent", () => { + it("renders correctly", () => { + const { container } = render(); + expect(container.firstChild).toBeDefined(); + }); +}); diff --git a/frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.tsx b/frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.tsx new file mode 100644 index 00000000..91bd492f --- /dev/null +++ b/frontend/features/LoadingSkeletons/LoadingSkeletonsComponent.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { useLoadingSkeletons } from "./useLoadingSkeletons"; +export const LoadingSkeletonsComponent: React.FC = () => { + const { isLoading, config } = useLoadingSkeletons(); + if (!isLoading) return null; + return ( +
+ {Array.from({ length: config.rowCount }).map((_, i) => ( +
+ ))} +
+ ); +}; diff --git a/frontend/features/LoadingSkeletons/LoadingSkeletonsStore.ts b/frontend/features/LoadingSkeletons/LoadingSkeletonsStore.ts new file mode 100644 index 00000000..64b52f60 --- /dev/null +++ b/frontend/features/LoadingSkeletons/LoadingSkeletonsStore.ts @@ -0,0 +1,8 @@ +import { createContext } from "react"; +import { LoadingState } from "./LoadingSkeletonsTypes"; +export interface LoadingStore extends LoadingState { + setLoading: (val: boolean) => void; +} +export const LoadingSkeletonsContext = createContext( + undefined, +); diff --git a/frontend/features/LoadingSkeletons/LoadingSkeletonsTypes.ts b/frontend/features/LoadingSkeletons/LoadingSkeletonsTypes.ts new file mode 100644 index 00000000..4183a185 --- /dev/null +++ b/frontend/features/LoadingSkeletons/LoadingSkeletonsTypes.ts @@ -0,0 +1,8 @@ +export interface SkeletonConfig { + rowCount: number; + showAvatar?: boolean; +} +export interface LoadingState { + isLoading: boolean; + config: SkeletonConfig; +} diff --git a/frontend/features/LoadingSkeletons/useLoadingSkeletons.ts b/frontend/features/LoadingSkeletons/useLoadingSkeletons.ts new file mode 100644 index 00000000..48f3ae59 --- /dev/null +++ b/frontend/features/LoadingSkeletons/useLoadingSkeletons.ts @@ -0,0 +1,7 @@ +import { useState } from "react"; +import { SkeletonConfig } from "./LoadingSkeletonsTypes"; +export const useLoadingSkeletons = () => { + const [isLoading, setIsLoading] = useState(true); + const [config, setConfig] = useState({ rowCount: 3 }); + return { isLoading, config, setLoading: setIsLoading, setConfig }; +};