DefaultLayout.tsx 5.1 KB

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