141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import useEmblaCarousel from "embla-carousel-react"
|
||
|
|
import { useCallback, useEffect, 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";
|
||
|
|
|
||
|
|
const MostChat = () => {
|
||
|
|
// 从 Context 获取数据
|
||
|
|
const { data: homeData, isLoading } = useHomeData()
|
||
|
|
|
||
|
|
// 只取前20条数据
|
||
|
|
const displayData = homeData?.mostChat?.slice(0, 20) || []
|
||
|
|
|
||
|
|
// 根据屏幕宽度计算每次滑动的卡片数量
|
||
|
|
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">
|
||
|
|
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
|