mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +00:00 
			
		
		
		
	CM LV Settings Dnd fixes (#11755)
* fixed draglayer for dnd * used drag sibling logic to remove hover state bug * updated snapshots
This commit is contained in:
		
							parent
							
								
									11f3519710
								
							
						
					
					
						commit
						bb53ba8638
					
				@ -54,7 +54,7 @@ const CustomDragLayer = () => {
 | 
			
		||||
    <LayoutDndProvider>
 | 
			
		||||
      <div style={layerStyles}>
 | 
			
		||||
        <div style={getItemStyles(initialOffset, currentOffset, mouseOffset)} className="col-md-2">
 | 
			
		||||
          {[ItemTypes.EDIT_RELATION, ItemTypes.EDIT_FIELD].includes(itemType) && (
 | 
			
		||||
          {[ItemTypes.EDIT_RELATION, ItemTypes.EDIT_FIELD, ItemTypes.FIELD].includes(itemType) && (
 | 
			
		||||
            <CardPreview labelField={item.labelField} />
 | 
			
		||||
          )}
 | 
			
		||||
          {itemType === ItemTypes.COMPONENT && (
 | 
			
		||||
 | 
			
		||||
@ -31,36 +31,40 @@ const DragButton = styled(ActionBox)`
 | 
			
		||||
const FieldContainer = styled(Flex)`
 | 
			
		||||
  display: inline-flex;
 | 
			
		||||
  max-height: ${32 / 16}rem;
 | 
			
		||||
  background-color: ${({ theme }) => theme.colors.primary100};
 | 
			
		||||
  border-color: ${({ theme }) => theme.colors.primary200};
 | 
			
		||||
  opacity: ${({ transparent }) => (transparent ? 0 : 1)};
 | 
			
		||||
  background-color: ${({ theme, isSibling }) =>
 | 
			
		||||
    isSibling ? theme.colors.neutral100 : theme.colors.primary100};
 | 
			
		||||
  border: 1px solid
 | 
			
		||||
    ${({ theme, isSibling }) => (isSibling ? theme.colors.neutral150 : theme.colors.primary200)};
 | 
			
		||||
 | 
			
		||||
  svg {
 | 
			
		||||
    width: ${10 / 16}rem;
 | 
			
		||||
    height: ${10 / 16}rem;
 | 
			
		||||
 | 
			
		||||
    path {
 | 
			
		||||
      fill: ${({ theme }) => theme.colors.primary600};
 | 
			
		||||
      fill: ${({ theme, isSibling }) => (isSibling ? undefined : theme.colors.primary600)};
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ${Typography} {
 | 
			
		||||
    color: ${({ theme }) => theme.colors.primary600};
 | 
			
		||||
    color: ${({ theme, isSibling }) => (isSibling ? undefined : theme.colors.primary600)};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ${DragButton} {
 | 
			
		||||
    border-right: 1px solid ${({ theme }) => theme.colors.primary200};
 | 
			
		||||
    border-right: 1px solid
 | 
			
		||||
      ${({ theme, isSibling }) => (isSibling ? theme.colors.neutral150 : theme.colors.primary200)};
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const CardPreview = ({ labelField }) => {
 | 
			
		||||
const CardPreview = ({ labelField, transparent, isSibling }) => {
 | 
			
		||||
  const cardEllipsisTitle = ellipsisCardTitle(labelField);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FieldContainer
 | 
			
		||||
      borderColor="neutral150"
 | 
			
		||||
      background="neutral100"
 | 
			
		||||
      hasRadius
 | 
			
		||||
      justifyContent="space-between"
 | 
			
		||||
      transparent={transparent}
 | 
			
		||||
      isSibling={isSibling}
 | 
			
		||||
    >
 | 
			
		||||
      <Stack horizontal size={3}>
 | 
			
		||||
        <DragButton alignItems="center">
 | 
			
		||||
@ -80,8 +84,15 @@ const CardPreview = ({ labelField }) => {
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CardPreview.defaultProps = {
 | 
			
		||||
  isSibling: false,
 | 
			
		||||
  transparent: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CardPreview.propTypes = {
 | 
			
		||||
  isSibling: PropTypes.bool,
 | 
			
		||||
  labelField: PropTypes.string.isRequired,
 | 
			
		||||
  transparent: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default CardPreview;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import React, { useEffect, useRef } from 'react';
 | 
			
		||||
import React, { useEffect, useRef, useState } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { useDrag, useDrop } from 'react-dnd';
 | 
			
		||||
@ -11,6 +11,7 @@ import { Stack } from '@strapi/design-system/Stack';
 | 
			
		||||
import Pencil from '@strapi/icons/Pencil';
 | 
			
		||||
import Cross from '@strapi/icons/Cross';
 | 
			
		||||
import Drag from '@strapi/icons/Drag';
 | 
			
		||||
import CardPreview from './CardPreview';
 | 
			
		||||
import ellipsisCardTitle from '../utils/ellipsisCardTitle';
 | 
			
		||||
import { getTrad, ItemTypes } from '../../../utils';
 | 
			
		||||
 | 
			
		||||
@ -38,7 +39,6 @@ const DragButton = styled(ActionButton)`
 | 
			
		||||
const FieldContainer = styled(Flex)`
 | 
			
		||||
  max-height: ${32 / 16}rem;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  opacity: ${({ isDragging }) => (isDragging ? 0 : 1)};
 | 
			
		||||
 | 
			
		||||
  svg {
 | 
			
		||||
    width: ${10 / 16}rem;
 | 
			
		||||
@ -77,14 +77,18 @@ const FieldWrapper = styled(Box)`
 | 
			
		||||
 | 
			
		||||
const DraggableCard = ({
 | 
			
		||||
  index,
 | 
			
		||||
  isDraggingSibling,
 | 
			
		||||
  labelField,
 | 
			
		||||
  onClickEditField,
 | 
			
		||||
  onMoveField,
 | 
			
		||||
  onRemoveField,
 | 
			
		||||
  name,
 | 
			
		||||
  setIsDraggingSibling,
 | 
			
		||||
}) => {
 | 
			
		||||
  const { formatMessage } = useIntl();
 | 
			
		||||
  const ref = useRef(null);
 | 
			
		||||
  const dragRef = useRef(null);
 | 
			
		||||
  const dropRef = useRef(null);
 | 
			
		||||
  const [, forceRerenderAfterDnd] = useState(false);
 | 
			
		||||
  const editButtonRef = useRef();
 | 
			
		||||
  const cardEllipsisTitle = ellipsisCardTitle(labelField);
 | 
			
		||||
 | 
			
		||||
@ -96,8 +100,8 @@ const DraggableCard = ({
 | 
			
		||||
 | 
			
		||||
  const [, drop] = useDrop({
 | 
			
		||||
    accept: ItemTypes.FIELD,
 | 
			
		||||
    hover(item) {
 | 
			
		||||
      if (!ref.current) {
 | 
			
		||||
    hover(item, monitor) {
 | 
			
		||||
      if (!dropRef.current) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const dragIndex = item.index;
 | 
			
		||||
@ -108,6 +112,27 @@ const DraggableCard = ({
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Determine rectangle on screen
 | 
			
		||||
      const hoverBoundingRect = dropRef.current.getBoundingClientRect();
 | 
			
		||||
      // Get vertical middle
 | 
			
		||||
      const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
 | 
			
		||||
      // Determine mouse position
 | 
			
		||||
      const clientOffset = monitor.getClientOffset();
 | 
			
		||||
      // Get pixels to the top
 | 
			
		||||
      const hoverClientX = clientOffset.x - hoverBoundingRect.left;
 | 
			
		||||
 | 
			
		||||
      // Only perform the move when the mouse has crossed half of the items height
 | 
			
		||||
      // When dragging downwards, only move when the cursor is below 50%
 | 
			
		||||
      // When dragging upwards, only move when the cursor is above 50%
 | 
			
		||||
      // Dragging downwards
 | 
			
		||||
      if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      // Dragging upwards
 | 
			
		||||
      if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      onMoveField(dragIndex, hoverIndex);
 | 
			
		||||
 | 
			
		||||
      item.index = hoverIndex;
 | 
			
		||||
@ -122,86 +147,117 @@ const DraggableCard = ({
 | 
			
		||||
    collect: monitor => ({
 | 
			
		||||
      isDragging: monitor.isDragging(),
 | 
			
		||||
    }),
 | 
			
		||||
    end: () => {
 | 
			
		||||
      setIsDraggingSibling(false);
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    preview(getEmptyImage(), { captureDraggingState: true });
 | 
			
		||||
    preview(getEmptyImage(), { captureDraggingState: false });
 | 
			
		||||
  }, [preview]);
 | 
			
		||||
 | 
			
		||||
  drag(drop(ref));
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (isDragging) {
 | 
			
		||||
      setIsDraggingSibling(true);
 | 
			
		||||
    }
 | 
			
		||||
  }, [isDragging, setIsDraggingSibling]);
 | 
			
		||||
 | 
			
		||||
  // Effect in order to force a rerender after reordering the components
 | 
			
		||||
  // Since we are removing the Accordion when doing the DnD  we are losing the dragRef, therefore the replaced element cannot be dragged
 | 
			
		||||
  // anymore, this hack forces a rerender in order to apply the dragRef
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!isDraggingSibling) {
 | 
			
		||||
      forceRerenderAfterDnd(prev => !prev);
 | 
			
		||||
    }
 | 
			
		||||
  }, [isDraggingSibling]);
 | 
			
		||||
 | 
			
		||||
  // Create the refs
 | 
			
		||||
  // We need 1 for the drop target
 | 
			
		||||
  // 1 for the drag target
 | 
			
		||||
  const refs = {
 | 
			
		||||
    dragRef: drag(dragRef),
 | 
			
		||||
    dropRef: drop(dropRef),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FieldWrapper>
 | 
			
		||||
      <FieldContainer
 | 
			
		||||
        borderColor="neutral150"
 | 
			
		||||
        background="neutral100"
 | 
			
		||||
        hasRadius
 | 
			
		||||
        justifyContent="space-between"
 | 
			
		||||
        onClick={handleClickEditRow}
 | 
			
		||||
        isDragging={isDragging}
 | 
			
		||||
      >
 | 
			
		||||
        <Stack horizontal size={3}>
 | 
			
		||||
          <DragButton
 | 
			
		||||
            aria-label={formatMessage(
 | 
			
		||||
              {
 | 
			
		||||
                id: getTrad('components.DraggableCard.move.field'),
 | 
			
		||||
                defaultMessage: 'Move {item}',
 | 
			
		||||
              },
 | 
			
		||||
              { item: name }
 | 
			
		||||
            )}
 | 
			
		||||
            onClick={e => e.stopPropagation()}
 | 
			
		||||
            ref={ref}
 | 
			
		||||
            type="button"
 | 
			
		||||
          >
 | 
			
		||||
            <Drag />
 | 
			
		||||
          </DragButton>
 | 
			
		||||
          <Typography fontWeight="bold">{cardEllipsisTitle}</Typography>
 | 
			
		||||
        </Stack>
 | 
			
		||||
        <Flex paddingLeft={3}>
 | 
			
		||||
          <ActionButton
 | 
			
		||||
            ref={editButtonRef}
 | 
			
		||||
            onClick={e => {
 | 
			
		||||
              e.stopPropagation();
 | 
			
		||||
              onClickEditField(name);
 | 
			
		||||
            }}
 | 
			
		||||
            aria-label={formatMessage(
 | 
			
		||||
              {
 | 
			
		||||
                id: getTrad('components.DraggableCard.edit.field'),
 | 
			
		||||
                defaultMessage: 'Edit {item}',
 | 
			
		||||
              },
 | 
			
		||||
              { item: name }
 | 
			
		||||
            )}
 | 
			
		||||
            type="button"
 | 
			
		||||
          >
 | 
			
		||||
            <Pencil />
 | 
			
		||||
          </ActionButton>
 | 
			
		||||
          <ActionButton
 | 
			
		||||
            onClick={onRemoveField}
 | 
			
		||||
            data-testid={`delete-${name}`}
 | 
			
		||||
            aria-label={formatMessage(
 | 
			
		||||
              {
 | 
			
		||||
                id: getTrad('components.DraggableCard.delete.field'),
 | 
			
		||||
                defaultMessage: 'Delete {item}',
 | 
			
		||||
              },
 | 
			
		||||
              { item: name }
 | 
			
		||||
            )}
 | 
			
		||||
            type="button"
 | 
			
		||||
          >
 | 
			
		||||
            <Cross />
 | 
			
		||||
          </ActionButton>
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </FieldContainer>
 | 
			
		||||
    <FieldWrapper ref={refs ? refs.dropRef : null}>
 | 
			
		||||
      {isDragging && <CardPreview transparent labelField={cardEllipsisTitle} />}
 | 
			
		||||
      {!isDragging && isDraggingSibling && <CardPreview isSibling labelField={cardEllipsisTitle} />}
 | 
			
		||||
 | 
			
		||||
      {!isDragging && !isDraggingSibling && (
 | 
			
		||||
        <FieldContainer
 | 
			
		||||
          borderColor="neutral150"
 | 
			
		||||
          background="neutral100"
 | 
			
		||||
          hasRadius
 | 
			
		||||
          justifyContent="space-between"
 | 
			
		||||
          onClick={handleClickEditRow}
 | 
			
		||||
          isDragging={isDragging}
 | 
			
		||||
        >
 | 
			
		||||
          <Stack horizontal size={3}>
 | 
			
		||||
            <DragButton
 | 
			
		||||
              aria-label={formatMessage(
 | 
			
		||||
                {
 | 
			
		||||
                  id: getTrad('components.DraggableCard.move.field'),
 | 
			
		||||
                  defaultMessage: 'Move {item}',
 | 
			
		||||
                },
 | 
			
		||||
                { item: name }
 | 
			
		||||
              )}
 | 
			
		||||
              onClick={e => e.stopPropagation()}
 | 
			
		||||
              ref={refs.dragRef}
 | 
			
		||||
              type="button"
 | 
			
		||||
            >
 | 
			
		||||
              <Drag />
 | 
			
		||||
            </DragButton>
 | 
			
		||||
            <Typography fontWeight="bold">{cardEllipsisTitle}</Typography>
 | 
			
		||||
          </Stack>
 | 
			
		||||
          <Flex paddingLeft={3}>
 | 
			
		||||
            <ActionButton
 | 
			
		||||
              ref={editButtonRef}
 | 
			
		||||
              onClick={e => {
 | 
			
		||||
                e.stopPropagation();
 | 
			
		||||
                onClickEditField(name);
 | 
			
		||||
              }}
 | 
			
		||||
              aria-label={formatMessage(
 | 
			
		||||
                {
 | 
			
		||||
                  id: getTrad('components.DraggableCard.edit.field'),
 | 
			
		||||
                  defaultMessage: 'Edit {item}',
 | 
			
		||||
                },
 | 
			
		||||
                { item: name }
 | 
			
		||||
              )}
 | 
			
		||||
              type="button"
 | 
			
		||||
            >
 | 
			
		||||
              <Pencil />
 | 
			
		||||
            </ActionButton>
 | 
			
		||||
            <ActionButton
 | 
			
		||||
              onClick={onRemoveField}
 | 
			
		||||
              data-testid={`delete-${name}`}
 | 
			
		||||
              aria-label={formatMessage(
 | 
			
		||||
                {
 | 
			
		||||
                  id: getTrad('components.DraggableCard.delete.field'),
 | 
			
		||||
                  defaultMessage: 'Delete {item}',
 | 
			
		||||
                },
 | 
			
		||||
                { item: name }
 | 
			
		||||
              )}
 | 
			
		||||
              type="button"
 | 
			
		||||
            >
 | 
			
		||||
              <Cross />
 | 
			
		||||
            </ActionButton>
 | 
			
		||||
          </Flex>
 | 
			
		||||
        </FieldContainer>
 | 
			
		||||
      )}
 | 
			
		||||
    </FieldWrapper>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DraggableCard.propTypes = {
 | 
			
		||||
  index: PropTypes.number.isRequired,
 | 
			
		||||
  isDraggingSibling: PropTypes.bool.isRequired,
 | 
			
		||||
  labelField: PropTypes.string.isRequired,
 | 
			
		||||
  name: PropTypes.string.isRequired,
 | 
			
		||||
  onClickEditField: PropTypes.func.isRequired,
 | 
			
		||||
  onMoveField: PropTypes.func.isRequired,
 | 
			
		||||
  onRemoveField: PropTypes.func.isRequired,
 | 
			
		||||
  setIsDraggingSibling: PropTypes.func.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DraggableCard;
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { PropTypes } from 'prop-types';
 | 
			
		||||
import { useIntl } from 'react-intl';
 | 
			
		||||
@ -35,6 +35,7 @@ const SortDisplayedFields = ({
 | 
			
		||||
  onRemoveField,
 | 
			
		||||
}) => {
 | 
			
		||||
  const { formatMessage } = useIntl();
 | 
			
		||||
  const [isDraggingSibling, setIsDraggingSibling] = useState(false);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
@ -61,11 +62,13 @@ const SortDisplayedFields = ({
 | 
			
		||||
              <DraggableCard
 | 
			
		||||
                key={field}
 | 
			
		||||
                index={index}
 | 
			
		||||
                isDraggingSibling={isDraggingSibling}
 | 
			
		||||
                onMoveField={onMoveField}
 | 
			
		||||
                onClickEditField={onClickEditField}
 | 
			
		||||
                onRemoveField={e => onRemoveField(e, index)}
 | 
			
		||||
                name={field}
 | 
			
		||||
                labelField={metadatas[field].list.label || field}
 | 
			
		||||
                setIsDraggingSibling={setIsDraggingSibling}
 | 
			
		||||
              />
 | 
			
		||||
            ))}
 | 
			
		||||
          </Stack>
 | 
			
		||||
 | 
			
		||||
@ -878,7 +878,6 @@ exports[`ADMIN | CM | LV | Configure the view renders and matches the snapshot 1
 | 
			
		||||
.c73 {
 | 
			
		||||
  max-height: 2rem;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c73 svg {
 | 
			
		||||
@ -2766,7 +2765,6 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
 | 
			
		||||
.c73 {
 | 
			
		||||
  max-height: 2rem;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.c73 svg {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user