Drop Zones
Create interactive drop zones with visual feedback and smart collision detection.
Overview
Drop zones are designated areas where draggable items can be dropped. This example demonstrates:
- Multiple drop zones with different behaviors
- Visual feedback during drag operations
- Conditional drop acceptance
- Dynamic zone activation
Key Features
- Visual Feedback: Clear indicators when items can be dropped
- Smart Detection: Intelligent collision algorithms
- Conditional Drops: Accept or reject items based on rules
- Multiple Zones: Support for complex layouts
- Animated Responses: Smooth transitions and feedback
Basic Implementation
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { DropProvider, Draggable, Droppable } from 'react-native-reanimated-dnd';
interface DropItem {
id: string;
label: string;
type: 'image' | 'text' | 'video';
color: string;
}
export function DropZonesExample() {
const [droppedItems, setDroppedItems] = useState<{[key: string]: DropItem[]}>({
images: [],
text: [],
trash: []
});
const items: DropItem[] = [
{ id: '1', label: 'Photo', type: 'image', color: '#ff6b6b' },
{ id: '2', label: 'Document', type: 'text', color: '#4ecdc4' },
{ id: '3', label: 'Video', type: 'video', color: '#45b7d1' },
];
const handleDrop = (zoneId: string, acceptedTypes?: string[]) => (item: DropItem) => {
if (acceptedTypes && !acceptedTypes.includes(item.type)) {
console.log(`${item.type} not accepted in ${zoneId}`);
return;
}
setDroppedItems(prev => ({
...prev,
[zoneId]: [...prev[zoneId], item]
}));
};
return (
<GestureHandlerRootView style={styles.container}>
<DropProvider>
<View style={styles.content}>
<Text style={styles.title}>Drop Zones</Text>
{/* Draggable Items */}
<View style={styles.itemsContainer}>
{items.map((item) => (
<Draggable
key={item.id}
data={item}
style={styles.draggable}
>
<View style={[styles.draggableContent, { backgroundColor: item.color }]}>
<Text style={styles.draggableText}>{item.label}</Text>
<Text style={styles.typeText}>{item.type}</Text>
</View>
</Draggable>
))}
</View>
{/* Drop Zones */}
<View style={styles.dropZonesContainer}>
{/* Images Only Zone */}
<Droppable
droppableId="images"
onDrop={handleDrop('images', ['image'])}
style={styles.dropZone}
>
<View style={[styles.zoneContent, styles.imageZone]}>
<Text style={styles.zoneTitle}>📷 Images Only</Text>
<Text style={styles.zoneSubtitle}>
{droppedItems.images.length} items
</Text>
</View>
</Droppable>
{/* Text Only Zone */}
<Droppable
droppableId="text"
onDrop={handleDrop('text', ['text'])}
style={styles.dropZone}
>
<View style={[styles.zoneContent, styles.textZone]}>
<Text style={styles.zoneTitle}>📄 Text Only</Text>
<Text style={styles.zoneSubtitle}>
{droppedItems.text.length} items
</Text>
</View>
</Droppable>
{/* Trash Zone (accepts all) */}
<Droppable
droppableId="trash"
onDrop={handleDrop('trash')}
style={styles.dropZone}
>
<View style={[styles.zoneContent, styles.trashZone]}>
<Text style={styles.zoneTitle}>🗑️ Trash</Text>
<Text style={styles.zoneSubtitle}>
{droppedItems.trash.length} items
</Text>
</View>
</Droppable>
</View>
</View>
</DropProvider>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
},
content: {
flex: 1,
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#FFFFFF',
textAlign: 'center',
marginBottom: 30,
},
itemsContainer: {
flexDirection: 'row',
justifyContent: 'center',
marginBottom: 30,
gap: 16,
},
draggable: {
// Draggable positioning handled by library
},
draggableContent: {
width: 80,
height: 80,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 6,
elevation: 8,
},
draggableText: {
color: '#FFFFFF',
fontWeight: 'bold',
fontSize: 14,
},
typeText: {
color: 'rgba(255, 255, 255, 0.8)',
fontSize: 10,
marginTop: 2,
},
dropZonesContainer: {
flex: 1,
gap: 16,
},
dropZone: {
flex: 1,
minHeight: 100,
},
zoneContent: {
flex: 1,
borderRadius: 16,
borderWidth: 2,
borderStyle: 'dashed',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
imageZone: {
backgroundColor: 'rgba(255, 107, 107, 0.1)',
borderColor: '#ff6b6b',
},
textZone: {
backgroundColor: 'rgba(78, 205, 196, 0.1)',
borderColor: '#4ecdc4',
},
trashZone: {
backgroundColor: 'rgba(136, 136, 136, 0.1)',
borderColor: '#888888',
},
zoneTitle: {
color: '#FFFFFF',
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
},
zoneSubtitle: {
color: '#8E8E93',
fontSize: 14,
},
});
Advanced Drop Zone Features
Conditional Drop Acceptance
function ConditionalDropZone({ maxSize }) {
const [rejectionMessage, setRejectionMessage] = useState('');
return (
<View>
<Droppable
droppableId="conditional-zone"
onDrop={(item) => {
if (item.size > maxSize) {
setRejectionMessage('File too large');
setTimeout(() => setRejectionMessage(''), 3000);
return; // Don't process the item
}
// Process the valid drop
processDroppedItem(item);
setRejectionMessage('');
}}
>
<View style={styles.dropZone}>
<Text>Drop files here</Text>
<Text>Max size: {formatFileSize(maxSize)}</Text>
</View>
</Droppable>
{rejectionMessage && (
<Text style={styles.errorMessage}>{rejectionMessage}</Text>
)}
</View>
);
}
Animated Drop Feedback
function AnimatedDropZone({ children, onDrop }) {
const [isActive, setIsActive] = useState(false);
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
useEffect(() => {
scale.value = withSpring(isActive ? 1.05 : 1);
}, [isActive]);
return (
<Droppable
onDrop={onDrop}
onActiveChange={(active) => setIsActive(active)}
activeStyle={{
backgroundColor: 'rgba(88, 166, 255, 0.1)',
borderColor: '#58a6ff',
borderWidth: 2,
}}
>
<Animated.View style={[styles.dropZone, animatedStyle]}>
{children}
</Animated.View>
</Droppable>
);
}
Capacity-Limited Zones
function CapacityDropZone({ maxItems = 3, items = [] }) {
const isFull = items.length >= maxItems;
return (
<Droppable
droppableId="capacity-zone"
disabled={isFull}
onDrop={isFull ? undefined : handleDrop}
style={[styles.dropZone, isFull && styles.fullZone]}
>
<Text style={styles.capacityText}>
{items.length}/{maxItems} items
</Text>
{isFull && <Text style={styles.fullText}>Zone Full</Text>}
</Droppable>
);
}
Zone Types
File Upload Zone
function FileUploadZone() {
return (
<Droppable
droppableId="upload"
acceptedTypes={['image', 'document']}
onDrop={(file) => uploadFile(file)}
>
<View style={styles.uploadZone}>
<Text style={styles.uploadIcon}>📁</Text>
<Text style={styles.uploadText}>Drop files to upload</Text>
</View>
</Droppable>
);
}
Shopping Cart Zone
function ShoppingCartZone({ items, onAddItem }) {
return (
<Droppable
droppableId="cart"
onDrop={onAddItem}
>
<View style={styles.cartZone}>
<Text style={styles.cartIcon}>🛒</Text>
<Text style={styles.cartText}>
Cart ({items.length} items)
</Text>
</View>
</Droppable>
);
}
Category Zones
function CategoryZones({ categories }) {
return (
<View style={styles.categoriesContainer}>
{categories.map((category) => (
<Droppable
key={category.id}
droppableId={category.id}
onDrop={(item) => categorizeItem(item, category)}
>
<View style={[styles.categoryZone, { borderColor: category.color }]}>
<Text style={styles.categoryTitle}>{category.name}</Text>
<Text style={styles.categoryCount}>
{category.items.length} items
</Text>
</View>
</Droppable>
))}
</View>
);
}
Visual States
Hover Effects
const [isHovered, setIsHovered] = useState(false);
<Droppable
onDragEnter={() => setIsHovered(true)}
onDragLeave={() => setIsHovered(false)}
style={[
styles.dropZone,
isHovered && styles.hoveredZone
]}
>
{/* Zone content */}
</Droppable>
Loading States
function LoadingDropZone({ isProcessing }) {
return (
<Droppable disabled={isProcessing}>
<View style={[styles.dropZone, isProcessing && styles.processingZone]}>
{isProcessing ? (
<ActivityIndicator color="#58a6ff" />
) : (
<Text>Drop items here</Text>
)}
</View>
</Droppable>
);
}
Common Use Cases
- File Management: Organize files into folders
- Task Management: Move tasks between status columns
- E-commerce: Add items to cart or wishlist
- Content Creation: Arrange elements in layouts
- Data Visualization: Group data points by categories
Best Practices
- Clear Visual Feedback: Show when zones are active
- Appropriate Sizing: Make zones large enough for easy targeting
- Logical Grouping: Group related zones together
- Error Handling: Provide feedback for invalid drops
- Performance: Optimize for smooth animations
Next Steps
- Learn about Collision Detection for precise targeting
- Explore Custom Animations for enhanced feedback
- Check out Visual Feedback for better user experience