Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(theme): new sidebar match algorithm and export SidebarList UI only component #1780

Merged
merged 2 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading