Remove HTML Preview Tool from navigation and add to gitignore

- Removed HTML Preview Tool from navigation menu in Layout.js
- Cleaned up unused Code import
- Added HTML Preview related files to .gitignore
- Project builds successfully without HTML Preview Tool
This commit is contained in:
dwindown
2025-08-04 13:04:11 +07:00
parent e1bc8d193d
commit 76c0a0d014
9 changed files with 897 additions and 159 deletions

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Code2, Home, ChevronDown, Menu, X, Database, FileText, Link as LinkIcon, Hash, FileSpreadsheet, Wand2, GitCompare, Code } from 'lucide-react';
import { Home, Hash, FileText, Key, Palette, QrCode, FileSpreadsheet, Wand2, GitCompare, Menu, X } from 'lucide-react';
import ThemeToggle from './ThemeToggle';
const Layout = ({ children }) => {
@@ -41,7 +41,7 @@ const Layout = ({ children }) => {
{ path: '/csv-json', name: 'CSV/JSON Tool', icon: FileSpreadsheet, description: 'Convert CSV ↔ JSON' },
{ path: '/beautifier', name: 'Beautifier Tool', icon: Wand2, description: 'Beautify/minify code' },
{ path: '/diff', name: 'Diff Tool', icon: GitCompare, description: 'Compare text differences' },
{ path: '/html-preview', name: 'HTML Preview', icon: Code, description: 'Render HTML, CSS, JS' }
];
return (

View File

@@ -316,7 +316,7 @@ const HtmlPreviewTool = () => {
<div className="p-4 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Code Editor</h2>
</div>
<div className="flex-1 p-4 overflow-y-auto">
<div className="flex-1 flex flex-col p-4">
<CodeInputs
htmlInput={htmlInput}
setHtmlInput={setHtmlInput}
@@ -381,7 +381,7 @@ const HtmlPreviewTool = () => {
<ToolLayout title="HTML Preview Tool">
<div className={`flex h-full ${inspectedElementInfo ? 'gap-4' : 'gap-6'}`}>
{/* Left column - Code inputs */}
<div className={`space-y-6 transition-all duration-300 ${
<div className={`flex flex-col transition-all duration-300 ${
inspectedElementInfo ? 'flex-1' : 'w-1/2'
}`}>
<CodeInputs

View File

@@ -1,5 +1,15 @@
import React, { useState } from 'react';
import { ChevronDown, ChevronUp, Maximize2, Minimize2 } from 'lucide-react';
import React, { useState, useRef } from 'react';
import { Search, Copy, Download } from 'lucide-react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/css/css';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/search/search';
import 'codemirror/addon/search/searchcursor';
import 'codemirror/addon/dialog/dialog';
import 'codemirror/addon/dialog/dialog.css';
const CodeInputs = ({
htmlInput,
@@ -10,129 +20,192 @@ const CodeInputs = ({
setJsInput,
isFullscreen
}) => {
const [showCss, setShowCss] = useState(false);
const [showJs, setShowJs] = useState(false);
const [htmlExtended, setHtmlExtended] = useState(true); // Default: HTML box extended
const [cssExtended, setCssExtended] = useState(false);
const [jsExtended, setJsExtended] = useState(false);
const [activeTab, setActiveTab] = useState('html');
const htmlEditorRef = useRef(null);
const cssEditorRef = useRef(null);
const jsEditorRef = useRef(null);
const getTextareaHeight = (type, isExtended) => {
if (isFullscreen) {
// In fullscreen, give HTML much more space as main script area
if (type === 'html') {
return isExtended ? 'h-[32rem]' : 'h-96'; // Much taller for HTML in fullscreen to balance with iPhone frame
} else {
return isExtended ? 'h-64' : 'h-32'; // CSS/JS remain smaller
}
// Handle search functionality
const handleSearch = (editorRef) => {
if (editorRef.current && editorRef.current.editor) {
editorRef.current.editor.execCommand('find');
}
// In non-fullscreen, give much more space for HTML as main script area
if (type === 'html') {
if (!showCss && !showJs) {
// When CSS/JS hidden, HTML gets maximum space
return isExtended ? 'h-[36rem]' : 'h-[28rem]'; // Much taller for main script to balance with iPhone frame
} else {
// When CSS/JS visible, HTML still gets substantial space
return isExtended ? 'h-[28rem]' : 'h-96';
}
} else {
// CSS and JS boxes remain smaller
return isExtended ? 'h-64' : 'h-32';
};
// Handle copy functionality
const handleCopy = async (content) => {
try {
await navigator.clipboard.writeText(content);
} catch (err) {
console.error('Failed to copy:', err);
}
};
// Handle export functionality
const handleExport = (content, filename) => {
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
// Get current editor ref based on active tab
const getCurrentEditorRef = () => {
switch (activeTab) {
case 'html': return htmlEditorRef;
case 'css': return cssEditorRef;
case 'js': return jsEditorRef;
default: return htmlEditorRef;
}
};
// Get current content based on active tab
const getCurrentContent = () => {
switch (activeTab) {
case 'html': return htmlInput;
case 'css': return cssInput;
case 'js': return jsInput;
default: return htmlInput;
}
};
// Get filename for export based on active tab
const getExportFilename = () => {
switch (activeTab) {
case 'html': return 'code.html';
case 'css': return 'styles.css';
case 'js': return 'script.js';
default: return 'code.txt';
}
};
return (
<div className="space-y-4">
{/* HTML Input */}
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
HTML
</label>
<div className="flex flex-col h-full">
{/* Tab Navigation */}
<div className="flex border-b border-gray-200 dark:border-gray-700">
{[
{ id: 'html', label: 'HTML' },
{ id: 'css', label: 'CSS' },
{ id: 'js', label: 'JavaScript' }
].map((tab) => (
<button
onClick={() => setHtmlExtended(!htmlExtended)}
className="flex items-center space-x-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
title={htmlExtended ? 'Minimize HTML box' : 'Maximize HTML box'}
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === tab.id
? 'border-blue-500 text-blue-600 dark:text-blue-400'
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
}`}
>
{htmlExtended ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
<span>{htmlExtended ? 'Min' : 'Max'}</span>
{tab.label}
</button>
</div>
<textarea
value={htmlInput}
onChange={(e) => setHtmlInput(e.target.value)}
placeholder="Enter your HTML code here..."
className={`w-full ${getTextareaHeight('html', htmlExtended)} p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
/>
{/* Toggle buttons for CSS and JS */}
<div className="flex space-x-2">
<button
onClick={() => setShowCss(!showCss)}
className="flex items-center space-x-1 px-3 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
{showCss ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
<span>CSS</span>
</button>
<button
onClick={() => setShowJs(!showJs)}
className="flex items-center space-x-1 px-3 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
>
{showJs ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
<span>JS</span>
</button>
</div>
))}
</div>
{/* CSS Input */}
{showCss && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
CSS
</label>
<button
onClick={() => setCssExtended(!cssExtended)}
className="flex items-center space-x-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
title={cssExtended ? 'Minimize CSS box' : 'Maximize CSS box'}
>
{cssExtended ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
<span>{cssExtended ? 'Min' : 'Max'}</span>
</button>
</div>
<textarea
value={cssInput}
onChange={(e) => setCssInput(e.target.value)}
placeholder="Enter your CSS code here..."
className={`w-full ${getTextareaHeight('css', cssExtended)} p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
/>
</div>
)}
{/* Action buttons above editor */}
<div className="flex items-center justify-end space-x-2 p-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => handleSearch(getCurrentEditorRef())}
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
title="Search"
>
<Search className="w-3 h-3" />
Search
</button>
<button
onClick={() => handleCopy(getCurrentContent())}
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
title="Copy"
>
<Copy className="w-3 h-3" />
Copy
</button>
<button
onClick={() => handleExport(getCurrentContent(), getExportFilename())}
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
title="Export"
>
<Download className="w-3 h-3" />
Export
</button>
</div>
{/* JS Input */}
{showJs && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
JavaScript
</label>
<button
onClick={() => setJsExtended(!jsExtended)}
className="flex items-center space-x-1 px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 rounded-md hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
title={jsExtended ? 'Minimize JS box' : 'Maximize JS box'}
>
{jsExtended ? <Minimize2 className="w-3 h-3" /> : <Maximize2 className="w-3 h-3" />}
<span>{jsExtended ? 'Min' : 'Max'}</span>
</button>
</div>
<textarea
value={jsInput}
onChange={(e) => setJsInput(e.target.value)}
placeholder="Enter your JavaScript code here..."
className={`w-full ${getTextareaHeight('js', jsExtended)} p-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
{/* Code Editor */}
<div className="flex-1">
{activeTab === 'html' && (
<CodeMirror
ref={htmlEditorRef}
value={htmlInput}
onBeforeChange={(editor, data, value) => setHtmlInput(value)}
options={{
mode: 'xml',
theme: 'material',
lineNumbers: true,
lineWrapping: true,
autoCloseTags: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
'Ctrl-F': 'findPersistent',
'Cmd-F': 'findPersistent'
}
}}
className="h-full"
/>
</div>
)}
)}
{activeTab === 'css' && (
<CodeMirror
ref={cssEditorRef}
value={cssInput}
onBeforeChange={(editor, data, value) => setCssInput(value)}
options={{
mode: 'css',
theme: 'material',
lineNumbers: true,
lineWrapping: true,
autoCloseBrackets: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
'Ctrl-F': 'findPersistent',
'Cmd-F': 'findPersistent'
}
}}
className="h-full"
/>
)}
{activeTab === 'js' && (
<CodeMirror
ref={jsEditorRef}
value={jsInput}
onBeforeChange={(editor, data, value) => setJsInput(value)}
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: true,
lineWrapping: true,
autoCloseBrackets: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
'Ctrl-F': 'findPersistent',
'Cmd-F': 'findPersistent'
}
}}
className="h-full"
/>
)}
</div>
</div>
);
};

View File

@@ -0,0 +1,213 @@
import React, { useState, useRef } from 'react';
import { Search, Copy, Download } from 'lucide-react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/css/css';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/addon/search/search';
import 'codemirror/addon/search/searchcursor';
import 'codemirror/addon/dialog/dialog';
import 'codemirror/addon/dialog/dialog.css';
const CodeInputs = ({
htmlInput,
setHtmlInput,
cssInput,
setCssInput,
jsInput,
setJsInput,
isFullscreen
}) => {
const [activeTab, setActiveTab] = useState('html');
const htmlEditorRef = useRef(null);
const cssEditorRef = useRef(null);
const jsEditorRef = useRef(null);
// Handle search functionality
const handleSearch = (editorRef) => {
if (editorRef.current && editorRef.current.editor) {
editorRef.current.editor.execCommand('find');
}
};
// Handle copy functionality
const handleCopy = async (content) => {
try {
await navigator.clipboard.writeText(content);
} catch (err) {
console.error('Failed to copy:', err);
}
};
// Handle export functionality
const handleExport = (content, filename) => {
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
// Get current editor ref based on active tab
const getCurrentEditorRef = () => {
switch (activeTab) {
case 'html': return htmlEditorRef;
case 'css': return cssEditorRef;
case 'js': return jsEditorRef;
default: return htmlEditorRef;
}
};
// Get current content based on active tab
const getCurrentContent = () => {
switch (activeTab) {
case 'html': return htmlInput;
case 'css': return cssInput;
case 'js': return jsInput;
default: return htmlInput;
}
};
// Get filename for export based on active tab
const getExportFilename = () => {
switch (activeTab) {
case 'html': return 'code.html';
case 'css': return 'styles.css';
case 'js': return 'script.js';
default: return 'code.txt';
}
};
return (
<div className="flex flex-col h-full">
{/* Tab Navigation */}
<div className="flex border-b border-gray-200 dark:border-gray-700">
{[
{ id: 'html', label: 'HTML' },
{ id: 'css', label: 'CSS' },
{ id: 'js', label: 'JavaScript' }
].map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
activeTab === tab.id
? 'border-blue-500 text-blue-600 dark:text-blue-400'
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Action buttons above editor */}
<div className="flex items-center justify-end space-x-2 p-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<button
onClick={() => handleSearch(getCurrentEditorRef())}
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
title="Search"
>
<Search className="w-3 h-3" />
Search
</button>
<button
onClick={() => handleCopy(getCurrentContent())}
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
title="Copy"
>
<Copy className="w-3 h-3" />
Copy
</button>
<button
onClick={() => handleExport(getCurrentContent(), getExportFilename())}
className="flex items-center gap-1 px-2 py-1 text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors"
title="Export"
>
<Download className="w-3 h-3" />
Export
</button>
</div>
{/* Code Editor */}
<div className="flex-1">
{activeTab === 'html' && (
<CodeMirror
ref={htmlEditorRef}
value={htmlInput}
onBeforeChange={(editor, data, value) => setHtmlInput(value)}
options={{
mode: 'xml',
theme: 'material',
lineNumbers: true,
lineWrapping: true,
autoCloseTags: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
'Ctrl-F': 'findPersistent',
'Cmd-F': 'findPersistent'
}
}}
className="h-full"
/>
)}
{activeTab === 'css' && (
<CodeMirror
ref={cssEditorRef}
value={cssInput}
onBeforeChange={(editor, data, value) => setCssInput(value)}
options={{
mode: 'css',
theme: 'material',
lineNumbers: true,
lineWrapping: true,
autoCloseBrackets: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
'Ctrl-F': 'findPersistent',
'Cmd-F': 'findPersistent'
}
}}
className="h-full"
/>
)}
{activeTab === 'js' && (
<CodeMirror
ref={jsEditorRef}
value={jsInput}
onBeforeChange={(editor, data, value) => setJsInput(value)}
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: true,
lineWrapping: true,
autoCloseBrackets: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
'Ctrl-F': 'findPersistent',
'Cmd-F': 'findPersistent'
}
}}
className="h-full"
/>
)}
</div>
</div>
);
};
export default CodeInputs;

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { Copy } from 'lucide-react';
const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameRef, selectedElementInfo }) => {
console.log('🔍 ELEMENT EDITOR: Received props:', { selectedElementInfo, previewFrameRef: !!previewFrameRef });
@@ -10,14 +11,14 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
if (selectedElementInfo) {
const elementInfo = {
tagName: selectedElementInfo.tagName,
innerText: selectedElementInfo.textContent || '',
innerHTML: selectedElementInfo.innerHTML || selectedElementInfo.textContent || '',
id: selectedElementInfo.attributes.id || '',
className: selectedElementInfo.attributes.class || '',
cascadeId: selectedElementInfo.cascadeId,
isContainer: selectedElementInfo.attributes.children?.length > 0 || isContainerElement(selectedElementInfo.tagName),
};
// Add all other attributes
// Add all other attributes (excluding cascadeId from display)
Object.entries(selectedElementInfo.attributes).forEach(([name, value]) => {
if (!['id', 'class', 'data-original', 'data-cascade-id'].includes(name)) {
elementInfo[name] = value;
@@ -68,7 +69,7 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
if (previewFrameRef?.current && edited?.cascadeId) {
let success = false;
if (field === 'innerText') {
if (field === 'innerHTML') {
success = previewFrameRef.current.updateElementText(edited.cascadeId, value);
} else if (field === 'className') {
success = previewFrameRef.current.updateElementClass(edited.cascadeId, value);
@@ -143,12 +144,48 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
if (!edited) return <p className="dark:text-gray-300">Loading editor...</p>;
const otherAttributes = Object.keys(edited || {}).filter(
key => key !== 'tagName' && key !== 'id' && key !== 'className' && key !== 'innerText' && key !== 'isContainer'
// Filter out system attributes for display (hide cascadeId)
const otherAttributes = Object.keys(edited).filter(key =>
!['tagName', 'innerHTML', 'id', 'className', 'cascadeId', 'isContainer'].includes(key)
);
// Handle copy element functionality
const handleCopyElement = async () => {
if (!edited) return;
try {
// Create a clean element string without cascade attributes
const elementString = `<${edited.tagName}${edited.id ? ` id="${edited.id}"` : ''}${edited.className ? ` class="${edited.className}"` : ''}${otherAttributes.map(attr => ` ${attr}="${edited[attr]}"`).join('')}>${edited.innerHTML || ''}</${edited.tagName}>`;
await navigator.clipboard.writeText(elementString);
console.log('✅ Element copied to clipboard');
} catch (err) {
console.error('❌ Failed to copy element:', err);
}
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
Edit {edited.tagName.toUpperCase()}
</h3>
<div className="flex items-center space-x-2">
<button
onClick={handleCopyElement}
className="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
title="Copy element"
>
<Copy className="w-4 h-4" />
</button>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
</button>
</div>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">Tag Name</label>
<textarea
@@ -189,34 +226,26 @@ const ElementEditor = ({ htmlInput, setHtmlInput, onClose, onSave, previewFrameR
/>
</div>
{/* Only show innerText field for non-container elements */}
{!edited.isContainer && (
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Inner Text
<span className="text-xs text-gray-500 ml-2">(text content)</span>
</label>
<textarea
ref={(el) => textareaRefs.current.innerText = el}
value={edited.innerText || ''}
onChange={(e) => {
handleFieldChange('innerText', e.target.value);
autoResizeTextarea(e.target);
}}
rows="2"
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none overflow-hidden"
/>
</div>
)}
{/* Show innerHTML field for all elements */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Inner HTML
<span className="text-xs text-gray-500 ml-2">(HTML content inside element)</span>
</label>
<textarea
ref={(el) => textareaRefs.current.innerHTML = el}
value={edited.innerHTML || ''}
onChange={(e) => {
handleFieldChange('innerHTML', e.target.value);
autoResizeTextarea(e.target);
}}
rows="3"
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 font-mono text-sm resize-none overflow-hidden"
placeholder="Enter HTML content or text..."
/>
</div>
{/* Show container hint for container elements */}
{edited.isContainer && (
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md">
<p className="text-sm text-blue-700 dark:text-blue-300">
📦 This is a container element. Edit its ID, class, or other attributes instead of text content.
</p>
</div>
)}
{otherAttributes.map(attr => (
<div key={attr} className="space-y-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">{attr}</label>

View File

@@ -0,0 +1,90 @@
import React from 'react';
import { Smartphone, Tablet, Monitor, RotateCcw, Maximize, Minimize, Eye, EyeOff } from 'lucide-react';
const SimpleToolbar = ({
selectedDevice,
setSelectedDevice,
isFullscreen,
onRefresh,
onToggleFullscreen,
onToggleSidebar,
showSidebar,
isSidebarExpanded
}) => {
const devices = [
{ id: 'mobile', label: 'Mobile', icon: Smartphone },
{ id: 'tablet', label: 'Tablet', icon: Tablet },
{ id: 'desktop', label: 'Desktop', icon: Monitor }
];
return (
<div className="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-center justify-between">
{/* Device selection */}
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 mr-3">
Device:
</span>
{devices.map((device) => {
const Icon = device.icon;
const isDisabled = isSidebarExpanded && device.id !== 'desktop';
return (
<button
key={device.id}
onClick={() => !isDisabled && setSelectedDevice(device.id)}
disabled={isDisabled}
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
selectedDevice === device.id
? 'bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400'
: isDisabled
? 'text-gray-400 dark:text-gray-600 cursor-not-allowed'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
}`}
title={isDisabled ? 'Disabled when sidebar is expanded' : `Switch to ${device.label} view`}
>
<Icon className="w-4 h-4" />
{device.label}
</button>
);
})}
</div>
{/* Action buttons */}
<div className="flex items-center space-x-2">
{/* Refresh button */}
<button
onClick={onRefresh}
className="flex items-center gap-2 px-3 py-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
title="Refresh preview"
>
<RotateCcw className="w-4 h-4" />
Refresh
</button>
{/* Sidebar toggle */}
<button
onClick={onToggleSidebar}
className="flex items-center gap-2 px-3 py-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
title={showSidebar ? 'Hide sidebar' : 'Show sidebar'}
>
{showSidebar ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
{showSidebar ? 'Hide' : 'Show'} Sidebar
</button>
{/* Fullscreen toggle */}
<button
onClick={onToggleFullscreen}
className="flex items-center gap-2 px-3 py-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors"
title={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
>
{isFullscreen ? <Minimize className="w-4 h-4" /> : <Maximize className="w-4 h-4" />}
{isFullscreen ? 'Exit' : 'Enter'} Fullscreen
</button>
</div>
</div>
</div>
);
};
export default SimpleToolbar;