fix: non-virtual dnd

This commit is contained in:
doprz
2024-03-06 10:48:51 -06:00
parent 677aa624d7
commit 837fddf804
3 changed files with 1858 additions and 999 deletions

2538
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,15 @@
import React from 'react';
import { Meta, StoryObj } from '@storybook/react';
import { Course, Status } from '@shared/types/Course'; import { Course, Status } from '@shared/types/Course';
import { CourseMeeting } from '@shared/types/CourseMeeting'; import { CourseMeeting } from '@shared/types/CourseMeeting';
import Instructor from '@shared/types/Instructor'; import Instructor from '@shared/types/Instructor';
import { Meta, StoryObj } from '@storybook/react';
import List from '@views/components/common/List/List'; import List from '@views/components/common/List/List';
import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock'; import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock';
import { getCourseColors } from 'src/shared/util/colors'; import React from 'react';
import { test_colors } from './PopupCourseBlock.stories'; import { test_colors } from './PopupCourseBlock.stories';
const numberOfCourses = 5; const numberOfCourses = 5;
const generateCourses = (count) => { const generateCourses = count => {
const courses = []; const courses = [];
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@@ -64,14 +62,10 @@ const generateCourses = (count) => {
}; };
const exampleCourses = generateCourses(numberOfCourses); const exampleCourses = generateCourses(numberOfCourses);
const generateCourseBlocks = (exampleCourses, colors) => { const generateCourseBlocks = (exampleCourses, colors) =>
return exampleCourses.map((course, i) => ( exampleCourses.map((course, i) => <PopupCourseBlock key={course.uniqueId} course={course} colors={colors[i]} />);
<PopupCourseBlock key={course.uniqueId} course={course} colors={colors[i]} />
))
}
const exampleCourseBlocks = generateCourseBlocks(exampleCourses, test_colors); const exampleCourseBlocks = generateCourseBlocks(exampleCourses, test_colors);
const meta = { const meta = {
title: 'Components/Common/List', title: 'Components/Common/List',
component: List, component: List,
@@ -97,6 +91,6 @@ export const Default: Story = {
itemHeight: 55, itemHeight: 55,
listHeight: 300, listHeight: 300,
listWidth: 300, listWidth: 300,
gap: 8, gap: 12,
}, },
}; };

View File

@@ -1,7 +1,6 @@
import React, { useState, ReactElement, useCallback } from 'react'; import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'; import React, { ReactElement, useCallback, useState } from 'react';
import { FixedSizeList, areEqual } from 'react-window'; import { areEqual } from 'react-window';
/* /*
* Ctrl + f dragHandleProps on PopupCourseBlock.tsx for example implementation of drag handle (two lines of code) * Ctrl + f dragHandleProps on PopupCourseBlock.tsx for example implementation of drag handle (two lines of code)
@@ -18,7 +17,6 @@ export interface ListProps {
listHeight: number; listHeight: number;
listWidth: number; listWidth: number;
gap: number; // Impacts the spacing between items in the list gap: number; // Impacts the spacing between items in the list
} }
function initial(draggableElements: any[] = []) { function initial(draggableElements: any[] = []) {
@@ -28,7 +26,7 @@ function initial(draggableElements: any[] = []) {
})); }));
} }
function reorder(list: { id: string, content: ReactElement }[], startIndex: number, endIndex: number) { function reorder(list: { id: string; content: ReactElement }[], startIndex: number, endIndex: number) {
const result = Array.from(list); const result = Array.from(list);
const [removed] = result.splice(startIndex, 1); const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed); result.splice(endIndex, 0, removed);
@@ -38,7 +36,7 @@ function reorder(list: { id: string, content: ReactElement }[], startIndex: numb
function getStyle({ provided, style /* , isDragging, gap */ }) { function getStyle({ provided, style /* , isDragging, gap */ }) {
const combined = { const combined = {
...style, ...style,
...provided.draggableProps.style ...provided.draggableProps.style,
}; };
return combined; return combined;
@@ -50,7 +48,7 @@ function Item({ provided, item, style, isDragging/* , gap */ }) {
{...provided.draggableProps} {...provided.draggableProps}
ref={provided.innerRef} ref={provided.innerRef}
style={getStyle({ provided, style /* , isDragging, gap */ })} style={getStyle({ provided, style /* , isDragging, gap */ })}
className={`item ${isDragging ? "is-dragging" : ""}`} className={`item ${isDragging ? 'is-dragging' : ''}`}
> >
{React.cloneElement(item.content, { dragHandleProps: provided.dragHandleProps })} {React.cloneElement(item.content, { dragHandleProps: provided.dragHandleProps })}
</div> </div>
@@ -58,9 +56,9 @@ function Item({ provided, item, style, isDragging/* , gap */ }) {
} }
interface RowProps { interface RowProps {
data: any, // DraggableElements[]; Need to define DraggableElements interface once those components are ready data: any; // DraggableElements[]; Need to define DraggableElements interface once those components are ready
index: number, index: number;
style: React.CSSProperties, style: React.CSSProperties;
} }
const Row: React.FC<RowProps> = React.memo(({ data: { items, gap }, index, style }) => { const Row: React.FC<RowProps> = React.memo(({ data: { items, gap }, index, style }) => {
@@ -68,7 +66,7 @@ const Row: React.FC<RowProps> = React.memo(({ data: { items, gap }, index, style
const adjustedStyle = { const adjustedStyle = {
...style, ...style,
height: `calc(${style.height}px - ${gap}px)`, // Reduce the height by gap to accommodate the margin height: `calc(${style.height}px - ${gap}px)`, // Reduce the height by gap to accommodate the margin
marginBottom: `${gap}px` // Add gap as bottom margin marginBottom: `${gap}px`, // Add gap as bottom margin
}; };
return ( return (
<Draggable draggableId={item.id} index={index} key={item.id}> <Draggable draggableId={item.id} index={index} key={item.id}>
@@ -84,10 +82,11 @@ const Row: React.FC<RowProps> = React.memo(({ data: { items, gap }, index, style
* @example * @example
* <List draggableElements={elements} /> * <List draggableElements={elements} />
*/ */
const List: React.FC<ListProps> = ( { draggableElements, itemHeight, listHeight, listWidth, gap=8 }: ListProps) => { const List: React.FC<ListProps> = ({ draggableElements, itemHeight, listHeight, listWidth, gap = 12 }: ListProps) => {
const [items, setItems] = useState(() => initial(draggableElements)); const [items, setItems] = useState(() => initial(draggableElements));
const onDragEnd = useCallback((result) => { const onDragEnd = useCallback(
result => {
if (!result.destination) { if (!result.destination) {
return; return;
} }
@@ -96,54 +95,28 @@ const List: React.FC<ListProps> = ( { draggableElements, itemHeight, listHeight,
return; return;
} }
const newItems = reorder( const newItems = reorder(items, result.source.index, result.destination.index);
items,
result.source.index,
result.destination.index
);
setItems(newItems as { id: string, content: React.ReactElement }[]); setItems(newItems as { id: string; content: React.ReactElement }[]);
}, [items]); },
[items]
);
return ( return (
<div style={{ overflow: 'hidden', width: listWidth }}> <div style={{ overflow: 'hidden', width: listWidth }}>
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<div style = {{
display: "flex",
flexDirection: "column",
alignItems: "center"
}}>
<Droppable <Droppable
droppableId="droppable" droppableId='droppable'
/* mode="virtual" */ direction='vertical'
direction="vertical"
renderClone={(provided, snapshot, rubric) => { renderClone={(provided, snapshot, rubric) => {
let { style } = provided.draggableProps; let { style } = provided.draggableProps;
const transform = style?.transform; const transform = style?.transform;
if (snapshot.isDragging && transform) { if (snapshot.isDragging && transform) {
let [, y] = transform console.log(transform);
.replace('translate(', '') let [, _x, y] = transform.match(/translate\(([-\d]+)px, ([-\d]+)px\)/) || [];
.replace(')', '')
.split(',')
.map((v) => parseInt(v, 10));
/* style.transform = `translate3d(0px, ${y}px, 0px)`; // Apply constrained y value
const minTranslateY = -1 * rubric.source.index * itemHeight;
const maxTranslateY = (items.length - rubric.source.index - 1) * itemHeight;
if (y < minTranslateY) {
}
else if (y > maxTranslateY) {
}
else {
}
*/
style.transform = `translate(0px, ${y}px)`; // Apply constrained y value
} }
return ( return (
@@ -152,34 +125,31 @@ const List: React.FC<ListProps> = ( { draggableElements, itemHeight, listHeight,
isDragging={snapshot.isDragging} isDragging={snapshot.isDragging}
item={items[rubric.source.index]} item={items[rubric.source.index]}
style={{ style={{
style style,
}} }}
/* gap = {gap} */
/> />
)} );
} }}
> >
{(provided) => ( {provided => (
<div <div
{...provided.droppableProps} {...provided.droppableProps}
ref={provided.innerRef} ref={provided.innerRef}
style={{ width: `${listWidth}px` }} style={{ width: `${listWidth}px` }}
className="space-y-2" className=''
> >
{items.map((item, index) => ( {items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}> <Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => ( {draggableProvided => (
<div <div
ref={provided.innerRef} ref={draggableProvided.innerRef}
key={item.id} {...draggableProvided.draggableProps}
{...provided.draggableProps} {...draggableProvided.dragHandleProps}
{...provided.dragHandleProps}
style={{ style={{
...provided.draggableProps.style, ...draggableProvided.draggableProps.style,
marginBottom: '8px', // if last item, don't add margin
background: snapshot.isDragging ? '#f4f4f4' : 'transparent', marginBottom: index === items.length - 1 ? '0px' : `${gap}px`,
}} }}
className="p-2"
> >
{item.content} {item.content}
</div> </div>
@@ -190,7 +160,6 @@ const List: React.FC<ListProps> = ( { draggableElements, itemHeight, listHeight,
</div> </div>
)} )}
</Droppable> </Droppable>
</div>
</DragDropContext> </DragDropContext>
</div> </div>
); );