Sitenin piksel imzasının hareketli hâli: yeni tema header'daki logodan başlar, kare ızgarası hâlinde dağınık bir ön yüzle ekrana yayılır; her kare sırası gelince yumuşakça belirir (900 ms, --kt-tema-sure). View Transitions + küçük tuvalden üretilen, önbelleklenen maske kareleri; pixelated ile keskin. Reduced-motion ve desteksiz tarayıcıda düz geçiş. DESIGN §2.3 güncel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
"use client";
|
||
|
||
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 = [
|
||
{ deger: "light", etiket: "Açık", ikon: Sun },
|
||
{ deger: "dark", etiket: "Koyu", ikon: Moon },
|
||
{ deger: "system", etiket: "Sistem", ikon: Monitor },
|
||
] as const;
|
||
|
||
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). Seçim logodan piksel piksel
|
||
* yayılarak uygulanır (lib/tema-gecisi.ts); altında `D` kısayolu ipucu durur.
|
||
*/
|
||
export function TemaSecici({ className }: { className?: string }) {
|
||
const { theme, setTheme } = useTheme();
|
||
const tarayicida = useSyncExternalStore(
|
||
aboneOlma,
|
||
() => true,
|
||
() => false,
|
||
);
|
||
|
||
return (
|
||
<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>
|
||
);
|
||
}
|