Skip to main content

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

  1. Clear Visual Feedback: Show when zones are active
  2. Appropriate Sizing: Make zones large enough for easy targeting
  3. Logical Grouping: Group related zones together
  4. Error Handling: Provide feedback for invalid drops
  5. Performance: Optimize for smooth animations

Next Steps