59 lines
2.7 KiB
TypeScript
59 lines
2.7 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-amber-500" },
|
||
|
|
annotation: { label: "Annotation", Icon: PenLine, iconCls: "text-slate-400" },
|
||
|
|
} 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="flex w-full items-center gap-1.5 rounded-md py-1.5 text-left text-xs font-semibold text-slate-500 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1"
|
||
|
|
>
|
||
|
|
{open ? (
|
||
|
|
<ChevronDown aria-hidden className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||
|
|
) : (
|
||
|
|
<ChevronRight aria-hidden className="h-3.5 w-3.5 shrink-0 text-slate-400" />
|
||
|
|
)}
|
||
|
|
<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-slate-100" />
|
||
|
|
</button>
|
||
|
|
</h3>
|
||
|
|
{open && (
|
||
|
|
<div id={contentId} className="pl-5">
|
||
|
|
{children}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|