Files
UT-Registration-Plus/src/views/components/common/List/List.tsx
2024-03-06 10:48:51 -06:00

169 lines
6.1 KiB
TypeScript

import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import React, { ReactElement, useCallback, useState } from 'react';
import { areEqual } from 'react-window';
/*
* Ctrl + f dragHandleProps on PopupCourseBlock.tsx for example implementation of drag handle (two lines of code)
*
*/
/**
* Props for the List component.
*/
export interface ListProps {
draggableElements: any[]; // Will later define draggableElements based on what types
// of components are draggable.
itemHeight: number;
listHeight: number;
listWidth: number;
gap: number; // Impacts the spacing between items in the list
}
function initial(draggableElements: any[] = []) {
return draggableElements.map((element, index) => ({
id: `id:${index}`,
content: element as ReactElement,
}));
}
function reorder(list: { id: string; content: ReactElement }[], startIndex: number, endIndex: number) {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
}
function getStyle({ provided, style /* , isDragging, gap */ }) {
const combined = {
...style,
...provided.draggableProps.style,
};
return combined;
}
function Item({ provided, item, style, isDragging /* , gap */ }) {
return (
<div
{...provided.draggableProps}
ref={provided.innerRef}
style={getStyle({ provided, style /* , isDragging, gap */ })}
className={`item ${isDragging ? 'is-dragging' : ''}`}
>
{React.cloneElement(item.content, { dragHandleProps: provided.dragHandleProps })}
</div>
);
}
interface RowProps {
data: any; // DraggableElements[]; Need to define DraggableElements interface once those components are ready
index: number;
style: React.CSSProperties;
}
const Row: React.FC<RowProps> = React.memo(({ data: { items, gap }, index, style }) => {
const item = items[index];
const adjustedStyle = {
...style,
height: `calc(${style.height}px - ${gap}px)`, // Reduce the height by gap to accommodate the margin
marginBottom: `${gap}px`, // Add gap as bottom margin
};
return (
<Draggable draggableId={item.id} index={index} key={item.id}>
{/* @ts-ignore */}
{provided => <Item provided={provided} item={item} style={adjustedStyle} gap={gap} />}
</Draggable>
);
}, areEqual);
/**
* `List` is a functional component that displays a course meeting.
*
* @example
* <List draggableElements={elements} />
*/
const List: React.FC<ListProps> = ({ draggableElements, itemHeight, listHeight, listWidth, gap = 12 }: ListProps) => {
const [items, setItems] = useState(() => initial(draggableElements));
const onDragEnd = useCallback(
result => {
if (!result.destination) {
return;
}
if (result.source.index === result.destination.index) {
return;
}
const newItems = reorder(items, result.source.index, result.destination.index);
setItems(newItems as { id: string; content: React.ReactElement }[]);
},
[items]
);
return (
<div style={{ overflow: 'hidden', width: listWidth }}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable
droppableId='droppable'
direction='vertical'
renderClone={(provided, snapshot, rubric) => {
let { style } = provided.draggableProps;
const transform = style?.transform;
if (snapshot.isDragging && transform) {
console.log(transform);
let [, _x, y] = transform.match(/translate\(([-\d]+)px, ([-\d]+)px\)/) || [];
style.transform = `translate3d(0px, ${y}px, 0px)`; // Apply constrained y value
}
return (
<Item
provided={provided}
isDragging={snapshot.isDragging}
item={items[rubric.source.index]}
style={{
style,
}}
/>
);
}}
>
{provided => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{ width: `${listWidth}px` }}
className=''
>
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{draggableProvided => (
<div
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
style={{
...draggableProvided.draggableProps.style,
// if last item, don't add margin
marginBottom: index === items.length - 1 ? '0px' : `${gap}px`,
}}
>
{item.content}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
</div>
);
};
export default List;