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:
ronronscelestes 2021-12-03 09:31:52 +01:00 committed by GitHub
parent 11f3519710
commit bb53ba8638
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 147 additions and 79 deletions

View File

@ -54,7 +54,7 @@ const CustomDragLayer = () => {
<LayoutDndProvider> <LayoutDndProvider>
<div style={layerStyles}> <div style={layerStyles}>
<div style={getItemStyles(initialOffset, currentOffset, mouseOffset)} className="col-md-2"> <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} /> <CardPreview labelField={item.labelField} />
)} )}
{itemType === ItemTypes.COMPONENT && ( {itemType === ItemTypes.COMPONENT && (

View File

@ -31,36 +31,40 @@ const DragButton = styled(ActionBox)`
const FieldContainer = styled(Flex)` const FieldContainer = styled(Flex)`
display: inline-flex; display: inline-flex;
max-height: ${32 / 16}rem; max-height: ${32 / 16}rem;
background-color: ${({ theme }) => theme.colors.primary100}; opacity: ${({ transparent }) => (transparent ? 0 : 1)};
border-color: ${({ theme }) => theme.colors.primary200}; background-color: ${({ theme, isSibling }) =>
isSibling ? theme.colors.neutral100 : theme.colors.primary100};
border: 1px solid
${({ theme, isSibling }) => (isSibling ? theme.colors.neutral150 : theme.colors.primary200)};
svg { svg {
width: ${10 / 16}rem; width: ${10 / 16}rem;
height: ${10 / 16}rem; height: ${10 / 16}rem;
path { path {
fill: ${({ theme }) => theme.colors.primary600}; fill: ${({ theme, isSibling }) => (isSibling ? undefined : theme.colors.primary600)};
} }
} }
${Typography} { ${Typography} {
color: ${({ theme }) => theme.colors.primary600}; color: ${({ theme, isSibling }) => (isSibling ? undefined : theme.colors.primary600)};
} }
${DragButton} { ${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); const cardEllipsisTitle = ellipsisCardTitle(labelField);
return ( return (
<FieldContainer <FieldContainer
borderColor="neutral150"
background="neutral100"
hasRadius hasRadius
justifyContent="space-between" justifyContent="space-between"
transparent={transparent}
isSibling={isSibling}
> >
<Stack horizontal size={3}> <Stack horizontal size={3}>
<DragButton alignItems="center"> <DragButton alignItems="center">
@ -80,8 +84,15 @@ const CardPreview = ({ labelField }) => {
); );
}; };
CardPreview.defaultProps = {
isSibling: false,
transparent: false,
};
CardPreview.propTypes = { CardPreview.propTypes = {
isSibling: PropTypes.bool,
labelField: PropTypes.string.isRequired, labelField: PropTypes.string.isRequired,
transparent: PropTypes.bool,
}; };
export default CardPreview; export default CardPreview;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useDrag, useDrop } from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd';
@ -11,6 +11,7 @@ import { Stack } from '@strapi/design-system/Stack';
import Pencil from '@strapi/icons/Pencil'; import Pencil from '@strapi/icons/Pencil';
import Cross from '@strapi/icons/Cross'; import Cross from '@strapi/icons/Cross';
import Drag from '@strapi/icons/Drag'; import Drag from '@strapi/icons/Drag';
import CardPreview from './CardPreview';
import ellipsisCardTitle from '../utils/ellipsisCardTitle'; import ellipsisCardTitle from '../utils/ellipsisCardTitle';
import { getTrad, ItemTypes } from '../../../utils'; import { getTrad, ItemTypes } from '../../../utils';
@ -38,7 +39,6 @@ const DragButton = styled(ActionButton)`
const FieldContainer = styled(Flex)` const FieldContainer = styled(Flex)`
max-height: ${32 / 16}rem; max-height: ${32 / 16}rem;
cursor: pointer; cursor: pointer;
opacity: ${({ isDragging }) => (isDragging ? 0 : 1)};
svg { svg {
width: ${10 / 16}rem; width: ${10 / 16}rem;
@ -77,14 +77,18 @@ const FieldWrapper = styled(Box)`
const DraggableCard = ({ const DraggableCard = ({
index, index,
isDraggingSibling,
labelField, labelField,
onClickEditField, onClickEditField,
onMoveField, onMoveField,
onRemoveField, onRemoveField,
name, name,
setIsDraggingSibling,
}) => { }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const ref = useRef(null); const dragRef = useRef(null);
const dropRef = useRef(null);
const [, forceRerenderAfterDnd] = useState(false);
const editButtonRef = useRef(); const editButtonRef = useRef();
const cardEllipsisTitle = ellipsisCardTitle(labelField); const cardEllipsisTitle = ellipsisCardTitle(labelField);
@ -96,8 +100,8 @@ const DraggableCard = ({
const [, drop] = useDrop({ const [, drop] = useDrop({
accept: ItemTypes.FIELD, accept: ItemTypes.FIELD,
hover(item) { hover(item, monitor) {
if (!ref.current) { if (!dropRef.current) {
return; return;
} }
const dragIndex = item.index; const dragIndex = item.index;
@ -108,6 +112,27 @@ const DraggableCard = ({
return; 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); onMoveField(dragIndex, hoverIndex);
item.index = hoverIndex; item.index = hoverIndex;
@ -122,86 +147,117 @@ const DraggableCard = ({
collect: monitor => ({ collect: monitor => ({
isDragging: monitor.isDragging(), isDragging: monitor.isDragging(),
}), }),
end: () => {
setIsDraggingSibling(false);
},
}); });
useEffect(() => { useEffect(() => {
preview(getEmptyImage(), { captureDraggingState: true }); preview(getEmptyImage(), { captureDraggingState: false });
}, [preview]); }, [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 ( return (
<FieldWrapper> <FieldWrapper ref={refs ? refs.dropRef : null}>
<FieldContainer {isDragging && <CardPreview transparent labelField={cardEllipsisTitle} />}
borderColor="neutral150" {!isDragging && isDraggingSibling && <CardPreview isSibling labelField={cardEllipsisTitle} />}
background="neutral100"
hasRadius {!isDragging && !isDraggingSibling && (
justifyContent="space-between" <FieldContainer
onClick={handleClickEditRow} borderColor="neutral150"
isDragging={isDragging} background="neutral100"
> hasRadius
<Stack horizontal size={3}> justifyContent="space-between"
<DragButton onClick={handleClickEditRow}
aria-label={formatMessage( isDragging={isDragging}
{ >
id: getTrad('components.DraggableCard.move.field'), <Stack horizontal size={3}>
defaultMessage: 'Move {item}', <DragButton
}, aria-label={formatMessage(
{ item: name } {
)} id: getTrad('components.DraggableCard.move.field'),
onClick={e => e.stopPropagation()} defaultMessage: 'Move {item}',
ref={ref} },
type="button" { item: name }
> )}
<Drag /> onClick={e => e.stopPropagation()}
</DragButton> ref={refs.dragRef}
<Typography fontWeight="bold">{cardEllipsisTitle}</Typography> type="button"
</Stack> >
<Flex paddingLeft={3}> <Drag />
<ActionButton </DragButton>
ref={editButtonRef} <Typography fontWeight="bold">{cardEllipsisTitle}</Typography>
onClick={e => { </Stack>
e.stopPropagation(); <Flex paddingLeft={3}>
onClickEditField(name); <ActionButton
}} ref={editButtonRef}
aria-label={formatMessage( onClick={e => {
{ e.stopPropagation();
id: getTrad('components.DraggableCard.edit.field'), onClickEditField(name);
defaultMessage: 'Edit {item}', }}
}, aria-label={formatMessage(
{ item: name } {
)} id: getTrad('components.DraggableCard.edit.field'),
type="button" defaultMessage: 'Edit {item}',
> },
<Pencil /> { item: name }
</ActionButton> )}
<ActionButton type="button"
onClick={onRemoveField} >
data-testid={`delete-${name}`} <Pencil />
aria-label={formatMessage( </ActionButton>
{ <ActionButton
id: getTrad('components.DraggableCard.delete.field'), onClick={onRemoveField}
defaultMessage: 'Delete {item}', data-testid={`delete-${name}`}
}, aria-label={formatMessage(
{ item: name } {
)} id: getTrad('components.DraggableCard.delete.field'),
type="button" defaultMessage: 'Delete {item}',
> },
<Cross /> { item: name }
</ActionButton> )}
</Flex> type="button"
</FieldContainer> >
<Cross />
</ActionButton>
</Flex>
</FieldContainer>
)}
</FieldWrapper> </FieldWrapper>
); );
}; };
DraggableCard.propTypes = { DraggableCard.propTypes = {
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
isDraggingSibling: PropTypes.bool.isRequired,
labelField: PropTypes.string.isRequired, labelField: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onClickEditField: PropTypes.func.isRequired, onClickEditField: PropTypes.func.isRequired,
onMoveField: PropTypes.func.isRequired, onMoveField: PropTypes.func.isRequired,
onRemoveField: PropTypes.func.isRequired, onRemoveField: PropTypes.func.isRequired,
setIsDraggingSibling: PropTypes.func.isRequired,
}; };
export default DraggableCard; export default DraggableCard;

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { PropTypes } from 'prop-types'; import { PropTypes } from 'prop-types';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
@ -35,6 +35,7 @@ const SortDisplayedFields = ({
onRemoveField, onRemoveField,
}) => { }) => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [isDraggingSibling, setIsDraggingSibling] = useState(false);
return ( return (
<> <>
@ -61,11 +62,13 @@ const SortDisplayedFields = ({
<DraggableCard <DraggableCard
key={field} key={field}
index={index} index={index}
isDraggingSibling={isDraggingSibling}
onMoveField={onMoveField} onMoveField={onMoveField}
onClickEditField={onClickEditField} onClickEditField={onClickEditField}
onRemoveField={e => onRemoveField(e, index)} onRemoveField={e => onRemoveField(e, index)}
name={field} name={field}
labelField={metadatas[field].list.label || field} labelField={metadatas[field].list.label || field}
setIsDraggingSibling={setIsDraggingSibling}
/> />
))} ))}
</Stack> </Stack>

View File

@ -878,7 +878,6 @@ exports[`ADMIN | CM | LV | Configure the view renders and matches the snapshot 1
.c73 { .c73 {
max-height: 2rem; max-height: 2rem;
cursor: pointer; cursor: pointer;
opacity: 1;
} }
.c73 svg { .c73 svg {
@ -2766,7 +2765,6 @@ exports[`ADMIN | CM | LV | Configure the view should add field 1`] = `
.c73 { .c73 {
max-height: 2rem; max-height: 2rem;
cursor: pointer; cursor: pointer;
opacity: 1;
} }
.c73 svg { .c73 svg {