Files
supabase/apps/studio/components/layouts/DefaultLayout.tsx
Francesco Sansalvadore 156f904018 feat: floating mobile toolbar (#43444)
- move navigation components to `apps/studio/components/layouts/Navigation`
- add [FloatingMobileToolbar](https://github.com/supabase/supabase/pull/43444/changes#diff-3dffe47fd51ca851d612d8728e03b2dc344ec213d4f3a46a824d3fa32a7cc851) as quick access to tools such as search, assistant, inline editor, etc - behind feature flag and feature preview (true by default as it's a bit
annoying to have to enable it all the time as previews are stored in
local-storage)
- fix sidebar panels closing on viewport resizing (regression from previous pr)

https://github.com/user-attachments/assets/d6881e3b-5128-4306-bb82-3ca39c755dba

<img width="986" height="697" alt="Screenshot 2026-03-12 at 12 40 11"
src="https://github.com/user-attachments/assets/da8511e2-7d01-4237-b814-596031c747c5"
/>
2026-03-13 12:13:07 +00:00

130 lines
5.1 KiB
TypeScript

import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { AppBannerWrapper } from 'components/interfaces/App/AppBannerWrapper'
import { Sidebar } from 'components/interfaces/Sidebar'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useCheckLatestDeploy } from 'hooks/use-check-latest-deploy'
import { useRouter } from 'next/router'
import { PropsWithChildren, useEffect, useState } from 'react'
import { useAppStateSnapshot } from 'state/app-state'
import { ResizablePanel, ResizablePanelGroup, SidebarProvider } from 'ui'
import { BannerStack } from '../ui/BannerStack/BannerStack'
import { BannerStackProvider } from '../ui/BannerStack/BannerStackProvider'
import { LayoutHeader } from './Navigation/LayoutHeader/LayoutHeader'
import MobileNavigationBar from './Navigation/NavigationBar/MobileNavigationBar'
import { MobileSheetProvider } from './Navigation/NavigationBar/MobileSheetContext'
import { StudioMobileSheetNav } from './Navigation/NavigationBar/StudioMobileSheetNav'
import { LayoutSidebar } from './ProjectLayout/LayoutSidebar'
import { LayoutSidebarProvider } from './ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { ProjectContextProvider } from './ProjectLayout/ProjectContext'
export interface DefaultLayoutProps {
headerTitle?: string
hideMobileMenu?: boolean
}
/**
* Base layout for all project pages in the dashboard, rendered as the first child on all page files within a project.
*
* A second layout as the child to this is required, and the layout depends on which section of the dashboard the page is on. (e.g Auth - AuthLayout)
*
* The base layout handles rendering the following UI components:
* - App banner (e.g for notices or incidents)
* - Mobile navigation bar
* - First level side navigation bar (e.g For navigating to Table Editor, SQL Editor, Database page, etc)
*/
export const DefaultLayout = ({
children,
headerTitle,
hideMobileMenu,
}: PropsWithChildren<DefaultLayoutProps>) => {
const { ref } = useParams()
const router = useRouter()
const appSnap = useAppStateSnapshot()
const [lastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
)
const backToDashboardURL = router.pathname.startsWith('/account')
? appSnap.lastRouteBeforeVisitingAccountPage.length > 0
? appSnap.lastRouteBeforeVisitingAccountPage
: !!lastVisitedOrganization
? `/org/${lastVisitedOrganization}`
: '/organizations'
: undefined
useCheckLatestDeploy()
const contentMinSizePercentage = 50
const contentMaxSizePercentage = 70
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
}, [])
// This is required to prevent layout shift when rendering resizable panels (they initially render at 50%, then shift
// to whatever is specified).
if (!isMounted) {
return null
}
return (
<SidebarProvider defaultOpen={false}>
<LayoutSidebarProvider>
<ProjectContextProvider projectRef={ref}>
<MobileSheetProvider>
<BannerStackProvider>
<div className="flex flex-col h-screen w-screen">
{/* Top Banner */}
<AppBannerWrapper />
<div className="flex-shrink-0">
<MobileNavigationBar
hideMobileMenu={hideMobileMenu}
backToDashboardURL={backToDashboardURL}
/>
<LayoutHeader headerTitle={headerTitle} backToDashboardURL={backToDashboardURL} />
</div>
{/* Main Content Area */}
<div className="flex flex-1 w-full overflow-y-hidden">
{/* Sidebar - Only show for project pages, not account pages */}
{!router.pathname.startsWith('/account') && <Sidebar />}
{/* Main Content with Layout Sidebar */}
<ResizablePanelGroup
orientation="horizontal"
className="h-full w-full overflow-x-hidden flex-1 flex flex-row gap-0"
autoSaveId="default-layout-content"
>
<ResizablePanel
id="panel-content"
className="w-full"
minSize={`${contentMinSizePercentage}`}
maxSize={`${contentMaxSizePercentage}`}
defaultSize={`${contentMaxSizePercentage}`}
>
<div className="h-full overflow-y-auto">{children}</div>
</ResizablePanel>
<LayoutSidebar
minSize={`${100 - contentMaxSizePercentage}`}
maxSize={`${100 - contentMinSizePercentage}`}
defaultSize={`${100 - contentMaxSizePercentage}`}
/>
</ResizablePanelGroup>
</div>
</div>
<BannerStack />
<StudioMobileSheetNav />
</BannerStackProvider>
</MobileSheetProvider>
</ProjectContextProvider>
</LayoutSidebarProvider>
</SidebarProvider>
)
}
export default DefaultLayout