DefaultLayout.tsx133 lines · main
1import { LOCAL_STORAGE_KEYS, useBreakpoint, useParams } from 'common'
2import { useRouter } from 'next/router'
3import { PropsWithChildren, useEffect, useState } from 'react'
4import { ResizablePanel, ResizablePanelGroup, SidebarProvider } from 'ui'
5
6import { BannerStack } from '../ui/BannerStack/BannerStack'
7import { LayoutHeader } from './Navigation/LayoutHeader/LayoutHeader'
8import MobileNavigationBar from './Navigation/NavigationBar/MobileNavigationBar'
9import { MobileSheetProvider } from './Navigation/NavigationBar/MobileSheetContext'
10import { StudioMobileSheetNav } from './Navigation/NavigationBar/StudioMobileSheetNav'
11import { LayoutSidebar } from './ProjectLayout/LayoutSidebar'
12import { LayoutSidebarProvider } from './ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
13import { ProjectContextProvider } from './ProjectLayout/ProjectContext'
14import { AppBannerWrapper } from '@/components/interfaces/App/AppBannerWrapper'
15import { Sidebar } from '@/components/interfaces/Sidebar'
16import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
17import { useCheckLatestDeploy } from '@/hooks/use-check-latest-deploy'
18import { IS_PLATFORM } from '@/lib/constants'
19import { useAppStateSnapshot } from '@/state/app-state'
20
21export interface DefaultLayoutProps {
22 headerTitle?: string
23 hideMobileMenu?: boolean
24}
25
26/**
27 * Base layout for all project pages in the dashboard, rendered as the first child on all page files within a project.
28 *
29 * 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)
30 *
31 * The base layout handles rendering the following UI components:
32 * - App banner (e.g for notices or incidents)
33 * - Mobile navigation bar
34 * - First level side navigation bar (e.g For navigating to Table Editor, SQL Editor, Database page, etc)
35 */
36export const DefaultLayout = ({
37 children,
38 headerTitle,
39 hideMobileMenu,
40}: PropsWithChildren<DefaultLayoutProps>) => {
41 const { ref } = useParams()
42 const router = useRouter()
43 const appSnap = useAppStateSnapshot()
44
45 const [lastVisitedOrganization] = useLocalStorageQuery(
46 LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
47 ''
48 )
49
50 const backToDashboardURL = router.pathname.startsWith('/account')
51 ? appSnap.lastRouteBeforeVisitingAccountPage.length > 0
52 ? appSnap.lastRouteBeforeVisitingAccountPage
53 : IS_PLATFORM && !!lastVisitedOrganization
54 ? `/org/${lastVisitedOrganization}`
55 : IS_PLATFORM
56 ? '/organizations'
57 : '/project/default'
58 : undefined
59
60 useCheckLatestDeploy()
61
62 const isMobile = useBreakpoint('md')
63
64 const contentMinSizePercentage = 50
65 const contentMaxSizePercentage = 70
66
67 const [isMounted, setIsMounted] = useState(false)
68
69 useEffect(() => {
70 setIsMounted(true)
71 }, [])
72
73 // This is required to prevent layout shift when rendering resizable panels (they initially render at 50%, then shift
74 // to whatever is specified).
75 if (!isMounted) {
76 return null
77 }
78
79 return (
80 <SidebarProvider defaultOpen={false}>
81 <LayoutSidebarProvider>
82 <ProjectContextProvider projectRef={ref}>
83 <MobileSheetProvider>
84 <div className="flex flex-col h-screen w-screen">
85 {/* Top Banner */}
86 <AppBannerWrapper />
87 <div className="shrink-0">
88 {isMobile && (
89 <MobileNavigationBar
90 hideMobileMenu={hideMobileMenu}
91 backToDashboardURL={backToDashboardURL}
92 />
93 )}
94 <LayoutHeader headerTitle={headerTitle} backToDashboardURL={backToDashboardURL} />
95 </div>
96 {/* Main Content Area */}
97 <div className="flex flex-1 w-full overflow-y-hidden">
98 {/* Sidebar - Only show for project pages, not account pages */}
99 {!router.pathname.startsWith('/account') && <Sidebar />}
100 {/* Main Content with Layout Sidebar */}
101 <ResizablePanelGroup
102 orientation="horizontal"
103 className="h-full w-full overflow-x-hidden flex-1 flex flex-row gap-0"
104 autoSaveId="default-layout-content"
105 >
106 <ResizablePanel
107 id="panel-content"
108 className="w-full"
109 minSize={`${contentMinSizePercentage}`}
110 maxSize={`${contentMaxSizePercentage}`}
111 defaultSize={`${contentMaxSizePercentage}`}
112 >
113 <div className="h-full overflow-y-auto">{children}</div>
114 </ResizablePanel>
115 <LayoutSidebar
116 minSize={`${100 - contentMaxSizePercentage}`}
117 maxSize={`${100 - contentMinSizePercentage}`}
118 defaultSize={`${100 - contentMaxSizePercentage}`}
119 />
120 </ResizablePanelGroup>
121 </div>
122 </div>
123
124 <BannerStack />
125 <StudioMobileSheetNav />
126 </MobileSheetProvider>
127 </ProjectContextProvider>
128 </LayoutSidebarProvider>
129 </SidebarProvider>
130 )
131}
132
133export default DefaultLayout