Skip to content

Commit

Permalink
Improvements and bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Aug 13, 2024
1 parent f59fa77 commit dc39996
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 120 deletions.
7 changes: 0 additions & 7 deletions frontend/src/lib/components/DragList/index.ts

This file was deleted.

7 changes: 7 additions & 0 deletions frontend/src/lib/components/SortableList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { SortableList } from "./sortableList";
export { SortableListItem } from "./sortableListItem";
export { SortableListGroup } from "./sortableListGroup";

export type { SortableListProps } from "./sortableList";
export type { SortableListItemProps } from "./sortableListItem";
export type { SortableListGroupProps } from "./sortableListGroup";
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { Vec2, point2Distance } from "@lib/utils/vec2";

import { isEqual } from "lodash";

import { DragListGroupProps } from "./dragListGroup";
import { DragListItemProps } from "./dragListItem";
import { SortableListGroupProps } from "./sortableListGroup";
import { SortableListItemProps } from "./sortableListItem";

export enum HoveredArea {
TOP = "top",
Expand All @@ -17,27 +17,27 @@ export enum HoveredArea {
CENTER = "center",
}

export type DragListContextType = {
export type SortableListContextType = {
draggedElementId: string | null;
hoveredElementId: string | null;
hoveredArea: HoveredArea | null;
dragPosition: Vec2 | null;
};

export const DragListContext = React.createContext<DragListContextType>({
export const SortableListContext = React.createContext<SortableListContextType>({
draggedElementId: null,
hoveredElementId: null,
hoveredArea: null,
dragPosition: null,
});

export type DragListProps = {
export type SortableListProps = {
contentWhenEmpty?: React.ReactNode;
children: React.ReactElement<DragListItemProps | DragListGroupProps>[];
children: React.ReactElement<SortableListItemProps | SortableListGroupProps>[];
onItemMove?: (itemId: string, originId: string | null, destinationId: string | null, position: number) => void;
};

function assertTargetIsDragListItemAndExtractProps(
function assertTargetIsSortableListItemAndExtractProps(
target: EventTarget | null
): { element: HTMLElement; id: string; parentId: string | null } | null {
if (!target) {
Expand All @@ -49,37 +49,37 @@ function assertTargetIsDragListItemAndExtractProps(
return null;
}

const dragListItemIndicator = element.closest(".drag-list-element-indicator");
if (!dragListItemIndicator) {
const sortableListItemIndicator = element.closest(".sortable-list-element-indicator");
if (!sortableListItemIndicator) {
return null;
}

const dragListElement = element.closest(".drag-list-element");
if (!dragListElement) {
const sortableListElement = element.closest(".sortable-list-element");
if (!sortableListElement) {
return null;
}

if (!(dragListElement instanceof HTMLElement)) {
if (!(sortableListElement instanceof HTMLElement)) {
return null;
}

const id = dragListElement.dataset.itemId;
const id = sortableListElement.dataset.itemId;
if (!id) {
return null;
}

if (
dragListElement.parentElement &&
dragListElement.parentElement instanceof HTMLElement &&
dragListElement.parentElement.classList.contains("drag-list-group")
sortableListElement.parentElement &&
sortableListElement.parentElement instanceof HTMLElement &&
sortableListElement.parentElement.classList.contains("sortable-list-group")
) {
const parentId = dragListElement.parentElement.dataset.itemId;
const parentId = sortableListElement.parentElement.dataset.itemId;
if (parentId) {
return { element: dragListElement, id, parentId };
return { element: sortableListElement, id, parentId };
}
}

return { element: dragListElement, id, parentId: null };
return { element: sortableListElement, id, parentId: null };
}

enum ItemType {
Expand All @@ -92,9 +92,9 @@ type HoveredItemIdAndArea = {
area: HoveredArea;
};

const ELEMENT_TOP_AND_CENTER_AREA_SIZE_IN_PX = 10;
const ELEMENT_TOP_AND_CENTER_AREA_SIZE_IN_PERCENT = 50;

export function DragList(props: DragListProps): React.ReactNode {
export function SortableList(props: SortableListProps): React.ReactNode {
const { onItemMove } = props;

const [isDragging, setIsDragging] = React.useState<boolean>(false);
Expand All @@ -103,7 +103,7 @@ export function DragList(props: DragListProps): React.ReactNode {
const [dragPosition, setDragPosition] = React.useState<Vec2>({ x: 0, y: 0 });
const [currentScrollPosition, setCurrentScrollPosition] = React.useState<number>(0);
const [prevChildren, setPrevChildren] = React.useState<
React.ReactElement<DragListItemProps | DragListGroupProps>[]
React.ReactElement<SortableListItemProps | SortableListGroupProps>[]
>(props.children);

const listDivRef = React.useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -150,13 +150,13 @@ export function DragList(props: DragListProps): React.ReactNode {
return;
}

const dragListItemProps = assertTargetIsDragListItemAndExtractProps(target);
if (!dragListItemProps) {
const sortableListItemProps = assertTargetIsSortableListItemAndExtractProps(target);
if (!sortableListItemProps) {
return;
}

const element = dragListItemProps.element;
draggedElement = dragListItemProps;
const element = sortableListItemProps.element;
draggedElement = sortableListItemProps;

pointerDownPosition = { x: e.clientX, y: e.clientY };
draggingActive = false;
Expand Down Expand Up @@ -225,12 +225,12 @@ export function DragList(props: DragListProps): React.ReactNode {
const parentElement = parent ?? currentListDivRef;

for (const child of parentElement.children) {
if (child instanceof HTMLElement && child.classList.contains("drag-list-item")) {
if (child instanceof HTMLElement && child.classList.contains("sortable-list-item")) {
items.push(child);
}
if (child instanceof HTMLElement && child.classList.contains("drag-list-group")) {
if (child instanceof HTMLElement && child.classList.contains("sortable-list-group")) {
items.push(child);
const content = child.querySelector(".drag-list-group-content");
const content = child.querySelector(".sortable-list-group-content");
if (content && content instanceof HTMLElement) {
items.push(...getDragElementsRecursively(content));
}
Expand All @@ -245,7 +245,7 @@ export function DragList(props: DragListProps): React.ReactNode {
if (rectContainsPoint(item.getBoundingClientRect(), { x: e.clientX, y: e.clientY })) {
const type = getItemType(item);
if (type === ItemType.CONTAINER) {
const content = item.querySelector(".drag-list-group-content");
const content = item.querySelector(".sortable-list-group-content");
if (
content &&
rectContainsPoint(content.getBoundingClientRect(), { x: e.clientX, y: e.clientY })
Expand All @@ -262,9 +262,9 @@ export function DragList(props: DragListProps): React.ReactNode {
}

function getItemType(item: HTMLElement): ItemType | null {
if (item.classList.contains("drag-list-item")) {
if (item.classList.contains("sortable-list-item")) {
return ItemType.ITEM;
} else if (item.classList.contains("drag-list-group")) {
} else if (item.classList.contains("sortable-list-group")) {
return ItemType.CONTAINER;
}
return null;
Expand All @@ -273,20 +273,20 @@ export function DragList(props: DragListProps): React.ReactNode {
function getHoveredAreaOfItem(item: HTMLElement, e: PointerEvent): HoveredArea {
const rect = item.getBoundingClientRect();
const topAreaTop = rect.top;
const topAreaBottom = rect.top + ELEMENT_TOP_AND_CENTER_AREA_SIZE_IN_PX;
const topAreaBottom = rect.top + (ELEMENT_TOP_AND_CENTER_AREA_SIZE_IN_PERCENT / 100) * rect.height;

if (e.clientY >= topAreaTop && e.clientY <= topAreaBottom) {
return HoveredArea.TOP;
}

const bottomAreaTop = rect.bottom - ELEMENT_TOP_AND_CENTER_AREA_SIZE_IN_PX;
const bottomAreaTop = rect.bottom - (ELEMENT_TOP_AND_CENTER_AREA_SIZE_IN_PERCENT / 100) * rect.height;
const bottomAreaBottom = rect.bottom;

if (e.clientY >= bottomAreaTop && e.clientY <= bottomAreaBottom) {
return HoveredArea.BOTTOM;
}

const headerElement = item.querySelector(".drag-list-item-header");
const headerElement = item.querySelector(".sortable-list-item-header");
if (!headerElement) {
return HoveredArea.CENTER;
}
Expand All @@ -300,15 +300,15 @@ export function DragList(props: DragListProps): React.ReactNode {
}

function getItemParentGroupId(item: HTMLElement): string | null {
const group = item.parentElement?.closest(".drag-list-group");
const group = item.parentElement?.closest(".sortable-list-group");
if (!group || !(group instanceof HTMLElement)) {
return null;
}
return group.dataset.itemId ?? null;
}

function getItemPositionInGroup(item: HTMLElement): number {
let group = item.parentElement?.closest(".drag-list-group-content");
let group = item.parentElement?.closest(".sortable-list-group-content");
if (!group || !(group instanceof HTMLElement)) {
group = currentListDivRef;
}
Expand Down Expand Up @@ -466,7 +466,7 @@ export function DragList(props: DragListProps): React.ReactNode {
if (typeof child.type === "string") {
continue;
}
if (child.type.name === "DragListItem") {
if (child.type.name === "SortableListItem") {
children.push(
React.cloneElement(child, {
key: child.props.id,
Expand All @@ -485,7 +485,7 @@ export function DragList(props: DragListProps): React.ReactNode {

return (
<div className="w-full h-full flex flex-col relative">
<DragListContext.Provider
<SortableListContext.Provider
value={{
draggedElementId: draggedItemId,
hoveredElementId: hoveredItemIdAndArea?.id ?? null,
Expand Down Expand Up @@ -519,7 +519,7 @@ export function DragList(props: DragListProps): React.ReactNode {
})}
></div>
)}
</DragListContext.Provider>
</SortableListContext.Provider>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function DragListDropIndicator() {
export function SortableListDropIndicator() {
return (
<div className="w-full h-0 relative">
<div className="absolute -top-0.5 h-1 w-full bg-blue-800 z-10"></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,41 @@ import { createPortal } from "@lib/utils/createPortal";
import { resolveClassNames } from "@lib/utils/resolveClassNames";
import { DragIndicator, ExpandLess, ExpandMore } from "@mui/icons-material";

import { DragListContext, HoveredArea } from "./dragList";
import { DragListDropIndicator } from "./dragListDropIndicator";
import { DragListItemProps } from "./dragListItem";
import { HoveredArea, SortableListContext } from "./sortableList";
import { SortableListDropIndicator } from "./sortableListDropIndicator";
import { SortableListItemProps } from "./sortableListItem";

export type DragListGroupProps = {
export type SortableListGroupProps = {
id: string;
icon?: React.ReactNode;
title: string;
startAdornment?: React.ReactNode;
endAdornment?: React.ReactNode;
contentWhenEmpty?: React.ReactNode;
children: React.ReactElement<DragListItemProps>[];
children: React.ReactElement<SortableListItemProps>[];
};

export function DragListGroup(props: DragListGroupProps): React.ReactNode {
export function SortableListGroup(props: SortableListGroupProps): React.ReactNode {
const [isExpanded, setIsExpanded] = React.useState<boolean>(true);

const divRef = React.useRef<HTMLDivElement>(null);
const boundingClientRect = useElementBoundingRect(divRef);
const dragListContext = React.useContext(DragListContext);
const sortableListContext = React.useContext(SortableListContext);

const isHovered = dragListContext.hoveredElementId === props.id;
const isHeaderHovered = isHovered && dragListContext.hoveredArea === HoveredArea.HEADER;
const isDragging = dragListContext.draggedElementId === props.id;
const dragPosition = dragListContext.dragPosition;
const isHovered = sortableListContext.hoveredElementId === props.id;
const isHeaderHovered = isHovered && sortableListContext.hoveredArea === HoveredArea.HEADER;
const isDragging = sortableListContext.draggedElementId === props.id;
const dragPosition = sortableListContext.dragPosition;

function handleToggleExpanded() {
setIsExpanded(!isExpanded);
}

return (
<>
{isHovered && dragListContext.hoveredArea === HoveredArea.TOP && <DragListDropIndicator />}
{isHovered && sortableListContext.hoveredArea === HoveredArea.TOP && <SortableListDropIndicator />}
<div
className={resolveClassNames("drag-list-element drag-list-group relative", {
className={resolveClassNames("sortable-list-element sortable-list-group relative", {
"bg-blue-200": isHeaderHovered,
"bg-gray-200": !isHeaderHovered,
})}
Expand All @@ -57,7 +57,7 @@ export function DragListGroup(props: DragListGroupProps): React.ReactNode {
createPortal(
<div
className={resolveClassNames(
"flex h-8 px-1 bg-blue-50 text-sm items-center gap-1 border-b border-b-gray-300 absolute z-50"
"flex h-8 px-1 bg-blue-50 text-sm items-center gap-1 border-b border-b-gray-300 absolute z-50 opacity-75"
)}
style={{
left: dragPosition.x,
Expand All @@ -70,7 +70,7 @@ export function DragListGroup(props: DragListGroupProps): React.ReactNode {
)}
<div
className={resolveClassNames(
"drag-list-group-content pl-2 bg-white mb-1 shadow-inner border-b border-b-gray-300",
"sortable-list-group-content pl-2 bg-white mb-1 shadow-inner border-b border-b-gray-300",
{
"overflow-hidden h-[0px]": !isExpanded,
}
Expand All @@ -79,7 +79,7 @@ export function DragListGroup(props: DragListGroupProps): React.ReactNode {
{props.children.length === 0 ? props.contentWhenEmpty : props.children}
</div>
</div>
{isHovered && dragListContext.hoveredArea === HoveredArea.BOTTOM && <DragListDropIndicator />}
{isHovered && sortableListContext.hoveredArea === HoveredArea.BOTTOM && <SortableListDropIndicator />}
</>
);
}
Expand All @@ -95,8 +95,8 @@ type HeaderProps = {

function Header(props: HeaderProps): React.ReactNode {
return (
<div className="drag-list-item-header flex items-center gap-1 h-8 text-sm font-bold border-b border-b-gray-300">
<div className={resolveClassNames("drag-list-element-indicator px-0.5 hover:cursor-grab")}>
<div className="sortable-list-item-header flex items-center gap-1 h-8 text-sm font-bold border-b border-b-gray-300">
<div className={resolveClassNames("sortable-list-element-indicator px-0.5 hover:cursor-grab")}>
<DragIndicator fontSize="inherit" className="pointer-events-none" />
</div>
<div
Expand Down
Loading

0 comments on commit dc39996

Please sign in to comment.