crush-level-web/src/app/(main)/home/components/MostChat/index.tsx

149 lines
4.3 KiB
TypeScript

"use client";
import useEmblaCarousel from "embla-carousel-react"
import { useCallback, useEffect, useMemo, useState } from "react"
import Link from "next/link"
import { RankType } from "@/types/global"
import { useHomeData } from "../../context/HomeDataContext"
import MostChatItem from "./MostChatItem"
import MostChatSkeleton from "./MostChatSkeleton"
import { IconButton } from "@/components/ui/button";
import { usePrefetchRoutes } from "@/hooks/useGlobalPrefetchRoutes";
const MostChat = () => {
// 从 Context 获取数据
const { data: homeData, isLoading } = useHomeData()
const displayData = useMemo(
() => homeData?.mostChat?.slice(0, 20) || [],
[homeData?.mostChat]
)
const chatRoutes = useMemo(
() => displayData.slice(0, 12).map((character) => character?.aiId ? `/chat/${character.aiId}` : null),
[displayData]
)
usePrefetchRoutes(chatRoutes)
// 根据屏幕宽度计算每次滑动的卡片数量
const getSlidesToScroll = () => {
if (typeof window === 'undefined') return 3
const width = window.innerWidth
if (width < 640) return 1 // 移动端: 1列
if (width < 1024) return 2 // sm-lg: 2列
if (width < 1280) return 2 // lg-xl: 2列
return 3 // xl+: 3列
}
const [slidesToScroll, setSlidesToScroll] = useState(getSlidesToScroll())
const [emblaRef, emblaApi] = useEmblaCarousel({
align: "start",
containScroll: "trimSnaps",
slidesToScroll: slidesToScroll,
skipSnaps: false
})
const [canScrollPrev, setCanScrollPrev] = useState(false)
const [canScrollNext, setCanScrollNext] = useState(false)
const scrollPrev = useCallback(() => {
if (emblaApi) emblaApi.scrollPrev()
}, [emblaApi])
const scrollNext = useCallback(() => {
if (emblaApi) emblaApi.scrollNext()
}, [emblaApi])
const onSelect = useCallback(() => {
if (!emblaApi) return
setCanScrollPrev(emblaApi.canScrollPrev())
setCanScrollNext(emblaApi.canScrollNext())
}, [emblaApi])
useEffect(() => {
if (!emblaApi) return
onSelect()
emblaApi.on("select", onSelect)
emblaApi.on("reInit", onSelect)
return () => {
emblaApi.off("select", onSelect)
emblaApi.off("reInit", onSelect)
}
}, [emblaApi, onSelect])
// 监听窗口大小变化,动态调整 slidesToScroll
useEffect(() => {
const handleResize = () => {
const newSlidesToScroll = getSlidesToScroll()
if (newSlidesToScroll !== slidesToScroll) {
setSlidesToScroll(newSlidesToScroll)
// 重新初始化 Embla
if (emblaApi) {
emblaApi.reInit()
}
}
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [slidesToScroll, emblaApi])
return (
<div className="mt-12">
{/* 标题栏 */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<span className="text-2xl">👑</span>
<h2 className="txt-headline-s">Most Chatted</h2>
</div>
<Link href={`/leaderboard?type=${RankType.CHAT}`} className="txt-label-m text-primary-variant-normal" prefetch>
More
</Link>
</div>
{/* 轮播容器 */}
<div className="relative">
<div className="overflow-hidden" ref={emblaRef}>
{isLoading ? (
<MostChatSkeleton />
) : (
<div className="flex gap-6">
{displayData.map((character) => (
<MostChatItem
key={character.aiId}
character={character}
/>
))}
</div>
)}
</div>
{/* 左侧箭头按钮 */}
{!isLoading && canScrollPrev && (
<IconButton
onClick={scrollPrev}
variant="tertiary"
size="large"
iconfont="icon-arrow-left-border"
className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 z-10"
/>
)}
{/* 右侧箭头按钮 */}
{!isLoading && canScrollNext && (
<IconButton
onClick={scrollNext}
variant="tertiary"
size="large"
iconfont="icon-arrow-right-border"
className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 z-10"
/>
)}
</div>
</div>
)
}
export default MostChat