feat(ui): Arc bileşenleri, landing bölümleri ve bildirim yığını
Sonner/vaul yerine kendi bildirim yığını ve alt sayfa; çip grubu, kart destesi, kaydırma eylemi, yazı dönüşümü, bölme tutamacı; landing'e program duvarı, süzülen haplar ve veri akışı; yaylar tek dosyada (lib/hareket.ts). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -4,13 +4,14 @@ import { useLayoutEffect, useRef, type ReactNode } from "react";
|
||||
import { animate, useReducedMotion } from "motion/react";
|
||||
import { ChevronRight, GraduationCap, X } from "lucide-react";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer";
|
||||
AltSayfa,
|
||||
AltSayfaKapat,
|
||||
AltSayfaGovde,
|
||||
AltSayfaIcerik,
|
||||
AltSayfaAciklama,
|
||||
AltSayfaBaslik,
|
||||
AltSayfaTetik,
|
||||
} from "@/components/ui/alt-sayfa";
|
||||
|
||||
/**
|
||||
* Üniversite ve bölüm sayfasında özet kartlarının altındaki ayrıntı satırı
|
||||
@@ -38,8 +39,8 @@ export function AkademikAyrinti({
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Drawer handleOnly>
|
||||
<DrawerTrigger asChild>
|
||||
<AltSayfa>
|
||||
<AltSayfaTetik asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 flex w-full cursor-pointer items-center gap-4 group rounded-2xl border border-slate-200 bg-card p-4 text-left transition-[background-color,border-color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:p-5"
|
||||
@@ -62,23 +63,23 @@ export function AkademikAyrinti({
|
||||
aria-hidden
|
||||
/>
|
||||
</button>
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="max-h-[92dvh] bg-card sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl">
|
||||
<DrawerClose className="absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]">
|
||||
</AltSayfaTetik>
|
||||
<AltSayfaIcerik>
|
||||
<AltSayfaKapat className="absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]">
|
||||
<X className="size-4" aria-hidden />
|
||||
<span className="sr-only">Kapat</span>
|
||||
</DrawerClose>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 pt-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
|
||||
<DrawerTitle className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||
</AltSayfaKapat>
|
||||
<AltSayfaGovde className="px-4 pt-3 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
|
||||
<AltSayfaBaslik className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||
{baslik}
|
||||
</DrawerTitle>
|
||||
<DrawerDescription className="mt-1 px-10 text-center text-sm text-slate-500">
|
||||
</AltSayfaBaslik>
|
||||
<AltSayfaAciklama className="mt-1 px-10 text-center text-sm text-slate-500">
|
||||
{baglam}
|
||||
</DrawerDescription>
|
||||
</AltSayfaAciklama>
|
||||
<AcilisSahnesi>{children}</AcilisSahnesi>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</AltSayfaGovde>
|
||||
</AltSayfaIcerik>
|
||||
</AltSayfa>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Armchair,
|
||||
Briefcase,
|
||||
Calculator,
|
||||
ChartLine,
|
||||
ChevronRight,
|
||||
GraduationCap,
|
||||
Landmark,
|
||||
@@ -35,11 +34,7 @@ import { VeriKaynagi } from "./veri-kaynagi";
|
||||
import { NavBaslik } from "./nav-baslik";
|
||||
import { AkademikAyrinti } from "./akademik-ayrinti";
|
||||
import { OzetKart, kartSayisi } from "@/components/ozet-kart";
|
||||
import {
|
||||
KartBaglantisi,
|
||||
kisaUniAdi,
|
||||
ortaTabanAciklamasi,
|
||||
} from "./taban-karti";
|
||||
import { KartBaglantisi, kisaUniAdi } from "./taban-karti";
|
||||
import { trBaslikDuzeni } from "@/lib/slug";
|
||||
import {
|
||||
BOLUM_ISTIHDAM_KAYNAK_URL,
|
||||
@@ -106,7 +101,7 @@ function MezunIstihdami({
|
||||
const istihdamKaresi =
|
||||
veri.istihdam != null ? (
|
||||
<div data-kare="sol" className={`${kare} min-h-40 bg-orange-500 py-4`}>
|
||||
<dt className={etiket}>Kayıtlı çalışan</dt>
|
||||
<dt className={etiket}>İşe yerleşen</dt>
|
||||
<dd className={`mt-3 ${devSayi}`}>%{ondalik(veri.istihdam)}</dd>
|
||||
{ort.istihdam != null ? (
|
||||
<dd className="mt-2 text-base font-bold text-white/90">
|
||||
@@ -165,7 +160,7 @@ function MezunIstihdami({
|
||||
>
|
||||
Türkiye geneli, tüm üniversitelerin{" "}
|
||||
{duzey === "lisans" ? "lisans" : "ön lisans"} mezunları birlikte;
|
||||
üniversiteye göre değişir. Kayıtlı çalışan, sigortalı bir işte
|
||||
üniversiteye göre değişir. İşe yerleşen, sigortalı bir işte
|
||||
çalışanların oranıdır. Kazanç grubu bölümler arası kıyastır, maaş
|
||||
değildir.
|
||||
</p>
|
||||
@@ -409,10 +404,11 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
...(bolum.onlisansProgramlari.length > 0 ? ["Önlisans"] : []),
|
||||
];
|
||||
// Özet kartları (Bilal, 6 Eki 2026: "kaç program var" sayıları ilgi
|
||||
// çekmiyor): öğrencinin sorusuna cevap veren dört sayı — en seçici
|
||||
// program, devlette en geniş kapı, orta tabanın yıllar içindeki seyri ve
|
||||
// boş kalan koltuk. Hesap lib/taban-ozeti.ts; program/devlet/vakıf sayıları
|
||||
// özet paragrafta kalır. Üniversite sayfasıyla aynı düzen (DESIGN.md §4.8).
|
||||
// çekmiyor): öğrencinin sorusuna cevap veren üç sayı — en yüksek taban,
|
||||
// devlette en düşük taban ve boş kalan kontenjan. Orta taban kartı
|
||||
// kaldırıldı (Bilal, 6 Eki 2026). Hesap lib/taban-ozeti.ts; program/
|
||||
// devlet/vakıf sayıları özet paragrafta kalır. Üniversite sayfasıyla aynı
|
||||
// kartlar (DESIGN.md §4.8).
|
||||
const { taban } = bolum;
|
||||
const uniBaglantisi = (universite: string) => (
|
||||
<KartBaglantisi
|
||||
@@ -554,13 +550,14 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
|
||||
</div>
|
||||
|
||||
{/* Sayfada en çok dört özet sayı (DESIGN.md §1.1, §4.8). Derin
|
||||
sayfalarda tekrar edilmez — üniversite sayfasıyla aynı. */}
|
||||
{/* Sayfada en çok dört özet sayı (DESIGN.md §1.1, §4.8); burada üç.
|
||||
Telefonda iki sütunda tek kalan üçüncü kart satırı boylu boyunca
|
||||
alır. Derin sayfalarda tekrar edilmez — üniversite sayfasıyla aynı. */}
|
||||
{ilkSayfa ? (
|
||||
<dl className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
<dl className="mt-8 grid grid-cols-2 gap-4 lg:grid-cols-3 [&>*:last-child]:col-span-2 lg:[&>*:last-child]:col-span-1">
|
||||
<OzetKart
|
||||
ikon={Target}
|
||||
etiket="En seçici"
|
||||
etiket="En yüksek taban"
|
||||
{...(taban.enSecici
|
||||
? kartSayisi(taban.enSecici.sira)
|
||||
: { deger: "—" })}
|
||||
@@ -575,11 +572,11 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
)
|
||||
}
|
||||
/>
|
||||
{/* Etiket kısa kalır ("Devlette en geniş" 131 px'te iki satıra
|
||||
{/* Etiket kısa kalır ("Devlette en düşük" 131 px'te iki satıra
|
||||
kırılıyor, 6 Eki 2026 ölçümü); küme açıklamanın başında. */}
|
||||
<OzetKart
|
||||
ikon={Landmark}
|
||||
etiket="En geniş kapı"
|
||||
etiket="En düşük taban"
|
||||
{...(taban.enGenis
|
||||
? kartSayisi(taban.enGenis.sira)
|
||||
: { deger: "—" })}
|
||||
@@ -595,18 +592,9 @@ export function BolumIcerik({ slug, sayfa }: { slug: string; sayfa: number }) {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<OzetKart
|
||||
ikon={ChartLine}
|
||||
etiket="Orta taban"
|
||||
{...(() => {
|
||||
const son = [...taban.ortaTaban].reverse().find((v) => v != null);
|
||||
return son != null ? kartSayisi(son) : { deger: "—" };
|
||||
})()}
|
||||
alt={ortaTabanAciklamasi(taban)}
|
||||
/>
|
||||
<OzetKart
|
||||
ikon={Armchair}
|
||||
etiket="Boş koltuk"
|
||||
etiket="Boş kontenjan"
|
||||
deger={taban.doluluk ? sayi(taban.doluluk.bosKoltuk) : "—"}
|
||||
alt={
|
||||
taban.doluluk
|
||||
|
||||
@@ -37,13 +37,14 @@ import { UNITUR_ETIKET } from "@/lib/unitur";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SON_YIL } from "@/lib/veri-yillari";
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer";
|
||||
AltSayfa,
|
||||
AltSayfaKapat,
|
||||
AltSayfaGovde,
|
||||
AltSayfaIcerik,
|
||||
AltSayfaAciklama,
|
||||
AltSayfaBaslik,
|
||||
AltSayfaTetik,
|
||||
} from "@/components/ui/alt-sayfa";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -137,8 +138,8 @@ export function ProgramAyrintiButonu({
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer onOpenChange={acildi} handleOnly>
|
||||
<DrawerTrigger asChild>
|
||||
<AltSayfa onOpenChange={acildi}>
|
||||
<AltSayfaTetik asChild>
|
||||
{varyant === "metin" ? (
|
||||
<button
|
||||
type="button"
|
||||
@@ -160,21 +161,24 @@ export function ProgramAyrintiButonu({
|
||||
<span className="sr-only">{isim} — program ayrıntısı</span>
|
||||
</button>
|
||||
)}
|
||||
</DrawerTrigger>
|
||||
{/* Çekmece kabı ve çarpı cta-sira-form.tsx'teki çekmeceyle birebir (§4.10, §4.3) */}
|
||||
<DrawerContent
|
||||
className="max-h-[92dvh] bg-card sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl"
|
||||
</AltSayfaTetik>
|
||||
{/* Alt sayfa kabı tek kaynak (alt-sayfa.tsx), çarpı §4.3. İçerik uzun:
|
||||
sayfa ekranın %60'ında açılır (başlık + künye), yukarı çekince ya da
|
||||
kaydırınca tam boya çıkar (§4.19). */}
|
||||
<AltSayfaIcerik
|
||||
duraklar={[0.6, 0.92]}
|
||||
ilkDurak={0}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DrawerClose className="absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]">
|
||||
<AltSayfaKapat className="absolute top-3 right-3 z-10 flex size-9 cursor-pointer items-center justify-center rounded-full border border-slate-200 bg-popover text-slate-500 transition-[background-color,transform] duration-150 hover:bg-slate-100 hover:text-slate-800 active:scale-[0.96]">
|
||||
<X className="size-4" aria-hidden />
|
||||
<span className="sr-only">Kapat</span>
|
||||
</DrawerClose>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 pt-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
|
||||
<DrawerTitle className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||
</AltSayfaKapat>
|
||||
<AltSayfaGovde className="px-4 pt-3 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6">
|
||||
<AltSayfaBaslik className="px-10 text-center font-heading text-2xl font-bold text-slate-900">
|
||||
{isim}
|
||||
</DrawerTitle>
|
||||
<DrawerDescription className="mx-auto mt-1 max-w-md px-4 text-center text-sm text-slate-500">
|
||||
</AltSayfaBaslik>
|
||||
<AltSayfaAciklama className="mx-auto mt-1 max-w-md px-4 text-center text-sm text-slate-500">
|
||||
{veri
|
||||
? [
|
||||
trBaslikDuzeni(veri.program.universite),
|
||||
@@ -184,13 +188,13 @@ export function ProgramAyrintiButonu({
|
||||
.filter(Boolean)
|
||||
.join(" · ")
|
||||
: (universite ?? "Programın tüm açık verisi")}
|
||||
</DrawerDescription>
|
||||
</AltSayfaAciklama>
|
||||
<div className="mt-5">
|
||||
<AyrintiIcerik durum={durum} veri={veri} />
|
||||
</div>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</AltSayfaGovde>
|
||||
</AltSayfaIcerik>
|
||||
</AltSayfa>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import type { ReactNode } from "react";
|
||||
import {
|
||||
ortaTabanYonu,
|
||||
YON_ETIKET,
|
||||
type TabanKumesi,
|
||||
type TabanDuzeyi,
|
||||
type TabanOzeti,
|
||||
} from "@/lib/taban-ozeti";
|
||||
import { kisaSayi } from "@/components/ozet-kart";
|
||||
|
||||
/**
|
||||
* Üniversite ve bölüm sayfasının özet kartlarının ortak metin ve link
|
||||
* yardımcıları (DESIGN.md §4.8). Yıl yıl ayrıntı çekmecesi ("Yıllara göre
|
||||
* taban") 6 Eki 2026'da Bilal'in isteğiyle şimdilik kaldırıldı; geri gelirse
|
||||
* git geçmişinde `taban-yillari.tsx` + `siranla-kapilar.tsx` olarak duruyor.
|
||||
* taban") ve orta taban kartı 6 Eki 2026'da Bilal'in isteğiyle kaldırıldı;
|
||||
* geri gelirse git geçmişinde `taban-yillari.tsx`, `siranla-kapilar.tsx` ve
|
||||
* `ortaTabanAciklamasi` olarak duruyor.
|
||||
*/
|
||||
|
||||
/** "2026'da" — yıl eki ünlü uyumuna göre (2023'te, 2026'da, 2027'de). */
|
||||
export function yilEki(yil: number): string {
|
||||
const son = yil % 10;
|
||||
if (son === 3 || son === 4 || son === 5) return `${yil}'te`;
|
||||
if (son === 0 || son === 6 || son === 9) return `${yil}'da`;
|
||||
return `${yil}'de`;
|
||||
}
|
||||
|
||||
/** Orta taban kümesinin adı: "Devlet" / "Lisans" / "Önlisans". */
|
||||
export function kumeAdi(kume: TabanKumesi, duzey: TabanDuzeyi): string {
|
||||
if (kume === "devlet") return "Devlet";
|
||||
return duzey === "lisans" ? "Lisans" : "Önlisans";
|
||||
}
|
||||
|
||||
/** Özet kartının açıklaması: "Devlet · 2021'de 105 bin · gevşedi". */
|
||||
export function ortaTabanAciklamasi(taban: TabanOzeti): string {
|
||||
const yon = ortaTabanYonu(taban.ortaTaban);
|
||||
const kume = kumeAdi(taban.kume, taban.duzey);
|
||||
if (!yon) {
|
||||
return taban.ortaTabanAdet > 0
|
||||
? `${kume} · ${taban.ortaTabanAdet.toLocaleString("tr-TR")} programın ortası`
|
||||
: "henüz taban oluşmadı";
|
||||
}
|
||||
return `${kume} · ${yilEki(yon.ilkYil)} ${kisaSayi(yon.ilk)} · ${YON_ETIKET[yon.yon]}`;
|
||||
}
|
||||
|
||||
/** Özet kartı açıklamasındaki ad linki (üniversite ↔ bölüm çapraz bağ). */
|
||||
export function KartBaglantisi({
|
||||
href,
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Armchair,
|
||||
ArrowRight,
|
||||
Building2,
|
||||
ChartLine,
|
||||
ChevronRight,
|
||||
Landmark,
|
||||
LayoutList,
|
||||
@@ -57,7 +56,7 @@ import { UrapLogo } from "./urap-logo";
|
||||
import { AkademikAyrinti } from "./akademik-ayrinti";
|
||||
import { NavBaslik } from "./nav-baslik";
|
||||
import { OzetKart, kartSayisi } from "@/components/ozet-kart";
|
||||
import { KartBaglantisi, ortaTabanAciklamasi } from "./taban-karti";
|
||||
import { KartBaglantisi } from "./taban-karti";
|
||||
import { bolumBazAdi } from "@/lib/slug";
|
||||
import { uniLogoYolu } from "@/lib/uni-logolar";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
@@ -678,14 +677,15 @@ export function UniversiteIcerik({
|
||||
{/* Şablon tam 4 hücre bırakıyor: masaüstünde harita 3. sütunu
|
||||
iki satır boyunca kaplıyor, 5. kart ızgarayı bozar. Dört
|
||||
kart öğrencinin sorusuna cevap verir (Bilal, 6 Eki 2026:
|
||||
program/kontenjan adedi ilgi çekmiyor): en seçici bölüm
|
||||
adıyla, orta tabanın yıllar içindeki seyri, doluluk ve
|
||||
Türkiye sırası. Program, birim ve kontenjan sayıları özet
|
||||
paragrafta. Hesap lib/taban-ozeti.ts. */}
|
||||
program/kontenjan adedi ilgi çekmiyor): en yüksek ve en
|
||||
düşük taban bölüm adıyla, doluluk ve Türkiye sırası. Orta
|
||||
taban kartı kaldırıldı (Bilal, 6 Eki 2026). Program, birim
|
||||
ve kontenjan sayıları özet paragrafta. Hesap
|
||||
lib/taban-ozeti.ts. */}
|
||||
<dl className="contents">
|
||||
<OzetKart
|
||||
ikon={Target}
|
||||
etiket="En seçici bölüm"
|
||||
etiket="En yüksek taban"
|
||||
{...(taban.enSecici
|
||||
? kartSayisi(taban.enSecici.sira)
|
||||
: { deger: "—" })}
|
||||
@@ -708,15 +708,28 @@ export function UniversiteIcerik({
|
||||
}
|
||||
/>
|
||||
<OzetKart
|
||||
ikon={ChartLine}
|
||||
etiket="Orta taban"
|
||||
{...(() => {
|
||||
const son = [...taban.ortaTaban]
|
||||
.reverse()
|
||||
.find((v) => v != null);
|
||||
return son != null ? kartSayisi(son) : { deger: "—" };
|
||||
})()}
|
||||
alt={ortaTabanAciklamasi(taban)}
|
||||
ikon={Landmark}
|
||||
etiket="En düşük taban"
|
||||
{...(taban.enGenis
|
||||
? kartSayisi(taban.enGenis.sira)
|
||||
: { deger: "—" })}
|
||||
alt={
|
||||
taban.enGenis ? (
|
||||
<>
|
||||
<KartBaglantisi
|
||||
href={(() => {
|
||||
const s = bolumSlugFromIsim(taban.enGenis.isim);
|
||||
return s ? `/bolum/${s}` : null;
|
||||
})()}
|
||||
>
|
||||
{bolumBazAdi(taban.enGenis.isim)}
|
||||
</KartBaglantisi>{" "}
|
||||
· {SON_YIL} taban sırası
|
||||
</>
|
||||
) : (
|
||||
"henüz taban oluşmadı"
|
||||
)
|
||||
}
|
||||
/>
|
||||
{taban.doluluk ? (
|
||||
<OzetKart
|
||||
@@ -725,7 +738,7 @@ export function UniversiteIcerik({
|
||||
deger={`%${taban.doluluk.yuzde}`}
|
||||
alt={
|
||||
taban.doluluk.bosKoltuk > 0
|
||||
? `${sayi(taban.doluluk.kontenjan)} kontenjandan ${sayi(taban.doluluk.bosKoltuk)} koltuk boş kaldı · ${SON_YIL}`
|
||||
? `${sayi(taban.doluluk.kontenjan)} kontenjanın ${sayi(taban.doluluk.bosKoltuk)} tanesi boş kaldı · ${SON_YIL}`
|
||||
: `${sayi(taban.doluluk.kontenjan)} kontenjanın tamamı doldu · ${SON_YIL}`
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user