Files
design-system/src/design-system/composites/LogViewer/LogViewer.tsx
hsiegeln 99ae66315b
All checks were successful
Build & Publish / publish (push) Successful in 50s
feat: add trace log level to LogViewer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:04:50 +01:00

79 lines
2.0 KiB
TypeScript

import { useRef, useEffect, useCallback } from 'react'
import styles from './LogViewer.module.css'
export interface LogEntry {
timestamp: string
level: 'info' | 'warn' | 'error' | 'debug' | 'trace'
message: string
}
export interface LogViewerProps {
entries: LogEntry[]
maxHeight?: number | string
className?: string
}
const LEVEL_CLASS: Record<LogEntry['level'], string> = {
info: styles.levelInfo,
warn: styles.levelWarn,
error: styles.levelError,
debug: styles.levelDebug,
trace: styles.levelTrace,
}
function formatTime(iso: string): string {
try {
return new Date(iso).toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
})
} catch {
return iso
}
}
export function LogViewer({ entries, maxHeight = 400, className }: LogViewerProps) {
const scrollRef = useRef<HTMLDivElement>(null)
const isAtTopRef = useRef(true)
const handleScroll = useCallback(() => {
const el = scrollRef.current
if (!el) return
isAtTopRef.current = el.scrollTop < 20
}, [])
useEffect(() => {
const el = scrollRef.current
if (el && isAtTopRef.current) {
el.scrollTop = 0
}
}, [entries])
const heightStyle = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight
return (
<div
ref={scrollRef}
className={[styles.container, className].filter(Boolean).join(' ')}
style={{ maxHeight: heightStyle }}
onScroll={handleScroll}
role="log"
>
{entries.map((entry, i) => (
<div key={i} className={styles.line}>
<span className={styles.timestamp}>{formatTime(entry.timestamp)}</span>
<span className={[styles.levelBadge, LEVEL_CLASS[entry.level]].join(' ')}>
{entry.level.toUpperCase()}
</span>
<span className={styles.message}>{entry.message}</span>
</div>
))}
{entries.length === 0 && (
<div className={styles.empty}>No log entries.</div>
)}
</div>
)
}