Skip to main content

PadDND

PadDND es un componente de alto nivel que construye un “pad” de elementos arrastrables (tecladito) encima del sistema DnD.

Su objetivo es que no tengas que armar a mano 10 Draggable para números, o un montón de letras/opciones:
ya trae patrones listos para:

  1. Números 0–9 (más . opcional).
  2. Opciones personalizadas (options).
  3. Caracteres de una palabra (word).

Siempre genera Draggable<PadObject<T>> envueltos en FactoryLiteThemedContainer con estilos coherentes para Explorax.


Tipo base: PadObject<T>

El tipo de dato que viaja en cada draggable del pad:

export interface PadObject<T = any> {
/** Identificador lógico: puede ser único o varios índices esperados */
id: number | number[];
/** Texto que se muestra en el botón del pad */
text: string;
/** Payload extra opcional para tu caso de uso */
data?: T;
/** ID único opcional (útil para tracking) */
randomUniqueId?: string;
/** Valor “default” opcional (por ejemplo, posición esperada) */
default?: number;
}

En el DNDProvider normalmente usarás:

<DNDProvider<PadObject<MyExtraData>> ...>
<PadDND<MyExtraData> ... />
{/* PadDroppable / Droppable usan también PadObject<MyExtraData> */}
</DNDProvider>

API de PadDND

interface NumberDnDProps<T extends any> {
workspaceDimensions: {
width: number;
height: number;
};

// Modo de contenido
useNumbers?: boolean;
useDot?: boolean;
word?: string;
options?: PadObject<T>[];
useOptions?: boolean;

// Comportamiento / visual
disappearAfterDrop?: boolean; // (por ahora no se usa internamente)
showOrderIndicator?: boolean;
adaptContent?: boolean;
useRandomUniqueId?: boolean;
disable?: boolean;

// Estilos
contentStyle?: StyleProp<ViewStyle>;
containerStyle?: StyleProp<ViewStyle>;
fontSize?: number;

// Imagen durante el drag
dragImage?: ImageProps | { uri: string };
styleDragImageProp?: StyleProp<ImageStyle>;

// Layout avanzado
keepPositions?: boolean;
answers?: number;
}

const PadDND = <T,>(props: NumberDnDProps<T>) => JSX.Element;
export default PadDND;

Props principales

PropTipoDefaultDescripción
workspaceDimensions{ width: number; height: number }— (obligatorio)Dimensiones del espacio donde vive el pad; se usan para tamaños relativos.
useNumbersbooleanfalseSi es true, el pad muestra dígitos 0–9 (y opcionalmente .). Tiene prioridad sobre useOptions.
useDotbooleanfalseSi es true y useNumbers es true, agrega un draggable extra con ".".
wordstring""Palabra a fragmentar en caracteres. Solo se usa en modo “palabra” (cuando useNumbers y useOptions son false) y para los indicadores de orden en modo números.
optionsPadObject<T>[][]Opciones personalizadas para el modo “opciones”.
useOptionsbooleanfalseSi es true (y useNumbers es false), el pad muestra options en vez de números o caracteres de word.
showOrderIndicatorbooleanfalseActiva los circulitos/indicadores de orden de FactoryLiteThemedContainer (varía por modo, ver más abajo).
adaptContentbooleanfalseSolo en modo “opciones”: usa un tamaño de caja más “alargado” (styles.adaptContent) para textos largos.
useRandomUniqueIdbooleanfalseEn modo “palabra”: rellena randomUniqueId con UUID.v4() por carácter. Útil para tracking fino.
disablebooleanfalseDeshabilita todos los drags del pad.
contentStyleStyleProp<ViewStyle>undefinedEstilo adicional para el contenido dentro de cada FactoryLiteThemedContainer.
containerStyleStyleProp<ViewStyle>undefinedEstilo extra para el View que agrupa todos los draggables.
fontSizenumberheight * 0.05Tamaño base del texto dentro del pad.
dragImageImageProps | { uri: string }undefinedImagen opcional que se dibuja encima del botón cuando isDragging === true.
styleDragImagePropStyleProp<ImageStyle>undefinedEstilos para la imagen de drag (tamaño, posición absoluta, etc.).
keepPositionsbooleanfalseControla si se memoiza el orden (word/opciones) o se re-barajan en cada render.
answersnumberundefinedAjusta tamaños/gaps del layout según cuántos “slots de respuesta” tienes (si >= 4, usa cajas un poco más pequeñas).
disappearAfterDropbooleanfalseReservado para futuro: hoy no se pasa a los Draggable, la desaparición la controla más bien el mode en DNDProvider.

Modos de operación

PadDND tiene 3 modos mutuamente excluyentes, con esta prioridad:

  1. useNumbers === truemodo números.
  2. useOptions === true (y !useNumbers) → modo opciones.
  3. Caso contrario → modo palabra.

1. Modo números (useNumbers)

  • Genera 10 Draggable<PadObject> con:

    data={{
    id: i,
    text: `${i}`,
    randomUniqueId: useMemo(() => UUID.v4(), [i]),
    }}
  • Cada uno:

    • Tiene resetAfterDrop.

    • Respeta disable.

    • Se renderiza con FactoryLiteThemedContainer:

      <FactoryLiteThemedContainer
      order={
      showOrderIndicator
      ? word.split("").reduce(
      (acc, char, idx) =>
      char === data.text ? [...acc, idx + 1] : acc,
      [] as number[]
      )
      : []
      }
      showOrderIndicator
      workspaceDimensions={workspaceDimensions}
      type="text"
      containerStyle={{ justifyContent: "center", alignItems: "center" }}
      contentStyle={[styles.content, contentStyle]}
      reactive
      colorCombination="answerblue"
      text={data.text}
      textVariant="textBig"
      textStyle={{
      fontSize: fontSize ?? workspaceDimensions.height * 0.05,
      color: "white",
      fontFamily: "lato-bold",
      }}
      />
  • Indicadores de orden (si showOrderIndicator):

    • Muestran todas las posiciones donde ese dígito aparece en word. Ejemplo: word = "2024" → para el dígito "2" se mostrarían {1, 3}.
  • Punto decimal (useDot):

    • Si useDot es true, se agrega un draggable extra:

      data={{ id: 10, text: ".", randomUniqueId: "10" }}
    • Visualmente se comporta igual que un número más.

Nota: aunque el comentario habla de números “shuffled”, la implementación actual los muestra en orden 0,1,...,9. Si quieres mezclarlos, puedes barajar tu propio arreglo y usar PadDND en modo useOptions.


2. Modo opciones (useOptions)

Se activa si:

useOptions === true && useNumbers === false
  • Primero se calcula:

    const shuffledOptions =
    keepPositions
    ? useMemo(() => shuffleArray(options), [options])
    : shuffleArray(options);
    • keepPositions = true: solo se rebarajan cuando cambia el array options.
    • keepPositions = false: se rebarajan en cada render (comportamiento más aleatorio).
  • Luego:

    shuffledOptions.map((option, i) => (
    <Draggable<PadObject<T>>
    disable={disable}
    timeMove={0}
    data={option}
    resetAfterDrop
    >
    {({ data, isDragging }) => (
    <>
    <FactoryLiteThemedContainer
    order={showOrderIndicator ? [i + 1] : []}
    showOrderIndicator
    workspaceDimensions={workspaceDimensions}
    type="text"
    containerStyle={{ justifyContent: "center", alignItems: "center" }}
    contentStyle={[
    adaptContent ? styles.adaptContent : styles.content,
    contentStyle,
    ]}
    reactive
    colorCombination="answerblue"
    text={data.text}
    textVariant="textBig"
    textStyle={{
    fontSize: fontSize ?? workspaceDimensions.height * 0.05,
    lineHeight: workspaceDimensions.height * 0.06,
    color: "white",
    fontFamily: "lato-bold",
    }}
    borderRadius={workspaceDimensions.height * 0.02}
    />
    {isDragging && dragImage && (
    <Image source={dragImage} style={styleDragImageProp} />
    )}
    </>
    )}
    </Draggable>
    ));
  • Indicadores de orden:

    • Si showOrderIndicator, cada opción muestra [i + 1] (posición secuencial en el pad).
  • adaptContent:

    • false → usa styles.content: cajas más cuadradas (similar a números/letras).
    • true → usa styles.adaptContent: cajas más altas y estrechas, mejor para textos largos.

3. Modo palabra (por defecto)

Se activa cuando:

!useNumbers && !useOptions
  • Se construye shuffledWord a partir de word:

    const shuffledWord =
    keepPositions
    ? useMemo(() => /* algoritmo de reordenamiento memorizado */, [word])
    : shuffleArray(
    word.split("").map((char, i) => ({ id: i, text: char }))
    );
    • En ambos casos, cada carácter termina como { id, text }.
    • keepPositions = true → solo se recalcula cuando cambia word.
    • keepPositions = false → se rebaraja en cada render.
  • Render:

    (useNumbers
    ? /* no aplica */
    : useOptions
    ? /* no aplica */
    : shuffledWord.map((char) => (
    <Draggable<PadObject>
    timeMove={0}
    data={{
    id: char.id,
    text: char.text,
    randomUniqueId: useRandomUniqueId
    ? useMemo(() => UUID.v4(), [char.text])
    : undefined,
    }}
    resetAfterDrop
    disable={disable}
    >
    {({ data, isDragging }) => (
    <>
    <FactoryLiteThemedContainer
    order={showOrderIndicator ? [char.id + 1] : []}
    showOrderIndicator
    workspaceDimensions={workspaceDimensions}
    type="text"
    containerStyle={{
    justifyContent: "center",
    alignItems: "center",
    }}
    contentStyle={[styles.content, contentStyle]}
    reactive
    colorCombination="answerblue"
    text={data.text}
    textVariant="textBig"
    textStyle={{
    fontSize: fontSize ?? workspaceDimensions.height * 0.05,
    color: "white",
    fontFamily: "lato-bold",
    }}
    />
    {isDragging && dragImage && (
    <Image source={dragImage} style={styleDragImageProp} />
    )}
    </>
    )}
    </Draggable>
    )));
  • Indicadores de orden:

    • [char.id + 1] → corresponden a la posición original del carácter en la palabra.
  • useRandomUniqueId:

    • Si es true, cada carácter lleva un randomUniqueId distinto, generado con UUID.v4(); útil si necesitas diferenciar dos letras iguales (“A” en posición 1 vs “A” en posición 4).

Layout y answers

El estilo se genera con:

const getStyle = ({ width, height }, answers?: number) => {
const droppableSize =
answers && answers >= 4 ? height * 0.1 : height * 0.11;

return StyleSheet.create({
container: {
flexDirection: "row",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "center",
width: height * 0.6,
maxWidth: width * 0.9,
gap: answers && answers >= 4 ? height * 0.03 : height * 0.024,
},
content: {
width: droppableSize,
height: droppableSize,
justifyContent: "center",
alignItems: "center",
},
adaptContent: {
height: height * 0.13,
width: height * 0.06,
justifyContent: "center",
alignItems: "center",
},
dragImage: {
resizeMode: "contain",
},
});
};
  • Si esperas muchas respuestas (>= 4), conviene pasar answers con ese valor para que:

    • Los botones sean un poco más pequeños.
    • El gap entre ellos aumente un poco.
  • content se usa en números y palabra (cajas cuadradas).

  • adaptContent se usa en opciones cuando adaptContent = true.


Ejemplo de uso típico con números

import { DNDProvider } from "@components/DnDRevolution/DNDProvider";
import PadDND, { PadObject } from "@components/DnDRevolution/PadDnD";
import PadDroppable from "@components/DnDRevolution/PadDroppable";

type ExtraData = { correctValue: string };

const workspaceDimensions = { width: 360, height: 640 };

export const NumericPadExample = () => {
return (
<DNDProvider<PadObject<ExtraData>>
workSpaceDimensions={workspaceDimensions}
mode="remplacement-multi"
onDrop={(dataByDroppable, historyByDroppable) => {
// Evaluar respuestas numéricas aquí
}}
>
{/* Droppables de destino (slots para los dígitos) */}
<View style={{ flexDirection: "row", justifyContent: "center", gap: 8 }}>
{Array.from({ length: 4 }, (_, i) => (
<PadDroppable key={i} index={i} workspaceDimensions={workspaceDimensions} />
))}
</View>

{/* Pad de números + punto */}
<PadDND<ExtraData>
workspaceDimensions={workspaceDimensions}
useNumbers
useDot
showOrderIndicator={false}
answers={4}
dragImage={require("@assets/drag-ghost.png")}
styleDragImageProp={{
width: 40,
height: 40,
position: "absolute",
}}
/>
</DNDProvider>
);
};

Ejemplo de uso con palabra

<PadDND
workspaceDimensions={workspaceDimensions}
word="CAÑA"
showOrderIndicator
useRandomUniqueId
/>
  • Genera un pad con las letras de “CAÑA” (barajadas por defecto).
  • Los indicadores de orden muestran 1, 2, 3, 4 según la posición correcta.
  • randomUniqueId permite identificar cada letra aunque se repitan caracteres.

Resumen

  • PadDND te da un pad configurable de draggables para:

    • Números (con o sin punto).
    • Opciones personalizadas.
    • Letras de una palabra.
  • Se integra naturalmente con:

    • DNDProvider<PadObject<T>>
    • PadDroppable / Droppable<PadObject<T>>
  • Soporta:

    • Indicadores de orden (showOrderIndicator).
    • Layout responsive con workspaceDimensions y answers.
    • Tracking fino con useRandomUniqueId.
    • Imagen overlay durante el drag (dragImage).

Siguiente sección recomendada: 👉 PadDroppable para ver cómo recibir estos PadObject del pad.