Files
f6b574cd-c07e-4a1d-b87a-11b…/src/components/bento/Bento3DCardGrid.tsx
Nikolay Pecheniev 39b6468dbf Initial commit
2026-02-10 19:04:07 +02:00

123 lines
3.8 KiB
TypeScript

"use client";
import { memo } from "react";
import { cls } from "@/lib/utils";
import type { LucideIcon } from "lucide-react";
import type { InvertedBackground } from "@/providers/themeProvider/config/constants";
export type GridCardItem = {
name: string;
icon: LucideIcon;
};
interface Bento3DCardGridProps {
useInvertedBackground: InvertedBackground;
items: [GridCardItem, GridCardItem, GridCardItem, GridCardItem];
centerIcon: LucideIcon;
className?: string;
}
const gridItemStyle = {
perspective: '1000px',
transformStyle: 'preserve-3d' as const,
};
const EmptyCell = () => (
<div
className="relative aspect-square card shadow rounded-theme-capped opacity-50"
style={gridItemStyle}
/>
);
const cardTranslateZ = [
'group-hover:[transform:translateZ(10px)]',
'group-hover:[transform:translateZ(14px)]',
'group-hover:[transform:translateZ(18px)]',
'group-hover:[transform:translateZ(22px)]',
] as const;
const CardCell = ({ name, Icon, cardIndex }: { name: string; Icon: LucideIcon; cardIndex: number }) => (
<div
className={cls(
"relative card shadow aspect-square rounded-theme-capped flex flex-col justify-between p-3 transition-transform duration-500",
cardTranslateZ[cardIndex]
)}
style={gridItemStyle}
>
<div className="h-6 w-[var(--height-6)] aspect-square rounded-theme primary-button flex items-center justify-center">
<Icon className="h-4/10 w-4/10 text-background" strokeWidth={1.5} />
</div>
<p className="text-xs text-foreground leading-tight line-clamp-4">{name}</p>
</div>
);
const CenterCell = ({ Icon }: { Icon: LucideIcon }) => (
<div
className="aspect-square flex items-center justify-center bg-transparent border-none overflow-visible"
style={gridItemStyle}
>
<div className="card shadow rounded-full h-6/10 aspect-square flex items-center justify-center">
<Icon className="h-4/10 w-4/10 text-foreground" strokeWidth={1.25} />
</div>
</div>
);
const Bento3DCardGrid = ({
useInvertedBackground,
items,
centerIcon: CenterIcon,
className = "",
}: Bento3DCardGridProps) => {
void useInvertedBackground;
const gridPositions = [
{ type: 'empty' },
{ type: 'card', index: 0 },
{ type: 'empty' },
{ type: 'card', index: 1 },
{ type: 'center' },
{ type: 'card', index: 2 },
{ type: 'empty' },
{ type: 'card', index: 3 },
{ type: 'empty' },
] as const;
return (
<div
className={cls("group w-full h-full", className)}
style={{
maskImage: 'linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%), linear-gradient(to bottom, transparent 0%, black 5%, black 95%, transparent 100%)',
maskComposite: 'intersect',
WebkitMaskImage: 'linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%), linear-gradient(to bottom, transparent 0%, black 5%, black 95%, transparent 100%)',
WebkitMaskComposite: 'source-in',
}}
>
<div
className="w-full h-full grid grid-cols-3 gap-4 -translate-y-9 -translate-x-8"
style={{
gridAutoRows: '1fr',
perspective: '5000px',
transformStyle: 'preserve-3d',
transform: 'rotateX(45deg) rotateY(20deg) rotate(-25deg) scale(1.1)',
}}
>
{gridPositions.map((pos, index) => {
switch (pos.type) {
case 'card':
const item = items[pos.index];
return <CardCell key={index} name={item.name} Icon={item.icon} cardIndex={pos.index} />;
case 'center':
return <CenterCell key={index} Icon={CenterIcon} />;
default:
return <EmptyCell key={index} />;
}
})}
</div>
</div>
);
};
Bento3DCardGrid.displayName = "Bento3DCardGrid";
export default memo(Bento3DCardGrid);