Files

137 lines
4.6 KiB
TypeScript

'use client';
import type { MenuItem } from '@/domain/menu/entities';
import WishlistButton from '@/components/WishlistButton';
import {
applyNextImageFallback,
getMenuPosterCandidates,
getMenuPosterSrc,
} from '@/lib/assets';
export type SignatureDish = MenuItem & {
desc: string;
};
interface SignatureMenuMarqueeProps {
dishes: SignatureDish[];
addToCartLabel: string;
perHalfKgLabel: string;
perKgLabel: string;
onAdd: (dish: SignatureDish) => void;
}
function renderMarqueePrice(
item: MenuItem,
perHalfKgLabel: string,
perKgLabel: string
) {
if (item.pricing === 'weight') {
return (
<div className="shrink-0 text-right">
<div className="text-[#B38B4D] text-lg font-medium tracking-[-0.5px] tabular-nums leading-tight">
{item.pricePerHalfKg ?? item.price}
</div>
<div className="text-[10px] text-[#8A8478] tracking-wide">{perHalfKgLabel}</div>
<div className="text-[#B38B4D] text-sm font-medium tabular-nums mt-0.5">
{item.pricePerKg} {perKgLabel}
</div>
</div>
);
}
return (
<div className="shrink-0 text-right">
<div className="text-[#B38B4D] text-xl font-medium tracking-[-0.5px] tabular-nums">
{item.price}
</div>
<div className="text-[10px] text-[#8A8478] tracking-widest -mt-1">KR</div>
</div>
);
}
const CARD_WIDTH = 240;
const CARD_GAP = 20;
export default function SignatureMenuMarquee({
dishes,
addToCartLabel,
perHalfKgLabel,
perKgLabel,
onAdd,
}: SignatureMenuMarqueeProps) {
if (dishes.length === 0) return null;
const reelItems = [...dishes, ...dishes];
const trackWidth = reelItems.length * (CARD_WIDTH + CARD_GAP);
const durationSeconds = Math.max(50, dishes.length * 2.8);
return (
<div className="signature-menu-reel relative -mx-6 overflow-hidden lg:-mx-8">
<div
aria-hidden
className="pointer-events-none absolute inset-y-0 left-0 z-10 w-16 bg-gradient-to-r from-[#fffdf8] to-transparent sm:w-24"
/>
<div
aria-hidden
className="pointer-events-none absolute inset-y-0 right-0 z-10 w-16 bg-gradient-to-l from-[#fffdf8] to-transparent sm:w-24"
/>
<div
className="signature-menu-reel-track flex w-max gap-5 py-2 pl-6 lg:pl-8"
style={{
width: `${trackWidth}px`,
animationDuration: `${durationSeconds}s`,
}}
>
{reelItems.map((dish, index) => (
<article
key={`${dish.id}-${index}`}
className="signature-card group flex w-[240px] shrink-0 flex-col overflow-hidden rounded-[1.75rem] border border-[#EDE6D9] bg-white shadow-sm transition-shadow hover:border-[#c99a2e]/45 hover:shadow-md"
>
<div className="relative h-40 overflow-hidden bg-[#F2EDE4]">
<img
src={getMenuPosterSrc(dish)}
alt={dish.name}
className="signature-image h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
loading="lazy"
onError={(e) => {
applyNextImageFallback(
e.target as HTMLImageElement,
getMenuPosterCandidates(dish),
(e.target as HTMLImageElement).src
);
}}
/>
<div className="absolute inset-0 bg-gradient-to-b from-black/5 via-transparent to-black/20" />
<div className="absolute top-3 left-3 z-10">
<WishlistButton
item={{ id: dish.id, name: dish.name, price: dish.price, image: dish.image }}
size="sm"
/>
</div>
</div>
<div className="flex flex-1 flex-col p-4">
<div className="mb-2 flex items-start justify-between gap-3">
<h4 className="min-w-0 line-clamp-2 text-[17px] leading-tight tracking-[-0.3px] text-[#101724] group-hover:text-[#B38B4D]">
{dish.name}
</h4>
{renderMarqueePrice(dish, perHalfKgLabel, perKgLabel)}
</div>
<p className="mb-3 line-clamp-2 flex-1 text-[12px] leading-snug text-[#6B665F]">
{dish.desc}
</p>
<button
type="button"
onClick={() => onAdd(dish)}
className="w-full rounded-full border border-[#B38B4D] py-2.5 text-xs font-medium tracking-wide text-[#B38B4D] transition-all hover:bg-[#B38B4D] hover:text-white active:scale-[0.98]"
>
{addToCartLabel}
</button>
</div>
</article>
))}
</div>
</div>
);
}