feat: enhance blog post layout and add scroll progress indicator
- Adjusted padding for the blog post thumbnail section. - Introduced a custom hook for tracking scroll progress. - Added horizontal scroll progress indicators for both desktop and mobile views in the blog table of contents.
This commit is contained in:
@@ -68,8 +68,8 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
|
||||
<>
|
||||
{/* تصویر شاخص تمامعرض بالای صفحه */}
|
||||
{thumbnailUrl && (
|
||||
<div className="pt-10 md:pt-24">
|
||||
<div className="relative container mx-auto max-w-6xl rounded-xl overflow-hidden shadow-sm w-full aspect-[5/2] min-h-[10rem] md:min-h-[10rem] bg-muted">
|
||||
<div className="pt-13 md:pt-24">
|
||||
<div className="relative container mx-auto max-w-6xl md:rounded-xl overflow-hidden shadow-sm w-full aspect-[5/2] min-h-[10rem] md:min-h-[10rem] bg-muted">
|
||||
<Image
|
||||
src={thumbnailUrl}
|
||||
alt={altText}
|
||||
|
||||
+55
-6
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { ChevronDown, List } from "lucide-react";
|
||||
import {
|
||||
@@ -12,6 +12,33 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { TocItem } from "@/lib/blog-utils";
|
||||
|
||||
function useScrollProgress() {
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
const update = useCallback(() => {
|
||||
const { scrollY } = window;
|
||||
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||
if (docHeight <= 0) {
|
||||
setProgress(0);
|
||||
return;
|
||||
}
|
||||
const value = Math.min(100, Math.max(0, (scrollY / docHeight) * 100));
|
||||
setProgress(value);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
update();
|
||||
window.addEventListener("scroll", update, { passive: true });
|
||||
window.addEventListener("resize", update);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", update);
|
||||
window.removeEventListener("resize", update);
|
||||
};
|
||||
}, [update]);
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
interface TocGroup {
|
||||
heading: TocItem;
|
||||
children: TocItem[];
|
||||
@@ -54,6 +81,7 @@ export function BlogToc({ items, className }: BlogTocProps) {
|
||||
const [activeId, setActiveId] = useState<string | null>(items[0]?.id ?? null);
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [openSections, setOpenSections] = useState<Set<string>>(() => new Set());
|
||||
const scrollProgress = useScrollProgress();
|
||||
|
||||
const groups = useMemo(() => groupByLevel2(items), [items]);
|
||||
|
||||
@@ -172,15 +200,25 @@ export function BlogToc({ items, className }: BlogTocProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop: sticky sidebar left */}
|
||||
{/* Desktop: sticky sidebar with scroll progress bar */}
|
||||
<aside
|
||||
className={cn(
|
||||
"hidden lg:block shrink-0",
|
||||
"hidden lg:block shrink-0 relative",
|
||||
"sticky top-24 self-start",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{/* Horizontal scroll progress indicator */}
|
||||
<div
|
||||
className="absolute -top-2 left-0 right-0 h-1 rounded-full bg-muted overflow-hidden"
|
||||
aria-hidden
|
||||
>
|
||||
<div
|
||||
className="h-full min-w-[2px] bg-primary transition-[width] duration-150 ease-out rounded-full"
|
||||
style={{ width: `${scrollProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="mb-3 text-md ms-2 font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
فهرست مطالب
|
||||
</p>
|
||||
<ScrollArea className="h-full pb-8" dir="rtl">
|
||||
@@ -188,21 +226,32 @@ export function BlogToc({ items, className }: BlogTocProps) {
|
||||
</ScrollArea>
|
||||
</aside>
|
||||
|
||||
{/* Mobile: collapsible, sticky when scrolling */}
|
||||
{/* Mobile: collapsible with horizontal scroll progress bar */}
|
||||
<Collapsible
|
||||
open={mobileOpen}
|
||||
onOpenChange={setMobileOpen}
|
||||
className={cn(
|
||||
"lg:hidden w-full sticky top-20 z-10 bg-background",
|
||||
"lg:hidden w-full sticky top-14 z-10 bg-background",
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
<CollapsibleTrigger
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-2 rounded-lg border border-border bg-muted/30 px-4 py-3 text-sm font-medium transition-colors hover:bg-muted/50",
|
||||
mobileOpen && "rounded-b-none border-b-0"
|
||||
)}
|
||||
>
|
||||
{/* Horizontal scroll progress indicator (mobile) */}
|
||||
<div
|
||||
className="absolute right-1 top-1 inset-inline-0 h-1 w-[97%] bg-muted overflow-hidden rounded-full"
|
||||
aria-hidden
|
||||
>
|
||||
<div
|
||||
className="h-full bg-primary transition-[width] duration-150 ease-out rounded-full"
|
||||
style={{ width: `${scrollProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="flex items-center gap-2">
|
||||
<List className="size-4 text-muted-foreground" />
|
||||
فهرست مطالب
|
||||
|
||||
Reference in New Issue
Block a user