ysandler-work/src/components/project-card.tsx

140 lines
4.2 KiB
TypeScript

'use client'
import { Badge } from '@/components/ui/badge'
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import { BadgeListBlock, Media } from '@/payload-types'
import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
import Image from 'next/image'
import Link from 'next/link'
import { RichText } from './RichText'
import clsx from 'clsx'
type Props = {
title: string
href?: string
description?: DefaultTypedEditorState
dates: string
tags?: BadgeListBlock[]
link?: string
image?: Media
video?: Media
links?: BadgeListBlock[]
className?: string
}
export function ProjectCard({
title,
href,
description,
dates,
link,
image,
video,
className,
...rest
}: Props) {
const tags = rest?.tags?.length ? rest.tags[0] : null
const links = rest?.links?.length ? rest.links[0] : null
return (
<Card
className={
'flex flex-col overflow-hidden border hover:shadow-lg transition-all duration-300 ease-out h-full'
}
>
<button
onClick={(e) => {
e.preventDefault()
if (!href) return
window.open(href)
}}
className={cn('block', className, !!href && 'cursor-pointer')}
>
{video && (
<video
src={video.url || ''}
autoPlay
loop
muted
playsInline
className="pointer-events-none mx-auto h-40 w-full object-cover object-top" // needed because random black line at bottom of video
/>
)}
{image && (
<Image
src={image.url || ''}
alt={image.alt || ''}
width={500}
height={300}
className="h-40 w-full overflow-hidden object-cover object-top"
/>
)}
</button>
<CardHeader className="px-2">
<div className="space-y-1">
<CardTitle className="mt-1 text-base">{title}</CardTitle>
<time className="font-sans text-xs">{dates}</time>
<div className="hidden font-sans text-xs underline print:visible">
{link?.replace('https://', '').replace('www.', '').replace('/', '')}
</div>
{!!description && (
<RichText
className="prose max-w-full text-pretty font-sans text-xs text-muted-foreground dark:prose-invert"
data={description}
/>
)}
</div>
</CardHeader>
<CardContent className="mt-auto flex flex-col px-2">
{tags?.badges && tags.badges.length > 0 && (
<div className="mt-2 flex flex-wrap gap-1">
{tags.badges.map((b, i) => (
<Badge
key={title + dates + b + i.toString()}
variant="secondary"
className={clsx(
'px-1 py-0 text-[10px]',
b.color || tags.defaultColor ? `bg-[${b.color || tags.defaultColor}]` : '',
b.textColor || tags.defaultTextColor
? `text-[${b.textColor || tags.defaultTextColor}]`
: '',
)}
>
{b.value}
</Badge>
))}
</div>
)}
</CardContent>
<CardFooter className="px-2 pb-2">
{links?.badges && links.badges.length > 0 && (
<div className="flex flex-row flex-wrap items-start gap-1">
{links.badges?.map((link, idx) => {
const icon = link.icon as Media | undefined
return (
<Link href={link?.href || ''} key={idx} target="_blank">
<Badge key={idx} className="flex gap-2 px-2 py-1 text-[10px]">
{!!icon && icon.url && (
<Image
src={icon.url}
alt={icon.alt || ''}
width={16}
height={16}
className="not-[dark]:invert"
/>
)}
{link.value}
</Badge>
</Link>
)
})}
</div>
)}
</CardFooter>
</Card>
)
}