'use client'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { cn } from '@djangocfg/ui-core/lib'; import { fpsVariants } from './types'; import type { FpsProps } from './types'; export function Fps(props: FpsProps) { const { strategy = 'fixed', position = 'top-right', label, updateInterval = 500, warningThreshold = 30, errorThreshold = 20, portalContainer: portalContainerProp, enabled = true, className, ...fpsProps } = props; const [mounted, setMounted] = React.useState(false); const [fps, setFps] = React.useState(0); const frameCountRef = React.useRef(0); const lastTimeRef = React.useRef(performance.now()); const animationFrameRef = React.useRef(null); const updateTimeoutRef = React.useRef | null>(null); React.useLayoutEffect(() => setMounted(true), []); const status = React.useMemo(() => { if (fps < errorThreshold) return 'error'; if (fps < warningThreshold) return 'warning'; return 'good'; }, [fps, errorThreshold, warningThreshold]); React.useEffect(() => { if (!enabled || typeof window === 'undefined') return; function measureFps() { const now = performance.now(); const delta = now - lastTimeRef.current; frameCountRef.current += 1; if (delta >= updateInterval) { const currentFps = Math.round((frameCountRef.current * 1000) / delta); setFps(currentFps); frameCountRef.current = 0; lastTimeRef.current = now; } animationFrameRef.current = requestAnimationFrame(measureFps); } animationFrameRef.current = requestAnimationFrame(measureFps); return () => { if (animationFrameRef.current !== null) { cancelAnimationFrame(animationFrameRef.current); } if (updateTimeoutRef.current !== null) { clearTimeout(updateTimeoutRef.current); } }; }, [enabled, updateInterval]); if (!enabled) return null; const portalContainer = strategy === 'absolute' ? null : (portalContainerProp ?? (mounted ? globalThis.document?.body : null)); const Comp = ( ); return portalContainer ? ReactDOM.createPortal(Comp, portalContainer) : Comp; } Fps.displayName = 'Fps';