mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-03 12:17:20 +02:00
feat(content-docs): sidebar item type "html" for rendering pure markup (#6519)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
65ba551f5b
commit
6ec0db4722
9 changed files with 172 additions and 3 deletions
|
@ -47,6 +47,42 @@ describe('validateSidebars', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('html item type', () => {
|
||||||
|
test('requires a value', () => {
|
||||||
|
const sidebars: SidebarsConfig = {
|
||||||
|
sidebar1: [
|
||||||
|
{
|
||||||
|
// @ts-expect-error - test missing value
|
||||||
|
type: 'html',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(() => validateSidebars(sidebars))
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"{
|
||||||
|
\\"type\\": \\"html\\",
|
||||||
|
[41m\\"value\\"[0m[31m [1]: -- missing --[0m
|
||||||
|
}
|
||||||
|
[31m
|
||||||
|
[1] \\"value\\" is required[0m"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('accepts valid values', () => {
|
||||||
|
const sidebars: SidebarsConfig = {
|
||||||
|
sidebar1: [
|
||||||
|
{
|
||||||
|
type: 'html',
|
||||||
|
value: '<p>Hello, World!</p>',
|
||||||
|
defaultStyle: true,
|
||||||
|
className: 'foo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
validateSidebars(sidebars);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('validateCategoryMetadataFile', () => {
|
describe('validateCategoryMetadataFile', () => {
|
||||||
// TODO add more tests
|
// TODO add more tests
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,12 @@ export type SidebarItemDoc = SidebarItemBase & {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SidebarItemHtml = SidebarItemBase & {
|
||||||
|
type: 'html';
|
||||||
|
value: string;
|
||||||
|
defaultStyle?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type SidebarItemLink = SidebarItemBase & {
|
export type SidebarItemLink = SidebarItemBase & {
|
||||||
type: 'link';
|
type: 'link';
|
||||||
href: string;
|
href: string;
|
||||||
|
@ -87,6 +93,7 @@ export type SidebarCategoriesShorthand = {
|
||||||
|
|
||||||
export type SidebarItemConfig =
|
export type SidebarItemConfig =
|
||||||
| SidebarItemDoc
|
| SidebarItemDoc
|
||||||
|
| SidebarItemHtml
|
||||||
| SidebarItemLink
|
| SidebarItemLink
|
||||||
| SidebarItemAutogenerated
|
| SidebarItemAutogenerated
|
||||||
| SidebarItemCategoryConfig
|
| SidebarItemCategoryConfig
|
||||||
|
@ -108,6 +115,7 @@ export type NormalizedSidebarItemCategory = Expand<
|
||||||
|
|
||||||
export type NormalizedSidebarItem =
|
export type NormalizedSidebarItem =
|
||||||
| SidebarItemDoc
|
| SidebarItemDoc
|
||||||
|
| SidebarItemHtml
|
||||||
| SidebarItemLink
|
| SidebarItemLink
|
||||||
| NormalizedSidebarItemCategory
|
| NormalizedSidebarItemCategory
|
||||||
| SidebarItemAutogenerated;
|
| SidebarItemAutogenerated;
|
||||||
|
@ -131,6 +139,7 @@ export type SidebarItemCategoryWithGeneratedIndex =
|
||||||
|
|
||||||
export type SidebarItem =
|
export type SidebarItem =
|
||||||
| SidebarItemDoc
|
| SidebarItemDoc
|
||||||
|
| SidebarItemHtml
|
||||||
| SidebarItemLink
|
| SidebarItemLink
|
||||||
| SidebarItemCategory;
|
| SidebarItemCategory;
|
||||||
|
|
||||||
|
@ -158,7 +167,12 @@ export type PropSidebarItemLink = SidebarItemLink & {
|
||||||
docId?: string;
|
docId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory;
|
export type PropSidebarItemHtml = SidebarItemHtml;
|
||||||
|
|
||||||
|
export type PropSidebarItem =
|
||||||
|
| PropSidebarItemLink
|
||||||
|
| PropSidebarItemCategory
|
||||||
|
| PropSidebarItemHtml;
|
||||||
export type PropSidebar = PropSidebarItem[];
|
export type PropSidebar = PropSidebarItem[];
|
||||||
export type PropSidebars = {
|
export type PropSidebars = {
|
||||||
[sidebarId: string]: PropSidebar;
|
[sidebarId: string]: PropSidebar;
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
||||||
SidebarItemBase,
|
SidebarItemBase,
|
||||||
SidebarItemAutogenerated,
|
SidebarItemAutogenerated,
|
||||||
SidebarItemDoc,
|
SidebarItemDoc,
|
||||||
|
SidebarItemHtml,
|
||||||
SidebarItemLink,
|
SidebarItemLink,
|
||||||
SidebarItemCategoryConfig,
|
SidebarItemCategoryConfig,
|
||||||
SidebarItemCategoryLink,
|
SidebarItemCategoryLink,
|
||||||
|
@ -48,6 +49,12 @@ const sidebarItemDocSchema = sidebarItemBaseSchema.append<SidebarItemDoc>({
|
||||||
label: Joi.string(),
|
label: Joi.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sidebarItemHtmlSchema = sidebarItemBaseSchema.append<SidebarItemHtml>({
|
||||||
|
type: 'html',
|
||||||
|
value: Joi.string().required(),
|
||||||
|
defaultStyle: Joi.boolean().default(false),
|
||||||
|
});
|
||||||
|
|
||||||
const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
|
const sidebarItemLinkSchema = sidebarItemBaseSchema.append<SidebarItemLink>({
|
||||||
type: 'link',
|
type: 'link',
|
||||||
href: URISchema.required(),
|
href: URISchema.required(),
|
||||||
|
@ -117,6 +124,7 @@ const sidebarItemSchema: Joi.Schema<SidebarItemConfig> = Joi.object()
|
||||||
is: Joi.string().valid('doc', 'ref').required(),
|
is: Joi.string().valid('doc', 'ref').required(),
|
||||||
then: sidebarItemDocSchema,
|
then: sidebarItemDocSchema,
|
||||||
},
|
},
|
||||||
|
{is: 'html', then: sidebarItemHtmlSchema},
|
||||||
{is: 'autogenerated', then: sidebarItemAutogeneratedSchema},
|
{is: 'autogenerated', then: sidebarItemAutogeneratedSchema},
|
||||||
{is: 'category', then: sidebarItemCategorySchema},
|
{is: 'category', then: sidebarItemCategorySchema},
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,6 +32,7 @@ import type {
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
import useIsBrowser from '@docusaurus/useIsBrowser';
|
||||||
|
import type {SidebarItemHtml} from '@docusaurus/plugin-content-docs/src/sidebars/types';
|
||||||
|
|
||||||
export default function DocSidebarItem({
|
export default function DocSidebarItem({
|
||||||
item,
|
item,
|
||||||
|
@ -40,6 +41,8 @@ export default function DocSidebarItem({
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'category':
|
case 'category':
|
||||||
return <DocSidebarItemCategory item={item} {...props} />;
|
return <DocSidebarItemCategory item={item} {...props} />;
|
||||||
|
case 'html':
|
||||||
|
return <DocSidebarItemHtml item={item} {...props} />;
|
||||||
case 'link':
|
case 'link':
|
||||||
default:
|
default:
|
||||||
return <DocSidebarItemLink item={item} {...props} />;
|
return <DocSidebarItemLink item={item} {...props} />;
|
||||||
|
@ -216,6 +219,29 @@ function DocSidebarItemCategory({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DocSidebarItemHtml({
|
||||||
|
item,
|
||||||
|
level,
|
||||||
|
index,
|
||||||
|
}: Props & {item: SidebarItemHtml}) {
|
||||||
|
const {value, defaultStyle, className} = item;
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.docs.docSidebarItemLink,
|
||||||
|
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
||||||
|
defaultStyle && `${styles.menuHtmlItem} menu__list-item`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
key={index}
|
||||||
|
// eslint-disable-next-line react/no-danger
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: value,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function DocSidebarItemLink({
|
function DocSidebarItemLink({
|
||||||
item,
|
item,
|
||||||
onItemClick,
|
onItemClick,
|
||||||
|
|
|
@ -17,4 +17,9 @@
|
||||||
.menuLinkText.hasHref {
|
.menuLinkText.hasHref {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menuHtmlItem {
|
||||||
|
padding: var(--ifm-menu-link-padding-vertical)
|
||||||
|
var(--ifm-menu-link-padding-horizontal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,9 +226,11 @@ describe('docsUtils', () => {
|
||||||
testCategory({
|
testCategory({
|
||||||
href: undefined,
|
href: undefined,
|
||||||
items: [
|
items: [
|
||||||
|
{type: 'html', value: '<p>test1</p>'},
|
||||||
testCategory({
|
testCategory({
|
||||||
href: undefined,
|
href: undefined,
|
||||||
items: [
|
items: [
|
||||||
|
{type: 'html', value: '<p>test2</p>'},
|
||||||
testCategory({
|
testCategory({
|
||||||
href: '/itemPath',
|
href: '/itemPath',
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -123,12 +123,13 @@ export function findFirstCategoryLink(
|
||||||
for (const subItem of item.items) {
|
for (const subItem of item.items) {
|
||||||
if (subItem.type === 'link') {
|
if (subItem.type === 'link') {
|
||||||
return subItem.href;
|
return subItem.href;
|
||||||
}
|
} else if (subItem.type === 'category') {
|
||||||
if (subItem.type === 'category') {
|
|
||||||
const categoryLink = findFirstCategoryLink(subItem);
|
const categoryLink = findFirstCategoryLink(subItem);
|
||||||
if (categoryLink) {
|
if (categoryLink) {
|
||||||
return categoryLink;
|
return categoryLink;
|
||||||
}
|
}
|
||||||
|
} else if (subItem.type === 'html') {
|
||||||
|
// skip
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected category item type for ${JSON.stringify(subItem)}`,
|
`Unexpected category item type for ${JSON.stringify(subItem)}`,
|
||||||
|
|
|
@ -71,6 +71,33 @@ const sidebars = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'HTML items tests',
|
||||||
|
items: [
|
||||||
|
// title
|
||||||
|
{
|
||||||
|
type: 'html',
|
||||||
|
value: 'Some Text',
|
||||||
|
defaultStyle: true,
|
||||||
|
},
|
||||||
|
// Divider
|
||||||
|
{
|
||||||
|
type: 'html',
|
||||||
|
value:
|
||||||
|
'<span style="border-top: 1px solid var(--ifm-color-gray-500); display: block;margin: 0.5rem 0 0.25rem 1rem;" />',
|
||||||
|
},
|
||||||
|
// Image
|
||||||
|
{
|
||||||
|
type: 'html',
|
||||||
|
defaultStyle: true,
|
||||||
|
value: `
|
||||||
|
<span style="font-size: 0.5rem; color: lightgrey;">Powered by</span>
|
||||||
|
<img style="width: 100px; height: 100px; display: block;" src="/img/docusaurus.png" alt="Docusaurus Logo" />
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
anotherSidebar: ['dummy'],
|
anotherSidebar: ['dummy'],
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ We have introduced three types of item types in the example in the previous sect
|
||||||
- **[Link](#sidebar-item-link)**: link to any internal or external page
|
- **[Link](#sidebar-item-link)**: link to any internal or external page
|
||||||
- **[Category](#sidebar-item-category)**: creates a dropdown of sidebar items
|
- **[Category](#sidebar-item-category)**: creates a dropdown of sidebar items
|
||||||
- **[Autogenerated](autogenerated.md)**: generate a sidebar slice automatically
|
- **[Autogenerated](autogenerated.md)**: generate a sidebar slice automatically
|
||||||
|
- **[HTML](#sidebar-item-html)**: renders pure HTML in the item's position
|
||||||
- **[\*Ref](multiple-sidebars.md#sidebar-item-ref)**: link to a doc page, without making the item take part in navigation generation
|
- **[\*Ref](multiple-sidebars.md#sidebar-item-ref)**: link to a doc page, without making the item take part in navigation generation
|
||||||
|
|
||||||
## Doc: link to a doc {#sidebar-item-doc}
|
## Doc: link to a doc {#sidebar-item-doc}
|
||||||
|
@ -160,6 +161,55 @@ module.exports = {
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## HTML: render custom markup {#sidebar-item-html}
|
||||||
|
|
||||||
|
Use the `html` type to render custom HTML within the item's `<li>` tag.
|
||||||
|
|
||||||
|
This can be useful for inserting custom items such as dividers, section titles, ads, and images.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type SidebarItemHtml = {
|
||||||
|
type: 'html';
|
||||||
|
value: string;
|
||||||
|
defaultStyle?: boolean; // Use default menu item styles
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js title="sidebars.js"
|
||||||
|
module.exports = {
|
||||||
|
myHtmlSidebar: [
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
type: 'html',
|
||||||
|
value: '<img src="sponsor.png" alt="Sponsor" />', // The HTML to be rendered
|
||||||
|
defaultStyle: true, // Use the default menu item styling
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```js title="sidebars.js"
|
||||||
|
module.exports = {
|
||||||
|
myHtmlSidebar: [
|
||||||
|
{
|
||||||
|
type: 'html',
|
||||||
|
value: 'Core concepts',
|
||||||
|
className: 'sidebar-title',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### Category links {#category-link}
|
### Category links {#category-link}
|
||||||
|
|
||||||
With category links, clicking on a category can navigate you to another page.
|
With category links, clicking on a category can navigate you to another page.
|
||||||
|
|
Loading…
Add table
Reference in a new issue