"use client"; import React from "react"; import { useStore } from "@/store/useStore"; import { Panel } from "./Panel"; import { PanelHeader } from "./PanelHeader"; import { Tag, Search, ChevronDown, ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; import { FALLBACK_LABEL_COLOR, MISSING_LABEL_COLOR, normalizeLabel } from "@/lib/labelColors"; import { useLabelLegend } from "./useLabelLegend"; interface ExplorerPanelProps { className?: string; } export function ExplorerPanel({ className }: ExplorerPanelProps) { const { datasetInfo, embeddingsByLayoutKey, activeLayoutKey, labelFilter, setLabelFilter, } = useStore(); const [labelSearch, setLabelSearch] = React.useState(""); const [isSearchOpen, setIsSearchOpen] = React.useState(false); const [isLabelsExpanded, setIsLabelsExpanded] = React.useState(true); const searchInputRef = React.useRef(null); const resolvedLayoutKey = activeLayoutKey ?? datasetInfo?.layouts?.[0]?.layout_key ?? null; const embeddings = resolvedLayoutKey ? embeddingsByLayoutKey[resolvedLayoutKey] ?? null : null; const { labelCounts, labelUniverse, distinctLabelCount, distinctColoringDisabled, labelColorMap, legendLabels, } = useLabelLegend({ datasetInfo, embeddings, labelSearch, labelFilter }); const hasCounts = labelCounts.size > 0; const baseLabelCount = labelUniverse.length > 0 ? labelUniverse.filter((label) => label !== "undefined").length : distinctLabelCount; const displayCount = labelSearch.trim().length > 0 ? legendLabels.length : baseLabelCount; const activeLabel = labelFilter ? normalizeLabel(labelFilter) : null; // Focus search input when opened React.useEffect(() => { if (isSearchOpen && searchInputRef.current) { searchInputRef.current.focus(); } }, [isSearchOpen]); const handleSearchToggle = () => { setIsSearchOpen(!isSearchOpen); if (isSearchOpen) { setLabelSearch(""); } }; return ( {/* Scrollable content area */}
{/* Labels section */}
{/* Section header - collapsible with search icon */}
{/* Search toggle button */}
{/* Search input - shown when search is toggled */} {isSearchOpen && (
setLabelSearch(e.target.value)} placeholder="Filter labels..." className="w-full h-6 px-2 rounded bg-background border border-border text-[12px] leading-[16px] text-foreground placeholder:text-muted-foreground/50 outline-none focus:ring-1 focus:ring-ring focus:border-ring" />
)} {/* Labels list - collapsible */} {isLabelsExpanded && (
{distinctColoringDisabled && (
Too many labels ({distinctLabelCount}) to color distinctly; using one color.
)} {legendLabels.length === 0 ? (
No labels available
) : (
{legendLabels.map((label) => { const color = label === "undefined" ? MISSING_LABEL_COLOR : labelColorMap[label] ?? FALLBACK_LABEL_COLOR; const normalized = normalizeLabel(label); const isActive = activeLabel === normalized; const isDimmed = activeLabel && !isActive; return ( ); })}
)}
)}
); }