Files
pse.dev/components/project/project-card.tsx
2024-09-19 02:47:06 +02:00

133 lines
4.1 KiB
TypeScript

"use client"
import React from "react"
import Image from "next/image"
import Link from "next/link"
import { VariantProps, cva } from "class-variance-authority"
import { ProjectInterface, ProjectLinkWebsite } from "@/lib/types"
import { cn } from "@/lib/utils"
import useContent from "@/hooks/useContent"
import { useTranslation } from "@/app/i18n/client"
import { LocaleTypes } from "@/app/i18n/settings"
import { Icons } from "../icons"
import { CategoryTag } from "../ui/categoryTag"
import { ThemesButtonMapping } from "./project-filters-bar"
import { ProjectLink } from "./project-link"
interface ProjectCardProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof projectCardVariants> {
project: ProjectInterface
showLinks?: boolean // show links in the card
showBanner?: boolean // show images in the card
}
const TagsIconMapping: Record<string, any> = {
build: <Icons.hammer height={12} width={12} />,
play: <Icons.hand height={12} width={12} />,
research: <Icons.readme height={12} width={12} />,
}
const projectCardVariants = cva(
"flex cursor-pointer flex-col overflow-hidden rounded-lg transition duration-150 ease-in hover:scale-105",
{
variants: {
showLinks: {
true: "min-h-[450px]",
false: "min-h-[200px]",
},
border: {
true: "border border-slate-900/20",
},
},
}
)
export default function ProjectCard({
project,
showLinks = false,
showBanner = false,
border = false,
className,
lang,
}: ProjectCardProps & { lang: LocaleTypes }) {
const { t } = useTranslation(lang, "common")
const { id, image, links, name, tags, imageAlt, projectStatus } = project
const projectNotActive = projectStatus !== "active"
const { projectContent } = useContent({
lang,
id,
})
return (
<Link
href={`/projects/${id}`}
className={cn(projectCardVariants({ showLinks, border, className }))}
>
{showBanner && (
<div className="relative flex flex-col">
<Image
src={`/project-banners/${image ? image : "fallback.webp"}`}
alt={`${name} banner`}
width={1200}
height={630}
className="min-h-[160px] w-full overflow-hidden rounded-t-lg border-none object-cover"
/>
{!image && (
<span className="absolute w-full px-5 text-xl font-bold text-center text-black -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
{imageAlt || name}
</span>
)}
</div>
)}
<div className="flex flex-col justify-between h-full p-4 bg-white rounded-b-lg">
<div className="flex flex-col justify-start gap-2">
<div className="flex gap-2 mb-2">
{tags?.themes?.map((theme, index) => {
const { label } = ThemesButtonMapping(lang)?.[theme]
const icon = TagsIconMapping?.[theme]
return (
<CategoryTag variant="blue" key={index}>
<div className="flex items-center gap-1">
{icon}
<span>{label ?? theme}</span>
</div>
</CategoryTag>
)
})}
</div>
<h1 className="text-xl font-bold text-black">{name}</h1>
<div className="flex flex-col gap-4 h-28">
<p className="text-slate-900/80">{projectContent?.tldr}</p>
</div>
</div>
<div className="flex justify-between mt-auto">
{showLinks && (
<div className="flex items-center justify-start gap-3">
{Object.entries(links ?? {})?.map(([website, url], index) => {
return (
<ProjectLink
key={index}
url={url}
website={website as ProjectLinkWebsite}
/>
)
})}
</div>
)}
{projectNotActive && (
<span className="ml-auto text-sm font-medium italic leading-[21px] text-tuatara-400">
{t("notCurrentlyActive")}
</span>
)}
</div>
</div>
</Link>
)
}