feat(tema): D kısayolu, logodan yayılan tema geçişi, /surum-notlari beta sayfası (→ 1.1.0)

- lib/tema-gecisi.ts: View Transitions + CSS maskesi (kolay-tercih-mark.svg),
  merkez header'daki işaret; reduced-motion ve desteksiz tarayıcıda düz geçiş
- tema-kisayolu.tsx: D tuşu açık ↔ koyu (yazı alanı/değiştirici tuşta pasif)
- tema-secici.tsx: seçim de maskeyle oynar, altında Kbd ile D ipucu
- /surum-notlari: beta açıklaması, geri bildirim (destek@kolaytercih.com),
  sürüm günlüğü; Beta rozeti ve footer buraya bağlanır, sitemap'e eklendi
- DESIGN.md §2.3 notu

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-10-07 23:25:38 +03:00
parent b2bab50337
commit 5f7ea8bbcd
13 changed files with 395 additions and 42 deletions

View File

@@ -3,6 +3,8 @@
import { useSyncExternalStore } from "react";
import { useTheme } from "next-themes";
import { Monitor, Moon, Sun } from "lucide-react";
import { Kbd } from "@/components/ui/kbd";
import { temayiGecisle } from "@/lib/tema-gecisi";
import { cn } from "@/lib/utils";
const SECENEKLER = [
@@ -16,7 +18,8 @@ const aboneOlma = () => () => {};
/**
* Tema seçici: açık / koyu / sistem (DESIGN.md §2.3). Seçili tema yalnızca
* tarayıcıda bilinir; sunucu HTML'inde hiçbir seçenek işaretli gelmez,
* hydration sonrası işaretlenir (yerleşim oynamaz).
* hydration sonrası işaretlenir (yerleşim oynamaz). Seçim logodan yayılan
* maskeyle uygulanır (lib/tema-gecisi.ts); altında `D` kısayolu ipucu durur.
*/
export function TemaSecici({ className }: { className?: string }) {
const { theme, setTheme } = useTheme();
@@ -27,35 +30,38 @@ export function TemaSecici({ className }: { className?: string }) {
);
return (
<div
role="radiogroup"
aria-label="Tema"
className={cn(
"inline-flex items-center gap-1 rounded-full border border-slate-200 bg-card p-1",
className,
)}
>
{SECENEKLER.map(({ deger, etiket, ikon: Ikon }) => {
const secili = tarayicida && theme === deger;
return (
<button
key={deger}
type="button"
role="radio"
aria-checked={secili}
onClick={() => setTheme(deger)}
className={cn(
"inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full px-3 text-sm font-semibold transition-[background-color,color,transform] duration-150 outline-none focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.97]",
secili
? "bg-slate-900 text-slate-50"
: "text-slate-600 hover:text-orange-500",
)}
>
<Ikon className="size-4" aria-hidden />
{etiket}
</button>
);
})}
<div className={cn("flex flex-col items-center gap-2", className)}>
<div
role="radiogroup"
aria-label="Tema"
className="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-card p-1"
>
{SECENEKLER.map(({ deger, etiket, ikon: Ikon }) => {
const secili = tarayicida && theme === deger;
return (
<button
key={deger}
type="button"
role="radio"
aria-checked={secili}
onClick={() => temayiGecisle(() => setTheme(deger))}
className={cn(
"inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full px-3 text-sm font-semibold transition-[background-color,color,transform] duration-150 outline-none focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.97]",
secili
? "bg-slate-900 text-slate-50"
: "text-slate-600 hover:text-orange-500",
)}
>
<Ikon className="size-4" aria-hidden />
{etiket}
</button>
);
})}
</div>
<p className="flex items-center gap-1.5 text-xs text-slate-500">
<Kbd>D</Kbd>
<span>ile açık / koyu arasında geç</span>
</p>
</div>
);
}