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:
Jody Heavener 2022-02-02 13:38:35 -04:00 committed by GitHub
parent 65ba551f5b
commit 6ec0db4722
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 172 additions and 3 deletions

View file

@ -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\\",
\\"value\\" [1]: -- missing --
}

[1] \\"value\\" is required"
`);
});
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

View file

@ -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;

View file

@ -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},
{ {

View file

@ -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,

View file

@ -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);
}
} }

View file

@ -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',
}), }),

View file

@ -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)}`,

View file

@ -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'],
}; };

View file

@ -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.