Несколько загрузчиков
Используйте несколько независимых экземпляров загрузки на одной странице.
Отдельные зоны загрузки
import { useDropup } from '@samithahansaka/dropup';
function MultipleUploadZones() {
// Profile picture uploader
const profilePic = useDropup({
accept: 'image/*',
maxFiles: 1,
multiple: false,
upload: { url: '/api/upload/profile' },
});
// Cover photo uploader
const coverPhoto = useDropup({
accept: 'image/*',
maxFiles: 1,
multiple: false,
maxWidth: 1920,
maxHeight: 1080,
upload: { url: '/api/upload/cover' },
});
// Documents uploader
const documents = useDropup({
accept: '.pdf,.doc,.docx',
maxFiles: 10,
maxSize: 10 * 1024 * 1024,
upload: { url: '/api/upload/documents' },
});
return (
<div style={styles.container}>
{/* Profile Picture */}
<div style={styles.section}>
<h3>Фото профиля</h3>
<div
{...profilePic.getDropProps()}
style={{
...styles.dropzone,
...styles.profileZone,
}}
>
<input {...profilePic.getInputProps()} />
{profilePic.files[0]?.preview ? (
<img
src={profilePic.files[0].preview}
alt="Профиль"
style={styles.profilePreview}
/>
) : (
<span>Перетащите фото профиля</span>
)}
</div>
</div>
{/* Cover Photo */}
<div style={styles.section}>
<h3>Обложка</h3>
<div
{...coverPhoto.getDropProps()}
style={{
...styles.dropzone,
...styles.coverZone,
}}
>
<input {...coverPhoto.getInputProps()} />
{coverPhoto.files[0]?.preview ? (
<img
src={coverPhoto.files[0].preview}
alt="Обложка"
style={styles.coverPreview}
/>
) : (
<span>Перетащите обложку (макс. 1920x1080)</span>
)}
</div>
</div>
{/* Documents */}
<div style={styles.section}>
<h3>Документы</h3>
<div
{...documents.getDropProps()}
style={styles.dropzone}
>
<input {...documents.getInputProps()} />
<span>Перетащите PDF или документы Word (макс. 10)</span>
</div>
{documents.files.length > 0 && (
<ul style={styles.fileList}>
{documents.files.map(file => (
<li key={file.id}>
{file.name}
<button onClick={() => documents.actions.remove(file.id)}>×</button>
</li>
))}
</ul>
)}
</div>
{/* Upload All Button */}
<button
onClick={() => {
profilePic.actions.upload();
coverPhoto.actions.upload();
documents.actions.upload();
}}
disabled={
profilePic.state.isUploading ||
coverPhoto.state.isUploading ||
documents.state.isUploading
}
style={styles.uploadButton}
>
Загрузить все
</button>
</div>
);
}
const styles = {
container: {
maxWidth: 600,
margin: '0 auto',
},
section: {
marginBottom: 30,
},
dropzone: {
border: '2px dashed #ccc',
borderRadius: 8,
padding: 20,
textAlign: 'center' as const,
cursor: 'pointer',
},
profileZone: {
width: 150,
height: 150,
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
},
profilePreview: {
width: '100%',
height: '100%',
objectFit: 'cover' as const,
},
coverZone: {
height: 200,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
},
coverPreview: {
width: '100%',
height: '100%',
objectFit: 'cover' as const,
},
fileList: {
listStyle: 'none',
padding: 0,
marginTop: 10,
},
uploadButton: {
width: '100%',
padding: 16,
fontSize: 16,
backgroundColor: '#2196f3',
color: 'white',
border: 'none',
borderRadius: 8,
cursor: 'pointer',
},
};
Категоризированные загрузки
import { useDropup } from '@samithahansaka/dropup';
import { useState } from 'react';
type Category = 'images' | 'videos' | 'documents';
function CategorizedUploader() {
const [activeCategory, setActiveCategory] = useState<Category>('images');
const uploaders = {
images: useDropup({
accept: 'image/*',
upload: { url: '/api/upload/images' },
}),
videos: useDropup({
accept: 'video/*',
maxSize: 100 * 1024 * 1024, // 100MB
upload: { url: '/api/upload/videos' },
}),
documents: useDropup({
accept: '.pdf,.doc,.docx,.xls,.xlsx',
upload: { url: '/api/upload/documents' },
}),
};
const current = uploaders[activeCategory];
const getCounts = () => ({
images: uploaders.images.files.length,
videos: uploaders.videos.files.length,
documents: uploaders.documents.files.length,
});
const counts = getCounts();
return (
<div style={styles.container}>
{/* Category Tabs */}
<div style={styles.tabs}>
{(['images', 'videos', 'documents'] as Category[]).map(cat => (
<button
key={cat}
onClick={() => setActiveCategory(cat)}
style={{
...styles.tab,
...(activeCategory === cat ? styles.activeTab : {}),
}}
>
{cat === 'images' ? 'Изображения' : cat === 'videos' ? 'Видео' : 'Документы'}
{counts[cat] > 0 && (
<span style={styles.badge}>{counts[cat]}</span>
)}
</button>
))}
</div>
{/* Drop Zone */}
<div
{...current.getDropProps()}
style={{
...styles.dropzone,
borderColor: current.state.isDragging ? '#2196f3' : '#ccc',
}}
>
<input {...current.getInputProps()} />
<p>Перетащите {activeCategory === 'images' ? 'изображения' : activeCategory === 'videos' ? 'видео' : 'документы'} сюда</p>
</div>
{/* File List */}
<div style={styles.fileList}>
{current.files.map(file => (
<div key={file.id} style={styles.fileItem}>
{file.preview && (
<img src={file.preview} alt="" style={styles.thumb} />
)}
<span style={styles.fileName}>{file.name}</span>
<span style={styles.status}>{file.status}</span>
<button onClick={() => current.actions.remove(file.id)}>×</button>
</div>
))}
</div>
{/* Actions */}
<div style={styles.actions}>
<button
onClick={() => current.actions.upload()}
disabled={current.state.isUploading || current.files.length === 0}
>
Загрузить {activeCategory === 'images' ? 'изображения' : activeCategory === 'videos' ? 'видео' : 'документы'}
</button>
<button
onClick={() => {
Object.values(uploaders).forEach(u => u.actions.upload());
}}
>
Загрузить все категории
</button>
</div>
</div>
);
}
const styles = {
container: {
maxWidth: 600,
margin: '0 auto',
},
tabs: {
display: 'flex',
gap: 8,
marginBottom: 20,
},
tab: {
padding: '10px 20px',
border: '1px solid #ccc',
backgroundColor: 'white',
cursor: 'pointer',
borderRadius: 8,
display: 'flex',
alignItems: 'center',
gap: 8,
},
activeTab: {
backgroundColor: '#2196f3',
color: 'white',
borderColor: '#2196f3',
},
badge: {
backgroundColor: 'rgba(0,0,0,0.2)',
padding: '2px 8px',
borderRadius: 10,
fontSize: 12,
},
dropzone: {
border: '2px dashed',
borderRadius: 8,
padding: 40,
textAlign: 'center' as const,
marginBottom: 20,
},
fileList: {
marginBottom: 20,
},
fileItem: {
display: 'flex',
alignItems: 'center',
gap: 12,
padding: 8,
borderBottom: '1px solid #eee',
},
thumb: {
width: 40,
height: 40,
objectFit: 'cover' as const,
borderRadius: 4,
},
fileName: {
flex: 1,
},
status: {
color: '#666',
fontSize: 12,
},
actions: {
display: 'flex',
gap: 12,
},
};
Форма с несколькими загрузками
import { useDropup } from '@samithahansaka/dropup';
import { useState } from 'react';
function ProductForm() {
const [formData, setFormData] = useState({
name: '',
description: '',
price: '',
});
const mainImage = useDropup({
accept: 'image/*',
maxFiles: 1,
multiple: false,
});
const gallery = useDropup({
accept: 'image/*',
maxFiles: 5,
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Upload images first
await Promise.all([
mainImage.actions.upload(),
gallery.actions.upload(),
]);
// Then submit form with uploaded URLs
const productData = {
...formData,
mainImage: mainImage.files[0]?.uploadedUrl,
gallery: gallery.files.map(f => f.uploadedUrl).filter(Boolean),
};
await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(productData),
});
};
return (
<form onSubmit={handleSubmit} style={styles.form}>
<h2>Добавить товар</h2>
{/* Text Fields */}
<div style={styles.field}>
<label>Название товара</label>
<input
type="text"
value={formData.name}
onChange={e => setFormData(d => ({ ...d, name: e.target.value }))}
style={styles.input}
/>
</div>
<div style={styles.field}>
<label>Описание</label>
<textarea
value={formData.description}
onChange={e => setFormData(d => ({ ...d, description: e.target.value }))}
style={styles.textarea}
/>
</div>
<div style={styles.field}>
<label>Цена</label>
<input
type="number"
value={formData.price}
onChange={e => setFormData(d => ({ ...d, price: e.target.value }))}
style={styles.input}
/>
</div>
{/* Main Image */}
<div style={styles.field}>
<label>Основное изображение</label>
<div
{...mainImage.getDropProps()}
style={styles.imageUpload}
>
<input {...mainImage.getInputProps()} />
{mainImage.files[0]?.preview ? (
<img
src={mainImage.files[0].preview}
alt="Основное"
style={styles.previewImage}
/>
) : (
<span>Перетащите основное изображение товара</span>
)}
</div>
</div>
{/* Gallery */}
<div style={styles.field}>
<label>Галерея изображений (макс. 5)</label>
<div
{...gallery.getDropProps()}
style={styles.galleryUpload}
>
<input {...gallery.getInputProps()} />
<span>Перетащите изображения для галереи</span>
</div>
{gallery.files.length > 0 && (
<div style={styles.galleryPreview}>
{gallery.files.map(file => (
<div key={file.id} style={styles.galleryItem}>
{file.preview && (
<img src={file.preview} alt="" style={styles.galleryThumb} />
)}
<button
type="button"
onClick={() => gallery.actions.remove(file.id)}
style={styles.removeBtn}
>
×
</button>
</div>
))}
</div>
)}
</div>
<button
type="submit"
disabled={
mainImage.state.isUploading ||
gallery.state.isUploading
}
style={styles.submitBtn}
>
{mainImage.state.isUploading || gallery.state.isUploading
? 'Загрузка...'
: 'Добавить товар'}
</button>
</form>
);
}
const styles = {
form: {
maxWidth: 500,
margin: '0 auto',
},
field: {
marginBottom: 20,
},
input: {
width: '100%',
padding: 10,
border: '1px solid #ccc',
borderRadius: 4,
},
textarea: {
width: '100%',
padding: 10,
border: '1px solid #ccc',
borderRadius: 4,
minHeight: 100,
},
imageUpload: {
border: '2px dashed #ccc',
borderRadius: 8,
padding: 20,
textAlign: 'center' as const,
height: 200,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
overflow: 'hidden',
},
previewImage: {
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain' as const,
},
galleryUpload: {
border: '2px dashed #ccc',
borderRadius: 8,
padding: 20,
textAlign: 'center' as const,
},
galleryPreview: {
display: 'flex',
gap: 8,
marginTop: 12,
flexWrap: 'wrap' as const,
},
galleryItem: {
position: 'relative' as const,
},
galleryThumb: {
width: 80,
height: 80,
objectFit: 'cover' as const,
borderRadius: 4,
},
removeBtn: {
position: 'absolute' as const,
top: -8,
right: -8,
width: 24,
height: 24,
border: 'none',
borderRadius: '50%',
backgroundColor: '#f44336',
color: 'white',
cursor: 'pointer',
},
submitBtn: {
width: '100%',
padding: 16,
backgroundColor: '#2196f3',
color: 'white',
border: 'none',
borderRadius: 8,
fontSize: 16,
cursor: 'pointer',
},
};
Общее состояние между загрузчиками
import { useDropup } from '@samithahansaka/dropup';
import { useMemo } from 'react';
function SharedStateUploaders() {
const uploader1 = useDropup({ accept: 'image/*' });
const uploader2 = useDropup({ accept: 'video/*' });
const uploader3 = useDropup({ accept: '.pdf' });
// Combine stats from all uploaders
const combinedStats = useMemo(() => {
const allFiles = [
...uploader1.files,
...uploader2.files,
...uploader3.files,
];
return {
totalFiles: allFiles.length,
totalSize: allFiles.reduce((sum, f) => sum + f.size, 0),
uploading: allFiles.filter(f => f.status === 'uploading').length,
complete: allFiles.filter(f => f.status === 'complete').length,
isAnyUploading:
uploader1.state.isUploading ||
uploader2.state.isUploading ||
uploader3.state.isUploading,
};
}, [uploader1.files, uploader2.files, uploader3.files]);
return (
<div>
{/* Stats Banner */}
<div style={styles.stats}>
<span>Всего: {combinedStats.totalFiles} файлов</span>
<span>Размер: {(combinedStats.totalSize / 1024 / 1024).toFixed(1)} МБ</span>
<span>Загружается: {combinedStats.uploading}</span>
<span>Завершено: {combinedStats.complete}</span>
</div>
{/* Upload Zones */}
<div style={styles.zones}>
<UploadZone
label="Изображения"
uploader={uploader1}
icon="🖼️"
/>
<UploadZone
label="Видео"
uploader={uploader2}
icon="🎬"
/>
<UploadZone
label="PDF"
uploader={uploader3}
icon="📄"
/>
</div>
{/* Global Upload Button */}
<button
onClick={() => {
uploader1.actions.upload();
uploader2.actions.upload();
uploader3.actions.upload();
}}
disabled={combinedStats.isAnyUploading}
style={styles.globalButton}
>
Загрузить все ({combinedStats.totalFiles} файлов)
</button>
</div>
);
}
function UploadZone({
label,
uploader,
icon,
}: {
label: string;
uploader: ReturnType<typeof useDropup>;
icon: string;
}) {
return (
<div style={styles.zone}>
<div {...uploader.getDropProps()} style={styles.dropzone}>
<input {...uploader.getInputProps()} />
<span style={styles.icon}>{icon}</span>
<span>{label}</span>
<span style={styles.count}>{uploader.files.length}</span>
</div>
</div>
);
}
const styles = {
stats: {
display: 'flex',
gap: 20,
padding: 16,
backgroundColor: '#f5f5f5',
borderRadius: 8,
marginBottom: 20,
},
zones: {
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: 16,
marginBottom: 20,
},
zone: {
flex: 1,
},
dropzone: {
border: '2px dashed #ccc',
borderRadius: 8,
padding: 30,
textAlign: 'center' as const,
display: 'flex',
flexDirection: 'column' as const,
alignItems: 'center',
gap: 8,
},
icon: {
fontSize: 32,
},
count: {
backgroundColor: '#2196f3',
color: 'white',
padding: '2px 8px',
borderRadius: 10,
fontSize: 12,
},
globalButton: {
width: '100%',
padding: 16,
fontSize: 16,
backgroundColor: '#4caf50',
color: 'white',
border: 'none',
borderRadius: 8,
cursor: 'pointer',
},
};