diff --git a/packages/zudoku/src/config/validators/SidebarSchema.ts b/packages/zudoku/src/config/validators/SidebarSchema.ts index adc22bd8..ed978dbc 100644 --- a/packages/zudoku/src/config/validators/SidebarSchema.ts +++ b/packages/zudoku/src/config/validators/SidebarSchema.ts @@ -32,6 +32,7 @@ export type SidebarItemCategory = Omit< items: SidebarItem[]; link?: SidebarItemCategoryLinkDoc; icon?: LucideIcon | string; + apiReference?: boolean; }; export type SidebarItem = diff --git a/packages/zudoku/src/config/validators/validate.ts b/packages/zudoku/src/config/validators/validate.ts index aa8db46d..ba401e71 100644 --- a/packages/zudoku/src/config/validators/validate.ts +++ b/packages/zudoku/src/config/validators/validate.ts @@ -43,6 +43,7 @@ const ThemeSchema = z const ApiConfigSchema = z.object({ server: z.string().optional(), navigationId: z.string().optional(), + defaultCollapsed: z.boolean().optional(), }); const ApiSchema = z.union([ diff --git a/packages/zudoku/src/lib/components/navigation/SidebarCategory.tsx b/packages/zudoku/src/lib/components/navigation/SidebarCategory.tsx index 2f2a27ae..debc2b98 100644 --- a/packages/zudoku/src/lib/components/navigation/SidebarCategory.tsx +++ b/packages/zudoku/src/lib/components/navigation/SidebarCategory.tsx @@ -1,10 +1,11 @@ import * as Collapsible from "@radix-ui/react-collapsible"; import { ChevronRightIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { NavLink, useMatch } from "react-router-dom"; import type { SidebarItemCategory } from "../../../config/validators/SidebarSchema.js"; import { cn } from "../../util/cn.js"; import { joinPath } from "../../util/joinPath.js"; +import { useViewportAnchor } from "../context/ViewportAnchorContext.js"; import { useTopNavigationItem } from "../context/ZudokuContext.js"; import { navigationListItem, SidebarItem } from "./SidebarItem.js"; import { useIsCategoryOpen } from "./utils.js"; @@ -27,6 +28,7 @@ export const SidebarCategory = ({ ); const [open, setOpen] = useState(isDefaultOpen); const isActive = useMatch(joinPath(topNavItem?.id, category.link?.id)); + const { activeAnchor } = useViewportAnchor(); useEffect(() => { // this is triggered when an item from the sidebar is clicked @@ -36,6 +38,25 @@ export const SidebarCategory = ({ } }, [isCategoryOpen]); + const categoryLabel = useMemo( + () => category.label.toLowerCase(), + [category.label], + ); + + // this is useful when sidebar is collapsed and then user scrolls to an anchor link in the content then the sidebar should open to show the active category + useEffect(() => { + if (!activeAnchor) return; + if (hasInteracted) return; + + // if this category is not part of the api reference then return as we don't want to close-open on scroll for other documentation + if (!category.apiReference) return; + + const currentActiveCategory = activeAnchor.split("-")[0]; + const shouldBeOpen = currentActiveCategory === categoryLabel; + + setOpen(shouldBeOpen); + }, [activeAnchor, category.apiReference, categoryLabel, hasInteracted]); + const ToggleButton = isCollapsible && (