Movatterモバイル変換


[0]ホーム

URL:


API Reference -@liveblocks/react-tiptap

@liveblocks/react-tiptap provides you with aReactplugin that adds collaboration to anyTiptap text editor.It also adds realtime cursors, document persistence on the cloud, comments, andmentions. Read ourget started guides tolearn more. Use@liveblocks/node-prosemirrorfor server-side editing.

Setup

To set up your collaborative Tiptap editor, adduseLiveblocksExtension to your editor, passing thereturn valueuseEditor extension array.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><EditorContenteditor={editor}/></div>);}

Liveblocks Tiptap components should be passededitor to enable them.

import{  useLiveblocksExtension,FloatingComposer,}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/></div>);}

Learn more in ourget started guides.

Default components

Toolbar

Displays a toolbar, allowing you to change the styles of selected text. You canadd contentbefore or after, or the toolbar’soptions can becustomized. Afloating toolbar also exists.

<Toolbareditor={editor}/>
Toolbar

Pass your Tiptapeditor to use the component. By default, one of the toolbarbuttons can create comment threads—to enable this addFloatingComposer anddisplay threads withAnchoredThreads orFloatingThreads.

import{  useLiveblocksExtension,Toolbar,FloatingComposer,FloatingThreads,}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><Toolbareditor={editor}/><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/><FloatingThreadseditor={editor}style={{ width:"350px"}}/></div>);}

Extending the defaults

You can insert contentbefore the first button andafter the last buttonusingbefore andafter. Components such asToolbar.Button andToolbar.Toggle can be used to create new buttons.

import{Toolbar}from"@liveblocks/react-tiptap";import{Icon}from"@liveblocks/react-ui";
<Toolbareditor={editor}before={<>I'm at the start</>}after={<Toolbar.Buttonname="Help"icon={<Icon.QuestionMark/>}shortcut="CMD-H"onClick={()=>console.log("help")}/>}/>;

For more complex customization, instead readcreating a custom floating toolbar.

Creating a custom toolbar

By passing elements as children, it’s possible to create a fully custom toolbar.

import{Toolbar}from"@liveblocks/react-lexical";import{Editor}from"@tiptap/react";
functionCustomToolbar({ editor}:{ editor:Editor|null}){return(<Toolbareditor={editor}> Hello<strong>world</strong></Toolbar>);}

Each part of our default toolbar is available as blocks which can be slottedtogether. This is how the default toolbar is constructed:

import{Toolbar}from"@liveblocks/react-tiptap";import{Editor}from"@tiptap/react";
functionCustomToolbar({ editor}:{ editor:Editor|null}){return(<Toolbareditor={editor}><Toolbar.BlockSelector/><Toolbar.SectionInline/><Toolbar.Separator/><Toolbar.SectionCollaboration/></Toolbar>);}

You can mix these default components with any custom ones of your own. Below theToolbar.SectionHistory component is added alongside some custom buttonscreated withToolbar.Button,Toolbar.Toggle, andIcon. Thehighlight toggle button requires aTiptap extension.

import{Toolbar}from"@liveblocks/react-tiptap";import{Icon}from"@liveblocks/react-ui";import{Editor}from"@tiptap/react";
functionCustomToolbar({ editor}:{ editor:Editor|null}){return(<Toolbareditor={editor}><Toolbar.SectionHistory/><Toolbar.Separator/><Toolbar.Buttonname="Help"icon={<Icon.QuestionMark/>}shortcut="CMD-H"onClick={()=>console.log("help")}/><Toolbar.Togglename="Highlight"icon={<div>🖊️</div>}active={editor?.isActive("highlight")??false}onClick={()=> editor?.chain().focus().toggleHighlight().run()}disabled={!editor?.can().chain().focus().toggleHighlight().run()}/></Toolbar>);}

To learn more about the different components, read more below.

Props

  • editorEditor | nullRequired

    The Tiptap editor.

  • childrenReactNode

    The content of the toolbar, overriding the default content. Use thebeforeandafter props if you want to keep and extend the default content. AnyReactNode orToolbar.* components work inside.

  • beforeReactNode

    The content to display at the start of the toolbar. AnyReactNode orToolbar.* components work inside.

  • afterReactNode

    The content to display at the end of the toolbar. AnyReactNode orToolbar.* components work inside.

Toolbar.Button

A button for triggering actions. Thename is displayed in a tooltip. Propssuch asonClick will be passed to the underlyingbutton element.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.Buttonname="Question"onClick={(e)=>console.log("Clicked")}/></Toolbar>;

Optionally takes an icon which will visually replace thename. Also optionallyaccepts a shortcut, which is displayed in the tooltip. Comment key names areconverted to symbols. Here are various examples.

import{Toolbar}from"@liveblocks/react-tiptap";import{Icon}from"@liveblocks/react-ui";
// Button says "Question"<Toolbar.Togglename="Question"onClick={/* ... */}/>
// Tooltip says "Question [⌘+Q]"<Toolbar.Buttonname="Question"shortcut="CMD+Q"onClick={/* ... */}/>
// Custom icon, replaces the name in the button<Toolbar.Buttonname="Question"icon={<div>?</div>}onClick={/* ... */}/>
// Using a Liveblocks icon, replaces the name in the button<Toolbar.Buttonname="Question"icon={<Icon.QuestionMark/>}onClick={/* ... */}/>
// Passing children visually replaces the `name` and `icon`<Toolbar.Buttonname="Question"onClick={/* ... */}> ? Ask a question</Toolbar.Button>
// Props are passed to the inner `button`<Toolbar.Buttonname="Question"style={{ marginLeft:10}}className="custom-button"onMouseOver={()=>console.log("Hovered")}/>
Props
  • namestringRequired

    The name of this button displayed in its tooltip. Will also be displayed inthe button if noicon orchildren are passed.

  • iconReactNode

    An optional icon displayed in this button.

  • shortcutstring

    An optional keyboard shortcut displayed in this button’s tooltip. Commonshortcuts such will be replaced by their symbols, for exampleCMD.

Toolbar.Toggle

A toggle button for values that can be active or inactive. Best used with texteditor commands. Thename is displayed in a tooltip. Props will be passed tothe underlyingbutton element.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.Togglename="Highlight"active={editor?.isActive("highlight")??false}onClick={()=> editor?.chain().focus().toggleHighlight().run()}/></Toolbar>;

The snippet above shows how to use the Toggle with theTiptap highlight extension.The toggle button can also be toggled withuseState.

import{Toolbar}from"@liveblocks/react-tiptap";import{Editor}from"@tiptap/react";import{ useState}from"react";
functionCustomToggle({ editor}:{ editor:Editor|null}){const[active, setActive]=useState(false);
return(<Toolbar.Togglename="Toggle options"active={active}onClick={()=>setActive(!active)}/>);}

Toolbar.Toggle optionally takes an icon which will visually replace thename. Also optionally accepts a shortcut, which is displayed in the tooltip.Comment key names are converted to symbols. Here are various examples.

import{Toolbar}from"@liveblocks/react-tiptap";import{Icon}from"@liveblocks/react-ui";
// Button says "Highlight"<Toolbar.Togglename="Highlight"active={/* ... */}onClick={/* ... */}/>
// Tooltip says "Highlight [⌘+H]"<Toolbar.Togglename="Highlight"shortcut="CMD+H"active={/* ... */}onClick={/* ... */}/>
// Custom icon, replaces the name in the button<Toolbar.Togglename="Highlight"icon={<div>🖊</div>}active={/* ... */}onClick={/* ... */}/>
// Using a Liveblocks icon, replaces the name in the button<Toolbar.Togglename="Highlight"icon={<Icon.QuestionMark/>}active={/* ... */}onClick={/* ... */}/>
// Passing children visually replaces the `name` and `icon`<Toolbar.Togglename="Highlight"active={/* ... */}onClick={/* ... */}> 🖊️Highlight</Toolbar.Toggle>
// Props are passed to the inner `button`<Toolbar.Togglename="Highlight"active={/* ... */}onClick={/* ... */}style={{ marginLeft:10}}className="custom-toggle"onMouseOver={()=>console.log("Hovered")}/>
Props
  • namestringRequired

    The name of this button displayed in its tooltip. Will also be displayed inthe button if noicon orchildren are passed.

  • activebooleanRequired

    Whether the button is toggled.

  • iconReactNode

    An optional icon displayed in this button.

  • shortcutstring

    An optional keyboard shortcut displayed in this button’s tooltip. Commonshortcuts such will be replaced by their symbols, for exampleCMD.

Toolbar.BlockSelector

Adds a dropdown selector for switching between different block types, such astext,heading 1,blockquote. Props will be passed to the innerbuttonelement. Can also be placed insideFloatingToolbar.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.BlockSelector/></Toolbar>;
Use custom item options

If you’d like to change the items shown in the dropdown menu, you can pass acustomitems array. Below a code block item(Tiptap extension)is added after the default options.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.BlockSelectoritems={(defaultItems)=>[...defaultItems,{ name:"Code block", icon:<div>❮ ❯</div>,// OptionalisActive:(editor)=> editor.isActive("codeBlock"),setActive:(editor)=> editor.chain().focus().clearNodes().toggleCodeBlock().run(),},]}/></Toolbar>;
Customize item styles

By passing alabel property, you can overwrite the styles of the dropdownitems. The toolbar button will still display thename, but in the dropdown,thelabel will be used instead of thename andicon. Below, a new item isadded and itslabel is customized.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.BlockSelectoritems={(defaultItems)=>[...defaultItems,{ name:"Code block", label:<divclassName="font-mono">Code</div>,// Optional, overwrites `icon` + `name`isActive:(editor)=> editor.isActive("codeBlock"),setActive:(editor)=> editor.chain().focus().clearNodes().toggleCodeBlock().run(),},]}/></Toolbar>;

You can also customize the default items. Below each item is styled to representthe effect each block applies to the document.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbar.BlockSelector items={(defaultItems)=> defaultItems.map((item)=>{let label;
if(item.name==="Text"){ label=<span>Regular text</span>;}
if(item.name==="Heading 1"){ label=(<spanstyle={{ fontSize:18, fontWeight:"bold"}}>Heading 1</span>);}
if(item.name==="Heading 2"){ label=(<spanstyle={{ fontSize:16, fontWeight:"bold"}}>Heading 2</span>);}
if(item.name==="Heading 3"){ label=(<spanstyle={{ fontSize:15, fontWeight:"bold"}}>Heading 3</span>);}
if(item.name==="Blockquote"){ label=(<spanstyle={{ borderLeft:"3px solid gray", paddingLeft:8}}> Blockquote</span>);}
return{...item, label, icon:null,// Hide all icons};})}/>;
Props
  • itemsarray | function

    The items displayed in this block selector. When provided as an array, thedefault items are overridden. To avoid this, a function can be providedinstead and it will receive the default items.

Toolbar.Separator

Adds a visual, and accessible, separator used to separate sections in thetoolbar. Props will be passed to the innerdiv element. Can also be placedinsideFloatingToolbar.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.SectionHistory/></Toolbar>;

Toolbar.SectionHistory

Adds a section containingundo andredo buttons. Can also be placed insideFloatingToolbar.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.SectionHistory/></Toolbar>;

Toolbar.SectionInline

Adds a section containing inline formatting actions such asbold,italic,underline. Can also be placed insideFloatingToolbar.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.SectionInline/></Toolbar>;

Toolbar.SectionCollaboration

Adds a section containing anadd comment button. Can also be placed insideFloatingToolbar.

import{Toolbar}from"@liveblocks/react-tiptap";
<Toolbareditor={editor}><Toolbar.SectionCollaboration/></Toolbar>;

FloatingToolbar

Displays a floating toolbar near the current Tiptap selection, allowing you tochange styles. You can add contentbefore or after, or the toolbar’soptions can becustomized. Astatic toolbar also exists.

<FloatingToolbareditor={editor}/>
FloatingToolbar

Pass your Tiptapeditor to use the component. By default, one of the toolbarbuttons can create comment threads—to enable this addFloatingComposer anddisplay threads withAnchoredThreads orFloatingThreads.

import{  useLiveblocksExtension,FloatingToolbar,FloatingComposer,FloatingThreads,}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><EditorContenteditor={editor}/><FloatingToolbareditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/><FloatingThreadseditor={editor}style={{ width:"350px"}}/></div>);}

Changing float position

Usingposition andoffset you can reposition the toolbar relative to thecurrent selection.position can be set to"top" or"bottom", andoffsetdefines the vertical distance in pixels from the selection.

<FloatingToolbareditor={editor}position="bottom"// Position can be `top` or `bottom`offset={12}// Distance in px from selection/>

Extending the defaults

You can insert custom contentbefore the first button andafter the lastbutton usingbefore andafter. Components such asToolbar.Button andToolbar.Toggle can be used to create new buttons.

import{Toolbar}from"@liveblocks/react-tiptap";import{Icon}from"@liveblocks/react-ui";
<FloatingToolbareditor={editor}before={<>I'm at the start</>}after={<Toolbar.Buttonname="Help"icon={<Icon.QuestionMark/>}shortcut="CMD-H"onClick={()=>console.log("help")}/>}/>;

For more complex customization, instead readcreating a custom floating toolbar.

Creating a custom floating toolbar

By passing elements as children, it’s possible to create a fully custom floatingtoolbar.

import{FloatingToolbar}from"@liveblocks/react-tiptap";import{Editor}from"@tiptap/react";
functionCustomToolbar({ editor}:{ editor:Editor|null}){return(<FloatingToolbareditor={editor}> Hello<strong>world</strong></FloatingToolbar>);}

Each part of our default toolbar is available as blocks which can be slottedtogether. This is how the default floating toolbar is constructed:

import{FloatingToolbar,Toolbar}from"@liveblocks/react-tiptap";import{Editor}from"@tiptap/react";
functionCustomToolbar({ editor}:{ editor:Editor|null}){return(<FloatingToolbareditor={editor}><Toolbar.BlockSelector/><Toolbar.SectionInline/><Toolbar.Separator/><Toolbar.SectionCollaboration/></FloatingToolbar>);}

You can mix these default components with any custom ones of your own. Below theToolbar.SectionHistory component is added alongside some custom buttonscreated withToolbar.Button,Toolbar.Toggle, andIcon. Thehighlight toggle button requires aTiptap extension.

import{FloatingToolbar,Toolbar}from"@liveblocks/react-tiptap";import{Icon}from"@liveblocks/react-ui";import{Editor}from"@tiptap/react";
functionCustomToolbar({ editor}:{ editor:Editor|null}){return(<FloatingToolbareditor={editor}><Toolbar.SectionHistory/><Toolbar.Separator/><Toolbar.Buttonname="Help"icon={<Icon.QuestionMark/>}shortcut="CMD-H"onClick={()=>console.log("help")}/><Toolbar.Togglename="Highlight"icon={<div>🖊️</div>}active={editor?.isActive("highlight")??false}onClick={()=> editor?.chain().focus().toggleHighlight().run()}disabled={!editor?.can().chain().focus().toggleHighlight().run()}/></FloatingToolbar>);}

To learn more about the different components, read more underToolbar.

Props

  • editorEditor | nullRequired

    The Tiptap editor.

  • position"top" | "bottom"

    The vertical position of the floating toolbar.

  • offsetnumber

    The vertical offset of the floating toolbar from the selection.

  • childrenReactNode

    The content of the toolbar, overriding the default content. Use thebeforeandafter props if you want to keep and extend the default content. AnyReactNode orToolbar.* components work inside.

  • beforeReactNode

    The content to display at the start of the toolbar. AnyReactNode orToolbar.* components work inside.

  • afterReactNode

    The content to display at the end of the toolbar. AnyReactNode orToolbar.* components work inside.

FloatingComposer

Displays aComposer near the current Tiptap selection, allowing you tocreate threads.

<FloatingComposereditor={editor}/>
FloatingComposer

Submitting a comment will attach an annotation thread at the current selection.Should be passed your Tiptapeditor, and it’s recommended you set a widthvalue. Display created threads withAnchoredThreads orFloatingThreads.

import{  useLiveblocksExtension,FloatingComposer,FloatingThreads,}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/><FloatingThreadseditor={editor}style={{ width:"350px"}}/></div>);}

Opening the composer

To open theFloatingComposer, you need to click the “Comment” button in theToolbar or call theaddPendingCommentcommand added by Liveblocks. Youcan useliveblocksCommentMark to check if the current selection is a comment.

import{Editor}from"@tiptap/react";
functionToolbar({ editor}:{ editor:Editor|null}){if(!editor){returnnull;}
return(<buttononClick={()=>{ editor.chain().focus().addPendingComment().run();}}data-active={editor.isActive("liveblocksCommentMark")}> 💬 New comment</button>);}

Props

  • metadataThreadMetadata

    The metadata of the thread to create.

  • onComposerSubmitfunction

    The event handler called when the composer is submitted.

  • defaultValueCommentBody

    The composer’s initial value.

  • collapsedboolean

    Whether the composer is collapsed. Setting a value will make the composercontrolled.

  • onCollapsedChangefunction

    The event handler called when the collapsed state of the composer changes.

  • defaultCollapsedboolean

    Whether the composer is initially collapsed. Setting a value will make thecomposer uncontrolled.

  • disabledboolean

    Whether the composer is disabled.

  • autoFocusboolean

    Whether to focus the composer on mount.

  • overridesPartial<GlobalOverrides & ComposerOverrides>

    Override the component’s strings.

FloatingThreads

Displays floatingThread components below text highlights in the editor.

<FloatingThreadseditor={editor}threads={threads}/>
FloatingThreads

Takes a list of threads retrieved fromuseThreads and renders them to thepage. Each thread is opened by clicking on its corresponding text highlight.Should be passed your Tiptapeditor, and it’s recommended you set a widthvalue.

import{ useThreads}from"@liveblocks/react/suspense";import{  useLiveblocksExtension,FloatingComposer,FloatingThreads,}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
const{ threads}=useThreads();
return(<div><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/><FloatingThreadseditor={editor}threads={threads}style={{ width:"350px"}}/></div>);}
Resolved threads

TheFloatingThreads component automatically excludes resolved threads fromdisplay. Any resolved threads passed in the threads list will not be shown.

Recommended usage

FloatingThreads andAnchoredThreads have been designed to worktogether to provide the optimal experience on mobile and desktop. We generallyrecommend using both components, hiding one on smaller screens, as we are belowwith Tailwind classes. Most apps also don’t need to display resolved threads, sowe can filter those out with auseThreads option.

import{ useThreads}from"@liveblocks/react/suspense";import{AnchoredThreads,FloatingThreads}from"@liveblocks/react-tiptap";import{Editor}from"@tiptap/react";
functionThreadOverlay({ editor}:{ editor:Editor|null}){const{ threads}=useThreads({ query:{ resolved:false}});
return(<><FloatingThreadseditor={editor}threads={threads}className="w-[350px] block md:hidden"/><AnchoredThreadseditor={editor}threads={threads}className="w-[350px] hidden sm:block"/></>);}

We can place this component insideClientSideSuspense to prevent itrendering until threads have loaded.

<div><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/><ClientSideSuspensefallback={null}><ThreadOverlayeditor={editor}/></ClientSideSuspense></div>

Customization

TheFloatingThreads component acts as a wrapper around each individualThread. You can treat the component like you would adiv, using classes,listeners, and more.

<FloatingThreadseditor={editor}threads={threads}className="my-floating-thread"/>

To apply styling to eachThread, you can pass a customThread propertytocomponents and modify this in any way. This is the best way to modify athread’s width.

import{Thread}from"@liveblocks/react-ui";
<FloatingThreads editor={editor} threads={threads} className="my-floating-thread" components={{Thread:(props)=>(<Thread{...props}className="border shadow"style={{ width:"300px"}}/>),}}/>;

You can return any customReactNode here, including anything from a simplewrapper aroundThread, up to a full customThread component built using ourComment primitives.

import{Comment}from"@liveblocks/react-ui/primitives";
<FloatingThreads editor={editor} threads={threads} className="my-floating-thread" components={{Thread:(props)=>(<div>{props.thread.comments.map((comment)=>(<Comment.Bodykey={comment.id}body={comment.body}components={/* ... */}/>))}</div>),}}/>;

Props

  • threadsThreadData[]Required

    The threads to display.

  • componentsPartial<AnchoredThreadsComponents>

    Override the component’s components.

  • components.Thread(props: ThreadProps) => ReactNode

    Override theThreadcomponent.

AnchoredThreads

Displays a list ofThread components vertically alongside the editor.

<AnchoredThreadseditor={editor}threads={threads}/>
AnchoredThreads

Takes a list of threads retrieved fromuseThreads and renders them to thepage. Each thread is displayed at the same vertical coordinates as itscorresponding text highlight. If multiple highlights are in the same location,each thread is placed in order below the previous thread.

import{ useThreads}from"@liveblocks/react/suspense";import{  useLiveblocksExtension,FloatingComposer,AnchoredThreads,}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
const{ threads}=useThreads();
return(<div><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/><AnchoredThreadseditor={editor}threads={threads}style={{ width:"350px"}}/></div>);}
Resolved threads

TheAnchoredThreads component automatically excludes resolved threads fromdisplay. Any resolved threads passed in the threads list will not be shown.

Recommended usage

FloatingThreads andAnchoredThreads have been designed to worktogether to provide the optimal experience on mobile and desktop. We generallyrecommend using both components, hiding one on smaller screens, as we are belowwith Tailwind classes. Most apps also don’t need to display resolved threads, sowe can filter those out with auseThreads option.

import{ useThreads}from"@liveblocks/react/suspense";import{AnchoredThreads,FloatingThreads}from"@liveblocks/react-tiptap";import{Editor}from"@tiptap/react";
functionThreadOverlay({ editor}:{ editor:Editor|null}){const{ threads}=useThreads({ query:{ resolved:false}});
return(<><FloatingThreadseditor={editor}threads={threads}className="w-[350px] block md:hidden"/><AnchoredThreadseditor={editor}threads={threads}className="w-[350px] hidden sm:block"/></>);}

We can place this component insideClientSideSuspense to prevent itrendering until threads have loaded.

<div><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:"350px"}}/><ClientSideSuspensefallback={null}><ThreadOverlayeditor={editor}/></ClientSideSuspense></div>

Customization

TheAnchoredThreads component acts as a wrapper around eachThread. Ithas no width, so setting this is required, and each thread will take on thewidth of the wrapper. You can treat the component like you would adiv, usingclasses, listeners, and more.

<AnchoredThreadseditor={editor}threads={threads}style={{ width:"350px"}}className="my-anchored-thread"/>

To apply styling to eachThread, you can pass a customThread propertytocomponents and modify this in any way.

import{Thread}from"@liveblocks/react-ui";
<AnchoredThreads editor={editor} threads={threads} style={{ width:"350px"}} className="my-anchored-thread" components={{Thread:(props)=>(<Thread{...props}className="border shadow"style={{ background:"white"}}/>),}}/>;

You can return any customReactNode here, including anything from a simplewrapper aroundThread, up to a full customThread component built using ourComment primitives.

import{Comment}from"@liveblocks/react-ui/primitives";
<AnchoredThreads editor={editor} threads={threads} style={{ width:"350px"}} className="my-anchored-thread" components={{Thread:(props)=>(<div>{props.thread.comments.map((comment)=>(<Comment.Bodykey={comment.id}body={comment.body}components={/* ... */}/>))}</div>),}}/>;
Modifying thread floating positions

Using CSS variables you can modify the gap between threads, and the horizontaloffset that’s added when a thread is selected.

.lb-tiptap-anchored-threads{/* Minimum gap between threads */--lb-tiptap-anchored-threads-gap:8px;
/* How far the active thread is offset to the left */--lb-tiptap-anchored-threads-active-thread-offset:12px;}

Props

  • threadsThreadData[]Required

    The threads to display.

  • componentsPartial<AnchoredThreadsComponents>

    Override the component’s components.

  • components.Thread(props: ThreadProps) => ReactNode

    Override theThreadcomponent.

HistoryVersionPreviewbeta

Private beta

Version history is currently in private beta. If you would like access to thebeta, pleasecontact us. We’d love tohear from you.

TheHistoryVersionPreview component allows you to display a preview of aspecific version of your Tiptap editor’s content. It also contains a button andlogic for restoring. It must be used inside the<LiveblocksPlugin> context. Torender a list of versions, seeVersionHistory.

Usage

import{HistoryVersionPreview}from"@liveblocks/react-tiptap";
functionVersionPreview({ selectedVersion, onVersionRestore}){return(<HistoryVersionPreviewversion={selectedVersion}onVersionRestore={onVersionRestore}/>);}

Props

  • versionHistoryVersionRequired

    The version of the editor content to preview.

  • onVersionRestore(version: HistoryVersion) => void

    Callback function called when the user chooses to restore this version.

TheHistoryVersionPreview component renders a read-only view of the specifiedversion of the editor content. It also provides a button for users to restorethe displayed version.

AiToolbarbeta

Private beta

AI Copilots is currently in private beta. If you would like access to the beta,pleasecontact us. We’d love to hear fromyou.

Displays a floating AI toolbar near the current Tiptap selection, allowing youto use AI to apply changes to the document or ask questions about it.

<AiToolbareditor={editor}/>

Pass your Tiptapeditor to use the component, and enable (or customize) the AIoption inuseLiveblocksExtension.

import{  useLiveblocksExtension,AiToolbar,FloatingToolbar,}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension({ ai:true,});
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><EditorContenteditor={editor}/><AiToolbareditor={editor}/><FloatingToolbareditor={editor}/></div>);}

Opening the toolbar

To open theAiToolbar, you need to call theaskAicommand added by Liveblocks.

import{Editor}from"@tiptap/react";
functionToolbar({ editor}:{ editor:Editor|null}){if(!editor){returnnull;}
return(<buttononClick={()=>{ editor.chain().focus().askAi().run();}}> ✨ Ask AI</button>);}

You can also pass a prompt to theaskAi command and it will directly requestit when opening the toolbar.

<buttononClick={()=>{    editor.chain().focus().askAi("Add emojis to the text").run();}}>  😃 Emojify</button>

Customizing suggestions

By default, the AI toolbar displays a list of suggestions (e.g. “Fix mistakes”,“Explain”, etc). These can be customized via thesuggestions prop and theAiToolbar.Suggestion component.

<AiToolbareditor={editor}suggestions={<><AiToolbar.SuggestionsLabel>Suggested</AiToolbar.SuggestionsLabel><AiToolbar.Suggestion>Fix mistakes</AiToolbar.Suggestion><AiToolbar.Suggestionprompt="Add emojis to the text">        Emojify</AiToolbar.Suggestion><AiToolbar.SuggestionsSeparator/><AiToolbar.Suggestionicon={<Icon.Sparkles/>}>        Continue writing</AiToolbar.Suggestion></>}/>

Doing so will override the default suggestions, instead you can use a functionto keep them while adding your own.

<AiToolbareditor={editor}suggestions={({ children})=>(<>{children}<AiToolbar.SuggestionsSeparator/><AiToolbar.SuggestionsLabel>Custom</AiToolbar.SuggestionsLabel><AiToolbar.Suggestion>Custom suggestion</AiToolbar.Suggestion></>)}/>

Props

  • editorEditor | nullRequired

    The Tiptap editor.

  • offsetnumber

    The vertical offset of the AI toolbar from the selection.

  • suggestionsReactNode

    The prompt suggestions to display below the AI toolbar.

AiToolbar.Suggestion

A prompt suggestion displayed below the AI toolbar.

import{AiToolbar}from"@liveblocks/react-tiptap";
<AiToolbareditor={editor}suggestions={<><AiToolbar.Suggestion>Fix mistakes</AiToolbar.Suggestion></>}/>;

By default, selecting a suggestion will use its label from thechildren as theprompt, this can be overridden with theprompt prop. Also optionally takes anicon.

import{AiToolbar}from"@liveblocks/react-tiptap";import{Icon}from"@liveblocks/react-ui";
// "Translate to French" is displayed in the suggestion and used as the prompt<AiToolbar.Suggestion>Translate to French</AiToolbar.Suggestion>
// "Emojify" is displayed in the suggestion but "Add emojis to the text" is used as the prompt<AiToolbar.Suggestionprompt="Add emojis to the text">Emojify</AiToolbar.Suggestion>
// Custom icon<AiToolbar.Suggestionicon={<div>?</div>}>Explain</AiToolbar.Suggestion>
// Using a Liveblocks icon<AiToolbar.Suggestionicon={<Icon.QuestionMark/>}>Explain</AiToolbar.Suggestion>
Props
  • childrenReactNode

    The suggestion’s label, used as the prompt if theprompt prop is not set.

  • iconReactNode

    An optional icon displayed before the label.

  • promptstring

    The prompt to use instead of the label.

AiToolbar.SuggestionsLabel

A label to describe a group of prompt suggestions displayed in the AI toolbar.

import{AiToolbar}from"@liveblocks/react-tiptap";
<AiToolbareditor={editor}suggestions={<><AiToolbar.SuggestionsLabel>Translation</AiToolbar.SuggestionsLabel><AiToolbar.Suggestion>Translate in French</AiToolbar.Suggestion><AiToolbar.Suggestion>Translate in English</AiToolbar.Suggestion></>}/>;

AiToolbar.SuggestionsSeparator

A separator between groups of prompt suggestions displayed in the AI toolbar.

import{AiToolbar}from"@liveblocks/react-tiptap";
<AiToolbareditor={editor}suggestions={<><AiToolbar.Suggestion>Translate in French</AiToolbar.Suggestion><AiToolbar.Suggestion>Translate in English</AiToolbar.Suggestion><AiToolbar.SuggestionsSeparator/><AiToolbar.Suggestion>Custom suggestion</AiToolbar.Suggestion></>}/>;

Hooks

useLiveblocksExtension

Liveblocks plugin for Tiptap that adds collaboration to your editor.liveblocks should be passed to Tiptap’suseEditor as an extension.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><EditorContenteditor={editor}/></div>);}

A number of options can be applied.

const liveblocks=useLiveblocksExtension({  initialContent:"Hello world",  field:"editor-one",
// Other options// ...});
Returns
Arguments
  • options.initialContentContent

    The initial content for the editor, if it’s never been set.Learnmore.

  • options.fieldstring

    The name of this text editor’s field. Allows you to use multiple editors onone page, if each has a separate field value.Learnmore.

  • options.offlineSupport_experimentalbooleanDefault is false

    Experimental. Enable offline support using IndexedDB. This means the afterthe first load, documents will be stored locally and load instantly.Learnmore.

  • options.commentsbooleanDefault is true

    Enable comments in the editor.

  • options.mentionsbooleanDefault is true

    Enable mentions in the editor.

  • options.aiboolean | AiConfigurationDefault is false

    Enable AI in the editor and optionally customize configuration options.

Setting initial content

Initial content for the editor can be set withinitialContent. This contentwill only be used if the current editor has never been edited by any users, andis ignored otherwise.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";
functionTextEditor(){const liveblocks=useLiveblocksExtension({ initialContent:"<p>Hello world</p>",});
// ...}

Multiple editors

It’s possible to use multiple editors on one page by passing values to thefield property. Think of it like an ID for the current editor.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";
functionTextEditor(){const liveblocks=useLiveblocksExtension({ field:"editor-one",});
// ...}

Here’s an example of how multiple editors may be set up.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";import{ useEditor,EditorContent}from"@tiptap/react";
functionTextEditors(){return(<div><TextEditorfield="one"/><TextEditorfield="two"/></div>);}
functionTextEditor({ field}:{ field:string}){const liveblocks=useLiveblocksExtension({ field});
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div><EditorContenteditor={editor}/></div>);}

Offline supportexperimental

It’s possible to enable offline support in your editor with an experimentaloption. This means that once a document has been opened, it’s saved locally onthe browser, and can be shown instantly without a loading screen. As soon asLiveblocks connects, any remote changes will be synchronized, without any loadspinner. Enable this by passing aofflineSupport_experimental value.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";
functionTextEditor(){const liveblocks=useLiveblocksExtension({ offlineSupport_experimental:true,});
// ...}

To make sure that your editor loads instantly, you must structure your appcarefully to avoid any Liveblocks hooks andClientSideSuspense componentsfrom triggering a loading screen. For example, if you’re displaying threads inyour editor withuseThreads, you must place this inside a separatecomponent and wrap it inClientSideSuspense.

"use client";
import{ClientSideSuspense, useThreads}from"@liveblocks/react/suspense";import{ useLiveblocksExtension,AnchoredThreads,FloatingComposer,}from"@liveblocks/react-tiptap";import{Editor,EditorContent, useEditor}from"@tiptap/react";
exportfunctionTiptapEditor(){const liveblocks=useLiveblocksExtension({ offlineSupport_experimental:true,});
const editor=useEditor({ extensions:[ liveblocks,// ...], immediatelyRender:false,});
return(<><EditorContenteditor={editor}/><FloatingComposereditor={editor}style={{ width:350}}/><ClientSideSuspensefallback={null}><Threadseditor={editor}/></ClientSideSuspense></>);}
functionThreads({ editor}:{ editor:Editor}){const{ threads}=useThreads();
return<AnchoredThreadseditor={editor}threads={threads}/>;}

Customizing AI componentsbeta

Private beta

AI Copilots is currently in private beta. If you would like access to the beta,pleasecontact us. We’d love to hear fromyou.

By default, AI components likeAiToolbar use the term"AI". This can becustomized with theai.name option inuseLiveblocksExtension. This valuewill be used throughout the AI components:"Ask {name}" in theToolbar/FloatingToolbar default buttons,"{name} is thinking…" in theAiToolbar, etc.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";import{ useEditor}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension({ ai:{// "Ask Liveblocks anything…", "Liveblocks is thinking…", etc name:"Liveblocks",},});
const editor=useEditor({ extensions:[ liveblocks,// ...],});
// ...}

If you’re after visual customization, AI components likeAiToolbarintegrate with the rest of Liveblocks' styles, heavily using tokens like--lb-accent for example. Learn more aboutstyling.

Generating AI toolbar responsesbeta

Private beta

AI Copilots is currently in private beta. If you would like access to the beta,pleasecontact us. We’d love to hear fromyou.

By default, theAiToolbar component sends its requests to Liveblocks togenerate its responses. This can be customized via theai.resolveContextualPrompt option inuseLiveblocksExtension. This optionaccepts an async function which will be called by the AI toolbar whenever aprompt is requested, it will receive the prompt and some context (the document’sand selection’s text, the previous request if it’s a follow-up, etc) and isexpected to return the type of response and the text to use.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";import{ useEditor}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension({ ai:{resolveContextualPrompt:async({ prompt, context, signal})=>{const response=awaitfetch("/api/contextual-prompt",{ method:"POST", body:JSON.stringify({ prompt, context}), signal,});
return response.json();},},});
const editor=useEditor({ extensions:[ liveblocks,// ...],});
// ...}

useIsEditorReady

Used to check if the editor content has been loaded or not, helpful fordisplaying a loading skeleton.

import{ useIsEditorReady}from"@liveblocks/react-tiptap";
const status=useIsEditorReady();

Here’s how it can be used in the context of your editor.

import{ useLiveblocksExtension}from"@liveblocks/react-tiptap";import{ useIsEditorReady, useEditor,EditorContent}from"@tiptap/react";
functionTextEditor(){const liveblocks=useLiveblocksExtension();const ready=useIsEditorReady();
const editor=useEditor({ extensions:[ liveblocks,// ...],});
return(<div>{!ready?<div>Loading...</div>:<EditorContenteditor={editor}/>}</div>);}

Stylesheets

React Tiptap comes with default styles, and these can be imported into the rootof your app or directly into a CSS file with@import. Note that you must alsoinstall and import a stylesheet from@liveblocks/react-ui to use thesestyles.

import"@liveblocks/react-ui/styles.css";import"@liveblocks/react-tiptap/styles.css";

Customizing your styles

Adding dark mode and customizing your styles is part of@liveblocks/react-ui,learn how to do this understyling and customization.

We use cookies to collect data to improve your experience on our site. Read ourPrivacy Policy to learn more.


    [8]ページ先頭

    ©2009-2025 Movatter.jp