feat: tip component

This commit is contained in:
2026-02-08 09:48:39 +03:30
parent aba27e78fb
commit ec6e121993
3 changed files with 92 additions and 4 deletions
+5 -2
View File
@@ -5,12 +5,13 @@ import { notFound } from "next/navigation";
import { getPostBySlug, getFeaturedImageUrl, getRelatedPosts } from "@/lib/wp-api";
import { formatDateShamsi } from "@/lib/format-date";
import { ChevronRight } from "lucide-react";
import type { CollapseBlockPayload, BlockquoteBlockPayload } from "@/lib/blog-blocks";
import type { CollapseBlockPayload, BlockquoteBlockPayload, TipBlockPayload } from "@/lib/blog-blocks";
import { parseContentHeadings, getReadingTimeMinutes } from "@/lib/blog-utils";
import { BlogToc } from "@/components/blog-toc";
import { BlogPostMeta } from "@/components/blog-post-meta";
import { BlogCollapseSection } from "@/components/blog-collapse-section";
import { BlogBlockquote } from "@/components/blog-blockquote";
import { BlogTip } from "@/components/blog-tip";
import { BlogPullquote } from "@/components/blog-pullquote";
import { ScrollToTop } from "@/components/scroll-to-top";
import { BlogPostCard } from "@/components/blog-post-card";
@@ -88,7 +89,7 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
<div className="lg:grid lg:grid-cols-[16rem_1fr] lg:gap-10">
<BlogToc items={toc} className="mb-6 lg:mb-0" />
<div className="min-w-0 max-w-3xl space-y-0">
<div className="min-w-0 space-y-0">
<div className="pb-8 md:pb-12">
<nav className="mb-6" aria-label="مسیر">
<Link
@@ -130,6 +131,8 @@ export default async function BlogPostPage({ params }: BlogPostPageProps) {
/>
) : seg.blockType === "blockquote" ? (
<BlogBlockquote key={i} payload={seg.payload as BlockquoteBlockPayload} />
) : seg.blockType === "tip" ? (
<BlogTip key={i} payload={seg.payload as TipBlockPayload} />
) : (
null
)
+57
View File
@@ -0,0 +1,57 @@
"use client";
import type { TipBlockPayload } from "@/lib/blog-blocks";
import { Info, AlertTriangle, AlertCircle } from "lucide-react";
import { cn } from "@/lib/utils";
interface BlogTipProps {
payload: TipBlockPayload;
}
const variantConfig = {
info: {
icon: Info,
className:
"border-primary/40 bg-primary/5 text-foreground [&_strong]:text-primary",
iconClassName: "text-primary",
},
warn: {
icon: AlertTriangle,
className:
"border-amber-500/50 bg-amber-500/10 text-foreground [&_strong]:text-amber-600 dark:[&_strong]:text-amber-400",
iconClassName: "text-amber-500",
},
danger: {
icon: AlertCircle,
className:
"border-destructive/50 bg-destructive/10 text-foreground [&_strong]:text-destructive",
iconClassName: "text-destructive",
},
} as const;
export function BlogTip({ payload }: BlogTipProps) {
const { variant, content } = payload;
if (!content.trim()) return null;
const config = variantConfig[variant ?? 'info'];
const Icon = config.icon;
return (
<div
className={cn(
"my-6 flex gap-3 rounded-xl border p-4 pr-4 shadow-sm",
config.className
)}
role="note"
>
<Icon
className={cn("mt-0.5 size-8 shrink-0", config.iconClassName)}
aria-hidden
/>
<div
className="blog-tip-content min-w-0 text-sm leading-relaxed [&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0 [&_a]:text-primary [&_a]:underline hover:[&_a]:no-underline"
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
);
}
+30 -2
View File
@@ -12,7 +12,7 @@ import type { Element } from "domhandler";
// ─── انواع بلاک (برای گسترش بعدی) ───────────────────────────────────────────
/** نام نوع بلاک؛ با افزودن بلاک جدید این یونیون را گسترش دهید. */
export type BlockType = "collapse" | "blockquote" | "pullquote" | "code";
export type BlockType = "collapse" | "blockquote" | "pullquote" | "tip" | "code";
/** Payload بلاک collapse (سوالات متداول / accordion). */
export interface CollapseBlockPayload {
@@ -27,13 +27,21 @@ export interface BlockquoteBlockPayload {
citation?: string;
}
/** Payload بلاک tip (نکته / اینفو با variant: info, warn, danger). */
export interface TipBlockPayload {
variant: "info" | "warn" | "danger";
/** HTML محتوای داخل بلاک (عنوان + متن). */
content: string;
}
/** Payload بلاک code — بعداً پیاده‌سازی می‌شود. */
// export interface CodeBlockPayload { ... }
/** یونیون همهٔ payloadها برای تایپ‌امن رندر. */
export type BlockPayload =
| CollapseBlockPayload
| BlockquoteBlockPayload;
| BlockquoteBlockPayload
| TipBlockPayload;
// | CodeBlockPayload
// ─── سگمنت خروجی برای لایه رندر ─────────────────────────────────────────────
@@ -143,6 +151,20 @@ function extractPullquotePayload(
return { content, citation };
}
/**
* استخراج از بلاک tip وردپرس: div.wp-block-group.tip با کلاس tip-info | tip-warn | tip-danger.
*/
function extractTipPayload($: cheerio.CheerioAPI, el: Element): TipBlockPayload {
const $el = $(el);
const classList = ($el.attr("class") ?? "").split(/\s+/);
let variant: "info" | "warn" | "danger" = "info";
if (classList.includes("tip-danger")) variant = "danger";
else if (classList.includes("tip-warn")) variant = "warn";
else if (classList.includes("tip-info")) variant = "info";
const content = ($el.html() ?? "").trim();
return { variant, content };
}
/**
* ثبت همهٔ بلاک‌های شناخته‌شده.
* برای بلاک‌های جدید (مثلاً blockquote, code) فقط یک مورد جدید به این آرایه اضافه کنید.
@@ -168,6 +190,11 @@ export const BLOCK_DEFINITIONS: BlockDefinition[] = [
selector: "figure.wp-block-pullquote",
extract: extractPullquotePayload as BlockDefinition["extract"],
},
{
blockType: "tip",
selector: "div.wp-block-group.tip",
extract: extractTipPayload as BlockDefinition["extract"],
},
// { blockType: "code", selector: "pre.wp-block-code", extract: extractCodePayload },
];
@@ -215,6 +242,7 @@ export function discoverBlocks(html: string): DiscoveredContent {
collapse: [],
blockquote: [],
pullquote: [],
tip: [],
code: [],
};