Highlight dragged and target items when dragging in the file tree (#15384)

GitOrigin-RevId: 4eaace34434753f6674724adafcf3b0754365d15
This commit is contained in:
Alf Eaton 2023-10-25 10:24:58 +01:00 committed by Copybot
parent e22c1d70f3
commit 949d4facc7
5 changed files with 76 additions and 46 deletions

View file

@ -1,6 +1,5 @@
import { useRef } from 'react'
import PropTypes from 'prop-types'
import { useDragLayer } from 'react-dnd'
import classNames from 'classnames'
// a custom component rendered on top of a draggable area that renders the
@ -8,12 +7,12 @@ import classNames from 'classnames'
// https://react-dnd.github.io/react-dnd/examples/drag-around/custom-drag-layer
// for more details.
// Also used to display a container border when hovered.
function FileTreeDraggablePreviewLayer({ isOver }) {
const { isDragging, item, clientOffset } = useDragLayer(monitor => ({
isDragging: monitor.isDragging(),
item: monitor.getItem(),
clientOffset: monitor.getClientOffset(),
}))
function FileTreeDraggablePreviewLayer({
isOver,
isDragging,
item,
clientOffset,
}) {
const ref = useRef()
return (
@ -39,6 +38,11 @@ function FileTreeDraggablePreviewLayer({ isOver }) {
FileTreeDraggablePreviewLayer.propTypes = {
isOver: PropTypes.bool.isRequired,
isDragging: PropTypes.bool.isRequired,
item: PropTypes.shape({
title: PropTypes.string.isRequired,
}),
clientOffset: PropTypes.number,
}
function DraggablePreviewItem({ title }) {

View file

@ -11,6 +11,7 @@ import FileTreeItemName from './file-tree-item-name'
import FileTreeItemMenu from './file-tree-item-menu'
import { useFileTreeSelectable } from '../../contexts/file-tree-selectable'
import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
import { useDragDropManager } from 'react-dnd'
function FileTreeItemInner({ id, name, isSelected, icons }) {
const { permissionsLevel } = useEditorContext(editorContextPropTypes)
@ -24,7 +25,9 @@ function FileTreeItemInner({ id, name, isSelected, icons }) {
isSelected &&
selectedEntityIds.size === 1
const { isDragging, dragRef, setIsDraggable } = useDraggable(id)
const { dragRef, setIsDraggable } = useDraggable(id)
const dragDropItem = useDragDropManager().getMonitor().getItem()
const itemRef = useRef()
@ -58,7 +61,7 @@ function FileTreeItemInner({ id, name, isSelected, icons }) {
return (
<div
className={classNames('entity', {
'dnd-draggable-dragging': isDragging,
'file-tree-entity-dragging': dragDropItem?.draggedEntityIds?.has(id),
})}
role="presentation"
ref={dragRef}

View file

@ -19,6 +19,8 @@ import { useDroppable } from '../contexts/file-tree-draggable'
import { useFileTreeSocketListener } from '../hooks/file-tree-socket-listener'
import FileTreeModalCreateFile from './modals/file-tree-modal-create-file'
import FileTreeInner from './file-tree-inner'
import { useDragLayer } from 'react-dnd'
import classnames from 'classnames'
const FileTreeRoot = React.memo(function FileTreeRoot({
refProviders,
@ -66,14 +68,24 @@ function FileTreeRootFolder() {
const { isOver, dropRef } = useDroppable(fileTreeData._id)
const dragLayer = useDragLayer(monitor => ({
isDragging: monitor.isDragging(),
item: monitor.getItem(),
clientOffset: monitor.getClientOffset(),
}))
return (
<>
<FileTreeDraggablePreviewLayer isOver={isOver} />
<FileTreeDraggablePreviewLayer isOver={isOver} {...dragLayer} />
<FileTreeFolderList
folders={fileTreeData.folders}
docs={fileTreeData.docs}
files={fileTreeData.fileRefs}
classes={{ root: 'file-tree-list' }}
classes={{
root: classnames('file-tree-list', {
'file-tree-dragging': dragLayer.isDragging,
}),
}}
dropRef={dropRef}
dataTestId="file-tree-list-root"
/>

View file

@ -88,7 +88,7 @@ export function useDraggable(draggedEntityId) {
const [isDraggable, setIsDraggable] = useState(true)
const item = { type: DRAGGABLE_TYPE }
const [{ isDragging }, dragRef, preview] = useDrag({
const [{ isDragging, draggedEntityIds }, dragRef, preview] = useDrag({
item, // required, but overwritten by the return value of `begin`
begin: () => {
const draggedEntityIds = getDraggedEntityIds(
@ -104,6 +104,7 @@ export function useDraggable(draggedEntityId) {
isDragging: !!monitor.isDragging(),
}),
canDrag: () => permissionsLevel !== 'readOnly' && isDraggable,
end: () => item,
})
// remove the automatic preview as we're using a custom preview via
@ -116,6 +117,7 @@ export function useDraggable(draggedEntityId) {
dragRef,
isDragging,
setIsDraggable,
draggedEntityIds,
}
}

View file

@ -67,22 +67,6 @@
}
}
li.dnd-droppable-hover .entity-name,
li .entity-name.droppable-hover {
font-weight: bold;
background-color: @file-tree-item-hover-bg;
.fake-full-width-bg(@file-tree-item-hover-bg);
}
ul.droppable-hover li div.entity-name:hover {
background-color: transparent;
.fake-full-width-bg(transparent);
&.droppable-hover {
background-color: @file-tree-item-hover-bg;
.fake-full-width-bg(@file-tree-item-hover-bg);
}
}
ul.file-tree-list {
margin: 0;
overflow-x: hidden;
@ -144,10 +128,10 @@
&:focus {
outline: none;
}
background-color: transparent;
&:hover {
background-color: @file-tree-item-hover-bg;
}
&:hover {
// When the entity is a subfolder, the DOM element is "indented" via margin-left. This makes the
// element not fill the entire file-tree width (as it's spaced from the left-hand side via margin)
// and, in consequence, the background gets clipped. The ::before pseudo-selector is used to fill
@ -163,13 +147,6 @@
}
}
.entity.dnd-draggable-dragging {
.entity-name:hover {
background-color: transparent;
.fake-full-width-bg(transparent);
}
}
i.fa {
color: @file-tree-item-icon-color;
font-size: 14px;
@ -309,15 +286,18 @@
> .entity {
> .entity-name {
color: @file-tree-item-selected-color;
> div > i.fa,
> button > i.fa,
> i.fa,
.entity-menu-toggle i.fa {
color: @file-tree-item-selected-color;
}
> i.linked-file-highlight {
color: @blue;
}
background-color: @file-tree-item-selected-bg;
font-weight: bold;
padding-right: 32px;
@ -332,13 +312,43 @@
}
}
}
ul.file-tree-list li.selected.dnd-droppable-hover {
> .entity {
> .entity-name {
background-color: @file-tree-item-hover-bg;
.fake-full-width-bg(@file-tree-item-hover-bg);
}
}
}
// while dragging, the previously selected item gets no highlight
ul.file-tree-list.file-tree-dragging li.selected .entity .entity-name {
font-weight: normal;
background-color: transparent;
.fake-full-width-bg(transparent);
color: @file-tree-item-color;
i.fa {
color: @file-tree-item-icon-color !important;
}
}
// the items being dragged get the full "hover" colour
ul.file-tree-list.file-tree-dragging
li
.entity.file-tree-entity-dragging
.entity-name {
background-color: fade(@file-tree-item-hover-bg, 90%);
.fake-full-width-bg(fade(@file-tree-item-hover-bg, 90%));
color: @file-tree-item-color;
i.fa {
color: @file-tree-item-icon-color !important;
}
}
// the drop target gets the "selected" colour
ul.file-tree-list.file-tree-dragging
li.dnd-droppable-hover
.entity
.entity-name {
background-color: @file-tree-item-selected-bg;
.fake-full-width-bg(@file-tree-item-selected-bg);
color: @file-tree-item-selected-color;
i.fa {
color: @file-tree-item-selected-color !important;
}
}
@ -355,8 +365,7 @@
}
}
.dnd-draggable-preview-item,
.ui-draggable-dragging {
.dnd-draggable-preview-item {
background-color: fade(@file-tree-item-selected-bg, 60%);
color: @file-tree-item-selected-color;
width: 75%;