diff --git a/src/blog/Blog.tsx b/src/blog/Blog.tsx index 087b2a1..12600ec 100644 --- a/src/blog/Blog.tsx +++ b/src/blog/Blog.tsx @@ -61,7 +61,14 @@ export default function Blog(props: { disableCustomTheme?: boolean }) { {selectedArticle === null ? ( <> - {/* show 3 most recent */} + { + // Optional: fetch more from API (if you want true pagination) + // await fetchMoreArticles(offset, limit); + }} + /> ) : (
diff --git a/src/blog/components/Latest.tsx b/src/blog/components/Latest.tsx index 664ca6e..c4f808e 100644 --- a/src/blog/components/Latest.tsx +++ b/src/blog/components/Latest.tsx @@ -3,10 +3,10 @@ import Avatar from '@mui/material/Avatar'; import AvatarGroup from '@mui/material/AvatarGroup'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; -import Pagination from '@mui/material/Pagination'; import Typography from '@mui/material/Typography'; import { styled } from '@mui/material/styles'; import NavigateNextRoundedIcon from '@mui/icons-material/NavigateNextRounded'; +import CircularProgress from '@mui/material/CircularProgress'; import type { Article } from '../providers/Article'; // โœ… import type for correctness @@ -92,19 +92,58 @@ function Author({ authors }: { authors: { name: string; avatar: string }[] }) { interface LatestProps { articles: Article[]; onSelectArticle?: (index: number) => void; + onLoadMore?: (offset: number, limit: number) => Promise; // optional async callback } -export default function Latest({ articles, onSelectArticle }: LatestProps) { - const [focusedCardIndex, setFocusedCardIndex] = React.useState(null); +export default function Latest({ articles, onSelectArticle, onLoadMore }: LatestProps) { + const [visibleCount, setVisibleCount] = React.useState(2); + const [loadingMore, setLoadingMore] = React.useState(false); - const handleFocus = (index: number) => setFocusedCardIndex(index); - const handleBlur = () => setFocusedCardIndex(null); + const displayedArticles = articles.slice(0, visibleCount); - // limit to 4-6 items for visual balance - const displayedArticles = articles.slice(0, 6); + // Intersection Observer ref + const loaderRef = React.useRef(null); + + React.useEffect(() => { + if (!loaderRef.current) return; + + const observer = new IntersectionObserver( + async (entries) => { + const first = entries[0]; + if (first.isIntersecting && !loadingMore && visibleCount < articles.length) { + console.log('๐ŸŸก Intersection triggered โ€” loading more blogs...'); + setLoadingMore(true); + + if (onLoadMore) { + console.log(`๐Ÿ“ก Calling onLoadMore(offset=${visibleCount}, limit=2)`); + await onLoadMore(visibleCount, 2); + } + + setVisibleCount((prev) => { + const newCount = prev + 2; + console.log(`โœ… Increasing visibleCount from ${prev} โ†’ ${newCount}`); + return newCount; + }); + + setLoadingMore(false); + } + }, + { threshold: 0.5 } + ); + + const current = loaderRef.current; + observer.observe(current); + + console.log('๐Ÿ‘€ IntersectionObserver attached to loaderRef:', loaderRef.current); + + return () => { + if (current) observer.unobserve(current); + console.log('๐Ÿงน IntersectionObserver detached'); + }; + }, [loadingMore, visibleCount, articles.length, onLoadMore]); return ( -
+ Latest @@ -128,17 +167,12 @@ export default function Latest({ articles, onSelectArticle }: LatestProps) { gutterBottom variant="h6" tabIndex={0} - onFocus={() => handleFocus(index)} - onBlur={handleBlur} onClick={() => onSelectArticle?.(index)} - className={focusedCardIndex === index ? 'Mui-focused' : ''} > {article.title} - + + {article.description} @@ -148,9 +182,11 @@ export default function Latest({ articles, onSelectArticle }: LatestProps) { ))} - - + + {/* Infinite scroll loader */} + + {loadingMore && } -
+ ); }