Repository: supabase/supabase
Pull request #34544: chore: ui lib / new sidebar mobile hook, and props update, more SSR 
Author: login: MildTomato
Created: 2025-03-30T06:30:19Z | Merged: 2025-03-31T09:16:26Z

## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file.

YES

## What kind of change does this PR introduce?

- uses hook for mobile menu state
- extends Link props for NavigationItem, now component behaves like `<Link />`, uses `{...props}` to spread props.
- fixed some import path formatting
- new component for outer mobile-sheet using use-client, so rest of sidebar can be SSR
- framework routing logic moved to navigation-item so rest of sidebar can be SSR



Diff:
diff --git a/apps/ui-library/app/(app)/layout.tsx b/apps/ui-library/app/(app)/layout.tsx
index 8c50de8327bba..325267198397a 100644
--- a/apps/ui-library/app/(app)/layout.tsx
+++ b/apps/ui-library/app/(app)/layout.tsx
@@ -1,8 +1,5 @@
-import SideNavigation from '@/components/side-navigation'
-import Sidebar from '@/components/sidebar'
+import { Sidebar } from '@/components/sidebar'
 import { SiteFooter } from '@/components/site-footer'
-// import ThemeSettings from '@/components/theme-settings'
-import TopNavigation from '@/components/top-navigation'
 
 interface AppLayoutProps {
   children: React.ReactNode
@@ -11,7 +8,6 @@ interface AppLayoutProps {
 export default function AppLayout({ children }: AppLayoutProps) {
   return (
     <>
-      {/* <TopNavigation /> */}
       {/* main container */}
       <div className="pt-10 md:pt-0">
         {/* main content */}
diff --git a/apps/ui-library/app/Providers.tsx b/apps/ui-library/app/Providers.tsx
index c10426f161d39..97e0ebdbc72b3 100644
--- a/apps/ui-library/app/Providers.tsx
+++ b/apps/ui-library/app/Providers.tsx
@@ -6,6 +6,7 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes'
 import { ThemeProviderProps } from 'next-themes/dist/types'
 
 import { FrameworkProvider } from '@/context/framework-context'
+import { MobileMenuProvider } from '@/context/mobile-menu-context'
 import { TooltipProvider } from 'ui'
 
 export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
@@ -13,9 +14,11 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
     <AuthProvider>
       <JotaiProvider>
         <NextThemesProvider {...props}>
-          <FrameworkProvider>
-            <TooltipProvider delayDuration={0}>{children}</TooltipProvider>
-          </FrameworkProvider>
+          <MobileMenuProvider>
+            <FrameworkProvider>
+              <TooltipProvider delayDuration={0}>{children}</TooltipProvider>
+            </FrameworkProvider>
+          </MobileMenuProvider>
         </NextThemesProvider>
       </JotaiProvider>
     </AuthProvider>
diff --git a/apps/ui-library/app/layout.tsx b/apps/ui-library/app/layout.tsx
index 63ad05e019fef..a03636ea6a5b7 100644
--- a/apps/ui-library/app/layout.tsx
+++ b/apps/ui-library/app/layout.tsx
@@ -1,5 +1,6 @@
-import '@/styles/globals.css'
 import type { Metadata } from 'next'
+
+import '@/styles/globals.css'
 import { Inter } from 'next/font/google'
 import { ThemeProvider } from './Providers'
 import { SonnerToaster } from './SonnerToast'
@@ -26,7 +27,6 @@ export default async function Layout({ children }: RootLayoutProps) {
           enableSystem
         >
           {children}
-
           <SonnerToaster />
         </ThemeProvider>
       </body>
diff --git a/apps/ui-library/components/framework-selector.tsx b/apps/ui-library/components/framework-selector.tsx
index 6b32e1fd2304b..62a78fe2ae3c8 100644
--- a/apps/ui-library/components/framework-selector.tsx
+++ b/apps/ui-library/components/framework-selector.tsx
@@ -33,10 +33,18 @@ export function FrameworkSelector() {
   }
 
   // Get the supported frameworks for this component
-  const supportedFrameworks = componentPages[docTitle]?.supportedFrameworks || []
+  const componentItem = componentPages.items.find(
+    (item) => item.href?.split('/').pop() === docTitle.toLowerCase()
+  )
+  // Use the supportedFrameworks property if it exists, otherwise default to all frameworks
+  const supportedFrameworks = componentItem?.supportedFrameworks
+
+  if (!supportedFrameworks) {
+    return null
+  }
 
   // Don't show selector if component only supports one framework
-  if (supportedFrameworks.length <= 1) {
+  if (supportedFrameworks && supportedFrameworks.length <= 1) {
     return null
   }
 
@@ -54,10 +62,11 @@ export function FrameworkSelector() {
     setPreferredFramework(value as keyof typeof frameworkTitles)
   }
 
-  const options = supportedFrameworks.map((f) => ({
-    label: frameworkTitles[f],
-    value: f,
-  }))
+  const options =
+    supportedFrameworks?.map((f) => ({
+      label: frameworkTitles[f],
+      value: f,
+    })) || []
 
   return (
     <Select_Shadcn_ value={selectedFramework} onValueChange={onSelect}>
diff --git a/apps/ui-library/components/mobile-menu-sheet.tsx b/apps/ui-library/components/mobile-menu-sheet.tsx
new file mode 100644
index 0000000000000..4e7c3cc019076
--- /dev/null
+++ b/apps/ui-library/components/mobile-menu-sheet.tsx
@@ -0,0 +1,18 @@
+'use client'
+
+import { useMobileMenu } from '@/hooks/use-mobile-menu'
+import { Sheet } from 'ui'
+
+interface MobileMenuSheetProps {
+  children: React.ReactNode
+}
+
+export function MobileMenuSheet({ children }: MobileMenuSheetProps) {
+  const { open, setOpen } = useMobileMenu()
+
+  return (
+    <Sheet open={open} onOpenChange={setOpen}>
+      {children}
+    </Sheet>
+  )
+}
diff --git a/apps/ui-library/components/side-navigation-item.tsx b/apps/ui-library/components/side-navigation-item.tsx
index 9c69fee1240e5..199cef529b017 100644
--- a/apps/ui-library/components/side-navigation-item.tsx
+++ b/apps/ui-library/components/side-navigation-item.tsx
@@ -1,40 +1,91 @@
 'use client'
 
-import { SidebarNavItem } from '@/types/nav'
-import Link from 'next/link'
+import Link, { LinkProps } from 'next/link'
 import { usePathname } from 'next/navigation'
 import React from 'react'
+
+import { useFramework } from '@/context/framework-context'
+import { useMobileMenu } from '@/hooks/use-mobile-menu'
+import { SidebarNavItem } from '@/types/nav'
 import { cn } from 'ui'
 
-interface NavigationItemProps {
+// We extend:
+// 1. LinkProps - for Next.js Link component props (prefetch, etc)
+// 2. AnchorHTMLAttributes - for standard HTML anchor props (className, etc)
+// We omit href from both since we compute it internally from item.href
+interface NavigationItemProps
+  extends Omit<LinkProps, 'href'>,
+    Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
   item: SidebarNavItem
-  onClick?: () => void
+  onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void
 }
 
-const NavigationItem: React.FC<NavigationItemProps> = React.memo(({ item, onClick }) => {
+const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props }) => {
+  const { setOpen } = useMobileMenu()
+  const { framework } = useFramework()
   const pathname = usePathname()
+
   const pathParts = pathname.split('/')
   const slug = pathParts[pathParts.length - 1]
-  const framework = pathParts[pathParts.length - 2]
 
-  // Build URL with framework param if provided
+  // Helper function to check if a framework is supported for a navigation item
+  const isFrameworkSupported = (item: SidebarNavItem, framework: string) => {
+    const supportedFrameworks = item.supportedFrameworks || []
+    const hasFrameworkRestrictions = supportedFrameworks.length > 0
+
+    // An item supports the current framework if either:
+    // 1. It has no framework restrictions (supports all frameworks)
+    // 2. The current framework is in its list of supported frameworks
+    return !hasFrameworkRestrictions || supportedFrameworks.includes(framework as any)
+  }
+
+  // Build URL with priority:
+  // 1. item.href if available (replacing any existing framework with current one)
+  // 2. Computed from current path considering framework support
   let href = item.href
 
-  // Handle component items with slug and framework
-  if (!href && slug) {
-    if (framework) {
+  // Only modify URLs for items that explicitly support frameworks
+  if (item.supportedFrameworks) {
+    if (href && href.startsWith('/docs/')) {
+      const hrefParts = href.split('/')
+      if (hrefParts.length >= 3) {
+        if (framework && isFrameworkSupported(item, framework)) {
+          if (hrefParts.length >= 4) {
+            href = `/docs/${framework}/${hrefParts[3]}`
+          }
+        }
+      }
+    }
+  }
+  // Handle component items with slug but no href
+  else if (!href && slug) {
+    // Build the URL using the current framework if it's supported
+    if (framework && isFrameworkSupported(item, framework)) {
       href = `/docs/${framework}/${slug}`
     } else {
+      // Fall back to framework-agnostic URL when framework not supported
       href = `/docs/${slug}`
     }
   }
 
+  // Determine if this link represents the current page
   const isActive = pathname === href
 
+  const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
+    // Close the mobile menu when navigating
+    setOpen(false)
+
+    // Call the onClick prop if it exists
+    if (onClick) {
+      onClick(e)
+    }
+  }
+
   return (
     <Link
       href={href || '#'}
-      onClick={onClick}
+      {...props}
+      onClick={handleClick}
       className={cn(
         'relative',
         'flex',
@@ -44,9 +95,11 @@ const NavigationItem: React.FC<NavigationItemProps> = React.memo(({ item, onClic
         'text-foreground-lighter px-6',
         !isActive && 'hover:bg-surface-100 hover:text-foreground',
         isActive && 'bg-surface-200 text-foreground',
-        'transition-all'
+        'transition-all',
+        props.className
       )}
     >
+      {/* Active indicator bar */}
       <div
         className={cn(
           'transition',
@@ -57,7 +110,7 @@ const NavigationItem: React.FC<NavigationItemProps> = React.memo(({ item, onClic
       {item.title}
     </Link>
   )
-})
+}
 
 NavigationItem.displayName = 'NavigationItem'
 
diff --git a/apps/ui-library/components/side-navigation.tsx b/apps/ui-library/components/side-navigation.tsx
index 88386df0043ae..614864cd7b153 100644
--- a/apps/ui-library/components/side-navigation.tsx
+++ b/apps/ui-library/components/side-navigation.tsx
@@ -1,45 +1,11 @@
-'use client'
+import Link from 'next/link'
 
 import NavigationItem from '@/components/side-navigation-item'
 import { aiEditorsRules, componentPages, gettingStarted } from '@/config/docs'
-import { useFramework } from '@/context/framework-context'
-import Link from 'next/link'
 import { CommandMenu } from './command-menu'
 import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
 
-interface SideNavigationProps {
-  setMobileMenuOpen?: (open: boolean) => void
-  hideLogo?: boolean
-}
-
-function SideNavigation({ setMobileMenuOpen, hideLogo = false }: SideNavigationProps) {
-  const { framework: preferredFramework } = useFramework()
-
-  // Create a function to build URLs
-  const buildUrl = (slug: string, framework?: string) => {
-    if (!framework) return `/docs/${slug}`
-    return `/docs/${framework}/${slug}`
-  }
-
-  // Get all component pages
-  const componentItems = Object.entries(componentPages).map(([slug, component]) => {
-    let frameworkToUse = undefined
-
-    // Add framework param only if needed
-    if (preferredFramework && component.supportedFrameworks.includes(preferredFramework)) {
-      frameworkToUse = preferredFramework
-    } else if (component.supportedFrameworks.length > 0) {
-      frameworkToUse = component.supportedFrameworks[0]
-    }
-
-    return {
-      title: component.title,
-      href: buildUrl(slug, frameworkToUse),
-      items: [],
-      commandItemLabel: component.commandItemLabel,
-    }
-  })
-
+function SideNavigation() {
   return (
     <nav className="flex flex-col h-full min-w-[220px]">
       <div className="p-6">
@@ -105,11 +71,7 @@ function SideNavigation({ setMobileMenuOpen, hideLogo = false }: SideNavigationP
           {gettingStarted.title}
         </div>
         {gettingStarted.items.map((item, i) => (
-          <NavigationItem
-            item={item}
-            key={`${item.href}-${i}`}
-            onClick={() => setMobileMenuOpen?.(false)}
-          />
+          <NavigationItem item={item} key={`${item.href}-${i}`} />
         ))}
       </div>
       <div className="pb-6">
@@ -118,11 +80,10 @@ function SideNavigation({ setMobileMenuOpen, hideLogo = false }: SideNavigationP
         </div>
         <div className="space-y-0.5">
           {/* Render items based on component definitions */}
-          {componentItems.map((item, i) => (
+          {componentPages.items.map((component, i) => (
             <NavigationItem
-              item={item}
-              key={`${item.href?.toString() || item.title}-${i}`}
-              onClick={() => setMobileMenuOpen?.(false)}
+              item={component}
+              key={`${component.href?.toString() || component.title}-${i}`}
             />
           ))}
         </div>
@@ -133,11 +94,7 @@ function SideNavigation({ setMobileMenuOpen, hideLogo = false }: SideNavigationP
           {aiEditorsRules.title}
         </div>
         {aiEditorsRules.items.map((item, i) => (
-          <NavigationItem
-            item={item}
-            key={`${item.href}-${i}`}
-            onClick={() => setMobileMenuOpen?.(false)}
-          />
+          <NavigationItem item={item} key={`${item.href}-${i}`} />
         ))}
       </div>
     </nav>
diff --git a/apps/ui-library/components/sidebar.tsx b/apps/ui-library/components/sidebar.tsx
index da295c62aea28..eeadd02aec17d 100644
--- a/apps/ui-library/components/sidebar.tsx
+++ b/apps/ui-library/components/sidebar.tsx
@@ -1,33 +1,30 @@
-'use client'
-import SideNavigation from '@/components/side-navigation'
-import { Button, cn, ScrollArea, Sheet, SheetContent, SheetTrigger } from 'ui'
-import { useState } from 'react'
 import { Menu } from 'lucide-react'
+
+import SideNavigation from '@/components/side-navigation'
+import { Button, ScrollArea, SheetContent, SheetTrigger } from 'ui'
+import { MobileMenuSheet } from './mobile-menu-sheet'
 import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
-import Link from 'next/link'
 
-export default function Sidebar() {
-  const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
+export function Sidebar() {
   return (
     <>
       <div className="md:hidden fixed top-0 left-0 right-0 z-50 bg-background justify-between flex items-center px-8 py-3 border-b">
-        <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
+        <MobileMenuSheet>
           <SheetTrigger asChild>
             <Button type="outline" icon={<Menu />} />
           </SheetTrigger>
           <SheetContent side="left" className="p-0 w-80" showClose={false}>
             <ScrollArea className="h-full">
-              <SideNavigation setMobileMenuOpen={setMobileMenuOpen} hideLogo={true} />
+              <SideNavigation />
             </ScrollArea>
           </SheetContent>
-        </Sheet>
-
+        </MobileMenuSheet>
         <ThemeSwitcherDropdown />
       </div>
 
       <aside className="fixed z-30 top-0 hidden h-screen w-full shrink-0 md:sticky md:block bg-200 border-r border-muted/50">
         <ScrollArea className="h-full">
-          <SideNavigation setMobileMenuOpen={setMobileMenuOpen} />
+          <SideNavigation />
         </ScrollArea>
       </aside>
     </>
diff --git a/apps/ui-library/config/docs.ts b/apps/ui-library/config/docs.ts
index 94a6ecc4a1508..f436e287425e6 100644
--- a/apps/ui-library/config/docs.ts
+++ b/apps/ui-library/config/docs.ts
@@ -1,9 +1,4 @@
-import { MainNavItem, SidebarNavGroup, SidebarNavItem } from 'types/nav'
-
-interface DocsConfig {
-  mainNav?: MainNavItem[]
-  sidebarNav: SidebarNavItem[]
-}
+import { SidebarNavGroup } from 'types/nav'
 
 export const gettingStarted: SidebarNavGroup = {
   title: 'Getting Started',
@@ -41,66 +36,60 @@ export const aiEditorsRules: SidebarNavGroup = {
   ],
 }
 
-// Framework titles for display
-export const frameworkTitles: Record<string, string> = {
-  nextjs: 'Next.js',
-  'react-router': 'React Router',
-  tanstack: 'TanStack Start',
-  react: 'React SPA',
-}
-
 // Component definitions with supported frameworks
-export const componentPages: Record<
-  string,
-  {
-    title: string
-    supportedFrameworks: string[]
-    commandItemLabel: string
-    href: string
-  }
-> = {
-  client: {
-    title: 'Client',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Supabase Client',
-    href: '/docs/nextjs/client',
-  },
-  'password-based-auth': {
-    title: 'Password-Based Auth',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Password-Based Auth',
-    href: '/docs/nextjs/password-based-auth',
-  },
-  dropzone: {
-    title: 'Dropzone',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Dropzone (File Upload)',
-    href: '/docs/nextjs/dropzone',
-  },
-  'realtime-cursor': {
-    title: 'Realtime Cursor',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Realtime Cursor',
-    href: '/docs/nextjs/realtime-cursor',
-  },
-  'current-user-avatar': {
-    title: 'Current User Avatar',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Current User Avatar',
-    href: '/docs/nextjs/current-user-avatar',
-  },
-  'realtime-avatar-stack': {
-    title: 'Realtime Avatar Stack',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Realtime Avatar Stack',
-    href: '/docs/nextjs/realtime-avatar-stack',
-  },
-  'realtime-chat': {
-    title: 'Realtime Chat',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Realtime Chat',
-    href: '/docs/nextjs/realtime-chat',
-  },
+export const componentPages: SidebarNavGroup = {
+  title: 'Components',
+  items: [
+    {
+      title: 'Client',
+      supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
+      href: '/docs/nextjs/client',
+      items: [],
+      commandItemLabel: 'Supabase Client',
+    },
+    {
+      title: 'Password-Based Auth',
+      supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
+      href: '/docs/nextjs/password-based-auth',
+      items: [],
+      commandItemLabel: 'Password-Based Auth',
+    },
+    {
+      title: 'Dropzone',
+      supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
+      href: '/docs/nextjs/dropzone',
+      items: [],
+      commandItemLabel: 'Dropzone (File Upload)',
+    },
+    {
+      title: 'Realtime Cursor',
+      supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
+      href: '/docs/nextjs/realtime-cursor',
+      items: [],
+      commandItemLabel: 'Realtime Cursor',
+    },
+    {
+      title: 'Current User Avatar',
+      supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
+      href: '/docs/nextjs/current-user-avatar',
+      items: [],
+      commandItemLabel: 'Current User Avatar',
+    },
+    {
+      title: 'Realtime Avatar Stack',
+      supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
+      href: '/docs/nextjs/realtime-avatar-stack',
+      items: [],
+      commandItemLabel: 'Realtime Avatar Stack',
+    },
+    {
+      title: 'Realtime Chat',
+      supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
+      href: '/docs/nextjs/realtime-chat',
+      items: [],
+      commandItemLabel: 'Realtime Chat',
+    },
+  ],
 }
 
 export const COMMAND_ITEMS = [
@@ -117,3 +106,11 @@ export const COMMAND_ITEMS = [
     href: component.href,
   })),
 ]
+
+// Framework titles for display
+export const frameworkTitles: Record<string, string> = {
+  nextjs: 'Next.js',
+  'react-router': 'React Router',
+  tanstack: 'TanStack Start',
+  react: 'React SPA',
+}
diff --git a/apps/ui-library/context/mobile-menu-context.tsx b/apps/ui-library/context/mobile-menu-context.tsx
new file mode 100644
index 0000000000000..f584ee3f80b5a
--- /dev/null
+++ b/apps/ui-library/context/mobile-menu-context.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+import React, { ReactNode, useState, useCallback } from 'react'
+import { MobileMenuContext, MobileMenuContextType } from '@/hooks/use-mobile-menu'
+
+interface MobileMenuProviderProps {
+  children: ReactNode
+}
+
+/**
+ * Provider component for mobile menu state
+ * Wraps children with context that provides mobile menu state and controls
+ */
+export function MobileMenuProvider({ children }: MobileMenuProviderProps) {
+  const [open, setOpen] = useState(false)
+
+  // Use useCallback for stable function references
+  const handleSetOpen = useCallback((value: boolean) => {
+    setOpen(value)
+  }, [])
+
+  const toggle = useCallback(() => {
+    setOpen((prev) => {
+      return !prev
+    })
+  }, [])
+
+  // Memoize the context value to prevent unnecessary re-renders
+  const value: MobileMenuContextType = {
+    open,
+    setOpen: handleSetOpen,
+    toggle,
+  }
+
+  return <MobileMenuContext.Provider value={value}>{children}</MobileMenuContext.Provider>
+}
diff --git a/apps/ui-library/hooks/use-mobile-menu.ts b/apps/ui-library/hooks/use-mobile-menu.ts
new file mode 100644
index 0000000000000..1126e372f9e27
--- /dev/null
+++ b/apps/ui-library/hooks/use-mobile-menu.ts
@@ -0,0 +1,35 @@
+'use client'
+
+import { createContext, useContext } from 'react'
+
+/**
+ * Context type for mobile menu state and controls
+ */
+export interface MobileMenuContextType {
+  /** Current state of the mobile menu */
+  open: boolean
+  /** Function to set the mobile menu state */
+  setOpen: (open: boolean) => void
+  /** Function to toggle the mobile menu state */
+  toggle: () => void
+}
+
+// Create context with null as default value to indicate it's not provided
+export const MobileMenuContext = createContext<MobileMenuContextType | null>(null)
+
+/**
+ * Hook to access the mobile menu state and controls
+ * Must be used within a MobileMenuProvider
+ */
+export function useMobileMenu(): MobileMenuContextType {
+  const context = useContext(MobileMenuContext)
+
+  if (context === null) {
+    throw new Error(
+      'useMobileMenu must be used within a MobileMenuProvider. ' +
+        'Ensure your component is wrapped with MobileMenuProvider.'
+    )
+  }
+
+  return context
+}
diff --git a/apps/ui-library/types/nav.ts b/apps/ui-library/types/nav.ts
index 85c42a7c151a5..23f25fb613bca 100644
--- a/apps/ui-library/types/nav.ts
+++ b/apps/ui-library/types/nav.ts
@@ -1,5 +1,4 @@
-// import { Icons } from '@/components/icons'
-
+type supportedFrameworks = 'nextjs' | 'react-router' | 'tanstack' | 'react'
 export interface NavItem {
   title: string
   href?: string
@@ -7,6 +6,7 @@ export interface NavItem {
   external?: boolean
   icon?: any // to do: clean up later | keyof typeof Icons
   label?: string
+  supportedFrameworks?: supportedFrameworks[]
 }
 
 export interface NavItemWithChildren extends NavItem {


Reviews:
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:12:53Z
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:13:08Z
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:13:39Z
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:13:46Z
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:13:58Z
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:14:24Z
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:14:43Z
user: login: MildTomato
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T06:15:31Z
user: login: ivasilov
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-31T08:51:41Z

Review comments:
diff_hunk: @@ -41,66 +36,60 @@ export const aiEditorsRules: SidebarNavGroup = {
   ],
 }
 
-// Framework titles for display
-export const frameworkTitles: Record<string, string> = {
-  nextjs: 'Next.js',
-  'react-router': 'React Router',
-  tanstack: 'TanStack Start',
-  react: 'React SPA',
-}
-
 // Component definitions with supported frameworks
-export const componentPages: Record<
-  string,
-  {
-    title: string
-    supportedFrameworks: string[]
-    commandItemLabel: string
-    href: string
-  }
-> = {
-  client: {
-    title: 'Client',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Supabase Client',
-    href: '/docs/nextjs/client',
-  },
-  'password-based-auth': {
-    title: 'Password-Based Auth',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Password-Based Auth',
-    href: '/docs/nextjs/password-based-auth',
-  },
-  dropzone: {
-    title: 'Dropzone',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Dropzone (File Upload)',
-    href: '/docs/nextjs/dropzone',
-  },
-  'realtime-cursor': {
-    title: 'Realtime Cursor',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Realtime Cursor',
-    href: '/docs/nextjs/realtime-cursor',
-  },
-  'current-user-avatar': {
-    title: 'Current User Avatar',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Current User Avatar',
-    href: '/docs/nextjs/current-user-avatar',
-  },
-  'realtime-avatar-stack': {
-    title: 'Realtime Avatar Stack',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Realtime Avatar Stack',
-    href: '/docs/nextjs/realtime-avatar-stack',
-  },
-  'realtime-chat': {
-    title: 'Realtime Chat',
-    supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
-    commandItemLabel: 'Realtime Chat',
-    href: '/docs/nextjs/realtime-chat',
-  },
+export const componentPages: SidebarNavGroup = {
path: apps/ui-library/config/docs.ts
user: login: MildTomato
body: changed structure to match others
created_at: 2025-03-31T06:12:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,35 @@
+'use client'
+
+import { createContext, useContext } from 'react'
+
+/**
+ * Context type for mobile menu state and controls
+ */
+export interface MobileMenuContextType {
+  /** Current state of the mobile menu */
+  open: boolean
+  /** Function to set the mobile menu state */
+  setOpen: (open: boolean) => void
+  /** Function to toggle the mobile menu state */
+  toggle: () => void
+}
+
+// Create context with null as default value to indicate it's not provided
+export const MobileMenuContext = createContext<MobileMenuContextType | null>(null)
path: apps/ui-library/hooks/use-mobile-menu.ts
user: login: MildTomato
body: to handle mobile menu state
created_at: 2025-03-31T06:13:08Z
author_association: MEMBER
diff_hunk: @@ -1,45 +1,11 @@
-'use client'
+import Link from 'next/link'
 
 import NavigationItem from '@/components/side-navigation-item'
 import { aiEditorsRules, componentPages, gettingStarted } from '@/config/docs'
-import { useFramework } from '@/context/framework-context'
-import Link from 'next/link'
 import { CommandMenu } from './command-menu'
 import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
 
-interface SideNavigationProps {
-  setMobileMenuOpen?: (open: boolean) => void
-  hideLogo?: boolean
-}
-
-function SideNavigation({ setMobileMenuOpen, hideLogo = false }: SideNavigationProps) {
-  const { framework: preferredFramework } = useFramework()
-
-  // Create a function to build URLs
-  const buildUrl = (slug: string, framework?: string) => {
path: apps/ui-library/components/side-navigation.tsx
user: login: MildTomato
body: all this logic moved to side-navigation-item.tsx
created_at: 2025-03-31T06:13:38Z
author_association: MEMBER
diff_hunk: @@ -1,45 +1,11 @@
-'use client'
path: apps/ui-library/components/side-navigation.tsx
user: login: MildTomato
body: now its SSR
created_at: 2025-03-31T06:13:45Z
author_association: MEMBER
diff_hunk: @@ -1,33 +1,30 @@
-'use client'
path: apps/ui-library/components/sidebar.tsx
user: login: MildTomato
body: now SSR
created_at: 2025-03-31T06:13:57Z
author_association: MEMBER
diff_hunk: @@ -1,33 +1,30 @@
-'use client'
-import SideNavigation from '@/components/side-navigation'
-import { Button, cn, ScrollArea, Sheet, SheetContent, SheetTrigger } from 'ui'
-import { useState } from 'react'
 import { Menu } from 'lucide-react'
+
+import SideNavigation from '@/components/side-navigation'
+import { Button, ScrollArea, SheetContent, SheetTrigger } from 'ui'
+import { MobileMenuSheet } from './mobile-menu-sheet'
 import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
-import Link from 'next/link'
 
-export default function Sidebar() {
-  const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
+export function Sidebar() {
   return (
     <>
       <div className="md:hidden fixed top-0 left-0 right-0 z-50 bg-background justify-between flex items-center px-8 py-3 border-b">
-        <Sheet open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
+        <MobileMenuSheet>
path: apps/ui-library/components/sidebar.tsx
user: login: MildTomato
body: outer sheet moved to its own component, using "use client"
created_at: 2025-03-31T06:14:23Z
author_association: MEMBER
diff_hunk: @@ -1,12 +1,12 @@
-// import { Icons } from '@/components/icons'
-
+type supportedFrameworks = 'nextjs' | 'react-router' | 'tanstack' | 'react'
 export interface NavItem {
   title: string
   href?: string
   disabled?: boolean
   external?: boolean
   icon?: any // to do: clean up later | keyof typeof Icons
   label?: string
+  supportedFrameworks?: supportedFrameworks[]
path: apps/ui-library/types/nav.ts
user: login: MildTomato
body: all NavItems can support framework list
created_at: 2025-03-31T06:14:43Z
author_association: MEMBER
diff_hunk: @@ -33,10 +33,18 @@ export function FrameworkSelector() {
   }
 
   // Get the supported frameworks for this component
-  const supportedFrameworks = componentPages[docTitle]?.supportedFrameworks || []
+  const componentItem = componentPages.items.find(
path: apps/ui-library/components/framework-selector.tsx
user: login: MildTomato
body: updated to use standardized menu items format in docs.ts
created_at: 2025-03-31T06:15:31Z
author_association: MEMBER

PR comments:
user: login: vercel[bot]
created_at: 2025-03-30T06:30:25Z
body: [vc]: #oJtv3AvaVXx+qgkSj8i2bNX7qh5s0nu4o7w33lBWlQE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ6b25lLXd3dy1kb3QtY29tIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvd3d3IiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2Uvem9uZS13d3ctZG90LWNvbS9IOWJHNTJjdmNvWlplVEJSSGg4dWlEc1N1bnh0IiwicHJldmlld1VybCI6IiIsIm5leHRDb21taXRTdGF0dXMiOiJTS0lQUEVEIn0seyJuYW1lIjoiZG9jcyIsInJvb3REaXJlY3RvcnkiOiJhcHBzL2RvY3MiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn0sImluc3BlY3RvclVybCI6Imh0dHBzOi8vdmVyY2VsLmNvbS9zdXBhYmFzZS9kb2NzLzI1RU55cFRqd05BVUJCa3JQZWRvWG9uVUdwa1YiLCJwcmV2aWV3VXJsIjoiIiwibmV4dENvbW1pdFN0YXR1cyI6IlNLSVBQRUQifSx7Im5hbWUiOiJzdHVkaW8tc2VsZi1ob3N0ZWQiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9zdHVkaW8iLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2Uvc3R1ZGlvLXNlbGYtaG9zdGVkL0RiM25YZjd2U1JoaHJlRXdxVkJvU25KcFhFRG0iLCJwcmV2aWV3VXJsIjoic3R1ZGlvLXNlbGYtaG9zdGVkLWdpdC1jaG9yZS1xdWljay1jaG9yZXMtdWktbGliLXN1cGFiYXNlLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoic3R1ZGlvLXNlbGYtaG9zdGVkLWdpdC1jaG9yZS1xdWljay1jaG9yZXMtdWktbGliLXN1cGFiYXNlLnZlcmNlbC5hcHAifX0seyJuYW1lIjoic3R1ZGlvIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvc3R1ZGlvIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3N1cGFiYXNlL3N0dWRpby9HZzVGdm93WFlraUpuNnY2TWRpNVBYeXBHcU5KIiwicHJldmlld1VybCI6InN0dWRpby1naXQtY2hvcmUtcXVpY2stY2hvcmVzLXVpLWxpYi1zdXBhYmFzZS52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IklHTk9SRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiIn19LHsibmFtZSI6InN0dWRpby1zdGFnaW5nIiwicm9vdERpcmVjdG9yeSI6ImFwcHMvc3R1ZGlvIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2Uvc3R1ZGlvLXN0YWdpbmcvOE00OFNnYW84dEFka1ViZFNldmplTW9EN05tbiIsInByZXZpZXdVcmwiOiIiLCJuZXh0Q29tbWl0U3RhdHVzIjoiU0tJUFBFRCJ9LHsibmFtZSI6ImRlc2lnbi1zeXN0ZW0iLCJyb290RGlyZWN0b3J5IjoiYXBwcy9kZXNpZ24tc3lzdGVtIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2UvZGVzaWduLXN5c3RlbS83cEc3VHdlWmpmOGtYR3VSZXdrNXdMOUJjWHhXIiwicHJldmlld1VybCI6IiIsIm5leHRDb21taXRTdGF0dXMiOiJTS0lQUEVEIn0seyJuYW1lIjoidWktbGlicmFyeSIsInJvb3REaXJlY3RvcnkiOiJhcHBzL3VpLWxpYnJhcnkiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vc3VwYWJhc2UvdWktbGlicmFyeS8zWWtqdkJFM0ZWU2c0VWtjeVB2U0xGQjJ5RUJHIiwicHJldmlld1VybCI6InVpLWxpYnJhcnktZ2l0LWNob3JlLXF1aWNrLWNob3Jlcy11aS1saWItc3VwYWJhc2UudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiJ1aS1saWJyYXJ5LWdpdC1jaG9yZS1xdWljay1jaG9yZXMtdWktbGliLXN1cGFiYXNlLnZlcmNlbC5hcHAifX1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **studio-self-hosted** | ✅ Ready ([Inspect](https://vercel.com/supabase/studio-self-hosted/Db3nXf7vSRhhreEwqVBoSnJpXEDm)) | [Visit Preview](https://studio-self-hosted-git-chore-quick-chores-ui-lib-supabase.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/studio-self-hosted-git-chore-quick-chores-ui-lib-supabase.vercel.app?via=pr-comment-feedback-link) | Mar 31, 2025 8:37am |
| **ui-library** | ✅ Ready ([Inspect](https://vercel.com/supabase/ui-library/3YkjvBE3FVSg4UkcyPvSLFB2yEBG)) | [Visit Preview](https://ui-library-git-chore-quick-chores-ui-lib-supabase.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/ui-library-git-chore-quick-chores-ui-lib-supabase.vercel.app?via=pr-comment-feedback-link) | Mar 31, 2025 8:37am |

<details><summary>5 Skipped Deployments</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **studio** | ⬜️ Ignored ([Inspect](https://vercel.com/supabase/studio/Gg5FvowXYkiJn6v6Mdi5PXypGqNJ)) | [Visit Preview](https://studio-git-chore-quick-chores-ui-lib-supabase.vercel.app) |  | Mar 31, 2025 8:37am |
| **design-system** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/design-system/7pG7TweZjf8kXGuRewk5wL9BcXxW)) |  |  | Mar 31, 2025 8:37am |
| **docs** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/docs/25ENypTjwNAUBBkrPedoXonUGpkV)) |  |  | Mar 31, 2025 8:37am |
| **studio-staging** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/studio-staging/8M48Sgao8tAdkUbdSevjeMoD7Nmn)) |  |  | Mar 31, 2025 8:37am |
| **zone-www-dot-com** | ⬜️ Skipped ([Inspect](https://vercel.com/supabase/zone-www-dot-com/H9bG52cvcoZZeTBRHh8uiDsSunxt)) |  |  | Mar 31, 2025 8:37am |
</details>








author_association: NONE
user: login: supabase[bot]
created_at: 2025-03-30T06:30:25Z
body: [supa]:xguihxuzqibwxjnimxev
This pull request has been ignored for the connected project `xguihxuzqibwxjnimxev` because there are no changes detected in `supabase` directory. You can change this behaviour in [Project Integrations Settings ↗︎](https://supabase.com/dashboard/project/xguihxuzqibwxjnimxev/settings/integrations).

<hr/>

Preview Branches by Supabase.
Learn more about [Supabase Branching ↗︎](https://supabase.com/docs/guides/platform/branching).
author_association: NONE