docs: DX and performance improvements in API reference (#9430)
- 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
This commit is contained in:
1 parent
5fb9c1f82e
commit
0f6f56e764
38 files changed
+294
-218
No files matched your search
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useActiveOnScroll, useSidebar } from "docs-ui"
|
import { useActiveOnScroll, useSidebar } from "docs-ui"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
import { useEffect, useRef } from "react"
|
import { useEffect, useRef } from "react"
|
||||||
|
|
||||||
export type SectionProps = {
|
export type SectionProps = {
|
||||||
@@ -20,6 +21,7 @@ const Section = ({
|
|||||||
useDefaultIfNoActive: false,
|
useDefaultIfNoActive: false,
|
||||||
})
|
})
|
||||||
const { setActivePath } = useSidebar()
|
const { setActivePath } = useSidebar()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ("scrollRestoration" in history) {
|
if ("scrollRestoration" in history) {
|
||||||
@@ -30,7 +32,9 @@ const Section = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeItemId.length) {
|
if (activeItemId.length) {
|
||||||
history.pushState({}, "", `#${activeItemId}`)
|
router.push(`#${activeItemId}`, {
|
||||||
|
scroll: false,
|
||||||
|
})
|
||||||
setActivePath(activeItemId)
|
setActivePath(activeItemId)
|
||||||
}
|
}
|
||||||
}, [activeItemId])
|
}, [activeItemId])
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { TagOperationCodeSectionProps } from "./CodeSection"
|
|||||||
import TagsOperationDescriptionSection from "./DescriptionSection"
|
import TagsOperationDescriptionSection from "./DescriptionSection"
|
||||||
import DividedLayout from "@/layouts/Divided"
|
import DividedLayout from "@/layouts/Divided"
|
||||||
import { useLoading } from "@/providers/loading"
|
import { useLoading } from "@/providers/loading"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
import SectionDivider from "../../Section/Divider"
|
import SectionDivider from "../../Section/Divider"
|
||||||
import checkElementInViewport from "../../../utils/check-element-in-viewport"
|
import checkElementInViewport from "../../../utils/check-element-in-viewport"
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@ const TagOperation = ({
|
|||||||
endpointPath,
|
endpointPath,
|
||||||
className,
|
className,
|
||||||
}: TagOperationProps) => {
|
}: TagOperationProps) => {
|
||||||
const { setActivePath } = useSidebar()
|
const { activePath, setActivePath } = useSidebar()
|
||||||
|
const router = useRouter()
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const path = useMemo(
|
const path = useMemo(
|
||||||
() => getSectionId([...(operation.tags || []), operation.operationId]),
|
() => getSectionId([...(operation.tags || []), operation.operationId]),
|
||||||
@@ -57,10 +59,14 @@ const TagOperation = ({
|
|||||||
}
|
}
|
||||||
setShow(true)
|
setShow(true)
|
||||||
}
|
}
|
||||||
// can't use next router as it doesn't support
|
if (location.hash !== path) {
|
||||||
// changing url without scrolling
|
router.push(`#${path}`, {
|
||||||
history.replaceState({}, "", `#${path}`)
|
scroll: false,
|
||||||
setActivePath(path)
|
})
|
||||||
|
}
|
||||||
|
if (activePath !== path) {
|
||||||
|
setActivePath(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -77,7 +83,7 @@ const TagOperation = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const scrollIntoView = useCallback(() => {
|
const scrollIntoView = useCallback(() => {
|
||||||
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 10)) {
|
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 0)) {
|
||||||
const elm = nodeRef.current as HTMLElement
|
const elm = nodeRef.current as HTMLElement
|
||||||
scrollToTop(
|
scrollToTop(
|
||||||
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
|
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (schemaSlug === (activePath || location.hash.replace("#", ""))) {
|
if (schemaSlug === (activePath || location.hash.replace("#", ""))) {
|
||||||
const elm = document.getElementById(schemaSlug) as HTMLElement
|
const elm = document.getElementById(schemaSlug) as HTMLElement
|
||||||
if (!checkElementInViewport(elm, 40)) {
|
if (!checkElementInViewport(elm, 0)) {
|
||||||
scrollToElement(elm)
|
scrollToElement(elm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
|||||||
const section = entry.target
|
const section = entry.target
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(inView || checkElementInViewport(section, 40)) &&
|
(inView || checkElementInViewport(section, 10)) &&
|
||||||
activePath !== schemaSlug
|
activePath !== schemaSlug
|
||||||
) {
|
) {
|
||||||
// can't use next router as it doesn't support
|
// can't use next router as it doesn't support
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { useArea } from "@/providers/area"
|
|||||||
import SectionDivider from "../../Section/Divider"
|
import SectionDivider from "../../Section/Divider"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { Feedback, Loading, Link } from "docs-ui"
|
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 formatReportLink from "@/utils/format-report-link"
|
||||||
import { SchemaObject, TagObject } from "@/types/openapi"
|
import { SchemaObject, TagObject } from "@/types/openapi"
|
||||||
import useSWR from "swr"
|
import useSWR from "swr"
|
||||||
@@ -49,11 +49,12 @@ const MDXContentClient = dynamic<MDXContentClientProps>(
|
|||||||
|
|
||||||
const TagSection = ({ tag }: TagSectionProps) => {
|
const TagSection = ({ tag }: TagSectionProps) => {
|
||||||
const { activePath, setActivePath } = useSidebar()
|
const { activePath, setActivePath } = useSidebar()
|
||||||
|
const router = useRouter()
|
||||||
const [loadPaths, setLoadPaths] = useState(false)
|
const [loadPaths, setLoadPaths] = useState(false)
|
||||||
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
|
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
|
||||||
const { area } = useArea()
|
const { area } = useArea()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { scrollableElement, scrollToElement } = useScrollController()
|
const { scrollableElement, scrollToTop } = useScrollController()
|
||||||
const { data } = useSWR<{
|
const { data } = useSWR<{
|
||||||
schema: SchemaObject
|
schema: SchemaObject
|
||||||
}>(
|
}>(
|
||||||
@@ -84,10 +85,14 @@ const TagSection = ({ tag }: TagSectionProps) => {
|
|||||||
// ensure that the hash link doesn't change if it links to an inner path
|
// ensure that the hash link doesn't change if it links to an inner path
|
||||||
const currentHashArr = location.hash.replace("#", "").split("_")
|
const currentHashArr = location.hash.replace("#", "").split("_")
|
||||||
if (currentHashArr.length < 2 || currentHashArr[0] !== slugTagName) {
|
if (currentHashArr.length < 2 || currentHashArr[0] !== slugTagName) {
|
||||||
// can't use next router as it doesn't support
|
if (location.hash !== slugTagName) {
|
||||||
// changing url without scrolling
|
router.push(`#${slugTagName}`, {
|
||||||
history.replaceState({}, "", `#${slugTagName}`)
|
scroll: false,
|
||||||
setActivePath(slugTagName)
|
})
|
||||||
|
}
|
||||||
|
if (activePath !== slugTagName) {
|
||||||
|
setActivePath(slugTagName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -98,8 +103,11 @@ const TagSection = ({ tag }: TagSectionProps) => {
|
|||||||
const tagName = activePath.split("_")
|
const tagName = activePath.split("_")
|
||||||
if (tagName.length === 1 && tagName[0] === slugTagName) {
|
if (tagName.length === 1 && tagName[0] === slugTagName) {
|
||||||
const elm = document.getElementById(tagName[0])
|
const elm = document.getElementById(tagName[0])
|
||||||
if (elm && !checkElementInViewport(elm, 10)) {
|
if (elm && !checkElementInViewport(elm, 0)) {
|
||||||
scrollToElement(elm)
|
scrollToTop(
|
||||||
|
elm.offsetTop + (elm.offsetParent as HTMLElement)?.offsetTop,
|
||||||
|
0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
|
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
|
||||||
setLoadPaths(true)
|
setLoadPaths(true)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { ExpandedDocument } from "@/types/openapi"
|
|||||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||||
import { SidebarItem, SidebarItemSections } from "types"
|
import { SidebarItem, SidebarItemSections } from "types"
|
||||||
import basePathUrl from "../../utils/base-path-url"
|
import basePathUrl from "../../utils/base-path-url"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
|
||||||
const TagSection = dynamic<TagSectionProps>(
|
const TagSection = dynamic<TagSectionProps>(
|
||||||
async () => import("./Section")
|
async () => import("./Section")
|
||||||
@@ -31,8 +32,9 @@ const Tags = () => {
|
|||||||
const [loadData, setLoadData] = useState<boolean>(false)
|
const [loadData, setLoadData] = useState<boolean>(false)
|
||||||
const [expand, setExpand] = useState<string>("")
|
const [expand, setExpand] = useState<string>("")
|
||||||
const { baseSpecs, setBaseSpecs } = useBaseSpecs()
|
const { baseSpecs, setBaseSpecs } = useBaseSpecs()
|
||||||
const { addItems, setActivePath } = useSidebar()
|
const { activePath, addItems, setActivePath } = useSidebar()
|
||||||
const { area, prevArea } = useArea()
|
const { area, prevArea } = useArea()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const { data } = useSWR<ExpandedDocument>(
|
const { data } = useSWR<ExpandedDocument>(
|
||||||
loadData && !baseSpecs
|
loadData && !baseSpecs
|
||||||
@@ -89,8 +91,14 @@ const Tags = () => {
|
|||||||
children: childItems,
|
children: childItems,
|
||||||
loaded: childItems.length > 0,
|
loaded: childItems.length > 0,
|
||||||
onOpen: () => {
|
onOpen: () => {
|
||||||
history.pushState({}, "", `#${tagPathName}`)
|
if (location.hash !== tagPathName) {
|
||||||
setActivePath(tagPathName)
|
router.push(`#${tagPathName}`, {
|
||||||
|
scroll: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (activePath !== tagPathName) {
|
||||||
|
setActivePath(tagPathName)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AnalyticsProvider,
|
AnalyticsProvider,
|
||||||
ColorModeProvider,
|
|
||||||
MobileProvider,
|
|
||||||
ModalProvider,
|
|
||||||
PageLoadingProvider,
|
PageLoadingProvider,
|
||||||
ScrollControllerProvider,
|
ScrollControllerProvider,
|
||||||
SiteConfigProvider,
|
SiteConfigProvider,
|
||||||
@@ -24,21 +21,15 @@ const Providers = ({ children }: ProvidersProps) => {
|
|||||||
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
||||||
<SiteConfigProvider config={config}>
|
<SiteConfigProvider config={config}>
|
||||||
<PageLoadingProvider>
|
<PageLoadingProvider>
|
||||||
<ModalProvider>
|
<BaseSpecsProvider>
|
||||||
<ColorModeProvider>
|
<ScrollControllerProvider scrollableSelector="#main">
|
||||||
<BaseSpecsProvider>
|
<SidebarProvider>
|
||||||
<ScrollControllerProvider scrollableSelector="#main">
|
<MainNavProvider>
|
||||||
<SidebarProvider>
|
<SearchProvider>{children}</SearchProvider>
|
||||||
<MainNavProvider>
|
</MainNavProvider>
|
||||||
<SearchProvider>
|
</SidebarProvider>
|
||||||
<MobileProvider>{children}</MobileProvider>
|
</ScrollControllerProvider>
|
||||||
</SearchProvider>
|
</BaseSpecsProvider>
|
||||||
</MainNavProvider>
|
|
||||||
</SidebarProvider>
|
|
||||||
</ScrollControllerProvider>
|
|
||||||
</BaseSpecsProvider>
|
|
||||||
</ColorModeProvider>
|
|
||||||
</ModalProvider>
|
|
||||||
</PageLoadingProvider>
|
</PageLoadingProvider>
|
||||||
</SiteConfigProvider>
|
</SiteConfigProvider>
|
||||||
</AnalyticsProvider>
|
</AnalyticsProvider>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type MainNavProviderProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const navigationDropdownItems = useMemo(
|
const navigationDropdownItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type PageTitleProviderProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
|
const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
|
||||||
const { activePath, getActiveItem } = useSidebar()
|
const { activePath, activeItem } = useSidebar()
|
||||||
const { area } = useArea()
|
const { area } = useArea()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -21,7 +21,6 @@ const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
|
|||||||
if (!activePath?.length) {
|
if (!activePath?.length) {
|
||||||
document.title = titleSuffix
|
document.title = titleSuffix
|
||||||
} else {
|
} else {
|
||||||
const activeItem = getActiveItem()
|
|
||||||
if (activeItem?.path === activePath) {
|
if (activeItem?.path === activePath) {
|
||||||
document.title = `${activeItem?.title} - ${titleSuffix}`
|
document.title = `${activeItem?.title} - ${titleSuffix}`
|
||||||
} else {
|
} else {
|
||||||
@@ -34,7 +33,7 @@ const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activePath, area, getActiveItem])
|
}, [activePath, area, activeItem])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageTitleContext.Provider value={null}>
|
<PageTitleContext.Provider value={null}>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
export default function checkElementInViewport(
|
export default function checkElementInViewport(
|
||||||
element: Element,
|
element: Element,
|
||||||
percentage = 100,
|
percentage = 100
|
||||||
height?: number
|
|
||||||
) {
|
) {
|
||||||
const rect = element.getBoundingClientRect()
|
const rect = element.getBoundingClientRect()
|
||||||
const windowHeight: number | undefined =
|
const windowHeight: number | undefined =
|
||||||
height || window.innerHeight || document.documentElement.clientHeight
|
window.innerHeight || document.documentElement.clientHeight
|
||||||
|
|
||||||
return !(
|
return !(
|
||||||
Math.floor(100 - ((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100) <
|
Math.floor(100 - ((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100) <
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type FeedbackProps = Omit<UiFeedbackProps, "event" | "pathName">
|
|||||||
|
|
||||||
const Feedback = (props: FeedbackProps) => {
|
const Feedback = (props: FeedbackProps) => {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
|
|
||||||
const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
|
const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
|
||||||
const reportLink = useMemo(
|
const reportLink = useMemo(
|
||||||
|
|||||||
@@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AnalyticsProvider,
|
AnalyticsProvider,
|
||||||
ColorModeProvider,
|
|
||||||
HooksLoader,
|
HooksLoader,
|
||||||
LearningPathProvider,
|
LearningPathProvider,
|
||||||
MobileProvider,
|
|
||||||
ModalProvider,
|
|
||||||
NotificationProvider,
|
NotificationProvider,
|
||||||
PaginationProvider,
|
PaginationProvider,
|
||||||
ScrollControllerProvider,
|
ScrollControllerProvider,
|
||||||
@@ -25,34 +22,28 @@ const Providers = ({ children }: ProvidersProps) => {
|
|||||||
return (
|
return (
|
||||||
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
||||||
<SiteConfigProvider config={config}>
|
<SiteConfigProvider config={config}>
|
||||||
<MobileProvider>
|
<LearningPathProvider>
|
||||||
<ColorModeProvider>
|
<NotificationProvider>
|
||||||
<ModalProvider>
|
<ScrollControllerProvider scrollableSelector="#main">
|
||||||
<LearningPathProvider>
|
<SidebarProvider>
|
||||||
<NotificationProvider>
|
<PaginationProvider>
|
||||||
<ScrollControllerProvider scrollableSelector="#main">
|
<MainNavProvider>
|
||||||
<SidebarProvider>
|
<SearchProvider>
|
||||||
<PaginationProvider>
|
<HooksLoader
|
||||||
<MainNavProvider>
|
options={{
|
||||||
<SearchProvider>
|
pageScrollManager: true,
|
||||||
<HooksLoader
|
currentLearningPath: true,
|
||||||
options={{
|
}}
|
||||||
pageScrollManager: true,
|
>
|
||||||
currentLearningPath: true,
|
{children}
|
||||||
}}
|
</HooksLoader>
|
||||||
>
|
</SearchProvider>
|
||||||
{children}
|
</MainNavProvider>
|
||||||
</HooksLoader>
|
</PaginationProvider>
|
||||||
</SearchProvider>
|
</SidebarProvider>
|
||||||
</MainNavProvider>
|
</ScrollControllerProvider>
|
||||||
</PaginationProvider>
|
</NotificationProvider>
|
||||||
</SidebarProvider>
|
</LearningPathProvider>
|
||||||
</ScrollControllerProvider>
|
|
||||||
</NotificationProvider>
|
|
||||||
</LearningPathProvider>
|
|
||||||
</ModalProvider>
|
|
||||||
</ColorModeProvider>
|
|
||||||
</MobileProvider>
|
|
||||||
</SiteConfigProvider>
|
</SiteConfigProvider>
|
||||||
</AnalyticsProvider>
|
</AnalyticsProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type MainNavProviderProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const navigationDropdownItems = useMemo(
|
const navigationDropdownItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type FeedbackProps = Omit<UiFeedbackProps, "event" | "pathName">
|
|||||||
|
|
||||||
export const Feedback = (props: FeedbackProps) => {
|
export const Feedback = (props: FeedbackProps) => {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
|
|
||||||
const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
|
const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
|
||||||
const reportLink = useMemo(
|
const reportLink = useMemo(
|
||||||
|
|||||||
@@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AnalyticsProvider,
|
AnalyticsProvider,
|
||||||
ColorModeProvider,
|
|
||||||
HooksLoader,
|
HooksLoader,
|
||||||
LearningPathProvider,
|
LearningPathProvider,
|
||||||
MobileProvider,
|
|
||||||
ModalProvider,
|
|
||||||
NotificationProvider,
|
NotificationProvider,
|
||||||
PaginationProvider,
|
PaginationProvider,
|
||||||
ScrollControllerProvider,
|
ScrollControllerProvider,
|
||||||
@@ -25,36 +22,30 @@ const Providers = ({ children }: ProvidersProps) => {
|
|||||||
return (
|
return (
|
||||||
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
||||||
<SiteConfigProvider config={config}>
|
<SiteConfigProvider config={config}>
|
||||||
<MobileProvider>
|
<LearningPathProvider
|
||||||
<ColorModeProvider>
|
baseUrl={process.env.NEXT_PUBLIC_BASE_PATH || "/resources"}
|
||||||
<ModalProvider>
|
>
|
||||||
<LearningPathProvider
|
<NotificationProvider>
|
||||||
baseUrl={process.env.NEXT_PUBLIC_BASE_PATH || "/resources"}
|
<ScrollControllerProvider scrollableSelector="#main">
|
||||||
>
|
<SidebarProvider>
|
||||||
<NotificationProvider>
|
<PaginationProvider>
|
||||||
<ScrollControllerProvider scrollableSelector="#main">
|
<MainNavProvider>
|
||||||
<SidebarProvider>
|
<SearchProvider>
|
||||||
<PaginationProvider>
|
<HooksLoader
|
||||||
<MainNavProvider>
|
options={{
|
||||||
<SearchProvider>
|
pageScrollManager: true,
|
||||||
<HooksLoader
|
currentLearningPath: true,
|
||||||
options={{
|
}}
|
||||||
pageScrollManager: true,
|
>
|
||||||
currentLearningPath: true,
|
{children}
|
||||||
}}
|
</HooksLoader>
|
||||||
>
|
</SearchProvider>
|
||||||
{children}
|
</MainNavProvider>
|
||||||
</HooksLoader>
|
</PaginationProvider>
|
||||||
</SearchProvider>
|
</SidebarProvider>
|
||||||
</MainNavProvider>
|
</ScrollControllerProvider>
|
||||||
</PaginationProvider>
|
</NotificationProvider>
|
||||||
</SidebarProvider>
|
</LearningPathProvider>
|
||||||
</ScrollControllerProvider>
|
|
||||||
</NotificationProvider>
|
|
||||||
</LearningPathProvider>
|
|
||||||
</ModalProvider>
|
|
||||||
</ColorModeProvider>
|
|
||||||
</MobileProvider>
|
|
||||||
</SiteConfigProvider>
|
</SiteConfigProvider>
|
||||||
</AnalyticsProvider>
|
</AnalyticsProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type MainNavProviderProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const navigationDropdownItems = useMemo(
|
const navigationDropdownItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ColorModeProvider,
|
|
||||||
MobileProvider,
|
|
||||||
ModalProvider,
|
|
||||||
AnalyticsProvider,
|
AnalyticsProvider,
|
||||||
ScrollControllerProvider,
|
ScrollControllerProvider,
|
||||||
SiteConfigProvider,
|
SiteConfigProvider,
|
||||||
@@ -21,19 +18,13 @@ const Providers = ({ children }: ProvidersProps) => {
|
|||||||
return (
|
return (
|
||||||
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
||||||
<SiteConfigProvider config={siteConfig}>
|
<SiteConfigProvider config={siteConfig}>
|
||||||
<MobileProvider>
|
<ScrollControllerProvider scrollableSelector="#main">
|
||||||
<ColorModeProvider>
|
<SidebarProvider>
|
||||||
<ModalProvider>
|
<MainNavProvider>
|
||||||
<ScrollControllerProvider scrollableSelector="#main">
|
<SearchProvider>{children}</SearchProvider>
|
||||||
<SidebarProvider>
|
</MainNavProvider>
|
||||||
<MainNavProvider>
|
</SidebarProvider>
|
||||||
<SearchProvider>{children}</SearchProvider>
|
</ScrollControllerProvider>
|
||||||
</MainNavProvider>
|
|
||||||
</SidebarProvider>
|
|
||||||
</ScrollControllerProvider>
|
|
||||||
</ModalProvider>
|
|
||||||
</ColorModeProvider>
|
|
||||||
</MobileProvider>
|
|
||||||
</SiteConfigProvider>
|
</SiteConfigProvider>
|
||||||
</AnalyticsProvider>
|
</AnalyticsProvider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type MainNavProviderProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const navigationDropdownItems = useMemo(
|
const navigationDropdownItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getNavDropdownItems({
|
getNavDropdownItems({
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type FeedbackProps = Omit<UiFeedbackProps, "event" | "pathName">
|
|||||||
|
|
||||||
const Feedback = (props: FeedbackProps) => {
|
const Feedback = (props: FeedbackProps) => {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
|
|
||||||
const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
|
const feedbackPathname = useMemo(() => basePathUrl(pathname), [pathname])
|
||||||
const reportLink = useMemo(
|
const reportLink = useMemo(
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type MainNavProviderProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
export const MainNavProvider = ({ children }: MainNavProviderProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const navigationDropdownItems = useMemo(
|
const navigationDropdownItems = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getNavDropdownItems({
|
getNavDropdownItems({
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type Bannerv2Props = {
|
|||||||
|
|
||||||
export const Bannerv2 = ({ className }: Bannerv2Props) => {
|
export const Bannerv2 = ({ className }: Bannerv2Props) => {
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isBrowser) {
|
if (!isBrowser) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const ChildDocs = ({
|
|||||||
hideTitle = false,
|
hideTitle = false,
|
||||||
childLevel = 1,
|
childLevel = 1,
|
||||||
}: ChildDocsProps) => {
|
}: ChildDocsProps) => {
|
||||||
const { currentItems, getActiveItem } = useSidebar()
|
const { currentItems, activeItem } = useSidebar()
|
||||||
const filterType = useMemo(() => {
|
const filterType = useMemo(() => {
|
||||||
return showItems !== undefined
|
return showItems !== undefined
|
||||||
? "show"
|
? "show"
|
||||||
@@ -75,7 +75,7 @@ export const ChildDocs = ({
|
|||||||
? Object.assign({}, currentItems)
|
? Object.assign({}, currentItems)
|
||||||
: undefined
|
: undefined
|
||||||
: {
|
: {
|
||||||
default: [...(getActiveItem()?.children || [])],
|
default: [...(activeItem?.children || [])],
|
||||||
}
|
}
|
||||||
if (filterType === "all" || !targetItems) {
|
if (filterType === "all" || !targetItems) {
|
||||||
return targetItems
|
return targetItems
|
||||||
@@ -85,7 +85,7 @@ export const ChildDocs = ({
|
|||||||
...targetItems,
|
...targetItems,
|
||||||
default: filterItems(targetItems.default),
|
default: filterItems(targetItems.default),
|
||||||
}
|
}
|
||||||
}, [currentItems, type, getActiveItem, filterItems])
|
}, [currentItems, type, activeItem, filterItems])
|
||||||
|
|
||||||
const filterNonInteractiveItems = (
|
const filterNonInteractiveItems = (
|
||||||
items: SidebarItem[] | undefined
|
items: SidebarItem[] | undefined
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Link from "next/link"
|
|||||||
import { SidebarItemLink } from "types"
|
import { SidebarItemLink } from "types"
|
||||||
|
|
||||||
export const MainNavBreadcrumbs = () => {
|
export const MainNavBreadcrumbs = () => {
|
||||||
const { currentItems, getActiveItem } = useSidebar()
|
const { currentItems, activeItem } = useSidebar()
|
||||||
const {
|
const {
|
||||||
activeItem: mainNavActiveItem,
|
activeItem: mainNavActiveItem,
|
||||||
breadcrumbOptions: { showCategories },
|
breadcrumbOptions: { showCategories },
|
||||||
@@ -63,7 +63,6 @@ export const MainNavBreadcrumbs = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeItem = getActiveItem()
|
|
||||||
if (activeItem && !mainNavActiveItem?.path.endsWith(activeItem.path)) {
|
if (activeItem && !mainNavActiveItem?.path.endsWith(activeItem.path)) {
|
||||||
if (
|
if (
|
||||||
activeItem.parentItem &&
|
activeItem.parentItem &&
|
||||||
@@ -83,7 +82,7 @@ export const MainNavBreadcrumbs = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return tempBreadcrumbItems
|
return tempBreadcrumbItems
|
||||||
}, [currentItems, getActiveItem])
|
}, [currentItems, activeItem])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import {
|
||||||
|
BrowserProvider,
|
||||||
|
ColorModeProvider,
|
||||||
|
MobileProvider,
|
||||||
|
ModalProvider,
|
||||||
|
} from "../../providers"
|
||||||
|
|
||||||
|
type RootProvidersProps = {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RootProviders = ({ children }: RootProvidersProps) => {
|
||||||
|
return (
|
||||||
|
<BrowserProvider>
|
||||||
|
<MobileProvider>
|
||||||
|
<ColorModeProvider>
|
||||||
|
<ModalProvider>{children}</ModalProvider>
|
||||||
|
</ColorModeProvider>
|
||||||
|
</MobileProvider>
|
||||||
|
</BrowserProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -76,6 +76,12 @@ export const SidebarItemLink = ({
|
|||||||
newTopCalculator,
|
newTopCalculator,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (active && isMobile) {
|
||||||
|
setSidebarOpen(false)
|
||||||
|
}
|
||||||
|
}, [active, isMobile])
|
||||||
|
|
||||||
const hasChildren = useMemo(() => {
|
const hasChildren = useMemo(() => {
|
||||||
return !item.isChildSidebar && (item.children?.length || 0) > 0
|
return !item.isChildSidebar && (item.children?.length || 0) > 0
|
||||||
}, [item.children])
|
}, [item.children])
|
||||||
@@ -108,14 +114,6 @@ export const SidebarItemLink = ({
|
|||||||
"flex justify-between items-center gap-[6px]",
|
"flex justify-between items-center gap-[6px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
scroll={true}
|
|
||||||
onClick={() => {
|
|
||||||
if (isMobile) {
|
|
||||||
setSidebarOpen(false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
replace={!item.isPathHref}
|
|
||||||
shallow={!item.isPathHref}
|
|
||||||
{...item.linkProps}
|
{...item.linkProps}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { TocMenu } from "./Menu"
|
|||||||
export const Toc = () => {
|
export const Toc = () => {
|
||||||
const [items, setItems] = useState<ToCItemUi[]>([])
|
const [items, setItems] = useState<ToCItemUi[]>([])
|
||||||
const [showMenu, setShowMenu] = useState(false)
|
const [showMenu, setShowMenu] = useState(false)
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const { items: headingItems, activeItemId } = useActiveOnScroll({})
|
const { items: headingItems, activeItemId } = useActiveOnScroll({})
|
||||||
const [maxHeight, setMaxHeight] = useState(0)
|
const [maxHeight, setMaxHeight] = useState(0)
|
||||||
const { scrollableElement } = useScrollController()
|
const { scrollableElement } = useScrollController()
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const TypeListItem = ({
|
|||||||
sectionTitle,
|
sectionTitle,
|
||||||
referenceType = "method",
|
referenceType = "method",
|
||||||
}: TypeListItemProps) => {
|
}: TypeListItemProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const {
|
const {
|
||||||
config: { baseUrl, basePath },
|
config: { baseUrl, basePath },
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export * from "./Notification/Item/Layout/Default"
|
|||||||
export * from "./Pagination"
|
export * from "./Pagination"
|
||||||
export * from "./Prerequisites"
|
export * from "./Prerequisites"
|
||||||
export * from "./Rating"
|
export * from "./Rating"
|
||||||
|
export * from "./RootProviders"
|
||||||
export * from "./Search"
|
export * from "./Search"
|
||||||
export * from "./Search/EmptyQueryBoundary"
|
export * from "./Search/EmptyQueryBoundary"
|
||||||
export * from "./Search/Hits"
|
export * from "./Search/Hits"
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export * from "./use-collapsible-code-lines"
|
|||||||
export * from "./use-copy"
|
export * from "./use-copy"
|
||||||
export * from "./use-current-learning-path"
|
export * from "./use-current-learning-path"
|
||||||
export * from "./use-is-external-link"
|
export * from "./use-is-external-link"
|
||||||
export * from "./use-is-browser"
|
|
||||||
export * from "./use-keyboard-shortcut"
|
export * from "./use-keyboard-shortcut"
|
||||||
export * from "./use-page-scroll-manager"
|
export * from "./use-page-scroll-manager"
|
||||||
export * from "./use-request-runner"
|
export * from "./use-request-runner"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const useActiveOnScroll = ({
|
|||||||
const [items, setItems] = useState<ActiveOnScrollItem[]>([])
|
const [items, setItems] = useState<ActiveOnScrollItem[]>([])
|
||||||
const [activeItemId, setActiveItemId] = useState("")
|
const [activeItemId, setActiveItemId] = useState("")
|
||||||
const { scrollableElement } = useScrollController()
|
const { scrollableElement } = useScrollController()
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const root = useMemo(() => {
|
const root = useMemo(() => {
|
||||||
if (!enable) {
|
if (!enable) {
|
||||||
@@ -86,6 +86,8 @@ export const useActiveOnScroll = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const headings = getHeadingsInElm()
|
const headings = getHeadingsInElm()
|
||||||
|
let selectedHeadingByHash: HTMLHeadingElement | undefined = undefined
|
||||||
|
const hash = location.hash.replace("#", "")
|
||||||
let closestPositiveHeading: HTMLHeadingElement | undefined = undefined
|
let closestPositiveHeading: HTMLHeadingElement | undefined = undefined
|
||||||
let closestNegativeHeading: HTMLHeadingElement | undefined = undefined
|
let closestNegativeHeading: HTMLHeadingElement | undefined = undefined
|
||||||
let closestPositiveDistance = Infinity
|
let closestPositiveDistance = Infinity
|
||||||
@@ -97,6 +99,9 @@ export const useActiveOnScroll = ({
|
|||||||
: 0
|
: 0
|
||||||
|
|
||||||
headings?.forEach((heading) => {
|
headings?.forEach((heading) => {
|
||||||
|
if (heading.id === hash) {
|
||||||
|
selectedHeadingByHash = heading as HTMLHeadingElement
|
||||||
|
}
|
||||||
const headingDistance = heading.getBoundingClientRect().top
|
const headingDistance = heading.getBoundingClientRect().top
|
||||||
|
|
||||||
if (headingDistance > 0 && headingDistance < closestPositiveDistance) {
|
if (headingDistance > 0 && headingDistance < closestPositiveDistance) {
|
||||||
@@ -126,6 +131,8 @@ export const useActiveOnScroll = ({
|
|||||||
setActiveItemId(
|
setActiveItemId(
|
||||||
chosenClosest
|
chosenClosest
|
||||||
? (chosenClosest as HTMLHeadingElement).id
|
? (chosenClosest as HTMLHeadingElement).id
|
||||||
|
: selectedHeadingByHash
|
||||||
|
? (selectedHeadingByHash as HTMLHeadingElement).id
|
||||||
: items.length
|
: items.length
|
||||||
? useDefaultIfNoActive
|
? useDefaultIfNoActive
|
||||||
? items[0].heading.id
|
? items[0].heading.id
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const useClickOutside = ({
|
|||||||
elmRef,
|
elmRef,
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
}: UseClickOutsideProps) => {
|
}: UseClickOutsideProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
|
|
||||||
const checkClickOutside = useCallback(
|
const checkClickOutside = useCallback(
|
||||||
(e: MouseEvent) => {
|
(e: MouseEvent) => {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
export const useIsBrowser = () => {
|
|
||||||
const [isBrowser, setIsBrowser] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsBrowser(typeof window !== "undefined")
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return isBrowser
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ export const MainContentLayout = ({
|
|||||||
mainWrapperClasses,
|
mainWrapperClasses,
|
||||||
showBanner = true,
|
showBanner = true,
|
||||||
}: MainContentLayoutProps) => {
|
}: MainContentLayoutProps) => {
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const { desktopSidebarOpen } = useSidebar()
|
const { desktopSidebarOpen } = useSidebar()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { Sidebar, SidebarProps } from "@/components"
|
import { RootProviders, Sidebar, SidebarProps } from "@/components"
|
||||||
import { MobileNavigation } from "../components/MobileNavigation"
|
import { MobileNavigation } from "../components/MobileNavigation"
|
||||||
import { Toc } from "../components/Toc"
|
import { Toc } from "../components/Toc"
|
||||||
import { MainContentLayout, MainContentLayoutProps } from "./main-content"
|
import { MainContentLayout, MainContentLayoutProps } from "./main-content"
|
||||||
@@ -36,14 +36,16 @@ export const RootLayout = ({
|
|||||||
bodyClassName
|
bodyClassName
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ProvidersComponent>
|
<RootProviders>
|
||||||
<MobileNavigation />
|
<ProvidersComponent>
|
||||||
<Sidebar {...sidebarProps} />
|
<MobileNavigation />
|
||||||
<div className={clsx("relative", "h-screen", "flex")}>
|
<Sidebar {...sidebarProps} />
|
||||||
<MainContentLayout {...mainProps} />
|
<div className={clsx("relative", "h-screen", "flex")}>
|
||||||
{showToc && <Toc />}
|
<MainContentLayout {...mainProps} />
|
||||||
</div>
|
{showToc && <Toc />}
|
||||||
</ProvidersComponent>
|
</div>
|
||||||
|
</ProvidersComponent>
|
||||||
|
</RootProviders>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import React, { useContext, useEffect, useState } from "react"
|
||||||
|
|
||||||
|
type BrowserContextType = {
|
||||||
|
isBrowser: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const BrowserContext = React.createContext<BrowserContextType | null>(null)
|
||||||
|
|
||||||
|
type BrowserProviderProps = {
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BrowserProvider = ({ children }: BrowserProviderProps) => {
|
||||||
|
const [isBrowser, setIsBrowser] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsBrowser(typeof window !== "undefined")
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BrowserContext.Provider
|
||||||
|
value={{
|
||||||
|
isBrowser,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</BrowserContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useIsBrowser = () => {
|
||||||
|
const context = useContext(BrowserContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useIsBrowser must be used within a BrowserProvider")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useIsBrowser } from "@/hooks"
|
|
||||||
import { getLearningPath } from "@/utils/learning-paths"
|
import { getLearningPath } from "@/utils/learning-paths"
|
||||||
import React, { createContext, useContext, useEffect, useState } from "react"
|
import React, { createContext, useContext, useEffect, useState } from "react"
|
||||||
import { LearningPathFinishType } from "@/components/LearningPath/Finish"
|
import { LearningPathFinishType } from "@/components/LearningPath/Finish"
|
||||||
import { useAnalytics } from "docs-ui"
|
import { useAnalytics, useIsBrowser } from "docs-ui"
|
||||||
import { usePathname, useRouter } from "next/navigation"
|
import { usePathname, useRouter } from "next/navigation"
|
||||||
|
|
||||||
export type LearningPathType = {
|
export type LearningPathType = {
|
||||||
@@ -54,7 +53,7 @@ export const LearningPathProvider: React.FC<LearningPathProviderProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [path, setPath] = useState<LearningPathType | null>(null)
|
const [path, setPath] = useState<LearningPathType | null>(null)
|
||||||
const [currentStep, setCurrentStep] = useState(-1)
|
const [currentStep, setCurrentStep] = useState(-1)
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { track } = useAnalytics()
|
const { track } = useAnalytics()
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import React, {
|
|||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useReducer,
|
useReducer,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react"
|
} from "react"
|
||||||
import { usePathname, useRouter } from "next/navigation"
|
import { usePathname, useRouter } from "next/navigation"
|
||||||
import { getScrolledTop } from "@/utils"
|
import { getScrolledTop } from "@/utils"
|
||||||
import { useIsBrowser } from "@/hooks"
|
|
||||||
import {
|
import {
|
||||||
SidebarItemSections,
|
SidebarItemSections,
|
||||||
SidebarItem,
|
SidebarItem,
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
SidebarItemCategory,
|
SidebarItemCategory,
|
||||||
SidebarItemLinkWithParent,
|
SidebarItemLinkWithParent,
|
||||||
} from "types"
|
} from "types"
|
||||||
|
import { useIsBrowser } from "../BrowserProvider"
|
||||||
|
|
||||||
export type CurrentItemsState = SidebarSectionItems & {
|
export type CurrentItemsState = SidebarSectionItems & {
|
||||||
previousSidebar?: CurrentItemsState
|
previousSidebar?: CurrentItemsState
|
||||||
@@ -35,7 +36,7 @@ export type SidebarContextType = {
|
|||||||
items: SidebarSectionItems
|
items: SidebarSectionItems
|
||||||
currentItems: CurrentItemsState | undefined
|
currentItems: CurrentItemsState | undefined
|
||||||
activePath: string | null
|
activePath: string | null
|
||||||
getActiveItem: () => SidebarItemLinkWithParent | undefined
|
activeItem: SidebarItemLinkWithParent | undefined
|
||||||
setActivePath: (path: string | null) => void
|
setActivePath: (path: string | null) => void
|
||||||
isLinkActive: (item: SidebarItem, checkChildren?: boolean) => boolean
|
isLinkActive: (item: SidebarItem, checkChildren?: boolean) => boolean
|
||||||
isChildrenActive: (item: SidebarItemCategory) => boolean
|
isChildrenActive: (item: SidebarItemCategory) => boolean
|
||||||
@@ -86,6 +87,8 @@ export type ActionType =
|
|||||||
replacementItems: SidebarSectionItems
|
replacementItems: SidebarSectionItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LinksMap = Map<string, SidebarItemLinkWithParent>
|
||||||
|
|
||||||
const areItemsEqual = (itemA: SidebarItem, itemB: SidebarItem): boolean => {
|
const areItemsEqual = (itemA: SidebarItem, itemB: SidebarItem): boolean => {
|
||||||
if (itemA.type === "separator" || itemB.type === "separator") {
|
if (itemA.type === "separator" || itemB.type === "separator") {
|
||||||
return false
|
return false
|
||||||
@@ -122,6 +125,32 @@ const findItem = (
|
|||||||
return foundItem
|
return foundItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getLinksMap = (
|
||||||
|
items: SidebarItem[],
|
||||||
|
initMap?: LinksMap,
|
||||||
|
parentItem?: InteractiveSidebarItem
|
||||||
|
): LinksMap => {
|
||||||
|
const map: LinksMap = initMap || new Map()
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (item.type === "separator") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.type === "link") {
|
||||||
|
map.set(item.path, {
|
||||||
|
...item,
|
||||||
|
parentItem,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (item.children?.length) {
|
||||||
|
getLinksMap(item.children, map, item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
export const reducer = (state: SidebarSectionItems, actionData: ActionType) => {
|
export const reducer = (state: SidebarSectionItems, actionData: ActionType) => {
|
||||||
if (actionData.type === "replace") {
|
if (actionData.type === "replace") {
|
||||||
return actionData.replacementItems
|
return actionData.replacementItems
|
||||||
@@ -226,6 +255,20 @@ export const SidebarProvider = ({
|
|||||||
CurrentItemsState | undefined
|
CurrentItemsState | undefined
|
||||||
>()
|
>()
|
||||||
const [activePath, setActivePath] = useState<string | null>("")
|
const [activePath, setActivePath] = useState<string | null>("")
|
||||||
|
const linksMap: LinksMap = useMemo(() => {
|
||||||
|
return new Map([
|
||||||
|
...getLinksMap(items.mobile),
|
||||||
|
...getLinksMap(items.default),
|
||||||
|
])
|
||||||
|
}, [items])
|
||||||
|
const findItemInSection = useCallback(findItem, [])
|
||||||
|
const activeItem = useMemo(() => {
|
||||||
|
if (activePath === null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return linksMap.get(activePath)
|
||||||
|
}, [activePath, linksMap])
|
||||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState<boolean>(false)
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState<boolean>(false)
|
||||||
const [sidebarTopHeight, setSidebarTopHeight] = useState(0)
|
const [sidebarTopHeight, setSidebarTopHeight] = useState(0)
|
||||||
const [desktopSidebarOpen, setDesktopSidebarOpen] = useState(true)
|
const [desktopSidebarOpen, setDesktopSidebarOpen] = useState(true)
|
||||||
@@ -233,35 +276,20 @@ export const SidebarProvider = ({
|
|||||||
|
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const isBrowser = useIsBrowser()
|
const { isBrowser } = useIsBrowser()
|
||||||
const getResolvedScrollableElement = useCallback(() => {
|
const getResolvedScrollableElement = useCallback(() => {
|
||||||
return scrollableElement || window
|
return scrollableElement || window
|
||||||
}, [scrollableElement])
|
}, [scrollableElement])
|
||||||
|
|
||||||
const findItemInSection = useCallback(findItem, [])
|
|
||||||
|
|
||||||
const isItemLoaded = useCallback(
|
const isItemLoaded = useCallback(
|
||||||
(path: string) => {
|
(path: string) => {
|
||||||
const item =
|
const item = linksMap.get(path)
|
||||||
findItemInSection(items.mobile, { path, type: "link" }) ||
|
|
||||||
findItemInSection(items.default, { path, type: "link" })
|
|
||||||
|
|
||||||
return item?.loaded || false
|
return item?.loaded || false
|
||||||
},
|
},
|
||||||
[items]
|
[items, linksMap]
|
||||||
)
|
)
|
||||||
|
|
||||||
const getActiveItem = useCallback(() => {
|
|
||||||
if (activePath === null) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
findItemInSection(items.mobile, { path: activePath, type: "link" }) ||
|
|
||||||
findItemInSection(items.default, { path: activePath, type: "link" })
|
|
||||||
)
|
|
||||||
}, [activePath, items, findItemInSection])
|
|
||||||
|
|
||||||
const addItems = (newItems: SidebarItem[], options?: ActionOptionsType) => {
|
const addItems = (newItems: SidebarItem[], options?: ActionOptionsType) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: options?.parent ? "update" : "add",
|
type: options?.parent ? "update" : "add",
|
||||||
@@ -322,10 +350,12 @@ export const SidebarProvider = ({
|
|||||||
if (!currentSidebar && item.children?.length) {
|
if (!currentSidebar && item.children?.length) {
|
||||||
const childSidebar =
|
const childSidebar =
|
||||||
getCurrentSidebar(item.children) ||
|
getCurrentSidebar(item.children) ||
|
||||||
findItem(item.children, {
|
(activePath
|
||||||
path: activePath || undefined,
|
? findItem(item.children, {
|
||||||
type: "link",
|
path: activePath || undefined,
|
||||||
})
|
type: "link",
|
||||||
|
})
|
||||||
|
: undefined)
|
||||||
|
|
||||||
if (childSidebar) {
|
if (childSidebar) {
|
||||||
currentSidebar = childSidebar.isChildSidebar ? childSidebar : item
|
currentSidebar = childSidebar.isChildSidebar ? childSidebar : item
|
||||||
@@ -385,10 +415,16 @@ export const SidebarProvider = ({
|
|||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (getScrolledTop(resolvedScrollableElement) === 0) {
|
if (getScrolledTop(resolvedScrollableElement) === 0) {
|
||||||
setActivePath("")
|
const firstItemPath =
|
||||||
// can't use next router as it doesn't support
|
items.default.length && items.default[0].type === "link"
|
||||||
// changing url without scrolling
|
? items.default[0].path
|
||||||
history.replaceState({}, "", location.pathname)
|
: ""
|
||||||
|
setActivePath(firstItemPath)
|
||||||
|
if (firstItemPath) {
|
||||||
|
router.push(`#${firstItemPath}`, {
|
||||||
|
scroll: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,7 +576,7 @@ export const SidebarProvider = ({
|
|||||||
setMobileSidebarOpen,
|
setMobileSidebarOpen,
|
||||||
desktopSidebarOpen,
|
desktopSidebarOpen,
|
||||||
setDesktopSidebarOpen,
|
setDesktopSidebarOpen,
|
||||||
getActiveItem,
|
activeItem,
|
||||||
staticSidebarItems,
|
staticSidebarItems,
|
||||||
disableActiveTransition,
|
disableActiveTransition,
|
||||||
shouldHandleHashChange,
|
shouldHandleHashChange,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from "./AiAssistant"
|
export * from "./AiAssistant"
|
||||||
export * from "./Analytics"
|
export * from "./Analytics"
|
||||||
|
export * from "./BrowserProvider"
|
||||||
export * from "./ColorMode"
|
export * from "./ColorMode"
|
||||||
export * from "./LearningPath"
|
export * from "./LearningPath"
|
||||||
export * from "./MainNav"
|
export * from "./MainNav"
|
||||||
|
|||||||
+4
-11
@@ -6304,17 +6304,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"caniuse-lite@npm:^1.0.30001464, caniuse-lite@npm:^1.0.30001578, caniuse-lite@npm:^1.0.30001587":
|
"caniuse-lite@npm:^1.0.30001464, caniuse-lite@npm:^1.0.30001578, caniuse-lite@npm:^1.0.30001579, caniuse-lite@npm:^1.0.30001587":
|
||||||
version: 1.0.30001589
|
version: 1.0.30001666
|
||||||
resolution: "caniuse-lite@npm:1.0.30001589"
|
resolution: "caniuse-lite@npm:1.0.30001666"
|
||||||
checksum: 20debfb949413f603011bc7dacaf050010778bc4f8632c86fafd1bd0c43180c95ae7c31f6c82348f6309e5e221934e327c3607a216e3f09640284acf78cd6d4d
|
checksum: 2d49e9be676233c24717f12aad3d01b3e5f902b457fe1deefaa8d82e64786788a8f79381ae437c61b50e15c9aea8aeb59871b1d54cb4c28b9190d53d292e2339
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"caniuse-lite@npm:^1.0.30001579":
|
|
||||||
version: 1.0.30001597
|
|
||||||
resolution: "caniuse-lite@npm:1.0.30001597"
|
|
||||||
checksum: 32dc315ffafacc8167286c95b05f41b3ce2818314ea913ffed6ceb7b58c64c38365ec250114d1ecceac34f1c77e5af089479e54b160c4a89b88fd25a98851b78
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user