Zonas de Soltar Personalizadas
Crie zonas de soltar bonitas e interativas com estilo personalizado.
Zona de Soltar com Estilo Básico
import { useDropup } from '@samithahansaka/dropup';
function StyledDropZone() {
const { state, getDropProps, getInputProps } = useDropup();
return (
<div
{...getDropProps()}
style={{
border: '2px dashed',
borderColor: state.isDragging ? '#2196f3' : '#ccc',
borderRadius: 12,
padding: 60,
textAlign: 'center',
backgroundColor: state.isDragging ? '#e3f2fd' : '#fafafa',
transition: 'all 0.2s ease',
cursor: 'pointer',
}}
>
<input {...getInputProps()} />
<div style={{ fontSize: 48, marginBottom: 16 }}>
{state.isDragging ? '📂' : '📁'}
</div>
<p style={{ margin: 0, color: '#666' }}>
{state.isDragging
? 'Solte os arquivos aqui...'
: 'Arraste arquivos aqui ou clique para procurar'}
</p>
</div>
);
}
Feedback de Aceitar/Rejeitar
import { useDropup } from '@samithahansaka/dropup';
function ValidationFeedbackZone() {
const { state, getDropProps, getInputProps } = useDropup({
accept: 'image/*',
maxSize: 5 * 1024 * 1024, // 5MB
});
const getZoneStyle = () => {
if (state.isDragAccept) {
return {
borderColor: '#4caf50',
backgroundColor: '#e8f5e9',
color: '#2e7d32',
};
}
if (state.isDragReject) {
return {
borderColor: '#f44336',
backgroundColor: '#ffebee',
color: '#c62828',
};
}
return {
borderColor: '#9e9e9e',
backgroundColor: '#fafafa',
color: '#666',
};
};
const getMessage = () => {
if (state.isDragAccept) return '✓ Solte para enviar';
if (state.isDragReject) return '✕ Tipo de arquivo inválido';
return 'Solte imagens aqui (máx 5MB)';
};
return (
<div
{...getDropProps()}
style={{
border: '3px dashed',
borderRadius: 16,
padding: 80,
textAlign: 'center',
transition: 'all 0.2s',
...getZoneStyle(),
}}
>
<input {...getInputProps()} />
<p style={{ fontSize: 18, fontWeight: 500 }}>
{getMessage()}
</p>
</div>
);
}
Zona de Soltar Animada
import { useDropup } from '@samithahansaka/dropup';
import { CSSProperties } from 'react';
function AnimatedDropZone() {
const { state, files, getDropProps, getInputProps } = useDropup();
const pulseAnimation: CSSProperties = state.isDragging
? { animation: 'pulse 1s infinite' }
: {};
return (
<>
<style>
{`
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
`}
</style>
<div
{...getDropProps()}
style={{
border: '2px dashed #2196f3',
borderRadius: 20,
padding: 60,
textAlign: 'center',
backgroundColor: state.isDragging ? '#e3f2fd' : 'white',
transition: 'background-color 0.3s',
...pulseAnimation,
}}
>
<input {...getInputProps()} />
<div
style={{
fontSize: 64,
transform: state.isDragging ? 'scale(1.2)' : 'scale(1)',
transition: 'transform 0.3s',
}}
>
{state.isDragging ? '📥' : '📤'}
</div>
<h3>
{state.isDragging ? 'Solte para enviar' : 'Arraste e solte arquivos'}
</h3>
{files.length > 0 && (
<p style={{ color: '#666' }}>
{files.length} arquivo(s) selecionado(s)
</p>
)}
</div>
</>
);
}
Zona de Soltar de Página Inteira
import { useDropup } from '@samithahansaka/dropup';
import { useState, useEffect } from 'react';
function FullPageDropZone({ children }: { children: React.ReactNode }) {
const [isPageDrag, setIsPageDrag] = useState(false);
const { files, getDropProps, getInputProps } = useDropup();
useEffect(() => {
let dragCounter = 0;
const handleDragEnter = () => {
dragCounter++;
setIsPageDrag(true);
};
const handleDragLeave = () => {
dragCounter--;
if (dragCounter === 0) {
setIsPageDrag(false);
}
};
const handleDrop = () => {
dragCounter = 0;
setIsPageDrag(false);
};
document.addEventListener('dragenter', handleDragEnter);
document.addEventListener('dragleave', handleDragLeave);
document.addEventListener('drop', handleDrop);
return () => {
document.removeEventListener('dragenter', handleDragEnter);
document.removeEventListener('dragleave', handleDragLeave);
document.removeEventListener('drop', handleDrop);
};
}, []);
return (
<div style={{ position: 'relative', minHeight: '100vh' }}>
{children}
{/* Overlay de página inteira ao arrastar */}
{isPageDrag && (
<div
{...getDropProps()}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(33, 150, 243, 0.95)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
zIndex: 9999,
}}
>
<input {...getInputProps()} />
<div style={{ fontSize: 100 }}>📂</div>
<h1 style={{ color: 'white', marginTop: 20 }}>
Solte arquivos em qualquer lugar
</h1>
</div>
)}
</div>
);
}
// Uso
function App() {
return (
<FullPageDropZone>
<div>Conteúdo do seu app aqui</div>
</FullPageDropZone>
);
}
Zona de Soltar Estilo Cartão
import { useDropup } from '@samithahansaka/dropup';
function CardDropZone() {
const { files, state, actions, getDropProps, getInputProps } = useDropup({
upload: { url: '/api/upload' },
});
return (
<div style={styles.card}>
<div
{...getDropProps()}
style={{
...styles.dropArea,
borderColor: state.isDragging ? '#2196f3' : 'transparent',
backgroundColor: state.isDragging ? '#e3f2fd' : '#f5f5f5',
}}
>
<input {...getInputProps()} />
<div style={styles.iconContainer}>
<span style={styles.icon}>☁️</span>
</div>
<h3 style={styles.title}>Enviar Arquivos</h3>
<p style={styles.subtitle}>
Arraste e solte ou{' '}
<span style={styles.link}>procure</span>
</p>
</div>
{files.length > 0 && (
<div style={styles.fileList}>
{files.map(file => (
<div key={file.id} style={styles.fileItem}>
<span>{file.name}</span>
<button
onClick={() => actions.remove(file.id)}
style={styles.removeBtn}
>
×
</button>
</div>
))}
</div>
)}
{files.length > 0 && (
<button
onClick={() => actions.upload()}
disabled={state.isUploading}
style={styles.uploadBtn}
>
{state.isUploading
? `Enviando ${state.progress}%`
: 'Enviar Todos'}
</button>
)}
</div>
);
}
const styles = {
card: {
backgroundColor: 'white',
borderRadius: 16,
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
overflow: 'hidden',
maxWidth: 400,
},
dropArea: {
padding: 40,
textAlign: 'center' as const,
cursor: 'pointer',
transition: 'all 0.2s',
border: '2px dashed transparent',
margin: 16,
borderRadius: 12,
},
iconContainer: {
width: 80,
height: 80,
borderRadius: '50%',
backgroundColor: '#e3f2fd',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 16px',
},
icon: {
fontSize: 40,
},
title: {
margin: '0 0 8px',
color: '#333',
},
subtitle: {
margin: 0,
color: '#666',
},
link: {
color: '#2196f3',
textDecoration: 'underline',
cursor: 'pointer',
},
fileList: {
padding: '0 16px',
maxHeight: 200,
overflow: 'auto',
},
fileItem: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '8px 12px',
backgroundColor: '#f5f5f5',
borderRadius: 8,
marginBottom: 8,
},
removeBtn: {
border: 'none',
background: 'none',
fontSize: 20,
cursor: 'pointer',
color: '#999',
},
uploadBtn: {
width: '100%',
padding: 16,
border: 'none',
backgroundColor: '#2196f3',
color: 'white',
fontSize: 16,
cursor: 'pointer',
marginTop: 16,
},
};
Múltiplas Zonas de Soltar
import { useDropup } from '@samithahansaka/dropup';
function MultipleDropZones() {
const images = useDropup({
accept: 'image/*',
upload: { url: '/api/upload/images' },
});
const documents = useDropup({
accept: '.pdf,.doc,.docx',
upload: { url: '/api/upload/documents' },
});
return (
<div style={{ display: 'flex', gap: 20 }}>
{/* Zona de Imagens */}
<div style={styles.zone}>
<div
{...images.getDropProps()}
style={{
...styles.dropArea,
borderColor: images.state.isDragging ? '#4caf50' : '#ccc',
}}
>
<input {...images.getInputProps()} />
<span style={{ fontSize: 40 }}>🖼️</span>
<p>Solte Imagens</p>
</div>
<p>{images.files.length} imagens</p>
</div>
{/* Zona de Documentos */}
<div style={styles.zone}>
<div
{...documents.getDropProps()}
style={{
...styles.dropArea,
borderColor: documents.state.isDragging ? '#2196f3' : '#ccc',
}}
>
<input {...documents.getInputProps()} />
<span style={{ fontSize: 40 }}>📄</span>
<p>Solte Documentos</p>
</div>
<p>{documents.files.length} documentos</p>
</div>
</div>
);
}
const styles = {
zone: {
flex: 1,
textAlign: 'center' as const,
},
dropArea: {
border: '2px dashed',
borderRadius: 12,
padding: 40,
cursor: 'pointer',
transition: 'border-color 0.2s',
},
};
Estado Desabilitado
import { useDropup } from '@samithahansaka/dropup';
function DisabledDropZone() {
const [isDisabled, setIsDisabled] = useState(false);
const { files, getDropProps, getInputProps } = useDropup({
disabled: isDisabled,
});
return (
<div>
<label>
<input
type="checkbox"
checked={isDisabled}
onChange={(e) => setIsDisabled(e.target.checked)}
/>
Desabilitar zona de soltar
</label>
<div
{...getDropProps()}
style={{
border: '2px dashed #ccc',
borderRadius: 8,
padding: 40,
textAlign: 'center',
opacity: isDisabled ? 0.5 : 1,
cursor: isDisabled ? 'not-allowed' : 'pointer',
backgroundColor: isDisabled ? '#f5f5f5' : 'white',
}}
>
<input {...getInputProps()} />
<p>
{isDisabled
? 'Zona de soltar desabilitada'
: 'Solte arquivos aqui'}
</p>
</div>
</div>
);
}