Spaces:
Running
Running
| 'use client' | |
| import React, { useState, useRef } from 'react' | |
| import { motion, AnimatePresence } from 'framer-motion' | |
| import { X, Upload, Image, Check } from '@phosphor-icons/react' | |
| interface BackgroundSelectorProps { | |
| isOpen: boolean | |
| onClose: () => void | |
| onSelectBackground: (background: string | File) => void | |
| currentBackground: string | |
| } | |
| const presetBackgrounds = [ | |
| { | |
| id: 'gradient-purple', | |
| name: 'Ubuntu Purple', | |
| style: 'linear-gradient(135deg, #77216F 0%, #5E2750 50%, #2C001E 100%)' | |
| }, | |
| { | |
| id: 'gradient-blue', | |
| name: 'Ocean Blue', | |
| style: 'linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e8ba3 100%)' | |
| }, | |
| { | |
| id: 'gradient-green', | |
| name: 'Forest Green', | |
| style: 'linear-gradient(135deg, #134e5e 0%, #71b280 50%, #a8e063 100%)' | |
| }, | |
| { | |
| id: 'gradient-orange', | |
| name: 'Sunset Orange', | |
| style: 'linear-gradient(135deg, #ff512f 0%, #dd2476 50%, #f09819 100%)' | |
| }, | |
| { | |
| id: 'gradient-dark', | |
| name: 'Dark Mode', | |
| style: 'linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 50%, #2d2d2d 100%)' | |
| }, | |
| { | |
| id: 'gradient-cosmic', | |
| name: 'Cosmic', | |
| style: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)' | |
| } | |
| ] | |
| export function BackgroundSelector({ | |
| isOpen, | |
| onClose, | |
| onSelectBackground, | |
| currentBackground | |
| }: BackgroundSelectorProps) { | |
| const [selectedTab, setSelectedTab] = useState<'presets' | 'upload'>('presets') | |
| const [uploadedImage, setUploadedImage] = useState<string | null>(null) | |
| const fileInputRef = useRef<HTMLInputElement>(null) | |
| const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => { | |
| const file = event.target.files?.[0] | |
| if (file) { | |
| const reader = new FileReader() | |
| reader.onload = (e) => { | |
| const result = e.target?.result as string | |
| setUploadedImage(result) | |
| onSelectBackground(file) | |
| } | |
| reader.readAsDataURL(file) | |
| } | |
| } | |
| return ( | |
| <AnimatePresence> | |
| {isOpen && ( | |
| <> | |
| {/* Backdrop */} | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| exit={{ opacity: 0 }} | |
| onClick={onClose} | |
| className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50" | |
| /> | |
| {/* Modal */} | |
| <motion.div | |
| initial={{ scale: 0.9, opacity: 0 }} | |
| animate={{ scale: 1, opacity: 1 }} | |
| exit={{ scale: 0.9, opacity: 0 }} | |
| transition={{ type: "spring", damping: 20, stiffness: 300 }} | |
| className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[90%] max-w-2xl bg-[#2C2C2C]/95 backdrop-blur-md rounded-xl shadow-2xl z-50 border border-white/10" | |
| > | |
| {/* Header */} | |
| <div className="flex items-center justify-between p-6 border-b border-white/10"> | |
| <h2 className="text-xl font-semibold text-white">Change Desktop Background</h2> | |
| <button | |
| onClick={onClose} | |
| className="text-gray-400 hover:text-white transition-colors p-1 hover:bg-white/10 rounded-lg" | |
| > | |
| <X size={20} /> | |
| </button> | |
| </div> | |
| {/* Tabs */} | |
| <div className="flex border-b border-white/10"> | |
| <button | |
| onClick={() => setSelectedTab('presets')} | |
| className={`flex-1 px-4 py-3 text-sm font-medium transition-colors ${ | |
| selectedTab === 'presets' | |
| ? 'text-white border-b-2 border-blue-500' | |
| : 'text-gray-400 hover:text-white' | |
| }`} | |
| > | |
| Gallery | |
| </button> | |
| <button | |
| onClick={() => setSelectedTab('upload')} | |
| className={`flex-1 px-4 py-3 text-sm font-medium transition-colors ${ | |
| selectedTab === 'upload' | |
| ? 'text-white border-b-2 border-blue-500' | |
| : 'text-gray-400 hover:text-white' | |
| }`} | |
| > | |
| Upload Image | |
| </button> | |
| </div> | |
| {/* Content */} | |
| <div className="p-6"> | |
| {selectedTab === 'presets' ? ( | |
| <div className="grid grid-cols-3 gap-4"> | |
| {presetBackgrounds.map((bg) => ( | |
| <button | |
| key={bg.id} | |
| onClick={() => onSelectBackground(bg.id)} | |
| className={`relative aspect-video rounded-lg overflow-hidden border-2 transition-all ${ | |
| currentBackground === bg.id | |
| ? 'border-blue-500 scale-105' | |
| : 'border-white/20 hover:border-white/40' | |
| }`} | |
| > | |
| <div | |
| className="w-full h-full" | |
| style={{ background: bg.style }} | |
| /> | |
| {currentBackground === bg.id && ( | |
| <div className="absolute top-2 right-2 w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center"> | |
| <Check size={14} weight="bold" className="text-white" /> | |
| </div> | |
| )} | |
| <div className="absolute bottom-0 left-0 right-0 bg-black/50 backdrop-blur-sm px-2 py-1"> | |
| <span className="text-xs text-white">{bg.name}</span> | |
| </div> | |
| </button> | |
| ))} | |
| </div> | |
| ) : ( | |
| <div className="flex flex-col items-center justify-center py-12"> | |
| <input | |
| ref={fileInputRef} | |
| type="file" | |
| accept="image/*" | |
| onChange={handleFileUpload} | |
| className="hidden" | |
| /> | |
| {uploadedImage ? ( | |
| <div className="relative w-full max-w-md"> | |
| <img | |
| src={uploadedImage} | |
| alt="Uploaded background" | |
| className="w-full h-48 object-cover rounded-lg" | |
| /> | |
| <button | |
| onClick={() => fileInputRef.current?.click()} | |
| className="mt-4 w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition-colors" | |
| > | |
| Choose Different Image | |
| </button> | |
| </div> | |
| ) : ( | |
| <> | |
| <div | |
| onClick={() => fileInputRef.current?.click()} | |
| className="w-32 h-32 border-2 border-dashed border-white/30 rounded-lg flex flex-col items-center justify-center cursor-pointer hover:border-white/50 transition-colors" | |
| > | |
| <Upload size={32} className="text-gray-400 mb-2" /> | |
| <span className="text-sm text-gray-400">Click to upload</span> | |
| </div> | |
| <p className="mt-4 text-sm text-gray-400"> | |
| Supported formats: JPG, PNG, WEBP, GIF | |
| </p> | |
| </> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| {/* Footer */} | |
| <div className="p-6 pt-0 flex gap-3 justify-end"> | |
| <button | |
| onClick={onClose} | |
| className="px-4 py-2 text-gray-300 hover:text-white transition-colors" | |
| > | |
| Cancel | |
| </button> | |
| <button | |
| onClick={onClose} | |
| className="px-4 py-2 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-lg font-medium hover:from-blue-600 hover:to-purple-600 transition-all" | |
| > | |
| Apply | |
| </button> | |
| </div> | |
| </motion.div> | |
| </> | |
| )} | |
| </AnimatePresence> | |
| ) | |
| } |