58 lines
2.6 KiB
TypeScript
58 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useId, useState, type ReactNode } from "react";
|
|
import { Bookmark, ChevronDown, ChevronRight, PenLine } from "lucide-react";
|
|
|
|
/** หุบ/กางกลุ่มรายการ (Bookmark/Annotation) ในหน้าสรุป — client component ตัวเดียว
|
|
* ของหน้า: เก็บแค่ state หุบ/กางต่อบล็อก เริ่มกางไว้เสมอ (เปิดหน้าสรุปครั้งแรกเห็น
|
|
* ครบ ผู้ใช้พับเฉพาะกลุ่มที่ไม่สนใจ) — รายการ (children) ส่งมาจาก server component
|
|
* ได้ตรง ๆ และเยื้องเข้า (pl-5) ให้หัวข้อกับรายการไม่ชิดเส้นซ้ายเดียวกัน */
|
|
const HEADS = {
|
|
bookmark: { label: "Bookmark", Icon: Bookmark, iconCls: "text-warning-ink" },
|
|
annotation: { label: "Annotation", Icon: PenLine, iconCls: "text-ink-faint" },
|
|
} as const;
|
|
|
|
export default function CollapsibleBlock({
|
|
kind,
|
|
count,
|
|
children,
|
|
}: {
|
|
kind: keyof typeof HEADS;
|
|
count: number;
|
|
children: ReactNode;
|
|
}) {
|
|
const [open, setOpen] = useState(true);
|
|
// design review 2026-09-17 L2: aria-controls ชี้ id ของก้อนเนื้อหา (useId
|
|
// กันชนกันระหว่างหลายหน้าเอกสารที่ render บล็อกนี้ซ้ำ)
|
|
const contentId = useId();
|
|
const { label, Icon, iconCls } = HEADS[kind];
|
|
return (
|
|
<div>
|
|
{/* h3 ครอบ button = disclosure pattern มาตรฐาน (heading level คงเดิม) */}
|
|
<h3>
|
|
<button
|
|
type="button"
|
|
aria-expanded={open}
|
|
aria-controls={contentId}
|
|
onClick={() => setOpen((v) => !v)}
|
|
className="focus-ring flex w-full items-center gap-1.5 rounded-md py-1.5 text-left text-xs font-semibold text-ink-soft hover:text-ink"
|
|
>
|
|
{open ? (
|
|
<ChevronDown aria-hidden className="h-3.5 w-3.5 shrink-0 text-ink-faint" />
|
|
) : (
|
|
<ChevronRight aria-hidden className="h-3.5 w-3.5 shrink-0 text-ink-faint" />
|
|
)}
|
|
<Icon aria-hidden className={`h-3.5 w-3.5 shrink-0 ${iconCls}`} />
|
|
{label}
|
|
<span className="font-normal tabular-nums">({count})</span>
|
|
<span aria-hidden className="ml-1 h-px min-w-4 flex-1 bg-line" />
|
|
</button>
|
|
</h3>
|
|
{open && (
|
|
<div id={contentId} className="pl-5">
|
|
{children}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|