111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
"use client";
|
|
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Card, CardHeader } from "@/components/ui/card";
|
|
import { cn } from "@/lib/utils";
|
|
import { motion } from "motion/react";
|
|
import { ChevronRightIcon } from "lucide-react";
|
|
import Link from "next/link";
|
|
import React from "react";
|
|
|
|
interface ResumeCardProps {
|
|
logoUrl: string;
|
|
altText: string;
|
|
title: string;
|
|
subtitle?: string;
|
|
href?: string;
|
|
badges?: readonly string[];
|
|
period: string;
|
|
description?: string;
|
|
}
|
|
export const ResumeCard = ({
|
|
logoUrl,
|
|
altText,
|
|
title,
|
|
subtitle,
|
|
href,
|
|
badges,
|
|
period,
|
|
description,
|
|
}: ResumeCardProps) => {
|
|
const [isExpanded, setIsExpanded] = React.useState(false);
|
|
|
|
const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
|
if (description) {
|
|
e.preventDefault();
|
|
setIsExpanded(!isExpanded);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Link
|
|
href={href || "#"}
|
|
className="font-normal no-underline block cursor-pointer"
|
|
onClick={handleClick}
|
|
>
|
|
<Card className="flex flex-row items-center p-4 shadow-sm transition-all duration-300 ease-out hover:shadow-lg dark:shadow-[var(--shadow-glow)] dark:hover:shadow-[var(--shadow-glow-hover)]">
|
|
<div className="flex-none">
|
|
<Avatar className="size-12 m-auto bg-muted-background shadow-sm dark:shadow-[var(--shadow-glow)]">
|
|
<AvatarImage
|
|
src={logoUrl}
|
|
alt={altText}
|
|
className="object-contain"
|
|
/>
|
|
<AvatarFallback>{altText[0]}</AvatarFallback>
|
|
</Avatar>
|
|
</div>
|
|
<div className="flex-grow ml-4 items-center flex-col group">
|
|
<CardHeader className="px-0">
|
|
<div className="flex items-center justify-between gap-x-0 text-base">
|
|
<h3 className="inline-flex items-center justify-center font-semibold leading-none text-xs sm:text-sm">
|
|
{title}
|
|
{badges && (
|
|
<span className="inline-flex gap-x-1">
|
|
{badges.map((badge, index) => (
|
|
<Badge
|
|
variant="secondary"
|
|
className="align-middle text-xs"
|
|
key={index}
|
|
>
|
|
{badge}
|
|
</Badge>
|
|
))}
|
|
</span>
|
|
)}
|
|
<ChevronRightIcon
|
|
className={cn(
|
|
"size-4 translate-x-0 transform opacity-0 transition-all duration-300 ease-out group-hover:translate-x-1 group-hover:opacity-100",
|
|
isExpanded ? "rotate-90" : "rotate-0"
|
|
)}
|
|
/>
|
|
</h3>
|
|
<div className="text-xs sm:text-sm tabular-nums text-muted-foreground text-right">
|
|
{period}
|
|
</div>
|
|
</div>
|
|
{subtitle && <div className="font-sans text-xs">{subtitle}</div>}
|
|
</CardHeader>
|
|
{description && (
|
|
<motion.div
|
|
initial={{ opacity: 0, height: 0 }}
|
|
animate={{
|
|
opacity: isExpanded ? 1 : 0,
|
|
|
|
height: isExpanded ? "auto" : 0,
|
|
}}
|
|
transition={{
|
|
duration: 0.7,
|
|
ease: [0.16, 1, 0.3, 1],
|
|
}}
|
|
className="mt-2 text-xs sm:text-sm"
|
|
>
|
|
{description}
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
};
|