feat: docs global top nav (#26917)

Docs global top nav
This commit is contained in:
Francesco Sansalvadore authored and GitHub committed 2024-07-04 11:51:13 +02:00
1 parent 4c6f0dd8b6
commit 0d657df42e
35 files changed
+1127 -544

No files matched your search

+1 -1
View File
@@ -43,7 +43,7 @@ const getMenuId = (pathname: string | null) => {
case pathname.startsWith('storage'):
return MenuId.Storage
default:
return MenuId.Home
return MenuId.GettingStarted
}
}
+1 -2
View File
@@ -1,6 +1,5 @@
import { type Metadata } from 'next'
import { type PropsWithChildren } from 'react'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import { LayoutMainContent } from '~/layouts/DefaultLayout'
import { MainSkeleton } from '~/layouts/MainSkeleton'
@@ -12,7 +11,7 @@ const metadata: Metadata = {
}
const NotFoundLayout = ({ children }: PropsWithChildren) => (
<MainSkeleton menuId={MenuId.Home}>
<MainSkeleton>
<LayoutMainContent>{children}</LayoutMainContent>
</MainSkeleton>
)
+2 -2
View File
@@ -3,7 +3,7 @@ import Link from 'next/link'
import { IconBackground, TextLink } from 'ui'
import { IconPanel } from 'ui-patterns/IconPanel'
import HomeMenuIconPicker from '~/components/Navigation/NavigationMenu/HomeMenuIconPicker'
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
import { GlassPanelWithIconPicker } from '~/features/ui/GlassPanelWithIconPicker'
import { IconPanelWithIconPicker } from '~/features/ui/IconPanelWithIconPicker'
import HomeLayout from '~/layouts/HomeLayout'
@@ -305,7 +305,7 @@ const HomePage = () => (
<div className="md:max-w-xs 2xl:max-w-none">
<div className="flex items-center gap-3 mb-3 text-brand-600">
<IconBackground>
<HomeMenuIconPicker icon="self-hosting" width={18} height={18} />
<MenuIconPicker icon="self-hosting" width={18} height={18} />
</IconBackground>
<h3 id="self-hosting" className="group scroll-mt-24">
Self-Hosting
@@ -58,7 +58,7 @@ export default function Extensions() {
</div>
<div className="lg:grid lg:grid-cols-12">
<div className="col-span-3 not-prose">
<div className="lg:sticky top-24">
<div className="lg:sticky top-32">
<h3 className="text-sm text-foreground-light">Filter</h3>
<ul className="mt-3 flex flex-wrap lg:grid gap-2 grow">
{tags.sort().map((tag) => (
@@ -32,7 +32,11 @@ type MenuItem = {
community?: boolean
}
export type HomepageMenuItems = MenuItem[][]
export type DropdownMenuItem = MenuItem & {
menuItems?: MenuItem[][]
}
export type GlobalMenuItems = DropdownMenuItem[][]
export type NavMenuConstant = Readonly<{
title: string
@@ -0,0 +1,198 @@
import React, { Dispatch, Fragment, SetStateAction, useEffect } from 'react'
import { m, AnimatePresence, LazyMotion, domAnimation } from 'framer-motion'
import Image from 'next/image'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { useKey } from 'react-use'
import { useIsLoggedIn, useIsUserLoading } from 'common'
import { Accordion, Button, cn, IconX } from 'ui'
import { ThemeToggle } from 'ui-patterns'
import { MenuItem, useActiveMenuLabel } from './GlobalNavigationMenu'
import { GLOBAL_MENU_ITEMS } from './NavigationMenu.constants'
import type { DropdownMenuItem } from '../Navigation.types'
const DEFAULT_EASE = [0.24, 0.25, 0.05, 1]
const container = {
hidden: { opacity: 0 },
show: { opacity: 1, transition: { duration: 0.15, staggerChildren: 0.05, ease: DEFAULT_EASE } },
exit: { opacity: 0, transition: { duration: 0.15 } },
}
const listItem = {
hidden: { opacity: 0, y: 10 },
show: { opacity: 1, y: 0, transition: { duration: 0.25, ease: DEFAULT_EASE } },
exit: { opacity: 0, transition: { duration: 0.05 } },
}
const itemClassName =
'block py-2 pl-2 pr-3.5 text-sm text-foreground-light hover:bg-surface-200 focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-foreground-lighter focus-visible:rounded'
const AccordionMenuItem = ({ section }: { section: DropdownMenuItem[] }) => {
const activeLabel = useActiveMenuLabel(GLOBAL_MENU_ITEMS)
return (
<m.div
variants={listItem}
className="border-b border-muted [&>div]:!rounded-none [&_div[data-state=open]>div]:py-1"
key={section[0].label}
>
{section[0].menuItems ? (
<Accordion.Item
header={section[0].label}
id={section[0].label}
className={cn(
'relative',
activeLabel === section[0].label && '!text-foreground',
itemClassName
)}
>
{section[0].menuItems?.map((menuItem, menuItemIndex) => (
<Fragment key={`desktop-docs-menu-section-${menuItemIndex}`}>
{menuItem.map((item) =>
!item.href ? (
<div className="font-mono tracking-wider flex items-center text-foreground-muted text-xs uppercase rounded-md p-2 leading-none">
{item.label}
</div>
) : (
<MenuItem
href={item.href}
title={item.label}
community={item.community}
icon={item.icon}
/>
)
)}
</Fragment>
))}
</Accordion.Item>
) : (
<Link
href={section[0].href}
className={cn(activeLabel === section[0].label && '!text-foreground', itemClassName)}
>
{section[0].label}
</Link>
)}
</m.div>
)
}
const Menu = () => (
<Accordion
type="default"
openBehaviour="multiple"
size="small"
className="space-y-1 mt-2.5"
justified
chevronAlign="right"
>
{GLOBAL_MENU_ITEMS.map((section) => (
<AccordionMenuItem section={section} />
))}
</Accordion>
)
interface Props {
open: boolean
setOpen: Dispatch<SetStateAction<boolean>>
}
const GlobalMobileMenu = ({ open, setOpen }: Props) => {
const isLoggedIn = useIsLoggedIn()
const isUserLoading = useIsUserLoading()
const pathname = usePathname()
// Close mobile menu on route change
useEffect(() => {
setOpen(false)
}, [pathname])
useKey('Escape', () => setOpen(false))
return (
<LazyMotion features={domAnimation}>
<AnimatePresence mode="wait">
{open && (
<m.div
variants={container}
initial="hidden"
animate="show"
exit="exit"
className="bg-overlay fixed overflow-hidden inset-0 z-50 h-screen max-h-screen w-screen supports-[height:100cqh]:h-[100cqh] supports-[height:100svh]:h-[100svh] transform"
>
<div className="absolute px-5 h-[var(--header-height)] flex items-center justify-between w-screen left-0 top-0 z-50 bg-overlay before:content[''] before:absolute before:w-full before:h-3 before:inset-0 before:top-full before:bg-gradient-to-b before:from-background-overlay before:to-transparent">
<Link href="/" className="flex items-center gap-2">
<Image
className="cursor-pointer hidden dark:block"
src="/docs/supabase-dark.svg"
priority
width={96}
height={24}
alt="Supabase Logo"
/>
<Image
className="cursor-pointer block dark:hidden"
src="/docs/supabase-light.svg"
priority
width={96}
height={24}
alt="Supabase Logo"
/>
<span className="font-mono text-sm font-medium text-brand-link mb-px">DOCS</span>
</Link>
<div className="flex gap-4 items-center">
<ThemeToggle contentClassName="bg-surface-200" />
<button
onClick={() => setOpen(false)}
type="button"
className="inline-flex items-center justify-center focus:ring-brand bg-surface-100 hover:bg-surface-200 focus:outline-none focus:ring-2 focus:ring-inset border border-default bg-surface-100/75 text-foreground-light rounded min-w-[30px] w-[30px] h-[30px]"
>
<span className="sr-only">Close menu</span>
<IconX />
</button>
</div>
</div>
<div className="max-h-screen supports-[height:100cqh]:h-[100cqh] supports-[height:100svh]:h-[100svh] overflow-y-auto pt-12 pb-32 px-3">
<Menu />
</div>
<div className="absolute bottom-0 left-0 right-0 top-auto w-full bg-alternative flex items-stretch p-4 gap-4">
{!isUserLoading && (
<>
{isLoggedIn ? (
<Button block size="medium" asChild>
<Link href="/dashboard/projects">Dashboard</Link>
</Button>
) : (
<>
<Button block size="medium" type="default" asChild>
<Link href="https://supabase.com/dashboard/sign-in">Sign in</Link>
</Button>
<Button block size="medium" asChild>
<Link href="https://supabase.com/dashboard/new">Start your project</Link>
</Button>
</>
)}
</>
)}
</div>
</m.div>
)}
</AnimatePresence>
<AnimatePresence mode="wait">
{open && (
<m.div
variants={container}
initial="hidden"
animate="show"
exit="exit"
className="bg-alternative fixed overflow-hidden inset-0 z-40 h-screen w-screen transform"
/>
)}
</AnimatePresence>
</LazyMotion>
)
}
export default GlobalMobileMenu
@@ -0,0 +1,183 @@
'use client'
import React, { FC, Fragment, useEffect, useState } from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import {
Badge,
cn,
MenubarSeparator,
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from 'ui'
import MenuIconPicker from './MenuIconPicker'
import { GLOBAL_MENU_ITEMS } from './NavigationMenu.constants'
/**
* Get TopNav active label based on current pathname
*/
export const useActiveMenuLabel = (GLOBAL_MENU_ITEMS) => {
const pathname = usePathname()
const [activeLabel, setActiveLabel] = useState('')
useEffect(() => {
// check if homepage
if (pathname === '/') {
return setActiveLabel('Home')
}
for (let index = 0; index < GLOBAL_MENU_ITEMS.length; index++) {
const section = GLOBAL_MENU_ITEMS[index]
// check if first level menu items match beginning of url
if (section[0].href?.startsWith(pathname)) {
return setActiveLabel(section[0].label)
}
// check if second level menu items match beginning of url
if (section[0].menuItems) {
section[0].menuItems.map((menuItemGroup) =>
menuItemGroup.map(
(menuItem) => menuItem.href?.startsWith(pathname) && setActiveLabel(section[0].label)
)
)
}
}
}, [pathname])
return activeLabel
}
const GlobalNavigationMenu: FC = () => {
const activeLabel = useActiveMenuLabel(GLOBAL_MENU_ITEMS)
const triggerClassName =
'h-[var(--header-height)] p-2 bg-transparent border-0 border-b-2 border-transparent font-normal rounded-none text-foreground-light hover:text-foreground data-[state=open]:!text-foreground data-[radix-collection-item]:focus-visible:ring-2 data-[radix-collection-item]:focus-visible:ring-foreground-lighter data-[radix-collection-item]:focus-visible:text-foreground h-full focus-visible:rounded !shadow-none outline-none transition-all outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-brand-600'
return (
<div className="flex relative gap-2 justify-start items-end w-full h-full">
<NavigationMenu
delayDuration={0}
skipDelayDuration={0}
className="w-full flex justify-start h-full"
renderViewport={false}
viewportClassName="mt-0 max-w-screen overflow-hidden border-0 rounded-none mt-1.5 rounded-md !border-x"
>
<NavigationMenuList className="px-6 space-x-2 h-[var(--header-height)]">
{GLOBAL_MENU_ITEMS.map((section, sectionIdx) =>
section[0].menuItems ? (
<NavigationMenuItem
key={`desktop-docs-menu-section-${section[0].label}-${sectionIdx}`}
className="text-sm relative h-full"
>
<NavigationMenuTrigger
className={cn(
navigationMenuTriggerStyle(),
triggerClassName,
activeLabel === section[0].label && 'text-foreground border-foreground'
)}
>
{section[0].label === 'Home' ? (
<MenuIconPicker icon={section[0].icon} />
) : (
section[0].label
)}
</NavigationMenuTrigger>
<NavigationMenuContent className="!top-[calc(100%+4px)] min-w-[14rem] max-h-[calc(100vh-4rem)] border-y w-screen md:w-64 overflow-hidden overflow-y-auto rounded-none md:rounded-md md:border border-overlay bg-overlay text-foreground-light shadow-md !duration-0">
<div className="p-3 md:p-1">
{section[0].menuItems?.map((menuItem, menuItemIndex) => (
<Fragment key={`desktop-docs-menu-section-${menuItemIndex}-${menuItemIndex}`}>
{menuItemIndex !== 0 && <MenubarSeparator className="bg-border-muted" />}
{menuItem.map((item, itemIdx) =>
!item.href ? (
<div
key={`desktop-docs-menu-section-label-${item.label}-${itemIdx}`}
className="font-mono tracking-wider flex items-center text-foreground-muted text-xs uppercase rounded-md p-2 leading-none"
>
{item.label}
</div>
) : (
<NavigationMenuLink
key={`desktop-docs-menu-section-label-${item.label}-${itemIdx}`}
asChild
>
<MenuItem
href={item.href}
title={item.label}
community={item.community}
icon={item.icon}
/>
</NavigationMenuLink>
)
)}
</Fragment>
))}
</div>
</NavigationMenuContent>
</NavigationMenuItem>
) : (
<NavigationMenuItem
key={`desktop-docs-menu-section-${section[0].label}-${sectionIdx}`}
className="text-sm relative h-full"
>
<NavigationMenuLink asChild>
<Link
href={section[0].href}
className={cn(
navigationMenuTriggerStyle(),
triggerClassName,
activeLabel === section[0].label && 'text-foreground border-foreground'
)}
>
{section[0].label === 'Home' ? (
<MenuIconPicker icon={section[0].icon} />
) : (
section[0].label
)}
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
)
)}
</NavigationMenuList>
</NavigationMenu>
</div>
)
}
export const MenuItem = React.forwardRef<
React.ElementRef<'a'>,
React.ComponentPropsWithoutRef<'a'> & {
icon?: string
community?: boolean
}
>(({ className, title, href = '', icon, community, children, ...props }, ref) => {
return (
<Link
href={href}
ref={ref}
className={cn(
'group/menu-item flex items-center gap-2',
'w-full flex h-8 items-center text-foreground-light text-sm hover:text-foreground select-none rounded-md p-2 leading-none no-underline !outline-none focus-visible:ring-2 focus-visible:ring-foreground-lighter focus-visible:text-foreground',
className
)}
{...props}
>
{children ?? (
<>
{icon && <MenuIconPicker icon={icon} className="text-foreground-lighter" />}
<span className="flex-1">{title}</span>
{community && <Badge size="small">Community</Badge>}
</>
)}
</Link>
)
})
GlobalNavigationMenu.displayName = 'GlobalNavigationMenu'
MenuItem.displayName = 'MenuItem'
export default GlobalNavigationMenu
@@ -1,59 +0,0 @@
import Link from 'next/link'
import { Fragment } from 'react'
import { Badge, cn } from 'ui'
import { HOMEPAGE_MENU_ITEMS } from './NavigationMenu.constants'
import HomeMenuIconPicker from './HomeMenuIconPicker'
const NavigationMenuHome = () => {
return (
<div className="transition-all duration-150 ease-out opacity-100 ml-0 delay-150">
<ul className="relative w-full flex flex-col gap-4 pb-5">
{HOMEPAGE_MENU_ITEMS.map((section, sectionIndex) => {
return (
<Fragment key={`section-container-${sectionIndex}-border`}>
{sectionIndex !== 0 && (
<div className="h-px w-full border-b" key={`section-${sectionIndex}-border`}></div>
)}
<div key={`section-${sectionIndex}`}>
<div className="flex flex-col gap-4">
{section.map((link, i) => {
if (!link.href) {
return (
<h2
key={link.label}
className={cn(
'font-mono uppercase text-xs text-foreground-lighter',
i !== 0 && 'mt-4'
)}
>
{link.label}
</h2>
)
} else {
return (
<Link href={link.href} passHref key={link.label}>
<li
className={[
'group flex items-center gap-2',
'text-sm transition-all duration-150 text-foreground-light hover:text-foreground hover:cursor-pointer ',
].join(' ')}
>
{link?.icon && <HomeMenuIconPicker icon={link.icon} />}
{link.label}
{link.community && <Badge size="small">Community</Badge>}
</li>
</Link>
)
}
})}
</div>
</div>
</Fragment>
)
})}
</ul>
</div>
)
}
export default NavigationMenuHome
@@ -26,12 +26,19 @@ import {
IconMenuKotlin,
IconMenuAI,
IconMenuDevCli,
} from './HomeMenuIcons'
IconGitHub,
IconSupport,
IconTerraform,
IconTroubleshooting,
IconBranching,
} from './MenuIcons'
function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, className?: string) {
switch (menuKey) {
case 'home':
return <IconMenuHome width={width} height={height} className={className} />
case 'branching':
return <IconBranching width={width} height={height} className={className} />
case 'getting-started':
return <IconMenuGettingStarted width={width} height={height} className={className} />
case 'database':
@@ -78,23 +85,27 @@ function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, c
return <IconMenuCli width={width} height={height} className={className} />
case 'status':
return <IconMenuStatus width={width} height={height} className={className} />
case 'github':
return <IconGitHub width={width} height={height} className={className} />
case 'support':
return <IconSupport width={width} height={height} className={className} />
default:
return <IconMenuPlatform width={width} height={height} className={className} />
}
}
type HomeMenuIconPickerProps = {
type MenuIconPickerProps = {
icon: string
width?: number
height?: number
className?: string
}
export default function HomeMenuIconPicker({
export default function MenuIconPicker({
icon,
width = 16,
height = 16,
className,
}: HomeMenuIconPickerProps) {
}: MenuIconPickerProps) {
return getMenuIcon(icon, width, height, className)
}
@@ -519,3 +519,119 @@ export function IconMenuStatus({ width = 16, height = 16, className }: HomeMenuI
</svg>
)
}
export function IconBranching({ width = 16, height = 16, className }: HomeMenuIcon) {
return (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.00018 2.69751C7.56432 2.69751 7.13983 2.74376 6.73114 2.83144C6.46114 2.88936 6.19531 2.71744 6.13738 2.44744C6.07946 2.17744 6.25138 1.91161 6.52138 1.85368C6.99858 1.75131 7.49338 1.69751 8.00018 1.69751C8.50698 1.69751 9.00178 1.75131 9.47898 1.85368C9.74898 1.91161 9.9209 2.17744 9.86297 2.44744C9.80505 2.71744 9.53921 2.88936 9.26921 2.83144C8.86053 2.74376 8.43603 2.69751 8.00018 2.69751Z"
fill="currentColor"
/>
<path
d="M1.73138 6.85327C2.00138 6.91119 2.1733 7.17703 2.11537 7.44703C2.02769 7.85571 1.98145 8.28021 1.98145 8.71606C1.98145 9.15192 2.02769 9.57642 2.11537 9.9851C2.1733 10.2551 2.00138 10.5209 1.73138 10.5789C1.46138 10.6368 1.19555 10.4649 1.13762 10.1949C1.03524 9.71767 0.981445 9.22287 0.981445 8.71606C0.981445 8.20926 1.03524 7.71446 1.13762 7.23726C1.19555 6.96726 1.46138 6.79534 1.73138 6.85327Z"
fill="currentColor"
/>
<path
d="M14.2686 6.85327C14.5386 6.79534 14.8045 6.96726 14.8624 7.23726C14.9648 7.71446 15.0186 8.20926 15.0186 8.71607C15.0186 9.22287 14.9648 9.71766 14.8624 10.1949C14.8045 10.4649 14.5386 10.6368 14.2686 10.5789C13.9986 10.5209 13.8267 10.2551 13.8846 9.9851C13.9723 9.57641 14.0186 9.15192 14.0186 8.71607C14.0186 8.28021 13.9723 7.85571 13.8846 7.44703C13.8267 7.17703 13.9986 6.91119 14.2686 6.85327Z"
fill="currentColor"
/>
<path
d="M6.13738 14.9847C6.19531 14.7147 6.46114 14.5428 6.73114 14.6007C7.13983 14.6884 7.56432 14.7346 8.00018 14.7346C8.43603 14.7346 8.86053 14.6884 9.26922 14.6007C9.53922 14.5428 9.80505 14.7147 9.86297 14.9847C9.9209 15.2547 9.74898 15.5205 9.47898 15.5784C9.00178 15.6808 8.50698 15.7346 8.00018 15.7346C7.49337 15.7346 6.99858 15.6808 6.52137 15.5784C6.25138 15.5205 6.07946 15.2547 6.13738 14.9847Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.8048 2.46822C12.4795 1.79347 13.5735 1.79347 14.2483 2.46822C14.923 3.14298 14.923 4.23697 14.2483 4.91173C13.6764 5.48362 12.8033 5.5708 12.1396 5.17328L10.6936 6.61929C11.1446 7.19789 11.4133 7.9256 11.4133 8.71606C11.4133 9.47805 11.1636 10.1817 10.7416 10.7497L12.2103 12.2184C12.8642 11.8681 13.6961 11.9687 14.2478 12.5204C14.9225 13.1951 14.9225 14.2891 14.2478 14.9639C13.573 15.6386 12.479 15.6386 11.8043 14.9639C11.2523 14.4119 11.1519 13.5794 11.503 12.9253L10.0346 11.457C9.46646 11.8794 8.76241 12.1294 7.99997 12.1294C7.20951 12.1294 6.4818 11.8607 5.90321 11.4097L4.4574 12.8555C4.85471 13.5192 4.76747 14.3921 4.19566 14.9639C3.52091 15.6386 2.42691 15.6386 1.75216 14.9639C1.0774 14.2891 1.07741 13.1951 1.75216 12.5204C2.28359 11.9889 3.07507 11.876 3.71698 12.1817L5.21234 10.6864C4.81821 10.1298 4.5866 9.44996 4.5866 8.71605C4.5866 7.95361 4.83658 7.24956 5.25904 6.68138L3.79073 5.21307C3.13666 5.56417 2.30413 5.46373 1.75216 4.91175C1.07741 4.237 1.07741 3.143 1.75216 2.46825C2.42692 1.79349 3.52091 1.79349 4.19566 2.46825C4.74732 3.01991 4.84796 3.8518 4.49758 4.5057L5.96628 5.9744C6.53429 5.55238 7.23797 5.30269 7.99997 5.30269C8.73387 5.30269 9.41367 5.5343 9.97027 5.92842L11.466 4.43272C11.1605 3.79087 11.2734 2.99956 11.8048 2.46822ZM13.5412 3.17533C13.2569 2.8911 12.7961 2.8911 12.5119 3.17533C12.2277 3.45956 12.2277 3.92039 12.5119 4.20462C12.7961 4.48885 13.2569 4.48885 13.5412 4.20462C13.8254 3.92039 13.8254 3.45956 13.5412 3.17533ZM9.67618 10.4523C9.24205 10.8715 8.65112 11.1294 7.99997 11.1294C6.6671 11.1294 5.5866 10.0489 5.5866 8.71605C5.5866 7.38319 6.6671 6.30269 7.99997 6.30269C9.33283 6.30269 10.4133 7.38319 10.4133 8.71606C10.4133 9.36685 10.1557 9.95747 9.73695 10.3915C9.72611 10.4007 9.71556 10.4104 9.70535 10.4206C9.69509 10.4308 9.68536 10.4414 9.67618 10.4523ZM3.48856 3.17536C3.20433 2.89113 2.7435 2.89113 2.45927 3.17536C2.17504 3.45959 2.17504 3.92042 2.45927 4.20465C2.7435 4.48888 3.20433 4.48888 3.48856 4.20465C3.77279 3.92042 3.77279 3.45959 3.48856 3.17536ZM3.48856 13.2275C3.20433 12.9432 2.7435 12.9432 2.45927 13.2275C2.17504 13.5117 2.17504 13.9725 2.45927 14.2568C2.7435 14.541 3.20433 14.541 3.48856 14.2568C3.77279 13.9725 3.77279 13.5117 3.48856 13.2275ZM13.5407 13.2275C13.2564 12.9432 12.7956 12.9432 12.5114 13.2275C12.2271 13.5117 12.2271 13.9725 12.5114 14.2568C12.7956 14.541 13.2564 14.541 13.5407 14.2568C13.8249 13.9725 13.8249 13.5117 13.5407 13.2275Z"
fill="currentColor"
/>
</svg>
)
}
export function IconGitHub({ width = 16, height = 16, className }: HomeMenuIcon) {
return (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.00011 1.8714C4.96998 1.8714 1.70312 5.14337 1.70312 9.18079C1.70312 12.4097 3.79371 15.1497 6.69353 16.1158C7.05838 16.183 7.19119 15.9575 7.19119 15.7634C7.19119 15.5905 7.18535 15.13 7.1817 14.5207C5.15168 14.9622 4.72335 13.5407 4.72335 13.5407C4.39206 12.6957 3.91338 12.471 3.91338 12.471C3.25082 12.0186 3.96373 12.0273 3.96373 12.0273C4.69562 12.0784 5.0809 12.7804 5.0809 12.7804C5.73179 13.8968 6.78912 13.5743 7.20432 13.3875C7.27145 12.9154 7.45972 12.5936 7.66841 12.4112C6.04848 12.2265 4.34463 11.599 4.34463 8.79843C4.34463 8.00087 4.62922 7.34779 5.09549 6.837C5.02033 6.65238 4.77005 5.90882 5.167 4.9033C5.167 4.9033 5.77995 4.70628 7.17367 5.65197C7.76894 5.48962 8.3831 5.40693 9.00011 5.40606C9.62035 5.40898 10.2442 5.48997 10.8273 5.65197C12.2203 4.70628 12.8318 4.90257 12.8318 4.90257C13.2302 5.90882 12.9792 6.65238 12.9047 6.837C13.3717 7.34779 13.6549 8.00087 13.6549 8.79843C13.6549 11.6063 11.9481 12.2244 10.3231 12.4053C10.585 12.6308 10.8178 13.0766 10.8178 13.7589C10.8178 14.7353 10.809 15.5241 10.809 15.7634C10.809 15.959 10.9404 16.1866 11.3111 16.1151C12.7641 15.6278 14.0272 14.6961 14.922 13.4517C15.8167 12.2074 16.2977 10.7134 16.2971 9.18079C16.2971 5.14337 13.0295 1.8714 9.00011 1.8714Z"
fill="currentColor"
/>
</svg>
)
}
export function IconSupport({ width = 16, height = 16, className }: HomeMenuIcon) {
return (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.15298 4.15407L7.05972 7.06081M10.9401 10.9404L13.8469 13.8471M13.8469 4.15407L10.9401 7.06081L13.3601 4.64069M4.15298 13.8471L7.05972 10.9404M15.8555 8.99998C15.8555 12.7861 12.7862 15.8555 9.00004 15.8555C5.21384 15.8555 2.14453 12.7861 2.14453 8.99998C2.14453 5.21379 5.21384 2.14447 9.00004 2.14447C12.7862 2.14447 15.8555 5.21379 15.8555 8.99998ZM11.7422 8.99998C11.7422 10.5145 10.5145 11.7422 9.00004 11.7422C7.48556 11.7422 6.25783 10.5145 6.25783 8.99998C6.25783 7.4855 7.48556 6.25778 9.00004 6.25778C10.5145 6.25778 11.7422 7.4855 11.7422 8.99998Z"
stroke="currentColor"
strokeMiterlimit="10"
strokeLinejoin="bevel"
/>
</svg>
)
}
export function IconTerraform({ width = 16, height = 16, className }: HomeMenuIcon) {
return (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M10.5 6.22713V10.9596L14.6 8.59463V3.85713L10.5 6.22713Z" fill="currentColor" />
<path d="M5.95 3.85713L10.05 6.22713V10.9596L5.95 8.59213V3.85713Z" fill="currentColor" />
<path
d="M1.4 1.21463V5.94963L5.5 8.31713V3.58213L1.4 1.21463ZM5.95 13.8471L10.05 16.2146V11.4796L5.95 9.11213V13.8471Z"
fill="currentColor"
/>
</svg>
)
}
export function IconTroubleshooting({ width = 16, height = 16, className }: HomeMenuIcon) {
return (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.0326 6.65586L11.679 6.30231L12.0326 6.65586ZM13.9812 4.70721L14.4367 4.50108C14.3691 4.35154 14.2323 4.24473 14.0708 4.21531C13.9094 4.18589 13.7437 4.2376 13.6277 4.35366L13.9812 4.70721ZM8.76103 9.9274L8.96716 9.47187L8.65205 9.32928L8.40747 9.57385L8.76103 9.9274ZM4.21801 14.4704L3.86446 14.1169L4.21801 14.4704ZM2.24565 12.4981L1.89209 12.1445H1.89209L2.24565 12.4981ZM6.78866 7.95504L7.14221 8.30859L7.38679 8.06402L7.24419 7.7489L6.78866 7.95504ZM7.5932 3.53939L7.23965 3.18584L7.5932 3.53939ZM9.67545 2.4479L9.76508 2.9398L9.67545 2.4479ZM12.0089 2.73485L12.3624 3.0884C12.4785 2.97234 12.5302 2.8067 12.5008 2.64522C12.4713 2.48374 12.3645 2.34699 12.215 2.27932L12.0089 2.73485ZM10.0602 4.6835L9.70665 4.32994L10.0602 4.6835ZM10.0671 6.66267L9.71597 7.01867L10.0671 6.66267ZM12.3861 7.00941L14.3348 5.06077L13.6277 4.35366L11.679 6.30231L12.3861 7.00941ZM13.5257 4.91335C13.8144 5.55131 13.9018 6.2621 13.7763 6.95099L14.7601 7.13024C14.922 6.24136 14.8092 5.32423 14.4367 4.50108L13.5257 4.91335ZM13.7763 6.95099C13.6507 7.63988 13.3183 8.27417 12.8231 8.76931L13.5302 9.47642C14.1691 8.83754 14.5981 8.01912 14.7601 7.13024L13.7763 6.95099ZM12.8231 8.76931C12.328 9.26445 11.6937 9.59694 11.0048 9.72245L11.1841 10.7063C12.0729 10.5443 12.8913 10.1153 13.5302 9.47642L12.8231 8.76931ZM11.0048 9.72245C10.3159 9.84797 9.60512 9.76056 8.96716 9.47187L8.55489 10.3829C9.37805 10.7554 10.2952 10.8682 11.1841 10.7063L11.0048 9.72245ZM8.40747 9.57385L3.86446 14.1169L4.57157 14.824L9.11458 10.281L8.40747 9.57385ZM3.86446 14.1169C3.69668 14.2846 3.46911 14.3789 3.23183 14.3789V15.3789C3.73433 15.3789 4.21625 15.1793 4.57157 14.824L3.86446 14.1169ZM3.23183 14.3789C2.99455 14.3789 2.76699 14.2846 2.5992 14.1169L1.89209 14.824C2.24741 15.1793 2.72933 15.3789 3.23183 15.3789V14.3789ZM2.5992 14.1169C2.43142 13.9491 2.33716 13.7215 2.33716 13.4842H1.33716C1.33716 13.9867 1.53677 14.4686 1.89209 14.824L2.5992 14.1169ZM2.33716 13.4842C2.33716 13.247 2.43142 13.0194 2.5992 12.8516L1.89209 12.1445C1.53678 12.4998 1.33716 12.9817 1.33716 13.4842H2.33716ZM2.5992 12.8516L7.14221 8.30859L6.43511 7.60149L1.89209 12.1445L2.5992 12.8516ZM7.24419 7.7489C6.9555 7.11095 6.86809 6.40016 6.99361 5.71126L6.00981 5.53201C5.84785 6.42089 5.96064 7.33802 6.33313 8.16118L7.24419 7.7489ZM6.99361 5.71126C7.11913 5.02237 7.45161 4.38808 7.94676 3.89294L7.23965 3.18584C6.60077 3.82472 6.17176 4.64313 6.00981 5.53201L6.99361 5.71126ZM7.94676 3.89294C8.4419 3.3978 9.07618 3.06532 9.76508 2.9398L9.58582 1.956C8.69694 2.11795 7.87853 2.54696 7.23965 3.18584L7.94676 3.89294ZM9.76508 2.9398C10.454 2.81428 11.1648 2.90169 11.8027 3.19038L12.215 2.27932C11.3918 1.90682 10.4747 1.79404 9.58582 1.956L9.76508 2.9398ZM11.6553 2.38129L9.70665 4.32994L10.4138 5.03705L12.3624 3.0884L11.6553 2.38129ZM11.679 6.30231C11.3313 6.64999 10.7682 6.65194 10.4182 6.30668L9.71597 7.01867C10.4573 7.74984 11.6498 7.74571 12.3861 7.00941L11.679 6.30231ZM10.4182 6.30668C10.0646 5.95801 10.0627 5.38815 10.4138 5.03705L9.70665 4.32994C8.96311 5.07349 8.96729 6.28029 9.71597 7.01867L10.4182 6.30668Z"
fill="currentColor"
/>
</svg>
)
}
@@ -1,15 +1,9 @@
import type { HomepageMenuItems, NavMenuConstant, References } from '../Navigation.types'
import type { GlobalMenuItems, NavMenuConstant, References } from '../Navigation.types'
export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
[
{
label: 'Home',
icon: 'home',
href: '/',
level: 'home',
},
{
label: 'Getting Started',
label: 'Start',
icon: 'getting-started',
href: '/guides/getting-started',
level: 'gettingstarted',
@@ -17,164 +11,188 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
],
[
{
label: 'Product',
},
{
label: 'Database',
icon: 'database',
href: '/guides/database/overview',
level: 'database',
},
{
label: 'Auth',
icon: 'auth',
href: '/guides/auth',
level: 'auth',
},
{
label: 'Storage',
icon: 'storage',
href: '/guides/storage',
level: 'storage',
},
{
label: 'Edge Functions',
icon: 'edge-functions',
href: '/guides/functions',
level: 'functions',
},
{
label: 'Realtime',
icon: 'realtime',
href: '/guides/realtime',
level: 'realtime',
},
{
label: 'AI & Vectors',
icon: 'ai',
href: '/guides/ai',
level: 'ai',
label: 'Products',
menuItems: [
[
{
label: 'Database',
icon: 'database',
href: '/guides/database/overview',
level: 'database',
},
{
label: 'Auth',
icon: 'auth',
href: '/guides/auth',
level: 'auth',
},
{
label: 'Storage',
icon: 'storage',
href: '/guides/storage',
level: 'storage',
},
{
label: 'Edge Functions',
icon: 'edge-functions',
href: '/guides/functions',
level: 'functions',
},
{
label: 'Realtime',
icon: 'realtime',
href: '/guides/realtime',
level: 'realtime',
},
{
label: 'AI & Vectors',
icon: 'ai',
href: '/guides/ai',
level: 'ai',
},
],
],
},
],
[
{
label: 'Data API',
},
{
label: 'REST',
icon: 'rest',
href: '/guides/api',
level: 'api',
},
{
label: 'GraphQL',
icon: 'graphql',
href: '/guides/graphql',
level: 'graphql',
label: 'Build',
menuItems: [
[
{
label: 'Local Dev / CLI',
icon: 'dev-cli',
href: '/guides/cli',
level: 'reference_javascript',
},
{
label: 'Platform',
icon: 'platform',
href: '/guides/platform',
level: 'platform',
},
{
label: 'Self-Hosting',
icon: 'self-hosting',
href: '/guides/self-hosting',
level: 'self_hosting',
},
{
label: 'Integrations',
icon: 'integrations',
hasLightIcon: true,
href: 'https://supabase.com/partners/integrations',
level: 'integrations',
},
],
],
},
],
[
{
label: 'Development Cycle',
},
{
label: 'Local Dev / CLI',
icon: 'dev-cli',
href: '/guides/cli',
level: 'reference_javascript',
},
{
label: 'Platform',
icon: 'platform',
href: '/guides/platform',
level: 'platform',
},
{
label: 'Self-Hosting',
icon: 'self-hosting',
href: '/guides/self-hosting',
level: 'self_hosting',
},
],
[
{
label: 'Client Library Reference',
},
{
label: 'JavaScript',
icon: 'reference-javascript',
href: '/reference/javascript/introduction',
level: 'reference_javascript',
},
{
label: 'Flutter',
icon: 'reference-dart',
href: '/reference/dart/introduction',
level: 'reference_dart',
},
{
label: 'Swift',
icon: 'reference-swift',
href: '/reference/swift/introduction',
level: 'reference_swift',
},
{
label: 'Python',
icon: 'reference-python',
href: '/reference/python/introduction',
level: 'reference_python',
community: true,
},
{
label: 'C#',
icon: 'reference-csharp',
href: '/reference/csharp/introduction',
level: 'reference_csharp',
community: true,
},
{
label: 'Kotlin',
icon: 'reference-kotlin',
href: '/reference/kotlin/introduction',
level: 'reference_kotlin',
community: true,
label: 'Reference',
menuItems: [
[
{
label: 'Client Library Reference',
},
{
label: 'JavaScript',
icon: 'reference-javascript',
href: '/reference/javascript/introduction',
level: 'reference_javascript',
},
{
label: 'Flutter',
icon: 'reference-dart',
href: '/reference/dart/introduction',
level: 'reference_dart',
},
{
label: 'Swift',
icon: 'reference-swift',
href: '/reference/swift/introduction',
level: 'reference_swift',
},
{
label: 'Python',
icon: 'reference-python',
href: '/reference/python/introduction',
level: 'reference_python',
community: true,
},
{
label: 'C#',
icon: 'reference-csharp',
href: '/reference/csharp/introduction',
level: 'reference_csharp',
community: true,
},
{
label: 'Kotlin',
icon: 'reference-kotlin',
href: '/reference/kotlin/introduction',
level: 'reference_kotlin',
community: true,
},
],
[
{
label: 'CLI Commands',
icon: 'reference-cli',
href: '/reference/cli/introduction',
level: 'reference_javascript',
},
{
label: 'Management API',
icon: 'reference-api',
href: '/reference/api/introduction',
level: 'reference_javascript',
},
],
[
{ label: 'Data API' },
{
label: 'REST',
icon: 'rest',
href: '/guides/api',
level: 'api',
},
{
label: 'GraphQL',
icon: 'graphql',
href: '/guides/graphql',
level: 'graphql',
},
],
],
},
],
[
{
label: 'Resources',
},
{
label: 'CLI Commands',
icon: 'reference-cli',
href: '/reference/cli/introduction',
level: 'reference_javascript',
},
{
label: 'Management API',
icon: 'reference-api',
href: '/reference/api/introduction',
level: 'reference_javascript',
},
{
label: 'Guides and Examples',
icon: 'resources',
href: '/guides/resources',
level: 'resources',
},
{
label: 'Integrations',
icon: 'integrations',
hasLightIcon: true,
href: 'https://supabase.com/partners/integrations',
level: 'integrations',
},
],
[
{
label: 'Status',
icon: 'status',
href: 'https://status.supabase.com/',
menuItems: [
[
{
label: 'Migration guides',
icon: 'resources',
href: '/guides/resources',
level: 'resources',
},
{
label: 'Changelog',
icon: 'changelog',
hasLightIcon: true,
href: 'https://supabase.com/changelog',
level: 'changelog',
},
{
label: 'Status',
icon: 'status',
href: 'https://status.supabase.com/',
},
],
],
},
],
]
@@ -226,7 +244,7 @@ export const REFERENCES: References = {
export const gettingstarted: NavMenuConstant = {
icon: 'getting-started',
title: 'Getting Started',
title: 'Start with Supabase',
items: [
{ name: 'Features', url: '/guides/getting-started/features' },
{ name: 'Architecture', url: '/guides/getting-started/architecture' },
@@ -1,10 +1,9 @@
import { memo } from 'react'
import NavigationMenuHome from './HomeMenu'
import NavigationMenuGuideList from './NavigationMenuGuideList'
import NavigationMenuRefList from './NavigationMenuRefList'
import { useCloseMenuOnRouteChange } from './NavigationMenu.utils'
enum MenuId {
Home = 'home',
GettingStarted = 'gettingstarted',
Database = 'database',
Api = 'api',
@@ -44,10 +43,6 @@ interface BaseMenu {
type: string
}
interface HomeMenu extends BaseMenu {
type: 'home'
}
interface GuideMenu extends BaseMenu {
type: 'guide'
}
@@ -59,13 +54,9 @@ interface ReferenceMenu extends BaseMenu {
specFile?: string
}
type Menu = HomeMenu | GuideMenu | ReferenceMenu
type Menu = GuideMenu | ReferenceMenu
const menus: Menu[] = [
{
id: MenuId.Home,
type: 'home',
},
{
id: MenuId.GettingStarted,
type: 'guide',
@@ -244,7 +235,7 @@ const menus: Menu[] = [
]
function getMenuById(id: MenuId) {
return menus.find((menu) => menu.id === id) ?? menus.find((menu) => menu.id === MenuId.Home)
return menus.find((menu) => menu.id === id)
}
function getMenuElement(menu: Menu | undefined) {
@@ -252,8 +243,6 @@ function getMenuElement(menu: Menu | undefined) {
const menuType = menu.type
switch (menuType) {
case 'home':
return <NavigationMenuHome />
case 'guide':
return <NavigationMenuGuideList id={menu.id} />
case 'reference':
@@ -266,7 +255,7 @@ function getMenuElement(menu: Menu | undefined) {
/>
)
default:
throw new Error(`Unknown menu type '${menuType}'`)
return null
}
}
@@ -274,6 +263,8 @@ const NavigationMenu = ({ menuId }: { menuId: MenuId }) => {
const level = menuId
const menu = getMenuById(level)
useCloseMenuOnRouteChange()
return getMenuElement(menu)
}
@@ -1,8 +1,10 @@
'use client'
import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import { ICommonItem } from '~/components/reference/Reference.types'
import { Json } from '~/types'
import { menuState } from '../../../hooks/useMenuState'
export function getPathWithoutHash(relativePath: string) {
return new URL(relativePath, 'http://placeholder').pathname
@@ -95,3 +97,11 @@ export function useSpec(specFile?: string) {
return spec
}
export const useCloseMenuOnRouteChange = () => {
const pathname = usePathname()
useEffect(() => {
menuState.setMenuMobileOpen(false)
}, [pathname])
}
@@ -1,13 +1,11 @@
import * as Accordion from '@radix-ui/react-accordion'
import { ChevronLeft } from 'lucide-react'
import { usePathname } from 'next/navigation'
import { useTheme } from 'next-themes'
import Image from 'next/legacy/image'
import Link from 'next/link'
import React, { useEffect, useRef } from 'react'
import HomeMenuIconPicker from './HomeMenuIconPicker'
import { getPathWithoutHash } from './NavigationMenu.utils'
import MenuIconPicker from './MenuIconPicker'
const HeaderLink = React.memo(function HeaderLink(props: {
title: string
@@ -141,24 +139,9 @@ const Content = (props) => {
return (
<ul className={['relative w-full flex flex-col gap-0 pb-5'].join(' ')}>
<Link
href={`${menu.parent ?? '/'}`}
className={[
'flex items-center gap-1 text-xs group mb-3',
'text-base transition-all duration-200 text-brand-link hover:text-brand-600 hover:cursor-pointer ',
].join(' ')}
>
<div className="relative w-2">
<div className="transition-all ease-out ml-0 group-hover:-ml-1">
<ChevronLeft size={10} strokeWidth={3} />
</div>
</div>
<span>Back to Home</span>
</Link>
<Link href={menu.url ?? ''}>
<div className="flex items-center gap-3 my-3 text-brand-link">
<HomeMenuIconPicker icon={menu.icon} />
<MenuIconPicker icon={menu.icon} />
<HeaderLink title={menu.title} url={menu.url} id={id} />
</div>
</Link>
@@ -1,16 +1,15 @@
'use client'
import * as Accordion from '@radix-ui/react-accordion'
import { ChevronLeft, ChevronUp } from 'lucide-react'
import { ChevronUp } from 'lucide-react'
import Image from 'next/legacy/image'
import Link from 'next/link'
import { Fragment, memo } from 'react'
import { cn } from 'ui'
import RevVersionDropdown from '~/components/RefVersionDropdown'
import { ICommonItem, ICommonSection } from '~/components/reference/Reference.types'
import { menuState, useMenuActiveRefId } from '~/hooks/useMenuState'
import { BASE_PATH } from '~/lib/constants'
import HomeMenuIconPicker from './HomeMenuIconPicker'
import MenuIconPicker from './MenuIconPicker'
import * as NavItems from './NavigationMenu.constants'
import { deepFilterSections } from './NavigationMenu.utils'
@@ -60,7 +59,10 @@ const FunctionLink = memo(function FunctionLink({
onClick={(e) => {
e.preventDefault()
history.pushState({}, '', url)
document.getElementById(slug)?.scrollIntoView()
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
document.getElementById(slug)?.scrollIntoView({
behavior: reduceMotion ? 'auto' : 'smooth',
})
onClick()
}}
className={cn(
@@ -180,22 +182,8 @@ const NavigationMenuRefListItems = ({
return (
<div className={'w-full flex flex-col gap-0 sticky top-8'}>
<Link
href="/"
className={[
'flex items-center gap-1 text-xs group mb-3',
'text-base transition-all duration-200 text-foreground-light hover:text-brand-600 hover:cursor-pointer',
].join(' ')}
>
<div className="relative w-2">
<div className="transition-all ease-out ml-0 group-hover:-ml-1">
<ChevronLeft size={10} strokeWidth={3} />
</div>
</div>
<span>Back to Main Menu</span>
</Link>
<div className="flex items-center gap-3 my-3">
<HomeMenuIconPicker icon={menu.icon} width={21} height={21} />
<MenuIconPicker icon={menu.icon} width={21} height={21} />
<HeaderLink title={menu.title} url={menu.url} id={id} />
<RevVersionDropdown />
</div>
@@ -1,108 +1,151 @@
import { useTheme } from 'next-themes'
import Image from 'next/legacy/image'
import { FC, memo, useState } from 'react'
import Link from 'next/link'
import { FC } from 'react'
import { Button, IconCommand, IconGitHub, IconSearch } from 'ui'
import { SearchButton } from 'ui-patterns/Cmdk'
import dynamic from 'next/dynamic'
import { useIsLoggedIn, useIsUserLoading } from 'common'
import { ThemeToggle } from 'ui-patterns/ThemeToggle'
import { SearchButton } from 'ui-patterns/Cmdk'
import { Button, buttonVariants, cn } from 'ui'
import { Command, Search, Menu } from 'lucide-react'
import Image from 'next/image'
import GlobalNavigationMenu from './GlobalNavigationMenu'
const GlobalMobileMenu = dynamic(() => import('./GlobalMobileMenu'))
const TopNavDropdown = dynamic(() => import('./TopNavDropdown'))
const TopNavBar: FC = () => {
const isLoggedIn = useIsLoggedIn()
const isUserLoading = useIsUserLoading()
const { resolvedTheme } = useTheme()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<nav
aria-label="top bar"
className="h-[var(--header-height,60px)] border-b backdrop-blur backdrop-filter bg bg-opacity-75"
>
<div className="px-5 max-w-7xl mx-auto flex gap-3 justify-between items-center h-full">
<div className="lg:hidden">
<Link href="/" className=" flex items-center gap-2">
<Image
className="cursor-pointer"
src={
resolvedTheme?.includes('dark')
? '/docs/supabase-dark.svg'
: '/docs/supabase-light.svg'
}
width={96}
height={24}
alt="Supabase Logo"
/>
<span className="font-mono text-sm font-medium text-brand-link">DOCS</span>
</Link>
</div>
<div className="flex items-center gap-6">
<SearchButton className="md:w-full lg:w-96 order-2 lg:order-1">
<div
className="
flex
group
items-center
justify-between
bg-surface-100
bg-opacity-75
hover:bg-surface-200
hover:bg-opacity-100
border
transition
pl-1.5 md:pl-3 pr-1.5 w-full h-[32px] rounded
text-foreground-lighter
"
>
<div className="flex items-center space-x-2">
<IconSearch size={18} strokeWidth={2} />
<p className="hidden md:flex text-sm">Search docs...</p>
</div>
<div className="hidden md:flex items-center space-x-1">
<div
aria-hidden="true"
className="md:flex items-center justify-center h-5 w-10 border rounded bg-surface-300 gap-1"
>
<IconCommand size={12} strokeWidth={1.5} />
<span className="text-[12px]">K</span>
</div>
</div>
<>
<nav
aria-label="top bar"
className="w-full z-40 flex flex-col border-b backdrop-blur backdrop-filter bg bg-opacity-75"
>
<div className="w-full px-5 lg:pl-10 flex justify-between h-[var(--header-height)] gap-3">
<div className="hidden lg:flex h-full items-center justify-center gap-2">
<HeaderLogo />
<GlobalNavigationMenu />
</div>
<div className="w-full grow lg:w-auto max-w-7xl flex gap-3 justify-between lg:justify-end items-center h-full">
<div className="lg:hidden">
<HeaderLogo />
</div>
</SearchButton>
<div className="flex gap-2 items-center">
<SearchButton
className="flex-grow md:w-44 xl:w-56 h-[30px]
focus-visible:!outline-4
focus-visible:outline-offset-1
focus-visible:outline-brand-600"
>
<div
className="
flex
group
items-center
justify-between
bg-surface-100/75
hover:bg-opacity-100
hover:border-strong
border
transition
pl-1.5 md:pl-2 pr-1 w-full h-full rounded-md
text-foreground-lighter
"
>
<div className="flex items-center space-x-2 text-foreground-muted">
<Search size={18} strokeWidth={2} />
<p className="flex text-sm pr-2">
Search<span className="hidden xl:inline ml-1"> docs...</span>
</p>
</div>
<div className="hidden md:flex items-center space-x-1">
<div
aria-hidden="true"
className="md:flex items-center justify-center h-full px-1 border rounded bg-surface-300 gap-0.5"
>
<Command size={12} strokeWidth={1.5} />
<span className="text-[12px]">K</span>
</div>
</div>
</div>
</SearchButton>
<button
title="Menu dropdown button"
className={cn(
buttonVariants({ type: 'default' }),
'flex lg:hidden border-default bg-surface-100/75 text-foreground-light rounded-md min-w-[30px] w-[30px] h-[30px] data-[state=open]:bg-overlay-hover/30'
)}
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<Menu size={18} strokeWidth={1} />
</button>
</div>
</div>
<div className="hidden lg:flex items-center justify-end gap-3">
{!isUserLoading && (
<Button asChild>
<a
href="https://supabase.com/dashboard"
className="h-[30px]"
target="_blank"
rel="noreferrer noopener"
>
{isLoggedIn ? 'Dashboard' : 'Sign up'}
</a>
</Button>
)}
{process.env.NEXT_PUBLIC_DEV_AUTH_PAGE === 'true' && (
<Button asChild>
<Link href="/__dev-secret-auth">Dev-only secret sign-in</Link>
</Button>
)}
<TopNavDropdown />
</div>
</div>
<div className="hidden lg:flex grow items-center justify-end gap-3">
<Button type="text" asChild>
<a href="https://supabase.com" target="_blank" rel="noreferrer noopener">
Supabase.com
</a>
</Button>
{!isUserLoading && (
<Button asChild>
<a href="https://supabase.com/dashboard" target="_blank" rel="noreferrer noopener">
{isLoggedIn ? 'Dashboard' : 'Sign up'}
</a>
</Button>
)}
{process.env.NEXT_PUBLIC_DEV_AUTH_PAGE === 'true' && (
<Button asChild>
<Link href="/__dev-secret-auth">Dev-only secret sign-in</Link>
</Button>
)}
<Link
href="https://github.com/supabase/supabase"
target="_blank"
rel="noreferrer noopener"
className="px-2.5 py-1"
>
<span className="sr-only">GitHub</span>
<IconGitHub
size={16}
className="text-foreground-light hover:text-foreground transition"
/>
</Link>
<ThemeToggle />
</div>
</div>
</nav>
</nav>
<GlobalMobileMenu open={mobileMenuOpen} setOpen={setMobileMenuOpen} />
</>
)
}
const HeaderLogo = memo(() => {
return (
<Link
href="/"
className={cn(
buttonVariants({ type: 'default' }),
'flex shrink-0 items-center w-fit !bg-transparent !border-none !shadow-none'
)}
>
<Image
className="hidden dark:block !m-0"
src="/docs/supabase-dark.svg"
priority={true}
loading="eager"
width={96}
height={18}
alt="Supabase wordmark"
/>
<Image
className="block dark:hidden !m-0"
src="/docs/supabase-light.svg"
priority={true}
loading="eager"
width={96}
height={18}
alt="Supabase wordmark"
/>
<span className="font-mono text-sm font-medium text-brand-link mb-px">DOCS</span>
</Link>
)
})
HeaderLogo.displayName = 'HeaderLogo'
TopNavBar.displayName = 'TopNavBar'
export default TopNavBar
@@ -0,0 +1,116 @@
'use client'
import React from 'react'
import Link from 'next/link'
import { useTheme } from 'next-themes'
import { Menu } from 'lucide-react'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Theme,
buttonVariants,
cn,
themes,
} from 'ui'
import MenuIconPicker from './MenuIconPicker'
const menu = [
[
{
label: 'Supabase.com',
icon: 'home',
href: 'https://supabase.com',
otherProps: {
target: '_blank',
rel: 'noreferrer noopener',
},
},
{
label: 'GitHub',
icon: 'github',
href: 'https://github.com/supabase/supabase',
otherProps: {
target: '_blank',
rel: 'noreferrer noopener',
},
},
{
label: 'Support',
icon: 'support',
href: 'https://supabase.com/support',
otherProps: {
target: '_blank',
rel: 'noreferrer noopener',
},
},
],
]
const TopNavDropdown = () => {
const { theme, setTheme } = useTheme()
return (
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild className="flex">
<button
title="Menu dropdown button"
className={cn(
buttonVariants({ type: 'default' }),
'text-foreground-light border-default w-[30px] min-w-[30px] h-[30px] data-[state=open]:bg-overlay-hover/30 hover:border-strong data-[state=open]:border-stronger hover:!bg-overlay-hover/50 bg-transparent'
)}
>
<Menu size={18} strokeWidth={1} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className="w-64">
{menu.map((menuSection, sectionIdx) => (
<>
{sectionIdx !== 0 && <DropdownMenuSeparator key={`topnav--${sectionIdx}`} />}
{menuSection.map((sectionItem, itemIdx) => (
<Link
key={`topnav-${sectionItem.label}-${sectionIdx}-${itemIdx}`}
href={sectionItem.href}
{...sectionItem.otherProps}
>
<DropdownMenuItem className="space-x-2" onClick={() => {}}>
{sectionItem.icon && (
<MenuIconPicker icon={sectionItem.icon} className="text-foreground-lighter" />
)}
<p>{sectionItem.label}</p>
</DropdownMenuItem>
</Link>
))}
</>
))}
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Theme</DropdownMenuLabel>
<DropdownMenuRadioGroup
value={theme}
onValueChange={(value) => {
setTheme(value)
}}
>
{themes
.filter((x) => x.value === 'light' || x.value === 'dark' || x.value === 'system')
.map((theme: Theme) => (
<DropdownMenuRadioItem key={`topnav-theme-${theme.value}`} value={theme.value}>
{theme.name}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}
export default TopNavDropdown
+1 -1
View File
@@ -43,7 +43,7 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
return (
<div
className="bg-background border-muted sidebar-width sticky top-16
className="bg-background border-muted sidebar-width sticky top-44
h-screen overflow-y-scroll border-r py-8 px-6 sidebar-menu-container hidden lg:block"
>
{isInReferencePages && (
+1 -1
View File
@@ -50,7 +50,7 @@ const Option: FC<IOption> = (props) => {
flex flex-col gap-3
"
>
<div className="flex gap-3 items-center">
<div className="flex flex-wrap gap-3 items-center">
<span className="text-sm text-foreground font-mono font-medium">
{props.name ?? 'no-name'}
</span>
+1 -1
View File
@@ -8,7 +8,7 @@ type IParamProps = any
const Param = ({ name, isOptional, type, description, children, isPrimitive }: IParamProps) => {
return (
<div className="border-t border-b py-5 flex flex-col gap-3 debugger">
<div className="flex gap-3 items-center">
<div className="flex gap-3 items-center flex-wrap">
{!isPrimitive && (
<span className="text-sm text-foreground font-mono font-medium">{name ?? 'no-name'}</span>
)}
+1 -1
View File
@@ -69,7 +69,7 @@ import {
IconMenuSelfHosting,
IconMenuStorage,
IconMenuSwift,
} from './Navigation/NavigationMenu/HomeMenuIcons'
} from './Navigation/NavigationMenu/MenuIcons'
// Heavy/rare (lazy-loaded)
import SqlToRest from '@ui-patterns/SqlToRest'
@@ -80,7 +80,7 @@ const CliCommandSection = (props) => {
<ReactMarkdown>{command.description}</ReactMarkdown>
</div>
) : (
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-foreground-light text-base">
<p className="capitalize mb-4 scroll-mt-24 mt-0 text-foreground-light text-base">
{command.summary}
</p>
)}
+1 -3
View File
@@ -1,5 +1,4 @@
import { CommandMenuProvider } from '@ui-patterns/Cmdk'
import PromoToast from '@ui-patterns/PromoToast'
import { ThemeProvider } from 'common'
import { PortalToast } from 'ui'
import { type PropsWithChildren } from 'react'
@@ -24,10 +23,9 @@ function GlobalProviders({ children }: PropsWithChildren) {
<ScrollRestoration />
<ThemeProvider defaultTheme="system" enableSystem disableTransitionOnChange>
<CommandMenuProvider site="docs">
<div className="h-screen flex flex-col">
<div className="flex flex-col">
<SiteLayout>
<PortalToast />
<PromoToast />
{children}
</SiteLayout>
<ThemeSandbox />
@@ -159,13 +159,15 @@ const GuideTemplate = ({ meta, content, children, editLink, mdxOptions }: GuideT
className={cn(
'hidden md:block',
'col-span-3 self-start',
'sticky top-[calc(var(--header-height)+2rem)]',
'sticky',
/**
* [TODO: Charis]
*
* Figure out what these magic numbers represent
* --header-height: height of nav
* 1px: height of nav border
* 4rem: content padding
*/
'max-h-[calc(100vh-60px-5rem)]'
'top-[calc(var(--header-height)+1px+4rem)]',
// 5rem accounts for 4rem of top padding + 1rem of extra breathing room
'max-h-[calc(100vh-var(--header-height)-5rem)]'
)}
/>
)}
@@ -1,7 +1,7 @@
import { type ComponentProps, type PropsWithChildren } from 'react'
import { GlassPanel } from 'ui-patterns/GlassPanel'
import HomeMenuIconPicker from '~/components/Navigation/NavigationMenu/HomeMenuIconPicker'
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
type GlassPanelWithIconPickerProps = PropsWithChildren<
Omit<ComponentProps<typeof GlassPanel>, 'icon'> & { icon: string }
@@ -9,7 +9,7 @@ type GlassPanelWithIconPickerProps = PropsWithChildren<
function GlassPanelWithIconPicker({ children, icon, ...props }: GlassPanelWithIconPickerProps) {
return (
<GlassPanel icon={<HomeMenuIconPicker icon={icon} width={18} height={18} />} {...props}>
<GlassPanel icon={<MenuIconPicker icon={icon} width={18} height={18} />} {...props}>
{children}
</GlassPanel>
)
@@ -1,14 +1,14 @@
import { type ComponentProps } from 'react'
import { IconPanel } from 'ui-patterns'
import HomeMenuIconPicker from '~/components/Navigation/NavigationMenu/HomeMenuIconPicker'
import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker'
type IconPanelWithIconPickerProps = Omit<ComponentProps<typeof IconPanel>, 'icon'> & {
icon: string
}
function IconPanelWithIconPicker({ icon, ...props }: IconPanelWithIconPickerProps) {
return <IconPanel icon={<HomeMenuIconPicker icon={icon} width={18} height={18} />} {...props} />
return <IconPanel icon={<MenuIconPicker icon={icon} width={18} height={18} />} {...props} />
}
export { IconPanelWithIconPicker }
+1 -2
View File
@@ -1,13 +1,12 @@
import { type PropsWithChildren } from 'react'
import HomePageCover from '~/components/HomePageCover'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import { LayoutMainContent } from './DefaultLayout'
import { MainSkeleton } from './MainSkeleton'
const HomeLayout = ({ children }: PropsWithChildren) => {
return (
<MainSkeleton menuId={MenuId.Home}>
<MainSkeleton>
<article>
<HomePageCover title="Supabase Documentation" />
<LayoutMainContent>
+103 -130
View File
@@ -1,17 +1,19 @@
'use client'
import { useTheme } from 'next-themes'
import Image from 'next/legacy/image'
import Link from 'next/link'
import { type PropsWithChildren, memo, useEffect } from 'react'
import { type PropsWithChildren, memo, useEffect, useRef } from 'react'
import dynamic from 'next/dynamic'
import { cn } from 'ui'
import Footer from '~/components/Navigation/Footer'
import HomeMenuIconPicker from '~/components/Navigation/NavigationMenu/HomeMenuIconPicker'
import NavigationMenu, { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar'
import { DOCS_CONTENT_CONTAINER_ID } from '~/features/ui/helpers.constants'
import { menuState, useMenuMobileOpen } from '~/hooks/useMenuState'
import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar'
const Footer = dynamic(() => import('~/components/Navigation/Footer'))
const NavigationMenu = dynamic(
() => import('~/components/Navigation/NavigationMenu/NavigationMenu')
)
const levelsData = {
home: {
icon: 'home',
@@ -158,60 +160,52 @@ const MobileHeader = memo(function MobileHeader({ menuId }: { menuId: MenuId })
return (
<div
className={cn(
'transition-all ease-out z-10',
'lg:hidden px-3.5 border-b z-10',
'transition-all ease-out',
'top-0',
mobileMenuOpen && 'absolute',
'flex items-center h-[var(--header-height,40px)]',
mobileMenuOpen ? 'gap-0' : 'gap-3'
'flex items-center',
mobileMenuOpen ? 'gap-0' : 'gap-1'
)}
>
<button
className={[
'h-8 w-8 flex group items-center justify-center mr-2',
mobileMenuOpen && 'mt-0.5',
].join(' ')}
className={cn(
'h-8 w-8 flex group items-center justify-center mr-1',
mobileMenuOpen && 'mt-0.5'
)}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
>
<div
className={[
className={cn(
'space-y-1 cursor-pointer relative',
mobileMenuOpen ? 'w-4 h-4' : 'w-4 h-[8px]',
].join(' ')}
mobileMenuOpen ? 'w-4 h-4' : 'w-4 h-[8px]'
)}
>
<span
className={[
className={cn(
'transition-all ease-out block w-4 h-px bg-foreground-muted group-hover:bg-foreground',
!mobileMenuOpen ? 'w-4' : 'absolute rotate-45 top-[6px]',
].join(' ')}
></span>
!mobileMenuOpen ? 'w-4' : 'absolute rotate-45 top-[6px]'
)}
/>
<span
className={[
className={cn(
'transition-all ease-out block h-px bg-foreground-muted group-hover:bg-foreground',
!mobileMenuOpen ? 'w-3 group-hover:w-4' : 'absolute w-4 -rotate-45 top-[2px]',
].join(' ')}
></span>
!mobileMenuOpen ? 'w-3 group-hover:w-4' : 'absolute w-4 -rotate-45 top-[2px]'
)}
/>
</div>
</button>
<div className={[].join(' ')}>
<HomeMenuIconPicker
icon={menuLevel ? levelsData[menuLevel].icon : 'home'}
className={[
'transition-all duration-200',
mobileMenuOpen ? 'invisible w-0 h-0' : 'w-4 h-4',
].join(' ')}
/>
</div>
<span
className={[
className={cn(
'transition-all duration-200',
'text-foreground',
mobileMenuOpen ? 'text-xs' : 'text-sm',
].join(' ')}
mobileMenuOpen ? 'text-xs' : 'text-sm'
)}
>
{mobileMenuOpen
? 'Close'
: menuLevel
? levelsData[menuLevel].name
? levelsData[menuLevel]?.name
: levelsData['home'].name}
</span>
</div>
@@ -235,7 +229,7 @@ const MobileMenuBackdrop = memo(function MobileMenuBackdrop() {
return (
<div
className={[
className={cn(
'h-full',
'left-0',
'right-0',
@@ -243,56 +237,30 @@ const MobileMenuBackdrop = memo(function MobileMenuBackdrop() {
'backdrop-blur-sm backdrop-filter bg-alternative/90',
mobileMenuOpen ? 'absolute h-full w-full top-0 left-0' : 'hidden h-0',
// always hide on desktop
'lg:hidden',
].join(' ')}
'lg:hidden'
)}
onClick={() => menuState.setMenuMobileOpen(!mobileMenuOpen)}
></div>
)
})
const HeaderLogo = memo(function HeaderLogo() {
const { resolvedTheme } = useTheme()
return (
<Link href="/" className="px-10 flex items-center gap-2">
<Image
className="cursor-pointer"
src={
resolvedTheme?.includes('dark') ? '/docs/supabase-dark.svg' : '/docs/supabase-light.svg'
}
width={96}
height={24}
alt="Supabase Logo"
/>
<span className="font-mono text-sm font-medium text-brand-link">DOCS</span>
</Link>
)
})
const Container = memo(function Container({
children,
className,
}: PropsWithChildren<{ className?: string }>) {
const mobileMenuOpen = useMenuMobileOpen()
return (
<div
<main
// used by layout to scroll to top
id={DOCS_CONTENT_CONTAINER_ID}
className={cn(
/**
* This controls scroll restoration on page navigation, so the overflow
* must be on the element with DOCS_CONTENT_CONTAINER_ID
*/
'overflow-auto',
'w-full transition-all ease-out',
mobileMenuOpen ? 'ml-[75%] sm:ml-[50%] md:ml-[33%] overflow-hidden' : 'overflow-auto',
'w-full transition-all ease-out relative',
// desktop override any margin styles
'lg:ml-0',
className
)}
>
{children}
</div>
<div className="flex flex-col sticky top-0">{children}</div>
</main>
)
})
@@ -302,58 +270,48 @@ const NavContainer = memo(function NavContainer({ menuId }: { menuId: MenuId })
return (
<nav
aria-labelledby="main-nav-title"
className={[
// 'hidden',
'absolute lg:relative',
mobileMenuOpen ? 'w-[75%] sm:w-[50%] md:w-[33%] left-0 z-20' : 'w-0 -left-[280px]',
className={cn(
'fixed lg:relative z-50 lg:z-40',
mobileMenuOpen ? 'w-[75%] sm:w-[50%] md:w-[33%] left-0' : 'w-0 -left-full',
'lg:w-[420px] !lg:left-0',
'lg:top-[var(--header-height)] lg:sticky',
'h-screen lg:h-[calc(100vh-var(--header-height))]',
// desktop override any left styles
'lg:left-0',
'transition-all',
'top-0',
'top-0 bottom-0',
'flex flex-col ml-0',
].join(' ')}
'border-r',
'lg:overflow-y-auto'
)}
>
<div
className={[
'top-0',
'relative',
'w-auto',
'border-r overflow-auto h-screen',
className={cn(
'top-0 lg:top-[var(--header-height)]',
'h-full',
'relative lg:sticky',
'w-full lg:w-auto',
'h-fit lg:h-screen overflow-y-scroll lg:overflow-auto',
'backdrop-blur backdrop-filter bg-background',
'flex flex-col',
].join(' ')}
'flex flex-col flex-grow'
)}
>
<h1 id="main-nav-title" className="sr-only">
<span id="main-nav-title" className="sr-only">
Main menu
</h1>
<div className="top-0 sticky z-10">
<div>
<div>
<div
className={[
'hidden lg:flex lg:height-auto',
'pt-8 bg-background flex-col gap-8',
].join(' ')}
>
<HeaderLogo />
</div>
<div className="h-4 bg-background w-full"></div>
<div className="bg-gradient-to-b from-background to-transparent h-4 w-full"></div>
</div>
</div>
</span>
<div className="top-0 sticky h-0 z-10">
<div className="bg-gradient-to-b from-background to-transparent h-4 w-full"></div>
</div>
<div
className={[
className={cn(
'transition-all ease-out duration-200',
'absolute left-0 right-0 h-screen',
'px-5 pl-5 py-16',
'top-[0px]',
'absolute left-0 right-0',
'px-5 pl-5 pt-6 pb-16 lg:pb-32',
'bg-background',
// desktop styles
'lg:relative lg:top-0 lg:left-0 lg:pb-10 lg:px-10 lg:pt-0 lg:flex',
'lg:opacity-100 lg:visible',
].join(' ')}
'lg:relative lg:left-0 lg:pb-10 lg:px-10 lg:flex',
'lg:opacity-100 lg:visible'
)}
>
<NavigationMenu menuId={menuId} />
</div>
@@ -362,29 +320,44 @@ const NavContainer = memo(function NavContainer({ menuId }: { menuId: MenuId })
)
})
function MainSkeleton({ children, menuId }: PropsWithChildren<{ menuId: MenuId }>) {
function MainSkeleton({ children, menuId }: PropsWithChildren<{ menuId?: MenuId }>) {
const ref = useRef(null)
const mobileMenuOpen = useMenuMobileOpen()
const hideSideNav = !menuId
return (
<div className="flex flex-row h-full">
<NavContainer menuId={menuId} />
<Container className="[--header-height:40px] lg:[--header-height:60px]">
<div className={['lg:sticky top-0 z-10 overflow-hidden'].join(' ')}>
<TopNavBar />
</div>
<div
className={[
'sticky transition-all top-0',
'z-10',
'backdrop-blur backdrop-filter bg-background',
].join(' ')}
>
<div className={['lg:hidden', 'px-3.5', 'border-b z-10'].join(' ')}>
<MobileHeader menuId={menuId} />
<div className="flex flex-col h-full w-full">
<div ref={ref} className="hidden lg:sticky w-full lg:flex top-0 left-0 right-0 z-50">
<TopNavBar />
</div>
<div className="flex flex-row h-full relative">
{!hideSideNav && <NavContainer menuId={menuId} />}
<Container>
<div
className={cn(
'flex lg:hidden w-full top-0 left-0 right-0 z-50',
hideSideNav && 'sticky',
mobileMenuOpen && 'z-10'
)}
>
<TopNavBar />
</div>
</div>
{children}
<Footer />
<MobileMenuBackdrop />
</Container>
<div
className={cn(
'sticky',
'transition-all top-0 z-10',
'backdrop-blur backdrop-filter bg-background'
)}
>
{!hideSideNav && <MobileHeader menuId={menuId} />}
</div>
<div className="grow">
{children}
<Footer />
</div>
<MobileMenuBackdrop />
</Container>
</div>
</div>
)
}
+1 -3
View File
@@ -1,7 +1,5 @@
import { PropsWithChildren } from 'react'
const SiteLayout = ({ children }: PropsWithChildren<{}>) => {
return <main className="grow overflow-hidden">{children}</main>
}
const SiteLayout = ({ children }: PropsWithChildren<{}>) => <div className="grow">{children}</div>
export default SiteLayout
+8 -7
View File
@@ -5,6 +5,7 @@ import { useRouter } from 'next/compat/router'
import { useNavigationMenuContext } from '~/components/Navigation/NavigationMenu/NavigationMenu.Context'
import { menuState } from '~/hooks/useMenuState'
import Image from 'next/legacy/image'
import { cn } from 'ui'
interface ISectionContainer {
id: string
@@ -66,7 +67,7 @@ const Section: FC<PropsWithChildren<ISectionContainer>> = (props) => {
key={props.id + 'section'}
className={[
props.singleColumn ? 'prose w-full' : 'w-full',
'py-16 lg:py-32 first:pt-8 last:pb-8',
'py-16 first:pt-8 last:pb-8',
].join(' ')}
>
<StickyHeader {...props} />
@@ -120,11 +121,11 @@ const StickyHeader: FC<StickyHeader> = ({ icon, ...props }) => {
ref={ref}
id={props.slug}
data-ref-id={props.id}
className={[
'text-2xl font-medium text-foreground scroll-mt-24',
className={cn(
'text-2xl font-medium text-foreground scroll-mt-[calc(33px+2rem)] lg:scroll-mt-[calc(var(--header-height)+1px+4rem)]',
!icon && 'mb-8',
props.monoFont && 'font-mono',
].join(' ')}
props.monoFont && 'font-mono'
)}
>
{props.title && <span className="max-w-xl">{props.title}</span>}
</h2>
@@ -148,7 +149,7 @@ const Examples: FC<PropsWithChildren<ISectionExamples>> = (props) => {
*/
return (
<div className="w-full min-w-full">
<div className="sticky top-24">{props.children}</div>
<div className="sticky top-32">{props.children}</div>
</div>
)
}
@@ -169,7 +170,7 @@ const EducationSection: FC<PropsWithChildren<IEducationSection>> = ({
return (
<article
key={props.id + 'education'}
className={'prose max-w-none py-16 lg:py-32 first:pt-8 last:pb-8'}
className={'prose max-w-none py-16 first:pb-8 first:pt-0 last:pb-8'}
>
{!hideTitle && <StickyHeader {...props} icon={icon} />}
{props.children}
@@ -37,10 +37,7 @@ const RefSubLayoutNonFunc: FC<PropsWithChildren<RefSubLayoutNonFuncType>> &
const Section: FC<PropsWithChildren<ISectionContainer>> = (props) => {
return (
<article
key={props.id}
className={`${props.singleColumn ? 'prose py-16 lg:py-32 ' : 'py-16 lg:py-32'}`}
>
<article key={props.id} className={`${props.singleColumn ? 'prose py-16' : 'py-16'}`}>
<StickyHeader {...props} />
<div
className={`ref-container gap-16 ${
@@ -99,7 +96,7 @@ interface ISectionExamples {}
const Examples: FC<PropsWithChildren<ISectionExamples>> = (props) => {
return (
<div className="w-full">
<div className="sticky top-24">{props.children}</div>
<div className="sticky top-32">{props.children}</div>
</div>
)
}
@@ -1,7 +1,7 @@
import { readdir } from 'node:fs/promises'
import { extname, join } from 'node:path'
import {
HOMEPAGE_MENU_ITEMS,
GLOBAL_MENU_ITEMS,
gettingstarted,
cli,
auth,
@@ -60,9 +60,9 @@ const main = async () => {
.filter((file) => extname(file) === '.mdx')
.map((file) => file.replace(/\.mdx$/, ''))
const flattenedHomepageMenuItems = HOMEPAGE_MENU_ITEMS.flat() as RefItem[]
const flattenedGlobalMenuItems = GLOBAL_MENU_ITEMS.flat() as RefItem[]
const pagesToPublish = [
flattenedHomepageMenuItems,
flattenedGlobalMenuItems,
gettingstarted.items,
cli.items,
auth.items,
+5
View File
@@ -29,6 +29,7 @@
:root {
--sidebar-width: 300px;
--header-height: 50px;
}
body {
@@ -267,6 +268,10 @@ article p code {
height: unset !important;
}
#docs-content-container #introduction {
@apply scroll-mt-60;
}
/* dat.GUI */
.dg.ac {
+13 -3
View File
@@ -12,14 +12,21 @@ import {
IconMoon,
IconSun,
Theme,
cn,
themes,
} from 'ui'
interface ThemeToggleProps {
forceDark?: boolean
triggerClassName?: string
contentClassName?: string
}
export const ThemeToggle = ({ forceDark = false }: ThemeToggleProps) => {
export const ThemeToggle = ({
forceDark = false,
triggerClassName,
contentClassName,
}: ThemeToggleProps) => {
const { theme, setTheme } = useTheme()
const [open, setOpen] = useState(false)
@@ -31,14 +38,17 @@ export const ThemeToggle = ({ forceDark = false }: ThemeToggleProps) => {
<DropdownMenuTrigger asChild disabled={forceDark}>
<button
id="user-settings-dropdown"
className="flex items-center justify-center h-7 w-7 text-foreground"
className={cn(
'flex items-center justify-center h-7 w-7 text-foreground-light',
triggerClassName
)}
>
<IconSun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<IconMoon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-60">
<DropdownMenuContent align="end" className={cn('w-60', contentClassName)}>
<DropdownMenuGroup>
<DropdownMenuRadioGroup
value={currentTheme} // Use the currentTheme variable here
@@ -6,7 +6,6 @@ import { ChevronDown } from 'lucide-react'
import * as React from 'react'
import { cn } from '../../../lib/utils/cn'
import { buttonVariants } from './../../Button/Button'
interface NavigationMenuProps
extends React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root> {