Files
soroushasadi/components/blog/BlogArticle.tsx
T
soroush.asadi add78d8460
ci / build (push) Failing after 23s
deploy / deploy (push) Failing after 10m12s
first commit
2026-05-31 12:47:02 +03:30

179 lines
5.6 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.
'use client';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { useLocale } from '@/lib/i18n/locale-context';
import type { PostContent, Block } from '@/lib/content/posts';
import { cn } from '@/lib/utils';
const FA_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const;
const toFa = (s: string | number) =>
s.toString().replace(/\d/g, (d) => FA_DIGITS[Number(d)]);
type Meta = { title: string; category: string; readTime: number };
const ACCENT_TEXT: Record<PostContent['accent'], string> = {
electric: 'text-electric',
violet: 'text-violet',
magenta: 'text-magenta',
emerald: 'text-emerald',
cyan: 'text-cyan',
};
const ACCENT_BORDER: Record<PostContent['accent'], string> = {
electric: 'border-electric/30 bg-electric/5 text-electric',
violet: 'border-violet/30 bg-violet/5 text-violet',
magenta: 'border-magenta/30 bg-magenta/5 text-magenta',
emerald: 'border-emerald/30 bg-emerald/5 text-emerald',
cyan: 'border-cyan/30 bg-cyan/5 text-cyan',
};
export function BlogArticle({
meta,
content,
}: {
meta: { fa: Meta; en: Meta };
content: PostContent;
}) {
const { t, locale } = useLocale();
const m = meta[locale];
const body = content[locale];
const dir = locale === 'fa' ? 'rtl' : 'ltr';
const dateLabel = new Intl.DateTimeFormat(
locale === 'fa' ? 'fa-IR' : 'en-US',
{ year: 'numeric', month: 'long', day: 'numeric' },
).format(new Date(content.date));
return (
<article dir={dir} className="relative px-5 py-32 sm:px-8">
{/* Cover glow */}
<div
aria-hidden
className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-80 bg-radial-aurora opacity-60"
/>
<div className="mx-auto max-w-3xl">
<Link
href="/#blog"
className="label-mono inline-flex items-center gap-2 text-slate-400 transition-colors hover:text-electric"
>
<span className={locale === 'fa' ? 'rotate-180' : ''}></span>
{t.nav.blog}
</Link>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
>
<div className="mt-7 flex flex-wrap items-center gap-3">
<span
className={cn(
'rounded-full border px-2.5 py-0.5 font-mono text-[0.65rem] uppercase tracking-wider',
ACCENT_BORDER[content.accent],
)}
>
{m.category}
</span>
<span className="font-mono text-[0.7rem] text-slate-500">
{dateLabel}
</span>
<span className="font-mono text-[0.7rem] text-slate-500">
{locale === 'fa' ? toFa(m.readTime) : m.readTime}{' '}
{t.blog.readTimeSuffix}
</span>
</div>
<h1
className={cn(
'mt-5 font-display text-[clamp(2rem,4.5vw,3.2rem)] font-extrabold leading-[1.08] tracking-tight text-white',
locale === 'fa' && 'font-fa',
)}
>
{m.title}
</h1>
<p className="mt-6 text-balance text-[clamp(1.05rem,1.8vw,1.3rem)] leading-relaxed text-slate-300">
{body.lead}
</p>
</motion.div>
{/* Body */}
<div className="mt-12 flex flex-col gap-6">
{body.blocks.map((block, i) => (
<BlockView key={i} block={block} accent={content.accent} />
))}
</div>
{/* CTA */}
<div className="mt-16 border-t border-white/5 pt-10">
<p className="text-slate-400">
{locale === 'fa'
? 'این موضوع به سیستم شما مربوط است؟ بیایید درباره‌اش صحبت کنیم.'
: 'Is this relevant to your system? Lets talk it through.'}
</p>
<div className="mt-5 flex flex-wrap gap-3">
<Link href="/#contact" className="btn-primary">
{t.hero.ctaPrimary}
</Link>
<Link href="/#blog" className="btn-ghost">
{t.nav.blog}
</Link>
</div>
</div>
</div>
</article>
);
}
function BlockView({
block,
accent,
}: {
block: Block;
accent: PostContent['accent'];
}) {
switch (block.k) {
case 'h2':
return (
<h2 className="mt-4 font-display text-[clamp(1.3rem,2.4vw,1.7rem)] font-semibold leading-snug text-white">
{block.t}
</h2>
);
case 'p':
return (
<p className="text-[1.02rem] leading-[1.85] text-slate-300">
{block.t}
</p>
);
case 'ul':
return (
<ul className="flex flex-col gap-2.5">
{block.items.map((it, i) => (
<li key={i} className="flex gap-3 text-[1.02rem] leading-relaxed text-slate-300">
<span className={cn('mt-2 h-1.5 w-1.5 shrink-0 rounded-full', `bg-current ${ACCENT_TEXT[accent]}`)} />
<span>{it}</span>
</li>
))}
</ul>
);
case 'quote':
return (
<blockquote
className={cn(
'my-2 border-s-2 ps-5 text-[1.1rem] font-medium italic leading-relaxed text-slate-200',
accent === 'magenta' ? 'border-magenta' : 'border-electric',
)}
>
{block.t}
</blockquote>
);
case 'code':
return (
<pre className="overflow-x-auto rounded-xl border border-white/10 bg-base-900/80 p-4 font-mono text-[0.85rem] leading-relaxed text-slate-200">
<code>{block.t}</code>
</pre>
);
}
}