import React, { useMemo } from 'react' import type { Color } from '../styles.js' import { useFps, type FpsStats } from '../hooks/use-fps.js' import Box from './Box.js' import Text from './Text.js' type FpsCounterProps = { /** * Show detailed per-phase timing breakdown. * @default false */ detailed?: boolean /** * Rolling window for FPS calculation in milliseconds. * @default 1000 */ sampleWindowMs?: number } function fpsColor(fps: number): Color { if (fps >= 50) return 'ansi:green' if (fps >= 30) return 'ansi:yellow' return 'ansi:red' } function formatMs(ms: number): string { return ms < 1 ? '<1' : ms.toFixed(1) } /** * Displays real-time frame rate and render timing. * * Subscribes to Ink's frame events via `useFps` and shows: * - Current FPS (color-coded: green ≥50, yellow ≥30, red <30) * - Last frame duration * - Optionally: per-phase breakdown (renderer, diff, optimize, write, yoga) * * Usage: * ```tsx * * * ``` */ export function FpsCounter({ detailed = false, sampleWindowMs, }: FpsCounterProps): React.ReactNode { const stats = useFps(sampleWindowMs) if (detailed && stats.phases) { return } return } const CompactView = React.memo(function CompactView({ stats, }: { stats: FpsStats }) { const color = useMemo(() => fpsColor(stats.fps), [stats.fps]) return ( {stats.fps.toFixed(0)} fps {formatMs(stats.lastFrameMs)}ms #{stats.totalFrames} ) }) const DetailedView = React.memo(function DetailedView({ stats, }: { stats: FpsStats }) { const { phases } = stats if (!phases) return const color = useMemo(() => fpsColor(stats.fps), [stats.fps]) return ( {stats.fps.toFixed(0)} fps frame {formatMs(stats.lastFrameMs)}ms #{stats.totalFrames} render: {formatMs(phases.renderer)}ms diff: {formatMs(phases.diff)}ms write: {formatMs(phases.write)}ms yoga: {formatMs(phases.yoga)}ms patches: {phases.patches} ) })