Skip to content

Commit

Permalink
feat(panel): added drag-n-drop sortable components
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed Apr 18, 2024
1 parent 81f5484 commit fd98ac6
Show file tree
Hide file tree
Showing 3 changed files with 245 additions and 0 deletions.
108 changes: 108 additions & 0 deletions panel/src/components/dndSortable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { cn } from "@/lib/utils";
import { GripVerticalIcon } from "lucide-react";
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent
} from '@dnd-kit/core';
import {
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy
} from '@dnd-kit/sortable';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { useAutoAnimate } from '@formkit/auto-animate/react';


type DndSortableItemProps = {
id: string;
children: React.ReactNode;
disabled?: boolean;
}

export function DndSortableItem({ id, disabled, children }: DndSortableItemProps) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
} = useSortable({ id });

attributes.role = 'listitem'; //Override role due to having a drag handle
const style: React.CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
};

return (
<li
ref={setNodeRef} style={style} {...attributes}
className="bg-card rounded-lg border px-2 py-3 flex gap-3 relative aria-pressed:z-50 aria-pressed:opacity-85"
>
<div
{...listeners}
title="Drag to reorder"
className={cn(
"text-muted-foreground cursor-grab hover:text-primary hover:scale-110",
disabled ? "cursor-not-allowed" : ""
)}
>
<GripVerticalIcon className="size-6" />
</div>
{children}
</li>
)
}


type DndSortableGroupProps = {
onDragEnd: (event: DragEndEvent) => void;
children: React.ReactNode;
className?: string;
ids: string[];
}

export function DndSortableGroup({ onDragEnd, className, children, ids }: DndSortableGroupProps) {
const [autoAnimateParentRef, enableAnimations] = useAutoAnimate();
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);

const handleDragEnd = (event: DragEndEvent) => {
onDragEnd(event);
setTimeout(() => {
enableAnimations(true);
}, 1);
}

return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={() => enableAnimations(false)}
onDragCancel={() => enableAnimations(true)}
onDragEnd={handleDragEnd}
>
<SortableContext
items={ids}
strategy={verticalListSortingStrategy}
>
<ol
ref={autoAnimateParentRef}
className={className}
>
{children}
</ol>
</SortableContext>
</DndContext>
)
}
3 changes: 3 additions & 0 deletions panel/src/pages/TestingPage/TestingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import TmpToasts from "./TmpToasts";
import TmpApi from "./TmpApi";
import TmpFiller from "./TmpFiller";
import PresetReasons from "../PresetReasons/PresetReasonsPage";
import TmpSortable from "./TmpSortable";
import TmpDndSortable from "./TmpDndSortable";


export default function TestingPage() {
Expand All @@ -25,5 +27,6 @@ export default function TestingPage() {
{/* <TmpMarkdown /> */}
{/* <TmpColors /> */}
<PresetReasons />
<TmpDndSortable />
</div>;
}
134 changes: 134 additions & 0 deletions panel/src/pages/TestingPage/TmpDndSortable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { cn } from "@/lib/utils";
import { Settings2Icon, XIcon } from "lucide-react";
import { useState } from "react";
import type { DragEndEvent } from '@dnd-kit/core';
import { arrayMove } from '@dnd-kit/sortable';
import { DndSortableGroup, DndSortableItem } from "@/components/dndSortable";


type BanTemplateLineProps = {
text: string;
duration: string;
disabled: boolean;
onEdit: () => void;
onRemove: () => void;
}

function BanTemplateLine({ text, duration, disabled, onEdit, onRemove }: BanTemplateLineProps) {
const banDurationToString = (x: string) => x;

return (<>
<div className="grow flex items-center justify-items-start gap-2">
<span className="line-clamp-1">
{text}
</span>
</div>
<div className="flex gap-2">
<button
className={cn(
"text-muted-foreground",
disabled ? "opacity-50 cursor-not-allowed" : "hover:text-primary hover:scale-110"
)}
onClick={onEdit}
disabled={disabled}
>
<Settings2Icon className="size-6" />
</button>
<button
className={cn(
"text-muted-foreground",
disabled ? "opacity-50 cursor-not-allowed" : "hover:text-destructive hover:scale-110"
)}
onClick={onRemove}
disabled={disabled}
>
<XIcon className="size-6" />
</button>
</div>
</>);
}


const defaultList = [
'apple',
'banana',
'cherry',
'elderberry',
'fig',
'grape',
];

export default function TmpDndSortable() {
const [items, setItems] = useState(defaultList);

//Drag and drop
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (over && active.id !== over.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id as string);
const newIndex = items.indexOf(over.id as string);

return arrayMove(items, oldIndex, newIndex);
});
}
}

//Meus controled
const handleAdd = () => {
const newName = Math.random().toString(36).substring(2, 15);
setItems(curr => [...curr, newName])
}
const handleAddRandom = () => {
const newName = Math.random().toString(36).substring(2, 15);
const index = Math.floor(Math.random() * items.length);
setItems(curr => [...curr.slice(0, index), newName, ...curr.slice(index)]);
}
const handleRemoveRandom = () => {
setItems(curr => {
const index = Math.floor(Math.random() * curr.length);
return [...curr.slice(0, index), ...curr.slice(index + 1)];
});
}
const handleRemoveItem = (id: string) => {
console.log('Remove item', id);
setItems(curr => curr.filter(item => item !== id));
}
const handlePrintState = () => {
console.log(items);
}
const handleReset = () => {
setItems(defaultList);
}

return (
<div className='w-[500px] mx-auto space-y-4'>
<div className='w-full flex gap-2 justify-center'>
<button className='px-2 py-1 rounded bg-green-800' onClick={handleAdd}>ADD BOTTOM</button>
<button className='px-2 py-1 rounded bg-green-800' onClick={handleAddRandom}>ADD RND</button>
<button className='px-2 py-1 rounded bg-rose-800' onClick={handleRemoveRandom}>REMOVE RND</button>
<button className='px-2 py-1 rounded bg-rose-800' onClick={handleReset}>RESET</button>
<button className='px-2 py-1 rounded bg-blue-800' onClick={handlePrintState}>PRINT</button>
</div>

<DndSortableGroup
className="space-y-2"
ids={items}
onDragEnd={handleDragEnd}
>
{items.map((item) => (
<DndSortableItem key={item} id={item} disabled={false}>
<BanTemplateLine
text={item}
duration='permanent'
disabled={false}
onEdit={() => alert(`Editing ${item}`)}
onRemove={() => handleRemoveItem(item)}
/>
</DndSortableItem>
))}
{/* NOTE: Here you can add extra components inside the <ol> */}
</DndSortableGroup>
</div>
)
}

0 comments on commit fd98ac6

Please sign in to comment.