import { useEffect, useLayoutEffect, useRef, useState, type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref } from "react"; import { X } from "lucide-react"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; export interface SidePanelTabProps { id: string; label: string; ariaLabel?: string; icon?: ReactNode; status?: ReactNode; active: boolean; closable?: boolean; disabled?: boolean; suppressTooltip?: boolean; tabRef?: Ref; dragHandleProps?: ButtonHTMLAttributes; onSelect: () => void; onClose?: () => void; onAuxClick?: (event: MouseEvent) => void; onKeyDown?: ButtonHTMLAttributes["onKeyDown"]; appearance?: "default" | "streamlined-task"; className?: string; } export function SidePanelTab({ id, label, ariaLabel, icon, status, active, closable = true, disabled = false, suppressTooltip = false, tabRef, dragHandleProps, onSelect, onClose, onAuxClick, onKeyDown, appearance = "default", className, }: SidePanelTabProps) { const closeLabel = `Close ${label}`; const wrapperRef = useRef(null); const labelRef = useRef(null); const [labelIsTruncated, setLabelIsTruncated] = useState(false); const [tooltipOpen, setTooltipOpen] = useState(false); const sizingKey = `${appearance}:${label}:${icon ? "icon" : "no-icon"}:${status ? "status" : "no-status"}:${closable ? "closable" : "fixed"}`; const [stableWidth, setStableWidth] = useState<{ key: string; width: number } | null>(null); const hasStableWidth = stableWidth?.key === sizingKey; useLayoutEffect(() => { if (appearance === "streamlined-task") return; if (hasStableWidth) return; const measuredWidth = wrapperRef.current?.getBoundingClientRect().width ?? 0; if (measuredWidth <= 0) return; setStableWidth({ key: sizingKey, width: Math.ceil(measuredWidth) }); }, [hasStableWidth, sizingKey]); useEffect(() => { const labelElement = labelRef.current; if (!labelElement) return; const updateTruncation = () => { setLabelIsTruncated(labelElement.scrollWidth > labelElement.clientWidth); }; updateTruncation(); if (typeof ResizeObserver === "undefined") return; const observer = new ResizeObserver(updateTruncation); observer.observe(labelElement); return () => observer.disconnect(); }, [label]); useEffect(() => { if (suppressTooltip || !labelIsTruncated) setTooltipOpen(false); }, [labelIsTruncated, suppressTooltip]); return (
setTooltipOpen(open && labelIsTruncated && !suppressTooltip)} > {labelIsTruncated && !suppressTooltip ? {label} : null} {closable && onClose && (appearance === "streamlined-task" || active) ? ( ) : null}
); }