0% found this document useful (0 votes)
4 views12 pages

799

This document is a React component for a text editor using the Tiptap library, featuring various text formatting options such as headings, alignment, color, and lists. It includes dropdowns for selecting headings and text alignment, as well as buttons for formatting text (bold, italic, underline, etc.). The component manages its state and updates based on user interactions, providing a user-friendly interface for text editing.

Uploaded by

haripatel1186
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views12 pages

799

This document is a React component for a text editor using the Tiptap library, featuring various text formatting options such as headings, alignment, color, and lists. It includes dropdowns for selecting headings and text alignment, as well as buttons for formatting text (bold, italic, underline, etc.). The component manages its state and updates based on user interactions, providing a user-friendly interface for text editing.

Uploaded by

haripatel1186
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

Secret code

import React, { useEffect, useState, useRef } from 'react';


import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import TextStyle from '@tiptap/extension-text-style';
import Color from '@tiptap/extension-color';
import Heading from '@tiptap/extension-heading';
import ListItem from '@tiptap/extension-list-item';
import BulletList from '@tiptap/extension-bullet-list';
import OrderedList from '@tiptap/extension-ordered-list';
import Highlight from '@tiptap/extension-highlight';

import { FaAlignLeft, FaAlignCenter, FaAlignRight, FaChevronDown } from 'react-icons/fa';


import { MdFormatBold, MdFormatItalic, MdOutlineFormatUnderlined,
MdOutlineStrikethroughS } from 'react-icons/md';
import { IoIosCode } from 'react-icons/io';
import { AiOutlineHighlight } from 'react-icons/ai';
import { PiListNumbersLight } from 'react-icons/pi';
import { TbList } from 'react-icons/tb';

const TextEditor = () => {


const editor = useEditor({
extensions: [
StarterKit.configure({ bulletList: true, orderedList: true }),
Underline,
TextStyle,
Color,
Heading.configure({ levels: [1, 2, 3] }).extend({
renderHTML({ node, HTMLAttributes }) {
const level = node.attrs.level;
const sizes = {
1: 'text-2xl',
2: 'text-xl',
3: 'text-lg',
};
return [
`h${level}`,
{ ...HTMLAttributes, class: `${sizes[level]} font-semibold` },
0,
];
},
}),
TextAlign.configure({ types: ['heading', 'paragraph'] }),
ListItem,
BulletList,
OrderedList,
Highlight,
],
content: '',
});

const [isEmpty, setIsEmpty] = useState(true);


const [alignment, setAlignment] = useState('left');
const [selectedHeading, setSelectedHeading] = useState('0');
const [headingDropdownOpen, setHeadingDropdownOpen] = useState(false);
const [alignmentDropdownOpen, setAlignmentDropdownOpen] = useState(false);

const headingRef = useRef();


const alignmentRef = useRef();

useEffect(() => {
const handleClickOutside = (event) => {
if (!headingRef.current?.contains(event.target)) {
setHeadingDropdownOpen(false);
}
if (!alignmentRef.current?.contains(event.target)) {
setAlignmentDropdownOpen(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
useEffect(() => {
const updateEmptyState = () => {
setIsEmpty(editor?.isEmpty ?? true);
};

updateEmptyState();
editor?.on('update', updateEmptyState);
return () => editor?.off('update', updateEmptyState);
}, [editor]);

useEffect(() => {
const updateHeadingState = () => {
const active = [1, 2, 3].find((lvl) => editor?.isActive('heading', { level: lvl }));
setSelectedHeading(active ? String(active) : '0');
};

updateHeadingState();
editor?.on('update', updateHeadingState);
return () => editor?.off('update', updateHeadingState);
}, [editor]);

const handleAlignmentChange = (newAlignment) => {


if (alignment !== newAlignment) {
setAlignment(newAlignment);
editor.chain().focus().setTextAlign(newAlignment).run();
}
setAlignmentDropdownOpen(false);
};

const handleColorChange = (e) => {


editor.chain().focus().setColor(e.target.value).run();
};

if (!editor) return null;

return (
<div className="max-w-4xl mx-auto mt-8 bg-white p-2 rounded-lg shadow space-y-4">
<div className="flex flex-wrap gap-2 items-center pb-3 text-base">
{/* Heading Dropdown */}
<div className="relative" ref={headingRef}>
<button
onClick={() => {
setHeadingDropdownOpen((prev) => !prev);
setAlignmentDropdownOpen(false);
}}
className="p-1 px-2 rounded flex items-center justify-between w-30 text-base bg-
gray-400 text-white"
>
{selectedHeading === '0' ? 'Normal Text' : `Heading ${selectedHeading}`}
<FaChevronDown className="ml-1 w-3 h-3" />
</button>
{headingDropdownOpen && (
<div className="absolute mt-1 w-30 bg-white rounded shadow z-10 text-base">
{[{ label: 'Normal Text', value: '0', size: 'text-base' },
{ label: 'Heading 1', value: '1', size: 'text-xl' },
{ label: 'Heading 2', value: '2', size: 'text-2xl' },
{ label: 'Heading 3', value: '3', size: 'text-3xl' }]
.map(({ label, value, size }) => (
<button
key={value}
onClick={() => {
const valueNum = parseInt(value);
if (value === '0') {
editor.chain().focus().setParagraph().run();
} else {
const isActive = editor.isActive('heading', { level: valueNum });
editor.chain().focus().toggleHeading({ level: valueNum }).run();
if (isActive) setSelectedHeading('0');
}
setSelectedHeading(value);
setHeadingDropdownOpen(false);
}}
className={`block w-full text-left px-4 py-2 truncate overflow-hidden text-base
${selectedHeading === value ? 'bg-gray-400 text-white' : 'hover:bg-gray-100'} ${size}`}
>
{label}
</button>
))}
</div>
)}
</div>

{/* Alignment Dropdown */}


<div className="relative" ref={alignmentRef}>
<button
onClick={() => {
setAlignmentDropdownOpen((prev) => !prev);
setHeadingDropdownOpen(false);
}}
className={`px-2 py-1 flex items-center text-base rounded ${
alignment ? 'bg-gray-400 text-white' : 'hover:bg-gray-100'
}`}
>
{alignment === 'left' && <FaAlignLeft className="w-4 h-4" />}
{alignment === 'center' && <FaAlignCenter className="w-4 h-4" />}
{alignment === 'right' && <FaAlignRight className="w-4 h-4" />}
<FaChevronDown className="ml-2 w-3 h-3" />
</button>

{alignmentDropdownOpen && (
<div className="absolute bg-white shadow-lg rounded mt-1 w-24 z-10 text-base">
<button
onClick={() => handleAlignmentChange('left')}
className="flex items-center p-2 hover:bg-gray-100 w-full"
>
<FaAlignLeft className="mr-2 w-3 h-3" /> Left
</button>
<button
onClick={() => handleAlignmentChange('center')}
className="flex items-center p-2 hover:bg-gray-100 w-full"
>
<FaAlignCenter className="mr-2 w-3 h-3" /> Center
</button>
<button
onClick={() => handleAlignmentChange('right')}
className="flex items-center p-2 hover:bg-gray-100 w-full"
>
<FaAlignRight className="mr-2 w-3 h-3" /> Right
</button>
</div>
)}
</div>

{/* Text Color Picker */}


<div className="relative">
<input
type="color"
onChange={handleColorChange}
className="w-6 h-7 cursor-pointer rounded-md"
title="Text color"
/>
</div>

{/* Format Buttons */}


<ToolbarButton editor={editor} command="toggleBold" icon={<MdFormatBold />} />
<ToolbarButton editor={editor} command="toggleItalic" icon={<MdFormatItalic />} />
<ToolbarButton editor={editor} command="toggleUnderline" icon=
{<MdOutlineFormatUnderlined />} />
<ToolbarButton editor={editor} command="toggleStrike" icon=
{<MdOutlineStrikethroughS />} />
<ToolbarButton editor={editor} command="toggleCodeBlock" icon={<IoIosCode />} />
<ToolbarButton editor={editor} command="toggleHighlight" icon={<AiOutlineHighlight />}
title="Highlight Text" />
<ToolbarButton editor={editor} command="toggleBulletList" icon={<TbList />}
title="Bullet List" />
<ToolbarButton editor={editor} command="toggleOrderedList" icon=
{<PiListNumbersLight />} title="Ordered List" />
</div>

{/* Editor Content */}


<div className="relative">
{isEmpty && (
<div className="absolute text-gray-400 pointer-events-none left-4 top-4 text-base">
Start typing here...
</div>
)}
<EditorContent
editor={editor}
className="prose max-w-none min-h-[200px] p-4 focus:outline-none text-base"
tabIndex={0}
/>
</div>
</div>
);
};

const ToolbarButton = ({ editor, command, icon, title }) => {


if (!editor) return null;

const handleClick = () => {


const chain = editor.chain().focus();

const commands = {
toggleBold: () => chain.toggleBold().run(),
toggleItalic: () => chain.toggleItalic().run(),
toggleUnderline: () => chain.toggleUnderline().run(),
toggleStrike: () => chain.toggleStrike().run(),
toggleCodeBlock: () => chain.toggleCodeBlock().run(),
toggleHighlight: () => chain.toggleHighlight().run(),
toggleBulletList: () => {
if (editor.isActive('bulletList')) {
editor.commands.liftListItem('listItem');
} else {
chain.toggleBulletList().run();
}
},
toggleOrderedList: () => {
if (editor.isActive('orderedList')) {
editor.commands.liftListItem('listItem');
} else {
chain.toggleOrderedList().run();
}
},
};

commands[command]?.();
};

const isActive = editor && {


toggleBold: editor.isActive('bold'),
toggleItalic: editor.isActive('italic'),
toggleUnderline: editor.isActive('underline'),
toggleStrike: editor.isActive('strike'),
toggleCodeBlock: editor.isActive('codeBlock'),
toggleBulletList: editor.isActive('bulletList'),
toggleOrderedList: editor.isActive('orderedList'),
toggleHighlight: editor.isActive('highlight'),
}[command];
return (
<button
onClick={handleClick}
className={`w-8 h-8 rounded flex justify-center items-center text-lg transition-colors
duration-200 ${
isActive ? 'bg-gray-400 text-white' : 'hover:bg-gray-100'
}`}
title={title}
>
{icon}
</button>
);
};

export default TextEditor;

You might also like