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,77 +1,71 @@
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++) {
const course = new Course({ const course = new Course({
courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB',
creditHours: 3, creditHours: 3,
department: 'C S', department: 'C S',
description: [ description: [
'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.', 'Problem solving and fundamental algorithms for various applications in science, business, and on the World Wide Web, and introductory programming in a modern object-oriented programming language.',
'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.', 'Only one of the following may be counted: Computer Science 303E, 312, 312H. Credit for Computer Science 303E may not be earned after a student has received credit for Computer Science 314, or 314H. May not be counted toward a degree in computer science.',
'May be counted toward the Quantitative Reasoning flag requirement.', 'May be counted toward the Quantitative Reasoning flag requirement.',
'Designed to accommodate 100 or more students.', 'Designed to accommodate 100 or more students.',
'Taught as a Web-based course.', 'Taught as a Web-based course.',
], ],
flags: ['Quantitative Reasoning'], flags: ['Quantitative Reasoning'],
fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB', fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB',
instructionMode: 'Online', instructionMode: 'Online',
instructors: [ instructors: [
new Instructor({ new Instructor({
firstName: 'Bevo', firstName: 'Bevo',
lastName: 'Bevo', lastName: 'Bevo',
fullName: 'Bevo Bevo', fullName: 'Bevo Bevo',
}), }),
], ],
isReserved: false, isReserved: false,
number: '303E', number: '303E',
schedule: { schedule: {
meetings: [ meetings: [
new CourseMeeting({ new CourseMeeting({
days: ['Tuesday', 'Thursday'], days: ['Tuesday', 'Thursday'],
endTime: 660, endTime: 660,
startTime: 570, startTime: 570,
}), }),
], ],
}, },
semester: { semester: {
code: '12345', code: '12345',
season: 'Spring', season: 'Spring',
year: 2024, year: 2024,
}, },
status: Status.WAITLISTED, status: Status.WAITLISTED,
uniqueId: 12345 + i, // Make uniqueId different for each course uniqueId: 12345 + i, // Make uniqueId different for each course
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/',
}); });
courses.push(course); courses.push(course);
} }
return courses; return courses;
}; };
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)
@@ -13,12 +12,11 @@ import { FixedSizeList, areEqual } from 'react-window';
*/ */
export interface ListProps { export interface ListProps {
draggableElements: any[]; // Will later define draggableElements based on what types draggableElements: any[]; // Will later define draggableElements based on what types
// of components are draggable. // of components are draggable.
itemHeight: number; itemHeight: number;
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,29 +36,29 @@ 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;
} }
function Item({ provided, item, style, isDragging/* , gap */ }) { function Item({ provided, item, style, isDragging /* , gap */ }) {
return ( return (
<div <div
{...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>
); );
} }
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,12 +66,12 @@ 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}>
{/* @ts-ignore */} {/* @ts-ignore */}
{provided => <Item provided={provided} item={item} style={adjustedStyle} gap={gap}/>} {provided => <Item provided={provided} item={item} style={adjustedStyle} gap={gap} />}
</Draggable> </Draggable>
); );
}, areEqual); }, areEqual);
@@ -84,114 +82,85 @@ 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(
if (!result.destination) { result => {
return; if (!result.destination) {
} return;
}
if (result.source.index === result.destination.index) { if (result.source.index === result.destination.index) {
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));
/*
const minTranslateY = -1 * rubric.source.index * itemHeight;
const maxTranslateY = (items.length - rubric.source.index - 1) * itemHeight;
if (y < minTranslateY) {
style.transform = `translate3d(0px, ${y}px, 0px)`; // Apply constrained y value
} }
else if (y > maxTranslateY) {
} return (
else { <Item
provided={provided}
} isDragging={snapshot.isDragging}
*/ item={items[rubric.source.index]}
style={{
style.transform = `translate(0px, ${y}px)`; // Apply constrained y value style,
} }}
/>
return ( );
<Item }}
provided={provided}
isDragging={snapshot.isDragging}
item={items[rubric.source.index]}
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={{ ...draggableProvided.draggableProps.style,
...provided.draggableProps.style, // if last item, don't add margin
marginBottom: '8px', marginBottom: index === items.length - 1 ? '0px' : `${gap}px`,
background: snapshot.isDragging ? '#f4f4f4' : 'transparent', }}
}} >
className="p-2" {item.content}
> </div>
{item.content} )}
</div> </Draggable>
)} ))}
</Draggable>
))}
{provided.placeholder} {provided.placeholder}
</div> </div>
)} )}
</Droppable> </Droppable>
</div> </DragDropContext>
</DragDropContext>
</div> </div>
); );
}; };