Skip to content

Commit 611ec57

Browse files
committed
added skeleton loaders for our services
1 parent 1e5c7f4 commit 611ec57

3 files changed

Lines changed: 68 additions & 26 deletions

File tree

‎apps/codebility/app/(marketing)/services/_components/tabs/ServicesTab.tsx‎

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type { ServicesProjectsPage } from "@/lib/server/services-projects-cached
1515
import { fetchApiJson } from "@/utils/api-fetch";
1616

1717
import type { ServiceProject } from "../ui/ServicesServiceCard";
18-
import { ServicesServiceCard } from "../ui";
18+
import { ServicesGridSkeleton, ServicesServiceCard, servicesProjectsGridClass } from "../ui";
1919

2020
const pagePromises = new Map<string, Promise<ServicesProjectsPage>>();
2121
const pageMetaCache = new Map<string, ServicesProjectsPage["pagination"]>();
@@ -105,17 +105,17 @@ function loadPage(
105105
return promise;
106106
}
107107

108-
function ServicesGridSkeleton() {
109-
return (
110-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
111-
{Array.from({ length: 4 }).map((_, index) => (
112-
<div
113-
key={index}
114-
className="aspect-square w-full animate-pulse rounded-xl bg-white/5"
115-
/>
116-
))}
117-
</div>
118-
);
108+
function resolveSkeletonCount(
109+
page: number,
110+
pageSize: number,
111+
total: number,
112+
) {
113+
if (total <= 0) {
114+
return pageSize;
115+
}
116+
117+
const remaining = total - (page - 1) * pageSize;
118+
return Math.min(pageSize, Math.max(1, remaining));
119119
}
120120

121121
function ServicesProjectsGrid({
@@ -136,10 +136,7 @@ function ServicesProjectsGrid({
136136
}
137137

138138
return (
139-
<div
140-
key={page}
141-
className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4"
142-
>
139+
<div key={page} className={servicesProjectsGridClass}>
143140
{projects.map((project, index) => (
144141
<div
145142
key={project.id}
@@ -227,13 +224,13 @@ function ServicesPaginationSlot({
227224
onPageChange: (page: number) => void;
228225
}) {
229226
if (totalPages <= 1) {
230-
return null;
227+
return <div className="mt-6 min-h-[4.5rem]" aria-hidden="true" />;
231228
}
232229

233230
const currentPage = Math.min(page, totalPages);
234231

235232
return (
236-
<div id="services-pagination" className="text-white">
233+
<div id="services-pagination" className="mt-6 min-h-[4.5rem] text-white">
237234
<DefaultPagination
238235
currentPage={currentPage}
239236
totalPages={totalPages}
@@ -263,7 +260,7 @@ export const ServicesTab = ({
263260
onServiceSelect,
264261
}: Props) => {
265262
const [page, setPage] = useState(initialData.pagination.page);
266-
const [isPending, startTransition] = useTransition();
263+
const [, startTransition] = useTransition();
267264

268265
rememberPagination(
269266
initialData.category as ServicesCategorySlug,
@@ -279,6 +276,11 @@ export const ServicesTab = ({
279276
initialData,
280277
);
281278
const totalPages = Math.max(1, activePagination.totalPages);
279+
const skeletonCount = resolveSkeletonCount(
280+
page,
281+
pageSize,
282+
activePagination.total,
283+
);
282284

283285
useMarketingPageUrl(page, (nextPage) => {
284286
startTransition(() => {
@@ -319,13 +321,11 @@ export const ServicesTab = ({
319321
})}
320322
</div>
321323

322-
<div
323-
id="services-grid"
324-
className={`transition-opacity duration-200 ${
325-
isPending ? "opacity-60" : "opacity-100"
326-
}`}
327-
>
328-
<Suspense key={`${category}:${page}`} fallback={<ServicesGridSkeleton />}>
324+
<div id="services-grid">
325+
<Suspense
326+
key={`${category}:${page}`}
327+
fallback={<ServicesGridSkeleton count={skeletonCount} />}
328+
>
329329
<ServicesTabGrid
330330
category={category}
331331
page={page}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Skeleton } from "@/components/ui/skeleton/skeleton";
2+
3+
const GRID_CLASS =
4+
"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4";
5+
6+
function ServicesServiceCardSkeleton() {
7+
return (
8+
<div
9+
className="flex h-full flex-col overflow-hidden rounded-xl bg-white/[0.04] ring-1 ring-white/[0.08]"
10+
aria-hidden="true"
11+
>
12+
<div className="relative min-h-0 w-full flex-1">
13+
<Skeleton className="absolute inset-0 rounded-none bg-white/10" />
14+
<div className="absolute left-3 top-3 flex gap-1.5">
15+
<Skeleton className="h-6 w-16 rounded-md bg-white/10" />
16+
<Skeleton className="h-6 w-20 rounded-md bg-white/10" />
17+
</div>
18+
<div className="absolute inset-x-0 bottom-0 border-t border-white/10 bg-gray-950/75 px-4 py-3">
19+
<Skeleton className="h-4 w-24 rounded bg-white/10" />
20+
</div>
21+
</div>
22+
<div className="flex shrink-0 items-center px-3 py-2.5">
23+
<Skeleton className="h-4 w-2/3 rounded bg-white/10" />
24+
</div>
25+
</div>
26+
);
27+
}
28+
29+
export function ServicesGridSkeleton({ count = 12 }: { count?: number }) {
30+
return (
31+
<div className={GRID_CLASS} aria-busy="true" aria-live="polite">
32+
{Array.from({ length: count }, (_, index) => (
33+
<div key={index} className="aspect-square w-full">
34+
<ServicesServiceCardSkeleton />
35+
</div>
36+
))}
37+
</div>
38+
);
39+
}
40+
41+
export { GRID_CLASS as servicesProjectsGridClass };
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./ServicesServiceCard";
2+
export * from "./ServicesGridSkeleton";

0 commit comments

Comments
 (0)