Skip to content

fix: sidebar type=file with data-context #2189

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

Merged
merged 6 commits into from
May 26, 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
5 changes: 5 additions & 0 deletions .changeset/open-zebras-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rspress/theme-default': minor
---

fix: sidebar top level item didn't add data-context correctly
3 changes: 2 additions & 1 deletion e2e/fixtures/auto-nav-sidebar/doc/api/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
{
"type": "file",
"name": "index",
"label": "API Overview"
"label": "API Overview",
"context": "api-overview"
},
{
"type": "dir",
Expand Down
6 changes: 5 additions & 1 deletion e2e/tests/auto-nav-sidebar-dir-convension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ test.describe('Auto nav and sidebar dir convention', async () => {
),
sidebarGroupItems,
);
expect(contexts3.join(',')).toEqual(['context-index-in-meta'].join(','));
// added container `div.rspress-sidebar-item` to ( depth=0 & type=file )'s sidebar item
// so have to modify this test result
expect(contexts3.join(',')).toEqual(
['', 'context-index-in-meta'].join(','),
);
});

test('/api/config/index.html /api/config/index /api/config should be the same page', async ({
Expand Down
12 changes: 10 additions & 2 deletions e2e/tests/auto-nav-sidebar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ test.describe('Auto nav and sidebar test', async () => {
['config', null, 'client-api', null].join(','),
);

// Find sidebar elements with data-context="api-overview"
const overviewItems = await page.$$('[data-context="api-overview"]');

// Assert that there is at least one api-overview marker and the content is correct
expect(overviewItems.length).toEqual(1);

const sidebarGroupCollapses = await page.$$('.rspress-sidebar-collapse');
const c2 = await page.evaluate(
sidebars =>
Expand All @@ -215,8 +221,10 @@ test.describe('Auto nav and sidebar test', async () => {

const sidebarGroupItems = await page.$$('.rspress-sidebar-item');
const c3 = await getDataContextFromElements(sidebarGroupItems);
expect(c3?.[2]).toEqual('front-matter');
expect(c3?.[3]).toEqual('config-build');
// added the `depth=0 type=file` sidebar item with div.rspress-sidebar-item container
// so modify this to update test case
expect(c3?.[3]).toEqual('front-matter');
expect(c3?.[4]).toEqual('config-build');

// custom link should work
const customLinkItems = await page.$$(
Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ test.describe('i18n test', async () => {
await page.goto(`http://localhost:${appPort}/guide/basic/quick-start`, {
waitUntil: 'networkidle',
});
const customLinkZh = await page.$('nav > a');
const customLinkZh = await page.$('nav > div.rspress-sidebar-item > a');
const hrefZh = await page.evaluate(
customLinkZh => customLinkZh?.getAttribute('href'),
customLinkZh,
Expand All @@ -171,7 +171,7 @@ test.describe('i18n test', async () => {
await page.goto(`http://localhost:${appPort}/en/guide/basic/quick-start`, {
waitUntil: 'networkidle',
});
const customLinkEn = await page.$('nav > a');
const customLinkEn = await page.$('nav > div.rspress-sidebar-item > a');
const hrefEn = await page.evaluate(
customLinkEn => customLinkEn?.getAttribute('href'),
customLinkEn,
Expand Down
2 changes: 1 addition & 1 deletion e2e/utils/getSideBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function getSidebar(page: Page) {
// take the sidebar, properly a section or a tag
const sidebar = await page.$$(
`.rspress-sidebar .rspress-scrollbar > nav > section,
.rspress-sidebar .rspress-scrollbar > nav > a`,
.rspress-sidebar .rspress-scrollbar > nav > div.rspress-sidebar-item > a`,
);
return sidebar;
}
Expand Down
17 changes: 6 additions & 11 deletions packages/theme-default/src/components/Sidebar/SidebarGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,13 @@ export function SidebarGroup(props: SidebarItemProps) {
/>
) : (
// eslint-disable-next-line react/no-array-index-key
<div
className="rspress-sidebar-item"
<SidebarItemComp
{...props}
key={index}
data-context={item.context}
>
<SidebarItemComp
{...props}
item={item}
depth={depth + 1}
id={`${id}-${index}`}
/>
</div>
item={item}
depth={depth + 1}
id={`${id}-${index}`}
/>
),
)}
</div>
Expand Down
75 changes: 56 additions & 19 deletions packages/theme-default/src/components/Sidebar/SidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,63 @@ export function SidebarItem(props: SidebarItemProps) {
);
}

// add the div.rspress-sidebar-item in an unified place
return (
<Link href={normalizeHref(item.link)} className={styles.menuLink}>
<div
ref={ref}
className={`${
active
? `${styles.menuItemActive} rspress-sidebar-item-active`
: styles.menuItem
} rp-mt-0.5 rp-py-2 rp-px-3 rp-font-medium rp-flex`}
style={{
// The first level menu item will have the same font size as the sidebar group
fontSize: depth === 0 ? '14px' : '13px',
marginLeft: depth === 0 ? 0 : '18px',
borderRadius: '0 var(--rp-radius) var(--rp-radius) 0',
...(depth === 0 ? highlightTitleStyle : {}),
}}
<LinkContextContainer
context={item.context}
className={props.contextContainerClassName}
>
<Link
// {...(depth === 0 ? { 'data-context': item.context } : {})}
href={normalizeHref(item.link)}
className={styles.menuLink}
>
<Tag tag={item.tag} />
<span>{renderInlineMarkdown(item.text)}</span>
</div>
</Link>
<div
ref={ref}
className={`${
active
? `${styles.menuItemActive} rspress-sidebar-item-active`
: styles.menuItem
} rp-mt-0.5 rp-py-2 rp-px-3 rp-font-medium rp-flex`}
style={{
// The first level menu item will have the same font size as the sidebar group
fontSize: depth === 0 ? '14px' : '13px',
marginLeft: depth === 0 ? 0 : '18px',
borderRadius: '0 var(--rp-radius) var(--rp-radius) 0',
...(depth === 0 ? highlightTitleStyle : {}),
}}
>
<Tag tag={item.tag} />
<span>{renderInlineMarkdown(item.text)}</span>
</div>
</Link>
</LinkContextContainer>
);
}

/**
* A container component for sidebar link items that conditionally wraps its children
* with a <div> element, adding contextual data and custom class names as needed.
*
* This design helps maintain sidebar structure stability and minimizes disruption to historical tests
* and user code.
*
* @param props - The component props.
* @param props.context - Optional context string to be added as a `data-context` attribute.
* @param props.className - Optional additional class name(s) for the container div.
* @param props.children - The content to be rendered inside the container.
*/
function LinkContextContainer(
props: React.PropsWithChildren<{ context?: string; className?: string }>,
) {
return (
<div
className={['rspress-sidebar-item', props.className]
.filter(Boolean)
.join(' ')}
{...(props.context ? { 'data-context': props.context } : {})}
>
{props.children}
</div>
);
}
24 changes: 10 additions & 14 deletions packages/theme-default/src/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface SidebarItemProps {
(NormalizedSidebarGroup | ISidebarItem | ISidebarDivider)[]
>
>;
contextContainerClassName?: string;
}

interface Props {
Expand Down Expand Up @@ -218,21 +219,16 @@ function SidebarListItem(props: {

if (isSideBarCustomLink(item)) {
return (
<div
className="rspress-sidebar-item rspress-sidebar-custom-link"
<SidebarItem
id={String(index)}
item={item}
depth={0}
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>
collapsed={(item as NormalizedSidebarGroup).collapsed ?? true}
setSidebarData={setSidebarData}
activeMatcher={activeMatcher}
contextContainerClassName="rspress-sidebar-custom-link"
/>
);
}

Expand Down