From 8cc68474dc0662b8423ca5acc958bbe0e6f8465c Mon Sep 17 00:00:00 2001 From: UdayRajSahai2 Date: Mon, 6 Oct 2025 18:32:15 +0530 Subject: [PATCH 1/3] Shimmer effect using framer motion --- app/[locale]/(user)/components/Datasets.tsx | 8 +- .../(user)/components/ListingComponent.tsx | 12 +- app/[locale]/(user)/components/Sectors.tsx | 9 +- app/[locale]/(user)/components/UseCases.tsx | 10 +- .../publishers/PublishersListingClient.tsx | 5 +- .../(user)/sectors/SectorsListing.tsx | 5 +- .../DatasetListingSkeleton.module.css | 218 ++++++++++++++++++ .../DatasetListingSkeleton.tsx | 142 ++++++++++++ .../PublisherListingSkeleton.module.css | 100 ++++++++ .../PublisherListingSkeleton.tsx | 52 +++++ .../SectorListingSkeleton.module.css | 74 ++++++ .../SectorListingSkeleton.tsx | 40 ++++ .../UseCaseListingSkeleton.module.css | 182 +++++++++++++++ .../UseCaseListingSkeleton.tsx | 135 +++++++++++ components/loading/index.ts | 4 + 15 files changed, 977 insertions(+), 19 deletions(-) create mode 100644 components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.module.css create mode 100644 components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.tsx create mode 100644 components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.module.css create mode 100644 components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.tsx create mode 100644 components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.module.css create mode 100644 components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.tsx create mode 100644 components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.module.css create mode 100644 components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.tsx diff --git a/app/[locale]/(user)/components/Datasets.tsx b/app/[locale]/(user)/components/Datasets.tsx index fd04b611..f815ea3c 100644 --- a/app/[locale]/(user)/components/Datasets.tsx +++ b/app/[locale]/(user)/components/Datasets.tsx @@ -18,6 +18,7 @@ import { import { cn } from '@/lib/utils'; import { Icons } from '@/components/icons'; import Styles from './datasets.module.scss'; +import { DatasetListingSkeleton } from '@/components/loading'; interface Bucket { key: string; @@ -81,9 +82,10 @@ const Datasets = () => { {isLoading ? ( -
- -
+ ) : ( facets && facets.results.map((item: any) => ( diff --git a/app/[locale]/(user)/components/ListingComponent.tsx b/app/[locale]/(user)/components/ListingComponent.tsx index 152a7875..b9b665cf 100644 --- a/app/[locale]/(user)/components/ListingComponent.tsx +++ b/app/[locale]/(user)/components/ListingComponent.tsx @@ -61,6 +61,10 @@ const stripMarkdown = (markdown: string): string => { .replace(/\s+/g, ' ') .trim(); }; +import { + DatasetListingSkeleton, + UseCaseListingSkeleton, +} from '@/components/loading'; // Interfaces interface Bucket { @@ -284,8 +288,12 @@ const ListingComponent: React.FC = ({ useEffect(() => { setHasMounted(true); }, []); - - if (!hasMounted) return ; + if (!hasMounted) { + if (type === 'usecase') { + return ; + } + return ; +} const handlePageChange = (newPage: number) => { setQueryParams({ type: 'SET_CURRENT_PAGE', payload: newPage }); diff --git a/app/[locale]/(user)/components/Sectors.tsx b/app/[locale]/(user)/components/Sectors.tsx index 04ecf203..b82eb4f5 100644 --- a/app/[locale]/(user)/components/Sectors.tsx +++ b/app/[locale]/(user)/components/Sectors.tsx @@ -10,6 +10,7 @@ import { useQuery } from '@tanstack/react-query'; // ✅ Ensure this is correct import { Button, Divider, Spinner, Text } from 'opub-ui'; import { GraphQL } from '@/lib/api'; +import { SectorListingSkeleton } from '@/components/loading'; const sectorDetails = graphql(` query SectorsList { @@ -59,10 +60,10 @@ const Sectors = () => { - {isLoading ? ( -
- -
+ {isLoading ? ( + ) : (
{data?.activeSectors.map((sectors: any) => ( diff --git a/app/[locale]/(user)/components/UseCases.tsx b/app/[locale]/(user)/components/UseCases.tsx index f748671c..bdae9976 100644 --- a/app/[locale]/(user)/components/UseCases.tsx +++ b/app/[locale]/(user)/components/UseCases.tsx @@ -19,6 +19,7 @@ import { GraphQL } from '@/lib/api'; import { cn, formatDate } from '@/lib/utils'; import { Icons } from '@/components/icons'; import Styles from './datasets.module.scss'; +import { UseCaseListingSkeleton } from '@/components/loading'; const useCasesListDoc: any = graphql(` query TopUseCases( @@ -125,10 +126,11 @@ const UseCasesListingPage = () => { - {getUseCasesList.isLoading ? ( -
- -
+ {getUseCasesList.isLoading ? ( + ) : ( {getUseCasesList && diff --git a/app/[locale]/(user)/publishers/PublishersListingClient.tsx b/app/[locale]/(user)/publishers/PublishersListingClient.tsx index 0ce41f92..57fa2ceb 100644 --- a/app/[locale]/(user)/publishers/PublishersListingClient.tsx +++ b/app/[locale]/(user)/publishers/PublishersListingClient.tsx @@ -11,6 +11,7 @@ import { cn, generateJsonLd } from '@/lib/utils'; import BreadCrumbs from '@/components/BreadCrumbs'; import JsonLd from '@/components/JsonLd'; import PublisherCard from './PublisherCard'; +import { PublisherListingSkeleton } from '@/components/loading'; const getAllPublishers: any = graphql(` query PublishersList { @@ -171,9 +172,7 @@ const PublishersListingPage = () => {
{Details.isLoading ? ( -
- -
+ ) : ( Details.data && Details.data.getPublishers.length > 0 && ( diff --git a/app/[locale]/(user)/sectors/SectorsListing.tsx b/app/[locale]/(user)/sectors/SectorsListing.tsx index f7cd9d48..b385f388 100644 --- a/app/[locale]/(user)/sectors/SectorsListing.tsx +++ b/app/[locale]/(user)/sectors/SectorsListing.tsx @@ -18,6 +18,7 @@ import BreadCrumbs from '@/components/BreadCrumbs'; import { ErrorPage } from '@/components/error'; import JsonLd from '@/components/JsonLd'; import Styles from '../datasets/dataset.module.scss'; +import { SectorListingSkeleton } from '@/components/loading'; const sectorsListQueryDoc: any = graphql(` query SectorsLists($order: SectorOrder, $filters: SectorFilter) { @@ -191,9 +192,7 @@ const SectorsListing = () => { {isLoading ? ( -
- -
+ ) : data && data?.activeSectors?.length > 0 ? ( <>
diff --git a/components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.module.css b/components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.module.css new file mode 100644 index 00000000..5b174cb1 --- /dev/null +++ b/components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.module.css @@ -0,0 +1,218 @@ +/* ============================================ + FILE: components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.module.css + ============================================ */ + +.container { + min-height: 100vh; + background-color: var(--base-pure-white, #ffffff); +} + +/* Search Bar */ +.searchBar { + height: 48px; + width: 100%; + background-color: #d1d5db; + border-radius: 8px; +} + +/* Controls */ +.buttonGroup { + height: 40px; + width: 80px; + background-color: #d1d5db; + border-radius: 4px; +} + +.sortButton { + height: 40px; + width: 40px; + background-color: #d1d5db; + border-radius: 4px; +} + +.dropdown { + height: 40px; + width: 128px; + background-color: #d1d5db; + border-radius: 4px; +} + +.filterButton { + height: 40px; + width: 80px; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Filter Sidebar */ +.filterSidebar { + display: flex; + width: 100%; + flex-direction: column; + padding: 1.5rem 1rem; + background-color: var(--surface-default, #ffffff); + border-radius: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.filterHeader { + display: flex; + justify-content: space-between; + margin-bottom: 1.25rem; +} + +.filterTitle { + height: 20px; + width: 64px; + background-color: #d1d5db; + border-radius: 4px; +} + +.resetButton { + height: 20px; + width: 48px; + background-color: #d1d5db; + border-radius: 4px; +} + +.filterList { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.filterItem { + display: flex; + width: 100%; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background-color: #1f5f8d1a; + border-radius: 4px; +} + +.filterLabel { + height: 20px; + width: 120px; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Card - Default Vertical Layout */ +.card { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1.5rem; + background-color: var(--surface-default, #ffffff); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +/* Carousel Cards Container - NEW */ +.carouselContainer { + display: flex; + gap: 1rem; + width: 100%; +} + +/* Carousel Card - Horizontal Layout for Homepage */ +.carouselCard { + display: flex; + flex-direction: column; + gap: 1.25rem; + padding: 2rem; + background-color: var(--surface-default, #ffffff); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + min-width: 480px; + min-height: 320px; + flex: 1; +} + +.cardTitle { + height: 24px; + width: 75%; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Metadata */ +.metadataRow { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; +} + +.metadataItem { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.metadataIcon { + height: 20px; + width: 20px; + background-color: #d1d5db; + border-radius: 50%; +} + +.metadataText { + height: 16px; + width: 80px; + background-color: #d1d5db; + border-radius: 4px; +} + +.metadataTextSmall { + height: 16px; + width: 32px; + background-color: #d1d5db; + border-radius: 4px; +} + +.metadataTextMedium { + height: 16px; + width: 64px; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Description */ +.descriptionLines { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.descriptionLine { + height: 16px; + width: 100%; + background-color: #d1d5db; + border-radius: 4px; +} + +.descriptionLineShort { + height: 16px; + width: 80%; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Footer */ +.cardFooter { + display: flex; + align-items: center; + gap: 0.75rem; + padding-top: 1rem; + margin-top: auto; + border-top: 1px solid var(--border-default, #e5e7eb); +} + +.footerIcon { + height: 32px; + width: 32px; + background-color: #d1d5db; + border-radius: 50%; +} \ No newline at end of file diff --git a/components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.tsx b/components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.tsx new file mode 100644 index 00000000..558b2fc5 --- /dev/null +++ b/components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.tsx @@ -0,0 +1,142 @@ +// ============================================ +// FILE: components/loading/Skeleton/DatasetListingSkeleton/DatasetListingSkeleton.tsx +// COMPLETE UPDATED VERSION WITH CAROUSEL SUPPORT +// ============================================ +import React from 'react'; +import { ShimmerWrapper } from 'opub-ui'; +import styles from './DatasetListingSkeleton.module.css'; + +interface DatasetListingSkeletonProps { + cardCount?: number; + filterCount?: number; + cardsOnly?: boolean; +} + +export function DatasetListingSkeleton({ + cardCount = 9, + filterCount = 12, + cardsOnly = false, +}: DatasetListingSkeletonProps) { + // If cardsOnly is true, return carousel layout for homepage + if (cardsOnly) { + return ( + +
+ {Array.from({ length: cardCount }).map((_, index) => ( + + ))} +
+
+ ); + } + + // Full layout with filters and search for listing page + return ( + +
+
+
+
+ {/* Filter Sidebar */} +
+ +
+ + {/* Main Content */} +
+ {/* Search + Controls */} +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + {/* Cards Grid */} +
+ {Array.from({ length: cardCount }).map((_, index) => ( + + ))} +
+
+
+
+
+
+ + ); +} + +function FilterSidebarSkeleton({ filterCount }: { filterCount: number }) { + return ( +
+ {/* Header */} +
+
+
+
+ + {/* Filter Items */} +
+ {Array.from({ length: filterCount }).map((_, index) => ( +
+
+
+ ))} +
+
+ ); +} + +function DatasetCardSkeleton({ carousel = false }: { carousel?: boolean }) { + return ( +
+ {/* Title */} +
+ + {/* Metadata Row */} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + {/* Description Lines */} +
+
+
+
+
+ + {/* Footer */} +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.module.css b/components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.module.css new file mode 100644 index 00000000..9213ae43 --- /dev/null +++ b/components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.module.css @@ -0,0 +1,100 @@ +/* ============================================ + FILE: components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.module.css + CARDS ONLY - Simplified version + ============================================ */ + +/* Cards Grid */ +.cardsGrid { + display: grid; + grid-template-columns: 1fr; + gap: 2.5rem; + margin-top: 2.5rem; +} + +@media (min-width: 768px) { + .cardsGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .cardsGrid { + grid-template-columns: repeat(3, 1fr); + gap: 4rem; + } +} + +/* Publisher Card */ +.card { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1.5rem; + border-radius: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + background-color: var(--surface-default, #ffffff); +} + +.cardHeader { + display: flex; + align-items: center; + gap: 1rem; +} + +.logo { + height: 80px; + width: 80px; + flex-shrink: 0; + background-color: #d1d5db; + border-radius: 8px; + border: 2px solid #e5e7eb; +} + +.cardHeaderText { + display: flex; + flex-direction: column; + gap: 0.5rem; + flex: 1; +} + +.publisherName { + height: 20px; + width: 140px; + background-color: #d1d5db; + border-radius: 4px; +} + +.typeBadge { + height: 28px; + width: 120px; + background-color: #e9eff4; + border-radius: 9999px; +} + +/* Stats Row */ +.statsRow { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.statPill { + height: 32px; + width: 100px; + background-color: #d1d5db; + border-radius: 9999px; +} + +/* Description */ +.description { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.descriptionLine { + height: 16px; + width: 100%; + background-color: #d1d5db; + border-radius: 4px; +} \ No newline at end of file diff --git a/components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.tsx b/components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.tsx new file mode 100644 index 00000000..50bac4df --- /dev/null +++ b/components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.tsx @@ -0,0 +1,52 @@ +// ============================================ +// FILE: components/loading/Skeleton/PublisherListingSkeleton/PublisherListingSkeleton.tsx +// CARDS ONLY - No header, no buttons +// ============================================ +import React from 'react'; +import { ShimmerWrapper } from 'opub-ui'; +import styles from './PublisherListingSkeleton.module.css'; + +interface PublisherListingSkeletonProps { + cardCount?: number; +} + +export function PublisherListingSkeleton({ + cardCount = 6, +}: PublisherListingSkeletonProps) { + return ( + +
+ {Array.from({ length: cardCount }).map((_, index) => ( + + ))} +
+
+ ); +} + +function PublisherCardSkeleton() { + return ( +
+ {/* Header: Logo + Name + Badge */} +
+
+
+
+
+
+
+ + {/* Stats Pills */} +
+
+
+
+ + {/* Description */} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.module.css b/components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.module.css new file mode 100644 index 00000000..3824da3b --- /dev/null +++ b/components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.module.css @@ -0,0 +1,74 @@ +/* ============================================ + FILE: components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.module.css + CARDS ONLY - Sector cards shimmer effect + ============================================ */ + +/* Cards Grid */ +.cardsGrid { + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; + margin-top: 2rem; +} + +@media (min-width: 768px) { + .cardsGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .cardsGrid { + grid-template-columns: repeat(3, 1fr); + } +} + +/* Sector Card */ +.card { + display: flex; + padding: 2rem 1.5rem; + border-radius: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + background-color: var(--surface-default, #ffffff); + transition: transform 0.2s ease-in-out; +} + +.cardContent { + display: flex; + align-items: center; + gap: 1.5rem; + width: 100%; +} + +/* Icon */ +.icon { + height: 80px; + width: 80px; + flex-shrink: 0; + background-color: #d1d5db; + border-radius: 12px; +} + +/* Text Section */ +.textSection { + display: flex; + flex-direction: column; + gap: 0.75rem; + flex: 1; +} + +/* Title */ +.title { + height: 24px; + width: 70%; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Dataset Count */ +.datasetCount { + height: 18px; + width: 40%; + background-color: #d1d5db; + border-radius: 4px; +} \ No newline at end of file diff --git a/components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.tsx b/components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.tsx new file mode 100644 index 00000000..b93c47dd --- /dev/null +++ b/components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.tsx @@ -0,0 +1,40 @@ +// ============================================ +// FILE: components/loading/Skeleton/SectorListingSkeleton/SectorListingSkeleton.tsx +// CARDS ONLY - No header, no breadcrumbs +// ============================================ +import React from 'react'; +import { ShimmerWrapper } from 'opub-ui'; +import styles from './SectorListingSkeleton.module.css'; + +interface SectorListingSkeletonProps { + cardCount?: number; +} + +export function SectorListingSkeleton({ + cardCount = 9, +}: SectorListingSkeletonProps) { + return ( + +
+ {Array.from({ length: cardCount }).map((_, index) => ( + + ))} +
+
+ ); +} + +function SectorCardSkeleton() { + return ( +
+ {/* Icon and Title section */} +
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.module.css b/components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.module.css new file mode 100644 index 00000000..6e0945d8 --- /dev/null +++ b/components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.module.css @@ -0,0 +1,182 @@ +/* ============================================ + FILE: components/loading/Skeleton/UseCaseListingSkeleton/UseCaseListingSkeleton.module.css + ============================================ */ + +.container { + min-height: 100vh; + background-color: var(--base-pure-white, #ffffff); +} + +/* Search Bar */ +.searchBar { + height: 48px; + width: 100%; + background-color: #d1d5db; + border-radius: 8px; +} + +/* Controls */ +.buttonGroup { + height: 40px; + width: 80px; + background-color: #d1d5db; + border-radius: 4px; +} + +.sortButton { + height: 40px; + width: 40px; + background-color: #d1d5db; + border-radius: 4px; +} + +.dropdown { + height: 40px; + width: 128px; + background-color: #d1d5db; + border-radius: 4px; +} + +.filterButton { + height: 40px; + width: 80px; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Filter Sidebar */ +.filterSidebar { + display: flex; + width: 100%; + flex-direction: column; + padding: 1.5rem 1rem; + background-color: var(--surface-default, #ffffff); + border-radius: 16px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.filterHeader { + display: flex; + justify-content: space-between; + margin-bottom: 1.25rem; +} + +.filterTitle { + height: 20px; + width: 64px; + background-color: #d1d5db; + border-radius: 4px; +} + +.resetButton { + height: 20px; + width: 48px; + background-color: #d1d5db; + border-radius: 4px; +} + +.filterList { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.filterItem { + display: flex; + width: 100%; + align-items: center; + gap: 0.5rem; + padding: 0.75rem; + background-color: #1f5f8d1a; + border-radius: 4px; +} + +.filterLabel { + height: 20px; + width: 120px; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Card - WITH IMAGE */ +.card { + display: flex; + flex-direction: column; + overflow: hidden; + background-color: var(--surface-default, #ffffff); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +/* Cover Image - THIS IS THE KEY DIFFERENCE */ +.coverImage { + height: 192px; + width: 100%; + background-color: #d1d5db; +} + +.cardContent { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1.5rem; +} + +.cardTitle { + height: 20px; + width: 66%; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Metadata */ +.metadataRow { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; +} + +.metadataItem { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.metadataIcon { + height: 20px; + width: 20px; + background-color: #d1d5db; + border-radius: 50%; +} + +.metadataText { + height: 16px; + width: 80px; + background-color: #d1d5db; + border-radius: 4px; +} + +.metadataTextLarge { + height: 16px; + width: 96px; + background-color: #d1d5db; + border-radius: 4px; +} + +/* Footer */ +.cardFooter { + display: flex; + align-items: center; + gap: 0.75rem; + padding-top: 1rem; + margin-top: auto; + border-top: 1px solid var(--border-default, #e5e7eb); +} + +.footerIcon { + height: 32px; + width: 32px; + background-color: #d1d5db; + border-radius: 50%; +} \ No newline at end of file diff --git a/components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.tsx b/components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.tsx new file mode 100644 index 00000000..ab4c3516 --- /dev/null +++ b/components/loading/Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton.tsx @@ -0,0 +1,135 @@ +// ============================================ +// FILE: components/loading/Skeleton/UseCaseListingSkeleton/UseCaseListingSkeleton.tsx +// UPDATED: Added cardsOnly prop for carousel use +// ============================================ +import React from 'react'; +import { ShimmerWrapper } from 'opub-ui'; +import styles from './UseCaseListingSkeleton.module.css'; + +interface UseCaseListingSkeletonProps { + cardCount?: number; + filterCount?: number; + /** + * If true, only render cards without grid wrapper (for use in carousels) + */ + cardsOnly?: boolean; +} + +export function UseCaseListingSkeleton({ + cardCount = 9, + filterCount = 12, + cardsOnly = false, +}: UseCaseListingSkeletonProps) { + // If cardsOnly mode, just return the cards without any wrapper + if (cardsOnly) { + return ( + <> + {Array.from({ length: cardCount }).map((_, index) => ( + + ))} + + ); + } + + // Full page layout with grid + return ( + +
+
+
+
+ {/* Filter Sidebar */} +
+ +
+ + {/* Main Content */} +
+ {/* Search + Controls */} +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + {/* Cards Grid */} +
+ {Array.from({ length: cardCount }).map((_, index) => ( + + ))} +
+
+
+
+
+
+ + ); +} + +function FilterSidebarSkeleton({ filterCount }: { filterCount: number }) { + return ( +
+
+
+
+
+ +
+ {Array.from({ length: filterCount }).map((_, index) => ( +
+
+
+ ))} +
+
+ ); +} + +function UseCaseCardSkeleton() { + return ( +
+ {/* Cover Image */} +
+ + {/* Content */} +
+ {/* Title */} +
+ + {/* Metadata Row */} +
+
+
+
+
+
+
+
+
+
+ + {/* Footer */} +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/loading/index.ts b/components/loading/index.ts index 4b29f247..8600a9cd 100644 --- a/components/loading/index.ts +++ b/components/loading/index.ts @@ -1 +1,5 @@ export { Loading } from './loading'; +export { DatasetListingSkeleton } from './Skeleton/DatasetListingSkeleton/DatasetListingSkeleton'; +export { UseCaseListingSkeleton } from './Skeleton/UsecaseListingSkeleton/UseCaseListingSkeleton'; +export { PublisherListingSkeleton } from './Skeleton/PublisherListingSkeleton/PublisherListingSkeleton'; +export { SectorListingSkeleton } from './Skeleton/SectorListingSkeleton/SectorListingSkeleton'; From 72f48e003ed70a7d5456d6f28b4a5c571fc0564f Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Tue, 25 Nov 2025 14:04:38 +0530 Subject: [PATCH 2/3] Build Fixws --- package-lock.json | 66 +++++++++++++++++++++++++++++++++++------------ package.json | 2 +- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index f8cafbef..f4d1c920 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "next-auth": "^4.24.7", "next-intl": "^3.4.0", "next-usequerystate": "^1.17.2", - "opub-ui": "^0.4.24", + "opub-ui": "^0.4.25", "react": "^19.2.0", "react-aria": "3.22.0", "react-dom": "^19.2.0", @@ -13923,6 +13923,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -14869,6 +14870,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "12.23.24", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", + "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -18748,6 +18776,21 @@ "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT" }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -19840,9 +19883,9 @@ } }, "node_modules/opub-ui": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/opub-ui/-/opub-ui-0.4.24.tgz", - "integrity": "sha512-IH4Qggo2g+fw73sy544q3Rq3Jq1laYdHtH4WilM3crvbbYJyrdOZz5Tlvh0GPQ9eVNjYgWvAabjEANzfMSGIzw==", + "version": "0.4.25", + "resolved": "https://registry.npmjs.org/opub-ui/-/opub-ui-0.4.25.tgz", + "integrity": "sha512-BJPOMsfwe6NypHMl5kihf69ZHsqNsCUbLzvYA5Y+20Z8zkUwjgKQHLGBmKPHVfKbiohUoT4u8sWGPditnWvwgQ==", "license": "MIT", "dependencies": { "@ariakit/react": "^0.4.18", @@ -19869,7 +19912,7 @@ "@react-types/calendar": "^3.2.0", "@react-types/datepicker": "^3.3.0", "@react-types/shared": "^3.18.0", - "@tabler/icons-react": "^2.17.0", + "@tabler/icons-react": "^2.47.0", "@tanstack/react-table": "^8.9.1", "autoprefixer": "^10.4.18", "chrono-node": "^2.7.5", @@ -19881,6 +19924,7 @@ "echarts-for-react": "^3.0.2", "embla-carousel-react": "8.0.0-rc15", "esbuild": "^0.24.0", + "framer-motion": "^12.23.22", "intl-segmenter-polyfill": "^0.4.4", "leaflet": "^1.9.4", "leaflet.fullscreen": "^4.0.0", @@ -19947,18 +19991,6 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, - "node_modules/opub-ui/node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, "node_modules/opub-ui/node_modules/cmdk": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.1.tgz", diff --git a/package.json b/package.json index 566db746..d94bf6f8 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "next-auth": "^4.24.7", "next-intl": "^3.4.0", "next-usequerystate": "^1.17.2", - "opub-ui": "^0.4.24", + "opub-ui": "^0.4.25", "react": "^19.2.0", "react-aria": "3.22.0", "react-dom": "^19.2.0", From 0930ff82c030af0c9678660238c442ba490508c6 Mon Sep 17 00:00:00 2001 From: Abhishekfm Date: Tue, 25 Nov 2025 14:10:20 +0530 Subject: [PATCH 3/3] package lock Sync --- package-lock.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index f4d1c920..f71e3a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13923,7 +13923,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -19991,6 +19990,18 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/opub-ui/node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, "node_modules/opub-ui/node_modules/cmdk": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.1.tgz",