Add @number-flow/react dependency and integrate SiteTopBanner component into layout
All checks were successful
Deploy / deploy (push) Successful in 11m11s
All checks were successful
Deploy / deploy (push) Successful in 11m11s
This commit is contained in:
@@ -3,6 +3,7 @@ import { Cal_Sans, Geist_Mono, Bricolage_Grotesque } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { SiteHeader } from "@/components/site-header";
|
||||
import { SiteTopBanner } from "@/components/site-top-banner";
|
||||
import { ListeCekmecesi } from "@/components/manuel-liste/liste-cekmecesi";
|
||||
import { HeaderGate } from "@/components/header-gate";
|
||||
import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41";
|
||||
@@ -73,6 +74,7 @@ export default function RootLayout({
|
||||
/>
|
||||
</div>
|
||||
<HeaderGate>
|
||||
<SiteTopBanner />
|
||||
<SiteHeader />
|
||||
</HeaderGate>
|
||||
<ScrollFlow className="flex min-h-0 flex-1 flex-col">
|
||||
|
||||
141
src/components/site-top-banner.tsx
Normal file
141
src/components/site-top-banner.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user