feat(blog): refresh post detail layout

This commit is contained in:
2026-06-11 21:21:52 +03:30
parent e89fcfb20e
commit f424225abc
3 changed files with 196 additions and 86 deletions

View File

@@ -12,14 +12,12 @@ type BlogPostActionsProps = {
slug: string;
initialLikes: number;
initialSaves: number;
initialComments: number;
};
export default function BlogPostActions({
slug,
initialLikes,
initialSaves,
initialComments,
}: BlogPostActionsProps) {
const { isAuthenticated } = useAuth();
const [loadingAction, setLoadingAction] = useState<"like" | "save" | null>(null);
@@ -28,7 +26,7 @@ export default function BlogPostActions({
saved: false,
likes_count: initialLikes,
saves_count: initialSaves,
comments_count: initialComments,
comments_count: 0,
});
useEffect(() => {
@@ -67,7 +65,7 @@ export default function BlogPostActions({
};
return (
<div className="flex flex-wrap items-center justify-center gap-3 pt-4" dir="rtl">
<div className="flex flex-wrap items-center justify-center gap-3 border-t border-border/70 pt-6" dir="rtl">
<Button
type="button"
variant="ghost"
@@ -92,10 +90,10 @@ export default function BlogPostActions({
<Button
type="button"
variant="ghost"
size="lg"
size="icon"
onClick={toggleSave}
disabled={!isAuthenticated || Boolean(loadingAction)}
className="gap-2 rounded-full border border-border/60 bg-background/80 px-5 shadow-sm backdrop-blur hover:bg-amber-50 hover:text-amber-600 dark:hover:bg-amber-950/30"
className="rounded-full border border-border/60 bg-background/80 shadow-sm backdrop-blur hover:bg-amber-50 hover:text-amber-600 dark:hover:bg-amber-950/30"
aria-label="ذخیره نوشته"
>
{loadingAction === "save" ? (
@@ -108,7 +106,6 @@ export default function BlogPostActions({
)}
/>
)}
<span>ذخیره</span>
</Button>
{!isAuthenticated ? (
<span className="basis-full text-center text-xs text-muted-foreground">

View File

@@ -0,0 +1,75 @@
"use client";
import { useEffect, useState } from "react";
import type { MarkdownHeading } from "@/lib/markdown-headings";
import { cn } from "@/lib/utils";
type Props = {
headings: MarkdownHeading[];
};
export default function BlogTableOfContents({ headings }: Props) {
const [activeId, setActiveId] = useState(headings[0]?.id ?? "");
useEffect(() => {
if (!headings.length) return;
const observer = new IntersectionObserver(
(entries) => {
const visible = entries
.filter((entry) => entry.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
if (visible?.target.id) {
setActiveId(visible.target.id);
}
},
{
rootMargin: "-20% 0px -65% 0px",
threshold: [0, 1],
},
);
headings.forEach((heading) => {
const element = document.getElementById(heading.id);
if (element) observer.observe(element);
});
return () => observer.disconnect();
}, [headings]);
if (!headings.length) {
return <p className="text-sm leading-7 text-muted-foreground">برای این نوشته فهرست محتوا ثبت نشده است.</p>;
}
const scrollToHeading = (id: string) => {
const element = document.getElementById(id);
if (!element) return;
element.scrollIntoView({ behavior: "smooth", block: "start" });
window.history.replaceState(null, "", `#${id}`);
setActiveId(id);
};
return (
<nav className="space-y-1 text-sm">
{headings.map((heading) => {
const active = activeId === heading.id;
return (
<button
key={heading.id}
type="button"
onClick={() => scrollToHeading(heading.id)}
className={cn(
"block w-full rounded-2xl px-3 py-2 text-right leading-6 transition",
active
? "bg-primary text-primary-foreground shadow-sm"
: "text-muted-foreground hover:bg-primary/10 hover:text-primary",
)}
style={{ paddingRight: `${(heading.level - 1) * 0.85 + 0.75}rem` }}
>
{heading.text}
</button>
);
})}
</nav>
);
}