File size: 1,887 Bytes
5f24abe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import NumberFlow from "number-flow"
import { continuous } from "number-flow/plugins"
import { createEffect, onCleanup, onMount } from "solid-js"

export function RollingNumber(props: {
  value: number
  target: number
  timing: () => { duration: number; easing: string; spinEasing: string }
}) {
  let root!: HTMLSpanElement
  // Keep the server-rendered text stable while the custom element owns its contents.
  const initial = props.value.toLocaleString()
  const growing = props.target.toLocaleString().length > initial.length

  onMount(() => {
    if (new Intl.NumberFormat().resolvedOptions().numberingSystem !== "latn") {
      createEffect(() => {
        root.textContent = props.value.toLocaleString()
      })
      return
    }

    const flow = new NumberFlow()
    flow.format = { useGrouping: true, maximumFractionDigits: 0 }
    flow.trend = 1
    if (growing) flow.plugins = [continuous]
    flow.opacityTiming = { duration: 180, easing: "ease-out" }
    const motion = window.matchMedia("(prefers-reduced-motion: reduce)")
    const updateMotion = () => {
      flow.animated = !motion.matches
    }
    updateMotion()
    motion.addEventListener("change", updateMotion)
    onCleanup(() => motion.removeEventListener("change", updateMotion))
    root.replaceChildren(flow)
    createEffect(() => {
      const value = props.value
      if (flow.value === value) return
      const timing = props.timing()
      flow.transformTiming = { duration: timing.duration, easing: timing.easing }
      flow.spinTiming = {
        duration: timing.duration,
        easing: growing ? "cubic-bezier(0.45, 0, 0.55, 1)" : timing.spinEasing,
      }
      flow.update(value)
    })
  })

  return (
    <span
      data-value
      ref={root}
      style={{ "min-width": `${props.target.toLocaleString().length}ch`, "text-align": "right" }}
    >
      {initial}
    </span>
  )
}