Skip to content

Commit

Permalink
refactor(theme): new sidebar match algorithm and export SidebarList
Browse files Browse the repository at this point in the history
… UI only component (#1780)
  • Loading branch information
SoonIter authored Feb 7, 2025
1 parent 54e2d41 commit ea3b304
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 188 deletions.
4 changes: 2 additions & 2 deletions packages/theme-default/src/components/Search/SearchPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import * as userSearchHooks from 'virtual-search-hooks';
import { useLocaleSiteData } from '../../logic/useLocaleSiteData';
import { getSidebarGroupData } from '../../logic/useSidebarData';
import { getSidebarData } from '../../logic/useSidebarData';
import { SvgWrapper } from '../SvgWrapper';
import { Tab, Tabs } from '../Tabs';
import { NoSearchResult } from './NoSearchResult';
Expand Down Expand Up @@ -123,7 +123,7 @@ export function SearchPanel({ focused, setFocused }: SearchPanelProps) {

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

async function initPageSearcher() {
if (search === false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { preloadLink } from './utils';

export function SidebarItem(props: SidebarItemProps) {
const { item, depth = 0, activeMatcher, id, setSidebarData } = props;

const active = 'link' in item && item.link && activeMatcher(item.link);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
Expand All @@ -23,10 +24,10 @@ export function SidebarItem(props: SidebarItemProps) {
return (
<SidebarGroup
id={id}
activeMatcher={activeMatcher}
key={`${item.text}-${id}`}
item={item}
depth={depth}
activeMatcher={activeMatcher}
collapsed={item.collapsed}
setSidebarData={setSidebarData}
/>
Expand Down
174 changes: 99 additions & 75 deletions packages/theme-default/src/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { matchRoutes, removeBase, useLocation } from '@rspress/runtime';
import { useLocation } from '@rspress/runtime';
import {
type SidebarDivider as ISidebarDivider,
type SidebarItem as ISidebarItem,
type SidebarSectionHeader as ISidebarSectionHeader,
type NormalizedSidebarGroup,
inBrowser,
normalizeSlash,
} from '@rspress/shared';
import { useEffect, useState } from 'react';
import { routes } from 'virtual-routes';
import { isActive, useLocaleSiteData, useSidebarData } from '../../logic';
import { useSidebarData } from '../../logic';

import type { UISwitchResult } from '../../logic/useUISwitch';
import { NavBarTitle } from '../Nav/NavBarTitle';
Expand All @@ -22,6 +20,7 @@ import {
isSideBarCustomLink,
isSidebarDivider,
isSidebarSectionHeader,
useActiveMatcher,
} from './utils';

export interface SidebarItemProps {
Expand All @@ -45,7 +44,11 @@ interface Props {
navTitle?: React.ReactNode;
}

type SidebarData = (ISidebarDivider | ISidebarItem | NormalizedSidebarGroup)[];
export type SidebarData = (
| ISidebarDivider
| ISidebarItem
| NormalizedSidebarGroup
)[];

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

const localesData = useLocaleSiteData();
const pathname = decodeURIComponent(rawPathname);
const langRoutePrefix = normalizeSlash(localesData.langRoutePrefix || '');

const activeMatcher = useActiveMatcher();

useEffect(() => {
if (inBrowser()) {
Expand Down Expand Up @@ -133,73 +136,6 @@ export function Sidebar(props: Props) {
setSidebarData(newSidebarData);
}, [rawSidebarData, pathname]);

const removeLangPrefix = (path: string) => {
return path.replace(langRoutePrefix, '');
};
const activeMatcher = (path: string) => {
return isActive(
removeBase(removeLangPrefix(pathname)),
removeLangPrefix(path),
true,
);
};

const renderItem = (
item:
| NormalizedSidebarGroup
| ISidebarItem
| ISidebarDivider
| ISidebarSectionHeader,
index: number,
) => {
if (isSidebarDivider(item)) {
return (
<SidebarDivider key={index} depth={0} dividerType={item.dividerType} />
);
}

if (isSidebarSectionHeader(item)) {
return (
<SidebarSectionHeader
key={index}
sectionHeaderText={item.sectionHeaderText}
tag={item.tag}
/>
);
}

if (isSideBarCustomLink(item)) {
return (
<div
className="rspress-sidebar-item rspress-sidebar-custom-link"
key={index}
data-context={item.context}
>
<SidebarItem
id={String(index)}
item={item}
depth={0}
activeMatcher={activeMatcher}
key={index}
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
setSidebarData={setSidebarData}
/>
</div>
);
}

return (
<SidebarItem
id={String(index)}
item={item}
depth={0}
activeMatcher={activeMatcher}
key={index}
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
setSidebarData={setSidebarData}
/>
);
};
return (
<aside
className={`${styles.sidebar} rspress-sidebar ${
Expand All @@ -212,10 +148,98 @@ export function Sidebar(props: Props) {
<div className={`rspress-scrollbar ${styles.sidebarContent}`}>
<nav className="pb-2">
{beforeSidebar}
{sidebarData.map(renderItem)}
<SidebarList
sidebarData={sidebarData}
setSidebarData={setSidebarData}
/>
{afterSidebar}
</nav>
</div>
</aside>
);
}

export function SidebarList({
sidebarData,
setSidebarData,
}: {
sidebarData: SidebarData;
setSidebarData: React.Dispatch<React.SetStateAction<SidebarData>>;
}) {
const activeMatcher = useActiveMatcher();
return (
<>
{sidebarData.map((item, index) => {
return (
<SidebarListItem
key={index}
item={item}
index={index}
setSidebarData={setSidebarData}
activeMatcher={activeMatcher}
/>
);
})}
</>
);
}

function SidebarListItem(props: {
item:
| NormalizedSidebarGroup
| ISidebarItem
| ISidebarDivider
| ISidebarSectionHeader;
index: number;
setSidebarData: React.Dispatch<React.SetStateAction<SidebarData>>;
activeMatcher: (link: string) => boolean;
}) {
const { item, index, setSidebarData, activeMatcher } = props;
if (isSidebarDivider(item)) {
return (
<SidebarDivider key={index} depth={0} dividerType={item.dividerType} />
);
}

if (isSidebarSectionHeader(item)) {
return (
<SidebarSectionHeader
key={index}
sectionHeaderText={item.sectionHeaderText}
tag={item.tag}
/>
);
}

if (isSideBarCustomLink(item)) {
return (
<div
className="rspress-sidebar-item rspress-sidebar-custom-link"
key={index}
data-context={item.context}
>
<SidebarItem
id={String(index)}
item={item}
depth={0}
key={index}
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
setSidebarData={setSidebarData}
activeMatcher={activeMatcher}
/>
</div>
);
}

return (
<SidebarItem
id={String(index)}
item={item}
depth={0}
key={index}
activeMatcher={activeMatcher}
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
setSidebarData={setSidebarData}
/>
);
}
25 changes: 24 additions & 1 deletion packages/theme-default/src/components/Sidebar/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { matchRoutes } from '@rspress/runtime';
import { matchRoutes, removeBase, useLocation } from '@rspress/runtime';
import {
type SidebarDivider as ISidebarDivider,
type SidebarItem as ISidebarItem,
type SidebarSectionHeader as ISidebarSectionHeader,
type NormalizedSidebarGroup,
isExternalUrl,
normalizeSlash,
} from '@rspress/shared';
import { routes } from 'virtual-routes';
import { isActive, useLocaleSiteData } from '../../logic';

export const isSidebarDivider = (
item:
Expand Down Expand Up @@ -49,3 +51,24 @@ export const preloadLink = (link: string) => {
route.preload();
}
};

export const useActiveMatcher = () => {
const localesData = useLocaleSiteData();
const langRoutePrefix = normalizeSlash(localesData.langRoutePrefix || '');

const { pathname: rawPathname } = useLocation();

const pathname = decodeURIComponent(rawPathname);
const removeLangPrefix = (path: string) => {
return path.replace(langRoutePrefix, '');
};
const activeMatcher = (link: string) => {
return isActive(
removeBase(removeLangPrefix(pathname)),
removeLangPrefix(link),
true,
);
};

return activeMatcher;
};
2 changes: 1 addition & 1 deletion packages/theme-default/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export { PackageManagerTabs } from './PackageManagerTabs';
export { PrevNextPage } from './PrevNextPage';
export { ScrollToTop } from './ScrollToTop';
export { Search, SearchPanel } from './Search';
export { Sidebar } from './Sidebar';
export { Sidebar, SidebarList, type SidebarData } from './Sidebar';
export { SocialLinks } from './SocialLinks';
export { SourceCode } from './SourceCode';
export { Steps } from './Steps';
Expand Down
4 changes: 2 additions & 2 deletions packages/theme-default/src/logic/useFullTextSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react';
import type { MatchResult } from '..';
import { PageSearcher } from '../components/Search/logic/search';
import { useLocaleSiteData } from './useLocaleSiteData';
import { getSidebarGroupData } from './useSidebarData';
import { getSidebarData } from './useSidebarData';

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

useEffect(() => {
Expand Down
Loading

0 comments on commit ea3b304

Please sign in to comment.