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}
)
})