Skip to content

Commit ea3b304

Browse files
authored
refactor(theme): new sidebar match algorithm and export SidebarList UI only component (#1780)
1 parent 54e2d41 commit ea3b304

File tree

7 files changed

+199
-188
lines changed

7 files changed

+199
-188
lines changed

packages/theme-default/src/components/Search/SearchPanel.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
88
import { createPortal } from 'react-dom';
99
import * as userSearchHooks from 'virtual-search-hooks';
1010
import { useLocaleSiteData } from '../../logic/useLocaleSiteData';
11-
import { getSidebarGroupData } from '../../logic/useSidebarData';
11+
import { getSidebarData } from '../../logic/useSidebarData';
1212
import { SvgWrapper } from '../SvgWrapper';
1313
import { Tab, Tabs } from '../Tabs';
1414
import { NoSearchResult } from './NoSearchResult';
@@ -123,7 +123,7 @@ export function SearchPanel({ focused, setFocused }: SearchPanelProps) {
123123

124124
// We need to extract the group name by the link so that we can divide the search result into different groups.
125125
const extractGroupName = (link: string) =>
126-
getSidebarGroupData(sidebar, link).group;
126+
getSidebarData(sidebar, link).group;
127127

128128
async function initPageSearcher() {
129129
if (search === false) {

packages/theme-default/src/components/Sidebar/SidebarItem.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { preloadLink } from './utils';
99

1010
export function SidebarItem(props: SidebarItemProps) {
1111
const { item, depth = 0, activeMatcher, id, setSidebarData } = props;
12+
1213
const active = 'link' in item && item.link && activeMatcher(item.link);
1314
const ref = useRef<HTMLDivElement>(null);
1415
useEffect(() => {
@@ -23,10 +24,10 @@ export function SidebarItem(props: SidebarItemProps) {
2324
return (
2425
<SidebarGroup
2526
id={id}
27+
activeMatcher={activeMatcher}
2628
key={`${item.text}-${id}`}
2729
item={item}
2830
depth={depth}
29-
activeMatcher={activeMatcher}
3031
collapsed={item.collapsed}
3132
setSidebarData={setSidebarData}
3233
/>

packages/theme-default/src/components/Sidebar/index.tsx

+99-75
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { matchRoutes, removeBase, useLocation } from '@rspress/runtime';
1+
import { useLocation } from '@rspress/runtime';
22
import {
33
type SidebarDivider as ISidebarDivider,
44
type SidebarItem as ISidebarItem,
55
type SidebarSectionHeader as ISidebarSectionHeader,
66
type NormalizedSidebarGroup,
77
inBrowser,
8-
normalizeSlash,
98
} from '@rspress/shared';
109
import { useEffect, useState } from 'react';
11-
import { routes } from 'virtual-routes';
12-
import { isActive, useLocaleSiteData, useSidebarData } from '../../logic';
10+
import { useSidebarData } from '../../logic';
1311

1412
import type { UISwitchResult } from '../../logic/useUISwitch';
1513
import { NavBarTitle } from '../Nav/NavBarTitle';
@@ -22,6 +20,7 @@ import {
2220
isSideBarCustomLink,
2321
isSidebarDivider,
2422
isSidebarSectionHeader,
23+
useActiveMatcher,
2524
} from './utils';
2625

2726
export interface SidebarItemProps {
@@ -45,7 +44,11 @@ interface Props {
4544
navTitle?: React.ReactNode;
4645
}
4746

48-
type SidebarData = (ISidebarDivider | ISidebarItem | NormalizedSidebarGroup)[];
47+
export type SidebarData = (
48+
| ISidebarDivider
49+
| ISidebarItem
50+
| NormalizedSidebarGroup
51+
)[];
4952

5053
export const highlightTitleStyle = {
5154
fontSize: '14px',
@@ -72,9 +75,9 @@ export function Sidebar(props: Props) {
7275
return rawSidebarData.filter(Boolean).flat();
7376
});
7477

75-
const localesData = useLocaleSiteData();
7678
const pathname = decodeURIComponent(rawPathname);
77-
const langRoutePrefix = normalizeSlash(localesData.langRoutePrefix || '');
79+
80+
const activeMatcher = useActiveMatcher();
7881

7982
useEffect(() => {
8083
if (inBrowser()) {
@@ -133,73 +136,6 @@ export function Sidebar(props: Props) {
133136
setSidebarData(newSidebarData);
134137
}, [rawSidebarData, pathname]);
135138

136-
const removeLangPrefix = (path: string) => {
137-
return path.replace(langRoutePrefix, '');
138-
};
139-
const activeMatcher = (path: string) => {
140-
return isActive(
141-
removeBase(removeLangPrefix(pathname)),
142-
removeLangPrefix(path),
143-
true,
144-
);
145-
};
146-
147-
const renderItem = (
148-
item:
149-
| NormalizedSidebarGroup
150-
| ISidebarItem
151-
| ISidebarDivider
152-
| ISidebarSectionHeader,
153-
index: number,
154-
) => {
155-
if (isSidebarDivider(item)) {
156-
return (
157-
<SidebarDivider key={index} depth={0} dividerType={item.dividerType} />
158-
);
159-
}
160-
161-
if (isSidebarSectionHeader(item)) {
162-
return (
163-
<SidebarSectionHeader
164-
key={index}
165-
sectionHeaderText={item.sectionHeaderText}
166-
tag={item.tag}
167-
/>
168-
);
169-
}
170-
171-
if (isSideBarCustomLink(item)) {
172-
return (
173-
<div
174-
className="rspress-sidebar-item rspress-sidebar-custom-link"
175-
key={index}
176-
data-context={item.context}
177-
>
178-
<SidebarItem
179-
id={String(index)}
180-
item={item}
181-
depth={0}
182-
activeMatcher={activeMatcher}
183-
key={index}
184-
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
185-
setSidebarData={setSidebarData}
186-
/>
187-
</div>
188-
);
189-
}
190-
191-
return (
192-
<SidebarItem
193-
id={String(index)}
194-
item={item}
195-
depth={0}
196-
activeMatcher={activeMatcher}
197-
key={index}
198-
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
199-
setSidebarData={setSidebarData}
200-
/>
201-
);
202-
};
203139
return (
204140
<aside
205141
className={`${styles.sidebar} rspress-sidebar ${
@@ -212,10 +148,98 @@ export function Sidebar(props: Props) {
212148
<div className={`rspress-scrollbar ${styles.sidebarContent}`}>
213149
<nav className="pb-2">
214150
{beforeSidebar}
215-
{sidebarData.map(renderItem)}
151+
<SidebarList
152+
sidebarData={sidebarData}
153+
setSidebarData={setSidebarData}
154+
/>
216155
{afterSidebar}
217156
</nav>
218157
</div>
219158
</aside>
220159
);
221160
}
161+
162+
export function SidebarList({
163+
sidebarData,
164+
setSidebarData,
165+
}: {
166+
sidebarData: SidebarData;
167+
setSidebarData: React.Dispatch<React.SetStateAction<SidebarData>>;
168+
}) {
169+
const activeMatcher = useActiveMatcher();
170+
return (
171+
<>
172+
{sidebarData.map((item, index) => {
173+
return (
174+
<SidebarListItem
175+
key={index}
176+
item={item}
177+
index={index}
178+
setSidebarData={setSidebarData}
179+
activeMatcher={activeMatcher}
180+
/>
181+
);
182+
})}
183+
</>
184+
);
185+
}
186+
187+
function SidebarListItem(props: {
188+
item:
189+
| NormalizedSidebarGroup
190+
| ISidebarItem
191+
| ISidebarDivider
192+
| ISidebarSectionHeader;
193+
index: number;
194+
setSidebarData: React.Dispatch<React.SetStateAction<SidebarData>>;
195+
activeMatcher: (link: string) => boolean;
196+
}) {
197+
const { item, index, setSidebarData, activeMatcher } = props;
198+
if (isSidebarDivider(item)) {
199+
return (
200+
<SidebarDivider key={index} depth={0} dividerType={item.dividerType} />
201+
);
202+
}
203+
204+
if (isSidebarSectionHeader(item)) {
205+
return (
206+
<SidebarSectionHeader
207+
key={index}
208+
sectionHeaderText={item.sectionHeaderText}
209+
tag={item.tag}
210+
/>
211+
);
212+
}
213+
214+
if (isSideBarCustomLink(item)) {
215+
return (
216+
<div
217+
className="rspress-sidebar-item rspress-sidebar-custom-link"
218+
key={index}
219+
data-context={item.context}
220+
>
221+
<SidebarItem
222+
id={String(index)}
223+
item={item}
224+
depth={0}
225+
key={index}
226+
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
227+
setSidebarData={setSidebarData}
228+
activeMatcher={activeMatcher}
229+
/>
230+
</div>
231+
);
232+
}
233+
234+
return (
235+
<SidebarItem
236+
id={String(index)}
237+
item={item}
238+
depth={0}
239+
key={index}
240+
activeMatcher={activeMatcher}
241+
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
242+
setSidebarData={setSidebarData}
243+
/>
244+
);
245+
}

packages/theme-default/src/components/Sidebar/utils.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { matchRoutes } from '@rspress/runtime';
1+
import { matchRoutes, removeBase, useLocation } from '@rspress/runtime';
22
import {
33
type SidebarDivider as ISidebarDivider,
44
type SidebarItem as ISidebarItem,
55
type SidebarSectionHeader as ISidebarSectionHeader,
66
type NormalizedSidebarGroup,
77
isExternalUrl,
8+
normalizeSlash,
89
} from '@rspress/shared';
910
import { routes } from 'virtual-routes';
11+
import { isActive, useLocaleSiteData } from '../../logic';
1012

1113
export const isSidebarDivider = (
1214
item:
@@ -49,3 +51,24 @@ export const preloadLink = (link: string) => {
4951
route.preload();
5052
}
5153
};
54+
55+
export const useActiveMatcher = () => {
56+
const localesData = useLocaleSiteData();
57+
const langRoutePrefix = normalizeSlash(localesData.langRoutePrefix || '');
58+
59+
const { pathname: rawPathname } = useLocation();
60+
61+
const pathname = decodeURIComponent(rawPathname);
62+
const removeLangPrefix = (path: string) => {
63+
return path.replace(langRoutePrefix, '');
64+
};
65+
const activeMatcher = (link: string) => {
66+
return isActive(
67+
removeBase(removeLangPrefix(pathname)),
68+
removeLangPrefix(link),
69+
true,
70+
);
71+
};
72+
73+
return activeMatcher;
74+
};

packages/theme-default/src/components/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export { PackageManagerTabs } from './PackageManagerTabs';
1616
export { PrevNextPage } from './PrevNextPage';
1717
export { ScrollToTop } from './ScrollToTop';
1818
export { Search, SearchPanel } from './Search';
19-
export { Sidebar } from './Sidebar';
19+
export { Sidebar, SidebarList, type SidebarData } from './Sidebar';
2020
export { SocialLinks } from './SocialLinks';
2121
export { SourceCode } from './SourceCode';
2222
export { Steps } from './Steps';

packages/theme-default/src/logic/useFullTextSearch.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react';
33
import type { MatchResult } from '..';
44
import { PageSearcher } from '../components/Search/logic/search';
55
import { useLocaleSiteData } from './useLocaleSiteData';
6-
import { getSidebarGroupData } from './useSidebarData';
6+
import { getSidebarData } from './useSidebarData';
77

88
export function useFullTextSearch(): {
99
initialized: boolean;
@@ -13,7 +13,7 @@ export function useFullTextSearch(): {
1313
const [initialized, setInitialized] = useState(false);
1414
const { sidebar } = useLocaleSiteData();
1515
const extractGroupName = (link: string) =>
16-
getSidebarGroupData(sidebar, link).group;
16+
getSidebarData(sidebar, link).group;
1717
const searchRef = useRef<PageSearcher | null>(null);
1818

1919
useEffect(() => {

0 commit comments

Comments
 (0)