evijit's picture
evijit HF Staff
Add horizontal scrolling (#12)
cca9c34 verified
import React, { useState, useEffect, useMemo } from "react";
import { ProviderInfo, ModelData, CalendarData } from "../types/heatmap";
import OrganizationButton from "../components/OrganizationButton";
import HeatmapGrid from "../components/HeatmapGrid";
import Navbar from "../components/Navbar";
import TagSelector from "../components/TagSelector";
import { getProviders } from "../utils/ranking";
import { ORGANIZATIONS, SCIENTIFIC_TAGS } from "../constants/organizations";
interface PageProps {
calendarData: CalendarData;
providers: ProviderInfo[];
}
function Page({
calendarData,
providers,
}: PageProps) {
const [isLoading, setIsLoading] = useState(true);
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const [showAllOrgs, setShowAllOrgs] = useState(false);
useEffect(() => {
if (calendarData && Object.keys(calendarData).length > 0) {
setIsLoading(false);
}
}, [calendarData]);
// Filter providers based on selected tags
const filteredProviders = useMemo(() => {
if (selectedTags.length === 0) {
return providers;
}
return providers.filter(provider => {
if (!provider.tags) return false;
return selectedTags.some(tag => provider.tags!.includes(tag));
});
}, [providers, selectedTags]);
const handleTagToggle = (tagId: string) => {
setSelectedTags(prev => {
if (prev.includes(tagId)) {
return prev.filter(t => t !== tagId);
} else {
return [...prev, tagId];
}
});
};
return (
<div className="w-full">
<Navbar />
<div className="w-full p-4 py-16">
<div className="text-center mb-16 max-w-4xl mx-auto">
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-6xl font-bold text-foreground mb-6 bg-gradient-to-r from-foreground to-foreground/80 bg-clip-text">
<span className="inline-flex items-center gap-1 sm:gap-2">
Hugging Science Heatmap
<img
src="/hf-icon.svg"
alt="Hugging Face icon"
className="size-6 sm:size-8 md:size-10"
/>
</span>
</h1>
<p className="text-base sm:text-lg lg:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed px-4">
Open models, datasets, and apps from orgs contributing to AI4Science in the last year.
</p>
</div>
{/* Tag Selector */}
<div className="mb-16">
<TagSelector
tags={SCIENTIFIC_TAGS}
selectedTags={selectedTags}
onTagToggle={handleTagToggle}
/>
</div>
<div className="mb-16 mx-auto max-w-full">
{/* Organization Buttons with Horizontal Scroll */}
<div className="relative px-4">
{/* Scrollable container with visible scrollbar */}
<div
className="overflow-x-auto overflow-y-hidden pb-4 scrollbar-thin scrollbar-thumb-muted-foreground/30 scrollbar-track-transparent hover:scrollbar-thumb-muted-foreground/50"
style={{
scrollbarWidth: 'thin',
scrollbarColor: 'rgba(115, 115, 115, 0.3) transparent'
}}
>
<div className="flex gap-6 py-2 w-max mx-auto">
{(showAllOrgs ? filteredProviders : filteredProviders.slice(0, 10)).map((provider, index) => (
<OrganizationButton
key={provider.fullName || provider.authors[0]}
provider={provider}
calendarData={calendarData}
rank={index + 1}
/>
))}
</div>
</div>
{/* Visual scroll indicators */}
<div className="absolute left-0 top-0 bottom-4 w-8 bg-gradient-to-r from-background via-background/80 to-transparent pointer-events-none" />
<div className="absolute right-0 top-0 bottom-4 w-8 bg-gradient-to-l from-background via-background/80 to-transparent pointer-events-none" />
</div>
{/* Show More/Less Button */}
{filteredProviders.length > 10 && (
<div className="flex justify-center mt-4">
<button
onClick={() => setShowAllOrgs(!showAllOrgs)}
className="px-6 py-2 text-sm font-medium text-foreground bg-muted hover:bg-muted/80 rounded-full transition-colors duration-200 border border-border"
>
{showAllOrgs ? `Show Less (Top 10)` : `Show All ${filteredProviders.length} Organizations`}
</button>
</div>
)}
</div>
<HeatmapGrid
sortedProviders={showAllOrgs ? filteredProviders : filteredProviders.slice(0, 10)}
calendarData={calendarData}
isLoading={isLoading}
/>
</div>
</div>
);
}
export async function getStaticProps() {
try {
const { calendarData, providers } = await getProviders(ORGANIZATIONS);
return {
props: {
calendarData,
providers,
},
revalidate: 3600, // regenerate every hour
};
} catch (error) {
console.error("Error fetching data:", error);
return {
props: {
calendarData: {},
providers: ORGANIZATIONS,
},
revalidate: 60, // retry after 1 minute if there was an error
};
}
}
export default Page;