mobile nav

This commit is contained in:
Francesco Sansalvadore committed 2026-04-02 19:56:10 +02:00
1 parent 54ac8f0c1c
commit 995d45b97f
2 files changed
+80 -28

No files matched your search

@@ -1,15 +1,20 @@
import { useParams } from 'common'
import { Plug } from 'lucide-react'
import { useRouter } from 'next/router'
import { parseAsBoolean, useQueryState } from 'nuqs'
import { Button, cn, Sidebar, SidebarContent, SidebarHeader } from 'ui'
import { ShimmeringLoader } from 'ui-patterns'
import { NavGroup } from './NavGroup'
import { useAppSidebarNavItems, type AppSidebarScope } from './useAppSidebarNavItems'
import { ConnectSheet } from '@/components/interfaces/ConnectSheet/ConnectSheet'
import { OrgSelector } from '@/components/layouts/Navigation/NavigationBar/OrgSelector'
import { ProjectBranchSelector } from '@/components/layouts/Navigation/NavigationBar/ProjectBranchSelector'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
import { useHideSidebar } from '@/hooks/misc/useHideSidebar'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { IS_PLATFORM } from '@/lib/constants'
export type { AppSidebarScope }
@@ -28,6 +33,8 @@ function AppSidebarNavBody({ scope, variant = 'desktop' }: AppSidebarNavBodyProp
const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false))
const { slug: orgRouteSlug } = useParams()
const { data: selectedOrganization } = useSelectedOrganizationQuery()
const { isPending: isLoadingOrganizations } = useOrganizationsQuery()
const { data: project, isPending: isLoadingProject } = useSelectedProjectQuery()
const {
isProjectScope,
@@ -40,31 +47,57 @@ function AppSidebarNavBody({ scope, variant = 'desktop' }: AppSidebarNavBodyProp
organizationItems,
} = useAppSidebarNavItems({ scope })
const isMobileSheet = variant === 'mobile-sheet'
const selectorHeaderClass = cn('flex-col gap-2', isMobileSheet ? 'flex' : 'hidden md:flex')
const isBranch = project != null && project.parentRef !== project.ref
const { data: parentProject, isPending: isLoadingParentProject } = useProjectDetailQuery(
{ ref: project?.parent_project_ref },
{ enabled: Boolean(isProjectScope && isBranch && project?.parent_project_ref) }
)
const selectedProject = parentProject ?? project
const isOrgSelectorLoading = isLoadingOrganizations
const isProjectSelectorLoading =
isLoadingProject || (isBranch && isLoadingParentProject) || !selectedProject
const shouldShowOrgSelector =
!isProjectScope && IS_PLATFORM && (Boolean(orgRouteSlug) || Boolean(selectedOrganization))
const isHeaderLoading =
(shouldShowOrgSelector && isOrgSelectorLoading) || (isProjectScope && isProjectSelectorLoading)
const isMobileSheet = variant === 'mobile-sheet'
const selectorHeaderClass = cn('flex-col gap-2', isMobileSheet ? 'flex' : 'hidden md:flex')
return (
<>
<SidebarHeader className={selectorHeaderClass}>
{shouldShowOrgSelector && <OrgSelector />}
{isProjectScope && (
{isHeaderLoading ? (
<ShimmeringLoader
className={
isProjectScope
? 'p-2 md:mr-2 md:w-[90px]'
: cn('p-2 md:mr-2 w-[90px]', isMobileSheet && 'w-full max-w-none')
}
/>
) : (
<>
<ProjectBranchSelector />
<div className="flex items-center px-0.5">
<Button
type="default"
size="small"
disabled={!isActiveHealthy}
onClick={() => setShowConnect(true)}
className="h-7 flex-1 justify-center gap-0 pl-2"
icon={<Plug className="rotate-90" strokeWidth={1.5} />}
>
Connect
</Button>
</div>
{shouldShowOrgSelector && <OrgSelector />}
{isProjectScope && (
<>
<ProjectBranchSelector />
<div className="flex items-center px-0.5">
<Button
type="default"
size="small"
disabled={!isActiveHealthy}
onClick={() => setShowConnect(true)}
className="h-7 flex-1 justify-center gap-0 pl-2"
icon={<Plug className="rotate-90" strokeWidth={1.5} />}
>
Connect
</Button>
</div>
</>
)}
</>
)}
</SidebarHeader>
@@ -125,12 +158,17 @@ export function AppSidebarV2({ scope }: AppSidebarV2Props = {}) {
/**
* Full-height V2 navigation for `StudioMobileSheetNav`, opened via `registerOpenMenu` from
* {@link ProjectLayoutV2}. Mirrors desktop {@link AppSidebarV2} structure.
* {@link ProjectLayoutV2}. Scope follows the current route like {@link DefaultLayoutV2}.
*/
export function AppSidebarMobileSheetMenu() {
const router = useRouter()
const sheetScope: AppSidebarScope = router.pathname.startsWith('/project')
? 'project'
: 'organization'
return (
<div className="flex h-full min-h-0 flex-col bg-sidebar text-foreground">
<AppSidebarNavBody scope="project" variant="mobile-sheet" />
<AppSidebarNavBody scope={sheetScope} variant="mobile-sheet" />
<ConnectSheet />
</div>
)
@@ -25,6 +25,7 @@ import { useEditorType } from '../editors/EditorsLayout.hooks'
import { useSetMainScrollContainer } from '../MainScrollContainerContext'
import { useMobileSheet } from '../Navigation/NavigationBar/MobileSheetContext'
import ProductMenuBar from '../Navigation/ProductMenuBar'
import { AppSidebarMobileSheetMenu } from '../NavigationV2/AppSidebar'
import BuildingState from './BuildingState'
import ConnectingState from './ConnectingState'
import { getSectionKeyFromPathname, MobileMenuContent } from './LayoutHeader/MobileMenuContent'
@@ -37,6 +38,7 @@ import RestartingState from './RestartingState'
import { RestoreFailedState } from './RestoreFailedState'
import { RestoringState } from './RestoringState'
import { UpgradingState } from './UpgradingState'
import { useIsNavigationV2Enabled } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { CreateBranchModal } from '@/components/interfaces/BranchManagement/CreateBranchModal'
import { ProjectAPIDocs } from '@/components/interfaces/ProjectAPIDocs/ProjectAPIDocs'
import { BannerFreeMicroUpgrade } from '@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade'
@@ -134,6 +136,7 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
)
const { showSidebar } = useAppStateSnapshot()
const { setContent: setMobileSheetContent, registerOpenMenu } = useMobileSheet()
const isNavigationV2 = useIsNavigationV2Enabled()
const pathname = getPathnameWithoutQuery(router.asPath, router.pathname)
const currentSectionKey = getSectionKeyFromPathname(pathname)
@@ -199,17 +202,28 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
useLayoutEffect(() => {
const unregister = registerOpenMenu(() => {
setMobileSheetContent(
<MobileMenuContent
currentProductMenu={productMenu ?? null}
currentProduct={product}
currentSectionKey={currentSectionKey}
onCloseSheet={() => setMobileSheetContent(null)}
/>
)
if (isNavigationV2) {
setMobileSheetContent(<AppSidebarMobileSheetMenu />)
} else {
setMobileSheetContent(
<MobileMenuContent
currentProductMenu={productMenu ?? null}
currentProduct={product}
currentSectionKey={currentSectionKey}
onCloseSheet={() => setMobileSheetContent(null)}
/>
)
}
})
return unregister
}, [registerOpenMenu, productMenu, product, currentSectionKey, setMobileSheetContent])
}, [
registerOpenMenu,
productMenu,
product,
currentSectionKey,
setMobileSheetContent,
isNavigationV2,
])
return (
<>