Files
config-vault/components/IconPicker.tsx
T
2026-08-26 14:21:53 +03:00

198 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useRef } from 'react'
import { Upload, Link as LinkIcon, Sparkles, X, Image as ImageIcon } from 'lucide-react'
import { AppIcon } from './AppIcon'
import { cn } from '@/lib/utils'
interface IconPickerProps {
value: string
onChange: (val: string) => void
appName: string
}
const PRESET_EMOJIS = [
'📱', '🚀', '🛍️', '💳', '⚡', '🛡️', '🤖', '📦',
'🎮', '📊', '🎧', '💬', '🌐', '🔒', '🍔', '🚗',
'✈️', '📈', '🎨', '🎵', '🏥', '🏢', '⚙️', '🔑'
]
export function IconPicker({ value, onChange, appName }: IconPickerProps) {
const [activeTab, setActiveTab] = useState<'presets' | 'url' | 'upload'>('presets')
const [urlInput, setUrlInput] = useState(value?.startsWith('http') ? value : '')
const fileInputRef = useRef<HTMLInputElement>(null)
function handleEmojiSelect(emoji: string) {
onChange(emoji)
}
function handleUrlChange(url: string) {
setUrlInput(url)
onChange(url)
}
function handleFileUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0]
if (!file) return
// Limit size to 1.5MB
if (file.size > 1.5 * 1024 * 1024) {
alert('İkon boyutu 1.5MB\'dan küçük olmalıdır.')
return
}
const reader = new FileReader()
reader.onload = () => {
if (typeof reader.result === 'string') {
onChange(reader.result)
}
}
reader.readAsDataURL(file)
}
function handleClear() {
onChange('')
setUrlInput('')
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
}
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="block text-xs font-semibold text-slate-300 uppercase tracking-wider">
Uygulama İkonu <span className="text-slate-500 font-normal lowercase">(isteğe bağlı)</span>
</label>
{value && (
<button
type="button"
onClick={handleClear}
className="text-[11px] text-rose-400 hover:text-rose-300 flex items-center gap-1 transition-colors"
>
<X className="w-3 h-3" />
<span>İkonu Kaldır</span>
</button>
)}
</div>
<div className="p-4 rounded-2xl bg-slate-900/70 border border-slate-800 space-y-4">
{/* Live Preview & Tabs Header */}
<div className="flex items-center gap-4 pb-3 border-b border-slate-800/80">
<div className="flex flex-col items-center gap-1.5">
<AppIcon name={appName || 'A'} iconUrl={value} size="lg" className="ring-2 ring-emerald-500/20" />
<span className="text-[10px] text-slate-500 font-medium">Önizleme</span>
</div>
{/* Mode Switcher */}
<div className="flex-1 flex gap-1 p-1 bg-slate-950/80 rounded-xl border border-slate-800">
<button
type="button"
onClick={() => setActiveTab('presets')}
className={cn(
'flex-1 py-1.5 px-2 rounded-lg text-xs font-medium transition-all flex items-center justify-center gap-1.5',
activeTab === 'presets' ? 'bg-slate-800 text-emerald-400 shadow-sm' : 'text-slate-400 hover:text-white'
)}
>
<Sparkles className="w-3 h-3" />
<span>Hazır İkonlar</span>
</button>
<button
type="button"
onClick={() => setActiveTab('upload')}
className={cn(
'flex-1 py-1.5 px-2 rounded-lg text-xs font-medium transition-all flex items-center justify-center gap-1.5',
activeTab === 'upload' ? 'bg-slate-800 text-emerald-400 shadow-sm' : 'text-slate-400 hover:text-white'
)}
>
<Upload className="w-3 h-3" />
<span>Görsel Yükle</span>
</button>
<button
type="button"
onClick={() => setActiveTab('url')}
className={cn(
'flex-1 py-1.5 px-2 rounded-lg text-xs font-medium transition-all flex items-center justify-center gap-1.5',
activeTab === 'url' ? 'bg-slate-800 text-emerald-400 shadow-sm' : 'text-slate-400 hover:text-white'
)}
>
<LinkIcon className="w-3 h-3" />
<span>URL</span>
</button>
</div>
</div>
{/* Tab 1: Presets */}
{activeTab === 'presets' && (
<div className="grid grid-cols-8 sm:grid-cols-12 gap-1.5 animate-fade-in">
{PRESET_EMOJIS.map((emoji) => (
<button
key={emoji}
type="button"
onClick={() => handleEmojiSelect(emoji)}
className={cn(
'h-9 rounded-xl flex items-center justify-center text-lg transition-all hover:scale-110 hover:bg-slate-800',
value === emoji
? 'bg-emerald-500/20 ring-2 ring-emerald-400'
: 'bg-slate-900/60 border border-slate-800/60'
)}
>
{emoji}
</button>
))}
</div>
)}
{/* Tab 2: Upload */}
{activeTab === 'upload' && (
<div className="space-y-2 animate-fade-in">
<input
type="file"
ref={fileInputRef}
onChange={handleFileUpload}
accept="image/png, image/jpeg, image/webp, image/svg+xml"
className="hidden"
/>
<button
type="button"
onClick={() => fileInputRef.current?.click()}
className="w-full py-4 border-2 border-dashed border-slate-700/80 hover:border-emerald-500/50 rounded-xl bg-slate-950/40 hover:bg-slate-900/40 flex flex-col items-center justify-center gap-1.5 transition-colors cursor-pointer"
>
<Upload className="w-5 h-5 text-emerald-400" />
<span className="text-xs font-semibold text-slate-300">
Görsel Seçin veya Sürükleyin
</span>
<span className="text-[10px] text-slate-500">
PNG, JPG, WebP veya SVG (Önerilen: 256x256)
</span>
</button>
</div>
)}
{/* Tab 3: URL */}
{activeTab === 'url' && (
<div className="space-y-2 animate-fade-in">
<div className="flex items-center glass-input rounded-xl overflow-hidden">
<span className="px-3 text-slate-500 text-xs border-r border-slate-800 py-2.5">
https://
</span>
<input
type="url"
value={urlInput}
onChange={(e) => handleUrlChange(e.target.value)}
placeholder="ornek.com/logo.png"
className="flex-1 bg-transparent px-3 py-2 text-white text-xs font-mono placeholder-slate-500 focus:outline-none"
/>
</div>
<p className="text-[11px] text-slate-500">
CDN veya harici doğrudan görsel linki yapıştırın.
</p>
</div>
)}
</div>
</div>
)
}