mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
- 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 --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45886) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
88 lines
2.1 KiB
TypeScript
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} />
|
|
}
|