feat(v2): expanded sidebar categories by default (#2682)

* feat: update sidebar categ to  take collapsed prop

* feat: add extra sidebars collapsed test

* fix: only mutate item.collapsed if necessary

* feat: update docs for SidebarItemCategory

* fix: update snapshots

* fix: update json to match new sidebar schema

* fix: update last snapshot

* refactor: check if item should be expanded

* docs: update sidebar categories section

* refactor: use new collpased on docusaurus

* feat: only highlight category for active page

* fix: check for window

* refactor: use ExecutionEnviornment

* refactor: make isCategoryOfActivePage pure

* fix: rename docs to docs-introduction in sidebars

* Update docs.md

* misc: remove setting for every sidebar

Co-authored-by: Yangshun Tay <tay.yang.shun@gmail.com>
This commit is contained in:
Joe Previte 2020-05-27 22:17:19 -07:00 committed by GitHub
parent d8ebe8b2e4
commit 07b9e9cd62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 273 additions and 26 deletions

View file

@ -9,25 +9,30 @@ module.exports = {
docs: [ docs: [
{ {
type: 'category', type: 'category',
collapsed: true,
label: 'level 1', label: 'level 1',
items: [ items: [
'a', 'a',
{ {
type: 'category', type: 'category',
collapsed: true,
label: 'level 2', label: 'level 2',
items: [ items: [
{ {
type: 'category', type: 'category',
collapsed: true,
label: 'level 3', label: 'level 3',
items: [ items: [
'c', 'c',
{ {
type: 'category', type: 'category',
collapsed: true,
label: 'level 4', label: 'level 4',
items: [ items: [
'd', 'd',
{ {
type: 'category', type: 'category',
collapsed: true,
label: 'deeper more more', label: 'deeper more more',
items: ['e'], items: ['e'],
}, },

View file

@ -0,0 +1,20 @@
{
"docs": [
{
"type": "category",
"label": "Introduction",
"items": [
"doc1"
],
"collapsed": false
},
{
"type": "category",
"label": "Powering MDX",
"items": [
"doc2"
],
"collapsed": false
}
]
}

View file

@ -0,0 +1,21 @@
{
"docs": {
"Test": [
{
"type": "category",
"label": "Introduction",
"items": ["doc1"],
"collapsed": false
}
],
"Reference": [
{
"type": "category",
"label": "Powering MDX",
"items": ["doc2"],
"collapsed": false
}
]
}
}

View file

@ -4,6 +4,7 @@ exports[`simple website content 1`] = `
Object { Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"items": Array [ "items": Array [
@ -36,6 +37,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/hello", "href": "/docs/hello",
@ -236,6 +238,7 @@ exports[`versioned website content: all sidebars 1`] = `
Object { Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/next/foo/bar", "href": "/docs/next/foo/bar",
@ -247,6 +250,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/next/hello", "href": "/docs/next/hello",
@ -260,6 +264,7 @@ Object {
], ],
"version-1.0.0/docs": Array [ "version-1.0.0/docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/1.0.0/foo/bar", "href": "/docs/1.0.0/foo/bar",
@ -276,6 +281,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/1.0.0/hello", "href": "/docs/1.0.0/hello",
@ -289,6 +295,7 @@ Object {
], ],
"version-1.0.1/docs": Array [ "version-1.0.1/docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/foo/bar", "href": "/docs/foo/bar",
@ -300,6 +307,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/hello", "href": "/docs/hello",
@ -319,6 +327,7 @@ Object {
"docsSidebars": Object { "docsSidebars": Object {
"version-1.0.0/docs": Array [ "version-1.0.0/docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/1.0.0/foo/bar", "href": "/docs/1.0.0/foo/bar",
@ -335,6 +344,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/1.0.0/hello", "href": "/docs/1.0.0/hello",
@ -361,6 +371,7 @@ Object {
"docsSidebars": Object { "docsSidebars": Object {
"version-1.0.1/docs": Array [ "version-1.0.1/docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/foo/bar", "href": "/docs/foo/bar",
@ -372,6 +383,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/hello", "href": "/docs/hello",
@ -397,6 +409,7 @@ Object {
"docsSidebars": Object { "docsSidebars": Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/next/foo/bar", "href": "/docs/next/foo/bar",
@ -408,6 +421,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "/docs/next/hello", "href": "/docs/next/hello",

View file

@ -4,6 +4,7 @@ exports[`loadSidebars sidebars link 1`] = `
Object { Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"href": "https://github.com", "href": "https://github.com",
@ -18,30 +19,107 @@ Object {
} }
`; `;
exports[`loadSidebars sidebars with category.collapsed property 1`] = `
Object {
"docs": Array [
Object {
"collapsed": true,
"items": Array [
Object {
"collapsed": false,
"items": Array [
Object {
"id": "doc1",
"type": "doc",
},
],
"label": "Introduction",
"type": "category",
},
],
"label": "Test",
"type": "category",
},
Object {
"collapsed": true,
"items": Array [
Object {
"collapsed": false,
"items": Array [
Object {
"id": "doc2",
"type": "doc",
},
],
"label": "Powering MDX",
"type": "category",
},
],
"label": "Reference",
"type": "category",
},
],
}
`;
exports[`loadSidebars sidebars with category.collapsed property at first level 1`] = `
Object {
"docs": Array [
Object {
"collapsed": false,
"items": Array [
Object {
"id": "doc1",
"type": "doc",
},
],
"label": "Introduction",
"type": "category",
},
Object {
"collapsed": false,
"items": Array [
Object {
"id": "doc2",
"type": "doc",
},
],
"label": "Powering MDX",
"type": "category",
},
],
}
`;
exports[`loadSidebars sidebars with deep level of category 1`] = ` exports[`loadSidebars sidebars with deep level of category 1`] = `
Object { Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "a", "id": "a",
"type": "doc", "type": "doc",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "c", "id": "c",
"type": "doc", "type": "doc",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "d", "id": "d",
"type": "doc", "type": "doc",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "e", "id": "e",
@ -100,6 +178,7 @@ exports[`loadSidebars sidebars with known sidebar item type 1`] = `
Object { Object {
"docs": Array [ "docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "foo/bar", "id": "foo/bar",
@ -123,6 +202,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "hello", "id": "hello",

View file

@ -4,6 +4,7 @@ exports[`docsVersion first time versioning 1`] = `
Object { Object {
"version-1.0.0/docs": Array [ "version-1.0.0/docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"items": Array [ "items": Array [
@ -33,6 +34,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-1.0.0/hello", "id": "version-1.0.0/hello",
@ -50,6 +52,7 @@ exports[`docsVersion not the first time versioning 1`] = `
Object { Object {
"version-2.0.0/docs": Array [ "version-2.0.0/docs": Array [
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-2.0.0/foo/bar", "id": "version-2.0.0/foo/bar",
@ -60,6 +63,7 @@ Object {
"type": "category", "type": "category",
}, },
Object { Object {
"collapsed": true,
"items": Array [ "items": Array [
Object { Object {
"id": "version-2.0.0/hello", "id": "version-2.0.0/hello",

View file

@ -126,4 +126,19 @@ describe('loadSidebars', () => {
const result = loadSidebars(null); const result = loadSidebars(null);
expect(result).toEqual({}); expect(result).toEqual({});
}); });
test('sidebars with category.collapsed property', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-collapsed.json');
const result = loadSidebars([sidebarPath]);
expect(result).toMatchSnapshot();
});
test('sidebars with category.collapsed property at first level', async () => {
const sidebarPath = path.join(
fixtureDir,
'sidebars-collapsed-first-level.json',
);
const result = loadSidebars([sidebarPath]);
expect(result).toMatchSnapshot();
});
}); });

View file

@ -33,6 +33,7 @@ function normalizeCategoryShorthand(
): SidebarItemCategoryRaw[] { ): SidebarItemCategoryRaw[] {
return Object.entries(sidebar).map(([label, items]) => ({ return Object.entries(sidebar).map(([label, items]) => ({
type: 'category', type: 'category',
collapsed: true,
label, label,
items, items,
})); }));
@ -56,7 +57,7 @@ function assertItem(item: Object, keys: string[]): void {
} }
function assertIsCategory(item: any): asserts item is SidebarItemCategoryRaw { function assertIsCategory(item: any): asserts item is SidebarItemCategoryRaw {
assertItem(item, ['items', 'label']); assertItem(item, ['items', 'label', 'collapsed']);
if (typeof item.label !== 'string') { if (typeof item.label !== 'string') {
throw new Error( throw new Error(
`Error loading ${JSON.stringify(item)}. "label" must be a string.`, `Error loading ${JSON.stringify(item)}. "label" must be a string.`,
@ -67,6 +68,12 @@ function assertIsCategory(item: any): asserts item is SidebarItemCategoryRaw {
`Error loading ${JSON.stringify(item)}. "items" must be an array.`, `Error loading ${JSON.stringify(item)}. "items" must be an array.`,
); );
} }
// "collapsed" is an optional property
if (item.hasOwnProperty('collapsed') && typeof item.collapsed !== 'boolean') {
throw new Error(
`Error loading ${JSON.stringify(item)}. "collapsed" must be a boolean.`,
);
}
} }
function assertIsDoc(item: any): asserts item is SidebarItemDoc { function assertIsDoc(item: any): asserts item is SidebarItemDoc {

View file

@ -42,12 +42,14 @@ export interface SidebarItemCategory {
type: 'category'; type: 'category';
label: string; label: string;
items: SidebarItem[]; items: SidebarItem[];
collapsed?: boolean;
} }
export interface SidebarItemCategoryRaw { export interface SidebarItemCategoryRaw {
type: 'category'; type: 'category';
label: string; label: string;
items: SidebarItemRaw[]; items: SidebarItemRaw[];
collapsed?: boolean;
} }
export type SidebarItem = export type SidebarItem =
@ -83,6 +85,7 @@ export interface DocsSidebarItemCategory {
type: 'category'; type: 'category';
label: string; label: string;
items: DocsSidebarItem[]; items: DocsSidebarItem[];
collapsed?: boolean;
} }
export type DocsSidebarItem = SidebarItemLink | DocsSidebarItemCategory; export type DocsSidebarItem = SidebarItemLink | DocsSidebarItemCategory;

View file

@ -6,6 +6,7 @@
*/ */
import React, {useState, useCallback} from 'react'; import React, {useState, useCallback} from 'react';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
import classnames from 'classnames'; import classnames from 'classnames';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useAnnouncementBarContext from '@theme/hooks/useAnnouncementBarContext'; import useAnnouncementBarContext from '@theme/hooks/useAnnouncementBarContext';
@ -44,6 +45,28 @@ function DocSidebarItem({
setCollapsed((state) => !state); setCollapsed((state) => !state);
}); });
// Make sure we have access to the window
const activePageRelativeUrl = ExecutionEnvironment.canUseDOM
? window.location.pathname + window.location.search
: null;
// We need to know if the category item
// is the parent of the active page
// If it is, this returns true and make sure to highlight this category
const isCategoryOfActivePage = (_items, _activePageRelativeUrl) => {
// Make sure we have items
if (typeof _items !== 'undefined') {
return _items.some((categoryItem) => {
// Grab the category item's href
const childHref = categoryItem.href;
// Compare it to the current active page
return _activePageRelativeUrl === childHref;
});
}
return false;
};
switch (type) { switch (type) {
case 'category': case 'category':
return ( return (
@ -56,7 +79,10 @@ function DocSidebarItem({
<a <a
className={classnames('menu__link', { className={classnames('menu__link', {
'menu__link--sublist': collapsible, 'menu__link--sublist': collapsible,
'menu__link--active': collapsible && !item.collapsed, 'menu__link--active':
collapsible &&
!item.collapsed &&
isCategoryOfActivePage(items, activePageRelativeUrl),
})} })}
href="#!" href="#!"
onClick={collapsible ? handleItemClick : undefined} onClick={collapsible ? handleItemClick : undefined}
@ -116,8 +142,18 @@ function mutateSidebarCollapsingState(item, path) {
items items
.map((childItem) => mutateSidebarCollapsingState(childItem, path)) .map((childItem) => mutateSidebarCollapsingState(childItem, path))
.filter((val) => val).length > 0; .filter((val) => val).length > 0;
// Check if the user wants the category to be expanded by default
const shouldExpand = item.collapsed === false;
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
item.collapsed = !anyChildItemsActive; item.collapsed = !anyChildItemsActive;
if (shouldExpand) {
// eslint-disable-next-line no-param-reassign
item.collapsed = false;
}
return anyChildItemsActive; return anyChildItemsActive;
} }

View file

@ -243,6 +243,7 @@ type SidebarItemCategory = {
type: 'category'; type: 'category';
label: string; // Sidebar label text. label: string; // Sidebar label text.
items: SidebarItem[]; // Array of sidebar items. items: SidebarItem[]; // Array of sidebar items.
collapsed: boolean; // Set the category to be collapsed or open by default
}; };
``` ```
@ -292,6 +293,26 @@ module.exports = {
}; };
``` ```
#### Expanded categories by default
For docs that have collapsible categories, you may want more fine-grain control over certain categories. If you want specific categories to be always expanded, you can set `collapsed` to `false`:
```js title="sidebars.js"
module.exports = {
docs: {
Guides: [
'creating-pages',
{
type: 'category',
label: 'Docs',
collapsed: false,
items: ['markdown-features', 'sidebar', 'versioning'],
},
],
},
};
```
## Docs-only mode ## Docs-only mode
If you just want the documentation feature, you can enable "docs-only mode". If you just want the documentation feature, you can enable "docs-only mode".

View file

@ -6,30 +6,51 @@
*/ */
module.exports = { module.exports = {
docs: { docs: [
Docusaurus: ['introduction', 'design-principles', 'contributing'], {
'Getting Started': ['installation', 'configuration'], type: 'category',
Guides: [ label: 'Docusaurus',
'creating-pages', items: ['introduction', 'design-principles', 'contributing'],
'styling-layout', },
'static-assets', {
{ type: 'category',
Docs: ['docs-introduction', 'markdown-features', 'versioning'], label: 'Getting Started',
}, collapsed: false,
'blog', items: ['installation', 'configuration'],
'search', },
'deployment', {
'migrating-from-v1-to-v2', type: 'category',
], label: 'Guides',
'Advanced Guides': ['using-plugins', 'using-themes', 'presets'], items: [
'API Reference': [ 'creating-pages',
'cli', 'styling-layout',
'docusaurus-core', 'static-assets',
'docusaurus.config.js', {
'lifecycle-apis', Docs: ['docs-introduction', 'markdown-features', 'versioning'],
'theme-classic', },
], 'blog',
}, 'search',
'deployment',
'migrating-from-v1-to-v2',
],
},
{
type: 'category',
label: 'Advanced Guides',
items: ['using-plugins', 'using-themes', 'presets'],
},
{
type: 'category',
label: 'API Reference',
items: [
'cli',
'docusaurus-core',
'docusaurus.config.js',
'lifecycle-apis',
'theme-classic',
],
},
],
community: [ community: [
'support', 'support',
'team', 'team',