Initial hook demo

This commit is contained in:
2025-04-23 15:32:29 -07:00
commit 55bda8a1bd
18 changed files with 1005 additions and 0 deletions

0
src/App.css Normal file
View File

169
src/App.tsx Normal file
View File

@@ -0,0 +1,169 @@
import { useEffect, useRef, useState } from 'react'
import './App.css'
import EditorJS, { OutputData } from '@editorjs/editorjs'
interface EditorWrapper {
instance: EditorJS | null;
addBlock: (text: string) => Promise<void>;
clear: () => Promise<void>;
getData: () => Promise<OutputData>;
holderRef: React.RefObject<HTMLDivElement | null>;
}
function useEditorJS(onChange: (data: OutputData) => void): EditorWrapper {
const [editor, setEditor] = useState<EditorJS | null>(null);
const holderRef = useRef<HTMLDivElement | null>(null);
const isDestroyed = useRef(false);
// Initialize editor
useEffect(() => {
if (!holderRef.current || editor || isDestroyed.current) return;
const instance = new EditorJS({
holder: holderRef.current,
placeholder: 'Start writing...',
onChange: async () => {
try {
const content = await instance.save();
onChange(content);
} catch (err) {
console.error('Error saving editor content:', err);
}
}
});
setEditor(instance);
return () => {
isDestroyed.current = true;
const cleanup = async () => {
if (instance) {
try {
await instance.destroy();
setEditor(null);
} catch (error: unknown) {
console.error('Error destroying editor:', error);
}
}
};
void cleanup();
};
}, []);
const wrapper: EditorWrapper = {
instance: editor,
holderRef,
addBlock: async (text: string) => {
if (!editor) return;
try {
const currentData = await editor.save();
const newBlock = {
id: String(Date.now()),
type: 'paragraph',
data: {
text
}
};
await editor.blocks.insert('paragraph', { text });
const updatedData = await editor.save();
onChange(updatedData);
} catch (err) {
console.error('Error adding block:', err);
}
},
clear: async () => {
if (!editor) return;
try {
await editor.clear();
} catch (err) {
console.error('Error clearing editor:', err);
}
},
getData: async () => {
if (!editor) throw new Error('Editor not initialized');
return editor.save();
}
};
return wrapper;
}
function App() {
const [editorData, setEditorData] = useState<OutputData | null>(null);
const [inputText, setInputText] = useState<string>("");
const inputRef = useRef<HTMLInputElement>(null);
const editor = useEditorJS((data) => {
setEditorData(data);
});
const handleAdd = async () => {
if (!inputText.trim()) return;
await editor.addBlock(inputText);
setInputText("");
setTimeout(() => {
inputRef.current?.focus();
}, 0);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
void handleAdd();
};
return (
<div style={{ backgroundColor: '#1E1E1E' }} className="flex flex-col w-full h-screen items-center">
<div className="flex flex-row w-full h-full gap-8 items-center px-8">
<div className="flex-1 flex justify-center">
<div style={{ backgroundColor: '#FFFFFF' }} className="min-h-[500px] min-w-[500px] rounded shadow-xl text-gray-900 p-6">
<div style={{ margin: '20px' }} ref={editor.holderRef} />
</div>
</div>
<div className="flex-1 flex flex-col justify-center items-center gap-4">
<OutputComponent data={editorData} />
<form onSubmit={handleSubmit} className="flex gap-2 m-2">
<input
ref={inputRef}
type="text"
className="px-6 py-3 bg-white text-gray-900 rounded border border-gray-300"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter text..."
/>
<button
type="submit"
className="px-6 py-3 bg-gray-500 text-white rounded hover:bg-gray-600"
disabled={!inputText.trim()}
>
Add
</button>
<button
type="button"
onClick={() => void editor.clear()}
className="px-6 py-3 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Clear All
</button>
</form>
</div>
</div>
</div>
)
}
export default App
function OutputComponent({ data }: { data: OutputData | null }) {
return (
<div style={{ backgroundColor: '#FFFFFF' }} className="min-h-[500px] min-w-[500px] rounded shadow-xl p-6 text-gray-900">
<pre className="whitespace-pre-wrap">
{data ? JSON.stringify(data, null, 2) : 'Output content will go here'}
</pre>
</div>
)
}

17
src/index.css Normal file
View File

@@ -0,0 +1,17 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
}

10
src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />