From ec1e0b0725630d51725d75c2961c96b935d31a58 Mon Sep 17 00:00:00 2001 From: JayadityaGit Date: Wed, 19 Nov 2025 12:30:15 +0530 Subject: [PATCH] feat: Introduce `SidebarItem` component to manage sidebar entry collapse/expand state and add `onClick` prop to `SidebarLink`. --- src/components/Layout/Sidebar/SidebarLink.tsx | 3 + .../Layout/Sidebar/SidebarRouteTree.tsx | 221 ++++++++++-------- 2 files changed, 129 insertions(+), 95 deletions(-) diff --git a/src/components/Layout/Sidebar/SidebarLink.tsx b/src/components/Layout/Sidebar/SidebarLink.tsx index 9650e95fa8f..35b18065bcf 100644 --- a/src/components/Layout/Sidebar/SidebarLink.tsx +++ b/src/components/Layout/Sidebar/SidebarLink.tsx @@ -29,6 +29,7 @@ interface SidebarLinkProps { isExpanded?: boolean; hideArrow?: boolean; isPending: boolean; + onClick?: (event: React.MouseEvent) => void; } export function SidebarLink({ @@ -40,6 +41,7 @@ export function SidebarLink({ isExpanded, hideArrow, isPending, + onClick, }: SidebarLinkProps) { const ref = useRef(null); @@ -64,6 +66,7 @@ export function SidebarLink({ title={title} target={target} passHref + onClick={onClick} aria-current={selected ? 'page' : undefined} className={cn( 'p-2 pe-2 w-full rounded-none lg:rounded-e-2xl text-start hover:bg-gray-5 dark:hover:bg-gray-80 relative flex items-center justify-between', diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 863355bfdc8..b2dd11f77cf 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -9,7 +9,7 @@ * Copyright (c) Facebook, Inc. and its affiliates. */ -import {useRef, useLayoutEffect, Fragment} from 'react'; +import {useRef, useLayoutEffect, useState, useEffect, Fragment} from 'react'; import cn from 'classnames'; import {useRouter} from 'next/router'; @@ -78,6 +78,95 @@ function CollapseWrapper({ ); } +interface SidebarItemProps { + item: RouteItem; + level: number; + isForceExpanded: boolean; + breadcrumbs: RouteItem[]; + slug: string; + pendingRoute: string | null; +} + +function SidebarItem({ + item, + level, + isForceExpanded, + breadcrumbs, + slug, + pendingRoute, +}: SidebarItemProps) { + const {path, title, routes, version, heading} = item; + const selected = slug === path; + const isBreadcrumb = + breadcrumbs.length > 1 && breadcrumbs[breadcrumbs.length - 1].path === path; + + const [isCollapsed, setIsCollapsed] = useState(false); + + const shouldBeExpanded = isForceExpanded || isBreadcrumb || selected; + const isExpanded = shouldBeExpanded && !isCollapsed; + + useEffect(() => { + if (!selected) { + setIsCollapsed(false); + } + }, [selected]); + + const handleClick = (event: React.MouseEvent) => { + if (selected) { + event.preventDefault(); + setIsCollapsed((prev) => !prev); + } + }; + + if (!path || heading) { + return ( + + ); + } else if (routes) { + return ( +
  • + + + + +
  • + ); + } else { + return ( +
  • + +
  • + ); + } +} + export function SidebarRouteTree({ isForceExpanded, breadcrumbs, @@ -89,102 +178,44 @@ export function SidebarRouteTree({ const currentRoutes = routeTree.routes as RouteItem[]; return (
      - {currentRoutes.map( - ( - { - path, - title, - routes, - version, - heading, - hasSectionHeader, - sectionHeader, - }, - index - ) => { - const selected = slug === path; - let listItem = null; - if (!path || heading) { - // if current route item has no path and children treat it as an API sidebar heading - listItem = ( - - ); - } else if (routes) { - // if route has a path and child routes, treat it as an expandable sidebar item - const isBreadcrumb = - breadcrumbs.length > 1 && - breadcrumbs[breadcrumbs.length - 1].path === path; - const isExpanded = isForceExpanded || isBreadcrumb || selected; - listItem = ( -
    • - - - - -
    • - ); - } else { - // if route has a path and no child routes, treat it as a sidebar link - listItem = ( -
    • - { + const {path, title, hasSectionHeader, sectionHeader} = item; + if (hasSectionHeader) { + let sectionHeaderText = + sectionHeader != null + ? sectionHeader.replace('{{version}}', siteConfig.version) + : ''; + return ( + + {index !== 0 && ( +
    • -
    • - ); - } - if (hasSectionHeader) { - let sectionHeaderText = - sectionHeader != null - ? sectionHeader.replace('{{version}}', siteConfig.version) - : ''; - return ( - - {index !== 0 && ( -
    • - )} -

      - {sectionHeaderText} -

      - - ); - } else { - return listItem; - } + )} +

      + {sectionHeaderText} +

      + + ); + } else { + return ( + + ); } - )} + })}
    ); }