Skip to content
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

feat(theme): enable multi level sidebar nesting (#1360) #1835

Merged
merged 4 commits into from
Jan 24, 2023
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
8 changes: 6 additions & 2 deletions __tests__/e2e/multi-sidebar/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ describe('test multi sidebar sort root', () => {
})

test('using / sidebar', async () => {
const sidebarLocator = page.locator('.VPSidebarGroup .title-text')
const sidebarLocator = page.locator(
'.VPSidebarItem.level-0 > .item > .link > .text'
)

const sidebarContent = await sidebarLocator.allTextContents()
expect(sidebarContent).toEqual([
Expand All @@ -22,7 +24,9 @@ describe('test multi sidebar sort order', () => {
})

test('using /multi-sidebar/ sidebar', async () => {
const sidebarLocator = page.locator('.VPSidebarGroup .title-text')
const sidebarLocator = page.locator(
'.VPSidebarItem.level-0 > .item > .link > .text'
)

const sidebarContent = await sidebarLocator.allTextContents()
expect(sidebarContent).toEqual(['Multi Sidebar'])
Expand Down
199 changes: 117 additions & 82 deletions __tests__/unit/client/theme-default/support/sidebar.test.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,131 @@
import { getSidebar } from 'client/theme-default/support/sidebar'
import { getSidebar, hasActiveLink } from 'client/theme-default/support/sidebar'

describe('client/theme-default/support/sidebar', () => {
const root = [
{
text: 'A',
collapsible: true,
items: [
{
text: 'A',
link: ''
}
]
},
{
text: 'B',
items: [
{
text: 'B',
link: ''
}
]
}
]
const another = [
{
text: 'C',
items: [
describe('getSidebar', () => {
const root = [
{
text: 'A',
collapsible: true,
items: [{ text: 'A', link: '' }]
},
{
text: 'B',
items: [{ text: 'B', link: '' }]
}
]

const another = [
{
text: 'C',
items: [{ text: 'C', link: '' }]
}
]

describe('normal sidebar sort', () => {
const normalSidebar = {
'/': root,
'/multi-sidebar/': another
}

test('gets `/` sidebar', () => {
expect(getSidebar(normalSidebar, '/')).toBe(root)
})

test('gets `/multi-sidebar/` sidebar', () => {
expect(getSidebar(normalSidebar, '/multi-sidebar/')).toBe(another)
})

test('gets `/` sidebar again', () => {
expect(getSidebar(normalSidebar, '/some-entry.html')).toBe(root)
})
})

describe('reversed sidebar sort', () => {
const reversedSidebar = {
'/multi-sidebar/': another,
'/': root
}

test('gets `/` sidebar', () => {
expect(getSidebar(reversedSidebar, '/')).toBe(root)
})

test('gets `/multi-sidebar/` sidebar', () => {
expect(getSidebar(reversedSidebar, '/multi-sidebar/')).toBe(another)
})

test('gets `/` sidebar again', () => {
expect(getSidebar(reversedSidebar, '/some-entry.html')).toBe(root)
})
})

describe('nested sidebar sort', () => {
const nested = [
{
text: 'C',
link: ''
text: 'D',
items: [{ text: 'D', link: '' }]
}
]
}
]
describe('normal sidebar sort', () => {
const normalSidebar = {
'/': root,
'/multi-sidebar/': another
}
test('gets / sidebar', () => {
expect(getSidebar(normalSidebar, '/')).toBe(root)
})
test('gets /multi-sidebar/ sidebar', () => {
expect(getSidebar(normalSidebar, '/multi-sidebar/')).toBe(another)
})
test('gets / sidebar again', () => {
expect(getSidebar(normalSidebar, '/some-entry.html')).toBe(root)
})
})
describe('reversed sidebar sort', () => {
const reversedSidebar = {
'/multi-sidebar/': another,
'/': root
}
test('gets / sidebar', () => {
expect(getSidebar(reversedSidebar, '/')).toBe(root)
})
test('gets /multi-sidebar/ sidebar', () => {
expect(getSidebar(reversedSidebar, '/multi-sidebar/')).toBe(another)
})
test('gets / sidebar again', () => {
expect(getSidebar(reversedSidebar, '/some-entry.html')).toBe(root)

const nestedSidebar = {
'/': root,
'/multi-sidebar/': another,
'/multi-sidebar/nested/': nested
}

test('gets `/` sidebar', () => {
expect(getSidebar(nestedSidebar, '/')).toBe(root)
})

test('gets `/multi-sidebar/` sidebar', () => {
expect(getSidebar(nestedSidebar, '/multi-sidebar/')).toBe(another)
})

test('gets `/multi-sidebar/nested/` sidebar', () => {
expect(getSidebar(nestedSidebar, '/multi-sidebar/nested/')).toBe(nested)
})

test('gets `/` sidebar again', () => {
expect(getSidebar(nestedSidebar, '/some-entry.html')).toBe(root)
})
})
})
describe('nested sidebar sort', () => {
const nested = [
{
text: 'D',

describe('hasActiveLink', () => {
test('checks `SidebarItem`', () => {
const item = {
text: 'Item 001',
items: [
{
text: 'D',
link: ''
}
{ text: 'Item 001', link: '/active-1' },
{ text: 'Item 002', link: '/active-2' }
]
}
]
const nestedSidebar = {
'/': root,
'/multi-sidebar/': another,
'/multi-sidebar/nested/': nested
}
test('gets / sidebar', () => {
expect(getSidebar(nestedSidebar, '/')).toBe(root)
})
test('gets /multi-sidebar/ sidebar', () => {
expect(getSidebar(nestedSidebar, '/multi-sidebar/')).toBe(another)
})
test('gets /multi-sidebar/nested/ sidebar', () => {
expect(getSidebar(nestedSidebar, '/multi-sidebar/nested/')).toBe(nested)

expect(hasActiveLink('active-1', item)).toBe(true)
expect(hasActiveLink('inactive', item)).toBe(false)
})
test('gets / sidebar again', () => {
expect(getSidebar(nestedSidebar, '/some-entry.html')).toBe(root)

test('checks `SidebarItem[]`', () => {
const item = [
{
text: 'Item 001',
items: [
{ text: 'Item 001', link: '/active-1' },
{ text: 'Item 002', link: '/active-2' }
]
},
{
text: 'Item 002',
items: [
{ text: 'Item 003', link: '/active-3' },
{ text: 'Item 004', link: '/active-4' }
]
}
]

expect(hasActiveLink('active-1', item)).toBe(true)
expect(hasActiveLink('active-3', item)).toBe(true)
expect(hasActiveLink('inactive', item)).toBe(false)
})
})
})
41 changes: 30 additions & 11 deletions docs/config/theme-configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,41 @@ export default {
```

```ts
type Sidebar = SidebarGroup[] | SidebarMulti
export type Sidebar = SidebarItem[] | SidebarMulti

interface SidebarMulti {
[path: string]: SidebarGroup[]
export interface SidebarMulti {
[path: string]: SidebarItem[]
}

interface SidebarGroup {
text: string
items: SidebarItem[]
export type SidebarItem = {
/**
* The text label of the item.
*/
text?: string

/**
* The link of the item.
*/
link?: string

/**
* The children of the item.
*/
items?: SidebarItem[]

/**
* If `true`, toggle button is shown.
*
* @default false
*/
collapsible?: boolean
collapsed?: boolean
}

interface SidebarItem {
text: string
link: string
/**
* If `true`, collapsible group is collapsed by default.
*
* @default false
*/
collapsed?: boolean
}
```

Expand Down
53 changes: 39 additions & 14 deletions docs/guide/theme-sidebar.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sidebar

The sidebar is the main navigation block for your documentation. You can configure the sidebar menu in `themeConfig.sidebar`.
The sidebar is the main navigation block for your documentation. You can configure the sidebar menu in [`themeConfig.sidebar`](/config/theme-configs#sidebar).

```js
export default {
Expand All @@ -21,7 +21,7 @@ export default {

## The Basics

The simplest form of the sidebar menu is passing in a single array of links. The first level item defines the "section" for the sidebar. It should contain `text`, which is the title of the section, and `items` which are the actual navigation links.
The simplest form of the sidebar menu is passing in a single array of links. The first level item defines the "section" for the sidebar. It should contain `text`, which is the title of the section, and `items` which are the actual navigation links.

```js
export default {
Expand Down Expand Up @@ -66,6 +66,33 @@ export default {
}
```

You may further nest the sidebar items up to 6 level deep counting up from the root level. Note that deeper than 6 level of nested items gets ignored and will not be displayed on the sidebar.

```js
export default {
themeConfig: {
sidebar: [
{
text: 'Level 1',
items: [
{
text: 'Level 2',
items: [
{
text: 'Level 3',
items: [
...
]
}
]
}
]
}
]
}
}
```

## Multiple Sidebars

You may show different sidebar depending on the page path. For example, as shown on this site, you might want to create a separate sections of content in your documentation like "Guide" page and "Config" page.
Expand All @@ -90,30 +117,28 @@ Then, update your configuration to define your sidebar for each section. This ti
export default {
themeConfig: {
sidebar: {
// This sidebar gets displayed when user is
// under `guide` directory.
// This sidebar gets displayed when a user
// is on `guide` directory.
'/guide/': [
{
text: 'Guide',
items: [
// This shows `/guide/index.md` page.
{ text: 'Index', link: '/guide/' }, // /guide/index.md
{ text: 'One', link: '/guide/one' }, // /guide/one.md
{ text: 'Two', link: '/guide/two' } // /guide/two.md
{ text: 'Index', link: '/guide/' },
{ text: 'One', link: '/guide/one' },
{ text: 'Two', link: '/guide/two' }
]
}
],

// This sidebar gets displayed when user is
// under `config` directory.
// This sidebar gets displayed when a user
// is on `config` directory.
'/config/': [
{
text: 'Config',
items: [
// This shows `/config/index.md` page.
{ text: 'Index', link: '/config/' }, // /config/index.md
{ text: 'Three', link: '/config/three' }, // /config/three.md
{ text: 'Four', link: '/config/four' } // /config/four.md
{ text: 'Index', link: '/config/' },
{ text: 'Three', link: '/config/three' },
{ text: 'Four', link: '/config/four' }
]
}
]
Expand Down
Loading