docs: redesign sidebar (#8408)

* initial changes

* redesign the sidebar + nav drawer

* changes to sidebar items

* finish up sidebar redesign

* support new sidebar in resources

* general fixes

* integrate in ui

* support api reference

* refactor

* integrate in user guide

* docs: fix build errors

* fix user guide build

* more refactoring

* added banner

* added bottom logo + icon

* fix up sidebar

* fix up paddings

* fix shadow bottom

* docs: add table of content (#8445)

* add toc types

* implement toc functionality

* finished toc redesign

* redesigned table of content

* mobile fixes

* truncate text in toc

* mobile fixes

* merge fixes

* implement redesign

* add hide sidebar

* add menu action item

* finish up hide sidebar design

* implement redesign in resources

* integrate in api reference

* integrate changes in ui

* fixes to api reference scrolling

* fix build error

* fix build errors

* fixes

* fixes to sidebar

* general fixes

* fix active category not closing

* fix long titles
This commit is contained in:
Shahed Nasser
2024-08-15 12:13:13 +03:00
committed by GitHub
parent 4cb28531e5
commit b4f3b8a79d
157 changed files with 5080 additions and 2010 deletions
@@ -1,26 +0,0 @@
"use client"
import React from "react"
import { useSidebar } from "../../../providers"
import clsx from "clsx"
import { ArrowUturnLeft } from "@medusajs/icons"
export const SidebarBack = () => {
const { goBack } = useSidebar()
return (
<div
className={clsx(
"mb-docs_1.5 cursor-pointer",
"flex items-center gap-docs_0.5 rounded-docs_sm px-docs_0.5 py-[6px] hover:no-underline",
"border border-transparent",
"text-medusa-fg-subtle text-medium-plus"
)}
tabIndex={-1}
onClick={goBack}
>
<ArrowUturnLeft className="mr-docs_0.5" />
<span>Back</span>
</div>
)
}
@@ -0,0 +1,34 @@
"use client"
import React from "react"
import clsx from "clsx"
import { InteractiveSidebarItem } from "types"
import { ArrowUturnLeft } from "@medusajs/icons"
import { useSidebar } from "../../.."
type SidebarTitleProps = {
item: InteractiveSidebarItem
}
export const SidebarChild = ({ item }: SidebarTitleProps) => {
const { goBack } = useSidebar()
return (
<div className="px-docs_0.75">
<div
onClick={goBack}
className={clsx(
"flex items-center justify-start my-docs_0.75 gap-[10px]",
"border border-transparent cursor-pointer mx-docs_0.5",
"!text-medusa-fg-base !text-compact-small-plus"
)}
tabIndex={-1}
>
<ArrowUturnLeft />
<span className="truncate flex-1">
{item.childSidebarTitle || item.title}
</span>
</div>
</div>
)
}
@@ -0,0 +1,115 @@
"use client"
// @refresh reset
import React, { useEffect, useMemo, useRef, useState } from "react"
import { SidebarItemCategory as SidebarItemCategoryType } from "types"
import { Loading, SidebarItem, useSidebar } from "../../../.."
import clsx from "clsx"
import { MinusMini, PlusMini } from "@medusajs/icons"
export type SidebarItemCategory = {
item: SidebarItemCategoryType
expandItems?: boolean
} & React.AllHTMLAttributes<HTMLDivElement>
export const SidebarItemCategory = ({
item,
expandItems = true,
className,
}: SidebarItemCategory) => {
const [showLoading, setShowLoading] = useState(false)
const [open, setOpen] = useState(
item.initialOpen !== undefined ? item.initialOpen : expandItems
)
const childrenRef = useRef<HTMLUListElement>(null)
const { isChildrenActive } = useSidebar()
useEffect(() => {
if (open && !item.loaded) {
setShowLoading(true)
}
}, [open])
useEffect(() => {
if (item.loaded && showLoading) {
setShowLoading(false)
}
}, [item])
useEffect(() => {
const isActive = isChildrenActive(item)
if (isActive && !open) {
setOpen(true)
}
}, [isChildrenActive])
const handleOpen = () => {
item.onOpen?.()
}
const isTitleOneWord = useMemo(
() => item.title.split(" ").length === 1,
[item.title]
)
return (
<div className={clsx("my-docs_0.75 w-full relative", className)}>
<div className="px-docs_0.75">
<div
className={clsx(
"py-docs_0.25 px-docs_0.5",
"flex justify-between items-center gap-docs_0.5",
"text-medusa-fg-muted",
"cursor-pointer relative",
"z-[2]",
!isTitleOneWord && "break-words"
)}
tabIndex={-1}
onClick={() => {
if (!open) {
handleOpen()
}
setOpen((prev) => !prev)
}}
>
<span
className={clsx(
"text-compact-x-small-plus",
isTitleOneWord && "truncate"
)}
>
{item.title}
</span>
{item.additionalElms}
{!item.additionalElms && (
<>
{open && <MinusMini />}
{!open && <PlusMini />}
</>
)}
</div>
</div>
<ul
className={clsx(
"ease-ease",
"flex flex-col gap-docs_0.125",
"z-[1] relative",
!open && "overflow-hidden m-0 h-0"
)}
ref={childrenRef}
>
{showLoading && (
<Loading
count={3}
className="!mb-0 !px-docs_0.5"
barClassName="h-[20px]"
/>
)}
{item.children?.map((childItem, index) => (
<SidebarItem item={childItem} key={index} expandItems={expandItems} />
))}
</ul>
</div>
)
}
@@ -0,0 +1,147 @@
"use client"
// @refresh reset
import React, { useCallback, useEffect, useMemo, useRef } from "react"
import { SidebarItemLink as SidebarItemlinkType } from "types"
import {
checkSidebarItemVisibility,
SidebarItem,
useMobile,
useSidebar,
} from "../../../.."
import clsx from "clsx"
import Link from "next/link"
export type SidebarItemLinkProps = {
item: SidebarItemlinkType
nested?: boolean
} & React.AllHTMLAttributes<HTMLLIElement>
export const SidebarItemLink = ({
item,
className,
nested = false,
}: SidebarItemLinkProps) => {
const {
isLinkActive,
setMobileSidebarOpen: setSidebarOpen,
disableActiveTransition,
sidebarRef,
sidebarTopHeight,
} = useSidebar()
const { isMobile } = useMobile()
const active = useMemo(() => isLinkActive(item, true), [isLinkActive, item])
const ref = useRef<HTMLLIElement>(null)
const newTopCalculator = useCallback(() => {
if (!sidebarRef.current || !ref.current) {
return 0
}
const sidebarBoundingRect = sidebarRef.current.getBoundingClientRect()
const itemBoundingRect = ref.current.getBoundingClientRect()
return (
itemBoundingRect.top -
(sidebarBoundingRect.top + sidebarTopHeight) +
sidebarRef.current.scrollTop
)
}, [sidebarTopHeight, sidebarRef, ref])
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(),
})
}
}
}, [
active,
sidebarRef.current,
disableActiveTransition,
isMobile,
newTopCalculator,
])
const hasChildren = useMemo(() => {
return !item.isChildSidebar && (item.children?.length || 0) > 0
}, [item.children])
const isTitleOneWord = useMemo(
() => item.title.split(" ").length === 1,
[item.title]
)
return (
<li ref={ref}>
<span className="block px-docs_0.75">
<Link
href={item.isPathHref ? item.path : `#${item.path}`}
className={clsx(
"py-docs_0.25 px-docs_0.5",
"block w-full rounded-docs_sm",
!isTitleOneWord && "break-words",
active && [
"bg-medusa-bg-base",
"shadow-elevation-card-rest dark:shadow-elevation-card-rest-dark",
"text-medusa-fg-base",
],
!active && [
!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
)}
scroll={true}
onClick={() => {
if (isMobile) {
setSidebarOpen(false)
}
}}
replace={!item.isPathHref}
shallow={!item.isPathHref}
{...item.linkProps}
>
<span className={clsx(isTitleOneWord && "truncate")}>
{item.title}
</span>
{item.additionalElms}
</Link>
</span>
{hasChildren && (
<ul
className={clsx(
"ease-ease overflow-hidden",
"flex flex-col gap-docs_0.125",
!item.childrenSameLevel && "pl-docs_1.5",
"pt-docs_0.125 pb-docs_0.5"
)}
>
{item.children!.map((childItem, index) => (
<SidebarItem
item={childItem}
key={index}
nested={!item.childrenSameLevel}
/>
))}
</ul>
)}
</li>
)
}
@@ -0,0 +1,145 @@
"use client"
// @refresh reset
import React, { useEffect, useMemo, useRef } from "react"
import { SidebarItemSubCategory as SidebarItemSubCategoryType } from "types"
import {
checkSidebarItemVisibility,
SidebarItem,
useMobile,
useSidebar,
} from "../../../.."
import clsx from "clsx"
export type SidebarItemLinkProps = {
item: SidebarItemSubCategoryType
nested?: 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]
)
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.children?.length || 0 > 0
}, [item.children])
const isTitleOneWord = useMemo(
() => item.title.split(" ").length === 1,
[item.title]
)
return (
<li ref={ref}>
<span className="block px-docs_0.75">
<span
className={clsx(
"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",
],
"text-compact-small-plus",
className
)}
>
<span className={clsx(isTitleOneWord && "truncate")}>
{item.title}
</span>
{item.additionalElms}
</span>
</span>
{hasChildren && (
<ul
className={clsx(
"ease-ease overflow-hidden",
"flex flex-col gap-docs_0.125",
!item.childrenSameLevel && "pl-docs_1.5",
"pb-docs_0.5 pt-docs_0.125"
)}
>
{item.children!.map((childItem, index) => (
<SidebarItem
item={childItem}
key={index}
nested={!item.childrenSameLevel}
/>
))}
</ul>
)}
</li>
)
}
@@ -1,204 +1,37 @@
"use client"
// @refresh reset
import React, { useEffect, useMemo, useRef, useState } from "react"
import { useSidebar } from "@/providers"
import clsx from "clsx"
import Link from "next/link"
import { checkSidebarItemVisibility } from "@/utils"
import { Loading } from "@/components"
import { SidebarItemType } from "types"
import React from "react"
import { SidebarItem as SidebarItemType } from "types"
import { SidebarItemCategory } from "./Category"
import { SidebarItemLink } from "./Link"
import { SidebarItemSubCategory } from "./SubCategory"
import { SidebarSeparator } from "../Separator"
export type SidebarItemProps = {
item: SidebarItemType
nested?: boolean
expandItems?: boolean
currentLevel?: number
isSidebarTitle?: boolean
sidebarHasParent?: boolean
isMobile?: boolean
} & React.AllHTMLAttributes<HTMLLIElement>
hasNextItems?: boolean
} & React.AllHTMLAttributes<HTMLElement>
export const SidebarItem = ({
item,
nested = false,
expandItems = false,
className,
currentLevel = 1,
sidebarHasParent = false,
isMobile = false,
hasNextItems = false,
...props
}: SidebarItemProps) => {
const [showLoading, setShowLoading] = useState(false)
const {
isItemActive,
setMobileSidebarOpen: setSidebarOpen,
disableActiveTransition,
noTitleStyling,
sidebarRef,
} = useSidebar()
const active = useMemo(
() => !isMobile && isItemActive(item, nested),
[isItemActive, item, nested, isMobile]
)
const collapsed = !expandItems && !isItemActive(item, true)
const ref = useRef<HTMLLIElement>(null)
const itemChildren = useMemo(() => {
return item.isChildSidebar ? undefined : item.children
}, [item])
const canHaveTitleStyling = useMemo(
() =>
item.hasTitleStyling ||
((itemChildren?.length || !item.loaded) && !noTitleStyling && !nested),
[itemChildren, noTitleStyling, item, nested]
)
const classNames = useMemo(
() =>
clsx(
"flex items-center justify-between gap-docs_0.5 rounded-docs_xs px-docs_0.5 py-[6px]",
"hover:no-underline hover:bg-medusa-bg-component-hover",
!canHaveTitleStyling && "text-compact-small-plus text-medusa-fg-subtle",
canHaveTitleStyling &&
"text-compact-x-small-plus text-medusa-fg-muted uppercase",
!item.path && "cursor-default",
item.path !== undefined &&
active &&
"text-medusa-fg-base bg-medusa-bg-component-hover"
),
[canHaveTitleStyling, active, item.path]
)
/**
* 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
switch (item.type) {
case "category":
return (
<>
<SidebarItemCategory item={item} {...props} />
{hasNextItems && <SidebarSeparator />}
</>
)
case "sub-category":
return <SidebarItemSubCategory item={item} {...props} />
case "link":
return <SidebarItemLink item={item} {...props} />
case "separator":
return <SidebarSeparator />
}
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(),
})
}
}
if (active) {
setShowLoading(true)
}
}, [active, sidebarRef.current, disableActiveTransition])
return (
<li
className={clsx(
canHaveTitleStyling &&
!collapsed && [
!sidebarHasParent && "my-docs_1.5",
sidebarHasParent && "[&:not(:first-child)]:my-docs_1.5",
],
!canHaveTitleStyling &&
!nested &&
active &&
!disableActiveTransition &&
"mt-docs_1.5",
!expandItems &&
((canHaveTitleStyling && !collapsed) ||
(!canHaveTitleStyling && !nested && active)) &&
"-translate-y-docs_1 transition-transform",
className
)}
ref={ref}
>
{item.path === undefined && (
<span className={classNames}>
<span>{item.title}</span>
{item.additionalElms}
</span>
)}
{item.path !== undefined && (
<Link
href={item.isPathHref ? item.path : `#${item.path}`}
className={classNames}
scroll={true}
onClick={() => {
if (window.innerWidth < 1025) {
setSidebarOpen(false)
}
}}
replace={!item.isPathHref}
shallow={!item.isPathHref}
{...item.linkProps}
>
<span>{item.title}</span>
{item.additionalElms}
</Link>
)}
{itemChildren && (
<ul
className={clsx("ease-ease overflow-hidden", collapsed && "m-0 h-0")}
style={{
paddingLeft: noTitleStyling ? `${currentLevel * 6}px` : 0,
}}
>
{showLoading && !item.loaded && (
<Loading
count={3}
className="!mb-0 !px-docs_0.5"
barClassName="h-[20px]"
/>
)}
{itemChildren?.map((childItem, index) => (
<SidebarItem
item={childItem}
key={index}
nested={true}
currentLevel={currentLevel + 1}
expandItems={expandItems}
/>
))}
</ul>
)}
</li>
)
}
@@ -0,0 +1,22 @@
"use client"
import clsx from "clsx"
import React from "react"
export type SidebarSeparatorProps = {
className?: string
}
export const SidebarSeparator = ({ className }: SidebarSeparatorProps) => {
return (
<div className="px-docs_0.75">
<span
className={clsx(
"block w-full h-px relative my-docs_0.75 bg-border-dotted",
"bg-[length:4px_1px] bg-repeat-x bg-bottom",
className
)}
></span>
</div>
)
}
@@ -1,27 +0,0 @@
import React from "react"
import Link from "next/link"
import clsx from "clsx"
import { SidebarItemType } from "types"
type SidebarTitleProps = {
item: SidebarItemType
}
export const SidebarTitle = ({ item }: SidebarTitleProps) => {
return (
<Link
className={clsx(
"flex items-center justify-between gap-docs_0.5 rounded-docs_sm px-docs_0.5 pb-[6px] hover:no-underline",
"border border-transparent",
"text-medusa-fg-subtle text-large-plus"
)}
href={item.isPathHref && item.path ? item.path : `#${item.path}`}
replace={!item.isPathHref}
shallow={!item.isPathHref}
{...item.linkProps}
>
<span>{item.childSidebarTitle || item.title}</span>
{item.additionalElms}
</Link>
)
}
@@ -0,0 +1,90 @@
"use client"
import clsx from "clsx"
import React, { useRef, useState } from "react"
import {
BorderedIcon,
getOsShortcut,
useClickOutside,
useSidebar,
} from "../../../.."
import {
EllipsisHorizontal,
SidebarLeft,
TimelineVertical,
} from "@medusajs/icons"
import { MedusaIcon } from "../../../Icons/MedusaLogo"
import { HouseIcon } from "../../../Icons/House"
import { Menu } from "../../../Menu"
export const SidebarTopMedusaMenu = () => {
const [openMenu, setOpenMenu] = useState(false)
const { setDesktopSidebarOpen } = useSidebar()
const ref = useRef<HTMLDivElement>(null)
const toggleOpen = () => setOpenMenu((prev) => !prev)
useClickOutside({
elmRef: ref,
onClickOutside: () => {
setOpenMenu(false)
},
})
return (
<div className={clsx("p-docs_0.75", "relative")} ref={ref}>
<div
className={clsx(
"flex justify-between items-center gap-docs_0.5",
"rounded-docs_sm hover:bg-medusa-bg-subtle-hover cursor-pointer",
"py-docs_0.125 pl-docs_0.125 pr-docs_0.5"
)}
tabIndex={-1}
onClick={toggleOpen}
>
<BorderedIcon IconComponent={MedusaIcon} />
<span className="text-compact-small-plus text-medusa-fg-base flex-1">
Medusa Docs
</span>
<EllipsisHorizontal className="text-medusa-fg-subtle" />
</div>
<div
className={clsx(
"absolute w-[calc(100%-16px)] bottom-[calc(-100%-40px)]",
"left-docs_0.5 z-40",
!openMenu && "hidden"
)}
>
<Menu
items={[
{
type: "link",
icon: <HouseIcon />,
title: "Homepage",
link: "https://medusajs.com",
},
{
type: "link",
icon: <TimelineVertical />,
title: "Changelog",
link: "https://medusajs.com/changelog",
},
{
type: "divider",
},
{
type: "action",
title: "Hide Sidebar",
icon: <SidebarLeft />,
shortcut: `${getOsShortcut()}.`,
action: () => {
setDesktopSidebarOpen(false)
setOpenMenu(false)
},
},
]}
/>
</div>
</div>
)
}
@@ -0,0 +1,21 @@
"use client"
import React from "react"
import { Button, useSidebar } from "../../../.."
import { XMarkMini } from "@medusajs/icons"
export const SidebarTopMobileClose = () => {
const { setMobileSidebarOpen } = useSidebar()
return (
<div className="m-docs_0.75 lg:hidden">
<Button
variant="transparent-clear"
onClick={() => setMobileSidebarOpen(false)}
className="!p-0 hover:!bg-transparent"
>
<XMarkMini className="text-medusa-fg-subtle" />
</Button>
</div>
)
}
@@ -0,0 +1,32 @@
"use client"
import React from "react"
import { SidebarChild } from "../Child"
import { InteractiveSidebarItem } from "types"
import { SidebarSeparator } from "../Separator"
import { SidebarTopMobileClose } from "./MobileClose"
import { SidebarTopMedusaMenu } from "./MedusaMenu"
export type SidebarTopProps = {
parentItem?: InteractiveSidebarItem
}
export const SidebarTop = React.forwardRef<HTMLDivElement, SidebarTopProps>(
function SidebarTop({ parentItem }, ref) {
return (
<div className="pt-docs_0.25">
<SidebarTopMobileClose />
<div ref={ref}>
<SidebarTopMedusaMenu />
{parentItem && (
<>
<SidebarSeparator />
<SidebarChild item={parentItem} />
</>
)}
<SidebarSeparator className="!my-0" />
</div>
</div>
)
}
)
@@ -5,125 +5,153 @@ import { useSidebar } from "@/providers"
import clsx from "clsx"
import { Loading } from "@/components"
import { SidebarItem } from "./Item"
import { SidebarTitle } from "./Title"
import { SidebarBack } from "./Back"
import { CSSTransition, SwitchTransition } from "react-transition-group"
import { SidebarTop, SidebarTopProps } from "./Top"
import useResizeObserver from "@react-hook/resize-observer"
import { SidebarSeparator } from "./Separator"
import { useClickOutside, useKeyboardShortcut } from "@/hooks"
export type SidebarProps = {
className?: string
expandItems?: boolean
banner?: React.ReactNode
sidebarTopProps?: Omit<SidebarTopProps, "parentItem">
}
export const Sidebar = ({
className = "",
expandItems = false,
banner,
expandItems = true,
sidebarTopProps,
}: SidebarProps) => {
const sidebarWrapperRef = useRef(null)
const sidebarTopRef = useRef<HTMLDivElement>(null)
const {
items,
currentItems,
mobileSidebarOpen,
desktopSidebarOpen,
setMobileSidebarOpen,
staticSidebarItems,
sidebarRef,
sidebarTopHeight,
setSidebarTopHeight,
desktopSidebarOpen,
setDesktopSidebarOpen,
} = useSidebar()
useClickOutside({
elmRef: sidebarWrapperRef,
onClickOutside: () => {
if (mobileSidebarOpen) {
setMobileSidebarOpen(false)
}
},
})
useKeyboardShortcut({
metakey: true,
shortcutKeys: ["."],
action: () => {
setDesktopSidebarOpen((prev) => !prev)
},
})
const sidebarItems = useMemo(
() => currentItems || items,
[items, currentItems]
)
const sidebarHasParent = useMemo(
() => sidebarItems.parentItem !== undefined,
[sidebarItems]
)
useResizeObserver(sidebarTopRef, () => {
setSidebarTopHeight(sidebarTopRef.current?.clientHeight || 0)
})
return (
<aside
className={clsx(
"clip bg-docs-bg dark:bg-docs-bg-dark block",
"border-medusa-border-base border-0 border-r border-solid",
"fixed -left-full top-0 h-screen transition-[left] lg:relative lg:left-0 lg:top-auto lg:h-auto",
"lg:w-sidebar w-full",
mobileSidebarOpen && "!left-0 z-50 top-[57px]",
!desktopSidebarOpen && "!absolute !-left-full",
className
<>
{mobileSidebarOpen && (
<div
className={clsx(
"lg:hidden bg-medusa-bg-overlay opacity-70",
"fixed top-0 left-0 w-full h-full z-10"
)}
></div>
)}
style={{
animationFillMode: "forwards",
}}
>
<ul
<aside
className={clsx(
"sticky top-0 h-screen max-h-screen w-full list-none p-0",
"px-docs_1.5 pb-[57px] pt-docs_1.5",
"flex flex-col"
"bg-medusa-bg-base lg:bg-transparent block",
"fixed -left-full top-0 h-[calc(100%-16px)] transition-[left] lg:relative lg:h-auto",
"max-w-sidebar-xs sm:max-w-sidebar-sm md:max-w-sidebar-md lg:max-w-sidebar-lg",
"xl:max-w-sidebar-xl xxl:max-w-sidebar-xxl xxxl:max-w-sidebar-xxxl",
mobileSidebarOpen && [
"!left-docs_0.5 !top-docs_0.5 z-50 shadow-elevation-modal dark:shadow-elevation-modal-dark",
"rounded",
"lg:!left-0 lg:!top-0 lg:shadow-none",
],
desktopSidebarOpen && "lg:left-0",
!desktopSidebarOpen && "lg:!absolute lg:!-left-full",
className
)}
id="sidebar"
style={{
animationFillMode: "forwards",
}}
ref={sidebarWrapperRef}
>
{banner && <div className="mb-docs_1">{banner}</div>}
{sidebarItems.parentItem && (
<div className={clsx("mb-docs_1", !banner && "mt-docs_1.5")}>
<SidebarBack />
<SidebarTitle item={sidebarItems.parentItem} />
</div>
)}
<SwitchTransition>
<CSSTransition
key={sidebarItems.parentItem?.title || "home"}
nodeRef={sidebarRef}
classNames={{
enter: "animate-fadeInLeft animate-fast",
exit: "animate-fadeOutLeft animate-fast",
}}
timeout={200}
>
<div className="overflow-auto" ref={sidebarRef}>
<div className={clsx("mb-docs_1.5 lg:hidden")}>
{!sidebarItems.mobile.length && !staticSidebarItems && (
<Loading className="px-0" />
<ul className={clsx("h-full w-full", "flex flex-col")}>
<SidebarTop
{...sidebarTopProps}
parentItem={sidebarItems.parentItem}
ref={sidebarTopRef}
/>
<SwitchTransition>
<CSSTransition
key={sidebarItems.parentItem?.title || "home"}
nodeRef={sidebarRef}
classNames={{
enter: "animate-fadeInLeft animate-fast",
exit: "animate-fadeOutLeft animate-fast",
}}
timeout={200}
>
<div
className={clsx(
"overflow-y-scroll clip",
"py-docs_0.75 flex-1"
)}
{sidebarItems.mobile.map((item, index) => (
<SidebarItem
item={item}
key={index}
expandItems={expandItems}
sidebarHasParent={sidebarHasParent}
isMobile={true}
/>
))}
ref={sidebarRef}
style={{
maxHeight: `calc(100vh - ${sidebarTopHeight}px)`,
}}
id="sidebar"
>
{/* MOBILE SIDEBAR */}
<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}
/>
))}
<SidebarSeparator />
</div>
{/* DESKTOP SIDEBAR */}
<div className="mt-docs_0.75 lg:mt-0">
{!sidebarItems.default.length && !staticSidebarItems && (
<Loading className="px-0" />
)}
{sidebarItems.default.map((item, index) => (
<SidebarItem
item={item}
key={index}
expandItems={expandItems}
hasNextItems={index !== sidebarItems.default.length - 1}
/>
))}
</div>
</div>
<div className={clsx("mb-docs_1.5")}>
{!sidebarItems.top.length && !staticSidebarItems && (
<Loading className="px-0" />
)}
{sidebarItems.top.map((item, index) => (
<SidebarItem
item={item}
key={index}
expandItems={expandItems}
sidebarHasParent={sidebarHasParent}
/>
))}
</div>
<div className="mb-docs_1.5">
{!sidebarItems.bottom.length && !staticSidebarItems && (
<Loading className="px-0" />
)}
{sidebarItems.bottom.map((item, index) => (
<SidebarItem
item={item}
key={index}
expandItems={expandItems}
sidebarHasParent={sidebarHasParent}
/>
))}
</div>
</div>
</CSSTransition>
</SwitchTransition>
</ul>
</aside>
</CSSTransition>
</SwitchTransition>
</ul>
</aside>
</>
)
}