diff --git a/app/(main)/header.tsx b/app/(main)/header.tsx index 692c876..b3440aa 100644 --- a/app/(main)/header.tsx +++ b/app/(main)/header.tsx @@ -1,11 +1,11 @@ "use client" +import AnimateCountClient from "@/components/AnimatedCount" import { LanguageSwitcher } from "@/components/LanguageSwitcher" import { ModeToggle } from "@/components/ThemeSwitcher" import { Separator } from "@/components/ui/separator" import getEnv from "@/lib/env-entry" -import NumberFlow, { NumberFlowGroup } from "@number-flow/react" import { DateTime } from "luxon" import { useTranslations } from "next-intl" import { useRouter } from "next/navigation" @@ -75,24 +75,15 @@ const Overview = memo(function Overview() { return (

{t("p_2277-2331_Overview")}

-
+

{t("p_2390-2457_wherethetimeis")}

- -
- - -

:{time.ss.toString().padStart(2, "0")}

-
-
+
+ + : + + : + {time.ss.toString().padStart(2, "0")} +
) diff --git a/app/globals.css b/app/globals.css deleted file mode 100644 index e69de29..0000000 diff --git a/bun.lockb b/bun.lockb index f406e82..1d581f8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/AnimatedCount.tsx b/components/AnimatedCount.tsx new file mode 100644 index 0000000..6eecf97 --- /dev/null +++ b/components/AnimatedCount.tsx @@ -0,0 +1,96 @@ +import { cn } from "@/lib/utils" +import { useEffect, useState } from "react" + +export default function AnimateCountClient({ + count, + className, + minDigits, +}: { + count: number + className?: string + minDigits?: number +}) { + const [previousCount, setPreviousCount] = useState(count) + + useEffect(() => { + if (count !== previousCount) { + setTimeout(() => { + setPreviousCount(count) + }, 300) + } + }, [count]) + return ( + + {count} + + ) +} + +export function AnimateCount({ + children: count, + className, + preCount, + minDigits = 1, + ...props +}: { + children: number + className?: string + preCount?: number + minDigits?: number +}) { + const currentDigits = count.toString().split("") + const previousDigits = ( + preCount !== undefined ? preCount.toString() : count - 1 >= 0 ? (count - 1).toString() : "0" + ).split("") + + // Ensure both numbers meet the minimum length requirement and maintain the same length for animation + const maxLength = Math.max(previousDigits.length, currentDigits.length, minDigits) + while (previousDigits.length < maxLength) { + previousDigits.unshift("0") + } + while (currentDigits.length < maxLength) { + currentDigits.unshift("0") + } + + return ( +
+ {currentDigits.map((digit, index) => { + const hasChanged = digit !== previousDigits[index] + return ( +
+
+ {previousDigits[index]} +
+
+ {digit} +
+
+ ) + })} +
+ ) +} diff --git a/package.json b/package.json index a772300..befb598 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "dependencies": { "@ducanh2912/next-pwa": "^10.2.9", "@heroicons/react": "^2.2.0", - "@number-flow/react": "^0.5.5", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-label": "^2.1.2", diff --git a/styles/globals.css b/styles/globals.css index c2f63e4..7a16433 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -146,6 +146,7 @@ --chart-8: 252 50% 50%; --chart-9: 288 50% 50%; --chart-10: 324 50% 50%; + --timing: cubic-bezier(0.4, 0, 0.2, 1); } .dark { @@ -334,3 +335,53 @@ filter: blur(0px); } } + +/* Thanks to next.js. */ +[data-issues-count-animation] { + display: flex; + justify-content: center; + align-items: center; +} + +[data-issues-count-animation] > div { + text-align: center; +} + +[data-issues-count-exit].animate { + animation: fadeOut 300ms var(--timing) forwards; +} + +[data-issues-count-enter].animate { + animation: fadeIn 300ms var(--timing) forwards; +} + +[data-issues-count-plural] { + display: inline-block; + animation: fadeIn 300ms var(--timing) forwards; +} + +@keyframes fadeIn { + 0% { + opacity: 0; + filter: blur(2px); + transform: translateY(8px); + } + 100% { + opacity: 1; + filter: blur(0px); + transform: translateY(0); + } +} + +@keyframes fadeOut { + 0% { + opacity: 1; + filter: blur(0px); + transform: translateY(0); + } + 100% { + opacity: 0; + transform: translateY(-12px); + filter: blur(2px); + } +}