Como Criar Grids de Drag Drop Bento.me

Thomas

Adiministração do forum
Membro da equipe
Administrador
Um de Nós
Parceiros
Membro
Abr 28, 2025
79
545
83
Olá pessoal. alguém conseguiu replicar os efeitos e correções das grids do bento.me? Eu usei o React Grid Layout, mas é cheio de bugs!

Tem esse codigo que corrige um pouco a parte de cima e deixando parecido.


Meu codigo:

Gravação de Tela 2025-11-10 145916.gif
Bento.me:

Gravação de Tela 2025-11-10 150027.gif

Codigo que fez fix da primeira linha:


JavaScript:
import * as RGL_UTILS from 'react-grid-layout/build/utils';
import {
  sortLayoutItems,
  cloneLayoutItem,
  getAllCollisions,
  Layout,
  LayoutItem,
  CompactType
} from 'react-grid-layout/build/utils';

// ============================================
// COMPACT - Compactação Horizontal + Vertical
// ============================================
RGL_UTILS.compact = function compact(
  layout: Layout,
  compactType: CompactType,
  cols: number,
  allowOverlap?: boolean
): Layout {
  // Se permite overlap, não compacta
  if (allowOverlap) return layout;

  // Pega os itens estáticos primeiro
  const compareWith = RGL_UTILS.getStatics(layout);
  const sorted = sortLayoutItems(layout, compactType);
  const out = Array(layout.length);

  for (let i = 0, len = sorted.length; i < len; i++) {
    let l = cloneLayoutItem(sorted[i]);

    // Não move elementos estáticos
    if (!l.static) {
      l = RGL_UTILS.compactItem(
        compareWith,
        l,
        compactType,
        cols,
        sorted,
        allowOverlap
      );
      // Adiciona ao array de comparação
      compareWith.push(l);
    }

    // Adiciona ao array de saída na posição original
    out[layout.indexOf(sorted[i])] = l;
    l.moved = false;
  }

  return out;
};

// ============================================
// SYNCHRONIZE LAYOUT WITH CHILDREN
// ============================================
RGL_UTILS.synchronizeLayoutWithChildren = function synchronizeLayoutWithChildren(
  initialLayout: Layout,
  children: any,
  cols: number,
  compactType: CompactType,
  allowOverlap?: boolean
): Layout {
  initialLayout = initialLayout || [];

  // Gera um item de layout por child
  const layout: Layout = [];
 
  // Importa React para usar Children API
  const React = require('react');
 
  React.Children.forEach(children, (child: any) => {
    if (child?.key == null) return;
   
    const exists = RGL_UTILS.getLayoutItem(initialLayout, String(child.key));
    const g = child.props?.['data-grid'];
   
    if (exists && g == null) {
      layout.push(cloneLayoutItem(exists));
    } else {
      if (g) {
        // Valida o layout em desenvolvimento
        if (process.env.NODE_ENV !== 'production') {
          RGL_UTILS.validateLayout([g], 'ReactGridLayout.children');
        }
        layout.push(cloneLayoutItem({ ...g, i: child.key }));
      } else {
        // Adiciona no final se não houver configuração
        layout.push(
          cloneLayoutItem({
            w: 1,
            h: 1,
            x: 0,
            y: RGL_UTILS.bottom(layout),
            i: String(child.key)
          })
        );
      }
    }
  });

  // Corrige os limites
  const correctedLayout = RGL_UTILS.correctBounds(layout, { cols });
 
  // Retorna compactado ou não dependendo do allowOverlap
  return allowOverlap ? correctedLayout : RGL_UTILS.compact(correctedLayout, compactType, cols);
};

// ============================================
// MOVE ELEMENT - Mantém ordem ao arrastar
// ============================================
type MoveElementParams = {
  layout: Layout;
  l: LayoutItem;
  x?: number;
  y?: number;
  isUserAction?: boolean;
  preventCollision?: boolean;
  compactType: CompactType;
  cols: number;
  allowOverlap?: boolean;
  isLeftShift?: boolean;
};

RGL_UTILS.moveElement = function moveElement(
  layout: Layout,
  l: LayoutItem,
  x?: number,
  y?: number,
  isUserAction?: boolean,
  preventCollision?: boolean,
  compactType?: CompactType,
  cols?: number,
  allowOverlap?: boolean,
  isLeftShift?: boolean
): Layout {
  // Se for estático e não explicitamente arrastável, retorna
  if (l.static && l.isDraggable !== true) return layout;

  // Se não há mudança, retorna
  if (l.y === y && l.x === x) return layout;

  const oldX = l.x;
  const oldY = l.y;

  // Atualiza posição
  if (typeof x === 'number') l.x = x;
  if (typeof y === 'number') l.y = y;
  l.moved = true;

  // Ordena os itens para comparação
  let sorted = sortLayoutItems(layout, compactType);
  const movingUp =
    compactType === 'vertical' && typeof y === 'number'
      ? oldY >= y
      : compactType === 'horizontal' && typeof x === 'number'
      ? oldX >= x
      : false;
     
  if (movingUp) sorted = sorted.reverse();
  const collisions = getAllCollisions(sorted, l);
  const hasCollisions = collisions.length > 0;

  // Se permite overlap, apenas clona e retorna
  if (hasCollisions && allowOverlap) {
    return RGL_UTILS.cloneLayout(layout);
  }

  // Se previne colisão, reverte a posição
  if (hasCollisions && preventCollision) {
    l.x = oldX;
    l.y = oldY;
    l.moved = false;
    return layout;
  }

  // ====== FIX: Detecta direção do movimento ======
  let newIsLeftShift = isLeftShift;
  let isVerticalMovement = false;
 
  if (isUserAction) {
    isUserAction = false;
   
    // Detecta se é movimento vertical (Y mudou)
    if (oldY !== y) {
      isVerticalMovement = true;
      newIsLeftShift = undefined; // Limpa flag para movimento vertical
    } else if (oldX !== x) {
      // Movimento horizontal (X mudou)
      newIsLeftShift = oldX - (x || 0) <= 0;
    }
  }

  // Resolve colisões
  for (let i = 0, len = collisions.length; i < len; i++) {
    const collision = collisions[i];

    if (collision.moved) continue;

    if (collision.static) {
      layout = moveElementAwayFromCollision(
        layout,
        collision,
        l,
        isUserAction,
        compactType,
        cols || 12,
        newIsLeftShift,
        isVerticalMovement
      );
    } else {
      layout = moveElementAwayFromCollision(
        layout,
        l,
        collision,
        isUserAction,
        compactType,
        cols || 12,
        newIsLeftShift,
        isVerticalMovement
      );
    }
  }

  return layout;
};

// ============================================
// MOVE ELEMENT AWAY FROM COLLISION
// ============================================
function moveElementAwayFromCollision(
  layout: Layout,
  collidesWith: LayoutItem,
  itemToMove: LayoutItem,
  isUserAction?: boolean,
  compactType?: CompactType,
  cols: number = 12,
  isLeftShift?: boolean,
  isVerticalMovement?: boolean
): Layout {
  const compactH = compactType === 'horizontal';
  const compactV = compactType === 'vertical';
  const preventCollision = collidesWith.static;

  // Se é ação do usuário, tenta mover para cima/esquerda primeiro
  if (isUserAction) {
    isUserAction = false;

    const fakeItem: LayoutItem = {
      x: compactH ? Math.max(collidesWith.x - itemToMove.w, 0) : itemToMove.x,
      y: compactV ? Math.max(collidesWith.y - itemToMove.h, 0) : itemToMove.y,
      w: itemToMove.w,
      h: itemToMove.h,
      i: '-1'
    };

    const firstCollision = RGL_UTILS.getFirstCollision(layout, fakeItem);

    if (!firstCollision) {
      return RGL_UTILS.moveElement(
        layout,
        itemToMove,
        compactH ? fakeItem.x : undefined,
        compactV ? fakeItem.y : undefined,
        isUserAction,
        preventCollision,
        compactType,
        cols
      );
    }
  }

  // ====== FIX: Se for movimento vertical, sempre move verticalmente ======
  if (isVerticalMovement) {
    return RGL_UTILS.moveElement(
      layout,
      itemToMove,
      undefined, // Não muda X
      itemToMove.y + 1, // Move para baixo
      isUserAction,
      preventCollision,
      compactType,
      cols,
      undefined,
      undefined // Limpa isLeftShift
    );
  }

  // ====== Lógica de troca lateral (apenas para movimento horizontal) ======
  // Se estão na mesma linha, faz swap horizontal
  if (itemToMove.y === collidesWith.y && isLeftShift !== undefined) {
    const deltaShift = isLeftShift ? -1 : 1;
   
    // Verifica se precisa quebrar linha
    const isTileWrapping = isLeftShift
      ? itemToMove.x - 1 < 0
      : itemToMove.x + 1 >= cols;

    if (isTileWrapping) {
      // Quebra para próxima/anterior linha
      const newX = isLeftShift ? cols - 1 : 0;
      const newY = itemToMove.y + deltaShift;
     
      return RGL_UTILS.moveElement(
        layout,
        itemToMove,
        newX,
        newY,
        isUserAction,
        preventCollision,
        compactType,
        cols,
        undefined,
        isLeftShift
      );
    } else {
      // Move horizontalmente na mesma linha
      const newX = itemToMove.x + deltaShift;
     
      return RGL_UTILS.moveElement(
        layout,
        itemToMove,
        newX,
        itemToMove.y, // Mantém o Y
        isUserAction,
        preventCollision,
        compactType,
        cols,
        undefined,
        isLeftShift
      );
    }
  }

  // Caso padrão: move para baixo (vertical)
  let newX = itemToMove.x;
  let newY = itemToMove.y + 1;

  if (compactH) {
    newX = itemToMove.x + 1;
  }

  return RGL_UTILS.moveElement(
    layout,
    itemToMove,
    compactH ? newX : undefined,
    newY,
    isUserAction,
    preventCollision,
    compactType,
    cols,
    undefined,
    undefined // Limpa isLeftShift para não confundir próximas iterações
  );
}

// ============================================
// EXPORTA FUNÇÕES AUXILIARES
// ============================================
export { RGL_UTILS };
export default RGL_UTILS;
Código:

Quem puder ajudar deixe comentário abaixo!
 
Última edição: