This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { getLocalizedPath, type AppLocale } from "@/lib/locale";
|
||||
import { getLocalizedValue, type PortfolioCategoryView } from "@/lib/portfolio";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type PortfolioCategoryFilterProps = {
|
||||
locale: AppLocale;
|
||||
categories: PortfolioCategoryView[];
|
||||
allLabel: string;
|
||||
activeCategorySlug?: string;
|
||||
};
|
||||
|
||||
export function PortfolioCategoryFilter({
|
||||
locale,
|
||||
categories,
|
||||
allLabel,
|
||||
activeCategorySlug,
|
||||
}: PortfolioCategoryFilterProps) {
|
||||
return (
|
||||
<section className="flex flex-wrap gap-3">
|
||||
<CategoryLink
|
||||
href={getLocalizedPath(locale, "/portfolio")}
|
||||
label={allLabel}
|
||||
active={!activeCategorySlug}
|
||||
/>
|
||||
{categories.map((category) => (
|
||||
<CategoryLink
|
||||
key={category.id}
|
||||
href={getLocalizedPath(locale, `/portfolio/category/${category.slug}`)}
|
||||
label={getLocalizedValue(category.name, locale)}
|
||||
active={activeCategorySlug === category.slug}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function CategoryLink({
|
||||
href,
|
||||
label,
|
||||
active,
|
||||
}: {
|
||||
href: string;
|
||||
label: string;
|
||||
active: boolean;
|
||||
}) {
|
||||
return (
|
||||
<AppCard
|
||||
layer="single"
|
||||
padding="none"
|
||||
className={cn(
|
||||
"overflow-hidden border-border/80",
|
||||
active ? "bg-primary text-primary-foreground" : "bg-surface-2 text-foreground/80",
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
href={href}
|
||||
className={cn(
|
||||
"block rounded-nested px-4 py-2 text-sm transition-colors",
|
||||
active ? "bg-primary text-primary-foreground" : "hover:bg-accent/40 hover:text-accent-foreground",
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
</AppCard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { ArrowUpRight, CalendarDays } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { MotionFade } from "@/components/motion-fade";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import { getLocalizedPath, type AppLocale } from "@/lib/locale";
|
||||
import { getLocalizedValue, type PortfolioProjectView } from "@/lib/portfolio";
|
||||
|
||||
type PortfolioProjectGridProps = {
|
||||
locale: AppLocale;
|
||||
projects: PortfolioProjectView[];
|
||||
emptyLabel: string;
|
||||
openLabel: string;
|
||||
};
|
||||
|
||||
export function PortfolioProjectGrid({
|
||||
locale,
|
||||
projects,
|
||||
emptyLabel,
|
||||
openLabel,
|
||||
}: PortfolioProjectGridProps) {
|
||||
return (
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
{projects.map((item, index) => (
|
||||
<MotionFade key={item.slug} delay={index * 0.05}>
|
||||
<AppCard interactive>
|
||||
<CardContent className="p-5">
|
||||
<Link
|
||||
href={getLocalizedPath(locale, `/portfolio/${item.slug}`)}
|
||||
className="group block"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<p className="text-sm text-muted-foreground/80">
|
||||
{getLocalizedValue(item.category.name, locale)}
|
||||
</p>
|
||||
<p className="inline-flex items-center gap-1 text-xs text-muted-foreground/80">
|
||||
<CalendarDays className="h-3.5 w-3.5" />
|
||||
{item.projectYear}
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="mt-3 text-xl font-semibold text-foreground">
|
||||
{getLocalizedValue(item.title, locale)}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{getLocalizedValue(item.summary, locale)}
|
||||
</p>
|
||||
<p className="mt-4 inline-flex items-center gap-2 text-sm font-medium text-foreground/80 group-hover:text-foreground">
|
||||
{openLabel}
|
||||
<ArrowUpRight className="h-4 w-4" />
|
||||
</p>
|
||||
</Link>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
))}
|
||||
|
||||
{projects.length === 0 ? (
|
||||
<AppCard className="md:col-span-2">
|
||||
<CardContent className="p-6 text-sm text-muted-foreground">
|
||||
{emptyLabel}
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user