244 lines
9.7 KiB
TypeScript
244 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
|