infinity scrolling init
This commit is contained in:
@@ -61,7 +61,14 @@ export default function Blog(props: { disableCustomTheme?: boolean }) {
|
||||
{selectedArticle === null ? (
|
||||
<>
|
||||
<MainContent articles={articles} onSelectArticle={handleSelectArticle} />
|
||||
<Latest articles={articles.slice(0, 3)} /> {/* show 3 most recent */}
|
||||
<Latest
|
||||
articles={articles}
|
||||
onSelectArticle={handleSelectArticle}
|
||||
onLoadMore={async (offset, limit) => {
|
||||
// Optional: fetch more from API (if you want true pagination)
|
||||
// await fetchMoreArticles(offset, limit);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Article article={articles[selectedArticle]} onBack={handleBack} />
|
||||
|
||||
@@ -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<void>; // optional async callback
|
||||
}
|
||||
|
||||
export default function Latest({ articles, onSelectArticle }: LatestProps) {
|
||||
const [focusedCardIndex, setFocusedCardIndex] = React.useState<number | null>(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<HTMLDivElement | null>(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 (
|
||||
<div>
|
||||
<Box>
|
||||
<Typography variant="h2" gutterBottom>
|
||||
Latest
|
||||
</Typography>
|
||||
@@ -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}
|
||||
<NavigateNextRoundedIcon
|
||||
className="arrow"
|
||||
sx={{ fontSize: '1rem' }}
|
||||
/>
|
||||
<NavigateNextRoundedIcon className="arrow" sx={{ fontSize: '1rem' }} />
|
||||
</TitleTypography>
|
||||
|
||||
<StyledTypography variant="body2" color="text.secondary" gutterBottom>
|
||||
{article.description}
|
||||
</StyledTypography>
|
||||
@@ -148,9 +182,11 @@ export default function Latest({ articles, onSelectArticle }: LatestProps) {
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 4 }}>
|
||||
<Pagination hidePrevButton hideNextButton count={10} boundaryCount={10} />
|
||||
|
||||
{/* Infinite scroll loader */}
|
||||
<Box ref={loaderRef} sx={{ display: 'flex', justifyContent: 'center', py: 3 }}>
|
||||
{loadingMore && <CircularProgress size={28} />}
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user