docs: support multiple sidebars in a project (#11768)
* changed to new sidebar across projects except resources * finalize multi sidebar support * clean up * remove redundant property * small changes * fixes * generate * fix error * fix initial open
This commit is contained in:
@@ -58,9 +58,6 @@ export default function RootLayout({
|
||||
gaId={process.env.NEXT_PUBLIC_GA_ID}
|
||||
>
|
||||
<WideLayout
|
||||
sidebarProps={{
|
||||
expandItems: false,
|
||||
}}
|
||||
showToc={false}
|
||||
showBreadcrumbs={false}
|
||||
ProvidersComponent={Providers}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { useScrollController, useSidebar, H2 as UiH2 } from "docs-ui"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import getSectionId from "../../../utils/get-section-id"
|
||||
import { SidebarItem } from "types"
|
||||
import { Sidebar } from "types"
|
||||
|
||||
type H2Props = React.HTMLAttributes<HTMLHeadingElement>
|
||||
|
||||
const H2 = ({ children, ...props }: H2Props) => {
|
||||
const headingRef = useRef<HTMLHeadingElement>(null)
|
||||
const { activePath, addItems, removeItems } = useSidebar()
|
||||
const { activePath, addItems, removeItems, shownSidebar } = useSidebar()
|
||||
const { scrollableElement, scrollToElement } = useScrollController()
|
||||
const [scrolledFirstTime, setScrolledFirstTime] = useState(false)
|
||||
|
||||
@@ -29,7 +29,10 @@ const H2 = ({ children, ...props }: H2Props) => {
|
||||
}, [scrollableElement, headingRef, id])
|
||||
|
||||
useEffect(() => {
|
||||
const item: SidebarItem[] = [
|
||||
if (!shownSidebar) {
|
||||
return
|
||||
}
|
||||
const items: Sidebar.SidebarItem[] = [
|
||||
{
|
||||
type: "link",
|
||||
path: `${id}`,
|
||||
@@ -37,12 +40,17 @@ const H2 = ({ children, ...props }: H2Props) => {
|
||||
loaded: true,
|
||||
},
|
||||
]
|
||||
addItems(item)
|
||||
addItems(items, {
|
||||
sidebar_id: shownSidebar.sidebar_id,
|
||||
})
|
||||
|
||||
return () => {
|
||||
removeItems(item)
|
||||
removeItems({
|
||||
items,
|
||||
sidebar_id: shownSidebar.sidebar_id,
|
||||
})
|
||||
}
|
||||
}, [id])
|
||||
}, [id, shownSidebar?.sidebar_id])
|
||||
|
||||
return (
|
||||
<UiH2 {...props} id={id} passRef={headingRef}>
|
||||
|
||||
@@ -60,11 +60,12 @@ const TagOperation = ({
|
||||
}, [isBrowser, scrollableElement])
|
||||
|
||||
const scrollIntoView = useCallback(() => {
|
||||
if (!isBrowser) {
|
||||
if (!isBrowser || !nodeRef.current) {
|
||||
// repeat timeout
|
||||
setTimeout(scrollIntoView, 200)
|
||||
return
|
||||
}
|
||||
|
||||
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 0)) {
|
||||
if (!checkElementInViewport(nodeRef.current, 0)) {
|
||||
const elm = nodeRef.current as HTMLElement
|
||||
scrollToTop(
|
||||
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import type { Operation, PathsObject } from "@/types/openapi"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import type { Operation, PathsObject, TagObject } from "@/types/openapi"
|
||||
import { findSidebarItem, useSidebar } from "docs-ui"
|
||||
import { Fragment, Suspense, useEffect } from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationProps } from "../Operation"
|
||||
@@ -10,32 +9,41 @@ import clsx from "clsx"
|
||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||
import { useLoading } from "@/providers/loading"
|
||||
import DividedLoading from "@/components/DividedLoading"
|
||||
import { SidebarItemSections, SidebarItem, SidebarItemCategory } from "types"
|
||||
import { Sidebar } from "types"
|
||||
|
||||
const TagOperation = dynamic<TagOperationProps>(
|
||||
async () => import("../Operation")
|
||||
) as React.FC<TagOperationProps>
|
||||
|
||||
export type TagPathsProps = {
|
||||
tag: OpenAPIV3.TagObject
|
||||
tag: TagObject
|
||||
paths: PathsObject
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagPaths = ({ tag, className, paths }: TagPathsProps) => {
|
||||
const { items, addItems, findItemInSection } = useSidebar()
|
||||
const { shownSidebar, addItems } = useSidebar()
|
||||
const { loading } = useLoading()
|
||||
|
||||
useEffect(() => {
|
||||
if (!shownSidebar) {
|
||||
return
|
||||
}
|
||||
|
||||
if (paths) {
|
||||
const parentItem = findItemInSection(
|
||||
items[SidebarItemSections.DEFAULT],
|
||||
{ title: tag.name },
|
||||
false
|
||||
) as SidebarItemCategory
|
||||
const pathItems: SidebarItem[] = getTagChildSidebarItems(paths)
|
||||
if ((parentItem?.children?.length || 0) < pathItems.length) {
|
||||
const parentItem = findSidebarItem({
|
||||
sidebarItems:
|
||||
"items" in shownSidebar
|
||||
? shownSidebar.items
|
||||
: shownSidebar.children || [],
|
||||
item: { title: tag.name, type: "category" },
|
||||
checkChildren: false,
|
||||
}) as Sidebar.SidebarItemCategory
|
||||
const pathItems: Sidebar.SidebarItem[] = getTagChildSidebarItems(paths)
|
||||
const targetLength =
|
||||
pathItems.length + (tag["x-associatedSchema"] ? 1 : 0)
|
||||
if ((parentItem.children?.length || 0) < targetLength) {
|
||||
addItems(pathItems, {
|
||||
section: SidebarItemSections.DEFAULT,
|
||||
sidebar_id: shownSidebar.sidebar_id,
|
||||
parent: {
|
||||
type: "category",
|
||||
title: tag.name,
|
||||
@@ -46,7 +54,7 @@ const TagPaths = ({ tag, className, paths }: TagPathsProps) => {
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [paths])
|
||||
}, [paths, shownSidebar?.sidebar_id])
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { Suspense, useEffect, useMemo, useRef } from "react"
|
||||
import { Suspense, useEffect, useMemo } from "react"
|
||||
import { SchemaObject } from "../../../../types/openapi"
|
||||
import TagOperationParameters from "../../Operation/Parameters"
|
||||
import {
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
useScrollController,
|
||||
useSidebar,
|
||||
} from "docs-ui"
|
||||
import { SidebarItemSections } from "types"
|
||||
import getSectionId from "../../../../utils/get-section-id"
|
||||
import DividedLayout from "../../../../layouts/Divided"
|
||||
import SectionContainer from "../../../Section/Container"
|
||||
@@ -30,7 +29,7 @@ export type TagSectionSchemaProps = {
|
||||
}
|
||||
|
||||
const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
||||
const { addItems, setActivePath, activePath } = useSidebar()
|
||||
const { addItems, setActivePath, activePath, shownSidebar } = useSidebar()
|
||||
const { displayedArea } = useArea()
|
||||
const formattedName = useMemo(
|
||||
() => singular(tagName).replaceAll(" ", ""),
|
||||
@@ -58,6 +57,9 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
||||
}, [isBrowser, scrollableElement])
|
||||
|
||||
useEffect(() => {
|
||||
if (!shownSidebar) {
|
||||
return
|
||||
}
|
||||
addItems(
|
||||
[
|
||||
{
|
||||
@@ -69,7 +71,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
||||
},
|
||||
],
|
||||
{
|
||||
section: SidebarItemSections.DEFAULT,
|
||||
sidebar_id: shownSidebar.sidebar_id,
|
||||
parent: {
|
||||
type: "category",
|
||||
title: tagName,
|
||||
@@ -80,7 +82,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
||||
}
|
||||
)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [formattedName])
|
||||
}, [formattedName, shownSidebar?.sidebar_id])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBrowser) {
|
||||
|
||||
@@ -85,23 +85,24 @@ const TagSectionComponent = ({ tag }: TagSectionProps) => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBrowser) {
|
||||
if (!isBrowser || !activePath || !activePath.includes(slugTagName)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (activePath && activePath.includes(slugTagName)) {
|
||||
const tagName = activePath.split("_")
|
||||
if (tagName.length === 1 && tagName[0] === slugTagName) {
|
||||
const elm = document.getElementById(tagName[0])
|
||||
if (elm && !checkElementInViewport(elm, 0)) {
|
||||
scrollToTop(
|
||||
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
|
||||
0
|
||||
)
|
||||
}
|
||||
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
|
||||
setLoadData(true)
|
||||
const tagName = activePath.split("_")
|
||||
if (tagName[0] !== slugTagName) {
|
||||
return
|
||||
}
|
||||
if (tagName.length === 1) {
|
||||
const elm = document.getElementById(tagName[0])
|
||||
if (elm && !checkElementInViewport(elm, 0)) {
|
||||
scrollToTop(
|
||||
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
|
||||
0
|
||||
)
|
||||
}
|
||||
} else if (tagName.length > 1) {
|
||||
setLoadData(true)
|
||||
}
|
||||
}, [slugTagName, activePath, isBrowser])
|
||||
|
||||
|
||||
@@ -8,17 +8,20 @@ export const config: DocsConfig = {
|
||||
baseUrl,
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
// sidebar is auto generated
|
||||
sidebar: {
|
||||
default: [
|
||||
{
|
||||
type: "link",
|
||||
title: "Introduction",
|
||||
path: "introduction",
|
||||
loaded: true,
|
||||
},
|
||||
],
|
||||
mobile: [],
|
||||
},
|
||||
sidebars: [
|
||||
{
|
||||
sidebar_id: "api-ref",
|
||||
title: "API Reference",
|
||||
items: [
|
||||
{
|
||||
type: "link",
|
||||
title: "Introduction",
|
||||
path: "introduction",
|
||||
loaded: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
project: {
|
||||
title: "API Reference",
|
||||
key: "api-reference",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ExpandedDocument, SecuritySchemeObject } from "@/types/openapi"
|
||||
import { ReactNode, createContext, useContext, useEffect, useMemo } from "react"
|
||||
import { SidebarItem, SidebarItemSections } from "types"
|
||||
import { Sidebar } from "types"
|
||||
import getSectionId from "../utils/get-section-id"
|
||||
import getTagChildSidebarItems from "../utils/get-tag-child-sidebar-items"
|
||||
import { useRouter } from "next/navigation"
|
||||
@@ -22,7 +22,8 @@ type BaseSpecsProviderProps = {
|
||||
|
||||
const BaseSpecsProvider = ({ children, baseSpecs }: BaseSpecsProviderProps) => {
|
||||
const router = useRouter()
|
||||
const { activePath, addItems, setActivePath, resetItems } = useSidebar()
|
||||
const { activePath, addItems, setActivePath, resetItems, shownSidebar } =
|
||||
useSidebar()
|
||||
|
||||
const getSecuritySchema = (
|
||||
securityName: string
|
||||
@@ -48,7 +49,7 @@ const BaseSpecsProvider = ({ children, baseSpecs }: BaseSpecsProviderProps) => {
|
||||
return []
|
||||
}
|
||||
|
||||
const itemsToAdd: SidebarItem[] = [
|
||||
const itemsToAdd: Sidebar.SidebarItem[] = [
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
@@ -67,6 +68,7 @@ const BaseSpecsProvider = ({ children, baseSpecs }: BaseSpecsProviderProps) => {
|
||||
children: childItems,
|
||||
loaded: childItems.length > 0,
|
||||
showLoadingIfEmpty: true,
|
||||
initialOpen: false,
|
||||
onOpen: () => {
|
||||
if (location.hash !== tagPathName) {
|
||||
router.push(`#${tagPathName}`, {
|
||||
@@ -84,14 +86,14 @@ const BaseSpecsProvider = ({ children, baseSpecs }: BaseSpecsProviderProps) => {
|
||||
}, [baseSpecs])
|
||||
|
||||
useEffect(() => {
|
||||
if (!itemsToAdd.length) {
|
||||
if (!itemsToAdd.length || !shownSidebar) {
|
||||
return
|
||||
}
|
||||
|
||||
addItems(itemsToAdd, {
|
||||
section: SidebarItemSections.DEFAULT,
|
||||
sidebar_id: shownSidebar.sidebar_id,
|
||||
})
|
||||
}, [itemsToAdd])
|
||||
}, [itemsToAdd, shownSidebar?.sidebar_id])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { createContext, useEffect } from "react"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import { useArea } from "./area"
|
||||
import { SidebarItemLink } from "types"
|
||||
import { Sidebar } from "types"
|
||||
|
||||
const PageTitleContext = createContext(null)
|
||||
|
||||
@@ -27,7 +27,7 @@ const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
|
||||
// find the child that matches the active path
|
||||
const item = activeItem?.children?.find(
|
||||
(i) => i.type === "link" && i.path === activePath
|
||||
) as SidebarItemLink
|
||||
) as Sidebar.SidebarItemLink
|
||||
if (item) {
|
||||
document.title = `${item.title} - ${titleSuffix}`
|
||||
}
|
||||
|
||||
@@ -20,10 +20,12 @@ const SidebarProvider = ({ children }: SidebarProviderProps) => {
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
shouldHandleHashChange={true}
|
||||
shouldHandlePathChange={false}
|
||||
scrollableElement={scrollableElement}
|
||||
initialItems={config.sidebar}
|
||||
persistState={false}
|
||||
projectName="api"
|
||||
sidebars={config.sidebars}
|
||||
persistCategoryState={false}
|
||||
disableActiveTransition={false}
|
||||
isSidebarStatic={false}
|
||||
>
|
||||
{children}
|
||||
</UiSidebarProvider>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { SidebarItem } from "types"
|
||||
import type { Operation, PathsObject } from "@/types/openapi"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { MethodLabelProps } from "@/components/MethodLabel"
|
||||
import getSectionId from "./get-section-id"
|
||||
import { Sidebar } from "types"
|
||||
|
||||
const MethodLabel = dynamic<MethodLabelProps>(
|
||||
async () => import("../components/MethodLabel")
|
||||
@@ -11,8 +11,8 @@ const MethodLabel = dynamic<MethodLabelProps>(
|
||||
|
||||
export default function getTagChildSidebarItems(
|
||||
paths: PathsObject
|
||||
): SidebarItem[] {
|
||||
const items: SidebarItem[] = []
|
||||
): Sidebar.SidebarItem[] {
|
||||
const items: Sidebar.SidebarItem[] = []
|
||||
Object.entries(paths).forEach(([, operations]) => {
|
||||
Object.entries(operations).map(([method, operation]) => {
|
||||
const definedOperation = operation as Operation
|
||||
|
||||
Reference in New Issue
Block a user