basecafe.jp/src/components/news/NewsCard.tsx

101 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/components/news/NewsCard.tsx
"use client";
import Image from "next/image";
import Link from "next/link";
import dayjs from "dayjs";
import { useMemo } from "react";
import type { NewsItem } from "@/types/news";
const stripTags = (html?: string | null) => (html ? html.replace(/<[^>]+>/g, "") : "");
// HTMLエンティティをデコード
const decodeEntities = (text: string) => {
const textarea = document.createElement("textarea");
textarea.innerHTML = text;
return textarea.value;
};
export default function NewsCard({ item }: { item: NewsItem }) {
const summary = useMemo(() => {
if (!item.content) return "";
const plain = decodeEntities(stripTags(item.content));
return plain.length > 80 ? plain.slice(0, 80) + "…" : plain;
}, [item.content]);
const href = (() => {
const url = item.linkurl?.trim();
// リンクなしnone/空)の場合は詳細ページへ
if (!url || url.toLowerCase() === "none") {
return `/news/${item.id}`;
}
// 外部URLはそのまま
if (/^https?:\/\//i.test(url)) {
return url;
}
// 内部スラッグ(相対指定)の場合
return `/news/${url}`;
})();
const target = item.link_target === "_blank" ? "_blank" : "_self";
const isExternal = href?.startsWith("http");
const imageId = typeof item.thumbnail === "string" ? item.thumbnail : item.thumbnail?.id;
const imageSrc = imageId
? `${process.env.NEXT_PUBLIC_DIRECTUS_ASSETS_URL}/assets/${imageId}?fit=cover`
: "/dummy_thumb.png";
const date = dayjs(item.published_at).format("YYYY.MM.DD");
const cardInner = (
<article className="flex flex-col justify-between h-full bg-stone-50 border border-stone-200 rounded-2xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 hover:-translate-y-1">
<div className="relative w-full aspect-[4/3] bg-stone-100 overflow-hidden">
<Image
src={imageSrc}
alt={stripTags(item.title) || "NEWS"}
fill
className="object-cover transition-transform duration-700 group-hover:scale-105"
/>
</div>
<div className="p-5 flex flex-col flex-1 font-sans">
<p className="text-xs text-stone-500 mb-1">{date}</p>
<h3 className="font-heading text-lg text-stone-800 mb-2 leading-tight line-clamp-2 min-h-[3rem]">
{stripTags(item.title)}
</h3>
{summary && <p className="text-sm text-stone-600 truncate mb-3">{summary}</p>}
<div className="mt-auto pt-2">
{Array.isArray(item.tags) && item.tags.length > 0 && (
<div className="flex flex-wrap gap-2">
{item.tags.map((tag, i) => (
<span
key={i}
className="text-xs bg-stone-200 text-stone-700 px-2 py-1 rounded-full"
>
#{tag}
</span>
))}
</div>
)}
</div>
</div>
</article>
);
if (!href) return cardInner;
if (isExternal)
return (
<a href={href} target={target} rel="noopener noreferrer" className="group block h-full">
{cardInner}
</a>
);
return (
<Link href={href} target={target} className="group block h-full">
{cardInner}
</Link>
);
}