import { type ComponentType, useState, useEffect, useRef } from 'react' import type { SidebarSelection } from '../types' import { cn } from '@/lib/utils' import { getTypeColor, getTypeLightColor } from '../utils/typeColors' import { type IconProps } from '@phosphor-icons/react' export interface SectionGroup { label: string type: string Icon: ComponentType customColor?: string | null } // eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component export function isSelectionActive(current: SidebarSelection, check: SidebarSelection): boolean { if (current.kind !== check.kind) return false switch (check.kind) { case 'filter': return (current as typeof check).filter === check.filter case 'sectionGroup': return (current as typeof check).type === check.type case 'folder': return (current as typeof check).path === check.path case 'entity': return (current as typeof check).entry.path === check.entry.path case 'view': return (current as typeof check).filename === check.filename default: return false } } // --- NavItem --- export function NavItem({ icon: Icon, emoji, label, count, isActive, activeClassName = 'bg-primary/10 text-primary', badgeClassName, badgeStyle, activeBadgeClassName, activeBadgeStyle, onClick, disabled, disabledTooltip, compact }: { icon: ComponentType emoji?: string | null label: string count?: number isActive?: boolean activeClassName?: string badgeClassName?: string badgeStyle?: React.CSSProperties activeBadgeClassName?: string activeBadgeStyle?: React.CSSProperties onClick?: () => void disabled?: boolean disabledTooltip?: string compact?: boolean }) { const iconSize = compact ? 14 : 16 const textClass = compact ? 'text-[12px]' : 'text-[13px]' const padding = compact ? '4px 16px' : '6px 16px' const resolvedBadgeClass = isActive && activeBadgeClassName ? activeBadgeClassName : badgeClassName const resolvedBadgeStyle = isActive && activeBadgeClassName ? activeBadgeStyle : badgeStyle const iconEl = emoji ? {emoji} : if (disabled) { return (
{iconEl} {label}
) } return (
{iconEl} {label} {count !== undefined && count > 0 && ( {count} )}
) } // --- Section Content --- export interface SectionContentProps { group: SectionGroup itemCount: number selection: SidebarSelection onSelect: (sel: SidebarSelection) => void onContextMenu: (e: React.MouseEvent, type: string) => void dragHandleProps?: Record isRenaming?: boolean renameInitialValue?: string onRenameSubmit?: (value: string) => void onRenameCancel?: () => void } export function SectionContent({ group, itemCount, selection, onSelect, onContextMenu, dragHandleProps, isRenaming, renameInitialValue, onRenameSubmit, onRenameCancel, }: SectionContentProps) { const { label, type, Icon, customColor } = group const sectionColor = getTypeColor(type, customColor) const sectionLightColor = getTypeLightColor(type, customColor) return ( onSelect({ kind: 'sectionGroup', type })} onContextMenu={(e) => onContextMenu(e, type)} dragHandleProps={dragHandleProps} isRenaming={isRenaming} renameInitialValue={renameInitialValue} onRenameSubmit={onRenameSubmit} onRenameCancel={onRenameCancel} /> ) } function InlineRenameInput({ initialValue, onSubmit, onCancel }: { initialValue: string onSubmit: (value: string) => void onCancel: () => void }) { const [value, setValue] = useState(initialValue) const inputRef = useRef(null) useEffect(() => { inputRef.current?.focus(); inputRef.current?.select() }, []) const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); onSubmit(value.trim()) } if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); onCancel() } } return ( setValue(e.target.value)} onKeyDown={handleKeyDown} onBlur={() => onSubmit(value.trim())} onClick={(e) => e.stopPropagation()} aria-label="Section name" className="flex-1 rounded border border-primary bg-background text-[13px] font-medium text-foreground outline-none" style={{ padding: '1px 4px' }} /> ) } function SectionHeader({ label, type, Icon, sectionColor, sectionLightColor, itemCount, isActive, onSelect, onContextMenu, dragHandleProps, isRenaming, renameInitialValue, onRenameSubmit, onRenameCancel }: { label: string; type: string; Icon: ComponentType sectionColor: string; sectionLightColor: string; itemCount: number; isActive: boolean onSelect: () => void; onContextMenu: (e: React.MouseEvent) => void dragHandleProps?: Record isRenaming?: boolean; renameInitialValue?: string onRenameSubmit?: (value: string) => void; onRenameCancel?: () => void }) { return (
{ if (!isRenaming) onSelect() }} onContextMenu={isRenaming ? undefined : onContextMenu} >
{isRenaming && onRenameSubmit && onRenameCancel ? ( ) : ( {label} )}
{itemCount > 0 && ( {itemCount} )}
) } // --- Visibility Popover --- export function VisibilityPopover({ sections, isSectionVisible, onToggle }: { sections: SectionGroup[] isSectionVisible: (type: string) => boolean onToggle: (type: string) => void }) { return (
Show in sidebar
{sections.map(({ label, type, Icon }) => ( ))}
) } function ToggleSwitch({ on }: { on: boolean }) { return (
) }