243 lines
9.7 KiB
TypeScript
243 lines
9.7 KiB
TypeScript
import React, { useState } from 'react'
|
||
import { motion, AnimatePresence } from 'framer-motion'
|
||
import { HiX, HiPlus, HiOutlineLink, HiOutlineUpload } from 'react-icons/hi'
|
||
import classNames from 'classnames'
|
||
|
||
export interface MediaItem {
|
||
id: string
|
||
type: 'image' | 'video'
|
||
url: string
|
||
file?: File
|
||
}
|
||
|
||
interface MediaManagerProps {
|
||
media: MediaItem[]
|
||
onChange: (media: MediaItem[]) => void
|
||
onClose: () => void
|
||
}
|
||
|
||
const MediaManager: React.FC<MediaManagerProps> = ({ media, onChange, onClose }) => {
|
||
const [activeTab, setActiveTab] = useState<'upload' | 'url'>('upload')
|
||
const [urlInput, setUrlInput] = useState('')
|
||
const [mediaType, setMediaType] = useState<'image' | 'video'>('image')
|
||
|
||
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const files = e.target.files
|
||
if (!files) return
|
||
|
||
const newMedia: MediaItem[] = Array.from(files).map((file) => ({
|
||
id: Math.random().toString(36).substr(2, 9),
|
||
type: file.type.startsWith('video/') ? 'video' : 'image',
|
||
url: URL.createObjectURL(file),
|
||
file
|
||
}))
|
||
|
||
onChange([...media, ...newMedia])
|
||
e.target.value = ''
|
||
}
|
||
|
||
const handleUrlAdd = () => {
|
||
if (!urlInput.trim()) return
|
||
|
||
const newMedia: MediaItem = {
|
||
id: Math.random().toString(36).substr(2, 9),
|
||
type: mediaType,
|
||
url: urlInput
|
||
}
|
||
|
||
onChange([...media, newMedia])
|
||
setUrlInput('')
|
||
}
|
||
|
||
const removeMedia = (id: string) => {
|
||
onChange(media.filter((m) => m.id !== id))
|
||
}
|
||
|
||
return (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.9 }}
|
||
animate={{ opacity: 1, scale: 1 }}
|
||
exit={{ opacity: 0, scale: 0.9 }}
|
||
className="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-3xl max-h-[90vh] overflow-hidden"
|
||
>
|
||
{/* Header */}
|
||
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
|
||
<h2 className="text-xl font-bold text-gray-900 dark:text-white">Medya Ekle</h2>
|
||
<button
|
||
onClick={onClose}
|
||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors"
|
||
>
|
||
<HiX className="w-5 h-5 text-gray-500 dark:text-gray-400" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Tabs */}
|
||
<div className="flex border-b border-gray-200 dark:border-gray-700 px-4">
|
||
<button
|
||
onClick={() => setActiveTab('upload')}
|
||
className={classNames(
|
||
'px-4 py-3 font-medium border-b-2 transition-colors',
|
||
activeTab === 'upload'
|
||
? 'border-blue-600 text-blue-600'
|
||
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
|
||
)}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<HiOutlineUpload className="w-5 h-5" />
|
||
<span>Bilgisayarımdan Seç</span>
|
||
</div>
|
||
</button>
|
||
<button
|
||
onClick={() => setActiveTab('url')}
|
||
className={classNames(
|
||
'px-4 py-3 font-medium border-b-2 transition-colors',
|
||
activeTab === 'url'
|
||
? 'border-blue-600 text-blue-600'
|
||
: 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
|
||
)}
|
||
>
|
||
<div className="flex items-center gap-2">
|
||
<HiOutlineLink className="w-5 h-5" />
|
||
<span>URL ile Ekle</span>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
|
||
{/* Content */}
|
||
<div className="p-4 overflow-y-auto max-h-[calc(90vh-240px)]">
|
||
{activeTab === 'upload' ? (
|
||
<div>
|
||
<label className="block">
|
||
<div className="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-8 text-center hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors cursor-pointer">
|
||
<HiOutlineUpload className="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
||
<p className="text-gray-700 dark:text-gray-300 font-medium mb-1">
|
||
Dosya seçmek için tıklayın
|
||
</p>
|
||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||
Resim veya Video (PNG, JPG, GIF, MP4, MOV)
|
||
</p>
|
||
</div>
|
||
<input
|
||
type="file"
|
||
accept="image/*,video/*"
|
||
multiple
|
||
onChange={handleFileSelect}
|
||
className="hidden"
|
||
/>
|
||
</label>
|
||
</div>
|
||
) : (
|
||
<div>
|
||
<div className="mb-4">
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
Medya Tipi
|
||
</label>
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={() => setMediaType('image')}
|
||
className={classNames(
|
||
'flex-1 py-2 px-4 rounded-lg font-medium transition-colors',
|
||
mediaType === 'image'
|
||
? 'bg-blue-600 text-white'
|
||
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||
)}
|
||
>
|
||
Resim
|
||
</button>
|
||
<button
|
||
onClick={() => setMediaType('video')}
|
||
className={classNames(
|
||
'flex-1 py-2 px-4 rounded-lg font-medium transition-colors',
|
||
mediaType === 'video'
|
||
? 'bg-blue-600 text-white'
|
||
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||
)}
|
||
>
|
||
Video
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="url"
|
||
value={urlInput}
|
||
onChange={(e) => setUrlInput(e.target.value)}
|
||
onKeyPress={(e) => e.key === 'Enter' && handleUrlAdd()}
|
||
placeholder={`${mediaType === 'image' ? 'Resim' : 'Video'} URL'si girin`}
|
||
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||
/>
|
||
<button
|
||
onClick={handleUrlAdd}
|
||
disabled={!urlInput.trim()}
|
||
className="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
|
||
>
|
||
Ekle
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Media Preview */}
|
||
{media.length > 0 && (
|
||
<div className="mt-6">
|
||
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||
Eklenen Medyalar ({media.length})
|
||
</h3>
|
||
<div className="grid grid-cols-4 gap-3">
|
||
{media.map((item) => (
|
||
<div key={item.id} className="relative group">
|
||
{item.type === 'image' ? (
|
||
<img
|
||
src={item.url}
|
||
alt="Media preview"
|
||
className="w-full h-24 object-cover rounded-lg"
|
||
/>
|
||
) : (
|
||
<div className="w-full h-24 bg-gray-900 rounded-lg flex items-center justify-center">
|
||
<video src={item.url} className="w-full h-full object-cover rounded-lg" />
|
||
<div className="absolute inset-0 flex items-center justify-center">
|
||
<div className="w-10 h-10 bg-black bg-opacity-50 rounded-full flex items-center justify-center">
|
||
<div className="w-0 h-0 border-t-8 border-t-transparent border-l-12 border-l-white border-b-8 border-b-transparent ml-1"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<button
|
||
onClick={() => removeMedia(item.id)}
|
||
className="absolute -top-2 -right-2 p-1 bg-red-600 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
|
||
>
|
||
<HiX className="w-4 h-4" />
|
||
</button>
|
||
<div className="absolute bottom-1 left-1 px-2 py-0.5 bg-black bg-opacity-70 text-white text-xs rounded">
|
||
{item.type === 'image' ? '📷' : '🎥'}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Footer */}
|
||
<div className="flex items-center justify-end gap-2 p-4 border-t border-gray-200 dark:border-gray-700">
|
||
<button
|
||
onClick={onClose}
|
||
className="px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
||
>
|
||
İptal
|
||
</button>
|
||
<button
|
||
onClick={onClose}
|
||
disabled={media.length === 0}
|
||
className="px-6 py-2 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
|
||
>
|
||
Tamam ({media.length})
|
||
</button>
|
||
</div>
|
||
</motion.div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default MediaManager
|