Skip to content

Commit

Permalink
docs: DX and performance improvements in API reference (medusajs#9430)
Browse files Browse the repository at this point in the history
- Improve scroll behavior between active sections
- Improve lag when clicking on a sidebar item
- Refactor internal working of the `SidebarProvider` to find active items faster.
- Use Next.js's `useRouter` hook for changing the hash (since they added the option to disable scroll)
- Change `isBrowser` from a hook to a provider since it's widely used across applications.
- Other general improvements and fixes.

Closes DOCS-952
  • Loading branch information
shahednasser authored and panalgin committed Oct 7, 2024
1 parent 103ef64 commit 4ed88d1
Show file tree
Hide file tree
Showing 38 changed files with 294 additions and 218 deletions.
6 changes: 5 additions & 1 deletion www/apps/api-reference/components/Section/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import clsx from "clsx"
import { useActiveOnScroll, useSidebar } from "docs-ui"
import { useRouter } from "next/navigation"
import { useEffect, useRef } from "react"

export type SectionProps = {
Expand All @@ -20,6 +21,7 @@ const Section = ({
useDefaultIfNoActive: false,
})
const { setActivePath } = useSidebar()
const router = useRouter()

useEffect(() => {
if ("scrollRestoration" in history) {
Expand All @@ -30,7 +32,9 @@ const Section = ({

useEffect(() => {
if (activeItemId.length) {
history.pushState({}, "", `#${activeItemId}`)
router.push(`#${activeItemId}`, {
scroll: false,
})
setActivePath(activeItemId)
}
}, [activeItemId])
Expand Down
18 changes: 12 additions & 6 deletions www/apps/api-reference/components/Tags/Operation/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { TagOperationCodeSectionProps } from "./CodeSection"
import TagsOperationDescriptionSection from "./DescriptionSection"
import DividedLayout from "@/layouts/Divided"
import { useLoading } from "@/providers/loading"
import { useRouter } from "next/navigation"
import SectionDivider from "../../Section/Divider"
import checkElementInViewport from "../../../utils/check-element-in-viewport"

Expand All @@ -33,7 +34,8 @@ const TagOperation = ({
endpointPath,
className,
}: TagOperationProps) => {
const { setActivePath } = useSidebar()
const { activePath, setActivePath } = useSidebar()
const router = useRouter()
const [show, setShow] = useState(false)
const path = useMemo(
() => getSectionId([...(operation.tags || []), operation.operationId]),
Expand All @@ -57,10 +59,14 @@ const TagOperation = ({
}
setShow(true)
}
// can't use next router as it doesn't support
// changing url without scrolling
history.replaceState({}, "", `#${path}`)
setActivePath(path)
if (location.hash !== path) {
router.push(`#${path}`, {
scroll: false,
})
}
if (activePath !== path) {
setActivePath(path)
}
}
},
})
Expand All @@ -77,7 +83,7 @@ const TagOperation = ({
)

const scrollIntoView = useCallback(() => {
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 10)) {
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 0)) {
const elm = nodeRef.current as HTMLElement
scrollToTop(
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
useEffect(() => {
if (schemaSlug === (activePath || location.hash.replace("#", ""))) {
const elm = document.getElementById(schemaSlug) as HTMLElement
if (!checkElementInViewport(elm, 40)) {
if (!checkElementInViewport(elm, 0)) {
scrollToElement(elm)
}
}
Expand All @@ -85,7 +85,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
const section = entry.target

if (
(inView || checkElementInViewport(section, 40)) &&
(inView || checkElementInViewport(section, 10)) &&
activePath !== schemaSlug
) {
// can't use next router as it doesn't support
Expand Down
24 changes: 16 additions & 8 deletions www/apps/api-reference/components/Tags/Section/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { useArea } from "@/providers/area"
import SectionDivider from "../../Section/Divider"
import clsx from "clsx"
import { Feedback, Loading, Link } from "docs-ui"
import { usePathname } from "next/navigation"
import { usePathname, useRouter } from "next/navigation"
import formatReportLink from "@/utils/format-report-link"
import { SchemaObject, TagObject } from "@/types/openapi"
import useSWR from "swr"
Expand Down Expand Up @@ -49,11 +49,12 @@ const MDXContentClient = dynamic<MDXContentClientProps>(

const TagSection = ({ tag }: TagSectionProps) => {
const { activePath, setActivePath } = useSidebar()
const router = useRouter()
const [loadPaths, setLoadPaths] = useState(false)
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
const { area } = useArea()
const pathname = usePathname()
const { scrollableElement, scrollToElement } = useScrollController()
const { scrollableElement, scrollToTop } = useScrollController()
const { data } = useSWR<{
schema: SchemaObject
}>(
Expand Down Expand Up @@ -84,10 +85,14 @@ const TagSection = ({ tag }: TagSectionProps) => {
// ensure that the hash link doesn't change if it links to an inner path
const currentHashArr = location.hash.replace("#", "").split("_")
if (currentHashArr.length < 2 || currentHashArr[0] !== slugTagName) {
// can't use next router as it doesn't support
// changing url without scrolling
history.replaceState({}, "", `#${slugTagName}`)
setActivePath(slugTagName)
if (location.hash !== slugTagName) {
router.push(`#${slugTagName}`, {
scroll: false,
})
}
if (activePath !== slugTagName) {
setActivePath(slugTagName)
}
}
}
},
Expand All @@ -98,8 +103,11 @@ const TagSection = ({ tag }: TagSectionProps) => {
const tagName = activePath.split("_")
if (tagName.length === 1 && tagName[0] === slugTagName) {
const elm = document.getElementById(tagName[0])
if (elm && !checkElementInViewport(elm, 10)) {
scrollToElement(elm)
if (elm && !checkElementInViewport(elm, 0)) {
scrollToTop(
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
0
)
}
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
setLoadPaths(true)
Expand Down
14 changes: 11 additions & 3 deletions www/apps/api-reference/components/Tags/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ExpandedDocument } from "@/types/openapi"
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
import { SidebarItem, SidebarItemSections } from "types"
import basePathUrl from "../../utils/base-path-url"
import { useRouter } from "next/navigation"

const TagSection = dynamic<TagSectionProps>(
async () => import("./Section")
Expand All @@ -31,8 +32,9 @@ const Tags = () => {
const [loadData, setLoadData] = useState<boolean>(false)
const [expand, setExpand] = useState<string>("")
const { baseSpecs, setBaseSpecs } = useBaseSpecs()
const { addItems, setActivePath } = useSidebar()
const { activePath, addItems, setActivePath } = useSidebar()
const { area, prevArea } = useArea()
const router = useRouter()

const { data } = useSWR<ExpandedDocument>(
loadData && !baseSpecs
Expand Down Expand Up @@ -89,8 +91,14 @@ const Tags = () => {
children: childItems,
loaded: childItems.length > 0,
onOpen: () => {
history.pushState({}, "", `#${tagPathName}`)
setActivePath(tagPathName)
if (location.hash !== tagPathName) {
router.push(`#${tagPathName}`, {
scroll: false,
})
}
if (activePath !== tagPathName) {
setActivePath(tagPathName)
}
},
})
})
Expand Down
27 changes: 9 additions & 18 deletions www/apps/api-reference/providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

import {
AnalyticsProvider,
ColorModeProvider,
MobileProvider,
ModalProvider,
PageLoadingProvider,
ScrollControllerProvider,
SiteConfigProvider,
Expand All @@ -24,21 +21,15 @@ const Providers = ({ children }: ProvidersProps) => {
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
<SiteConfigProvider config={config}>
<PageLoadingProvider>
<ModalProvider>
<ColorModeProvider>
<BaseSpecsProvider>
<ScrollControllerProvider scrollableSelector="#main">
<SidebarProvider>
<MainNavProvider>
<SearchProvider>
<MobileProvider>{children}</MobileProvider>
</SearchProvider>
</MainNavProvider>
</SidebarProvider>
</ScrollControllerProvider>
</BaseSpecsProvider>
</ColorModeProvider>
</ModalProvider>
<BaseSpecsProvider>
<ScrollControllerProvider scrollableSelector="#main">
<SidebarProvider>
<MainNavProvider>
<SearchProvider>{children}</SearchProvider>
</MainNavProvider>
</SidebarProvider>
</ScrollControllerProvider>
</BaseSpecsProvider>
</PageLoadingProvider>
</SiteConfigProvider>
</AnalyticsProvider>
Expand Down
2 changes: 1 addition & 1 deletion www/apps/api-reference/providers/main-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type MainNavProviderProps = {
}

export const MainNavProvider = ({ children }: MainNavProviderProps) => {
const isBrowser = useIsBrowser()
const { isBrowser } = useIsBrowser()
const pathname = usePathname()
const navigationDropdownItems = useMemo(
() =>
Expand Down
5 changes: 2 additions & 3 deletions www/apps/api-reference/providers/page-title.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type PageTitleProviderProps = {
}

const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
const { activePath, getActiveItem } = useSidebar()
const { activePath, activeItem } = useSidebar()
const { area } = useArea()

useEffect(() => {
Expand All @@ -21,7 +21,6 @@ const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
if (!activePath?.length) {
document.title = titleSuffix
} else {
const activeItem = getActiveItem()
if (activeItem?.path === activePath) {
document.title = `${activeItem?.title} - ${titleSuffix}`
} else {
Expand All @@ -34,7 +33,7 @@ const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
}
}
}
}, [activePath, area, getActiveItem])
}, [activePath, area, activeItem])

return (
<PageTitleContext.Provider value={null}>
Expand Down
5 changes: 2 additions & 3 deletions www/apps/api-reference/utils/check-element-in-viewport.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
export default function checkElementInViewport(
element: Element,
percentage = 100,
height?: number
percentage = 100
) {
const rect = element.getBoundingClientRect()
const windowHeight: number | undefined =
height || window.innerHeight || document.documentElement.clientHeight
window.innerHeight || document.documentElement.clientHeight

return !(
Math.floor(100 - ((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100) <
Expand Down
2 changes: 1 addition & 1 deletion www/apps/book/components/Feedback/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type FeedbackProps = Omit<UiFeedbackProps, "event" | "pathName">

const Feedback = (props: FeedbackProps) => {
const pathname = usePathname()
const isBrowser = useIsBrowser()
const { isBrowser } = useIsBrowser()

const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
const reportLink = useMemo(
Expand Down
53 changes: 22 additions & 31 deletions www/apps/book/providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

import {
AnalyticsProvider,
ColorModeProvider,
HooksLoader,
LearningPathProvider,
MobileProvider,
ModalProvider,
NotificationProvider,
PaginationProvider,
ScrollControllerProvider,
Expand All @@ -25,34 +22,28 @@ const Providers = ({ children }: ProvidersProps) => {
return (
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
<SiteConfigProvider config={config}>
<MobileProvider>
<ColorModeProvider>
<ModalProvider>
<LearningPathProvider>
<NotificationProvider>
<ScrollControllerProvider scrollableSelector="#main">
<SidebarProvider>
<PaginationProvider>
<MainNavProvider>
<SearchProvider>
<HooksLoader
options={{
pageScrollManager: true,
currentLearningPath: true,
}}
>
{children}
</HooksLoader>
</SearchProvider>
</MainNavProvider>
</PaginationProvider>
</SidebarProvider>
</ScrollControllerProvider>
</NotificationProvider>
</LearningPathProvider>
</ModalProvider>
</ColorModeProvider>
</MobileProvider>
<LearningPathProvider>
<NotificationProvider>
<ScrollControllerProvider scrollableSelector="#main">
<SidebarProvider>
<PaginationProvider>
<MainNavProvider>
<SearchProvider>
<HooksLoader
options={{
pageScrollManager: true,
currentLearningPath: true,
}}
>
{children}
</HooksLoader>
</SearchProvider>
</MainNavProvider>
</PaginationProvider>
</SidebarProvider>
</ScrollControllerProvider>
</NotificationProvider>
</LearningPathProvider>
</SiteConfigProvider>
</AnalyticsProvider>
)
Expand Down
2 changes: 1 addition & 1 deletion www/apps/book/providers/main-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type MainNavProviderProps = {
}

export const MainNavProvider = ({ children }: MainNavProviderProps) => {
const isBrowser = useIsBrowser()
const { isBrowser } = useIsBrowser()
const pathname = usePathname()
const navigationDropdownItems = useMemo(
() =>
Expand Down
2 changes: 1 addition & 1 deletion www/apps/resources/components/Feedback/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type FeedbackProps = Omit<UiFeedbackProps, "event" | "pathName">

export const Feedback = (props: FeedbackProps) => {
const pathname = usePathname()
const isBrowser = useIsBrowser()
const { isBrowser } = useIsBrowser()

const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
const reportLink = useMemo(
Expand Down
Loading

0 comments on commit 4ed88d1

Please sign in to comment.