gongxiangp2/components/home/MaterialGrid.tsx

156 lines
4.5 KiB
TypeScript

'use client'
import React from 'react'
import MaterialCard, { Material } from './MaterialCard'
// 用户会话类型
interface UserSession {
id: string
role: string
}
interface MaterialGridProps {
materials: Material[]
displayedCount: number
session: { user: UserSession } | null
loading: boolean
isTransitionLoading: boolean
// 音频播放状态
playingAudioId: string | null
audioElement: HTMLAudioElement | null
audioProgress: number
audioCurrentTime: number
audioDuration: number
audioRef: React.RefObject<HTMLAudioElement | null>
// 下载状态
downloadingId: string | null
downloadProgress: number
// 主题色
themeColor: string
// 筛选状态(用于空状态提示)
hasFilters: boolean
// 搜索关键词(用于高亮)
searchKeyword?: string
// 事件处理
onAudioPlay: (materialId: string, audioUrl: string) => void
onDownload: (id: string) => void
onLike: (id: string) => void
onFavorite: (id: string) => void
onShare: (id: string) => void
onEdit: (material: Material) => void
onDelete: (id: string) => void
onMaterialClick: (material: Material) => void
onRetryTranscode: (materialId: string) => void
showToast: (message: string, options?: { variant?: 'success' | 'error' }) => void
}
function MaterialGridSkeleton() {
return (
<div
className="grid grid-cols-1 gap-x-8 gap-y-12 sm:grid-cols-2 lg:grid-cols-3"
aria-busy="true"
aria-live="polite"
aria-label="素材列表加载中"
>
<span className="sr-only">加载中...</span>
{Array.from({ length: 8 }, (_, index) => (
<article
key={index}
className="overflow-hidden"
>
<div className="aspect-[4/3] animate-pulse rounded-[12px] bg-[#eceef0]" />
<div className="space-y-3 px-4 py-4">
<div className="h-4 w-3/4 animate-pulse bg-[#eceef0]" />
<div className="h-3 w-1/2 animate-pulse bg-[#f2f3f5]" />
<div className="flex items-center justify-between pt-1">
<div className="h-3 w-20 animate-pulse bg-[#f2f3f5]" />
<div className="h-3 w-12 animate-pulse bg-[#f2f3f5]" />
</div>
</div>
</article>
))}
</div>
)
}
export default function MaterialGrid({
materials,
displayedCount,
session,
loading,
isTransitionLoading,
playingAudioId,
audioElement,
audioProgress,
audioCurrentTime,
audioDuration,
audioRef,
downloadingId,
downloadProgress,
themeColor,
hasFilters,
searchKeyword,
onAudioPlay,
onDownload,
onLike,
onFavorite,
onShare,
onEdit,
onDelete,
onMaterialClick,
onRetryTranscode,
showToast,
}: MaterialGridProps) {
return (
<div className="relative">
{/* 过渡加载遮罩 */}
{isTransitionLoading && (
<div className="absolute inset-0 z-20 flex items-center justify-center bg-[#fafafa]/70 backdrop-blur-[1px]">
<div className="h-6 w-6 animate-spin rounded-full border-2 border-[#e7e8ea] border-t-[#1a1c1d]" />
</div>
)}
{loading ? (
<MaterialGridSkeleton />
) : materials.length === 0 ? (
<div className="border-t border-[#e7e8ea] py-24 text-center">
<p className="text-[15px] tracking-[-0.02em] text-[#6b7075]">
{hasFilters ? '没有找到匹配的素材' : '还没有素材,快来上传第一个吧!'}
</p>
</div>
) : (
<div className="grid grid-cols-1 gap-x-8 gap-y-12 sm:grid-cols-2 lg:grid-cols-3">
{materials.slice(0, displayedCount).map((material) => (
<MaterialCard
key={material.id}
material={material}
session={session}
playingAudioId={playingAudioId}
audioElement={audioElement}
audioProgress={audioProgress}
audioCurrentTime={audioCurrentTime}
audioDuration={audioDuration}
audioRef={audioRef}
downloadingId={downloadingId}
downloadProgress={downloadProgress}
themeColor={themeColor}
onAudioPlay={onAudioPlay}
onDownload={onDownload}
onLike={onLike}
onFavorite={onFavorite}
onShare={onShare}
onEdit={onEdit}
onDelete={onDelete}
onClick={onMaterialClick}
onRetryTranscode={onRetryTranscode}
showToast={showToast}
/>
))}
</div>
)}
</div>
)
}
export type { Material }