Major improvements to Object Editor, Table Editor, and Invoice Editor: ## UX Enhancements: - Made export sections collapsible across all editors to reduce page height - Added comprehensive, collapsible usage tips with eye-catching design - Implemented consistent input method patterns (file auto-load, inline URL buttons) - Paste sections now collapse after successful parsing with data summaries ## Data Loss Prevention: - Added confirmation modals when switching input methods with existing data - Amber-themed warning design with specific data summaries - Suggests saving before proceeding with destructive actions - Prevents accidental data loss across all editor tools ## Consistency Improvements: - Standardized file input styling with 'tool-input' class - URL fetch buttons now inline (not below input) across all editors - Parse buttons positioned consistently on bottom-right - Auto-load behavior for file inputs matching across editors ## Bug Fixes: - Fixed Table Editor cell text overflow with proper truncation - Fixed Object Editor file input to auto-load content - Removed unnecessary parse buttons and checkboxes - Fixed Invoice Editor URL form layout ## Documentation: - Created EDITOR_TOOL_GUIDE.md with comprehensive patterns - Created EDITOR_CHECKLIST.md for quick reference - Created PROJECT_ROADMAP.md with future plans - Created TODO.md with detailed task lists - Documented data loss prevention patterns - Added code examples and best practices All editors now follow consistent UX patterns with improved user experience and data protection.
749 lines
23 KiB
Markdown
749 lines
23 KiB
Markdown
# Editor Tool Building Guide
|
|
|
|
This guide ensures consistency across all editor tools in the Developer Tools application. Follow these patterns when creating new editor tools.
|
|
|
|
## Table of Contents
|
|
1. [Input Section Patterns](#input-section-patterns)
|
|
2. [Main Editor Section](#main-editor-section)
|
|
3. [Export/Output Section](#export-output-section)
|
|
4. [Usage Tips Section](#usage-tips-section)
|
|
5. [State Management](#state-management)
|
|
6. [Error Handling](#error-handling)
|
|
7. [Complete Example](#complete-example)
|
|
|
|
---
|
|
|
|
## Input Section Patterns
|
|
|
|
### Required Tabs
|
|
All editor tools must have these 4 input tabs:
|
|
1. **Create New** - Start empty or load sample data
|
|
2. **URL** - Fetch data from API endpoints
|
|
3. **Paste** - Paste data directly
|
|
4. **Open** - Upload files
|
|
|
|
### Tab Structure
|
|
```jsx
|
|
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
|
<button onClick={() => handleTabChange('create')} className={...}>
|
|
<Plus className="h-4 w-4" />
|
|
Create New
|
|
</button>
|
|
<button onClick={() => handleTabChange('url')} className={...}>
|
|
<Globe className="h-4 w-4" />
|
|
URL
|
|
</button>
|
|
<button onClick={() => handleTabChange('paste')} className={...}>
|
|
<FileText className="h-4 w-4" />
|
|
Paste
|
|
</button>
|
|
<button onClick={() => handleTabChange('open')} className={...}>
|
|
<Upload className="h-4 w-4" />
|
|
Open
|
|
</button>
|
|
</div>
|
|
```
|
|
|
|
### Create New Tab
|
|
```jsx
|
|
{activeTab === 'create' && (
|
|
<div className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* Start Empty Button */}
|
|
<button onClick={handleStartEmpty} className="flex flex-col items-center p-6 border-2 border-dashed...">
|
|
<Plus className="h-8 w-8..." />
|
|
<span className="font-medium...">Start Empty</span>
|
|
<span className="text-xs...">Create a blank structure</span>
|
|
</button>
|
|
|
|
{/* Load Sample Button */}
|
|
<button onClick={handleLoadSample} className="flex flex-col items-center p-6 border-2 border-dashed...">
|
|
<FileText className="h-8 w-8..." />
|
|
<span className="font-medium...">Load Sample</span>
|
|
<span className="text-xs...">Start with example data</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tip */}
|
|
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
|
|
<p className="text-xs text-blue-700 dark:text-blue-300">
|
|
💡 <strong>Tip:</strong> You can always import data later using the URL, Paste, or Open tabs.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
### URL Tab (CONSISTENT LAYOUT)
|
|
**IMPORTANT:** Button must be INLINE on the right, not below!
|
|
|
|
```jsx
|
|
{activeTab === 'url' && (
|
|
<div className="space-y-3">
|
|
<div className="flex gap-2">
|
|
<div className="relative flex-1">
|
|
<input
|
|
type="url"
|
|
value={url}
|
|
onChange={(e) => setUrl(e.target.value)}
|
|
placeholder="https://api.telegram.org/bot<token>/getMe"
|
|
className="tool-input w-full"
|
|
onKeyPress={(e) => e.key === 'Enter' && handleFetchData()}
|
|
/>
|
|
</div>
|
|
<button
|
|
onClick={handleFetchData}
|
|
disabled={isLoading || !url.trim()}
|
|
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white font-medium px-4 py-2 rounded-md transition-colors flex items-center whitespace-nowrap"
|
|
>
|
|
{isLoading ? 'Fetching...' : 'Fetch Data'}
|
|
</button>
|
|
</div>
|
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
|
Enter any URL that returns JSON data. Examples: Telegram Bot API, JSONPlaceholder, GitHub API, etc.
|
|
</p>
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
### Paste Tab (WITH COLLAPSE)
|
|
```jsx
|
|
{activeTab === 'paste' && (
|
|
pasteCollapsed ? (
|
|
// Collapsed State
|
|
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm text-green-700 dark:text-green-300">
|
|
✓ Data loaded: {pasteDataSummary.format} ({pasteDataSummary.size.toLocaleString()} chars)
|
|
</span>
|
|
<button
|
|
onClick={() => setPasteCollapsed(false)}
|
|
className="text-sm text-blue-600 hover:underline"
|
|
>
|
|
Edit Input ▼
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
// Expanded State
|
|
<div className="space-y-3">
|
|
<div>
|
|
<CodeMirrorEditor
|
|
value={inputText}
|
|
onChange={setInputText}
|
|
language="json"
|
|
placeholder="Paste your data here..."
|
|
maxLines={12}
|
|
showToggle={true}
|
|
className="w-full"
|
|
/>
|
|
</div>
|
|
{error && (
|
|
<div className="p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
|
<p className="text-sm text-red-600 dark:text-red-400">
|
|
<strong>Invalid Data:</strong> {error}
|
|
</p>
|
|
</div>
|
|
)}
|
|
<div className="flex items-center justify-between flex-shrink-0">
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
Auto-detects format
|
|
</div>
|
|
<button
|
|
onClick={handleParseData}
|
|
disabled={!inputValid || !inputText.trim()}
|
|
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium px-4 py-2 rounded-md transition-colors flex-shrink-0"
|
|
>
|
|
Parse Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
)}
|
|
```
|
|
|
|
### Open Tab (CONSISTENT WITH TABLE EDITOR)
|
|
**IMPORTANT:** Use `tool-input` class and auto-load on file selection!
|
|
|
|
```jsx
|
|
{activeTab === 'open' && (
|
|
<div className="space-y-3">
|
|
<input
|
|
ref={fileInputRef}
|
|
type="file"
|
|
accept=".json,.txt"
|
|
onChange={handleFileImport}
|
|
className="tool-input"
|
|
/>
|
|
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-md p-3">
|
|
<p className="text-xs text-green-700 dark:text-green-300">
|
|
🔒 <strong>Privacy:</strong> Your data stays in your browser. We don't store or upload anything - just help you open, edit, and export your files locally.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
### Parse Button Behavior
|
|
- **Valid data**: Collapse input, show summary, display editor
|
|
- **Invalid data**: Show error, keep input expanded, hide editor
|
|
- **Button disabled**: When no data or invalid format
|
|
|
|
---
|
|
|
|
## Main Editor Section
|
|
|
|
### Structure
|
|
```jsx
|
|
{(activeTab !== 'create' || createNewCompleted) && (
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden mb-6">
|
|
{/* Editor Header */}
|
|
<div className="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
|
<Edit3 className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
|
Your Editor Name
|
|
</h3>
|
|
{/* Optional: Settings or Actions */}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Editor Content */}
|
|
<div className="p-4">
|
|
{/* Your editor implementation */}
|
|
</div>
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
### Fullscreen Support (Optional)
|
|
If your editor supports fullscreen:
|
|
```jsx
|
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
|
|
<div className={`... ${
|
|
isFullscreen
|
|
? "fixed inset-0 z-[99999] rounded-none border-0 shadow-none overflow-hidden !m-0"
|
|
: "..."
|
|
}`}
|
|
style={isFullscreen ? { marginTop: "0 !important" } : {}}
|
|
>
|
|
```
|
|
|
|
---
|
|
|
|
## Export/Output Section
|
|
|
|
### Collapsible Export (REQUIRED)
|
|
```jsx
|
|
{data && (
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden mt-6">
|
|
{/* Collapsible Header */}
|
|
<div
|
|
onClick={() => setExportExpanded(!exportExpanded)}
|
|
className="px-4 py-3 border-b border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
|
|
<Download className="h-5 w-5 text-green-600 dark:text-green-400" />
|
|
Export Results
|
|
{exportExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
|
</h3>
|
|
<div className="text-sm text-gray-600 dark:text-gray-400">
|
|
{/* Summary info */}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Export Content - Collapsible */}
|
|
{exportExpanded && (
|
|
<div>
|
|
{/* Export tabs and content */}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
### Export Tabs Pattern
|
|
```jsx
|
|
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
|
<button
|
|
onClick={() => setExportTab('json')}
|
|
className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors ${
|
|
exportTab === 'json'
|
|
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500'
|
|
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'
|
|
}`}
|
|
>
|
|
<Braces className="h-4 w-4" />
|
|
JSON
|
|
</button>
|
|
{/* More export format tabs */}
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## Usage Tips Section
|
|
|
|
### Collapsible Usage Tips (REQUIRED)
|
|
```jsx
|
|
{/* Usage Tips */}
|
|
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md overflow-hidden mt-6">
|
|
<div
|
|
onClick={() => setUsageTipsExpanded(!usageTipsExpanded)}
|
|
className="px-4 py-3 cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors flex items-center justify-between"
|
|
>
|
|
<h4 className="text-blue-800 dark:text-blue-200 font-medium flex items-center gap-2">
|
|
💡 Usage Tips
|
|
</h4>
|
|
{usageTipsExpanded ? <ChevronUp className="h-4 w-4 text-blue-600 dark:text-blue-400" /> : <ChevronDown className="h-4 w-4 text-blue-600 dark:text-blue-400" />}
|
|
</div>
|
|
|
|
{usageTipsExpanded && (
|
|
<div className="px-4 pb-4 text-blue-700 dark:text-blue-300 text-sm space-y-3">
|
|
<div>
|
|
<p className="font-medium mb-1">📝 Input Methods:</p>
|
|
<ul className="list-disc list-inside space-y-1 ml-2">
|
|
<li><strong>Create New:</strong> Description</li>
|
|
<li><strong>URL Import:</strong> Description</li>
|
|
<li><strong>Paste Data:</strong> Description</li>
|
|
<li><strong>Open Files:</strong> Description</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="font-medium mb-1">✏️ Editing Features:</p>
|
|
<ul className="list-disc list-inside space-y-1 ml-2">
|
|
{/* Your editor-specific features */}
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="font-medium mb-1">📤 Export Options:</p>
|
|
<ul className="list-disc list-inside space-y-1 ml-2">
|
|
{/* Your export formats */}
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="font-medium mb-1">💾 Data Privacy:</p>
|
|
<ul className="list-disc list-inside space-y-1 ml-2">
|
|
<li><strong>Local Processing:</strong> All data stays in your browser</li>
|
|
<li><strong>No Upload:</strong> We don't store or transmit your data</li>
|
|
<li><strong>Secure:</strong> Your information remains private</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## Data Loss Prevention (CRITICAL!)
|
|
|
|
### Confirmation Modal for Tab Changes
|
|
|
|
**IMPORTANT:** Always confirm before clearing user data when switching input methods!
|
|
|
|
### When to Show Confirmation
|
|
- User has entered/loaded data
|
|
- User tries to switch to a different input tab
|
|
- User tries to use Create New buttons (Start Empty/Load Sample)
|
|
|
|
### Implementation Pattern
|
|
|
|
#### 1. Check if User Has Data
|
|
```jsx
|
|
const hasUserData = () => {
|
|
// Check if there's meaningful data
|
|
return Object.keys(editorData).length > 0;
|
|
};
|
|
|
|
const hasModifiedData = () => {
|
|
// Check if data has been modified from initial state
|
|
// Return false for empty or sample data
|
|
// Return true for user-entered data
|
|
};
|
|
```
|
|
|
|
#### 2. Handle Tab Change with Confirmation
|
|
```jsx
|
|
const handleTabChange = (newTab) => {
|
|
if (hasModifiedData() && activeTab !== newTab) {
|
|
setPendingTabChange(newTab);
|
|
setShowInputChangeModal(true);
|
|
} else {
|
|
setActiveTab(newTab);
|
|
}
|
|
};
|
|
```
|
|
|
|
#### 3. Confirmation Modal Component
|
|
```jsx
|
|
const InputChangeConfirmationModal = ({
|
|
editorData,
|
|
currentMethod,
|
|
newMethod,
|
|
onConfirm,
|
|
onCancel
|
|
}) => {
|
|
const getMethodName = (method) => {
|
|
switch (method) {
|
|
case 'create': return 'Create New';
|
|
case 'create_empty': return 'Start Empty';
|
|
case 'create_sample': return 'Load Sample';
|
|
case 'url': return 'URL Import';
|
|
case 'paste': return 'Paste Data';
|
|
case 'open': return 'File Upload';
|
|
default: return method;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full overflow-hidden">
|
|
{/* Header - Amber Warning */}
|
|
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-700 bg-amber-50 dark:bg-amber-900/20">
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex-shrink-0">
|
|
<AlertTriangle className="h-6 w-6 text-amber-600 dark:text-amber-400" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-amber-800 dark:text-amber-200">
|
|
Confirm Action
|
|
</h3>
|
|
<p className="text-sm text-amber-700 dark:text-amber-300">
|
|
{newMethod === 'create_empty' || newMethod === 'create_sample'
|
|
? `Using ${getMethodName(newMethod)} will clear your current data.`
|
|
: `Switching from ${getMethodName(currentMethod)} to ${getMethodName(newMethod)} will clear your current data.`
|
|
}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Body - Show Current Data Summary */}
|
|
<div className="px-6 py-4">
|
|
<div className="space-y-3">
|
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
|
You currently have:
|
|
</p>
|
|
<ul className="text-sm text-gray-700 dark:text-gray-300 space-y-1 ml-4">
|
|
{/* List current data items */}
|
|
<li>• [Data summary item 1]</li>
|
|
<li>• [Data summary item 2]</li>
|
|
<li>• [Data summary item 3]</li>
|
|
</ul>
|
|
|
|
{/* Blue Tip Box */}
|
|
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
|
<p className="text-xs text-blue-700 dark:text-blue-300">
|
|
<strong>Tip:</strong> Consider downloading your current data as JSON before proceeding to save your work.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer - Action Buttons */}
|
|
<div className="px-6 py-4 bg-gray-50 dark:bg-gray-700/50 flex justify-end gap-3">
|
|
<button
|
|
onClick={onCancel}
|
|
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded-md hover:bg-gray-50 dark:hover:bg-gray-500 transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={onConfirm}
|
|
className="px-4 py-2 text-sm font-medium text-white bg-amber-600 hover:bg-amber-700 rounded-md transition-colors flex items-center gap-2"
|
|
>
|
|
<AlertTriangle className="h-4 w-4" />
|
|
Switch & Clear Data
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
#### 4. Usage in Component
|
|
```jsx
|
|
{/* Confirmation Modal */}
|
|
{showInputChangeModal && (
|
|
<InputChangeConfirmationModal
|
|
editorData={editorData}
|
|
currentMethod={activeTab}
|
|
newMethod={pendingTabChange}
|
|
onConfirm={() => {
|
|
clearAllData();
|
|
setActiveTab(pendingTabChange);
|
|
setShowInputChangeModal(false);
|
|
setPendingTabChange(null);
|
|
}}
|
|
onCancel={() => {
|
|
setShowInputChangeModal(false);
|
|
setPendingTabChange(null);
|
|
}}
|
|
/>
|
|
)}
|
|
```
|
|
|
|
### Visual Design
|
|
- **Header**: Amber background with AlertTriangle icon
|
|
- **Body**: White/dark background with data summary
|
|
- **Tip Box**: Blue background with save suggestion
|
|
- **Footer**: Gray background with Cancel and Warning buttons
|
|
- **Warning Button**: Amber color with AlertTriangle icon
|
|
|
|
### Key Points
|
|
1. **Always show** when user has meaningful data
|
|
2. **List specific data** user will lose (not generic message)
|
|
3. **Suggest saving** before proceeding (blue tip box)
|
|
4. **Clear button text** - "Switch & Clear Data" (not ambiguous)
|
|
5. **z-50** to appear above everything
|
|
6. **Amber theme** for warning (not red, not blue)
|
|
|
|
---
|
|
|
|
## State Management
|
|
|
|
### Required State Variables
|
|
```jsx
|
|
// Tab management
|
|
const [activeTab, setActiveTab] = useState('create');
|
|
const [createNewCompleted, setCreateNewCompleted] = useState(false);
|
|
|
|
// Input data
|
|
const [inputText, setInputText] = useState('');
|
|
const [url, setUrl] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState('');
|
|
|
|
// Collapse states
|
|
const [pasteCollapsed, setPasteCollapsed] = useState(false);
|
|
const [pasteDataSummary, setPasteDataSummary] = useState(null);
|
|
const [exportExpanded, setExportExpanded] = useState(false);
|
|
const [usageTipsExpanded, setUsageTipsExpanded] = useState(false);
|
|
|
|
// Confirmation modals
|
|
const [showInputChangeModal, setShowInputChangeModal] = useState(false);
|
|
const [pendingTabChange, setPendingTabChange] = useState(null);
|
|
|
|
// Refs
|
|
const fileInputRef = useRef(null);
|
|
|
|
// Your editor-specific state
|
|
const [editorData, setEditorData] = useState(null);
|
|
```
|
|
|
|
### Tab Change with Confirmation
|
|
```jsx
|
|
const handleTabChange = (newTab) => {
|
|
if (hasModifiedData() && activeTab !== newTab) {
|
|
setPendingTabChange(newTab);
|
|
setShowInputChangeModal(true);
|
|
} else {
|
|
setActiveTab(newTab);
|
|
}
|
|
};
|
|
|
|
const confirmInputChange = () => {
|
|
clearAllData();
|
|
setActiveTab(pendingTabChange);
|
|
setShowInputChangeModal(false);
|
|
setPendingTabChange(null);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Parse Data with Error Handling
|
|
```jsx
|
|
const handleParseData = () => {
|
|
try {
|
|
const parsed = parseYourData(inputText);
|
|
|
|
if (parsed.valid) {
|
|
setEditorData(parsed.data);
|
|
setError('');
|
|
setCreateNewCompleted(true);
|
|
setPasteDataSummary({
|
|
format: parsed.format,
|
|
size: inputText.length,
|
|
items: parsed.data.length
|
|
});
|
|
setPasteCollapsed(true);
|
|
} else {
|
|
// Show error, keep input expanded
|
|
setError(parsed.error || 'Invalid data format');
|
|
setPasteCollapsed(false);
|
|
}
|
|
} catch (err) {
|
|
setError(err.message || 'Failed to parse data');
|
|
setPasteCollapsed(false);
|
|
}
|
|
};
|
|
```
|
|
|
|
### Error Display
|
|
```jsx
|
|
{error && (
|
|
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
|
<div className="flex items-center gap-2">
|
|
<AlertTriangle className="h-5 w-5 text-red-600 dark:text-red-400" />
|
|
<p className="text-red-700 dark:text-red-300">{error}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
## Complete Example
|
|
|
|
See existing editors for complete implementations:
|
|
- **Object Editor** (`/src/pages/ObjectEditor.js`) - Best for JSON/structured data
|
|
- **Table Editor** (`/src/pages/TableEditor.js`) - Best for tabular data
|
|
- **Invoice Editor** (`/src/pages/InvoiceEditor.js`) - Best for form-based editors
|
|
|
|
---
|
|
|
|
## Checklist for New Editor Tools
|
|
|
|
### Input Section
|
|
- [ ] All 4 tabs implemented (Create New, URL, Paste, Open)
|
|
- [ ] URL tab has inline button (not below)
|
|
- [ ] Open tab uses `tool-input` class
|
|
- [ ] Open tab auto-loads on file selection
|
|
- [ ] Paste tab has parse button on bottom-right
|
|
- [ ] Paste tab collapses after successful parse
|
|
- [ ] Error handling shows errors and keeps input expanded
|
|
|
|
### Main Editor
|
|
- [ ] Only shows after data is loaded
|
|
- [ ] Has proper header with icon
|
|
- [ ] Implements your editor functionality
|
|
- [ ] Optional: Fullscreen support with proper z-index
|
|
|
|
### Export Section
|
|
- [ ] Collapsible by default
|
|
- [ ] Header shows summary info
|
|
- [ ] Chevron icon indicates state
|
|
- [ ] Hover effect on header
|
|
- [ ] Export tabs if multiple formats
|
|
|
|
### Usage Tips
|
|
- [ ] Collapsible by default
|
|
- [ ] 💡 emoji in header
|
|
- [ ] Comprehensive tips organized by category
|
|
- [ ] Includes Input Methods, Editing Features, Export Options, Data Privacy
|
|
|
|
### State & Behavior
|
|
- [ ] Tab change confirmation when data exists
|
|
- [ ] Proper error handling
|
|
- [ ] Loading states for async operations
|
|
- [ ] Dark mode support
|
|
|
|
### Styling
|
|
- [ ] Uses Tailwind CSS classes
|
|
- [ ] Consistent with other editors
|
|
- [ ] Responsive design
|
|
- [ ] Proper spacing and layout
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Button Styling
|
|
```jsx
|
|
// Primary action button
|
|
className="bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-medium px-4 py-2 rounded-md transition-colors flex-shrink-0"
|
|
|
|
// Secondary button
|
|
className="bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 px-4 py-2 rounded-md transition-colors"
|
|
```
|
|
|
|
### Input Styling
|
|
```jsx
|
|
// Standard input
|
|
className="tool-input w-full"
|
|
|
|
// Or explicit:
|
|
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
|
|
```
|
|
|
|
### Card/Section Styling
|
|
```jsx
|
|
className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"
|
|
```
|
|
|
|
---
|
|
|
|
## Import Requirements
|
|
|
|
### Required Icons (from lucide-react)
|
|
```jsx
|
|
import {
|
|
Plus, // Create New
|
|
Globe, // URL
|
|
FileText, // Paste
|
|
Upload, // Open
|
|
Download, // Export
|
|
Edit3, // Editor
|
|
AlertTriangle, // Errors
|
|
ChevronUp, // Collapse indicators
|
|
ChevronDown, // Collapse indicators
|
|
// Add your specific icons
|
|
} from 'lucide-react';
|
|
```
|
|
|
|
### Required Components
|
|
```jsx
|
|
import ToolLayout from '../components/ToolLayout';
|
|
import CodeMirrorEditor from '../components/CodeMirrorEditor';
|
|
import CodeEditor from '../components/CodeEditor'; // For read-only code display
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
1. **Consistency First**: Always follow existing patterns before innovating
|
|
2. **User Data Safety**: Always confirm before clearing user data
|
|
3. **Error Messages**: Be specific and helpful
|
|
4. **Loading States**: Show loading indicators for async operations
|
|
5. **Accessibility**: Use semantic HTML and proper ARIA labels
|
|
6. **Dark Mode**: Test in both light and dark modes
|
|
7. **Mobile**: Ensure responsive design works on small screens
|
|
8. **Performance**: Lazy load heavy components when possible
|
|
|
|
---
|
|
|
|
## Testing Your Editor
|
|
|
|
1. **Input Methods**: Test all 4 tabs work correctly
|
|
2. **Parse Valid Data**: Ensure data loads and displays
|
|
3. **Parse Invalid Data**: Ensure errors show and input stays expanded
|
|
4. **Tab Switching**: Confirm data loss prevention works
|
|
5. **Export**: Test all export formats
|
|
6. **Collapse/Expand**: Test all collapsible sections
|
|
7. **Dark Mode**: Toggle and verify styling
|
|
8. **Mobile**: Test on small screen sizes
|
|
9. **Edge Cases**: Empty data, very large data, special characters
|
|
|
|
---
|
|
|
|
## Questions or Issues?
|
|
|
|
Refer to existing editors:
|
|
- Object Editor: JSON/PHP serialized data handling
|
|
- Table Editor: Tabular data with inline editing
|
|
- Invoice Editor: Form-based data entry
|
|
|
|
Follow these patterns and your new editor will be consistent with the rest of the application!
|