Skip to content

Commit 6ec0db4

Browse files
feat(content-docs): sidebar item type "html" for rendering pure markup (#6519)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
1 parent 65ba551 commit 6ec0db4

File tree

9 files changed

+172
-3
lines changed

9 files changed

+172
-3
lines changed

packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts

+36
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,42 @@ describe('validateSidebars', () => {
4747
});
4848
});
4949

50+
describe('html item type', () => {
51+
test('requires a value', () => {
52+
const sidebars: SidebarsConfig = {
53+
sidebar1: [
54+
{
55+
// @ts-expect-error - test missing value
56+
type: 'html',
57+
},
58+
],
59+
};
60+
expect(() => validateSidebars(sidebars))
61+
.toThrowErrorMatchingInlineSnapshot(`
62+
"{
63+
\\"type\\": \\"html\\",
64+
\\"value\\" [1]: -- missing --
65+
}
66+

67+
[1] \\"value\\" is required"
68+
`);
69+
});
70+
71+
test('accepts valid values', () => {
72+
const sidebars: SidebarsConfig = {
73+
sidebar1: [
74+
{
75+
type: 'html',
76+
value: '<p>Hello, World!</p>',
77+
defaultStyle: true,
78+
className: 'foo',
79+
},
80+
],
81+
};
82+
validateSidebars(sidebars);
83+
});
84+
});
85+
5086
describe('validateCategoryMetadataFile', () => {
5187
// TODO add more tests
5288

packages/docusaurus-plugin-content-docs/src/sidebars/types.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export type SidebarItemDoc = SidebarItemBase & {
2727
id: string;
2828
};
2929

30+
export type SidebarItemHtml = SidebarItemBase & {
31+
type: 'html';
32+
value: string;
33+
defaultStyle?: boolean;
34+
};
35+
3036
export type SidebarItemLink = SidebarItemBase & {
3137
type: 'link';
3238
href: string;
@@ -87,6 +93,7 @@ export type SidebarCategoriesShorthand = {
8793

8894
export type SidebarItemConfig =
8995
| SidebarItemDoc
96+
| SidebarItemHtml
9097
| SidebarItemLink
9198
| SidebarItemAutogenerated
9299
| SidebarItemCategoryConfig
@@ -108,6 +115,7 @@ export type NormalizedSidebarItemCategory = Expand<
108115

109116
export type NormalizedSidebarItem =
110117
| SidebarItemDoc
118+
| SidebarItemHtml
111119
| SidebarItemLink
112120
| NormalizedSidebarItemCategory
113121
| SidebarItemAutogenerated;
@@ -131,6 +139,7 @@ export type SidebarItemCategoryWithGeneratedIndex =
131139

132140
export type SidebarItem =
133141
| SidebarItemDoc
142+
| SidebarItemHtml
134143
| SidebarItemLink
135144
| SidebarItemCategory;
136145

@@ -158,7 +167,12 @@ export type PropSidebarItemLink = SidebarItemLink & {
158167
docId?: string;
159168
};
160169

161-
export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory;
170+
export type PropSidebarItemHtml = SidebarItemHtml;
171+
172+
export type PropSidebarItem =
173+
| PropSidebarItemLink
174+
| PropSidebarItemCategory
175+
| PropSidebarItemHtml;
162176
export type PropSidebar = PropSidebarItem[];
163177
export type PropSidebars = {
164178
[sidebarId: string]: PropSidebar;

packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts

+8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
SidebarItemBase,
1313
SidebarItemAutogenerated,
1414
SidebarItemDoc,
15+
SidebarItemHtml,
1516
SidebarItemLink,
1617
SidebarItemCategoryConfig,
1718
SidebarItemCategoryLink,
@@ -48,6 +49,12 @@ const sidebarItemDocSchema = sidebarItemBaseSchema.append<SidebarItemDoc>({
4849
label: Joi.string(),
4950
});
5051

52+
const sidebarItemHtmlSchema = sidebarItemBaseSchema.append<SidebarItemHtml>({
53+
type: 'html',
54+
value: Joi.string().required(),
55+
defaultStyle: Joi.boolean().default(false),
56+
});
57+
5158
const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
5259
type: 'link',
5360
href: URISchema.required(),
@@ -117,6 +124,7 @@ const sidebarItemSchema: Joi.Schema<SidebarItemConfig> = Joi.object()
117124
is: Joi.string().valid('doc', 'ref').required(),
118125
then: sidebarItemDocSchema,
119126
},
127+
{is: 'html', then: sidebarItemHtmlSchema},
120128
{is: 'autogenerated', then: sidebarItemAutogeneratedSchema},
121129
{is: 'category', then: sidebarItemCategorySchema},
122130
{

packages/docusaurus-theme-classic/src/theme/DocSidebarItem/index.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type {
3232

3333
import styles from './styles.module.css';
3434
import useIsBrowser from '@docusaurus/useIsBrowser';
35+
import type {SidebarItemHtml} from '@docusaurus/plugin-content-docs/src/sidebars/types';
3536

3637
export default function DocSidebarItem({
3738
item,
@@ -40,6 +41,8 @@ export default function DocSidebarItem({
4041
switch (item.type) {
4142
case 'category':
4243
return <DocSidebarItemCategory item={item} {...props} />;
44+
case 'html':
45+
return <DocSidebarItemHtml item={item} {...props} />;
4346
case 'link':
4447
default:
4548
return <DocSidebarItemLink item={item} {...props} />;
@@ -216,6 +219,29 @@ function DocSidebarItemCategory({
216219
);
217220
}
218221

222+
function DocSidebarItemHtml({
223+
item,
224+
level,
225+
index,
226+
}: Props & {item: SidebarItemHtml}) {
227+
const {value, defaultStyle, className} = item;
228+
return (
229+
<li
230+
className={clsx(
231+
ThemeClassNames.docs.docSidebarItemLink,
232+
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
233+
defaultStyle && `${styles.menuHtmlItem} menu__list-item`,
234+
className,
235+
)}
236+
key={index}
237+
// eslint-disable-next-line react/no-danger
238+
dangerouslySetInnerHTML={{
239+
__html: value,
240+
}}
241+
/>
242+
);
243+
}
244+
219245
function DocSidebarItemLink({
220246
item,
221247
onItemClick,

packages/docusaurus-theme-classic/src/theme/DocSidebarItem/styles.module.css

+5
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@
1717
.menuLinkText.hasHref {
1818
cursor: pointer;
1919
}
20+
21+
.menuHtmlItem {
22+
padding: var(--ifm-menu-link-padding-vertical)
23+
var(--ifm-menu-link-padding-horizontal);
24+
}
2025
}

packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,11 @@ describe('docsUtils', () => {
226226
testCategory({
227227
href: undefined,
228228
items: [
229+
{type: 'html', value: '<p>test1</p>'},
229230
testCategory({
230231
href: undefined,
231232
items: [
233+
{type: 'html', value: '<p>test2</p>'},
232234
testCategory({
233235
href: '/itemPath',
234236
}),

packages/docusaurus-theme-common/src/utils/docsUtils.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,13 @@ export function findFirstCategoryLink(
123123
for (const subItem of item.items) {
124124
if (subItem.type === 'link') {
125125
return subItem.href;
126-
}
127-
if (subItem.type === 'category') {
126+
} else if (subItem.type === 'category') {
128127
const categoryLink = findFirstCategoryLink(subItem);
129128
if (categoryLink) {
130129
return categoryLink;
131130
}
131+
} else if (subItem.type === 'html') {
132+
// skip
132133
} else {
133134
throw new Error(
134135
`Unexpected category item type for ${JSON.stringify(subItem)}`,

website/_dogfooding/docs-tests-sidebars.js

+27
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,33 @@ const sidebars = {
7171
},
7272
],
7373
},
74+
{
75+
type: 'category',
76+
label: 'HTML items tests',
77+
items: [
78+
// title
79+
{
80+
type: 'html',
81+
value: 'Some Text',
82+
defaultStyle: true,
83+
},
84+
// Divider
85+
{
86+
type: 'html',
87+
value:
88+
'<span style="border-top: 1px solid var(--ifm-color-gray-500); display: block;margin: 0.5rem 0 0.25rem 1rem;" />',
89+
},
90+
// Image
91+
{
92+
type: 'html',
93+
defaultStyle: true,
94+
value: `
95+
<span style="font-size: 0.5rem; color: lightgrey;">Powered by</span>
96+
<img style="width: 100px; height: 100px; display: block;" src="/img/docusaurus.png" alt="Docusaurus Logo" />
97+
`,
98+
},
99+
],
100+
},
74101
],
75102
anotherSidebar: ['dummy'],
76103
};

website/docs/guides/docs/sidebar/items.md

+50
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ We have introduced three types of item types in the example in the previous sect
1111
- **[Link](#sidebar-item-link)**: link to any internal or external page
1212
- **[Category](#sidebar-item-category)**: creates a dropdown of sidebar items
1313
- **[Autogenerated](autogenerated.md)**: generate a sidebar slice automatically
14+
- **[HTML](#sidebar-item-html)**: renders pure HTML in the item's position
1415
- **[\*Ref](multiple-sidebars.md#sidebar-item-ref)**: link to a doc page, without making the item take part in navigation generation
1516

1617
## Doc: link to a doc {#sidebar-item-doc}
@@ -160,6 +161,55 @@ module.exports = {
160161

161162
:::
162163

164+
## HTML: render custom markup {#sidebar-item-html}
165+
166+
Use the `html` type to render custom HTML within the item's `<li>` tag.
167+
168+
This can be useful for inserting custom items such as dividers, section titles, ads, and images.
169+
170+
```ts
171+
type SidebarItemHtml = {
172+
type: 'html';
173+
value: string;
174+
defaultStyle?: boolean; // Use default menu item styles
175+
className?: string;
176+
};
177+
```
178+
179+
Example:
180+
181+
```js title="sidebars.js"
182+
module.exports = {
183+
myHtmlSidebar: [
184+
// highlight-start
185+
{
186+
type: 'html',
187+
value: '<img src="sponsor.png" alt="Sponsor" />', // The HTML to be rendered
188+
defaultStyle: true, // Use the default menu item styling
189+
},
190+
// highlight-end
191+
],
192+
};
193+
```
194+
195+
:::tip
196+
197+
The menu item is already wrapped in an `<li>` tag, so if your custom item is simple, such as a title, just supply a string as the value and use the `className` property to style it:
198+
199+
```js title="sidebars.js"
200+
module.exports = {
201+
myHtmlSidebar: [
202+
{
203+
type: 'html',
204+
value: 'Core concepts',
205+
className: 'sidebar-title',
206+
},
207+
],
208+
};
209+
```
210+
211+
:::
212+
163213
### Category links {#category-link}
164214

165215
With category links, clicking on a category can navigate you to another page.

0 commit comments

Comments
 (0)