2026-02-24 20:44:16 +00:00
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
|
|
import TailwindModal from "./TailwindModal";
|
|
|
|
|
|
import { ComponentInfo, HookInfo, PropertyInfo } from "../../proxy/developerKit/componentInfo";
|
|
|
|
|
|
import { getComponentDefinition } from "./data/componentDefinitions";
|
2026-03-30 20:40:20 +00:00
|
|
|
|
import { Button } from "../ui";
|
2026-02-24 20:44:16 +00:00
|
|
|
|
|
|
|
|
|
|
interface PropertyPanelProps {
|
|
|
|
|
|
selectedComponent: ComponentInfo | null;
|
|
|
|
|
|
currentCode: string;
|
|
|
|
|
|
onPropertiesChange: (
|
|
|
|
|
|
componentId: string,
|
|
|
|
|
|
updates: Record<string, any>
|
|
|
|
|
|
) => void;
|
|
|
|
|
|
onHookToggle: (
|
|
|
|
|
|
componentId: string,
|
|
|
|
|
|
hookType: string,
|
|
|
|
|
|
enabled: boolean
|
|
|
|
|
|
) => void;
|
|
|
|
|
|
onMultipleHookToggle: (
|
|
|
|
|
|
toggles: { componentId: string; hookType: string; enabled: boolean }[]
|
|
|
|
|
|
) => void;
|
|
|
|
|
|
onDeleteComponent: (componentId: string) => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const PropertyPanel: React.FC<PropertyPanelProps> = ({
|
|
|
|
|
|
selectedComponent,
|
|
|
|
|
|
currentCode,
|
|
|
|
|
|
onPropertiesChange,
|
|
|
|
|
|
onHookToggle,
|
|
|
|
|
|
onMultipleHookToggle,
|
|
|
|
|
|
onDeleteComponent,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const [tailwindModalOpen, setTailwindModalOpen] = useState(false);
|
|
|
|
|
|
const [currentTailwindProperty, setCurrentTailwindProperty] =
|
|
|
|
|
|
useState<string>("");
|
|
|
|
|
|
const [activeHooks, setActiveHooks] = useState<Set<string>>(new Set());
|
|
|
|
|
|
const [activeTab, setActiveTab] = useState<"props" | "hooks">("props");
|
|
|
|
|
|
|
|
|
|
|
|
// Local state for pending changes
|
|
|
|
|
|
const [pendingProperties, setPendingProperties] = useState<
|
|
|
|
|
|
Record<string, any>
|
|
|
|
|
|
>({});
|
|
|
|
|
|
const [pendingEvents, setPendingEvents] = useState<Record<string, string>>(
|
|
|
|
|
|
{}
|
|
|
|
|
|
);
|
|
|
|
|
|
const [pendingHooks, setPendingHooks] = useState<Record<string, boolean>>({});
|
|
|
|
|
|
const [hasChanges, setHasChanges] = useState(false);
|
|
|
|
|
|
const [hasHookChanges, setHasHookChanges] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
const componentDefinition = selectedComponent
|
|
|
|
|
|
? getComponentDefinition(selectedComponent.name)
|
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
|
|
// Reset pending changes when component changes
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
setPendingProperties({});
|
|
|
|
|
|
setPendingEvents({});
|
|
|
|
|
|
setPendingHooks({});
|
|
|
|
|
|
setHasChanges(false);
|
|
|
|
|
|
setHasHookChanges(false);
|
|
|
|
|
|
}, [selectedComponent?.id]);
|
|
|
|
|
|
|
|
|
|
|
|
// Check which hooks are currently active in the code
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (selectedComponent && currentCode) {
|
|
|
|
|
|
const hooks = new Set<string>();
|
|
|
|
|
|
|
|
|
|
|
|
// Check for useState
|
|
|
|
|
|
if (
|
|
|
|
|
|
currentCode.includes(
|
|
|
|
|
|
`const [state_${selectedComponent.id}, setState_${selectedComponent.id}]`
|
|
|
|
|
|
)
|
|
|
|
|
|
) {
|
|
|
|
|
|
hooks.add("useState");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for useRef
|
|
|
|
|
|
if (
|
|
|
|
|
|
currentCode.includes(`const ref_${selectedComponent.id}`) &&
|
|
|
|
|
|
currentCode.includes(`ref={ref_${selectedComponent.id}`)
|
|
|
|
|
|
) {
|
|
|
|
|
|
hooks.add("useRef");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// // Check for useEffect
|
|
|
|
|
|
// if (
|
|
|
|
|
|
// currentCode.includes("useEffect") &&
|
|
|
|
|
|
// currentCode.includes(selectedComponent.id)
|
|
|
|
|
|
// ) {
|
|
|
|
|
|
// hooks.add("useEffect");
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
setActiveHooks(hooks);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [selectedComponent, currentCode]);
|
|
|
|
|
|
|
|
|
|
|
|
// Handle local property changes
|
|
|
|
|
|
const handleLocalPropertyChange = (propName: string, value: any) => {
|
|
|
|
|
|
setPendingProperties((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[propName]: value,
|
|
|
|
|
|
}));
|
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Handle local hook changes
|
|
|
|
|
|
const handleLocalHookToggle = (hookType: string, enabled: boolean) => {
|
|
|
|
|
|
setPendingHooks((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[hookType]: enabled,
|
|
|
|
|
|
}));
|
|
|
|
|
|
setHasHookChanges(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Handle local event changes
|
|
|
|
|
|
const handleLocalEventChange = (eventName: string, value: string) => {
|
|
|
|
|
|
setPendingEvents((prev) => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[eventName]: value,
|
|
|
|
|
|
}));
|
|
|
|
|
|
setHasChanges(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Apply only property/event changes
|
|
|
|
|
|
const handleApplyPropChanges = () => {
|
|
|
|
|
|
if (!selectedComponent) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Combine all changes into a single update object
|
|
|
|
|
|
const allUpdates = {
|
|
|
|
|
|
...pendingProperties,
|
|
|
|
|
|
...pendingEvents,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Apply property and event changes together
|
|
|
|
|
|
if (Object.keys(allUpdates).length > 0) {
|
|
|
|
|
|
onPropertiesChange(selectedComponent.id, allUpdates);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reset pending changes
|
|
|
|
|
|
setPendingProperties({});
|
|
|
|
|
|
setPendingEvents({});
|
|
|
|
|
|
setHasChanges(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Apply only hook changes
|
|
|
|
|
|
const handleApplyHookChanges = () => {
|
|
|
|
|
|
if (!selectedComponent) return;
|
|
|
|
|
|
|
|
|
|
|
|
const hookToggles = Object.entries(pendingHooks).map(
|
|
|
|
|
|
([hookType, enabled]) => ({
|
|
|
|
|
|
componentId: selectedComponent.id,
|
|
|
|
|
|
hookType,
|
|
|
|
|
|
enabled,
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
if (hookToggles.length > 1) {
|
|
|
|
|
|
onMultipleHookToggle(hookToggles);
|
|
|
|
|
|
} else if (hookToggles.length === 1) {
|
|
|
|
|
|
const { componentId, hookType, enabled } = hookToggles[0];
|
|
|
|
|
|
onHookToggle(componentId, hookType, enabled);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reset pending changes
|
|
|
|
|
|
setPendingHooks({});
|
|
|
|
|
|
setHasHookChanges(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Reset all pending changes
|
|
|
|
|
|
const handleResetChanges = () => {
|
|
|
|
|
|
setPendingProperties({});
|
|
|
|
|
|
setPendingEvents({});
|
|
|
|
|
|
setPendingHooks({});
|
|
|
|
|
|
setHasChanges(false);
|
|
|
|
|
|
setHasHookChanges(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const openTailwindModal = (propertyName: string) => {
|
|
|
|
|
|
setCurrentTailwindProperty(propertyName);
|
|
|
|
|
|
setTailwindModalOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleTailwindClassSelect = (className: string) => {
|
|
|
|
|
|
const currentValue =
|
|
|
|
|
|
pendingProperties[currentTailwindProperty] ||
|
|
|
|
|
|
selectedComponent?.props[currentTailwindProperty] ||
|
|
|
|
|
|
"";
|
|
|
|
|
|
const newValue = currentValue ? `${currentValue} ${className}` : className;
|
|
|
|
|
|
handleLocalPropertyChange(currentTailwindProperty, newValue);
|
|
|
|
|
|
// Don't close modal - let user continue selecting
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const renderPropertyControl = (property: PropertyInfo) => {
|
|
|
|
|
|
// Handle children property specially - get from children, not props
|
|
|
|
|
|
const currentValue =
|
|
|
|
|
|
property.name === "children"
|
|
|
|
|
|
? typeof selectedComponent?.children === "string"
|
|
|
|
|
|
? selectedComponent.children
|
|
|
|
|
|
: selectedComponent?.props[property.name] || property.value
|
|
|
|
|
|
: selectedComponent?.props[property.name] || property.value;
|
|
|
|
|
|
|
|
|
|
|
|
// Use pending value if available, otherwise use current value
|
|
|
|
|
|
const value =
|
|
|
|
|
|
pendingProperties[property.name] !== undefined
|
|
|
|
|
|
? pendingProperties[property.name]
|
|
|
|
|
|
: currentValue;
|
|
|
|
|
|
const isTailwindProperty = ["className", "class", "css"].includes(
|
|
|
|
|
|
property.name
|
|
|
|
|
|
);
|
|
|
|
|
|
const isColorProperty = property.name.toLowerCase().includes("color");
|
|
|
|
|
|
|
|
|
|
|
|
// Don't show children property if component has nested elements
|
|
|
|
|
|
if (
|
|
|
|
|
|
property.name === "children" &&
|
|
|
|
|
|
Array.isArray(selectedComponent?.children)
|
|
|
|
|
|
) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let arrayError = "";
|
|
|
|
|
|
let arrayInputValue = "";
|
|
|
|
|
|
if (property.type === "array") {
|
|
|
|
|
|
try {
|
|
|
|
|
|
arrayInputValue = JSON.stringify(value, null, 2);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
arrayInputValue = "";
|
|
|
|
|
|
arrayError = "Array verisi gösterilemiyor.";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={property.name} className="mb-4">
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{property.name}
|
|
|
|
|
|
{property.description && (
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<span className="text-gray-500 dark:text-gray-400 text-xs ml-1">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
({property.description})
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{pendingProperties[property.name] !== undefined && (
|
|
|
|
|
|
<span className="text-orange-500 text-xs ml-1">(değiştirildi)</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-2 flex-col">
|
|
|
|
|
|
{property.type === "boolean" && (
|
|
|
|
|
|
<label className="flex items-center">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={Boolean(value)}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleLocalPropertyChange(property.name, e.target.checked)
|
|
|
|
|
|
}
|
|
|
|
|
|
className="mr-2"
|
|
|
|
|
|
/>
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<span className="text-sm text-gray-600 dark:text-gray-300">{property.name}</span>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</label>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{property.type === "select" && property.options && (
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={value || ""}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleLocalPropertyChange(property.name, e.target.value)
|
|
|
|
|
|
}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
>
|
|
|
|
|
|
<option value="">Select {property.name}</option>
|
|
|
|
|
|
{property.options.map((option) => (
|
|
|
|
|
|
<option key={option} value={option}>
|
|
|
|
|
|
{option}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{property.type === "function" && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={value || ""}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleLocalPropertyChange(property.name, e.target.value)
|
|
|
|
|
|
}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
placeholder={`Enter ${property.name}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{isTailwindProperty && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => openTailwindModal(property.name)}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white transition-colors text-sm"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
title="Select Tailwind Classes"
|
|
|
|
|
|
>
|
|
|
|
|
|
TW
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{isColorProperty && (
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
value={value || "#000000"}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleLocalPropertyChange(property.name, e.target.value)
|
|
|
|
|
|
}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="w-10 h-10 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{property.type === "string" && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={value || ""}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleLocalPropertyChange(property.name, e.target.value)
|
|
|
|
|
|
}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
placeholder={`Enter ${property.name}`}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{isTailwindProperty && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => openTailwindModal(property.name)}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white transition-colors text-sm"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
title="Select Tailwind Classes"
|
|
|
|
|
|
>
|
|
|
|
|
|
TW
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{isColorProperty && (
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="color"
|
|
|
|
|
|
value={value || "#000000"}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleLocalPropertyChange(property.name, e.target.value)
|
|
|
|
|
|
}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="w-10 h-10 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{property.type === "number" && (
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
value={value || 0}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
handleLocalPropertyChange(property.name, Number(e.target.value))
|
|
|
|
|
|
}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{property.type === "array" && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<textarea
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md font-mono text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white dark:placeholder-gray-400"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
rows={Math.max(3, arrayInputValue.split('\n').length)}
|
|
|
|
|
|
value={arrayInputValue}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsed = JSON.parse(e.target.value);
|
|
|
|
|
|
handleLocalPropertyChange(property.name, parsed);
|
|
|
|
|
|
arrayError = "";
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
arrayError = "Geçersiz JSON formatı";
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
placeholder="[\n { ... }\n]"
|
|
|
|
|
|
spellCheck={false}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{arrayError && (
|
|
|
|
|
|
<span className="text-xs text-red-500">{arrayError}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const renderHookControl = (hook: HookInfo) => {
|
|
|
|
|
|
const currentlyActive = activeHooks.has(hook.type);
|
|
|
|
|
|
const isActive =
|
|
|
|
|
|
pendingHooks[hook.type] !== undefined
|
|
|
|
|
|
? pendingHooks[hook.type]
|
|
|
|
|
|
: currentlyActive;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={hook.name} className="mb-4">
|
|
|
|
|
|
<label className="flex items-center">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
checked={isActive}
|
|
|
|
|
|
onChange={(e) => handleLocalHookToggle(hook.type, e.target.checked)}
|
|
|
|
|
|
className="mr-2"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="text-sm font-medium text-gray-700">
|
|
|
|
|
|
{hook.name} ({hook.type})
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{pendingHooks[hook.type] !== undefined && (
|
|
|
|
|
|
<span className="text-orange-500 text-xs ml-1">(değiştirildi)</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!selectedComponent) {
|
|
|
|
|
|
return (
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<div className="h-full bg-gray-50 dark:bg-gray-900 p-4">
|
|
|
|
|
|
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<div className="text-4xl mb-4">🎯</div>
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<h3 className="text-lg font-medium mb-2 text-gray-700 dark:text-gray-200">No Component Selected</h3>
|
|
|
|
|
|
<p className="text-sm text-gray-500 dark:text-gray-400">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
Select a component from the editor to edit its properties
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getDynamicProperties(): PropertyInfo[] {
|
|
|
|
|
|
if (!selectedComponent) return [];
|
|
|
|
|
|
|
|
|
|
|
|
const allDefinitionProps = componentDefinition?.properties || [];
|
|
|
|
|
|
|
|
|
|
|
|
// Sadece tanımdan gelen properties
|
|
|
|
|
|
const defProps = allDefinitionProps.filter(
|
|
|
|
|
|
(p: any) => p.category === "properties"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// allDefinitionNames prop isimlerini belirle
|
|
|
|
|
|
const allDefinitionNames = new Set(allDefinitionProps.map((p:any) => p.name));
|
|
|
|
|
|
|
|
|
|
|
|
// Koddan gelen tüm props (id hariç), styling hariç, events hariç
|
|
|
|
|
|
const codeProps = Object.entries(selectedComponent.props || {})
|
|
|
|
|
|
.filter(([name]) => name !== "id" && !allDefinitionNames.has(name))
|
|
|
|
|
|
.map(([name, value]) => {
|
|
|
|
|
|
let type: PropertyInfo["type"] = "string";
|
|
|
|
|
|
if (typeof value === "boolean") type = "boolean";
|
|
|
|
|
|
else if (typeof value === "number") type = "number";
|
|
|
|
|
|
else if (typeof value === "function") type = "function";
|
|
|
|
|
|
else if (Array.isArray(value)) type = "array";
|
|
|
|
|
|
else if (typeof value === "object" && value !== null) type = "object";
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
name,
|
|
|
|
|
|
type,
|
|
|
|
|
|
value,
|
|
|
|
|
|
category: "properties",
|
|
|
|
|
|
} as PropertyInfo;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Merge - öncelik kodda olanlar
|
|
|
|
|
|
const merged: PropertyInfo[] = [];
|
|
|
|
|
|
const seen = new Set<string>();
|
|
|
|
|
|
|
|
|
|
|
|
for (const prop of codeProps) {
|
|
|
|
|
|
merged.push(prop);
|
|
|
|
|
|
seen.add(prop.name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (const prop of defProps) {
|
|
|
|
|
|
if (!seen.has(prop.name)) {
|
|
|
|
|
|
merged.push(prop);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return merged;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const properties = getDynamicProperties();
|
|
|
|
|
|
const hooks = componentDefinition?.hooks || [];
|
|
|
|
|
|
const styling =
|
|
|
|
|
|
componentDefinition?.properties?.filter((p: any) => p.category === "styling") ||
|
|
|
|
|
|
[];
|
|
|
|
|
|
const events =
|
|
|
|
|
|
componentDefinition?.properties?.filter((p: any) => p.category === "events") ||
|
|
|
|
|
|
[];
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="w-full text-white flex flex-col h-full">
|
|
|
|
|
|
{/* Header */}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<div className="border-b bg-gray-50 dark:bg-gray-900 flex items-center justify-between dark:border-gray-700">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<div>
|
|
|
|
|
|
{(hasChanges || hasHookChanges) && (
|
|
|
|
|
|
<p className="text-sm text-orange-600 mt-1">
|
|
|
|
|
|
Bekleyen değişiklikler var
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* Tabs */}
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<div className="flex gap-2 p-1">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
<button
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className={`px-3 py-1 font-medium border-b-2 transition-colors ${
|
2026-02-24 20:44:16 +00:00
|
|
|
|
activeTab === "props"
|
2026-05-19 20:22:25 +00:00
|
|
|
|
? "border-blue-500 text-blue-700 bg-white dark:bg-gray-900 dark:text-blue-400 dark:border-blue-400"
|
|
|
|
|
|
: "border-transparent text-gray-500 bg-gray-100 dark:bg-gray-800 dark:text-gray-400 dark:border-transparent"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}`}
|
|
|
|
|
|
onClick={() => setActiveTab("props")}
|
|
|
|
|
|
>
|
|
|
|
|
|
Properties
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className={`px-3 py-1 font-medium border-b-2 transition-colors ${
|
2026-02-24 20:44:16 +00:00
|
|
|
|
activeTab === "hooks"
|
2026-05-19 20:22:25 +00:00
|
|
|
|
? "border-blue-500 text-blue-700 bg-white dark:bg-gray-900 dark:text-blue-400 dark:border-blue-400"
|
|
|
|
|
|
: "border-transparent text-gray-500 bg-gray-100 dark:bg-gray-800 dark:text-gray-400 dark:border-transparent"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
}`}
|
|
|
|
|
|
onClick={() => setActiveTab("hooks")}
|
|
|
|
|
|
>
|
|
|
|
|
|
Hooks
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/* Sil Butonu */}
|
2026-03-30 20:40:20 +00:00
|
|
|
|
<Button
|
|
|
|
|
|
variant="solid"
|
2026-05-05 17:59:30 +00:00
|
|
|
|
size="sm"
|
2026-05-19 20:22:25 +00:00
|
|
|
|
className="mr-2 px-3 py-1 rounded bg-red-500 text-white hover:bg-red-600 dark:bg-red-700 dark:hover:bg-red-800 dark:text-white transition-colors text-sm"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
onClick={() => {
|
|
|
|
|
|
if (selectedComponent) {
|
|
|
|
|
|
if (
|
|
|
|
|
|
window.confirm(
|
|
|
|
|
|
"Seçili komponenti silmek istediğinize emin misiniz?"
|
|
|
|
|
|
)
|
|
|
|
|
|
) {
|
|
|
|
|
|
onDeleteComponent(selectedComponent.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
title="Komponenti Sil"
|
|
|
|
|
|
>
|
|
|
|
|
|
Sil
|
2026-03-30 20:40:20 +00:00
|
|
|
|
</Button>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Footer Action Buttons - her iki tabda da sabit */}
|
|
|
|
|
|
<div className="p-4 border-t">
|
|
|
|
|
|
<div className="flex gap-2">
|
2026-03-30 20:40:20 +00:00
|
|
|
|
<Button
|
2026-05-05 17:59:30 +00:00
|
|
|
|
size="sm"
|
2026-03-30 20:40:20 +00:00
|
|
|
|
variant="solid"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
onClick={
|
|
|
|
|
|
activeTab === "props"
|
|
|
|
|
|
? handleApplyPropChanges
|
|
|
|
|
|
: handleApplyHookChanges
|
|
|
|
|
|
}
|
|
|
|
|
|
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
|
2026-03-30 20:40:20 +00:00
|
|
|
|
className={`flex-1 rounded-md font-medium transition-colors ${
|
2026-02-24 20:44:16 +00:00
|
|
|
|
(activeTab === "props" ? hasChanges : hasHookChanges)
|
|
|
|
|
|
? "bg-green-500 text-white hover:bg-green-600"
|
|
|
|
|
|
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
Uygula
|
2026-03-30 20:40:20 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
2026-05-05 17:59:30 +00:00
|
|
|
|
size="sm"
|
2026-03-30 20:40:20 +00:00
|
|
|
|
variant="default"
|
2026-02-24 20:44:16 +00:00
|
|
|
|
onClick={
|
|
|
|
|
|
activeTab === "props"
|
|
|
|
|
|
? handleResetChanges
|
|
|
|
|
|
: () => {
|
|
|
|
|
|
setPendingHooks({});
|
|
|
|
|
|
setHasHookChanges(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
disabled={activeTab === "props" ? !hasChanges : !hasHookChanges}
|
2026-03-30 20:40:20 +00:00
|
|
|
|
className={`flex-1 rounded-md font-medium transition-colors ${
|
2026-02-24 20:44:16 +00:00
|
|
|
|
(activeTab === "props" ? hasChanges : hasHookChanges)
|
|
|
|
|
|
? "bg-red-500 text-white hover:bg-red-600"
|
|
|
|
|
|
: "bg-gray-300 text-gray-500 cursor-not-allowed"
|
|
|
|
|
|
}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
İptal
|
2026-03-30 20:40:20 +00:00
|
|
|
|
</Button>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Content */}
|
|
|
|
|
|
{activeTab === "props" && (
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<div className="flex-1 text-black dark:text-gray-200 overflow-y-auto p-4 max-h-[calc(100vh-200px)] bg-white dark:bg-gray-900">
|
|
|
|
|
|
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4">Properties</h3>
|
2026-02-24 20:44:16 +00:00
|
|
|
|
{/* Properties */}
|
|
|
|
|
|
{properties.length > 0 && (
|
|
|
|
|
|
<div>{properties.map(renderPropertyControl)}</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* Events */}
|
|
|
|
|
|
{events.length > 0 && (
|
|
|
|
|
|
<div>
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4 mt-6">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
Events
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
{events.map(renderPropertyControl)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* Styling */}
|
|
|
|
|
|
{styling.length > 0 && (
|
|
|
|
|
|
<div>
|
2026-05-19 20:22:25 +00:00
|
|
|
|
<h3 className="text-md font-medium text-gray-800 dark:text-gray-100 mb-4 mt-6">
|
2026-02-24 20:44:16 +00:00
|
|
|
|
Styling
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
{styling.map(renderPropertyControl)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{activeTab === "hooks" && (
|
|
|
|
|
|
<div className="flex-1 overflow-y-auto p-4">
|
|
|
|
|
|
{/* Sadece useState ve useRef göster */}
|
|
|
|
|
|
{hooks
|
|
|
|
|
|
.filter((h: any) => h.type === "useState" || h.type === "useRef")
|
|
|
|
|
|
.map(renderHookControl)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Tailwind Modal */}
|
|
|
|
|
|
<TailwindModal
|
|
|
|
|
|
isOpen={tailwindModalOpen}
|
|
|
|
|
|
onClose={() => setTailwindModalOpen(false)}
|
|
|
|
|
|
onSelectClass={handleTailwindClassSelect}
|
|
|
|
|
|
currentValue={
|
|
|
|
|
|
pendingProperties[currentTailwindProperty] ||
|
|
|
|
|
|
(selectedComponent &&
|
|
|
|
|
|
selectedComponent.props[currentTailwindProperty]) ||
|
|
|
|
|
|
""
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
export default PropertyPanel;
|