This commit is contained in:
Mackie 2026-06-03 12:43:48 +08:00
parent c9e4a0b004
commit 73abb661c6

View file

@ -23,30 +23,12 @@ type ShowcaseBlockProps = {
items?: ShowcaseItem[]
}
function ShowcaseImage({ item }: { item: ShowcaseItem }) {
const imageUrl = item.image && typeof item.image === 'object' ? item.image.url : null
const imageAlt = item.image && typeof item.image === 'object' ? item.image.alt : item.title
function ShowcaseImage(props: { item: ShowcaseItem }): React.ReactElement {
const item = props.item
const imageUrl = item.image != null && typeof item.image === 'object' ? item.image.url : null
const imageAlt = item.image != null && typeof item.image === 'object' ? item.image.alt : item.title
const inner = imageUrl ? (
<>
<Image src={imageUrl} alt={imageAlt ?? item.title} fill className="object-cover" />
{item.imageUrl && (
<div className="absolute inset-0 bg-background/0 group-hover:bg-background/20 transition-colors duration-200" />
)}
{item.imageUrl && (
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<i className="ti ti-external-link text-foreground/80" style={{ fontSize: 20 }} aria-hidden="true" />
</div>
)}
</>
) : (
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted/50">
<i className="ti ti-photo-off text-foreground/15" style={{ fontSize: 32 }} aria-hidden="true" />
<span className="text-xs text-foreground/20">Coming soon</span>
</div>
)
if (item.imageUrl) {
if (imageUrl != null && item.imageUrl != null) {
return (
href={item.imageUrl}
@ -54,19 +36,34 @@ function ShowcaseImage({ item }: { item: ShowcaseItem }) {
rel="noopener noreferrer"
className="block relative w-full aspect-video overflow-hidden rounded-t-xl group"
>
{inner}
<Image src={imageUrl} alt={imageAlt ?? item.title} fill className="object-cover" />
<div className="absolute inset-0 bg-background/0 group-hover:bg-background/20 transition-colors duration-200" />
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
<i className="ti ti-external-link text-foreground/80" style={{ fontSize: 20 }} aria-hidden="true" />
</div>
</a>
)
}
if (imageUrl != null) {
return (
<div className="relative w-full aspect-video overflow-hidden rounded-t-xl">
<Image src={imageUrl} alt={imageAlt ?? item.title} fill className="object-cover" />
</div>
)
}
return (
<div className="relative w-full aspect-video overflow-hidden rounded-t-xl">
{inner}
<div className="relative w-full aspect-video overflow-hidden rounded-t-xl bg-muted/50 flex flex-col items-center justify-center gap-2">
<i className="ti ti-photo-off text-foreground/15" style={{ fontSize: 32 }} aria-hidden="true" />
<span className="text-xs text-foreground/20">Coming soon</span>
</div>
)
}
export function ShowcaseBlock({ heading, subheading, items }: ShowcaseBlockProps) {
export function ShowcaseBlock(props: ShowcaseBlockProps): React.ReactElement | null {
const { heading, subheading, items } = props
if (!Array.isArray(items) || items.length === 0) return null
return (
@ -85,16 +82,59 @@ export function ShowcaseBlock({ heading, subheading, items }: ShowcaseBlockProps
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{items.map((item, i) => (
<div
key={i}
className="bg-muted/50 border-0 rounded-xl overflow-hidden flex flex-col"
>
<ShowcaseImage item={item} />
{items.map(function (item, i) {
return (
<div
key={i}
className="bg-muted/50 border-0 rounded-xl overflow-hidden flex flex-col"
>
<ShowcaseImage item={item} />
<div className="p-4 flex flex-col gap-3 flex-1">
<h3 className="text-sm font-medium text-foreground/85">{item.title}</h3>
<div className="p-4 flex flex-col gap-3 flex-1">
<h3 className="text-sm font-medium text-foreground/85">{item.title}</h3>
{item.description && (
<p className="text-xs text-foreground/50 leading-relaxed">{item.description}</p>
)}
{item.description && (
<p className="text-xs text-foreground/50 leading-relaxed">{item.description}</p>
)}
{Array.isArray(item.tags) && item.tags.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{item.tags.map(function (t, j) {
return (
<span
key={j}
className="text-xs text-foreground/50 bg-background/50 border border-foreground/10 rounded-full px-2.5 py-0.5"
>
{t.tag}
</span>
)
})}
</div>
)}
{Array.isArray(item.links) && item.links.length > 0 && (
<div className="flex flex-wrap gap-3 mt-auto pt-2 border-t border-foreground/8">
{item.links.map(function (link, k) {
return (
key={k}
href={link.url}
target={link.newTab ? '_blank' : '_self'}
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-xs text-foreground/50 hover:text-foreground/80 transition-colors"
>
<i className="ti ti-external-link" style={{ fontSize: 13 }} aria-hidden="true" />
{link.label}
</a>
)
})}
</div>
)}
</div>
</div>
)
})}
</div>
</section>
)
}