Examples
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