0.1.6 • Published 6 months ago

@its-arun/tiptap-studio v0.1.6

Weekly downloads
-
License
MIT
Repository
-
Last release
6 months ago

@its-arun/tiptap-studio

A modern and feature-rich Rich Text Editor component built with TipTap for React applications.

Tiptap Studio Demo

Features

  • 📝 Rich Text Editor with comprehensive formatting options
  • 🌓 Light/Dark theme support
  • 📱 Responsive design
  • 🎨 Customizable toolbar and styling
  • 🔧 TypeScript support
  • 📊 Content Rendering (SSR/CSR)
  • 🖼️ Image support with captions
  • 📋 Table support
  • 🎯 Link management
  • 📝 Code blocks with syntax highlighting
  • 🔍 Source code editing with CodeMirror
  • ⚙️ Configurable image upload/fetch endpoints

Installation

Basic Installation

For basic editor functionality:

npm install @its-arun/tiptap-studio @tiptap/react @tiptap/pm @tiptap/starter-kit

Full Feature Installation

For all features including UI components, syntax highlighting, etc.:

npm install @its-arun/tiptap-studio @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-character-count @tiptap/extension-code-block-lowlight @tiptap/extension-color @tiptap/extension-highlight @tiptap/extension-image @tiptap/extension-link @tiptap/extension-list-keymap @tiptap/extension-placeholder @tiptap/extension-subscript @tiptap/extension-superscript @tiptap/extension-table @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-table-row @tiptap/extension-text-align @tiptap/extension-text-style @tiptap/extension-underline @radix-ui/react-dropdown-menu @radix-ui/react-popover @radix-ui/react-tooltip react-colorful react-icons lowlight

Peer Dependencies

Make sure you have the required peer dependencies installed:

npm install react react-dom

Basic Usage

import React, { useState } from "react";
import { TipTapEditor } from "@its-arun/tiptap-studio";
import "@its-arun/tiptap-studio/dist/styles.css"; // Import the styles

function MyEditor() {
  const [content, setContent] = useState("<p>Start writing...</p>");

  return (
    <TipTapEditor
      initialContent={content}
      onContentChange={(value) => setContent(value)}
      placeholder={{
        paragraph: "Start writing your content...",
        imageCaption: "Add a caption...",
      }}
    />
  );
}

export default MyEditor;

Advanced Usage

With Custom Configuration

import React, { useRef } from 'react';
import { TipTapEditor, type TiptapEditorRef } from '@its-arun/tiptap-studio';
import '@its-arun/tiptap-studio/dist/styles.css';

function AdvancedEditor() {
  const editorRef = useRef<TiptapEditorRef>(null);

  const handleSave = () => {
    const editor = editorRef.current?.getInstance();
    if (editor) {
      const html = editor.getHTML();
      const json = editor.getJSON();
      console.log({ html, json });
    }
  };

  return (
    <div>
      <TipTapEditor
        ref={editorRef}
        initialContent="<p>Advanced editor example</p>"
        output="html" // or "json"
        contentMinHeight={300}
        contentMaxHeight={600}
        hideMenuBar={false}
        hideStatusBar={false}
        hideBubbleMenu={false}
        onContentChange={(value) => console.log('Content changed:', value)}
      />
      <button onClick={handleSave}>Save Content</button>
    </div>
  );
}

With Custom Image Configuration

Configure your own image upload/fetch endpoints:

import React from 'react';
import { TipTapEditor, type ImageConfig } from '@its-arun/tiptap-studio';
import '@its-arun/tiptap-studio/dist/styles.css';

function EditorWithCustomImages() {
  const imageConfig: ImageConfig = {
    upload: {
      url: '/api/my-upload-endpoint',
      headers: {
        'Authorization': 'Bearer your-token',
      },
      formDataKey: 'image', // default is 'file'
    },
    fetch: {
      url: '/api/my-images-endpoint',
      headers: {
        'Authorization': 'Bearer your-token',
      },
    },
  };

  return (
    <TipTapEditor
      imageConfig={imageConfig}
      initialContent="<p>Editor with custom image handling</p>"
      onContentChange={(value) => console.log('Content:', value)}
    />
  );
}

With Custom Provider

import React from "react";
import {
  TiptapProvider,
  useTiptapContext,
  ExtensionKit,
} from "@its-arun/tiptap-studio";

function CustomEditor() {
  const editorOptions = {
    extensions: ExtensionKit,
    content: "<p>Custom provider example</p>",
    editable: true,
  };

  return (
    <TiptapProvider editorOptions={editorOptions}>
      <CustomToolbar />
      <EditorContent />
    </TiptapProvider>
  );
}

function CustomToolbar() {
  const { editor } = useTiptapContext();

  return (
    <div>
      <button
        onClick={() => editor.chain().focus().toggleBold().run()}
        data-active={editor.isActive("bold")}
      >
        Bold
      </button>
      <button
        onClick={() => editor.chain().focus().toggleItalic().run()}
        data-active={editor.isActive("italic")}
      >
        Italic
      </button>
    </div>
  );
}

Props

TipTapEditor Props

PropTypeDefaultDescription
initialContentContentundefinedInitial editor content (HTML string or JSON)
onContentChange(value: Content) => voidundefinedCallback when content changes
output"html" \| "json""html"Output format for content changes
placeholderobjectundefinedPlaceholder text for different elements
readonlybooleanfalseMake editor read-only
disabledbooleanfalseDisable editor interactions
hideMenuBarbooleanfalseHide the top menu bar
hideStatusBarbooleanfalseHide the bottom status bar
hideBubbleMenubooleantrueHide the bubble menu
contentMinHeightstring \| number200Minimum height of editor content
contentMaxHeightstring \| numberundefinedMaximum height of editor content
ssrbooleanauto-detectEnable server-side rendering support
imageConfigImageConfigundefinedConfigure image upload/fetch endpoints

ImageConfig Interface

interface ImageConfig {
  upload?: {
    url: string; // Upload endpoint URL
    headers?: Record<string, string>; // Optional headers
    formDataKey?: string; // Form data key (default: 'file')
  };
  fetch?: {
    url: string; // Fetch images endpoint URL
    headers?: Record<string, string>; // Optional headers
  };
}

Server-Side Rendering (SSR)

The editor automatically detects SSR environments and adjusts accordingly. You can also explicitly control SSR behavior:

// Explicitly enable SSR mode
<TipTapEditor ssr={true} />

// Explicitly disable SSR mode
<TipTapEditor ssr={false} />

// Auto-detect (default)
<TipTapEditor />

Next.js App Router Usage

When using the editor in Next.js with the App Router, you may need to import it dynamically to prevent SSR issues:

"use client";

import dynamic from "next/dynamic";

const TipTapEditor = dynamic(
  () => import("@its-arun/tiptap-studio").then((mod) => mod.TipTapEditor),
  {
    ssr: false,
    loading: () => <p>Loading editor...</p>,
  }
);

function MyEditor() {
  return (
    <TipTapEditor
      initialContent="<p>Start writing...</p>"
      onContentChange={(value) => console.log(value)}
    />
  );
}

Next.js Pages Router Usage

For the Pages Router, wrap your editor component:

import dynamic from "next/dynamic";

const TipTapEditor = dynamic(
  () => import("@its-arun/tiptap-studio").then((mod) => mod.TipTapEditor),
  {
    ssr: false,
  }
);

export default function MyPage() {
  return (
    <div>
      <TipTapEditor ssr={false} initialContent="<p>Start writing...</p>" />
    </div>
  );
}

Troubleshooting SSR Issues

If you encounter hydration errors like "SSR has been detected, and immediatelyRender has been set to true", try:

  1. Use dynamic imports: Import the editor dynamically with ssr: false
  2. Explicit SSR prop: Set ssr={false} on the TipTapEditor component
  3. Client-side only rendering: Wrap your editor in a component with 'use client' directive

Image Upload/Fetch API

Default API Endpoints

By default, the editor expects these endpoints:

  • Upload: POST /api/images - Upload a single image file
  • Fetch: GET /api/images - Get list of available images

API Response Format

Both endpoints must return responses in the following standardized format:

Upload Response Format

{
  "success": boolean,
  "data": {
    "id": string,
    "url": string,
    "filename": string,
    "mimeType": string,
    "size": number,
    "existing": boolean
  },
  "error"?: string
}

Fetch Response Format

{
  "success": boolean,
  "data": [
    {
      "id": number,
      "url": string,
      "filename": string,
      "mimeType": string,
      "size": number
    }
  ],
  "error"?: string
}

Custom API Implementation

You can implement your own image handling endpoints. Here are examples:

Upload Endpoint Example

// pages/api/images/route.js or app/api/images/route.js
export async function POST(request) {
  try {
    const formData = await request.formData();
    const file = formData.get("file"); // default formDataKey is 'file'

    if (!file) {
      return Response.json(
        {
          success: false,
          error: "No file provided",
        },
        { status: 400 }
      );
    }

    // Your upload logic here
    const result = await uploadToYourService(file);

    return Response.json({
      success: true,
      data: {
        id: result.id,
        url: result.url,
        filename: file.name,
        mimeType: file.type,
        size: file.size,
        existing: false,
      },
    });
  } catch (error) {
    return Response.json(
      {
        success: false,
        error: error.message || "Upload failed",
      },
      { status: 500 }
    );
  }
}

Fetch Endpoint Example

// pages/api/images/route.js or app/api/images/route.js
export async function GET(request) {
  try {
    // Your fetch logic here
    const images = await getImagesFromYourService();

    return Response.json({
      success: true,
      data: images.map((img) => ({
        id: img.id,
        url: img.url,
        filename: img.filename,
        mimeType: img.mimeType,
        size: img.size,
      })),
    });
  } catch (error) {
    return Response.json(
      {
        success: false,
        data: [],
        error: error.message || "Failed to fetch images",
      },
      { status: 500 }
    );
  }
}

Error Handling

The editor automatically handles API errors and displays user-friendly messages. Make sure your API returns:

  • success: false for any errors
  • Descriptive error messages for debugging
  • Appropriate HTTP status codes (400 for client errors, 500 for server errors)

Available Exports

// Main components
import {
  TipTapEditor,
  TiptapProvider,
  SourceEditor,
  TipTapClientRenderer,
  TipTapServerRenderer
} from '@its-arun/tiptap-studio';

// Hooks
import {
  useTiptapEditor,
  useTiptapContext
} from '@its-arun/tiptap-studio';

// Utils
import {
  isNodeSelected,
  isTextSelected,
  getNodeContainer,
  ExtensionKit
} from '@its-arun/tiptap-studio';

// UI Components
import {
  BubbleMenu,
  Tooltip,
  Input
} from '@its-arun/tiptap-studio';

// Types
import type {
  TiptapEditorProps,
  TiptapEditorRef,
  UseTiptapEditorOptions,
  ImageConfig,
  Editor,
  Content
} from '@its-arun/tiptap-studio';

Styling

The package includes default styles that you need to import:

import "@its-arun/tiptap-studio/dist/styles.css";

You can also customize the appearance using CSS variables:

:root {
  --rte-editor-font: "Inter", sans-serif;
  --rte-editor-font-size: 16px;
  --rte-editor-line-height: 1.6;
  --rte-editor-min-height: 200px;
  --rte-editor-max-height: 600px;
}

Tech Stack

  • React 18+
  • TipTap
  • CodeMirror (for source editing)
  • TypeScript
  • Radix UI (for UI components)
  • SCSS + CSS Variables

Credits

This package is based on the excellent work from next-tiptap by @ndtrung341. We've transformed their Next.js TipTap editor implementation into a reusable React component package.

License

MIT

0.1.6

6 months ago

0.1.5

6 months ago

0.1.4

6 months ago

0.1.3

6 months ago