From 370a95a2b81689a5f6f639d42cd4cd8ee398eece Mon Sep 17 00:00:00 2001 From: Pranav-IIITM Date: Fri, 21 Aug 2026 15:43:32 +0530 Subject: [PATCH 1/3] fix: make sidebar dropdown arrow togglable Signed-off-by: Pranav-IIITM --- src/components/Layout/Sidebar/SidebarLink.tsx | 21 +++- .../Layout/Sidebar/SidebarRouteTree.tsx | 100 +++++++++++++----- 2 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/components/Layout/Sidebar/SidebarLink.tsx b/src/components/Layout/Sidebar/SidebarLink.tsx index 9650e95fa8f..dd583a35f37 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; + onToggle?: () => void; } export function SidebarLink({ @@ -40,6 +41,7 @@ export function SidebarLink({ isExpanded, hideArrow, isPending, + onToggle, }: SidebarLinkProps) { const ref = useRef(null); @@ -115,7 +117,24 @@ export function SidebarLink({ className={cn('pe-1', { 'text-link dark:text-link-dark': isExpanded, 'text-tertiary dark:text-tertiary-dark': !isExpanded, - })}> + })} + role="button" + tabIndex={0} + onClick={(e) => { + if (onToggle) { + e.preventDefault(); + onToggle(); + } + }} + onKeyDown={(e) => { + if (onToggle && (e.key === 'Enter' || e.key === ' ')) { + e.preventDefault(); + onToggle(); + } + }} + aria-label={isExpanded ? "Collapse" : "Expand"} + aria-expanded={isExpanded} + > )} diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 863355bfdc8..8c24ed037aa 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, Fragment, useState, useEffect} from 'react'; import cn from 'classnames'; import {useRouter} from 'next/router'; @@ -78,6 +78,67 @@ function CollapseWrapper({ ); } +interface ExpandableSidebarItemProps { + title: string; + path: string; + level: number; + routes?: RouteItem[]; + version?: 'canary' | 'major' | 'experimental' | 'rc'; + isForceExpanded: boolean; + breadcrumbs: RouteItem[]; + selected: boolean; + pendingRoute: string | null; +} + +function ExpandableSidebarItem({ + title, + path, + level, + routes, + version, + isForceExpanded, + breadcrumbs, + selected, + pendingRoute, +}: ExpandableSidebarItemProps) { + const isBreadcrumb = + breadcrumbs.length > 1 && + breadcrumbs[breadcrumbs.length - 1].path === path; + const defaultExpanded = isForceExpanded || isBreadcrumb || selected; + const [isExpanded, setIsExpanded] = useState(defaultExpanded); + + useEffect(() => { + if (defaultExpanded) { + setIsExpanded(true); + } + }, [defaultExpanded]); + + return ( +
  • + setIsExpanded(!isExpanded)} + /> + + + +
  • + ); +} + export function SidebarRouteTree({ isForceExpanded, breadcrumbs, @@ -116,32 +177,19 @@ export function SidebarRouteTree({ ); } 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 From 0670ad1b99a7f7965cd56318a1a31f2abdb54baf Mon Sep 17 00:00:00 2001 From: Pranav-IIITM Date: Fri, 21 Aug 2026 16:18:58 +0530 Subject: [PATCH 2/3] fix: make sidebar dropdowns mutually exclusive at the same level Signed-off-by: Pranav-IIITM --- .../Layout/Sidebar/SidebarRouteTree.tsx | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 8c24ed037aa..0ee67836d98 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -100,19 +100,12 @@ function ExpandableSidebarItem({ breadcrumbs, selected, pendingRoute, -}: ExpandableSidebarItemProps) { - const isBreadcrumb = - breadcrumbs.length > 1 && - breadcrumbs[breadcrumbs.length - 1].path === path; - const defaultExpanded = isForceExpanded || isBreadcrumb || selected; - const [isExpanded, setIsExpanded] = useState(defaultExpanded); - - useEffect(() => { - if (defaultExpanded) { - setIsExpanded(true); - } - }, [defaultExpanded]); - + isExpanded, + onToggle, +}: ExpandableSidebarItemProps & { + isExpanded: boolean; + onToggle: () => void; +}) { return (
  • setIsExpanded(!isExpanded)} + onToggle={onToggle} /> - + { + const isBreadcrumb = + breadcrumbs.length > 1 && + breadcrumbs[breadcrumbs.length - 1].path === path; + const selected = slug === path; + return isBreadcrumb || selected; + })?.path || null; + + const [expandedPath, setExpandedPath] = useState( + defaultExpandedPath + ); + + useEffect(() => { + if (defaultExpandedPath) { + setExpandedPath(defaultExpandedPath); + } + }, [defaultExpandedPath]); + return (
      {currentRoutes.map( @@ -189,6 +201,10 @@ export function SidebarRouteTree({ breadcrumbs={breadcrumbs} selected={selected} pendingRoute={pendingRoute} + isExpanded={expandedPath === path} + onToggle={() => + setExpandedPath(expandedPath === path ? null : (path || null)) + } /> ); } else { From 833a252779dcd2a3b5dea507090d22f44ae03845 Mon Sep 17 00:00:00 2001 From: Pranav-IIITM Date: Fri, 21 Aug 2026 17:33:17 +0530 Subject: [PATCH 3/3] style: run prettier to fix formatting Signed-off-by: Pranav-IIITM --- src/components/Layout/Sidebar/SidebarLink.tsx | 5 ++--- .../Layout/Sidebar/SidebarRouteTree.tsx | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/Layout/Sidebar/SidebarLink.tsx b/src/components/Layout/Sidebar/SidebarLink.tsx index dd583a35f37..50608705d60 100644 --- a/src/components/Layout/Sidebar/SidebarLink.tsx +++ b/src/components/Layout/Sidebar/SidebarLink.tsx @@ -132,9 +132,8 @@ export function SidebarLink({ onToggle(); } }} - aria-label={isExpanded ? "Collapse" : "Expand"} - aria-expanded={isExpanded} - > + aria-label={isExpanded ? 'Collapse' : 'Expand'} + aria-expanded={isExpanded}> )} diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 0ee67836d98..8912cea78a0 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -120,7 +120,9 @@ function ExpandableSidebarItem({ hideArrow={isForceExpanded} onToggle={onToggle} /> - + { - const isBreadcrumb = - breadcrumbs.length > 1 && - breadcrumbs[breadcrumbs.length - 1].path === path; - const selected = slug === path; - return isBreadcrumb || selected; - })?.path || null; + const defaultExpandedPath = + currentRoutes.find(({path}) => { + const isBreadcrumb = + breadcrumbs.length > 1 && + breadcrumbs[breadcrumbs.length - 1].path === path; + const selected = slug === path; + return isBreadcrumb || selected; + })?.path || null; const [expandedPath, setExpandedPath] = useState( defaultExpandedPath @@ -203,7 +206,7 @@ export function SidebarRouteTree({ pendingRoute={pendingRoute} isExpanded={expandedPath === path} onToggle={() => - setExpandedPath(expandedPath === path ? null : (path || null)) + setExpandedPath(expandedPath === path ? null : path || null) } /> );