Skip to content

Commit ec1e0b0

Browse files
committed
feat: Introduce SidebarItem component to manage sidebar entry collapse/expand state and add onClick prop to SidebarLink.
1 parent 2534424 commit ec1e0b0

File tree

2 files changed

+129
-95
lines changed

2 files changed

+129
-95
lines changed

src/components/Layout/Sidebar/SidebarLink.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface SidebarLinkProps {
2929
isExpanded?: boolean;
3030
hideArrow?: boolean;
3131
isPending: boolean;
32+
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
3233
}
3334

3435
export function SidebarLink({
@@ -40,6 +41,7 @@ export function SidebarLink({
4041
isExpanded,
4142
hideArrow,
4243
isPending,
44+
onClick,
4345
}: SidebarLinkProps) {
4446
const ref = useRef<HTMLAnchorElement>(null);
4547

@@ -64,6 +66,7 @@ export function SidebarLink({
6466
title={title}
6567
target={target}
6668
passHref
69+
onClick={onClick}
6770
aria-current={selected ? 'page' : undefined}
6871
className={cn(
6972
'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',

src/components/Layout/Sidebar/SidebarRouteTree.tsx

Lines changed: 126 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* Copyright (c) Facebook, Inc. and its affiliates.
1010
*/
1111

12-
import {useRef, useLayoutEffect, Fragment} from 'react';
12+
import {useRef, useLayoutEffect, useState, useEffect, Fragment} from 'react';
1313

1414
import cn from 'classnames';
1515
import {useRouter} from 'next/router';
@@ -78,6 +78,95 @@ function CollapseWrapper({
7878
);
7979
}
8080

81+
interface SidebarItemProps {
82+
item: RouteItem;
83+
level: number;
84+
isForceExpanded: boolean;
85+
breadcrumbs: RouteItem[];
86+
slug: string;
87+
pendingRoute: string | null;
88+
}
89+
90+
function SidebarItem({
91+
item,
92+
level,
93+
isForceExpanded,
94+
breadcrumbs,
95+
slug,
96+
pendingRoute,
97+
}: SidebarItemProps) {
98+
const {path, title, routes, version, heading} = item;
99+
const selected = slug === path;
100+
const isBreadcrumb =
101+
breadcrumbs.length > 1 && breadcrumbs[breadcrumbs.length - 1].path === path;
102+
103+
const [isCollapsed, setIsCollapsed] = useState(false);
104+
105+
const shouldBeExpanded = isForceExpanded || isBreadcrumb || selected;
106+
const isExpanded = shouldBeExpanded && !isCollapsed;
107+
108+
useEffect(() => {
109+
if (!selected) {
110+
setIsCollapsed(false);
111+
}
112+
}, [selected]);
113+
114+
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
115+
if (selected) {
116+
event.preventDefault();
117+
setIsCollapsed((prev) => !prev);
118+
}
119+
};
120+
121+
if (!path || heading) {
122+
return (
123+
<SidebarRouteTree
124+
level={level + 1}
125+
isForceExpanded={isForceExpanded}
126+
routeTree={{title, routes}}
127+
breadcrumbs={[]}
128+
/>
129+
);
130+
} else if (routes) {
131+
return (
132+
<li>
133+
<SidebarLink
134+
href={path}
135+
isPending={pendingRoute === path}
136+
selected={selected}
137+
level={level}
138+
title={title}
139+
version={version}
140+
isExpanded={isExpanded}
141+
hideArrow={isForceExpanded}
142+
onClick={handleClick}
143+
/>
144+
<CollapseWrapper duration={250} isExpanded={isExpanded}>
145+
<SidebarRouteTree
146+
isForceExpanded={isForceExpanded}
147+
routeTree={{title, routes}}
148+
breadcrumbs={breadcrumbs}
149+
level={level + 1}
150+
/>
151+
</CollapseWrapper>
152+
</li>
153+
);
154+
} else {
155+
return (
156+
<li>
157+
<SidebarLink
158+
isPending={pendingRoute === path}
159+
href={path}
160+
selected={selected}
161+
level={level}
162+
title={title}
163+
version={version}
164+
/>
165+
</li>
166+
);
167+
}
168+
}
169+
81170
export function SidebarRouteTree({
82171
isForceExpanded,
83172
breadcrumbs,
@@ -89,102 +178,44 @@ export function SidebarRouteTree({
89178
const currentRoutes = routeTree.routes as RouteItem[];
90179
return (
91180
<ul>
92-
{currentRoutes.map(
93-
(
94-
{
95-
path,
96-
title,
97-
routes,
98-
version,
99-
heading,
100-
hasSectionHeader,
101-
sectionHeader,
102-
},
103-
index
104-
) => {
105-
const selected = slug === path;
106-
let listItem = null;
107-
if (!path || heading) {
108-
// if current route item has no path and children treat it as an API sidebar heading
109-
listItem = (
110-
<SidebarRouteTree
111-
level={level + 1}
112-
isForceExpanded={isForceExpanded}
113-
routeTree={{title, routes}}
114-
breadcrumbs={[]}
115-
/>
116-
);
117-
} else if (routes) {
118-
// if route has a path and child routes, treat it as an expandable sidebar item
119-
const isBreadcrumb =
120-
breadcrumbs.length > 1 &&
121-
breadcrumbs[breadcrumbs.length - 1].path === path;
122-
const isExpanded = isForceExpanded || isBreadcrumb || selected;
123-
listItem = (
124-
<li key={`${title}-${path}-${level}-heading`}>
125-
<SidebarLink
126-
key={`${title}-${path}-${level}-link`}
127-
href={path}
128-
isPending={pendingRoute === path}
129-
selected={selected}
130-
level={level}
131-
title={title}
132-
version={version}
133-
isExpanded={isExpanded}
134-
hideArrow={isForceExpanded}
135-
/>
136-
<CollapseWrapper duration={250} isExpanded={isExpanded}>
137-
<SidebarRouteTree
138-
isForceExpanded={isForceExpanded}
139-
routeTree={{title, routes}}
140-
breadcrumbs={breadcrumbs}
141-
level={level + 1}
142-
/>
143-
</CollapseWrapper>
144-
</li>
145-
);
146-
} else {
147-
// if route has a path and no child routes, treat it as a sidebar link
148-
listItem = (
149-
<li key={`${title}-${path}-${level}-link`}>
150-
<SidebarLink
151-
isPending={pendingRoute === path}
152-
href={path}
153-
selected={selected}
154-
level={level}
155-
title={title}
156-
version={version}
181+
{currentRoutes.map((item, index) => {
182+
const {path, title, hasSectionHeader, sectionHeader} = item;
183+
if (hasSectionHeader) {
184+
let sectionHeaderText =
185+
sectionHeader != null
186+
? sectionHeader.replace('{{version}}', siteConfig.version)
187+
: '';
188+
return (
189+
<Fragment key={`${sectionHeaderText}-${level}-separator`}>
190+
{index !== 0 && (
191+
<li
192+
role="separator"
193+
className="mt-4 mb-2 ms-5 border-b border-border dark:border-border-dark"
157194
/>
158-
</li>
159-
);
160-
}
161-
if (hasSectionHeader) {
162-
let sectionHeaderText =
163-
sectionHeader != null
164-
? sectionHeader.replace('{{version}}', siteConfig.version)
165-
: '';
166-
return (
167-
<Fragment key={`${sectionHeaderText}-${level}-separator`}>
168-
{index !== 0 && (
169-
<li
170-
role="separator"
171-
className="mt-4 mb-2 ms-5 border-b border-border dark:border-border-dark"
172-
/>
173-
)}
174-
<h3
175-
className={cn(
176-
'mb-1 text-sm font-bold ms-5 text-tertiary dark:text-tertiary-dark',
177-
index !== 0 && 'mt-2'
178-
)}>
179-
{sectionHeaderText}
180-
</h3>
181-
</Fragment>
182-
);
183-
} else {
184-
return listItem;
185-
}
195+
)}
196+
<h3
197+
className={cn(
198+
'mb-1 text-sm font-bold ms-5 text-tertiary dark:text-tertiary-dark',
199+
index !== 0 && 'mt-2'
200+
)}>
201+
{sectionHeaderText}
202+
</h3>
203+
</Fragment>
204+
);
205+
} else {
206+
return (
207+
<SidebarItem
208+
key={`${title}-${path}-${level}-item`}
209+
item={item}
210+
level={level}
211+
isForceExpanded={isForceExpanded}
212+
breadcrumbs={breadcrumbs}
213+
slug={slug}
214+
pendingRoute={pendingRoute}
215+
/>
216+
);
186217
}
187-
)}
218+
})}
188219
</ul>
189220
);
190221
}

0 commit comments

Comments
 (0)