117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
import Link from 'next/link'
|
|
import type { Locale } from '@/i18n/config'
|
|
import type { MediaAssetCategoryCard } from '@/lib/media-assets'
|
|
|
|
export function MediaAssetPageHeader({
|
|
description,
|
|
eyebrow,
|
|
title,
|
|
}: {
|
|
description: string
|
|
eyebrow: string
|
|
title: string
|
|
}) {
|
|
return (
|
|
<header className="max-w-reading">
|
|
<div className="text-sm uppercase tracking-[0.18em] text-text-muted">{eyebrow}</div>
|
|
<h1 className="mt-2 text-4xl font-medium tracking-tight md:text-6xl">{title}</h1>
|
|
<p className="mt-4 text-lg leading-8 text-text-secondary">{description}</p>
|
|
</header>
|
|
)
|
|
}
|
|
|
|
export function MediaAssetBreadcrumbs({
|
|
items,
|
|
}: {
|
|
items: Array<{ href?: string; label: string }>
|
|
}) {
|
|
return (
|
|
<nav aria-label="Breadcrumb" className="mb-6 flex flex-wrap items-center gap-2 text-sm text-text-muted">
|
|
{items.map((item, index) => (
|
|
<span key={`${item.label}-${index}`} className="inline-flex items-center gap-2">
|
|
{index > 0 ? <span aria-hidden="true">/</span> : null}
|
|
{item.href ? (
|
|
<Link href={item.href} className="transition hover:text-text-primary">
|
|
{item.label}
|
|
</Link>
|
|
) : (
|
|
<span className="text-text-primary">{item.label}</span>
|
|
)}
|
|
</span>
|
|
))}
|
|
</nav>
|
|
)
|
|
}
|
|
|
|
export function MediaAssetCategoryGrid({
|
|
categories,
|
|
locale,
|
|
}: {
|
|
categories: MediaAssetCategoryCard[]
|
|
locale: Locale
|
|
}) {
|
|
return (
|
|
<div className="grid gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
{categories.map((category) => {
|
|
const countLabel =
|
|
category.childCount > 0
|
|
? locale === 'zh'
|
|
? `${category.childCount} 个分类`
|
|
: `${category.childCount} categories`
|
|
: locale === 'zh'
|
|
? `${category.assetCount} 个资源`
|
|
: `${category.assetCount} assets`
|
|
|
|
return (
|
|
<article
|
|
key={category.id}
|
|
className="group overflow-hidden bg-white shadow-[0_18px_60px_rgba(18,24,31,0.055)] ring-1 ring-black/[0.05]"
|
|
>
|
|
<Link href={category.href} className="block">
|
|
<div className="relative aspect-[16/10] overflow-hidden bg-[#ecefed]">
|
|
{category.imageUrl ? (
|
|
<img
|
|
src={category.imageUrl}
|
|
alt={category.name}
|
|
className="h-full w-full object-cover transition duration-500 group-hover:scale-[1.035]"
|
|
/>
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center bg-[linear-gradient(135deg,#e4e8e7_0%,#faf9f5_100%)] px-8 text-center text-sm uppercase tracking-[0.18em] text-text-muted">
|
|
{category.name}
|
|
</div>
|
|
)}
|
|
<span className="absolute bottom-4 left-4 bg-white/92 px-3 py-1 text-xs font-medium text-text-secondary shadow-[0_10px_24px_rgba(0,0,0,0.08)]">
|
|
{countLabel}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="p-6">
|
|
<h2 className="text-2xl font-medium tracking-tight text-text-primary transition group-hover:opacity-70">
|
|
{category.name}
|
|
</h2>
|
|
{category.description ? (
|
|
<p className="mt-3 line-clamp-2 text-sm leading-7 text-text-secondary">
|
|
{category.description}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
</Link>
|
|
</article>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function MediaAssetEmptyState({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode
|
|
}) {
|
|
return (
|
|
<div className="mt-8 border border-dashed border-black/15 bg-white/70 px-6 py-10 text-sm leading-7 text-text-secondary md:px-8">
|
|
{children}
|
|
</div>
|
|
)
|
|
}
|