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:
Shahed Nasser
2025-03-07 15:47:38 +02:00
committed by GitHub
parent 2a0bd86204
commit 5deb8eaf50
108 changed files with 37634 additions and 36749 deletions
@@ -3,94 +3,53 @@
import React, { useMemo } from "react"
import clsx from "clsx"
import Link from "next/link"
import { SidebarItemLink } from "types"
import {
CurrentItemsState,
isSidebarItemLink,
useSidebar,
useSiteConfig,
} from "../../providers"
import { useSidebar, useSiteConfig } from "../../providers"
import { Button } from "../Button"
import { TriangleRightMini } from "@medusajs/icons"
import { Sidebar } from "types"
type BreadcrumbItems = {
title: string
link: string
}[]
export const Breadcrumbs = () => {
const { currentItems, activeItem: sidebarActiveItem } = useSidebar()
const { sidebarHistory, getSidebarFirstLinkChild, getSidebar } = useSidebar()
const {
config: { breadcrumbOptions, project, baseUrl, basePath },
config: { breadcrumbOptions },
} = useSiteConfig()
const getLinkPath = (item?: SidebarItemLink): string | undefined => {
if (!item) {
return
}
const getLinkPath = (item: Sidebar.SidebarItemLink): string => {
return item.isPathHref ? item.path : `#${item.path}`
}
const getBreadcrumbsOfItem = (
item: CurrentItemsState
): Map<string, string> => {
let tempBreadcrumbItems: Map<string, string> = new Map()
if (item.previousSidebar) {
tempBreadcrumbItems = getBreadcrumbsOfItem(item.previousSidebar)
}
const parentPath = isSidebarItemLink(item.parentItem)
? getLinkPath(item.parentItem)
: (item.parentItem?.type === "category" &&
breadcrumbOptions?.showCategories) ||
item.parentItem?.type === "sub-category"
? "#"
: undefined
const firstItemPath = isSidebarItemLink(item.default[0])
? getLinkPath(item.default[0])
: (item.default[0].type === "category" &&
breadcrumbOptions?.showCategories) ||
item.default[0].type === "sub-category"
? "#"
: undefined
const breadcrumbPath = parentPath || firstItemPath || "/"
tempBreadcrumbItems.set(
breadcrumbPath,
item.parentItem?.childSidebarTitle ||
item.parentItem?.chapterTitle ||
item.parentItem?.title ||
""
)
return tempBreadcrumbItems
}
const breadcrumbItems = useMemo(() => {
const tempBreadcrumbItems: Map<string, string> = new Map()
tempBreadcrumbItems.set(`${baseUrl}${basePath}`, project.title)
if (currentItems) {
getBreadcrumbsOfItem(currentItems).forEach((value, key) =>
tempBreadcrumbItems.set(key, value)
)
const items: BreadcrumbItems = []
if (breadcrumbOptions?.startItems) {
items.push(...breadcrumbOptions.startItems)
}
if (sidebarActiveItem) {
if (
sidebarActiveItem.parentItem &&
(sidebarActiveItem.parentItem.type !== "category" ||
breadcrumbOptions?.showCategories)
) {
tempBreadcrumbItems.set(
isSidebarItemLink(sidebarActiveItem.parentItem)
? getLinkPath(sidebarActiveItem.parentItem) || "#"
: "#",
sidebarActiveItem.parentItem.chapterTitle ||
sidebarActiveItem.parentItem.title ||
""
)
sidebarHistory.forEach((sidebar_id) => {
const sidebar = getSidebar(sidebar_id)
if (!sidebar) {
return
}
}
return tempBreadcrumbItems
}, [currentItems, sidebarActiveItem, breadcrumbOptions])
const sidebarFirstChild = getSidebarFirstLinkChild(sidebar)
if (!sidebarFirstChild) {
return
}
items.push({
title: sidebar.title,
link: getLinkPath(sidebarFirstChild),
})
})
return items
}, [sidebarHistory, breadcrumbOptions])
return (
<div
@@ -100,7 +59,7 @@ export const Breadcrumbs = () => {
"mb-docs_1 flex-wrap"
)}
>
{Array.from(breadcrumbItems).map(([link, title], index) => (
{breadcrumbItems.map(({ title, link }, index) => (
<React.Fragment key={link}>
{index > 0 && <TriangleRightMini />}
<Button
@@ -1,17 +1,26 @@
"use client"
import React from "react"
import React, { useMemo } from "react"
import clsx from "clsx"
import { InteractiveSidebarItem } from "types"
import { ArrowUturnLeft } from "@medusajs/icons"
import { useSidebar } from "../../.."
import { useSidebar } from "../../../providers"
type SidebarTitleProps = {
item: InteractiveSidebarItem
}
export const SidebarChild = () => {
const { goBack, shownSidebar } = useSidebar()
export const SidebarChild = ({ item }: SidebarTitleProps) => {
const { goBack } = useSidebar()
const title = useMemo(() => {
if (!shownSidebar) {
return ""
}
return "childSidebarTitle" in shownSidebar
? shownSidebar.childSidebarTitle || shownSidebar.title
: shownSidebar.title
}, [shownSidebar])
if (!shownSidebar) {
return <></>
}
return (
<div className="px-docs_0.75">
@@ -25,9 +34,7 @@ export const SidebarChild = ({ item }: SidebarTitleProps) => {
tabIndex={-1}
>
<ArrowUturnLeft />
<span className="truncate flex-1">
{item.childSidebarTitle || item.title}
</span>
<span className="truncate flex-1">{title}</span>
</div>
</div>
)
@@ -3,34 +3,37 @@
// @refresh reset
import React, { useEffect, useMemo, useState } from "react"
import { SidebarItemCategory as SidebarItemCategoryType } from "types"
import { Sidebar } from "types"
import { Loading, SidebarItem, useSidebar } from "../../../.."
import clsx from "clsx"
import { MinusMini, PlusMini } from "@medusajs/icons"
export type SidebarItemCategory = {
item: SidebarItemCategoryType
expandItems?: boolean
export type SidebarItemCategoryProps = {
item: Sidebar.SidebarItemCategory
} & React.AllHTMLAttributes<HTMLDivElement>
export const SidebarItemCategory = ({
item,
expandItems = true,
className,
}: SidebarItemCategory) => {
}: SidebarItemCategoryProps) => {
const [showLoading, setShowLoading] = useState(false)
const [open, setOpen] = useState(
item.initialOpen !== undefined ? item.initialOpen : expandItems
item.initialOpen !== undefined ? item.initialOpen : true
)
const {
isChildrenActive,
isItemActive,
updatePersistedCategoryState,
getPersistedCategoryState,
persistState,
persistCategoryState,
} = useSidebar()
const itemShowLoading = useMemo(() => {
return !item.loaded || (item.showLoadingIfEmpty && !item.children?.length)
}, [item])
const isActive = useMemo(() => {
return isItemActive({
item,
})
}, [isItemActive, item])
useEffect(() => {
if (open && itemShowLoading) {
@@ -45,22 +48,20 @@ export const SidebarItemCategory = ({
}, [itemShowLoading, showLoading])
useEffect(() => {
const isActive = isChildrenActive(item)
if (isActive && !open) {
setOpen(true)
}
}, [isChildrenActive, item.children])
}, [isActive, item.children])
useEffect(() => {
if (!persistState) {
if (!persistCategoryState) {
return
}
const persistedOpen = getPersistedCategoryState(item.title)
if (persistedOpen !== undefined) {
if (persistedOpen !== undefined && !isActive) {
setOpen(persistedOpen)
}
}, [persistState])
}, [persistCategoryState])
const handleOpen = () => {
item.onOpen?.()
@@ -90,7 +91,7 @@ export const SidebarItemCategory = ({
if (!open) {
handleOpen()
}
if (persistState) {
if (persistCategoryState) {
updatePersistedCategoryState(item.title, !open)
}
setOpen((prev) => !prev)
@@ -133,7 +134,7 @@ export const SidebarItemCategory = ({
<SidebarItem
item={childItem}
key={index}
expandItems={expandItems}
isParentCategoryOpen={open}
/>
))}
</ul>
@@ -2,8 +2,8 @@
// @refresh reset
import React, { useEffect, useMemo, useRef } from "react"
import { SidebarItemLink as SidebarItemlinkType } from "types"
import React, { useCallback, useEffect, useMemo, useRef } from "react"
import { Sidebar } from "types"
import {
checkSidebarItemVisibility,
SidebarItem,
@@ -14,27 +14,36 @@ import clsx from "clsx"
import Link from "next/link"
export type SidebarItemLinkProps = {
item: SidebarItemlinkType
item: Sidebar.SidebarItemLink
nested?: boolean
isParentCategoryOpen?: boolean
} & React.AllHTMLAttributes<HTMLLIElement>
export const SidebarItemLink = ({
item,
className,
nested = false,
isParentCategoryOpen,
}: SidebarItemLinkProps) => {
const {
isLinkActive,
isItemActive,
setMobileSidebarOpen: setSidebarOpen,
disableActiveTransition,
sidebarRef,
sidebarTopHeight,
} = useSidebar()
const { isMobile } = useMobile()
const active = useMemo(() => isLinkActive(item, true), [isLinkActive, item])
const active = useMemo(
() =>
isItemActive({
item,
checkLinkChildren: false,
}),
[isItemActive, item]
)
const ref = useRef<HTMLLIElement>(null)
const newTopCalculator = useMemo(() => {
const getNewTopCalculator = useCallback(() => {
if (!sidebarRef.current || !ref.current) {
return 0
}
@@ -48,33 +57,43 @@ export const SidebarItemLink = ({
sidebarRef.current.scrollTop -
10 // remove extra margin just in case
)
}, [sidebarTopHeight, sidebarRef, ref])
}, [sidebarTopHeight, sidebarRef.current, ref.current])
useEffect(() => {
if (active && ref.current && sidebarRef.current && !isMobile) {
const isVisible = checkSidebarItemVisibility(
(ref.current.children.item(0) as HTMLElement) || ref.current,
!disableActiveTransition
)
if (isVisible) {
return
}
if (!disableActiveTransition) {
ref.current.scrollIntoView({
block: "center",
})
} else {
sidebarRef.current.scrollTo({
top: newTopCalculator,
})
}
if (
!active ||
!ref.current ||
!sidebarRef.current ||
isMobile ||
!isParentCategoryOpen
) {
return
}
const isVisible = checkSidebarItemVisibility(
(ref.current.children.item(0) as HTMLElement) || ref.current,
!disableActiveTransition
)
if (isVisible) {
return
}
if (!disableActiveTransition) {
ref.current.scrollIntoView({
block: "center",
})
} else {
sidebarRef.current.scrollTo({
top: getNewTopCalculator(),
})
}
}, [
active,
sidebarRef.current,
disableActiveTransition,
isMobile,
newTopCalculator,
ref.current,
getNewTopCalculator,
isParentCategoryOpen,
])
useEffect(() => {
@@ -84,11 +103,7 @@ export const SidebarItemLink = ({
}, [active, isMobile])
const hasChildren = useMemo(() => {
return (
!item.isChildSidebar &&
!item.hideChildren &&
(item.children?.length || 0) > 0
)
return !item.hideChildren && (item.children?.length || 0) > 0
}, [item.children])
const isTitleOneWord = useMemo(
@@ -147,6 +162,7 @@ export const SidebarItemLink = ({
item={childItem}
key={index}
nested={!item.childrenSameLevel}
isParentCategoryOpen={isParentCategoryOpen}
/>
))}
</ul>
@@ -0,0 +1,63 @@
"use client"
// @refresh reset
import React, { useMemo } from "react"
import { Sidebar } from "types"
import { useSidebar } from "../../../.."
import clsx from "clsx"
import Link from "next/link"
export type SidebarItemSidebarProps = {
item: Sidebar.SidebarItemSidebar
nested?: boolean
} & React.AllHTMLAttributes<HTMLLIElement>
export const SidebarItemSidebar = ({
item,
className,
nested = false,
}: SidebarItemSidebarProps) => {
const { getSidebarFirstLinkChild: getSidebarFirstChild } = useSidebar()
const isTitleOneWord = useMemo(
() => item.title.split(" ").length === 1,
[item.title]
)
const firstChild = useMemo(() => getSidebarFirstChild(item), [item])
return (
<li>
<span className="block px-docs_0.75">
<Link
href={
firstChild?.isPathHref ? firstChild.path : `#${firstChild?.path}`
}
className={clsx(
"py-docs_0.25 px-docs_0.5",
"block w-full rounded-docs_sm",
!isTitleOneWord && "break-words",
!nested && "text-medusa-fg-subtle",
nested && "text-medusa-fg-muted",
"hover:bg-medusa-bg-base-hover lg:hover:bg-medusa-bg-subtle-hover",
"text-compact-small-plus",
"flex justify-between items-center gap-[6px]",
className
)}
{...firstChild?.linkProps}
>
<span
className={clsx(
isTitleOneWord && "truncate",
nested && "inline-block pl-docs_1.5"
)}
>
{item.title}
</span>
{item.additionalElms}
</Link>
</span>
</li>
)
}
@@ -2,90 +2,25 @@
// @refresh reset
import React, { useEffect, useMemo, useRef } from "react"
import { SidebarItemSubCategory as SidebarItemSubCategoryType } from "types"
import {
checkSidebarItemVisibility,
SidebarItem,
useMobile,
useSidebar,
} from "../../../.."
import React, { useMemo, useRef } from "react"
import { Sidebar } from "types"
import { SidebarItem } from "../../../.."
import clsx from "clsx"
export type SidebarItemLinkProps = {
item: SidebarItemSubCategoryType
export type SidebarItemSubCategoryProps = {
item: Sidebar.SidebarItemSubCategory
nested?: boolean
isParentCategoryOpen?: boolean
} & React.AllHTMLAttributes<HTMLLIElement>
export const SidebarItemSubCategory = ({
item,
className,
nested = false,
}: SidebarItemLinkProps) => {
const { isLinkActive, disableActiveTransition, sidebarRef } = useSidebar()
const { isMobile } = useMobile()
const active = useMemo(
() => !isMobile && isLinkActive(item, true),
[isLinkActive, item, isMobile]
)
isParentCategoryOpen,
}: SidebarItemSubCategoryProps) => {
const ref = useRef<HTMLLIElement>(null)
/**
* Tries to place the item in the center of the sidebar
*/
const newTopCalculator = (): number => {
if (!sidebarRef.current || !ref.current) {
return 0
}
const sidebarBoundingRect = sidebarRef.current.getBoundingClientRect()
const sidebarHalf = sidebarBoundingRect.height / 2
const itemTop = ref.current.offsetTop
const itemBottom =
itemTop + (ref.current.children.item(0) as HTMLElement)?.clientHeight
// try deducting half
let newTop = itemTop - sidebarHalf
let newBottom = newTop + sidebarBoundingRect.height
if (newTop <= itemTop && newBottom >= itemBottom) {
return newTop
}
// try adding half
newTop = itemTop + sidebarHalf
newBottom = newTop + sidebarBoundingRect.height
if (newTop <= itemTop && newBottom >= itemBottom) {
return newTop
}
//return the item's top minus some top margin
return itemTop - sidebarBoundingRect.top
}
useEffect(() => {
if (
active &&
ref.current &&
sidebarRef.current &&
window.innerWidth >= 1025
) {
if (
!disableActiveTransition &&
!checkSidebarItemVisibility(
(ref.current.children.item(0) as HTMLElement) || ref.current,
!disableActiveTransition
)
) {
ref.current.scrollIntoView({
block: "center",
})
} else if (disableActiveTransition) {
sidebarRef.current.scrollTo({
top: newTopCalculator(),
})
}
}
}, [active, sidebarRef.current, disableActiveTransition])
const hasChildren = useMemo(() => {
return !item.hideChildren && (item.children?.length || 0) > 0
}, [item.children])
@@ -103,15 +38,8 @@ export const SidebarItemSubCategory = ({
"py-docs_0.25 px-docs_0.5",
"block w-full",
!isTitleOneWord && "break-words",
active && [
"rounded-docs_sm",
"shadow-borders-base dark:shadow-borders-base-dark",
"text-medusa-fg-base",
],
!active && [
!nested && "text-medusa-fg-subtle",
nested && "text-medusa-fg-muted",
],
!nested && "text-medusa-fg-subtle",
nested && "text-medusa-fg-muted",
"text-compact-small-plus",
className
)}
@@ -140,6 +68,7 @@ export const SidebarItemSubCategory = ({
item={childItem}
key={index}
nested={!item.childrenSameLevel}
isParentCategoryOpen={isParentCategoryOpen}
/>
))}
</ul>
@@ -1,17 +1,18 @@
"use client"
import React from "react"
import { SidebarItem as SidebarItemType } from "types"
import { SidebarItemCategory } from "./Category"
import { Sidebar } from "types"
import { SidebarItemLink } from "./Link"
import { SidebarItemSubCategory } from "./SubCategory"
import { DottedSeparator } from "../.."
import { SidebarItemCategory } from "./Category"
import { SidebarItemSidebar } from "./Sidebar"
export type SidebarItemProps = {
item: SidebarItemType
item: Sidebar.SidebarItem
nested?: boolean
expandItems?: boolean
hasNextItems?: boolean
isParentCategoryOpen?: boolean
} & React.AllHTMLAttributes<HTMLElement>
export const SidebarItem = ({
@@ -33,6 +34,8 @@ export const SidebarItem = ({
case "ref":
case "external":
return <SidebarItemLink item={item} {...props} />
case "sidebar":
return <SidebarItemSidebar item={item} {...props} />
case "separator":
return <DottedSeparator />
}
@@ -2,17 +2,14 @@
import React from "react"
import { SidebarChild } from "../Child"
import { InteractiveSidebarItem } from "types"
import { SidebarTopMobileClose } from "./MobileClose"
import { DottedSeparator } from "../../.."
import { DottedSeparator, useSidebar } from "../../.."
import clsx from "clsx"
export type SidebarTopProps = {
parentItem?: InteractiveSidebarItem
}
export const SidebarTop = React.forwardRef<HTMLDivElement>(
function SidebarTop(props, ref) {
const { sidebarHistory } = useSidebar()
export const SidebarTop = React.forwardRef<HTMLDivElement, SidebarTopProps>(
function SidebarTop({ parentItem }, ref) {
return (
<div
className={clsx(
@@ -23,9 +20,9 @@ export const SidebarTop = React.forwardRef<HTMLDivElement, SidebarTopProps>(
>
<SidebarTopMobileClose />
<div>
{parentItem && (
{sidebarHistory.length > 1 && (
<>
<SidebarChild item={parentItem} />
<SidebarChild />
<DottedSeparator wrapperClassName="!my-0" />
</>
)}
@@ -1,39 +1,35 @@
"use client"
import React, { Suspense, useMemo, useRef } from "react"
import { isSidebarItemLink, useSidebar } from "@/providers"
import { useSidebar } from "@/providers"
import clsx from "clsx"
import { Loading } from "@/components"
import { SidebarItem } from "./Item"
// @ts-expect-error can't install the types package because it doesn't support React v19
import { CSSTransition, SwitchTransition } from "react-transition-group"
import { SidebarTop, SidebarTopProps } from "./Top"
import { SidebarTop } from "./Top"
import { useClickOutside, useKeyboardShortcut } from "@/hooks"
import useResizeObserver from "@react-hook/resize-observer"
import { isSidebarItemLink } from "../../utils/sidebar-utils"
export type SidebarProps = {
className?: string
expandItems?: boolean
sidebarTopProps?: Omit<SidebarTopProps, "parentItem">
}
export const Sidebar = ({
className = "",
expandItems = true,
sidebarTopProps,
}: SidebarProps) => {
export const Sidebar = ({ className = "" }: SidebarProps) => {
const sidebarWrapperRef = useRef(null)
const sidebarTopRef = useRef<HTMLDivElement>(null)
const {
items,
currentItems,
sidebars,
shownSidebar,
mobileSidebarOpen,
setMobileSidebarOpen,
staticSidebarItems,
isSidebarStatic,
sidebarRef,
desktopSidebarOpen,
setDesktopSidebarOpen,
setSidebarTopHeight,
sidebarHistory,
} = useSidebar()
useClickOutside({
elmRef: sidebarWrapperRef,
@@ -51,15 +47,16 @@ export const Sidebar = ({
},
})
const sidebarItems = useMemo(
() => currentItems || items,
[items, currentItems]
)
useResizeObserver(sidebarTopRef as React.RefObject<HTMLElement>, () => {
setSidebarTopHeight(sidebarTopRef.current?.clientHeight || 0)
})
const sidebarItems = useMemo(() => {
return shownSidebar && "items" in shownSidebar
? shownSidebar.items
: shownSidebar?.children || []
}, [shownSidebar])
return (
<>
{mobileSidebarOpen && (
@@ -94,7 +91,11 @@ export const Sidebar = ({
<ul className={clsx("h-full w-full", "flex flex-col")}>
<SwitchTransition>
<CSSTransition
key={sidebarItems.parentItem?.title || "home"}
key={
sidebarHistory.length
? sidebarHistory[sidebarHistory.length - 1]
: sidebars[0].sidebar_id
}
nodeRef={sidebarRef}
classNames={{
enter: "animate-fadeInLeft animate-fast",
@@ -110,32 +111,12 @@ export const Sidebar = ({
ref={sidebarRef}
id="sidebar"
>
<SidebarTop
{...sidebarTopProps}
parentItem={sidebarItems.parentItem}
ref={sidebarTopRef}
/>
{/* MOBILE SIDEBAR - keeping this in case we need it in the future */}
{/* <div className={clsx("lg:hidden")}>
{!sidebarItems.mobile.length && !staticSidebarItems && (
<Loading className="px-0" />
)}
{sidebarItems.mobile.map((item, index) => (
<SidebarItem
item={item}
key={index}
expandItems={expandItems}
hasNextItems={index !== sidebarItems.default.length - 1}
/>
))}
{sidebarItems.mobile.length > 0 && <DottedSeparator />}
</div> */}
{/* DESKTOP SIDEBAR */}
<SidebarTop ref={sidebarTopRef} />
<div className="pt-docs_0.75">
{!sidebarItems.default.length && !staticSidebarItems && (
<Loading className="px-0" />
{!sidebarItems.length && !isSidebarStatic && (
<Loading className="px-docs_0.75" />
)}
{sidebarItems.default.map((item, index) => {
{sidebarItems.map((item, index) => {
const itemKey =
item.type === "separator"
? index
@@ -155,10 +136,7 @@ export const Sidebar = ({
>
<SidebarItem
item={item}
expandItems={expandItems}
hasNextItems={
index !== sidebarItems.default.length - 1
}
hasNextItems={index !== sidebarItems.length - 1}
/>
</Suspense>
)
@@ -9,17 +9,17 @@ import {
H3,
H4,
Hr,
isSidebarItemLink,
LocalSearch,
MarkdownContent,
SearchInput,
useIsBrowser,
useSidebar,
} from "../.."
import { InteractiveSidebarItem, SidebarItem, SidebarItemLink } from "types"
import { Sidebar } from "types"
import slugify from "slugify"
import { MDXComponents } from "../.."
import { ChevronDoubleRight, ExclamationCircle } from "@medusajs/icons"
import { isSidebarItemLink } from "../../utils/sidebar-utils"
type HeadingComponent = (
props: React.HTMLAttributes<HTMLHeadingElement>
@@ -60,11 +60,11 @@ export const useChildDocs = ({
...searchProps
} = { enable: false },
}: UseChildDocsProps) => {
const { currentItems, activeItem } = useSidebar()
const { shownSidebar, activeItem } = useSidebar()
const { isBrowser } = useIsBrowser()
const [searchQuery, setSearchQuery] = useState("")
const [localSearch, setLocalSearch] = useState<
LocalSearch<SidebarItemLink> | undefined
LocalSearch<Sidebar.SidebarItemLink> | undefined
>()
const TitleHeaderComponent = useCallback(
(level: number): HeadingComponent => {
@@ -91,7 +91,7 @@ export const useChildDocs = ({
: "all"
}, [showItems, hideItems])
const filterCondition = (item: SidebarItem): boolean => {
const filterCondition = (item: Sidebar.SidebarItem): boolean => {
if (item.type === "separator") {
return false
}
@@ -111,8 +111,10 @@ export const useChildDocs = ({
}
}
const filterItems = (items: SidebarItem[]): InteractiveSidebarItem[] => {
return (items.filter(filterCondition) as InteractiveSidebarItem[])
const filterItems = (
items: Sidebar.SidebarItem[]
): Sidebar.InteractiveSidebarItem[] => {
return (items.filter(filterCondition) as Sidebar.InteractiveSidebarItem[])
.map((item) => Object.assign({}, item))
.map((item) => {
if (item.children && filterType === "hide") {
@@ -124,19 +126,19 @@ export const useChildDocs = ({
}
const filterNonInteractiveItems = (
items: SidebarItem[] | undefined
): InteractiveSidebarItem[] => {
items: Sidebar.SidebarItem[] | undefined
): Sidebar.InteractiveSidebarItem[] => {
return (
(items?.filter(
(item) => item.type !== "separator"
) as InteractiveSidebarItem[]) || []
) as Sidebar.InteractiveSidebarItem[]) || []
)
}
const getChildrenForLevel = (
item: InteractiveSidebarItem,
item: Sidebar.InteractiveSidebarItem,
currentLevel = 1
): InteractiveSidebarItem[] | undefined => {
): Sidebar.InteractiveSidebarItem[] | undefined => {
if (currentLevel === childLevel) {
return filterNonInteractiveItems(item.children)
}
@@ -144,7 +146,7 @@ export const useChildDocs = ({
return
}
const childrenResult: InteractiveSidebarItem[] = []
const childrenResult: Sidebar.InteractiveSidebarItem[] = []
filterNonInteractiveItems(item.children).forEach((child) => {
const childChildren = getChildrenForLevel(child, currentLevel + 1)
@@ -162,34 +164,24 @@ export const useChildDocs = ({
const filteredItems = useMemo(() => {
let targetItems =
type === "sidebar"
? currentItems
? Object.assign({}, currentItems)
: undefined
: {
default: [...(activeItem?.children || [])],
}
? shownSidebar && "items" in shownSidebar
? shownSidebar.items
: shownSidebar?.children || []
: [...(activeItem?.children || [])]
if (filterType !== "all" && targetItems) {
targetItems = {
...targetItems,
default: filterItems(targetItems.default),
}
targetItems = filterItems(targetItems)
}
return targetItems
? {
...targetItems,
default: filterNonInteractiveItems(targetItems?.default),
}
: undefined
}, [currentItems, type, activeItem, filterType])
return filterNonInteractiveItems(targetItems)
}, [shownSidebar, type, activeItem, filterType])
const searchableItems = useMemo(() => {
const searchableItems: SidebarItemLink[] = []
const searchableItems: Sidebar.SidebarItemLink[] = []
if (!enableSearch) {
return searchableItems
}
if (onlyTopLevel) {
filteredItems?.default?.forEach((item) => {
filteredItems.forEach((item) => {
if (isSidebarItemLink(item)) {
searchableItems.push(item)
} else {
@@ -197,16 +189,16 @@ export const useChildDocs = ({
isSidebarItemLink(child)
)
if (firstChild) {
searchableItems.push(firstChild as SidebarItemLink)
searchableItems.push(firstChild as Sidebar.SidebarItemLink)
}
}
})
} else {
filteredItems?.default?.forEach((item) => {
const childItems: SidebarItemLink[] =
filteredItems?.forEach((item) => {
const childItems: Sidebar.SidebarItemLink[] =
(getChildrenForLevel(item)?.filter((childItem) => {
return isSidebarItemLink(childItem)
}) as SidebarItemLink[]) || []
}) as Sidebar.SidebarItemLink[]) || []
searchableItems.push(...childItems)
})
}
@@ -224,7 +216,7 @@ export const useChildDocs = ({
}
setLocalSearch(
getLocalSearch<SidebarItemLink>({
getLocalSearch<Sidebar.SidebarItemLink>({
docs: searchableItems,
searchableFields: ["title", "description"],
options: {
@@ -263,7 +255,7 @@ export const useChildDocs = ({
localStorage.setItem(`${storageKey}-query`, searchQuery)
}, [isBrowser, searchQuery, storageKey, enableSearch])
const getTopLevelElms = (items?: InteractiveSidebarItem[]) => {
const getTopLevelElms = (items?: Sidebar.InteractiveSidebarItem[]) => {
return (
<CardList
items={
@@ -274,7 +266,7 @@ export const useChildDocs = ({
? (
childItem.children.find((item) =>
isSidebarItemLink(item)
) as SidebarItemLink
) as Sidebar.SidebarItemLink
)?.path
: "#"
return {
@@ -292,7 +284,7 @@ export const useChildDocs = ({
}
const getAllLevelsElms = (
items?: InteractiveSidebarItem[],
items?: Sidebar.InteractiveSidebarItem[],
headerLevel = titleLevel
) => {
return items?.map((item, key) => {
@@ -405,8 +397,8 @@ export const useChildDocs = ({
{!searchQuery && (
<>
{onlyTopLevel
? getTopLevelElms(filteredItems?.default)
: getAllLevelsElms(filteredItems?.default)}
? getTopLevelElms(filteredItems)
: getAllLevelsElms(filteredItems)}
</>
)}
</>
@@ -1,9 +1,8 @@
"use client"
import React, { useEffect } from "react"
import { useSidebar } from "../providers/Sidebar"
import clsx from "clsx"
import { MainNav, useIsBrowser, useLayout } from ".."
import { MainNav, useIsBrowser, useLayout, useSidebar } from ".."
export type MainContentLayoutProps = {
mainWrapperClasses?: string
@@ -1,15 +1,10 @@
"use client"
import React, {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from "react"
import { isSidebarItemLink, useSidebar } from "../Sidebar"
import React, { createContext, useContext, useEffect, useState } from "react"
import { usePrevious } from "@uidotdev/usehooks"
import { InteractiveSidebarItem, SidebarItem } from "types"
import { useSidebar } from "../Sidebar"
import { isSidebarItemLink } from "../../utils/sidebar-utils"
import { Sidebar } from "types"
export type Page = {
title: string
@@ -27,8 +22,8 @@ export const PaginationContext = createContext<PaginationContextType | null>(
null
)
type SidebarItemWithParent = InteractiveSidebarItem & {
parent?: SidebarItem
type SidebarItemWithParent = Sidebar.InteractiveSidebarItem & {
parent?: Sidebar.InteractiveSidebarItem
}
type SearchItemsResult = {
@@ -42,14 +37,13 @@ export type PaginationProviderProps = {
}
export const PaginationProvider = ({ children }: PaginationProviderProps) => {
const { items, activePath } = useSidebar()
const combinedItems = useMemo(() => [...items.default], [items])
const { shownSidebar, activePath } = useSidebar()
const previousActivePath = usePrevious(activePath)
const [nextPage, setNextPage] = useState<Page | undefined>()
const [prevPage, setPrevPage] = useState<Page | undefined>()
const getFirstChild = (
item: InteractiveSidebarItem
item: Sidebar.InteractiveSidebarItem
): SidebarItemWithParent | undefined => {
const children = getChildrenWithPages(item)
if (!children?.length) {
@@ -65,7 +59,7 @@ export const PaginationProvider = ({ children }: PaginationProviderProps) => {
}
const getChildrenWithPages = (
item: InteractiveSidebarItem
item: Sidebar.InteractiveSidebarItem
): SidebarItemWithParent[] | undefined => {
return item.children?.filter(
(childItem) =>
@@ -76,7 +70,7 @@ export const PaginationProvider = ({ children }: PaginationProviderProps) => {
}
const getPrevItem = (
items: SidebarItem[],
items: Sidebar.SidebarItem[],
index: number
): SidebarItemWithParent | undefined => {
let foundItem: SidebarItemWithParent | undefined
@@ -106,7 +100,7 @@ export const PaginationProvider = ({ children }: PaginationProviderProps) => {
}
const getNextItem = (
items: SidebarItem[],
items: Sidebar.SidebarItem[],
index: number
): SidebarItemWithParent | undefined => {
let foundItem: SidebarItemWithParent | undefined
@@ -133,7 +127,9 @@ export const PaginationProvider = ({ children }: PaginationProviderProps) => {
return foundItem
}
const searchItems = (currentItems: SidebarItem[]): SearchItemsResult => {
const searchItems = (
currentItems: Sidebar.SidebarItem[]
): SearchItemsResult => {
const result: SearchItemsResult = {
foundActive: false,
}
@@ -182,7 +178,11 @@ export const PaginationProvider = ({ children }: PaginationProviderProps) => {
useEffect(() => {
if (activePath !== previousActivePath) {
const result = searchItems(combinedItems)
const sidebarItems =
shownSidebar && "items" in shownSidebar
? shownSidebar.items
: shownSidebar?.children || []
const result = searchItems(sidebarItems)
setPrevPage(
result.prevItem
? {
@@ -190,10 +190,7 @@ export const PaginationProvider = ({ children }: PaginationProviderProps) => {
link: isSidebarItemLink(result.prevItem)
? result.prevItem.path
: "",
parentTitle:
result.prevItem.parent?.type !== "separator"
? result.prevItem.parent?.title
: undefined,
parentTitle: result.prevItem.parent?.title,
}
: undefined
)
@@ -204,10 +201,7 @@ export const PaginationProvider = ({ children }: PaginationProviderProps) => {
link: isSidebarItemLink(result.nextItem)
? result.nextItem.path
: "",
parentTitle:
result.nextItem.parent?.type !== "separator"
? result.nextItem.parent?.title
: undefined,
parentTitle: result.nextItem?.parent?.title,
}
: undefined
)
File diff suppressed because it is too large Load Diff
@@ -25,17 +25,11 @@ export const SiteConfigProvider = ({
Object.assign(
{
baseUrl: "",
sidebar: {
default: [],
mobile: [],
},
sidebars: [],
project: {
title: "",
key: "",
},
breadcrumbOptions: {
showCategories: true,
},
reportIssueLink: GITHUB_ISSUES_LINK,
logo: "",
},
@@ -21,12 +21,16 @@ export const getLocalSearch = <T extends BaseSearchRecord = BaseSearchRecord>({
docs,
searchableFields,
options,
}: GetLocalSearchInput<T>): LocalSearch<T> => {
const miniSearch = new MiniSearch({
fields: searchableFields,
...options,
})
miniSearch.addAll(docs)
}: GetLocalSearchInput<T>): LocalSearch<T> | undefined => {
try {
const miniSearch = new MiniSearch({
fields: searchableFields,
...options,
})
miniSearch.addAll(docs)
return miniSearch as LocalSearch<T>
return miniSearch as LocalSearch<T>
} catch (e) {
console.warn(e)
}
}
+1 -1
View File
@@ -12,6 +12,6 @@ export * from "./is-elm-window"
export * from "./is-in-view"
export * from "./learning-paths"
export * from "./set-obj-value"
export * from "./sidebar-attach-href-common-options"
export * from "./sidebar-utils"
export * from "./swr-fetcher"
export * from "./workflow-diagram-utils"
@@ -1,22 +0,0 @@
import { RawSidebarItem } from "types"
const commonOptions: Partial<RawSidebarItem> = {
loaded: true,
isPathHref: true,
}
export function sidebarAttachHrefCommonOptions(
sidebar: RawSidebarItem[]
): RawSidebarItem[] {
return sidebar.map((item) => {
if (item.type === "separator") {
return item
}
return {
...commonOptions,
...item,
children: sidebarAttachHrefCommonOptions(item.children || []),
}
})
}
@@ -0,0 +1,129 @@
import { Sidebar } from "types"
export const isSidebarItemLink = (
item: Sidebar.SidebarItem | undefined,
options?: {
checkRef?: boolean
checkExternal?: boolean
}
): item is Sidebar.SidebarItemLink => {
const { checkRef = true, checkExternal = true } = options || {}
return (
item !== undefined &&
(item.type === "link" ||
(checkRef && item.type === "ref") ||
(checkExternal && item.type === "external"))
)
}
export const areSidebarItemsEqual = ({
itemA,
itemB,
compareTitles = true,
}: {
itemA: Sidebar.SidebarItem
itemB: Sidebar.SidebarItem
compareTitles?: boolean
}): boolean => {
if (itemA.type !== itemB.type) {
return false
}
// after this, we know that itemA and itemB have the same type
switch (itemA.type) {
case "separator":
return true
case "sidebar":
return (
itemA.sidebar_id === (itemB as Sidebar.SidebarItemSidebar).sidebar_id
)
case "category":
case "sub-category":
return compareTitles
? itemA.title === (itemB as Sidebar.SidebarItemCategory).title
: false
case "link":
case "ref":
case "external": {
const hasSameTitle =
!compareTitles ||
itemA.title === (itemB as Sidebar.SidebarItemLink).title
const hasSamePath = itemA.path === (itemB as Sidebar.SidebarItemLink).path
return hasSameTitle && hasSamePath
}
}
}
export const findSidebarItem = ({
sidebarItems,
item,
checkChildren = true,
compareTitles = true,
}: {
sidebarItems: Sidebar.SidebarItem[]
item: Sidebar.SidebarItem
checkChildren?: boolean
compareTitles?: boolean
}): Sidebar.SidebarItem | undefined => {
let foundItem: Sidebar.SidebarItem | undefined
sidebarItems.some((i) => {
if (areSidebarItemsEqual({ itemA: i, itemB: item })) {
foundItem = i
} else if (checkChildren && "children" in i && i.children) {
foundItem = findSidebarItem({
sidebarItems: i.children,
item,
checkChildren,
compareTitles,
})
}
return foundItem !== undefined
})
return foundItem
}
export const getSidebarItemWithHistory = ({
sidebarItems,
item,
sidebarHistory = [],
checkChildren = true,
compareTitles = true,
}: {
sidebarItems: Sidebar.SidebarItem[]
item: Sidebar.SidebarItem
sidebarHistory?: string[]
checkChildren?: boolean
compareTitles?: boolean
}): {
item: Sidebar.SidebarItem | undefined
sidebarHistory: string[]
} => {
let foundItem: Sidebar.SidebarItem | undefined
sidebarItems.some((i) => {
if (areSidebarItemsEqual({ itemA: i, itemB: item, compareTitles })) {
foundItem = i
} else if (checkChildren && "children" in i && i.children) {
const result = getSidebarItemWithHistory({
sidebarItems: i.children,
item,
checkChildren,
compareTitles,
})
if (result.item) {
foundItem = result.item
if (i.type === "sidebar") {
sidebarHistory.push(i.sidebar_id)
}
sidebarHistory.push(...result.sidebarHistory)
}
}
return foundItem !== undefined
})
return { item: foundItem, sidebarHistory }
}