66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
|
|
import React from 'react';
|
||
|
|
import { useSortable } from '@dnd-kit/sortable';
|
||
|
|
import { CSS } from '@dnd-kit/utilities';
|
||
|
|
import { GripVertical, FolderOpen } from 'lucide-react';
|
||
|
|
import * as LucideIcons from 'lucide-react';
|
||
|
|
import clsx from 'clsx';
|
||
|
|
|
||
|
|
interface SortableMenuItemProps {
|
||
|
|
id: string;
|
||
|
|
label: string;
|
||
|
|
icon: string;
|
||
|
|
isGroup?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getIcon(name: string): React.ReactNode {
|
||
|
|
const Icon = (LucideIcons as any)[name];
|
||
|
|
return Icon ? <Icon className="h-4 w-4" /> : <LucideIcons.FileText className="h-4 w-4" />;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function SortableMenuItem({ id, label, icon, isGroup }: SortableMenuItemProps) {
|
||
|
|
const {
|
||
|
|
attributes,
|
||
|
|
listeners,
|
||
|
|
setNodeRef,
|
||
|
|
transform,
|
||
|
|
transition,
|
||
|
|
isDragging,
|
||
|
|
} = useSortable({ id });
|
||
|
|
|
||
|
|
const style = {
|
||
|
|
transform: CSS.Transform.toString(transform),
|
||
|
|
transition,
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
ref={setNodeRef}
|
||
|
|
style={style}
|
||
|
|
className={clsx(
|
||
|
|
'flex items-center gap-3 px-3 py-2.5 rounded-md text-sm font-medium',
|
||
|
|
'bg-white border border-secondary-200',
|
||
|
|
'motion-safe:transition-shadow',
|
||
|
|
isDragging && 'shadow-lg ring-2 ring-primary-500 z-50',
|
||
|
|
!isDragging && 'hover:bg-secondary-50'
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
<button
|
||
|
|
className="cursor-grab active:cursor-grabbing text-secondary-400 hover:text-secondary-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 rounded"
|
||
|
|
{...attributes}
|
||
|
|
{...listeners}
|
||
|
|
aria-label="Ziehen zum Sortieren"
|
||
|
|
type="button"
|
||
|
|
>
|
||
|
|
<GripVertical className="w-4 h-4" />
|
||
|
|
</button>
|
||
|
|
<span className="text-secondary-400">
|
||
|
|
{isGroup ? <FolderOpen className="h-4 w-4" /> : getIcon(icon)}
|
||
|
|
</span>
|
||
|
|
<span className="flex-1 truncate">
|
||
|
|
{isGroup && <span className="text-xs font-semibold uppercase tracking-wider text-secondary-500 mr-2">{label}</span>}
|
||
|
|
{!isGroup && label}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|