Add AppLayout and simplify height logic in layout component

This commit is contained in:
Joshen Lim committed 2023-07-17 22:31:00 +08:00
1 parent 0672fe0464
commit 14e8c19c96
8 files changed
+181 -144

No files matched your search

@@ -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>
)
}
+28 -25
View File
@@ -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>