diff --git a/apps/studio/components/interfaces/Sidebar.tsx b/apps/studio/components/interfaces/Sidebar.tsx index aa9010f9800..64137d95c49 100644 --- a/apps/studio/components/interfaces/Sidebar.tsx +++ b/apps/studio/components/interfaces/Sidebar.tsx @@ -30,15 +30,18 @@ import { import { Route } from '../ui/ui.types' import { useIsAPIDocsSidePanelEnabled, + useIsNavigationV2Enabled, useIsPlatformWebhooksEnabled, useUnifiedLogsPreview, } from './App/FeaturePreview/FeaturePreviewContext' +import { ConnectSheet } from './ConnectSheet/ConnectSheet' import { generateOtherRoutes, generateProductRoutes, generateSettingsRoutes, generateToolRoutes, } from '@/components/layouts/Navigation/NavigationBar/NavigationBar.utils' +import { SidebarHeader } from '@/components/layouts/NavigationV2/SidebarHeader' import { ProjectIndexPageLink } from '@/data/prefetchers/project.$ref' import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' import { useHideSidebar } from '@/hooks/misc/useHideSidebar' @@ -65,6 +68,7 @@ export interface SidebarProps extends ComponentPropsWithoutRef { const { setOpen } = useSidebar() const hideSideBar = useHideSidebar() + const isNavigationV2 = useIsNavigationV2Enabled() const [sidebarBehaviour, setSidebarBehaviour] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.SIDEBAR_BEHAVIOR, @@ -78,52 +82,69 @@ export const Sidebar = ({ className, ...props }: SidebarProps) => { }, [sidebarBehaviour, setOpen]) return ( - - {!hideSideBar && ( - { - if (sidebarBehaviour === 'expandable') setOpen(true) - }} - onMouseLeave={() => { - if (sidebarBehaviour === 'expandable') setOpen(false) - }} - > - - - - - - )} - - )} - -
-
- -
-
- - ) -} - -interface AppSidebarV2Props { - scope?: AppSidebarScope -} - -export function AppSidebarV2({ scope }: AppSidebarV2Props = {}) { - const hideSidebar = useHideSidebar() - - return ( - <> - {!hideSidebar && ( - - - - )} - - - - ) -} - -/** - * Full-height V2 navigation for `StudioMobileSheetNav`, opened via `registerOpenMenu` from - * {@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 ( -
- - -
- ) -} diff --git a/apps/studio/components/layouts/NavigationV2/DefaultLayout.tsx b/apps/studio/components/layouts/NavigationV2/DefaultLayout.tsx index acecf11e963..1438cb3bcfd 100644 --- a/apps/studio/components/layouts/NavigationV2/DefaultLayout.tsx +++ b/apps/studio/components/layouts/NavigationV2/DefaultLayout.tsx @@ -5,12 +5,12 @@ import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { useRouter } from 'next/router' import type { PropsWithChildren } from 'react' import { useAppStateSnapshot } from 'state/app-state' -import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'ui' +import { cn } from 'ui' import { DefaultLayoutProviders } from '../DefaultLayoutProviders' -import { AppSidebarV2 } from './AppSidebar' import { LayoutHeader } from './LayoutHeader' import { RightPanelToolbarLayout } from './RightPanelToolbar' +import { Sidebar } from '@/components/interfaces/Sidebar' import { useHideSidebar } from '@/hooks/misc/useHideSidebar' export interface DefaultLayoutV2Props { @@ -18,14 +18,10 @@ export interface DefaultLayoutV2Props { hideMobileMenu?: boolean } -const leftSidebarMinSize = 160 -const leftSidebarMaxSize = 300 -const leftSidebarDefaultSize = '230px' - const mainContentClass = 'flex h-full w-full min-h-0 min-w-0 flex-col overflow-hidden' /** - * V2 navigation shell: mobile bar, then AppSidebar (when shown) + main column. Main column has + * V2 navigation shell: mobile bar, then primary `Sidebar` (when shown) + main column. Main column has * LayoutHeader above the row of page content and right panel toolbar. */ export const DefaultLayoutV2 = ({ @@ -60,7 +56,7 @@ export const DefaultLayoutV2 = ({ const contentRow = {children} const mainColumn = ( -
+
{header}
{contentRow}
@@ -77,29 +73,8 @@ export const DefaultLayoutV2 = ({ />
- {showLeftSidebar ? ( - - - - - - - {mainColumn} - - - ) : ( - mainColumn - )} + {showLeftSidebar && } + {mainColumn}
diff --git a/apps/studio/components/layouts/NavigationV2/ProjectLayout.tsx b/apps/studio/components/layouts/NavigationV2/ProjectLayout.tsx index 53b34c8248b..1c0f9744c87 100644 --- a/apps/studio/components/layouts/NavigationV2/ProjectLayout.tsx +++ b/apps/studio/components/layouts/NavigationV2/ProjectLayout.tsx @@ -15,7 +15,7 @@ import { useSetMainScrollContainer } from '../MainScrollContainerContext' import { useMobileSheet } from '../Navigation/NavigationBar/MobileSheetContext' import { ContentWrapper } from '../ProjectLayout' import { ProjectPausedState } from '../ProjectLayout/PausedState/ProjectPausedState' -import { AppSidebarMobileSheetMenu } from './AppSidebar' +import { SidebarMobileSheetMenu } from '@/components/interfaces/Sidebar' export interface ProjectLayoutV2Props { title?: string @@ -27,7 +27,7 @@ export interface ProjectLayoutV2Props { /** * V2 Project layout - renders content directly without a secondary product menu sidebar. - * In V2, all navigation is in the primary sidebar (AppSidebar), so this layout + * In V2, all navigation is in the primary `Sidebar`, so this layout * only handles title, loading states, and content wrapping. */ export const ProjectLayoutV2 = forwardRef>( @@ -57,7 +57,7 @@ export const ProjectLayoutV2 = forwardRef { const unregister = registerOpenMenu(() => { - setMobileSheetContent() + setMobileSheetContent() }) return unregister }, [registerOpenMenu, setMobileSheetContent]) diff --git a/apps/studio/components/layouts/NavigationV2/SidebarHeader.tsx b/apps/studio/components/layouts/NavigationV2/SidebarHeader.tsx new file mode 100644 index 00000000000..882fbc482e2 --- /dev/null +++ b/apps/studio/components/layouts/NavigationV2/SidebarHeader.tsx @@ -0,0 +1,65 @@ +import { Plug } from 'lucide-react' +import { + Button, + cn, + SidebarHeader as SidebarHeaderPrimitive, + Tooltip, + TooltipContent, + TooltipTrigger, +} from 'ui' + +import { useSidebarHeader, type SidebarHeaderProps } from './useSidebarHeader' +import { OrgSelector } from '@/components/layouts/Navigation/NavigationBar/OrgSelector' +import { ProjectBranchSelector } from '@/components/layouts/Navigation/NavigationBar/ProjectBranchSelector' + +export type { SidebarHeaderProps } + +export function SidebarHeader(props: SidebarHeaderProps) { + const { + setShowConnect, + isProjectScope, + isActiveHealthy, + shouldShowOrgSelector, + selectorHeaderClass, + isCollapsedRail, + } = useSidebarHeader(props) + + return ( + + {shouldShowOrgSelector && } + {isProjectScope && ( + <> + +
+ {isCollapsedRail ? ( + + + + )} +
+ + )} +
+ ) +} diff --git a/apps/studio/components/layouts/NavigationV2/index.tsx b/apps/studio/components/layouts/NavigationV2/index.tsx index 467259d16eb..8d82676af55 100644 --- a/apps/studio/components/layouts/NavigationV2/index.tsx +++ b/apps/studio/components/layouts/NavigationV2/index.tsx @@ -1,5 +1,4 @@ export type { AppSidebarScope } from './app-sidebar.types' -export { AppSidebarV2 } from './AppSidebar' export { DefaultLayoutV2 } from './DefaultLayout' export { ProjectLayoutV2, ProjectLayoutV2WithAuth } from './ProjectLayout' export { RightPanelToolbarLayout } from './RightPanelToolbar' diff --git a/apps/studio/components/layouts/NavigationV2/useSidebarHeader.ts b/apps/studio/components/layouts/NavigationV2/useSidebarHeader.ts new file mode 100644 index 00000000000..009bd35ea83 --- /dev/null +++ b/apps/studio/components/layouts/NavigationV2/useSidebarHeader.ts @@ -0,0 +1,42 @@ +import { useParams } from 'common' +import { useRouter } from 'next/router' +import { parseAsBoolean, useQueryState } from 'nuqs' +import { useSidebar } from 'ui' + +import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { IS_PLATFORM, PROJECT_STATUS } from '@/lib/constants' + +export type SidebarHeaderProps = { + variant: 'desktop' | 'mobile-sheet' + scope?: 'project' | 'organization' +} + +export function useSidebarHeader({ variant, scope: scopeProp }: SidebarHeaderProps) { + const { state, isMobile } = useSidebar() + const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) + const router = useRouter() + const { slug: orgRouteSlug } = useParams() + const { data: selectedOrganization } = useSelectedOrganizationQuery() + const { data: project } = useSelectedProjectQuery() + + const resolvedScope = + scopeProp ?? (router.pathname.startsWith('/project') ? 'project' : 'organization') + const isProjectScope = resolvedScope === 'project' + const isActiveHealthy = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY + + const shouldShowOrgSelector = + !isProjectScope && IS_PLATFORM && (Boolean(orgRouteSlug) || Boolean(selectedOrganization)) + + const selectorHeaderClass = 'flex-col gap-2 hidden md:flex' + const isCollapsedRail = variant === 'desktop' && state === 'collapsed' && !isMobile + + return { + setShowConnect, + isProjectScope, + isActiveHealthy, + shouldShowOrgSelector, + selectorHeaderClass, + isCollapsedRail, + } +} diff --git a/apps/studio/components/layouts/ProjectLayout/index.tsx b/apps/studio/components/layouts/ProjectLayout/index.tsx index b3632e71cbd..0db3abfac10 100644 --- a/apps/studio/components/layouts/ProjectLayout/index.tsx +++ b/apps/studio/components/layouts/ProjectLayout/index.tsx @@ -25,7 +25,6 @@ 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' @@ -41,6 +40,7 @@ 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 { SidebarMobileSheetMenu } from '@/components/interfaces/Sidebar' import { BannerFreeMicroUpgrade } from '@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade' import { BANNER_ID, useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider' import { ResourceExhaustionWarningBanner } from '@/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner' @@ -203,7 +203,7 @@ export const ProjectLayout = forwardRef { const unregister = registerOpenMenu(() => { if (isNavigationV2) { - setMobileSheetContent() + setMobileSheetContent() } else { setMobileSheetContent( - + {/* */} - {!isTestEnv && ( + {/* {!isTestEnv && ( - )} + )} */}