NEWONE1 / invokeai /frontend /web /src /features /dnd /DndListDropIndicator.tsx
roshikhan301's picture
Upload 2113 files
8a37e0a verified
// Adapted from https://github.com/alexreardon/pdnd-react-tailwind/blob/main/src/drop-indicator.tsx
import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/types';
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Box } from '@invoke-ai/ui-library';
import type { DndListTargetState } from 'features/dnd/types';
/**
* Design decisions for the drop indicator's main line
*/
const line = {
thickness: 2,
backgroundColor: 'base.500',
};
type DropIndicatorProps = {
/**
* The `edge` to draw a drop indicator on.
*
* `edge` is required as for the best possible performance
* outcome you should only render this component when it needs to do something
*
* @example {closestEdge && <DropIndicator edge={closestEdge} />}
*/
edge: Edge;
/**
* `gap` allows you to position the drop indicator further away from the drop target.
* `gap` should be the distance between your drop targets
* a drop indicator will be rendered halfway between the drop targets
* (the drop indicator will be offset by half of the `gap`)
*
* `gap` should be a valid CSS length.
* @example "8px"
* @example "var(--gap)"
*/
gap?: string;
};
const lineStyles: SystemStyleObject = {
display: 'block',
position: 'absolute',
zIndex: 1,
borderRadius: 'full',
// Blocking pointer events to prevent the line from triggering drag events
// Dragging over the line should count as dragging over the element behind it
pointerEvents: 'none',
background: line.backgroundColor,
};
type Orientation = 'horizontal' | 'vertical';
const orientationStyles: Record<Orientation, SystemStyleObject> = {
horizontal: {
height: `${line.thickness}px`,
left: 2,
right: 2,
},
vertical: {
width: `${line.thickness}px`,
top: 2,
bottom: 2,
},
};
const edgeToOrientationMap: Record<Edge, Orientation> = {
top: 'horizontal',
bottom: 'horizontal',
left: 'vertical',
right: 'vertical',
};
const edgeStyles: Record<Edge, SystemStyleObject> = {
top: {
top: 'var(--local-line-offset)',
},
right: {
right: 'var(--local-line-offset)',
},
bottom: {
bottom: 'var(--local-line-offset)',
},
left: {
left: 'var(--local-line-offset)',
},
};
/**
* __Drop indicator__
*
* A drop indicator is used to communicate the intended resting place of the draggable item. The orientation of the drop indicator should always match the direction of the content flow.
*/
function DndDropIndicatorInternal({ edge, gap = '0px' }: DropIndicatorProps) {
/**
* To clearly communicate the resting place of a draggable item during a drag operation,
* the drop indicator should be positioned half way between draggable items.
*/
const lineOffset = `calc(-0.5 * (${gap} + ${line.thickness}px))`;
const orientation = edgeToOrientationMap[edge];
return (
<Box
sx={{ ...lineStyles, ...orientationStyles[orientation], ...edgeStyles[edge], '--local-line-offset': lineOffset }}
/>
);
}
export const DndListDropIndicator = ({ dndState }: { dndState: DndListTargetState }) => {
if (dndState.type !== 'is-dragging-over') {
return null;
}
if (!dndState.closestEdge) {
return null;
}
return (
<DndDropIndicatorInternal
edge={dndState.closestEdge}
// This is the gap between items in the list, used to calculate the position of the drop indicator
gap="var(--invoke-space-2)"
/>
);
};