Skip to content

Commit

Permalink
docs: fixed sidebar in API reference (#5871)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahednasser authored Dec 14, 2023
1 parent b5748ab commit c0ce969
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 42 deletions.
12 changes: 9 additions & 3 deletions www/apps/api-reference/components/MDXComponents/H2/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use client"

import { InView } from "react-intersection-observer"
import { useSidebar } from "docs-ui"
import { isElmWindow, useScrollController, useSidebar } from "docs-ui"
import checkElementInViewport from "../../../utils/check-element-in-viewport"
import { useEffect } from "react"
import { useEffect, useMemo } from "react"
import getSectionId from "../../../utils/get-section-id"

type H2Props = {
Expand All @@ -12,6 +12,7 @@ type H2Props = {

const H2 = ({ addToSidebar = true, children, ...props }: H2Props) => {
const { activePath, setActivePath, addItems } = useSidebar()
const { getScrolledTop, scrollableElement } = useScrollController()

const handleViewChange = (
inView: boolean,
Expand All @@ -24,7 +25,7 @@ const H2 = ({ addToSidebar = true, children, ...props }: H2Props) => {
if (
(inView ||
checkElementInViewport(heading.parentElement || heading, 40)) &&
window.scrollY !== 0 &&
getScrolledTop() !== 0 &&
activePath !== heading.id
) {
// can't use next router as it doesn't support
Expand All @@ -50,6 +51,10 @@ const H2 = ({ addToSidebar = true, children, ...props }: H2Props) => {
])
}, [])

const root = useMemo(() => {
return isElmWindow(scrollableElement) ? document.body : scrollableElement
}, [scrollableElement])

return (
<InView
as="h2"
Expand All @@ -59,6 +64,7 @@ const H2 = ({ addToSidebar = true, children, ...props }: H2Props) => {
{...props}
onChange={handleViewChange}
id={id}
root={root}
>
{children}
</InView>
Expand Down
8 changes: 6 additions & 2 deletions www/apps/api-reference/components/Tags/Operation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import getSectionId from "@/utils/get-section-id"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import dynamic from "next/dynamic"
import { useInView } from "react-intersection-observer"
import { useSidebar } from "docs-ui"
import { isElmWindow, useScrollController, useSidebar } from "docs-ui"
import type { TagOperationCodeSectionProps } from "./CodeSection"
import TagsOperationDescriptionSection from "./DescriptionSection"
import DividedLayout from "@/layouts/Divided"
Expand Down Expand Up @@ -40,10 +40,14 @@ const TagOperation = ({
)
const nodeRef = useRef<Element | null>(null)
const { loading, removeLoading } = useLoading()
const { scrollableElement } = useScrollController()
const root = useMemo(() => {
return isElmWindow(scrollableElement) ? document.body : scrollableElement
}, [scrollableElement])
const { ref } = useInView({
threshold: 0.3,
rootMargin: `112px 0px 112px 0px`,
root: document.getElementById("main") || document.body,
root,
onChange: (changedInView) => {
if (changedInView) {
if (!show) {
Expand Down
8 changes: 6 additions & 2 deletions www/apps/api-reference/components/Tags/Section/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import getSectionId from "@/utils/get-section-id"
import type { OpenAPIV3 } from "openapi-types"
import { useInView } from "react-intersection-observer"
import { useEffect, useMemo, useState } from "react"
import { useSidebar } from "docs-ui"
import { isElmWindow, useScrollController, useSidebar } from "docs-ui"
import dynamic from "next/dynamic"
import type { SectionProps } from "../../Section"
import type { MDXContentClientProps } from "../../MDXContent/Client"
Expand Down Expand Up @@ -40,10 +40,14 @@ const TagSection = ({ tag }: TagSectionProps) => {
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
const { area } = useArea()
const pathname = usePathname()
const { scrollableElement } = useScrollController()
const root = useMemo(() => {
return isElmWindow(scrollableElement) ? document.body : scrollableElement
}, [scrollableElement])
const { ref } = useInView({
threshold: 0.5,
rootMargin: `112px 0px 112px 0px`,
root: document.getElementById("main") || document.body,
root,
onChange: (inView) => {
if (inView && !loadPaths) {
setLoadPaths(true)
Expand Down
12 changes: 6 additions & 6 deletions www/apps/api-reference/providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ const Providers = ({ children }: ProvidersProps) => {
<ModalProvider>
<ColorModeProvider>
<BaseSpecsProvider>
<SidebarProvider>
<NavbarProvider>
<ScrollControllerProvider scrollableSelector="#main">
<ScrollControllerProvider scrollableSelector="#main">
<SidebarProvider>
<NavbarProvider>
<SearchProvider>
<MobileProvider>{children}</MobileProvider>
</SearchProvider>
</ScrollControllerProvider>
</NavbarProvider>
</SidebarProvider>
</NavbarProvider>
</SidebarProvider>
</ScrollControllerProvider>
</BaseSpecsProvider>
</ColorModeProvider>
</ModalProvider>
Expand Down
8 changes: 7 additions & 1 deletion www/apps/api-reference/providers/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
"use client"
import { SidebarProvider as UiSidebarProvider, usePageLoading } from "docs-ui"
import {
SidebarProvider as UiSidebarProvider,
usePageLoading,
useScrollController,
} from "docs-ui"

type SidebarProviderProps = {
children?: React.ReactNode
}

const SidebarProvider = ({ children }: SidebarProviderProps) => {
const { isLoading, setIsLoading } = usePageLoading()
const { scrollableElement } = useScrollController()

return (
<UiSidebarProvider
isLoading={isLoading}
setIsLoading={setIsLoading}
shouldHandleHashChange={true}
scrollableElement={scrollableElement}
initialItems={{
top: [
{
Expand Down
16 changes: 7 additions & 9 deletions www/apps/ui/src/providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@ const Providers = ({ children }: ProvidersProps) => {
<MobileProvider>
<ColorModeProvider>
<ModalProvider>
<SidebarProvider>
<NavbarProvider basePath={process.env.NEXT_PUBLIC_BASE_PATH}>
<SearchProvider>
<ScrollControllerProvider scrollableSelector="#main">
{children}
</ScrollControllerProvider>
</SearchProvider>
</NavbarProvider>
</SidebarProvider>
<ScrollControllerProvider scrollableSelector="#main">
<SidebarProvider>
<NavbarProvider basePath={process.env.NEXT_PUBLIC_BASE_PATH}>
<SearchProvider>{children}</SearchProvider>
</NavbarProvider>
</SidebarProvider>
</ScrollControllerProvider>
</ModalProvider>
</ColorModeProvider>
</MobileProvider>
Expand Down
7 changes: 6 additions & 1 deletion www/apps/ui/src/providers/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { SidebarProvider as UiSidebarProvider } from "docs-ui"
import {
SidebarProvider as UiSidebarProvider,
useScrollController,
} from "docs-ui"
import { docsConfig } from "@/config/docs"

type SidebarProviderProps = {
children?: React.ReactNode
}

const SidebarProvider = ({ children }: SidebarProviderProps) => {
const { scrollableElement } = useScrollController()
return (
<UiSidebarProvider
initialItems={docsConfig.sidebar}
shouldHandlePathChange={true}
scrollableElement={scrollableElement}
>
{children}
</UiSidebarProvider>
Expand Down
7 changes: 4 additions & 3 deletions www/packages/docs-ui/src/components/Sidebar/Item/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ export const SidebarItem = ({
}: SidebarItemProps) => {
const [showLoading, setShowLoading] = useState(false)
const { isItemActive, setMobileSidebarOpen: setSidebarOpen } = useSidebar()
const active = useMemo(() => {
return isItemActive(item, nested)
}, [isItemActive, item, nested])
const active = useMemo(
() => isItemActive(item, nested),
[isItemActive, item, nested]
)
const collapsed = !expandItems && !isItemActive(item, true)
const ref = useRef<HTMLLIElement>(null)

Expand Down
31 changes: 23 additions & 8 deletions www/packages/docs-ui/src/hooks/use-scroll-utils/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import React, {
useMemo,
useRef,
type ReactNode,
useState,
} from "react"
import { getScrolledTop as getScrolledTopUtil } from "../../utils"

type EventFunc = (...args: never[]) => unknown

Expand Down Expand Up @@ -54,17 +56,29 @@ type ScrollController = {
/** Disable scroll events in `useScrollPosition`. */
disableScrollEvents: () => void
/** Retrieves the scrollable element. By default, it's window. */
getScrollableElement: () => Element | Window
scrollableElement: Element | Window | undefined
/** Retrieves the scroll top if the scrollable element */
getScrolledTop: () => number
}

function useScrollControllerContextValue(
scrollableSelector: string
): ScrollController {
const scrollEventsEnabledRef = useRef(true)

const getScrollableElement = useCallback(() => {
return (document.querySelector(scrollableSelector) as Element) || window
}, [scrollableSelector])
const [scrollableElement, setScrollableElement] = useState<
Element | Window | undefined
>()

useEffect(() => {
setScrollableElement(
(document.querySelector(scrollableSelector) as Element) || window
)
}, [])

const getScrolledTop = () => {
return scrollableElement ? getScrolledTopUtil(scrollableElement) : 0
}

return useMemo(
() => ({
Expand All @@ -75,9 +89,10 @@ function useScrollControllerContextValue(
disableScrollEvents: () => {
scrollEventsEnabledRef.current = false
},
getScrollableElement,
scrollableElement,
getScrolledTop,
}),
[getScrollableElement]
[scrollableElement]
)
}

Expand Down Expand Up @@ -176,7 +191,7 @@ type UseScrollPositionSaver = {
}

function useScrollPositionSaver(): UseScrollPositionSaver {
const { getScrollableElement } = useScrollController()
const { scrollableElement } = useScrollController()
const lastElementRef = useRef<{ elem: HTMLElement | null; top: number }>({
elem: null,
top: 0,
Expand All @@ -199,7 +214,7 @@ function useScrollPositionSaver(): UseScrollPositionSaver {
const newTop = elem.getBoundingClientRect().top
const heightDiff = newTop - top
if (heightDiff) {
getScrollableElement().scrollBy({ left: 0, top: heightDiff })
scrollableElement?.scrollBy({ left: 0, top: heightDiff })
}
lastElementRef.current = { elem: null, top: 0 }

Expand Down
29 changes: 22 additions & 7 deletions www/packages/docs-ui/src/providers/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, {
useState,
} from "react"
import { usePathname } from "next/navigation"
import { getScrolledTop } from "../../utils"

export enum SidebarItemSections {
TOP = "top",
Expand Down Expand Up @@ -129,6 +130,7 @@ export type SidebarProviderProps = {
initialItems?: SidebarSectionItemsType
shouldHandleHashChange?: boolean
shouldHandlePathChange?: boolean
scrollableElement?: Element | Window
}

export const SidebarProvider = ({
Expand All @@ -138,6 +140,7 @@ export const SidebarProvider = ({
initialItems,
shouldHandleHashChange = false,
shouldHandlePathChange = false,
scrollableElement,
}: SidebarProviderProps) => {
const [items, dispatch] = useReducer(reducer, {
top: initialItems?.top || [],
Expand All @@ -148,6 +151,9 @@ export const SidebarProvider = ({
const [mobileSidebarOpen, setMobileSidebarOpen] = useState<boolean>(false)
const [desktopSidebarOpen, setDesktopSidebarOpen] = useState(true)
const pathname = usePathname()
const getResolvedScrollableElement = useCallback(() => {
return scrollableElement || window
}, [scrollableElement])

const findItemInSection = useCallback(
(
Expand Down Expand Up @@ -249,30 +255,39 @@ export const SidebarProvider = ({
}
}, [activePath])

useEffect(() => {
if (shouldHandleHashChange) {
init()
}
}, [shouldHandleHashChange])

useEffect(() => {
if (!shouldHandleHashChange) {
return
}

init()
const resolvedScrollableElement = getResolvedScrollableElement()

const handleScroll = () => {
if (window.scrollY === 0) {
if (getScrolledTop(resolvedScrollableElement) === 0) {
setActivePath("")
// can't use next router as it doesn't support
// changing url without scrolling
history.replaceState({}, "", location.pathname)
}
}

window.addEventListener("scroll", handleScroll)
window.addEventListener("hashchange", handleHashChange)
resolvedScrollableElement.addEventListener("scroll", handleScroll)
resolvedScrollableElement.addEventListener("hashchange", handleHashChange)

return () => {
window.removeEventListener("scroll", handleScroll)
window.removeEventListener("hashchange", handleHashChange)
resolvedScrollableElement.removeEventListener("scroll", handleScroll)
resolvedScrollableElement.removeEventListener(
"hashchange",
handleHashChange
)
}
}, [handleHashChange, shouldHandleHashChange])
}, [handleHashChange, shouldHandleHashChange, getResolvedScrollableElement])

useEffect(() => {
if (isLoading && items.top.length && items.bottom.length) {
Expand Down
8 changes: 8 additions & 0 deletions www/packages/docs-ui/src/utils/get-scrolled-top.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { isElmWindow } from "./is-elm-window"

export function getScrolledTop(elm?: Element | Window): number {
if (!elm) {
return 0
}
return isElmWindow(elm) ? elm.scrollY : elm.scrollTop
}
2 changes: 2 additions & 0 deletions www/packages/docs-ui/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export * from "./capitalize"
export * from "./check-sidebar-item-visibility"
export * from "./dom-utils"
export * from "./format-report-link"
export * from "./get-scrolled-top"
export * from "./is-elm-window"
export * from "./swr-fetcher"
3 changes: 3 additions & 0 deletions www/packages/docs-ui/src/utils/is-elm-window.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isElmWindow(elm: unknown): elm is Window {
return typeof window !== "undefined" && elm === window
}

3 comments on commit c0ce969

@vercel
Copy link

@vercel vercel bot commented on c0ce969 Dec 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

docs-ui – ./www/apps/ui

docs-ui-medusajs.vercel.app
docs-ui-git-develop-medusajs.vercel.app
docs-ui.vercel.app

@vercel
Copy link

@vercel vercel bot commented on c0ce969 Dec 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

api-reference – ./www/apps/api-reference

api-reference-git-develop-medusajs.vercel.app
docs.medusajs.com
api-reference-delta.vercel.app
api-reference-medusajs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on c0ce969 Dec 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

medusa-docs – ./www/apps/docs

medusa-docs.vercel.app
medusa-docs-git-develop-medusajs.vercel.app
medusa-docs-medusajs.vercel.app

Please sign in to comment.