Files
kolaytercih/src/components/site-top-banner.tsx
bilalgursen 78d7c51a7a
Some checks failed
Deploy / deploy (push) Has been cancelled
perf: deterministik dekor memo'ları ve küçük render süpürmeleri (Faz 7)
- pixel-decor: PixelField/PixelDivider piksel üretimi useMemo([seed]) —
  504 iterasyon + ~150 element çekmece reorder'ının her karesinde
  yeniden üretiliyordu
- rapor-listesi: acikSira effect'i önceki-prop desenine döndü (çift render
  ve eslint-disable kalktı), Set lazy init, tekIl useMemo
- program-tablosu: 3 dilimin spread'i useMemo
- typing-animation: displayedText state yerine türetme; kaynak reset'i
  render sırasında — tick başına state yazımı ve effect bağımlılığı azaldı
- site-top-banner: saniyelik geri sayım tiki startTransition'da
- program-liste-verileri: sparkline 6 dizi geçişi tek döngüde
- secimlerim-paneli: SecimSatiri profili prop'tan alır + memo (24 ayrı
  store aboneliği kalktı)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 14:29:14 +03:00

144 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { startTransition, 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(() => {
// Saniyelik tik acil değil — layout'ta her sayfada monte; urgent şeritten çık
const guncelle = () =>
startTransition(() => 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>
);
}