MultipleDraggables
MultipleDraggables es un componente de alto nivel que construye una grilla de Draggable a partir de un arreglo de datos.
Resuelve el caso típico de Explorax:
- Tengo un banco de opciones (letras, números, palabras, íconos, etc.).
- Quiero mostrarlas en filas bien distribuidas.
- Quiero que todas tengan la misma configuración de drag (reset, desaparecer, animaciones, etc.).
- Quiero recibir en el render prop el índice global (
idx) y eldistancecalculado por el sistema DnD.
Internamente, simplemente mapea cada item a un <Draggable /> y se encarga de:
- Partir el arreglo de datos en filas balanceadas.
- Calcular
idxglobal (0, 1, 2, …) independientemente de la fila. - Pasar flags a
Draggable(resetAfterDrop,disappearAfterDrop, etc.). - Aplicar una animación de escala opcional mientras se hace drag.
- Renderizar una imagen de arrastre opcional sobre el ítem.
Firma
interface ChildrenProps<T> {
isDragging: boolean;
data: T;
idx: number;
distance: number;
}
interface MultipleDraggablesProps<T> {
data: T[];
children: (info: ChildrenProps<T>) => React.ReactNode;
resetAfterDrop?: boolean;
disableIfAllFilled?: boolean;
disappearAfterDrop?: boolean;
shakeItAnimation?: boolean;
blackHoleAnimation?: boolean;
styleContainer?: StyleProp<ViewStyle>;
workspaceDimensions: {
width: number;
height: number;
};
numberRows?: number; // número de filas (por defecto 2)
gap?: number; // espacio horizontal entre elementos
rowStyle?: StyleProp<ViewStyle>;
disable?: boolean; // deshabilita TODOS los draggables
movedTimeout?: number; // delay base de animación (se pasa a Draggable.timeMove)
// Escala dinámica mientras se arrastra
enableScale?: boolean;
customTargetScale?: number;
customTransformScale?: number;
// Imagen que se muestra mientras se arrastra
dragImage?: ImageProps | { uri: string };
styleDragImageProp?: StyleProp<ImageStyle>;
}
const MultipleDraggables = <T,>(props: MultipleDraggablesProps<T>) => JSX.Element;
export default MultipleDraggables;
Props
Datos y render
| Prop | Tipo | Requerido | Descripción |
|---|---|---|---|
data | T[] | ✅ | Arreglo de elementos lógicos que se volverán Draggable. Cada item se pasa luego a children como data. |
children | (info: ChildrenProps<T>) => ReactNode | ✅ | Render prop para cada draggable. Recibe { isDragging, data, idx, distance }. |
ChildrenProps<T>:
data: el item original del arreglodata.isDragging:truemientras ese elemento específico está en drag.idx: índice global (0, 1, 2, …) dentro de todo el conjunto, no solo de la fila.distance: métrica de distancia al droppable más cercano, calculada por el sistema DnD.
Configuración de drag / estado
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
resetAfterDrop | boolean | true | Se pasa a cada Draggable. Si está en true, el elemento vuelve a su posición original después del drop. |
disableIfAllFilled | boolean | false | Se pasa a cada Draggable. Permite deshabilitar drags cuando todos los droppables relevantes están llenos (según la lógica del slice). |
disappearAfterDrop | boolean | false | Se pasa a cada Draggable. Permite que el elemento desaparezca tras un drop válido (según mode del DNDProvider). |
shakeItAnimation | boolean | false | Activa animaciones de “shake” en Draggable (feedback de error o rechazo). |
blackHoleAnimation | boolean | false | Activa animaciones estilo “agujero negro” en Draggable cuando desaparece. |
disable | boolean | false | Si true, se pasa disable a todos los Draggable (bloquea el banco de opciones completo). |
movedTimeout | number | 0 | Se pasa como timeMove a cada Draggable; controla el tiempo base de animación de movimiento/rebote. |
Layout y estilos
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
workspaceDimensions | { width: number; height: number } | ✅ | Dimensiones del espacio lógico de trabajo. height se usa para calcular gaps y el comportamiento de escala. |
numberRows | number | 2 | Número máximo de filas en la grilla. Si hay menos datos que filas, se usan menos filas. |
gap | number | height * 0.02 | Espacio horizontal entre elementos. Si no se pasa, se calcula en función de la altura del workspace. |
styleContainer | StyleProp<ViewStyle> | — | Estilo adicional para el contenedor de la grilla (envoltura vertical). |
rowStyle | StyleProp<ViewStyle> | — | Estilo adicional para cada fila (envoltura horizontal). |
Internamente:
-
El arreglo
datase divide enrowsCountfilas con distribución lo más uniforme posible:- Se calcula
basePerRowy un número de filas que llevan un extra (extra) para repartir los sobrantes.
- Se calcula
-
getStyles(gap, height)genera:container: columna, centrada, congapvertical ≈height * 0.018ymarginVertical≈height * 0.1.row: fila, centrada, congaphorizontal =gap(oheight * 0.02si no se pasó).
Escala dinámica y drag image
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
enableScale | boolean | undefined (off) | Si true, se activa una animación de escala basada en distance. |
customTargetScale | number | 0.65 | Escala mínima a la que puede llegar el elemento mientras se arrastra. |
customTransformScale | number | — | Si se define, la escala se separa en scaleX y scaleY (solo se estira/achica en alto según este factor). |
dragImage | ImageProps | { uri: string } | — | Imagen a mostrar solo cuando el ítem está en drag (isDragging). Puede ser require() o { uri }. |
styleDragImageProp | StyleProp<ImageStyle> | — | Estilo a aplicar a la imagen de drag. |
Lógica de escala (resumen):
-
Se crea un
scale = useSharedValue(1)por cada ítem. -
En un
useEffectdependiente dedistance+isDragging:-
Si
isDraggingy la distancia está en un rango entre ≈3%y25%de la altura del workspace:- Se calcula una escala lineal entre
originalScale = 1ytargetscale = customTargetScale ?? 0.65. - Entre más grande
distance, más se acerca la escala atargetscale.
- Se calcula una escala lineal entre
-
Si no se está arrastrando y la distancia es mayor a ≈
10%de la altura:- Se resetea la escala a
1.
- Se resetea la escala a
-
-
animatedStyleScaleelige entre:-
Escala uniforme:
transform: [{ scale: scale.value }]. -
Escala diferenciada (si
customTransformScaleyisDragging):scaleX = scale.valuescaleY = scale.value * customTransformScale
-
La imagen de drag se superpone así:
{isDragging && dragImage && (
<Image source={dragImage} style={styleDragImageProp} />
)}
Ejemplo básico: banco de números
import MultipleDraggables from "@components/DnDRevolution/MultipleDraggables";
import { Droppable, DNDProvider } from "@components/DnDRevolution";
import { View, Text } from "react-native";
type Option = { id: number; label: string };
const options: Option[] = Array.from({ length: 10 }, (_, i) => ({
id: i,
label: `${i}`,
}));
export const NumbersBank = () => {
const workspaceDimensions = { width: 360, height: 640 };
return (
<DNDProvider<Option>
workSpaceDimensions={workspaceDimensions}
mode="remplacement-multi"
>
<Droppable<Option> id="target">
{({ isOver, data }) => (
<View
style={{
height: 80,
borderWidth: 2,
borderRadius: 12,
borderColor: isOver ? "green" : "gray",
alignItems: "center",
justifyContent: "center",
}}
>
<Text>{data?.label ?? "Suelta un número aquí"}</Text>
</View>
)}
</Droppable>
<MultipleDraggables<Option>
data={options}
workspaceDimensions={workspaceDimensions}
numberRows={2}
resetAfterDrop
enableScale
>
{({ isDragging, data, idx, distance }) => (
<View
style={{
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 8,
marginVertical: 4,
backgroundColor: isDragging ? "#4f46e5" : "#6366f1",
}}
>
<Text style={{ color: "white" }}>
{data.label} (#{idx})
</Text>
</View>
)}
</MultipleDraggables>
</DNDProvider>
);
};
Ejemplo con imagen de drag y tres filas
<MultipleDraggables<Option>
data={options}
workspaceDimensions={workspaceDimensions}
numberRows={3}
gap={12}
resetAfterDrop={false}
disappearAfterDrop
dragImage={require("@assets/drag-ghost.png")}
styleDragImageProp={{ width: 40, height: 40, position: "absolute" }}
enableScale
customTargetScale={0.7}
customTransformScale={1.2}
>
{({ isDragging, data }) => (
<View
style={{
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 10,
borderWidth: 1,
borderColor: isDragging ? "#22c55e" : "#e5e7eb",
backgroundColor: "white",
}}
>
<Text>{data.label}</Text>
</View>
)}
</MultipleDraggables>
Cómo se integra con el resto del sistema
-
Cada item de
MultipleDraggableses un<Draggable />configurado con los mismos flags. -
La lógica de colisión, selección de droppable, historial, etc. sigue siendo responsabilidad del slice del
DNDProvidery deDroppable. -
MultipleDraggablessolo resuelve:- Layout en filas.
- Reutilización de props de
Draggable. - Animación de escala y drag image para un conjunto de opciones.
Siguiente sección recomendada:
👉 PadDND para ver un pad de números/letras construido encima de MultipleDraggables.