mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
Add AppLayout and simplify height logic in layout component
This commit is contained in:
1 parent
0672fe0464
commit
14e8c19c96
8 files changed
+181
-144
No files matched your search
+10
-4
@@ -1,18 +1,23 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import FeedbackDropdown from '../ProjectLayout/LayoutHeader/FeedbackDropdown'
|
||||
import HelpPopover from '../ProjectLayout/LayoutHeader/HelpPopover'
|
||||
import NotificationsPopover from '../ProjectLayout/LayoutHeader/NotificationsPopover'
|
||||
import OrganizationDropdown from './OrganizationDropdown'
|
||||
import UserSettingsDropdown from './UserSettingsDropdown'
|
||||
import { useSelectedOrganization, useSelectedProject } from 'hooks'
|
||||
import ProjectDropdown from '../ProjectLayout/LayoutHeader/ProjectDropdown'
|
||||
|
||||
const HeaderBar = () => {
|
||||
const AppHeader = () => {
|
||||
const router = useRouter()
|
||||
const project = useSelectedProject()
|
||||
const organization = useSelectedOrganization()
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-scale-100 shadow-md">
|
||||
<div className="flex items-center justify-between px-4 py-1 bg-black shadow-md">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link href="/">
|
||||
<Link href={`/org/${organization?.slug}`}>
|
||||
<a className="block">
|
||||
<img
|
||||
src={`${router.basePath}/img/supabase-logo.svg`}
|
||||
@@ -22,6 +27,7 @@ const HeaderBar = () => {
|
||||
</a>
|
||||
</Link>
|
||||
<OrganizationDropdown />
|
||||
<ProjectDropdown alt />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
@@ -34,4 +40,4 @@ const HeaderBar = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default HeaderBar
|
||||
export default AppHeader
|
||||
@@ -1,13 +1,14 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
import HeaderBar from './HeaderBar'
|
||||
import AppHeader from './AppHeader'
|
||||
import { useFlag } from 'hooks'
|
||||
|
||||
const AppLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
const navLayoutV2 = useFlag('navigationLayoutV2')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<HeaderBar />
|
||||
{navLayoutV2 && <AppHeader />}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { Button, Dropdown, IconChevronDown, IconChevronsDown, IconCode, IconPlus } from 'ui'
|
||||
import Link from 'next/link'
|
||||
import { Badge, Button, Dropdown, IconCode, IconPlus } from 'ui'
|
||||
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useFlag, useSelectedOrganization, useStore } from 'hooks'
|
||||
import { useFlag, useSelectedOrganization } from 'hooks'
|
||||
import { IS_PLATFORM } from 'lib/constants'
|
||||
import Link from 'next/link'
|
||||
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
||||
|
||||
const OrganizationDropdown = () => {
|
||||
const router = useRouter()
|
||||
const { ui } = useStore()
|
||||
const { data: organizations } = useOrganizationsQuery()
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
const orgCreationV2 = useFlag('orgcreationv2')
|
||||
|
||||
const { data, isSuccess } = useOrgSubscriptionQuery({ orgSlug: selectedOrganization?.slug })
|
||||
|
||||
return IS_PLATFORM ? (
|
||||
<Dropdown
|
||||
side="bottom"
|
||||
@@ -52,6 +52,11 @@ const OrganizationDropdown = () => {
|
||||
iconRight={<IconCode className="text-scale-1100 rotate-90" strokeWidth={2} size={12} />}
|
||||
>
|
||||
<span className="text-sm">{selectedOrganization?.name}</span>
|
||||
{isSuccess && (
|
||||
<Badge color="slate" className="ml-2">
|
||||
{data?.plan.name}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import { Button, Dropdown, IconPlus, Popover } from 'ui'
|
||||
import { Button, Dropdown, IconCode, IconPlus, Popover } from 'ui'
|
||||
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useSelectedOrganization, useSelectedProject } from 'hooks'
|
||||
@@ -30,7 +30,11 @@ export const sanitizeRoute = (route: string, routerQueries: ParsedUrlQuery) => {
|
||||
}
|
||||
}
|
||||
|
||||
const ProjectDropdown = () => {
|
||||
interface ProjectDropdownProps {
|
||||
alt?: boolean
|
||||
}
|
||||
|
||||
const ProjectDropdown = ({ alt }: ProjectDropdownProps) => {
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
const selectedProject = useSelectedProject()
|
||||
const { data: allProjects } = useProjectsQuery()
|
||||
@@ -75,13 +79,18 @@ const ProjectDropdown = () => {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Button asChild type="text" size="tiny" className="my-1">
|
||||
<span>{selectedProject?.name}</span>
|
||||
<Button
|
||||
type="text"
|
||||
iconRight={
|
||||
alt ? <IconCode className="text-scale-1100 rotate-90" strokeWidth={2} size={12} /> : null
|
||||
}
|
||||
>
|
||||
<span className="text-sm">{selectedProject?.name}</span>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
) : (
|
||||
<Button asChild type="text" size="tiny">
|
||||
<span>{selectedProject?.name}</span>
|
||||
<Button type="text">
|
||||
<span className="text-sm">{selectedProject?.name}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { isUndefined } from 'lodash'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { FC } from 'react'
|
||||
|
||||
import { useTheme } from 'common'
|
||||
import { useParams } from 'common/hooks'
|
||||
@@ -27,15 +26,13 @@ import {
|
||||
} from './NavigationBar.utils'
|
||||
import NavigationIconButton from './NavigationIconButton'
|
||||
|
||||
interface Props {}
|
||||
|
||||
const NavigationBar: FC<Props> = ({}) => {
|
||||
const NavigationBar = ({}) => {
|
||||
const router = useRouter()
|
||||
const { isDarkMode, toggleTheme } = useTheme()
|
||||
const { ref: projectRef } = useParams()
|
||||
|
||||
const { project } = useProjectContext()
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
const navLayoutV2 = useFlag('navigationLayoutV2')
|
||||
|
||||
const activeRoute = router.pathname.split('/')[3]
|
||||
const toolRoutes = generateToolRoutes(projectRef, project)
|
||||
@@ -46,22 +43,23 @@ const NavigationBar: FC<Props> = ({}) => {
|
||||
const { setIsOpen } = useCommandMenu()
|
||||
return (
|
||||
<div
|
||||
style={{ height: ongoingIncident ? 'calc(100vh - 44px)' : '100vh' }}
|
||||
className={[
|
||||
'flex w-14 flex-col justify-between overflow-y-hidden p-2',
|
||||
'border-r bg-body border-scale-500',
|
||||
].join(' ')}
|
||||
>
|
||||
<ul className="flex flex-col space-y-2">
|
||||
<Link href="/projects">
|
||||
<a className="block">
|
||||
<img
|
||||
src={`${router.basePath}/img/supabase-logo.svg`}
|
||||
alt="Supabase"
|
||||
className="mx-auto h-[40px] w-6 cursor-pointer rounded"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
{!navLayoutV2 && (
|
||||
<Link href="/projects">
|
||||
<a className="block">
|
||||
<img
|
||||
src={`${router.basePath}/img/supabase-logo.svg`}
|
||||
alt="Supabase"
|
||||
className="mx-auto h-[40px] w-6 cursor-pointer rounded"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
<NavigationIconButton
|
||||
isActive={isUndefined(activeRoute) && !isUndefined(router.query.ref)}
|
||||
route={{
|
||||
@@ -71,7 +69,7 @@ const NavigationBar: FC<Props> = ({}) => {
|
||||
link: `/project/${projectRef}`,
|
||||
}}
|
||||
/>
|
||||
<div className="bg-scale-500 h-px w-full"></div>
|
||||
<div className="bg-scale-500 h-px w-full" />
|
||||
{toolRoutes.map((route) => (
|
||||
<NavigationIconButton
|
||||
key={route.key}
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { useFlag } from 'hooks'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
interface Props {
|
||||
interface ProductMenuBarProps {
|
||||
title: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const ProductMenuBar: FC<Props> = ({ title, children }) => {
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
const maxHeight = ongoingIncident ? 'calc(100vh - 44px)' : '100vh'
|
||||
|
||||
const ProductMenuBar = ({ title, children }: PropsWithChildren<ProductMenuBarProps>) => {
|
||||
return (
|
||||
<div
|
||||
style={{ height: maxHeight, maxHeight }}
|
||||
className={[
|
||||
'hide-scrollbar flex w-64 flex-col border-r', // Layout
|
||||
'bg-scale-200', // Light mode
|
||||
@@ -25,7 +19,9 @@ const ProductMenuBar: FC<Props> = ({ title, children }) => {
|
||||
>
|
||||
<h4 className="text-lg">{title}</h4>
|
||||
</div>
|
||||
<div className="flex-grow overflow-y-auto">{children}</div>
|
||||
<div className="flex-grow overflow-y-auto" style={{ maxHeight: 'calc(100vh - 96px)' }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ProjectContextProvider } from './ProjectContext'
|
||||
import ProjectPausedState from './ProjectPausedState'
|
||||
import RestoringState from './RestoringState'
|
||||
import UpgradingState from './UpgradingState'
|
||||
import AppLayout from '../AppLayout/AppLayout'
|
||||
|
||||
export interface ProjectLayoutProps {
|
||||
title?: string
|
||||
@@ -39,61 +40,70 @@ const ProjectLayout = ({
|
||||
}: PropsWithChildren<ProjectLayoutProps>) => {
|
||||
const router = useRouter()
|
||||
const { ref: projectRef } = useParams()
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
const selectedProject = useSelectedProject()
|
||||
const projectName = selectedProject?.name
|
||||
const organizationName = selectedOrganization?.name
|
||||
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
const navLayoutV2 = useFlag('navigationLayoutV2')
|
||||
const appHeaderHeight = navLayoutV2 ? 48 : 0
|
||||
|
||||
const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE
|
||||
const ignorePausedState =
|
||||
router.pathname === '/project/[ref]' || router.pathname.includes('/project/[ref]/settings')
|
||||
const showPausedState = isPaused && !ignorePausedState
|
||||
|
||||
return (
|
||||
<ProjectContextProvider projectRef={projectRef}>
|
||||
<Head>
|
||||
<title>
|
||||
{title
|
||||
? `${title} | Supabase`
|
||||
: selectedTable
|
||||
? `${selectedTable} | ${projectName} | ${organizationName} | Supabase`
|
||||
: projectName
|
||||
? `${projectName} | ${organizationName} | Supabase`
|
||||
: organizationName
|
||||
? `${organizationName} | Supabase`
|
||||
: 'Supabase'}
|
||||
</title>
|
||||
<meta name="description" content="Supabase Studio" />
|
||||
</Head>
|
||||
<div className="flex h-full">
|
||||
{/* Left-most navigation side bar to access products */}
|
||||
{!hideIconBar && <NavigationBar />}
|
||||
|
||||
{/* Product menu bar */}
|
||||
{!showPausedState && (
|
||||
<MenuBarWrapper isLoading={isLoading} productMenu={productMenu}>
|
||||
<ProductMenuBar title={product}>{productMenu}</ProductMenuBar>
|
||||
</MenuBarWrapper>
|
||||
)}
|
||||
|
||||
<main
|
||||
className="flex flex-col flex-1 w-full overflow-x-hidden"
|
||||
style={{ height: ongoingIncident ? 'calc(100vh - 44px)' : '100vh' }}
|
||||
<AppLayout>
|
||||
<ProjectContextProvider projectRef={projectRef}>
|
||||
<Head>
|
||||
<title>
|
||||
{title
|
||||
? `${title} | Supabase`
|
||||
: selectedTable
|
||||
? `${selectedTable} | ${projectName} | ${organizationName} | Supabase`
|
||||
: projectName
|
||||
? `${projectName} | ${organizationName} | Supabase`
|
||||
: organizationName
|
||||
? `${organizationName} | Supabase`
|
||||
: 'Supabase'}
|
||||
</title>
|
||||
<meta name="description" content="Supabase Studio" />
|
||||
</Head>
|
||||
<div
|
||||
className="flex"
|
||||
style={{
|
||||
height: ongoingIncident
|
||||
? `calc(100vh - 44px - ${appHeaderHeight}px)`
|
||||
: `calc(100vh - ${appHeaderHeight}px)`,
|
||||
}}
|
||||
>
|
||||
{!hideHeader && <LayoutHeader />}
|
||||
{showPausedState ? (
|
||||
<div className="mx-auto my-16 w-full h-full max-w-7xl flex items-center">
|
||||
<div className="w-full">
|
||||
<ProjectPausedState product={product} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ContentWrapper isLoading={isLoading}>{children}</ContentWrapper>
|
||||
{/* Left-most navigation side bar to access products */}
|
||||
{!hideIconBar && <NavigationBar />}
|
||||
|
||||
{/* Product menu bar */}
|
||||
{!showPausedState && (
|
||||
<MenuBarWrapper isLoading={isLoading} productMenu={productMenu}>
|
||||
<ProductMenuBar title={product}>{productMenu}</ProductMenuBar>
|
||||
</MenuBarWrapper>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</ProjectContextProvider>
|
||||
|
||||
<main className="flex flex-col flex-1 w-full overflow-x-hidden">
|
||||
{!navLayoutV2 && !hideHeader && <LayoutHeader />}
|
||||
{showPausedState ? (
|
||||
<div className="mx-auto my-16 w-full h-full max-w-7xl flex items-center">
|
||||
<div className="w-full">
|
||||
<ProjectPausedState product={product} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ContentWrapper isLoading={isLoading}>{children}</ContentWrapper>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</ProjectContextProvider>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -190,44 +200,53 @@ export const ProjectLayoutNonBlocking = ({
|
||||
const selectedProject = useSelectedProject()
|
||||
const router = useRouter()
|
||||
const { ref: projectRef } = useParams()
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
const isPaused = selectedProject?.status === PROJECT_STATUS.INACTIVE
|
||||
const ignorePausedState =
|
||||
router.pathname === '/project/[ref]' || router.pathname.includes('/project/[ref]/settings')
|
||||
const showPausedState = isPaused && !ignorePausedState
|
||||
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
const navLayoutV2 = useFlag('navigationLayoutV2')
|
||||
const appHeaderHeight = navLayoutV2 ? 48 : 0
|
||||
|
||||
return (
|
||||
<ProjectContextProvider projectRef={projectRef}>
|
||||
<Head>
|
||||
<title>{title ? `${title} | Supabase` : 'Supabase'}</title>
|
||||
<meta name="description" content="Supabase Studio" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<div className="flex h-full">
|
||||
{/* Left-most navigation side bar to access products */}
|
||||
{!hideIconBar && <NavigationBar />}
|
||||
|
||||
{/* Product menu bar */}
|
||||
{productMenu && !showPausedState && (
|
||||
<ProductMenuBar title={product}>{productMenu}</ProductMenuBar>
|
||||
)}
|
||||
|
||||
<main
|
||||
className="flex w-full flex-1 flex-col overflow-x-hidden"
|
||||
style={{ height: ongoingIncident ? 'calc(100vh - 44px)' : '100vh' }}
|
||||
<AppLayout>
|
||||
<ProjectContextProvider projectRef={projectRef}>
|
||||
<Head>
|
||||
<title>{title ? `${title} | Supabase` : 'Supabase'}</title>
|
||||
<meta name="description" content="Supabase Studio" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<div
|
||||
className="flex"
|
||||
style={{
|
||||
height: ongoingIncident
|
||||
? `calc(100vh - 44px - ${appHeaderHeight}px)`
|
||||
: `calc(100vh - ${appHeaderHeight}px)`,
|
||||
}}
|
||||
>
|
||||
{!hideHeader && <LayoutHeader />}
|
||||
{showPausedState ? (
|
||||
<div className="mx-auto my-16 w-full h-full max-w-7xl flex items-center">
|
||||
<div className="w-full">
|
||||
<ProjectPausedState product={product} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
{/* Left-most navigation side bar to access products */}
|
||||
{!hideIconBar && <NavigationBar />}
|
||||
|
||||
{/* Product menu bar */}
|
||||
{productMenu && !showPausedState && (
|
||||
<ProductMenuBar title={product}>{productMenu}</ProductMenuBar>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</ProjectContextProvider>
|
||||
|
||||
<main className="flex w-full flex-1 flex-col overflow-x-hidden">
|
||||
{!hideHeader && <LayoutHeader />}
|
||||
{showPausedState ? (
|
||||
<div className="mx-auto my-16 w-full h-full max-w-7xl flex items-center">
|
||||
<div className="w-full">
|
||||
<ProjectPausedState product={product} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</ProjectContextProvider>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import Link from 'next/link'
|
||||
import { Button, IconPlus } from 'ui'
|
||||
|
||||
import ProjectCard from 'components/interfaces/Home/ProjectList/ProjectCard'
|
||||
import ShimmeringCard from 'components/interfaces/Home/ProjectList/ShimmeringCard'
|
||||
import AppLayout from 'components/layouts/AppLayout/AppLayout'
|
||||
@@ -5,9 +8,7 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useSelectedOrganization } from 'hooks'
|
||||
import Link from 'next/link'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { Button, IconPlus } from 'ui'
|
||||
|
||||
const ProjectsPage: NextPageWithLayout = () => {
|
||||
const {
|
||||
@@ -27,31 +28,33 @@ const ProjectsPage: NextPageWithLayout = () => {
|
||||
return (
|
||||
<ScaffoldContainer>
|
||||
<ScaffoldSection>
|
||||
<div className="col-span-12 space-y-4">
|
||||
<div className="flex items-center space-x-6">
|
||||
<div className="col-span-12 space-y-8">
|
||||
<Link href={`/new/${organization?.slug}`}>
|
||||
<a>
|
||||
<Button size="medium" type="default" iconRight={<IconPlus />}>
|
||||
New project
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-lg">Projects</h4>
|
||||
<Link href={`/new/${organization?.slug}`}>
|
||||
<a>
|
||||
<Button icon={<IconPlus />}>New project</Button>
|
||||
</a>
|
||||
</Link>
|
||||
{isLoadingProjects && (
|
||||
<ul className="mx-auto grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
||||
<ShimmeringCard />
|
||||
<ShimmeringCard />
|
||||
</ul>
|
||||
)}
|
||||
{isErrorProjects && (
|
||||
<AlertError error={projectsError} subject="Failed to retrieve projects" />
|
||||
)}
|
||||
{isSuccessProjects && (
|
||||
<ul className="mx-auto grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{projects?.map((project) => (
|
||||
<ProjectCard key={project.ref} project={project} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
{isLoadingProjects && (
|
||||
<ul className="mx-auto grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
||||
<ShimmeringCard />
|
||||
<ShimmeringCard />
|
||||
</ul>
|
||||
)}
|
||||
{isErrorProjects && (
|
||||
<AlertError error={projectsError} subject="Failed to retrieve projects" />
|
||||
)}
|
||||
{isSuccessProjects && (
|
||||
<ul className="mx-auto grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
|
||||
{projects?.map((project) => (
|
||||
<ProjectCard key={project.ref} project={project} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</ScaffoldSection>
|
||||
</ScaffoldContainer>
|
||||
|
||||
Reference in new issue
Block a user