'use client'; import { ChevronDown } from 'lucide-react'; import { useEffect, useMemo, useRef, useState } from 'react'; import type { ReactNode } from 'react'; export interface TreeItem { id: number; label: string; description?: string; children: TreeItem[]; } export function collectTreeItemIds( item: TItem, ): number[] { return [ item.id, ...item.children.flatMap((child) => collectTreeItemIds(child as TItem)), ]; } function TreeCheckbox({ checked, indeterminate, onChange, }: { checked: boolean; indeterminate: boolean; onChange: (checked: boolean) => void; }) { const ref = useRef(null); useEffect(() => { if (ref.current) { ref.current.indeterminate = indeterminate; } }, [indeterminate]); return ( onChange(event.target.checked)} className="size-4 shrink-0 rounded-lg border-border accent-primary" /> ); } function CheckboxTreeNode({ item, selectedIds, onToggle, renderMeta, showDescription = true, }: { item: TItem; selectedIds: Set; onToggle: (item: TItem, checked: boolean) => void; renderMeta?: (item: TItem) => ReactNode; showDescription?: boolean; }) { const [isExpanded, setIsExpanded] = useState(true); const itemIds = collectTreeItemIds(item); const selectedCount = itemIds.filter((id) => selectedIds.has(id)).length; const checked = selectedCount === itemIds.length; const indeterminate = selectedCount > 0 && !checked; const hasChildren = item.children.length > 0; return (
  • {hasChildren ? ( ) : (
    {showDescription && item.description ? (

    {item.description}

    ) : null}
    {hasChildren && isExpanded ? (
      {item.children.map((child) => ( ))}
    ) : null}
  • ); } export function CheckboxTree({ items, selectedIds, onSelectedIdsChange, renderMeta, emptyMessage = 'No items available.', showDescription = true, }: { items: TItem[]; selectedIds: number[]; onSelectedIdsChange: (ids: number[]) => void; renderMeta?: (item: TItem) => ReactNode; emptyMessage?: string; showDescription?: boolean; }) { const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds]); const handleToggle = (item: TItem, checked: boolean) => { const next = new Set(selectedSet); collectTreeItemIds(item).forEach((id) => { if (checked) { next.add(id); } else { next.delete(id); } }); onSelectedIdsChange(Array.from(next)); }; if (items.length === 0) { return (
    {emptyMessage}
    ); } return (
      {items.map((item) => ( ))}
    ); }