erp-platform/ui/src/components/codeLayout/ComponentLibrary.tsx
2025-08-16 22:47:24 +03:00

191 lines
7.1 KiB
TypeScript

import React, { useMemo, useState } from "react";
import { FaSquare } from 'react-icons/fa';
import { AiOutlineSearch } from 'react-icons/ai';
import { ComponentDefinition, HookInfo, PropertyInfo } from "../../@types/componentInfo";
import { getAllComponentDefinitions } from "./data/componentDefinitions";
import navigationIcon from "@/configs/navigation-icon.config";
interface ComponentLibraryProps {
onDragStart: (componentDef: ComponentDefinition, e: React.DragEvent) => void;
}
export const ComponentLibrary: React.FC<ComponentLibraryProps> = ({
onDragStart,
}) => {
const [searchTerm, setSearchTerm] = useState("");
const handleDragStart = (
componentDef: ComponentDefinition,
e: React.DragEvent
) => {
e.stopPropagation();
try {
const data = JSON.stringify(componentDef);
e.dataTransfer.setData("text/plain", data);
e.dataTransfer.setData("application/json", data);
} catch (error) {
console.error("Error setting drag data:", error);
}
e.dataTransfer.effectAllowed = "copy";
if (e.dataTransfer.setDragImage) {
try {
const dragPreview = document.createElement("div");
dragPreview.className = "component-drag-preview";
dragPreview.innerHTML = `<div class="flex items-center">
<span class="mr-2">${componentDef.icon || "📦"}</span>
<span>${componentDef.name}</span>
</div>`;
Object.assign(dragPreview.style, {
position: "absolute",
top: "-1000px",
left: "0",
padding: "8px 12px",
backgroundColor: "white",
border: "2px solid #4f46e5",
borderRadius: "4px",
boxShadow: "0 2px 5px rgba(0,0,0,0.2)",
zIndex: "9999",
pointerEvents: "none",
});
document.body.appendChild(dragPreview);
e.dataTransfer.setDragImage(dragPreview, 20, 20);
setTimeout(() => document.body.removeChild(dragPreview), 0);
} catch (error) {
console.error("Error setting drag image:", error);
}
}
onDragStart(componentDef, e);
};
const filteredComponents = useMemo(
() =>
getAllComponentDefinitions().filter(
(comp) =>
comp.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
comp.description.toLowerCase().includes(searchTerm.toLowerCase())
),
[searchTerm]
);
const categories = [
"basic",
"form",
"layout",
"feedback",
"media",
"interactive",
"navigation",
"data",
]
.map((cat: any) => ({
id: cat,
name: cat,
components: filteredComponents?.filter((c) => c.category === cat),
}))
.filter((cat) => cat.components.length > 0);
const getIcon = (iconName: string): React.ComponentType<any> => {
return navigationIcon[iconName] || FaSquare;
};
return (
<div className="w-full bg-gray-900 text-white flex flex-col h-full">
{/* Arama kutusu */}
<div className="p-4 border-b border-gray-700">
<div className="relative">
<AiOutlineSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="Search Components..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-gray-800 border border-gray-600 rounded-lg text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
</div>
{/* Bileşen kategorileri */}
<div className="flex-1 overflow-y-auto">
<div className="p-4 space-y-6">
{categories.map((category) => (
<div key={category.id}>
<h3 className="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">
{category.name}
</h3>
<div className="space-y-1">
{category.components.map((componentDef) => {
const IconComponent = getIcon(componentDef.icon);
return (
<div
key={componentDef.name}
className="component-library-item flex items-center p-3 bg-gray-800 rounded-lg cursor-move hover:bg-gray-700 border border-gray-700 hover:border-gray-600"
draggable
onDragStart={(e) => handleDragStart(componentDef, e)}
onDragEnd={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
<div className="w-8 h-8 bg-gray-700 rounded-lg flex items-center justify-center mr-3 flex-shrink-0">
<IconComponent className="w-4 h-4 text-gray-300" />
</div>
<div className="flex-1 min-w-0 pointer-events-none">
<span className="text-sm font-medium text-gray-200 block truncate">
{componentDef.name}
</span>
<p className="text-xs text-gray-400 truncate">
{componentDef.description}
</p>
{/* Özellikler */}
{componentDef.properties?.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{componentDef.properties.slice(0, 2).map((prop: PropertyInfo) => (
<span
key={prop.name}
className="bg-gray-700 text-gray-300 text-[10px] px-2 py-0.5 rounded mr-1"
>
{prop.name}
</span>
))}
{componentDef.properties.length > 2 && (
<span className="text-gray-500 text-[10px]">
...
</span>
)}
</div>
)}
{/* Hooklar */}
{componentDef.hooks?.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
{componentDef.hooks.slice(0, 2).map((hook: HookInfo) => (
<span
key={hook.name}
className="bg-gray-900 text-green-300 text-[10px] px-2 py-0.5 rounded mr-1"
>
{hook.name}
</span>
))}
{componentDef.hooks.length > 2 && (
<span className="text-gray-500 text-[10px]">
...
</span>
)}
</div>
)}
</div>
</div>
);
})}
</div>
</div>
))}
</div>
</div>
</div>
);
};