mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 11:55:05 +03:00
Project sidebars now expose enabled navigation on fixed-width pages, and the mobile project menu has an accessible dialog name. Fixed sidebar sizing and editor resize controls remain intact. **Changed:** - Remove the disabled state from the shared sidebar panel while retaining 256px fixed sizing, the disabled resize handle, and existing editor resize limits. - Add a screen-reader-only Project menu title inside the mobile project sheet without changing other sheets or their titles. **Added:** - Tests using the actual resizable wrappers for enabled navigation and handle semantics, and the actual mobile sheet for its accessible name through menu navigation. ## To test - Navigate through Database and Settings sidebar links with ordinary clicks and Tab/Enter. Enabled links should have no disabled ancestor. - Confirm fixed sidebars remain 256px wide, including an ordinary drag of their disabled handle. - In SQL Editor, verify keyboard resizing stays within 256–512px and collapse/expand still works; restore the original width and visibility without editing or executing anything. - Open the mobile project menu. Confirm its dialog name is Project menu, navigate through Tables, and close it without a missing-title warning. Restore the viewport. Validation: real regressions reproduce disabled navigation inheritance and the unnamed mobile dialog before their fixes. All 26 focused layout/menu/utility tests and source checks passed. Initial full browser checks passed normal mouse/keyboard navigation, fixed-handle drag resistance, editor resize limits, collapse/expand, and mobile navigation, with viewport/state restored and no backend writes. Both Next and TanStack production rebuilds passed. The focused mobile retest passed the linked Project menu title through sections, closing, navigation, and reopening with no new missing-title warnings, followed by desktop sidebar and editor controls; viewport, width, and visibility were restored. Unrelated development React mount/ref warnings remain separately recorded; native Next local browser and catalog data were not verified. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Project sidebar links remain available when sidebar resizing is turned off; the resize handle stays unavailable in that setting. * The mobile project menu retains the “Project menu” name as you navigate between sections. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
211 lines
7.3 KiB
TypeScript
211 lines
7.3 KiB
TypeScript
'use client'
|
|
|
|
import { useFlag, useParams } from 'common'
|
|
import { Home } from 'icons'
|
|
import { ChevronLeft } from 'lucide-react'
|
|
import { useRouter } from 'next/router'
|
|
import React, { useMemo } from 'react'
|
|
import { Button, cn, Separator, SheetTitle, SidebarGroup, SidebarMenu } from 'ui'
|
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import { resolveSectionDisplay } from './MobileMenuContent.utils'
|
|
import { getProductMenuComponent } from './mobileProductMenuRegistry'
|
|
import { TopLevelRouteItem } from './TopLevelRouteItem'
|
|
import { routeHasSubmenu, useMobileMenuNavigation } from './useMobileMenuNavigation'
|
|
import { ICON_SIZE, ICON_STROKE_WIDTH } from '@/components/interfaces/Sidebar'
|
|
import {
|
|
generateProductRoutes,
|
|
generateSettingsRoutes,
|
|
useGenerateOtherRoutes,
|
|
useGenerateToolRoutes,
|
|
} from '@/components/layouts/Navigation/NavigationBar/NavigationBar.utils'
|
|
import { ProductMenuBarHeader } from '@/components/layouts/Navigation/ProductMenuBar'
|
|
import type { Route } from '@/components/ui/ui.types'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { getPathnameWithoutQuery, getPathSegment } from '@/lib/pathname.utils'
|
|
|
|
export interface MobileMenuContentProps {
|
|
currentProductMenu: React.ReactNode
|
|
currentProductMenuHeader?: React.ReactNode
|
|
currentProduct: string
|
|
currentSectionKey: string | null
|
|
onCloseSheet?: () => void
|
|
}
|
|
|
|
export function MobileMenuContent({
|
|
currentProductMenu,
|
|
currentProductMenuHeader,
|
|
currentProduct,
|
|
currentSectionKey,
|
|
onCloseSheet,
|
|
}: MobileMenuContentProps) {
|
|
const router = useRouter()
|
|
const { ref } = useParams()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
const pathname = getPathnameWithoutQuery(router.asPath, router.pathname)
|
|
const activeRoute = getPathSegment(pathname, 3)
|
|
|
|
const { viewLevel, selectedSectionKey, handleTopLevelClick, handleBackToTop } =
|
|
useMobileMenuNavigation({
|
|
currentSectionKey,
|
|
hasCurrentProductMenu: !!currentProductMenu,
|
|
onCloseSheet,
|
|
})
|
|
|
|
const {
|
|
projectAuthAll: authEnabled,
|
|
projectEdgeFunctionAll: edgeFunctionsEnabled,
|
|
projectStorageAll: storageEnabled,
|
|
realtimeAll: realtimeEnabled,
|
|
} = useIsFeatureEnabled([
|
|
'project_auth:all',
|
|
'project_edge_function:all',
|
|
'project_storage:all',
|
|
'realtime:all',
|
|
])
|
|
const authOverviewPageEnabled = useFlag('authOverviewPage')
|
|
const computeEnabled = useFlag('compute')
|
|
|
|
const toolRoutes = useGenerateToolRoutes()
|
|
const productRoutes = useMemo(
|
|
() =>
|
|
generateProductRoutes(ref, project, {
|
|
auth: authEnabled,
|
|
edgeFunctions: edgeFunctionsEnabled,
|
|
storage: storageEnabled,
|
|
realtime: realtimeEnabled,
|
|
authOverviewPage: authOverviewPageEnabled,
|
|
compute: computeEnabled,
|
|
}),
|
|
[
|
|
ref,
|
|
project,
|
|
authEnabled,
|
|
edgeFunctionsEnabled,
|
|
storageEnabled,
|
|
realtimeEnabled,
|
|
authOverviewPageEnabled,
|
|
computeEnabled,
|
|
]
|
|
)
|
|
const otherRoutes = useGenerateOtherRoutes()
|
|
const settingsRoutes = useMemo(() => generateSettingsRoutes(ref), [ref])
|
|
|
|
const homeRoute: Route = useMemo(
|
|
() => ({
|
|
key: 'HOME',
|
|
label: 'Project Overview',
|
|
icon: <Home size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />,
|
|
link: ref ? `/project/${ref}` : undefined,
|
|
}),
|
|
[ref]
|
|
)
|
|
|
|
const allTopLevelRoutes = useMemo(
|
|
() => [homeRoute, ...toolRoutes, ...productRoutes, ...otherRoutes, ...settingsRoutes],
|
|
[homeRoute, toolRoutes, productRoutes, otherRoutes, settingsRoutes]
|
|
)
|
|
|
|
const { sectionKey: sectionKeyToShow, sectionLabel } = resolveSectionDisplay({
|
|
viewLevel,
|
|
selectedSectionKey,
|
|
currentSectionKey,
|
|
currentProduct,
|
|
routes: allTopLevelRoutes,
|
|
})
|
|
|
|
const SectionMenuContent = sectionKeyToShow ? getProductMenuComponent(sectionKeyToShow) : null
|
|
const hasCurrentProductHeader =
|
|
viewLevel === 'section' && sectionKeyToShow === currentSectionKey && !!currentProductMenuHeader
|
|
const pageSegment = getPathSegment(pathname, 4)
|
|
|
|
const renderRoute = (route: Route, isActive: boolean) => (
|
|
<TopLevelRouteItem
|
|
key={route.key}
|
|
route={route}
|
|
isActive={isActive}
|
|
hasSubmenu={routeHasSubmenu(route)}
|
|
onTopLevelClick={handleTopLevelClick}
|
|
onCloseSheet={onCloseSheet}
|
|
/>
|
|
)
|
|
|
|
return (
|
|
<div className="flex flex-col h-full bg-background">
|
|
<SheetTitle className="sr-only">Project menu</SheetTitle>
|
|
{viewLevel === 'section' && sectionLabel && (
|
|
<div
|
|
className={cn(
|
|
'shrink-0 flex items-center gap-2 border-b border-default px-3 min-h-(--header-height)'
|
|
)}
|
|
>
|
|
<Button
|
|
variant="text"
|
|
className="p-1! justify-start"
|
|
icon={<ChevronLeft size={20} />}
|
|
onClick={handleBackToTop}
|
|
aria-label="Back to menu"
|
|
block
|
|
>
|
|
<span className="font-medium truncate text-sm">{sectionLabel}</span>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
{hasCurrentProductHeader && (
|
|
<ProductMenuBarHeader>{currentProductMenuHeader}</ProductMenuBarHeader>
|
|
)}
|
|
<div
|
|
className={cn(
|
|
'flex-1 overflow-y-auto pb-8 text-sidebar-foreground',
|
|
hasCurrentProductHeader && 'min-h-0 flex flex-col'
|
|
)}
|
|
>
|
|
{viewLevel === 'top' && (
|
|
<nav className="flex flex-col gap-2 p-1" aria-label="Project menu">
|
|
<SidebarMenu>
|
|
<SidebarGroup className="gap-0.5">
|
|
{[homeRoute, ...toolRoutes].map((route) =>
|
|
renderRoute(
|
|
route,
|
|
activeRoute === route.key || (route.key === 'HOME' && activeRoute === undefined)
|
|
)
|
|
)}
|
|
</SidebarGroup>
|
|
<Separator className="mx-2 w-auto bg-sidebar-border" />
|
|
<SidebarGroup className="gap-0.5">
|
|
{productRoutes.map((route) => renderRoute(route, activeRoute === route.key))}
|
|
</SidebarGroup>
|
|
<Separator className="mx-2 w-auto bg-sidebar-border" />
|
|
<SidebarGroup className="gap-0.5">
|
|
{otherRoutes.map((route) => renderRoute(route, activeRoute === route.key))}
|
|
</SidebarGroup>
|
|
<Separator className="mx-2 w-auto bg-sidebar-border" />
|
|
<SidebarGroup className="gap-0.5">
|
|
{settingsRoutes.map((route) => renderRoute(route, activeRoute === route.key))}
|
|
</SidebarGroup>
|
|
</SidebarMenu>
|
|
</nav>
|
|
)}
|
|
{viewLevel === 'section' && sectionKeyToShow && (
|
|
<div className={cn('p-1', hasCurrentProductHeader && 'min-h-0 flex-1 p-0')}>
|
|
{sectionKeyToShow === currentSectionKey && currentProductMenu ? (
|
|
currentProductMenu
|
|
) : SectionMenuContent ? (
|
|
<React.Suspense fallback={<GenericSkeletonLoader className="p-4" />}>
|
|
{sectionKeyToShow === 'advisors' ? (
|
|
<SectionMenuContent
|
|
{...({ page: pageSegment } as React.ComponentProps<typeof SectionMenuContent>)}
|
|
/>
|
|
) : (
|
|
<SectionMenuContent />
|
|
)}
|
|
</React.Suspense>
|
|
) : null}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|