// Adapted from jalcoui (MIT) — github.com/jal-co/ui 'use client'; import * as React from 'react'; import { ArrowDownToLine, Circle, Pause, Play, Search, Terminal, Trash2, X, } from 'lucide-react'; import { cn } from '@djangocfg/ui-core/lib'; import type { LogLevel } from '../types'; import { LEVEL_LABELS, getLevelToneClasses } from '../types'; interface ToolbarButtonProps { onClick: () => void; label: string; active?: boolean; children: React.ReactNode; } function ToolbarButton({ onClick, label, active, children }: ToolbarButtonProps) { return ( {children} ); } export interface ToolbarProps { title: string; totalCount: number; filteredCount: number; levels: LogLevel[]; activeLevels: Set; toggleLevel: (level: LogLevel) => void; levelCounts: Partial>; searchQuery: string; setSearchQuery: (q: string) => void; autoScrollEnabled: boolean; paused: boolean; setPaused: (p: boolean) => void; onClear?: () => void; } /** * Header + filter row for the LogViewer. The level chips also serve as * the count badges — clicking strikes them out and removes the level * from the active set. The search input is always visible (no expand / * collapse toggle) because filter and search are the two main jobs of * the toolbar; hiding either is friction. */ export function Toolbar({ title, totalCount, filteredCount, levels, activeLevels, toggleLevel, levelCounts, searchQuery, setSearchQuery, autoScrollEnabled, paused, setPaused, onClear, }: ToolbarProps) { return ( {title} {filteredCount} {filteredCount !== totalCount && ` / ${totalCount}`} lines {autoScrollEnabled && ( setPaused(!paused)} label={paused ? 'Resume auto-scroll' : 'Pause auto-scroll'} active={paused} > {paused ? ( ) : ( )} )} {onClear && ( )} {levels.map((level) => { const tone = getLevelToneClasses(level); const isActive = activeLevels.has(level); const count = levelCounts[level] ?? 0; return ( toggleLevel(level)} role="checkbox" aria-checked={isActive} aria-label={`${isActive ? 'Hide' : 'Show'} ${level} logs`} className={cn( 'inline-flex items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium outline-none transition-colors', 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1', isActive ? tone.badge : 'bg-muted/50 text-muted-foreground/60 line-through', )} > {LEVEL_LABELS[level]} {count > 0 && {count}} ); })} setSearchQuery(e.target.value)} placeholder="Filter…" aria-label="Filter logs" className="w-32 bg-transparent text-xs text-foreground outline-none placeholder:text-muted-foreground sm:w-40" /> {searchQuery && ( setSearchQuery('')} className="inline-flex size-4 items-center justify-center rounded text-muted-foreground hover:text-foreground" aria-label="Clear search" > )} ); } export interface ScrollToBottomPillProps { visible: boolean; onClick: () => void; } export function ScrollToBottomPill({ visible, onClick }: ScrollToBottomPillProps) { if (!visible) return null; return ( New logs below ); }