{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "collaboration-demo",
  "description": "Real-time collaboration with cursors and selections.",
  "dependencies": [
    "@platejs/yjs",
    "nanoid"
  ],
  "registryDependencies": [
    "https://platejs.org/r/use-mounted.json",
    "https://platejs.org/r/remote-cursor-overlay.json",
    "button",
    "input"
  ],
  "files": [
    {
      "path": "src/registry/examples/collaboration-demo.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { UnifiedProvider } from '@platejs/yjs';\nimport { YjsPlugin } from '@platejs/yjs/react';\nimport { RefreshCw } from 'lucide-react';\nimport { nanoid } from 'nanoid';\nimport {\n  Plate,\n  useEditorRef,\n  usePlateEditor,\n  usePluginOption,\n} from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\nimport { EditorKit } from '@/registry/components/editor/editor-kit';\nimport { useMounted } from '@/registry/hooks/use-mounted';\nimport { Editor, EditorContainer } from '@/registry/ui/editor';\nimport { RemoteCursorOverlay } from '@/registry/ui/remote-cursor-overlay';\n\nconst INITIAL_VALUE = [\n  {\n    children: [{ text: 'This is the initial content loaded into the Y.Doc.' }],\n    type: 'p',\n  },\n];\n\nexport default function CollaborativeEditingDemo(): React.ReactNode {\n  const mounted = useMounted();\n  const { generateNewRoom, roomName, handleRoomChange } =\n    useCollaborationRoom();\n  const { cursorColor, username } = useCollaborationUser();\n\n  const editor = usePlateEditor(\n    {\n      plugins: [\n        ...EditorKit,\n        YjsPlugin.configure({\n          options: {\n            cursors: {\n              data: { color: cursorColor, name: username },\n            },\n            providers: [\n              {\n                options: {\n                  name: roomName,\n                  url: 'ws://localhost:8888',\n                },\n                type: 'hocuspocus',\n              },\n              {\n                options: {\n                  maxConns: 9, // Limit to 10 total participants\n                  roomName,\n                  signaling: [\n                    process.env.NODE_ENV === 'production'\n                      ? // Use public signaling server just for demo purposes\n                        'wss://signaling.yjs.dev'\n                      : 'ws://localhost:4444',\n                  ],\n                },\n                type: 'webrtc',\n              },\n            ],\n          },\n          render: {\n            afterEditable: RemoteCursorOverlay,\n          },\n        }),\n      ],\n      skipInitialization: true,\n    },\n    [roomName]\n  );\n\n  React.useEffect(() => {\n    if (!mounted) return;\n\n    editor.getApi(YjsPlugin).yjs.init({\n      id: roomName,\n      autoSelect: 'end',\n      value: INITIAL_VALUE,\n    });\n\n    return () => {\n      editor.getApi(YjsPlugin).yjs.destroy();\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [editor, mounted]);\n\n  return (\n    <div className=\"flex flex-col\">\n      <div className=\"rounded-md bg-muted p-4 text-muted-foreground text-sm\">\n        <div className=\"flex items-center gap-2\">\n          <div className=\"flex-1\">\n            <label className=\"mb-1 block font-medium text-xs\" htmlFor=\"room-id\">\n              Room ID (share this to collaborate)\n            </label>\n            <div className=\"flex items-center gap-2\">\n              <Input\n                id=\"room-id\"\n                className=\"h-[28px] bg-background px-1.5 py-1\"\n                value={roomName}\n                onChange={handleRoomChange}\n                type=\"text\"\n              />\n              <Button\n                size=\"icon\"\n                variant=\"outline\"\n                onClick={generateNewRoom}\n                title=\"Generate new room\"\n              >\n                <RefreshCw className=\"h-4 w-4\" />\n              </Button>\n            </div>\n          </div>\n        </div>\n        <p className=\"mt-2\">\n          You can{' '}\n          <a\n            className=\"underline underline-offset-4 transition-colors hover:text-primary\"\n            href={typeof window === 'undefined' ? '#' : window.location.href}\n            rel=\"noopener noreferrer\"\n            target=\"_blank\"\n          >\n            open this page in another tab\n          </a>{' '}\n          or share your Room ID with others to test real-time collaboration.\n          Each instance will have a different cursor color for easy\n          identification.\n        </p>\n        <div className=\"mt-2\">\n          <strong>About this demo:</strong>\n          <ul className=\"mt-1 list-inside list-disc\">\n            <li>\n              Share your Room ID with others to collaborate in the same document\n            </li>\n            <li>Limited to 10 concurrent participants per room</li>\n            <li>\n              Using WebRTC with public signaling servers - for demo purposes\n              only\n            </li>\n          </ul>\n        </div>\n      </div>\n\n      <div className=\"flex-1 overflow-hidden border-t\">\n        <Plate editor={editor}>\n          <CollaborativeEditor cursorColor={cursorColor} username={username} />\n        </Plate>\n      </div>\n    </div>\n  );\n}\n\nfunction CollaborativeEditor({\n  cursorColor,\n  username,\n}: {\n  cursorColor: string;\n  username: string;\n}): React.ReactNode {\n  const editor = useEditorRef();\n  const providers = usePluginOption(YjsPlugin, '_providers');\n  const isConnected = usePluginOption(YjsPlugin, '_isConnected');\n\n  const toggleConnection = () => {\n    if (editor.getOptions(YjsPlugin)._isConnected) {\n      return editor.getApi(YjsPlugin).yjs.disconnect();\n    }\n\n    editor.getApi(YjsPlugin).yjs.connect();\n  };\n\n  return (\n    <>\n      <div className=\"bg-muted px-4 py-2 font-medium\">\n        Connected as <span style={{ color: cursorColor }}>{username}</span>\n        <div className=\"mt-1 flex items-center gap-2 text-xs\">\n          {providers.map((provider: UnifiedProvider) => (\n            <span\n              key={provider.type}\n              className={`rounded px-2 py-0.5 ${\n                provider.isConnected\n                  ? 'bg-green-100 text-green-800'\n                  : 'bg-red-100 text-red-800'\n              }`}\n            >\n              {provider.type.charAt(0).toUpperCase() + provider.type.slice(1)}:{' '}\n              {provider.isConnected ? 'Connected' : 'Disconnected'}\n            </span>\n          ))}\n          <Button\n            size=\"sm\"\n            variant=\"outline\"\n            className=\"ml-auto\"\n            onClick={toggleConnection}\n          >\n            {isConnected ? 'Disconnect' : 'Connect'}\n          </Button>\n        </div>\n      </div>\n\n      <EditorContainer variant=\"demo\">\n        <Editor autoFocus />\n      </EditorContainer>\n    </>\n  );\n}\n\n// Hook for managing room state\nfunction useCollaborationRoom() {\n  const [roomName, setRoomName] = React.useState(() => {\n    if (typeof window === 'undefined') return '';\n\n    const storedRoomId = localStorage.getItem('demo-room-id');\n    if (storedRoomId) return storedRoomId;\n\n    const newRoomId = nanoid();\n    localStorage.setItem('demo-room-id', newRoomId);\n    return newRoomId;\n  });\n\n  const handleRoomChange = React.useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const newRoomId = e.target.value;\n      localStorage.setItem('demo-room-id', newRoomId);\n      setRoomName(newRoomId);\n    },\n    []\n  );\n\n  const generateNewRoom = React.useCallback(() => {\n    const newRoomId = nanoid();\n    localStorage.setItem('demo-room-id', newRoomId);\n    setRoomName(newRoomId);\n  }, []);\n\n  return {\n    generateNewRoom,\n    roomName,\n    handleRoomChange,\n  };\n}\n\n// Hook for managing user/cursor state\nfunction useCollaborationUser() {\n  const [username] = React.useState(\n    () => `user-${Math.floor(Math.random() * 1000)}`\n  );\n  const [cursorColor] = React.useState(() => getRandomColor());\n\n  return {\n    cursorColor,\n    username,\n  };\n}\n\nconst getRandomColor = (): string => {\n  const letters = '0123456789ABCDEF';\n  let color = '#';\n  for (let i = 0; i < 6; i++) {\n    color += letters[Math.floor(Math.random() * 16)];\n  }\n  return color;\n};\n",
      "type": "registry:example"
    }
  ],
  "type": "registry:example"
}