mirror of
https://github.com/supabase/supabase.git
synced 2026-05-09 08:18:16 +08:00
## Problem We want to upgrade to react 19. However some libraries aren't compatible with it. Besides, `next-mdx-remote` is now archived and not maintained anymore. ## Solution The [NextJS documentation)[https://nextjs.org/docs/15/app/guides/mdx#remote-mdx] suggest using [`next-mdx-remote-client`](https://github.com/ipikuka/next-mdx-remote-client) which was a fork of `next-mdx-remote`. - [x] migrate `apps/www` from `next-mdx-remote` to `next-mdx-remote-client` - [x] migrate `apps/www` from `next-mdx-remote` to `next-mdx-remote-client` I haven't noticed any change in the pages. When upgrading to react 19, we'll have to use v2 of `next-mdx-remote-client`. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Switched MDX rendering/serialization to a newer client-focused implementation across docs and site for improved compatibility. * **Bug Fixes** * Improved handling of serialization errors so MDX failures render clear fallback messages instead of breaking pages. * **Chores** * Updated local environment template value for the public anonymous key. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
45 lines
8.4 KiB
JSON
45 lines
8.4 KiB
JSON
{
|
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
"name": "realtime-cursor-tanstack",
|
|
"type": "registry:component",
|
|
"title": "Realtime Cursor",
|
|
"description": "Component which renders realtime cursors from other users in a room.",
|
|
"dependencies": [
|
|
"lucide-react",
|
|
"@supabase/ssr@latest",
|
|
"@supabase/supabase-js@latest"
|
|
],
|
|
"registryDependencies": [],
|
|
"files": [
|
|
{
|
|
"path": "registry/default/blocks/realtime-cursor/components/cursor.tsx",
|
|
"content": "import { MousePointer2 } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nexport const Cursor = ({\n className,\n style,\n color,\n name,\n}: {\n className?: string\n style?: React.CSSProperties\n color: string\n name: string\n}) => {\n return (\n <div className={cn('pointer-events-none', className)} style={style}>\n <MousePointer2 color={color} fill={color} size={30} />\n\n <div\n className=\"mt-1 px-2 py-1 rounded-sm text-xs font-bold text-white text-center\"\n style={{ backgroundColor: color }}\n >\n {name}\n </div>\n </div>\n )\n}\n",
|
|
"type": "registry:component"
|
|
},
|
|
{
|
|
"path": "registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx",
|
|
"content": "'use client'\n\nimport { Cursor } from '@/registry/default/blocks/realtime-cursor/components/cursor'\nimport { useRealtimeCursors } from '@/registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors'\n\nconst THROTTLE_MS = 50\n\nexport const RealtimeCursors = ({ roomName, username }: { roomName: string; username: string }) => {\n const { cursors } = useRealtimeCursors({ roomName, username, throttleMs: THROTTLE_MS })\n\n return (\n <div>\n {Object.keys(cursors).map((id) => (\n <Cursor\n key={id}\n className=\"fixed transition-transform ease-in-out z-50\"\n style={{\n transitionDuration: '20ms',\n top: 0,\n left: 0,\n transform: `translate(${cursors[id].position.x}px, ${cursors[id].position.y}px)`,\n }}\n color={cursors[id].color}\n name={cursors[id].user.name}\n />\n ))}\n </div>\n )\n}\n",
|
|
"type": "registry:component"
|
|
},
|
|
{
|
|
"path": "registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts",
|
|
"content": "import { REALTIME_SUBSCRIBE_STATES, RealtimeChannel } from '@supabase/supabase-js'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\n\n/**\n * Throttle a callback to a certain delay, It will only call the callback if the delay has passed, with the arguments\n * from the last call\n */\nconst useThrottleCallback = <Params extends unknown[], Return>(\n callback: (...args: Params) => Return,\n delay: number\n) => {\n const lastCall = useRef(0)\n const timeout = useRef<NodeJS.Timeout | null>(null)\n\n return useCallback(\n (...args: Params) => {\n const now = Date.now()\n const remainingTime = delay - (now - lastCall.current)\n\n if (remainingTime <= 0) {\n if (timeout.current) {\n clearTimeout(timeout.current)\n timeout.current = null\n }\n lastCall.current = now\n callback(...args)\n } else if (!timeout.current) {\n timeout.current = setTimeout(() => {\n lastCall.current = Date.now()\n timeout.current = null\n callback(...args)\n }, remainingTime)\n }\n },\n [callback, delay]\n )\n}\n\nconst supabase = createClient()\n\nconst generateRandomColor = () => `hsl(${Math.floor(Math.random() * 360)}, 100%, 70%)`\n\nconst generateRandomNumber = () => Math.floor(Math.random() * 100)\n\nconst EVENT_NAME = 'realtime-cursor-move'\n\ntype CursorEventPayload = {\n position: {\n x: number\n y: number\n }\n user: {\n id: number\n name: string\n }\n color: string\n timestamp: number\n}\n\nexport const useRealtimeCursors = ({\n roomName,\n username,\n throttleMs,\n}: {\n roomName: string\n username: string\n throttleMs: number\n}) => {\n const [color] = useState(generateRandomColor())\n const [userId] = useState(generateRandomNumber())\n const [cursors, setCursors] = useState<Record<string, CursorEventPayload>>({})\n const cursorPayload = useRef<CursorEventPayload | null>(null)\n\n const channelRef = useRef<RealtimeChannel | null>(null)\n\n const callback = useCallback(\n (event: MouseEvent) => {\n const { clientX, clientY } = event\n\n const payload: CursorEventPayload = {\n position: {\n x: clientX,\n y: clientY,\n },\n user: {\n id: userId,\n name: username,\n },\n color: color,\n timestamp: new Date().getTime(),\n }\n\n cursorPayload.current = payload\n\n channelRef.current?.send({\n type: 'broadcast',\n event: EVENT_NAME,\n payload: payload,\n })\n },\n [color, userId, username]\n )\n\n const handleMouseMove = useThrottleCallback(callback, throttleMs)\n\n useEffect(() => {\n const channel = supabase.channel(roomName)\n\n channel\n .on('presence', { event: 'leave' }, ({ leftPresences }) => {\n leftPresences.forEach(function (element) {\n // Remove cursor when user leaves\n setCursors((prev) => {\n if (prev[element.key]) {\n delete prev[element.key]\n }\n\n return { ...prev }\n })\n })\n })\n .on('presence', { event: 'join' }, () => {\n if (!cursorPayload.current) return\n\n // All cursors broadcast their position when a new cursor joins\n channelRef.current?.send({\n type: 'broadcast',\n event: EVENT_NAME,\n payload: cursorPayload.current,\n })\n })\n .on('broadcast', { event: EVENT_NAME }, (data: { payload: CursorEventPayload }) => {\n const { user } = data.payload\n // Don't render your own cursor\n if (user.id === userId) return\n\n setCursors((prev) => {\n if (prev[userId]) {\n delete prev[userId]\n }\n\n return {\n ...prev,\n [user.id]: data.payload,\n }\n })\n })\n .subscribe(async (status) => {\n if (status === REALTIME_SUBSCRIBE_STATES.SUBSCRIBED) {\n await channel.track({ key: userId })\n channelRef.current = channel\n } else {\n setCursors({})\n channelRef.current = null\n }\n })\n\n return () => {\n channel.unsubscribe()\n channelRef.current = null\n }\n }, [])\n\n useEffect(() => {\n // Add event listener for mousemove\n window.addEventListener('mousemove', handleMouseMove)\n\n // Cleanup on unmount\n return () => {\n window.removeEventListener('mousemove', handleMouseMove)\n }\n }, [handleMouseMove])\n\n return { cursors }\n}\n",
|
|
"type": "registry:hook"
|
|
},
|
|
{
|
|
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
|
|
"content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY!\n )\n}\n",
|
|
"type": "registry:lib"
|
|
},
|
|
{
|
|
"path": "registry/default/clients/tanstack/lib/supabase/server.ts",
|
|
"content": "import { createServerClient } from '@supabase/ssr'\nimport { getCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n return createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_PUBLISHABLE_KEY!,\n {\n cookies: {\n getAll() {\n return Object.entries(getCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n }\n )\n}\n",
|
|
"type": "registry:lib"
|
|
}
|
|
],
|
|
"envVars": {
|
|
"VITE_SUPABASE_URL": "",
|
|
"VITE_SUPABASE_PUBLISHABLE_KEY": ""
|
|
},
|
|
"docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_KEY`."
|
|
} |