Add @number-flow/react dependency and integrate SiteTopBanner component into layout
All checks were successful
Deploy / deploy (push) Successful in 11m11s

This commit is contained in:
bilalgursen
2026-07-30 12:24:37 +03:00
parent e74710a20a
commit 65d630d754
4 changed files with 172 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
"use client";
import { useEffect, useState } from "react";
import NumberFlow, { NumberFlowGroup } from "@number-flow/react";
import { CalendarDays, Clock3 } from "lucide-react";
const TERCIH_BASLANGICI = new Date("2026-07-29T00:00:00+03:00").getTime();
const TERCIH_BITISI = new Date("2026-08-10T23:59:59+03:00").getTime();
type GeriSayim = {
asama: "baslamadi" | "devam-ediyor" | "sona-erdi";
gun: number;
saat: number;
dakika: number;
saniye: number;
};
function geriSayimiHesapla(simdi: number): GeriSayim {
const asama =
simdi < TERCIH_BASLANGICI
? "baslamadi"
: simdi <= TERCIH_BITISI
? "devam-ediyor"
: "sona-erdi";
const hedef =
asama === "baslamadi" ? TERCIH_BASLANGICI : TERCIH_BITISI;
const kalan = Math.max(0, hedef - simdi);
return {
asama,
gun: Math.floor(kalan / 86_400_000),
saat: Math.floor((kalan % 86_400_000) / 3_600_000),
dakika: Math.floor((kalan % 3_600_000) / 60_000),
saniye: Math.floor((kalan % 60_000) / 1_000),
};
}
const BIRIMLER = [
{ anahtar: "gun", etiket: "gün" },
{ anahtar: "saat", etiket: "sa" },
{ anahtar: "dakika", etiket: "dk" },
{ anahtar: "saniye", etiket: "sn" },
] as const;
export function SiteTopBanner() {
const [geriSayim, setGeriSayim] = useState<GeriSayim | null>(null);
useEffect(() => {
const guncelle = () => setGeriSayim(geriSayimiHesapla(Date.now()));
guncelle();
const zamanlayici = window.setInterval(guncelle, 1_000);
return () => window.clearInterval(zamanlayici);
}, []);
const asamaMetni =
geriSayim?.asama === "baslamadi"
? "Başlamasına"
: geriSayim?.asama === "sona-erdi"
? "Tercih dönemi sona erdi"
: "Sona ermesine";
return (
<section
aria-label="2026 YKS tercih dönemi"
className="relative z-50 border-b border-primary-foreground/15 bg-primary text-primary-foreground print:hidden"
>
<div className="mx-auto flex min-h-12 max-w-6xl flex-wrap items-center justify-center gap-x-5 gap-y-1.5 px-4 py-2 sm:justify-between">
<div className="flex min-w-0 items-center gap-2.5">
<CalendarDays className="size-4 shrink-0 opacity-80" aria-hidden />
<p className="truncate text-sm font-semibold tracking-[-0.01em]">
<span className="hidden sm:inline">2026 YKS tercih dönemi · </span>
<time dateTime="2026-07-29">29 Temmuz</time>
<span aria-hidden> </span>
<time dateTime="2026-08-10">10 Ağustos 2026</time>
</p>
</div>
<div className="flex items-center gap-2.5" role="timer">
<div className="flex items-center gap-1.5 text-xs font-medium opacity-85">
<Clock3 className="size-3.5" aria-hidden />
<span>{asamaMetni}</span>
</div>
{geriSayim?.asama !== "sona-erdi" ? (
<dl
className="flex items-baseline gap-1 font-mono text-xs tabular-nums"
aria-label={
geriSayim
? `${geriSayim.gun} gün, ${geriSayim.saat} saat, ${geriSayim.dakika} dakika, ${geriSayim.saniye} saniye`
: "Geri sayım yükleniyor"
}
>
<NumberFlowGroup>
{BIRIMLER.map(({ anahtar, etiket }) => (
<div className="flex items-baseline gap-0.5" key={anahtar}>
<dt className="sr-only">{etiket}</dt>
<dd className="font-semibold">
{geriSayim ? (
<NumberFlow
value={geriSayim[anahtar]}
trend={-1}
format={{
minimumIntegerDigits: 2,
useGrouping: false,
}}
transformTiming={{
duration: 280,
easing: "cubic-bezier(0.23, 1, 0.32, 1)",
}}
spinTiming={{
duration: 280,
easing: "cubic-bezier(0.23, 1, 0.32, 1)",
}}
opacityTiming={{
duration: 180,
easing: "ease-out",
}}
willChange={anahtar === "saniye"}
className="inline-block min-w-[2ch] text-right"
aria-hidden
/>
) : (
"--"
)}
<span className="ml-0.5 text-[10px] font-medium opacity-70">
{etiket}
</span>
</dd>
</div>
))}
</NumberFlowGroup>
</dl>
) : null}
</div>
</div>
</section>
);
}