Files
supabase/apps/www/components/Wrapped/AnimatedCounter.tsx
Gildas Garcia 86a3f8b03d chore: upgrade to react-19 (#45886)
- Most changes are related to either types or `useRef` usages (it now
requires an initial value).
- also updated `vaul` to its latest version and haven't noticed any
change ([design-system
demo](https://design-system-git-react-19-supabase.vercel.app/design-system/docs/components/drawer))

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Upgraded workspace to React 19.

* **Bug Fixes**
* Improved null-safety and ref handling across editors, UI components,
shortcuts, and markdown/image rendering to reduce runtime errors.
* Safer event/timeout/interval cleanup and more robust command/context
handling.

* **Chores**
  * Bumped vaul dependency versions.

* **Documentation**
* Type and TypeScript accuracy improvements for clearer developer
feedback.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45886)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-15 16:04:41 +02:00

88 lines
2.1 KiB
TypeScript

'use client'
import { useCallback, useEffect, useRef } from 'react'
interface AnimatedCounterProps {
value: number
increment: number
intervalMs?: number
compact?: boolean
}
export function AnimatedCounter({
value,
increment,
intervalMs = 1000,
compact,
}: AnimatedCounterProps) {
const spanRef = useRef<HTMLSpanElement>(null)
const countRef = useRef(value)
const lastUpdateRef = useRef(0)
const isVisibleRef = useRef(false)
const rafIdRef = useRef<number | undefined>(undefined)
const formatNumber = useCallback(
(num: number) => {
if (compact) {
return Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(
num
)
}
return num.toLocaleString()
},
[compact]
)
// Update countRef when value prop changes
useEffect(() => {
countRef.current = value
if (spanRef.current) {
spanRef.current.textContent = formatNumber(value)
}
}, [value, formatNumber])
useEffect(() => {
// Set initial content
if (spanRef.current) {
spanRef.current.textContent = formatNumber(countRef.current)
}
if (increment <= 0) return
const tick = (timestamp: number) => {
if (isVisibleRef.current && timestamp - lastUpdateRef.current >= intervalMs) {
countRef.current += increment
if (spanRef.current) {
spanRef.current.textContent = formatNumber(countRef.current)
}
lastUpdateRef.current = timestamp
}
rafIdRef.current = requestAnimationFrame(tick)
}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
isVisibleRef.current = entry.isIntersecting
})
},
{ threshold: 0 }
)
if (spanRef.current) {
observer.observe(spanRef.current)
}
rafIdRef.current = requestAnimationFrame(tick)
return () => {
if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current)
}
observer.disconnect()
}
}, [increment, intervalMs, formatNumber])
return <span ref={spanRef} />
}