feat(ui): white-label theme customizer panel — Türkçe + sansürlü

Yan paneldeki görünüm özelleştirme widget'ı template branding'inden
arındırıldı:

- 'Customizer' → 'Görünüm'
- Tab adları: 'Theme'/'Layout' → 'Renk'/'Düzen'
- 'Shadcn UI Theme Presets' → 'Hazır temalar'
- 'Tweakcn Theme Presets' → 'Genişletilmiş temalar' (kaynak adı
  uygulamada görünmüyor)
- 'Random' → 'Rastgele', 'Choose ... Theme' → 'Tema seçin'
- 'Radius' → 'Köşe yuvarlama'
- 'Mode' / 'Light' / 'Dark' → 'Görünüm modu' / 'Açık' / 'Koyu'
- 'Import Theme' → 'Tema içe aktar' (modal başlığı + butonlar dahil)
- 'Brand Colors' → 'Marka renkleri'; alt etiketler de TR
- 'Sidebar Variant'/'Collapsible Mode'/'Position' → 'Kenar çubuğu stili
  / Daraltma davranışı / Kenar çubuğu konumu', açıklamalar TR
- Constants (theme-customizer-constants): name alanları Türkçe
  (Default→Standart, Floating→Yüzen, Inset→İçeri çekik, Off Canvas→Gizle,
  Icon→İkon, None→Sabit, Left→Sol, Right→Sağ; brand color isimleri de
  Türkçeye çevrildi)
- Tweakcn.com reklam kartı **tamamen kaldırıldı** (artık 3rd party link
  yok). Onun yerine import butonunun altına nötr bir ipucu yazısı:
  'tweakcn.com gibi araçlardan dışa aktardığınız JSON tema dosyasını
  yükleyebilirsiniz.' — yani aracı bilgi olarak veriyoruz, reklam değil.
- Reset/X butonlarına a11y label + tooltip eklendi.

Theme datasını ('colorThemes' / 'tweakcnThemes' from @/config/theme-data)
ve hook adlarını ('applyTweakcnTheme', 'tweakcn-theme-presets') aynen
bıraktım — kullanıcıya görünmüyor, refactor maliyetli, runtime davranışı
identical.
This commit is contained in:
kovakmedya
2026-04-30 08:03:00 +03:00
parent 37cf745ca1
commit 2549ce097c
5 changed files with 89 additions and 106 deletions
@@ -66,9 +66,10 @@ export function ImportModal({ open, onOpenChange, onImport }: ImportModalProps)
<Dialog open={open} onOpenChange={onOpenChange} modal={true}> <Dialog open={open} onOpenChange={onOpenChange} modal={true}>
<DialogContent className="max-w-4xl w-[90vw]"> <DialogContent className="max-w-4xl w-[90vw]">
<DialogHeader> <DialogHeader>
<DialogTitle>Import Custom CSS</DialogTitle> <DialogTitle>Tema CSS'i içe aktar</DialogTitle>
<DialogDescription> <DialogDescription>
Paste your CSS theme below. Include both <code>:root</code> (light mode) and <code>.dark</code> (dark mode) sections with CSS variables like <code>--primary</code>, <code>--background</code>, etc. The theme will automatically switch between light and dark modes. CSS temasını aşağıya yapıştırın. Açık tema için <code>:root</code> ve koyu tema için{" "}
<code>.dark</code> bölümleri olmalı; <code>--primary</code>, <code>--background</code> gibi CSS değişkenlerini içerir. Açık/koyu mod arasında otomatik geçiş yapılır.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-4"> <div className="space-y-4">
@@ -94,10 +95,10 @@ export function ImportModal({ open, onOpenChange, onImport }: ImportModalProps)
</div> </div>
<div className="flex gap-2 justify-end"> <div className="flex gap-2 justify-end">
<Button variant="outline" onClick={() => onOpenChange(false)} className="cursor-pointer"> <Button variant="outline" onClick={() => onOpenChange(false)} className="cursor-pointer">
Cancel Vazgeç
</Button> </Button>
<Button onClick={processImport} disabled={!importText.trim()} className="cursor-pointer"> <Button onClick={processImport} disabled={!importText.trim()} className="cursor-pointer">
Import Theme İçe aktar
</Button> </Button>
</div> </div>
</div> </div>
+23 -10
View File
@@ -96,18 +96,31 @@ export function ThemeCustomizer({ open, onOpenChange }: ThemeCustomizerProps) {
<div className="p-2 bg-primary/10 rounded-lg"> <div className="p-2 bg-primary/10 rounded-lg">
<Settings className="h-4 w-4" /> <Settings className="h-4 w-4" />
</div> </div>
<SheetTitle className="text-lg font-semibold">Customizer</SheetTitle> <SheetTitle className="text-lg font-semibold">Görünüm</SheetTitle>
<div className="ml-auto flex items-center gap-2"> <div className="ml-auto flex items-center gap-2">
<Button variant="outline" size="icon" onClick={handleReset} className="cursor-pointer h-8 w-8"> <Button
variant="outline"
size="icon"
onClick={handleReset}
className="cursor-pointer h-8 w-8"
aria-label="Varsayılana dön"
title="Varsayılana dön"
>
<RotateCcw className="h-4 w-4" /> <RotateCcw className="h-4 w-4" />
</Button> </Button>
<Button variant="outline" size="icon" onClick={() => onOpenChange(false)} className="cursor-pointer h-8 w-8"> <Button
variant="outline"
size="icon"
onClick={() => onOpenChange(false)}
className="cursor-pointer h-8 w-8"
aria-label="Kapat"
>
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</Button> </Button>
</div> </div>
</div> </div>
<SheetDescription className="text-sm text-muted-foreground sr-only"> <SheetDescription className="text-sm text-muted-foreground sr-only">
Customize the them and layout of your dashboard. Panelin renklerini ve düzenini özelleştirin.
</SheetDescription> </SheetDescription>
</SheetHeader> </SheetHeader>
@@ -115,13 +128,13 @@ export function ThemeCustomizer({ open, onOpenChange }: ThemeCustomizerProps) {
<Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col"> <Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col">
<div className="py-2"> <div className="py-2">
<TabsList className="grid w-full grid-cols-2 rounded-none h-12 p-1.5"> <TabsList className="grid w-full grid-cols-2 rounded-none h-12 p-1.5">
<TabsTrigger value="theme" className="cursor-pointer data-[state=active]:bg-background"><Palette className="h-4 w-4 mr-1" /> Theme</TabsTrigger> <TabsTrigger value="theme" className="cursor-pointer data-[state=active]:bg-background">
<TabsTrigger value="layout" className="cursor-pointer data-[state=active]:bg-background"><Layout className="h-4 w-4 mr-1" /> Layout</TabsTrigger> <Palette className="h-4 w-4 mr-1" /> Renk
</TabsTrigger>
<TabsTrigger value="layout" className="cursor-pointer data-[state=active]:bg-background">
<Layout className="h-4 w-4 mr-1" /> Düzen
</TabsTrigger>
</TabsList> </TabsList>
{/* <TabsList className="grid w-full grid-cols-2 rounded-none h-12 p-1.5">
<TabsTrigger value="theme" className="cursor-pointer data-[state=active]:bg-primary data-[state=active]:text-primary-foreground"><Palette className="h-4 w-4 mr-1" /> Theme</TabsTrigger>
<TabsTrigger value="layout" className="cursor-pointer data-[state=active]:bg-primary data-[state=active]:text-primary-foreground"><Layout className="h-4 w-4 mr-1" /> Layout</TabsTrigger>
</TabsList> */}
</div> </div>
<TabsContent value="theme" className="flex-1 mt-0"> <TabsContent value="theme" className="flex-1 mt-0">
+14 -15
View File
@@ -30,16 +30,15 @@ export function LayoutTab() {
return ( return (
<div className="p-4 space-y-6"> <div className="p-4 space-y-6">
{/* Sidebar Configuration */} {/* Kenar çubuğu ayarları */}
<div className="space-y-3"> <div className="space-y-3">
{/* Sidebar Variant */}
<div> <div>
<Label className="text-sm font-medium">Sidebar Variant</Label> <Label className="text-sm font-medium">Kenar çubuğu stili</Label>
{sidebarConfig.variant && ( {sidebarConfig.variant && (
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
{sidebarConfig.variant === "sidebar" && "Default: Standard sidebar layout"} {sidebarConfig.variant === "sidebar" && "Standart: klasik kenar çubuğu"}
{sidebarConfig.variant === "floating" && "Floating: Floating sidebar with border"} {sidebarConfig.variant === "floating" && "Yüzen: çerçeveli, yüzen kenar çubuğu"}
{sidebarConfig.variant === "inset" && "Inset: Inset sidebar with rounded corners"} {sidebarConfig.variant === "inset" && "İçeri çekik: yuvarlak köşeli içeri çekik"}
</p> </p>
)} )}
</div> </div>
@@ -84,15 +83,15 @@ export function LayoutTab() {
<Separator /> <Separator />
{/* Sidebar Collapsible Mode */} {/* Daraltma davranışı */}
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label className="text-sm font-medium">Sidebar Collapsible Mode</Label> <Label className="text-sm font-medium">Daraltma davranışı</Label>
{sidebarConfig.collapsible && ( {sidebarConfig.collapsible && (
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
{sidebarConfig.collapsible === "offcanvas" && "Off Canvas: Slides out of view"} {sidebarConfig.collapsible === "offcanvas" && "Gizle: kenar görünümden tamamen kayar"}
{sidebarConfig.collapsible === "icon" && "Icon: Collapses to icon only"} {sidebarConfig.collapsible === "icon" && "İkon: sadece ikonlar görünür"}
{sidebarConfig.collapsible === "none" && "None: Always visible"} {sidebarConfig.collapsible === "none" && "Sabit: her zaman açık"}
</p> </p>
)} )}
</div> </div>
@@ -152,14 +151,14 @@ export function LayoutTab() {
<Separator /> <Separator />
{/* Sidebar Side */} {/* Kenar çubuğu konumu */}
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<Label className="text-sm font-medium">Sidebar Position</Label> <Label className="text-sm font-medium">Kenar çubuğu konumu</Label>
{sidebarConfig.side && ( {sidebarConfig.side && (
<p className="text-xs text-muted-foreground mt-1"> <p className="text-xs text-muted-foreground mt-1">
{sidebarConfig.side === "left" && "Left: Sidebar positioned on the left side"} {sidebarConfig.side === "left" && "Sol: kenar çubuğu solda"}
{sidebarConfig.side === "right" && "Right: Sidebar positioned on the right side"} {sidebarConfig.side === "right" && "Sağ: kenar çubuğu sağda"}
</p> </p>
)} )}
</div> </div>
+24 -49
View File
@@ -1,6 +1,6 @@
"use client" "use client"
import { Palette, Dices, Upload, ExternalLink, Sun, Moon } from 'lucide-react' import { Dices, Upload, Sun, Moon } from 'lucide-react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
@@ -87,25 +87,25 @@ export function ThemeTab({
<div className="p-4 space-y-6"> <div className="p-4 space-y-6">
{/* Shadcn UI Theme Presets */} {/* Hazır temalar */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-sm font-medium">Shadcn UI Theme Presets</Label> <Label className="text-sm font-medium">Hazır temalar</Label>
<Button variant="outline" size="sm" onClick={handleRandomShadcn} className="cursor-pointer"> <Button variant="outline" size="sm" onClick={handleRandomShadcn} className="cursor-pointer">
<Dices className="h-3.5 w-3.5 mr-1.5" /> <Dices className="h-3.5 w-3.5 mr-1.5" />
Random Rastgele
</Button> </Button>
</div> </div>
<Select value={selectedTheme} onValueChange={(value) => { <Select value={selectedTheme} onValueChange={(value) => {
setSelectedTheme(value) setSelectedTheme(value)
setSelectedTweakcnTheme("") // Clear tweakcn selection setSelectedTweakcnTheme("") // Clear other selection
setBrandColorsValues({}) // Clear brand colors state setBrandColorsValues({}) // Clear brand colors state
setImportedTheme(null) // Clear imported theme setImportedTheme(null) // Clear imported theme
applyTheme(value, isDarkMode) applyTheme(value, isDarkMode)
}}> }}>
<SelectTrigger className="w-full cursor-pointer"> <SelectTrigger className="w-full cursor-pointer">
<SelectValue placeholder="Choose Shadcn Theme" /> <SelectValue placeholder="Tema seçin" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-60"> <SelectContent className="max-h-60">
<div className="p-2"> <div className="p-2">
@@ -141,19 +141,19 @@ export function ThemeTab({
<Separator /> <Separator />
{/* Tweakcn Theme Presets */} {/* Genişletilmiş temalar */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-sm font-medium">Tweakcn Theme Presets</Label> <Label className="text-sm font-medium">Genişletilmiş temalar</Label>
<Button variant="outline" size="sm" onClick={handleRandomTweakcn} className="cursor-pointer"> <Button variant="outline" size="sm" onClick={handleRandomTweakcn} className="cursor-pointer">
<Dices className="h-3.5 w-3.5 mr-1.5" /> <Dices className="h-3.5 w-3.5 mr-1.5" />
Random Rastgele
</Button> </Button>
</div> </div>
<Select value={selectedTweakcnTheme} onValueChange={(value) => { <Select value={selectedTweakcnTheme} onValueChange={(value) => {
setSelectedTweakcnTheme(value) setSelectedTweakcnTheme(value)
setSelectedTheme("") // Clear shadcn selection setSelectedTheme("") // Clear other selection
setBrandColorsValues({}) // Clear brand colors state setBrandColorsValues({}) // Clear brand colors state
setImportedTheme(null) // Clear imported theme setImportedTheme(null) // Clear imported theme
const selectedPreset = tweakcnThemes.find(t => t.value === value)?.preset const selectedPreset = tweakcnThemes.find(t => t.value === value)?.preset
@@ -162,7 +162,7 @@ export function ThemeTab({
} }
}}> }}>
<SelectTrigger className="w-full cursor-pointer"> <SelectTrigger className="w-full cursor-pointer">
<SelectValue placeholder="Choose Tweakcn Theme" /> <SelectValue placeholder="Tema seçin" />
</SelectTrigger> </SelectTrigger>
<SelectContent className="max-h-60"> <SelectContent className="max-h-60">
<div className="p-2"> <div className="p-2">
@@ -198,9 +198,9 @@ export function ThemeTab({
<Separator /> <Separator />
{/* Radius Selection */} {/* Köşe yuvarlama */}
<div className="space-y-3"> <div className="space-y-3">
<Label className="text-sm font-medium">Radius</Label> <Label className="text-sm font-medium">Köşe yuvarlama</Label>
<div className="grid grid-cols-5 gap-2"> <div className="grid grid-cols-5 gap-2">
{radiusOptions.map((option) => ( {radiusOptions.map((option) => (
<div <div
@@ -222,9 +222,9 @@ export function ThemeTab({
<Separator /> <Separator />
{/* Mode Section */} {/* Görünüm modu */}
<div className="space-y-3"> <div className="space-y-3">
<Label className="text-sm font-medium">Mode</Label> <Label className="text-sm font-medium">Görünüm modu</Label>
<div className="grid grid-cols-2 gap-2"> <div className="grid grid-cols-2 gap-2">
<Button <Button
variant={!isDarkMode ? "secondary" : "outline"} variant={!isDarkMode ? "secondary" : "outline"}
@@ -233,7 +233,7 @@ export function ThemeTab({
className="cursor-pointer" className="cursor-pointer"
> >
<Sun className="h-4 w-4 mr-1" /> <Sun className="h-4 w-4 mr-1" />
Light Açık
</Button> </Button>
<Button <Button
variant={isDarkMode ? "secondary" : "outline"} variant={isDarkMode ? "secondary" : "outline"}
@@ -242,14 +242,14 @@ export function ThemeTab({
className="cursor-pointer" className="cursor-pointer"
> >
<Moon className="h-4 w-4 mr-1" /> <Moon className="h-4 w-4 mr-1" />
Dark Koyu
</Button> </Button>
</div> </div>
</div> </div>
<Separator /> <Separator />
{/* Import Theme Button */} {/* Tema içe aktar */}
<div className="space-y-3"> <div className="space-y-3">
<Button <Button
variant="outline" variant="outline"
@@ -258,15 +258,18 @@ export function ThemeTab({
className="w-full cursor-pointer" className="w-full cursor-pointer"
> >
<Upload className="h-3.5 w-3.5 mr-1.5" /> <Upload className="h-3.5 w-3.5 mr-1.5" />
Import Theme Tema içe aktar
</Button> </Button>
<p className="text-muted-foreground text-xs">
tweakcn.com gibi araçlardan dışa aktardığınız JSON tema dosyasını yükleyebilirsiniz.
</p>
</div> </div>
{/* Brand Colors Section */} {/* Marka renkleri */}
<Accordion type="single" collapsible className="w-full border-b rounded-lg"> <Accordion type="single" collapsible className="w-full border-b rounded-lg">
<AccordionItem value="brand-colors" className="border border-border rounded-lg overflow-hidden"> <AccordionItem value="brand-colors" className="border border-border rounded-lg overflow-hidden">
<AccordionTrigger className="px-4 py-3 hover:no-underline hover:bg-muted/50 transition-colors"> <AccordionTrigger className="px-4 py-3 hover:no-underline hover:bg-muted/50 transition-colors">
<Label className="text-sm font-medium cursor-pointer">Brand Colors</Label> <Label className="text-sm font-medium cursor-pointer">Marka renkleri</Label>
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="px-4 pb-4 pt-2 space-y-3 border-t border-border bg-muted/20"> <AccordionContent className="px-4 pb-4 pt-2 space-y-3 border-t border-border bg-muted/20">
{baseColors.map((color) => ( {baseColors.map((color) => (
@@ -282,34 +285,6 @@ export function ThemeTab({
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{/* Tweakcn */}
<div className="p-4 bg-muted rounded-lg space-y-3">
<div className="flex items-center gap-2">
<Palette className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">Advanced Customization</span>
</div>
<p className="text-xs text-muted-foreground">
For advanced theme customization with real-time preview, visual color picker, and hundreds of prebuilt themes, visit{" "}
<a
href="https://tweakcn.com/editor/theme"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline font-medium cursor-pointer"
>
tweakcn.com
</a>
</p>
<Button
variant="outline"
size="sm"
className="w-full cursor-pointer"
onClick={() => typeof window !== "undefined" && window.open('https://tweakcn.com/editor/theme', '_blank')}
>
<ExternalLink className="h-3.5 w-3.5 mr-1.5" />
Open Tweakcn
</Button>
</div>
</div> </div>
) )
} }
+23 -28
View File
@@ -1,12 +1,11 @@
import type { import type {
SidebarVariant, SidebarVariant,
SidebarCollapsibleOption, SidebarCollapsibleOption,
SidebarSideOption, SidebarSideOption,
RadiusOption, RadiusOption,
BrandColor BrandColor,
} from '@/types/theme-customizer' } from "@/types/theme-customizer"
// Radius options
export const radiusOptions: RadiusOption[] = [ export const radiusOptions: RadiusOption[] = [
{ name: "0", value: "0rem" }, { name: "0", value: "0rem" },
{ name: "0.3", value: "0.3rem" }, { name: "0.3", value: "0.3rem" },
@@ -15,34 +14,30 @@ export const radiusOptions: RadiusOption[] = [
{ name: "1.0", value: "1rem" }, { name: "1.0", value: "1rem" },
] ]
// Sidebar variant options
export const sidebarVariants: SidebarVariant[] = [ export const sidebarVariants: SidebarVariant[] = [
{ name: "Default", value: "sidebar", description: "Standard sidebar layout" }, { name: "Standart", value: "sidebar", description: "Klasik kenar çubuğu" },
{ name: "Floating", value: "floating", description: "Floating sidebar with border" }, { name: "Yüzen", value: "floating", description: "Çerçeveli, yüzen kenar çubuğu" },
{ name: "Inset", value: "inset", description: "Inset sidebar with rounded corners" }, { name: "İçeri çekik", value: "inset", description: "Yuvarlak köşeli içeri çekik kenar" },
] ]
// Sidebar collapsible options
export const sidebarCollapsibleOptions: SidebarCollapsibleOption[] = [ export const sidebarCollapsibleOptions: SidebarCollapsibleOption[] = [
{ name: "Off Canvas", value: "offcanvas", description: "Slides out of view" }, { name: "Gizle", value: "offcanvas", description: "Görünümden tamamen kayar" },
{ name: "Icon", value: "icon", description: "Collapses to icon only" }, { name: "İkon", value: "icon", description: "Sadece ikonlar görünür" },
{ name: "None", value: "none", description: "Always visible" }, { name: "Sabit", value: "none", description: "Her zaman açık" },
] ]
// Sidebar side options
export const sidebarSideOptions: SidebarSideOption[] = [ export const sidebarSideOptions: SidebarSideOption[] = [
{ name: "Left", value: "left" }, { name: "Sol", value: "left" },
{ name: "Right", value: "right" }, { name: "Sağ", value: "right" },
] ]
// Define brand colors for custom color inputs
export const baseColors: BrandColor[] = [ export const baseColors: BrandColor[] = [
{ name: "Primary", cssVar: "--primary" }, { name: "Birincil", cssVar: "--primary" },
{ name: "Primary Foreground", cssVar: "--primary-foreground" }, { name: "Birincil — yazı", cssVar: "--primary-foreground" },
{ name: "Secondary", cssVar: "--secondary" }, { name: "İkincil", cssVar: "--secondary" },
{ name: "Secondary Foreground", cssVar: "--secondary-foreground" }, { name: "İkincil — yazı", cssVar: "--secondary-foreground" },
{ name: "Accent", cssVar: "--accent" }, { name: "Vurgu", cssVar: "--accent" },
{ name: "Accent Foreground", cssVar: "--accent-foreground" }, { name: "Vurgu — yazı", cssVar: "--accent-foreground" },
{ name: "Muted", cssVar: "--muted" }, { name: "Soluk", cssVar: "--muted" },
{ name: "Muted Foreground", cssVar: "--muted-foreground" }, { name: "Soluk — yazı", cssVar: "--muted-foreground" },
] ]