61 lines
1.7 KiB
TypeScript
61 lines
1.7 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { ChevronDown } from 'lucide-react';
|
|
|
|
interface AccordionItem {
|
|
id: string;
|
|
title: string;
|
|
content: React.ReactNode;
|
|
}
|
|
|
|
interface AccordionProps {
|
|
items: AccordionItem[];
|
|
allowMultiple?: boolean;
|
|
}
|
|
|
|
export default function Accordion({ items, allowMultiple = false }: AccordionProps) {
|
|
const [openIds, setOpenIds] = useState<Set<string>>(new Set());
|
|
|
|
const toggleItem = (id: string) => {
|
|
setOpenIds((prev) => {
|
|
const next = new Set(prev);
|
|
if (next.has(id)) {
|
|
next.delete(id);
|
|
} else {
|
|
if (!allowMultiple) {
|
|
next.clear();
|
|
}
|
|
next.add(id);
|
|
}
|
|
return next;
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-2 w-full">
|
|
{items.map((item) => {
|
|
const isOpen = openIds.has(item.id);
|
|
return (
|
|
<div
|
|
key={item.id}
|
|
className="border border-slate-200 dark:border-slate-800 rounded-lg overflow-hidden"
|
|
>
|
|
<button
|
|
onClick={() => toggleItem(item.id)}
|
|
className="flex items-center justify-between w-full p-4 text-left font-medium transition-colors hover:bg-slate-50 dark:hover:bg-slate-900"
|
|
>
|
|
<span>{item.title}</span>
|
|
<ChevronDown
|
|
className={`w-5 h-5 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
|
/>
|
|
</button>
|
|
{isOpen && (
|
|
<div className="p-4 pt-0 border-t border-slate-100 dark:border-slate-800 text-slate-600 dark:text-slate-400">
|
|
{item.content}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
} |