191 lines
7.1 KiB
TypeScript
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>
|
|
);
|
|
};
|