125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
import React, { useState, useRef } from 'react';
|
||
import { LayoutGrid, Settings2, Trash2, GripVertical, X, Check } from 'lucide-react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { Logo } from './Logo';
|
||
|
||
interface SidebarItem {
|
||
id: string;
|
||
label: string;
|
||
icon: React.ReactNode;
|
||
}
|
||
|
||
interface SidebarProps {
|
||
items: SidebarItem[];
|
||
onReorder: (newItems: SidebarItem[]) => void;
|
||
onDelete: (id: string) => void;
|
||
onItemClick: (id: string) => void;
|
||
}
|
||
|
||
export const Sidebar: React.FC<SidebarProps> = ({ items, onReorder, onDelete, onItemClick }) => {
|
||
const navigate = useNavigate();
|
||
const [isEditing, setIsEditing] = useState(false);
|
||
const dragItem = useRef<number | null>(null);
|
||
const dragOverItem = useRef<number | null>(null);
|
||
|
||
const handleDragStart = (e: React.DragEvent<HTMLDivElement>, position: number) => {
|
||
dragItem.current = position;
|
||
e.dataTransfer.effectAllowed = "move";
|
||
// Add a ghost class or style if needed
|
||
};
|
||
|
||
const handleDragEnter = (e: React.DragEvent<HTMLDivElement>, position: number) => {
|
||
dragOverItem.current = position;
|
||
};
|
||
|
||
const handleDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
|
||
e.preventDefault();
|
||
if (dragItem.current !== null && dragOverItem.current !== null) {
|
||
const copyListItems = [...items];
|
||
const dragItemContent = copyListItems[dragItem.current];
|
||
copyListItems.splice(dragItem.current, 1);
|
||
copyListItems.splice(dragOverItem.current, 0, dragItemContent);
|
||
onReorder(copyListItems);
|
||
}
|
||
dragItem.current = null;
|
||
dragOverItem.current = null;
|
||
};
|
||
|
||
return (
|
||
<aside className="w-64 bg-white border-r border-slate-200 flex-col hidden md:flex h-screen sticky top-0 select-none">
|
||
<div
|
||
className="h-16 flex items-center px-6 border-b border-slate-100 cursor-pointer hover:bg-slate-50 transition-colors"
|
||
onClick={() => navigate('/')}
|
||
>
|
||
<Logo />
|
||
</div>
|
||
|
||
<div className="flex-1 overflow-y-auto py-6 px-3 space-y-6">
|
||
<div>
|
||
<div className="flex items-center justify-between px-3 mb-2 text-slate-900 font-semibold group">
|
||
<div className="flex items-center gap-3">
|
||
<LayoutGrid size={20} />
|
||
<span>评级数据快照</span>
|
||
</div>
|
||
<button
|
||
onClick={() => setIsEditing(!isEditing)}
|
||
className={`p-1.5 rounded-md transition-all ${isEditing ? 'bg-blue-100 text-blue-600' : 'text-slate-400 hover:text-slate-700 hover:bg-slate-100'}`}
|
||
title={isEditing ? "完成编辑" : "管理维度"}
|
||
>
|
||
{isEditing ? <Check size={16} /> : <Settings2 size={16} />}
|
||
</button>
|
||
</div>
|
||
<div className="text-xs font-medium text-slate-500 px-3 mb-3 uppercase tracking-wider">
|
||
贵州茅台 (600519.SH)
|
||
</div>
|
||
|
||
<nav className="space-y-1">
|
||
{items.map((item, index) => (
|
||
<div
|
||
key={item.id}
|
||
draggable={isEditing}
|
||
onDragStart={(e) => handleDragStart(e, index)}
|
||
onDragEnter={(e) => handleDragEnter(e, index)}
|
||
onDragEnd={handleDragEnd}
|
||
onDragOver={(e) => e.preventDefault()}
|
||
onClick={() => !isEditing && onItemClick(item.id)}
|
||
className={`
|
||
flex items-center gap-3 px-3 py-2 rounded-lg font-medium text-sm transition-all relative group
|
||
${isEditing ? 'cursor-move hover:bg-slate-50 border border-transparent hover:border-slate-200' : 'cursor-pointer text-slate-600 hover:bg-slate-50'}
|
||
`}
|
||
>
|
||
{isEditing && (
|
||
<div className="text-slate-400">
|
||
<GripVertical size={16} />
|
||
</div>
|
||
)}
|
||
<div className={`${isEditing ? 'text-slate-500' : 'text-slate-600'}`}>
|
||
{item.icon}
|
||
</div>
|
||
<span className="flex-1">{item.label}</span>
|
||
|
||
{isEditing && (
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
onDelete(item.id);
|
||
}}
|
||
className="p-1.5 text-slate-400 hover:text-red-500 hover:bg-red-50 rounded-md transition-colors"
|
||
>
|
||
<Trash2 size={16} />
|
||
</button>
|
||
)}
|
||
</div>
|
||
))}
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
{isEditing && (
|
||
<div className="p-4 bg-blue-50/50 border-t border-blue-100 text-center text-xs text-blue-600 font-medium">
|
||
拖拽可排序,点击垃圾桶删除
|
||
</div>
|
||
)}
|
||
</aside>
|
||
);
|
||
}; |