diff --git a/apps/docs/features/docs/Reference.apiPage.tsx b/apps/docs/features/docs/Reference.apiPage.tsx index e8344cb3a85..e1deb6ae802 100644 --- a/apps/docs/features/docs/Reference.apiPage.tsx +++ b/apps/docs/features/docs/Reference.apiPage.tsx @@ -27,21 +27,22 @@ async function ApiOperationPage({ operationSlug }: { operationSlug: string }) { const section = sectionsBySlug?.get(operationSlug) if (!section) notFound() + const navigationProps = { + libraryId: 'api', + name: 'Management API', + menuData: reference_api, + libPath: 'api', + version: 'latest', + isLatestVersion: true, + realNavigation: true, + } + return ( - } + NavigationMenu={} + rightRail={} >
diff --git a/apps/docs/features/docs/Reference.cliPage.tsx b/apps/docs/features/docs/Reference.cliPage.tsx index 9d549b5d858..aca9c394c23 100644 --- a/apps/docs/features/docs/Reference.cliPage.tsx +++ b/apps/docs/features/docs/Reference.cliPage.tsx @@ -8,20 +8,21 @@ import { LayoutMainContent } from '~/layouts/DefaultLayout' import { SidebarSkeleton } from '~/layouts/MainSkeleton' export async function CliReferencePage() { + const navigationProps = { + libraryId: 'cli', + name: 'Supabase CLI', + menuData: reference_cli, + libPath: 'cli', + version: 'latest', + isLatestVersion: true, + } + return ( - } + NavigationMenu={} + rightRail={} >
diff --git a/apps/docs/features/docs/Reference.navigation.client.tsx b/apps/docs/features/docs/Reference.navigation.client.tsx index 18dd75f8d65..9a418fefa9a 100644 --- a/apps/docs/features/docs/Reference.navigation.client.tsx +++ b/apps/docs/features/docs/Reference.navigation.client.tsx @@ -7,6 +7,7 @@ import { } from '~/components/Navigation/NavSection' import type { AbbrevApiReferenceSection } from '~/features/docs/Reference.utils' import { isElementInViewport } from '~/features/ui/helpers.dom' +import { HeadingSlotCrumb } from '~/layouts/HeadingSlot' import { BASE_PATH } from '~/lib/constants' import { debounce } from 'lodash-es' import Link from 'next/link' @@ -25,6 +26,19 @@ import { } from 'react' import { cn } from 'ui' +export interface ReferenceCrumbHeading { + url: string + title: string +} + +interface ReferenceActiveCrumbProps { + basePath: string + headings: ReferenceCrumbHeading[] +} + +export const ACTIVE_BAR_CLASS_NAME = + 'aria-[current=page]:before:absolute aria-[current=page]:before:-left-[13px] aria-[current=page]:before:top-1/2 aria-[current=page]:before:h-[1em] aria-[current=page]:before:w-px aria-[current=page]:before:-translate-y-1/2 aria-[current=page]:before:bg-brand' + export const ReferenceContentInitiallyScrolledContext = createContext(false) let patchCount = 0 @@ -126,38 +140,27 @@ export function ReferenceContentScrollHandler({ export function ReferenceNavigationScrollHandler({ children, ...rest -}: PropsWithChildren & HTMLAttributes) { - const parentRef = useRef(null) - const ref = useRef(null) +}: PropsWithChildren & HTMLAttributes) { + const ref = useRef(null) const initialScrollHappened = useContext(ReferenceContentInitiallyScrolledContext) - useEffect(() => { - if (!ref.current) return + const scrollActiveIntoView = useCallback(() => { + if (!ref.current?.offsetParent) return + + const currentLink = ref.current.querySelector('[aria-current=page]') + if (!currentLink || isElementInViewport(currentLink)) return let scrollingParent: HTMLElement = ref.current - - while (scrollingParent && !(scrollingParent.scrollHeight > scrollingParent.clientHeight)) { - const parent = scrollingParent.parentElement - if (!parent) break - scrollingParent = parent + while (scrollingParent.scrollHeight <= scrollingParent.clientHeight) { + if (!scrollingParent.parentElement) return + scrollingParent = scrollingParent.parentElement } - parentRef.current = scrollingParent - }, []) - - const scrollActiveIntoView = useCallback(() => { - const currentLink = ref.current?.querySelector('[aria-current=page]') as HTMLElement - if (currentLink && !isElementInViewport(currentLink)) { - // Calculate the offset of the current link relative to scrollingParent - // and scroll the parent to the top of the link. - const offsetTop = currentLink.offsetTop - const parentOffsetTop = parentRef.current?.offsetTop ?? 0 - const scrollPosition = offsetTop - parentOffsetTop - - parentRef.current?.scrollTo({ - top: scrollPosition - 60 /* space for header + padding */, - }) - } + const linkOffset = + currentLink.getBoundingClientRect().top - scrollingParent.getBoundingClientRect().top + scrollingParent.scrollTo({ + top: scrollingParent.scrollTop + linkOffset - 60 /* space for header + padding */, + }) }, []) useEffect(() => { @@ -174,9 +177,9 @@ export function ReferenceNavigationScrollHandler({ }, [scrollActiveIntoView]) return ( -
+
+ ) } @@ -184,12 +187,19 @@ function deriveHref(basePath: string, section: AbbrevApiReferenceSection) { return 'slug' in section ? `${basePath}/${section.slug}` : '' } -function getLinkStyles(isActive: boolean, className?: string) { +export function ReferenceActiveCrumb({ basePath, headings }: ReferenceActiveCrumbProps) { + const pathname = useCurrentPathname() + const activePath = pathname === basePath ? `${basePath}/introduction` : pathname + const index = headings.findIndex((heading) => heading.url === activePath) + + return +} + +function getLinkStyles(className?: string) { return cn( - 'text-sm text-foreground-lighter', - !isActive && 'hover:text-foreground', - isActive && 'text-primary', - 'transition-colors', + 'relative block py-1 text-sm text-foreground-lighter wrap-anywhere', + 'transition-colors duration-150 hover:text-foreground-light', + 'aria-[current=page]:text-foreground', className ) } @@ -264,9 +274,8 @@ export function RefLink({ useEffect(() => { if (ref.current) { ref.current.ariaCurrent = isActive ? 'page' : null - ref.current.className = getLinkStyles(isActive, className) } - }, [isActive, className]) + }, [isActive]) const onClick = useCallback( (evt: MouseEvent) => { @@ -292,7 +301,7 @@ export function RefLink({ // pages omit the prop and keep Next.js's default prefetch behavior. {...(!realNavigation ? { prefetch: false } : {})} href={href} - className={getLinkStyles(isActive, className)} + className={getLinkStyles(className)} onClick={onClick} > {section.title} @@ -353,7 +362,7 @@ function CompoundRefLink({ 'flex items-center justify-between gap-2' )} > - {section.title} + {section.title} @@ -366,7 +375,7 @@ function CompoundRefLink({ diff --git a/apps/docs/features/docs/Reference.navigation.tsx b/apps/docs/features/docs/Reference.navigation.tsx index 393624245c3..08dd10c29fe 100644 --- a/apps/docs/features/docs/Reference.navigation.tsx +++ b/apps/docs/features/docs/Reference.navigation.tsx @@ -1,16 +1,15 @@ -import { isFeatureEnabled } from 'common' -import { type PropsWithChildren } from 'react' - -import { cn } from 'ui' - import MenuIconPicker from '~/components/Navigation/NavigationMenu/MenuIconPicker' import RefVersionDropdown from '~/components/RefVersionDropdown' import { getReferenceSections } from '~/features/docs/Reference.generated.singleton' import { - RefLink, + ACTIVE_BAR_CLASS_NAME, + ReferenceActiveCrumb, ReferenceNavigationScrollHandler, + RefLink, + type ReferenceCrumbHeading, } from '~/features/docs/Reference.navigation.client' import { type AbbrevApiReferenceSection } from '~/features/docs/Reference.utils' +import { isFeatureEnabled } from 'common' interface ReferenceNavigationProps { libraryId: string @@ -23,8 +22,27 @@ interface ReferenceNavigationProps { // instead of scrolling within one giant page. SDK/CLI/self-hosting callers // never pass this, so their behavior is unchanged. realNavigation?: boolean + // only one of the two rendered copies owns the header crumb + hasActiveCrumb?: boolean } +interface RefCategoryProps { + basePath: string + section: AbbrevApiReferenceSection + realNavigation?: boolean +} + +const flattenSections = (sections: AbbrevApiReferenceSection[]): AbbrevApiReferenceSection[] => + sections.flatMap((section) => [section, ...flattenSections(section.items ?? [])]) + +const getCrumbHeadings = (basePath: string, sections: AbbrevApiReferenceSection[]) => + flattenSections(sections).reduce((headings, section) => { + if (section.slug && section.title) { + headings.push({ url: `${basePath}/${section.slug}`, title: section.title }) + } + return headings + }, []) + export async function ReferenceNavigation({ libraryId, name, @@ -33,6 +51,7 @@ export async function ReferenceNavigation({ version, isLatestVersion, realNavigation, + hasActiveCrumb = false, }: ReferenceNavigationProps) { const navSections = await getReferenceSections(libraryId, version) const filteredNavSections = navSections?.filter((section) => section.title !== 'Auth') @@ -41,20 +60,31 @@ export async function ReferenceNavigation({ const basePath = `/reference/${libPath}${isLatestVersion ? '' : `/${version}`}` return ( - -
- {'icon' in menuData && } - {name} + + {hasActiveCrumb && displayedNavSections ? ( + + ) : null} +
+ {'icon' in menuData ? ( + + ) : null} + {name}
-
    - {displayedNavSections?.map((section, index) => +
      + {displayedNavSections?.map((section) => section.type === 'category' ? ( -
    • +
    • ) : ( -
    • +
    • ) @@ -64,47 +94,28 @@ export async function ReferenceNavigation({ ) } -const topLvlRefNavItemStyles = 'leading-5' - -function RefCategory({ - basePath, - section, - realNavigation, -}: { - basePath: string - section: AbbrevApiReferenceSection - realNavigation?: boolean -}) { +function RefCategory({ basePath, section, realNavigation }: RefCategoryProps) { if (!('items' in section && section.items && section.items.length > 0)) return null return ( - <> - - {'title' in section && {section.title}} -
        +
        + {'title' in section ? ( + + {section.title} + + ) : null} +
          {section.items?.map((item) => ( -
        • - +
        • +
        • ))}
        - - ) -} - -function Divider() { - return
        -} - -function SideMenuTitle({ children, className }: PropsWithChildren<{ className?: string }>) { - return ( -
        - {children}
        ) } diff --git a/apps/docs/features/docs/Reference.sdkPage.tsx b/apps/docs/features/docs/Reference.sdkPage.tsx index 9f03d453bde..cc7f01aae31 100644 --- a/apps/docs/features/docs/Reference.sdkPage.tsx +++ b/apps/docs/features/docs/Reference.sdkPage.tsx @@ -20,6 +20,15 @@ export async function ClientSdkReferencePage({ sdkId, libVersion }: ClientSdkRef const menuData = NavItems[libraryMeta.meta[libVersion].libId] + const navigationProps = { + libraryId: sdkId, + name: menuData.title, + menuData: menuData, + libPath: libraryMeta.libPath, + version: libVersion, + isLatestVersion: isLatestVersion, + } + return ( - } + NavigationMenu={} + rightRail={} > {!isLatestVersion && ( diff --git a/apps/docs/features/docs/Reference.selfHostingPage.tsx b/apps/docs/features/docs/Reference.selfHostingPage.tsx index 53773af3514..e2054b1d119 100644 --- a/apps/docs/features/docs/Reference.selfHostingPage.tsx +++ b/apps/docs/features/docs/Reference.selfHostingPage.tsx @@ -47,20 +47,21 @@ export async function SelfHostingReferencePage({ const name = REFERENCES[servicePath.replaceAll('-', '_')].name + const navigationProps = { + libraryId: servicePath, + name: name, + menuData: menuData, + libPath: servicePath, + version: 'latest', + isLatestVersion: true, + } + return ( - } + NavigationMenu={} + rightRail={} >