mirror of
https://github.com/wavetermdev/docusaurus-og.git
synced 2026-08-05 13:46:35 -07:00
implement sidebar
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: 1
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 111
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: 2
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 222
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: 333
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 333
|
||||
@@ -12,7 +12,6 @@
|
||||
--ifm-navbar-link-color: rgb(var(--lsd-text-primary));
|
||||
--ifm-dropdown-link-color: rgb(var(--lsd-text-secondary));
|
||||
--ifm-dropdown-hover-background-color: rgb(var(--lsd-surface-secondary));
|
||||
--ifm-menu-color-active: rgb(var(--lsd-text-secondary));
|
||||
|
||||
/* Colors. */
|
||||
/*--ifm-dark-value: 10%;*/
|
||||
@@ -159,7 +158,8 @@
|
||||
|
||||
--ifm-menu-link-padding-horizontal: var(--ifm-spacing-horizontal);
|
||||
--ifm-menu-link-padding-vertical: 0.375rem;
|
||||
--ifm-menu-color-background-active: var(--ifm-color-gray-300);
|
||||
--ifm-menu-color: rgb(var(--lsd-text-primary), 0.6);
|
||||
--ifm-menu-color-background-active: transparent;
|
||||
|
||||
--ifm-menu-link-sublist-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24"><path fill="rgba(0,0,0,0.5)" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"></path></svg>');
|
||||
--ifm-menu-link-sublist-icon-filter: none;
|
||||
@@ -317,6 +317,12 @@ small {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.menu__link {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
:root {
|
||||
--ifm-global-spacing: 0.5rem;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { prefersReducedMotion, ThemeClassNames } from '@docusaurus/theme-common'
|
||||
import { useDocsSidebar } from '@docusaurus/theme-common/internal'
|
||||
import { useLocation } from '@docusaurus/router'
|
||||
import DocSidebar from '@theme/DocSidebar'
|
||||
import ExpandButton from '@theme/DocPage/Layout/Sidebar/ExpandButton'
|
||||
import styles from './styles.module.css'
|
||||
// Reset sidebar state when sidebar changes
|
||||
// Use React key to unmount/remount the children
|
||||
// See https://github.com/facebook/docusaurus/issues/3414
|
||||
function ResetOnSidebarChange({ children }) {
|
||||
const sidebar = useDocsSidebar()
|
||||
return (
|
||||
<React.Fragment key={sidebar?.name ?? 'noSidebar'}>
|
||||
{children}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
export default function DocPageLayoutSidebar({
|
||||
sidebar,
|
||||
hiddenSidebarContainer,
|
||||
setHiddenSidebarContainer,
|
||||
}) {
|
||||
const { pathname } = useLocation()
|
||||
const [hiddenSidebar, setHiddenSidebar] = useState(false)
|
||||
const toggleSidebar = useCallback(() => {
|
||||
if (hiddenSidebar) {
|
||||
setHiddenSidebar(false)
|
||||
}
|
||||
// onTransitionEnd won't fire when sidebar animation is disabled
|
||||
// fixes https://github.com/facebook/docusaurus/issues/8918
|
||||
if (!hiddenSidebar && prefersReducedMotion()) {
|
||||
setHiddenSidebar(true)
|
||||
}
|
||||
setHiddenSidebarContainer((value) => !value)
|
||||
}, [setHiddenSidebarContainer, hiddenSidebar])
|
||||
return (
|
||||
<aside
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docSidebarContainer,
|
||||
styles.docSidebarContainer,
|
||||
hiddenSidebarContainer && styles.docSidebarContainerHidden,
|
||||
)}
|
||||
onTransitionEnd={(e) => {
|
||||
if (
|
||||
!e.currentTarget.classList.contains(styles.docSidebarContainer ?? '')
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (hiddenSidebarContainer) {
|
||||
setHiddenSidebar(true)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ResetOnSidebarChange>
|
||||
<div
|
||||
className={clsx(
|
||||
styles.sidebarViewport,
|
||||
hiddenSidebar && styles.sidebarViewportHidden,
|
||||
)}
|
||||
>
|
||||
<DocSidebar
|
||||
sidebar={sidebar}
|
||||
path={pathname}
|
||||
onCollapse={toggleSidebar}
|
||||
isHidden={hiddenSidebar}
|
||||
/>
|
||||
{hiddenSidebar && <ExpandButton toggleSidebar={toggleSidebar} />}
|
||||
</div>
|
||||
</ResetOnSidebarChange>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
:root {
|
||||
--doc-sidebar-width: 300px;
|
||||
--doc-sidebar-hidden-width: 30px;
|
||||
}
|
||||
|
||||
.docSidebarContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.docSidebarContainer {
|
||||
display: block;
|
||||
width: var(--doc-sidebar-width);
|
||||
margin-top: calc(-1 * var(--ifm-navbar-height));
|
||||
will-change: width;
|
||||
transition: width var(--ifm-transition-fast) ease;
|
||||
clip-path: inset(0);
|
||||
}
|
||||
|
||||
.docSidebarContainerHidden {
|
||||
width: var(--doc-sidebar-hidden-width);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebarViewport {
|
||||
top: 0;
|
||||
position: sticky;
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { translate } from '@docusaurus/Translate'
|
||||
import IconArrow from '@theme/Icon/Arrow'
|
||||
import styles from './styles.module.css'
|
||||
|
||||
export default function CollapseButton({ onClick }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
title={translate({
|
||||
id: 'theme.docs.sidebar.collapseButtonTitle',
|
||||
message: 'Collapse sidebar',
|
||||
description: 'The title attribute for collapse button of doc sidebar',
|
||||
})}
|
||||
aria-label={translate({
|
||||
id: 'theme.docs.sidebar.collapseButtonAriaLabel',
|
||||
message: 'Collapse sidebar',
|
||||
description: 'The title attribute for collapse button of doc sidebar',
|
||||
})}
|
||||
className={clsx(
|
||||
'button button--secondary button--outline',
|
||||
styles.collapseSidebarButton,
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<IconArrow className={styles.collapseSidebarButtonIcon} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
:root {
|
||||
--docusaurus-collapse-button-bg: transparent;
|
||||
--docusaurus-collapse-button-bg-hover: rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
[data-theme='dark']:root {
|
||||
--docusaurus-collapse-button-bg: rgb(255 255 255 / 5%);
|
||||
--docusaurus-collapse-button-bg-hover: rgb(255 255 255 / 10%);
|
||||
}
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.collapseSidebarButton {
|
||||
display: block !important;
|
||||
background-color: var(--docusaurus-collapse-button-bg);
|
||||
height: 40px;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--ifm-toc-border-color);
|
||||
}
|
||||
|
||||
.collapseSidebarButtonIcon {
|
||||
transform: rotate(180deg);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
[dir='rtl'] .collapseSidebarButtonIcon {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.collapseSidebarButton:hover,
|
||||
.collapseSidebarButton:focus {
|
||||
background-color: var(--docusaurus-collapse-button-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.collapseSidebarButton {
|
||||
display: none;
|
||||
margin: 0;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, { useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { ThemeClassNames } from '@docusaurus/theme-common'
|
||||
import {
|
||||
useAnnouncementBar,
|
||||
useScrollPosition,
|
||||
} from '@docusaurus/theme-common/internal'
|
||||
import { translate } from '@docusaurus/Translate'
|
||||
import DocSidebarItems from '@theme/DocSidebarItems'
|
||||
import styles from './styles.module.css'
|
||||
function useShowAnnouncementBar() {
|
||||
const { isActive } = useAnnouncementBar()
|
||||
const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive)
|
||||
useScrollPosition(
|
||||
({ scrollY }) => {
|
||||
if (isActive) {
|
||||
setShowAnnouncementBar(scrollY === 0)
|
||||
}
|
||||
},
|
||||
[isActive],
|
||||
)
|
||||
return isActive && showAnnouncementBar
|
||||
}
|
||||
export default function DocSidebarDesktopContent({ path, sidebar, className }) {
|
||||
const showAnnouncementBar = useShowAnnouncementBar()
|
||||
return (
|
||||
<nav
|
||||
aria-label={translate({
|
||||
id: 'theme.docs.sidebar.navAriaLabel',
|
||||
message: 'Docs sidebar',
|
||||
description: 'The ARIA label for the sidebar navigation',
|
||||
})}
|
||||
className={clsx(
|
||||
'menu thin-scrollbar',
|
||||
styles.menu,
|
||||
showAnnouncementBar && styles.menuWithAnnouncementBar,
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
|
||||
<DocSidebarItems items={sidebar} activePath={path} level={1} />
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
@media (min-width: 997px) {
|
||||
.menu {
|
||||
flex-grow: 1;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
@supports (scrollbar-gutter: stable) {
|
||||
.menu {
|
||||
padding: 0.5rem 0 0.5rem 0.5rem;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
}
|
||||
|
||||
.menuWithAnnouncementBar {
|
||||
margin-bottom: var(--docusaurus-announcement-bar-height);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { useThemeConfig } from '@docusaurus/theme-common'
|
||||
import Logo from '@theme/Logo'
|
||||
import Content from '@theme/DocSidebar/Desktop/Content'
|
||||
import styles from './styles.module.css'
|
||||
function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }) {
|
||||
const {
|
||||
navbar: { hideOnScroll },
|
||||
docs: {
|
||||
sidebar: { hideable },
|
||||
},
|
||||
} = useThemeConfig()
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
styles.sidebar,
|
||||
hideOnScroll && styles.sidebarWithHideableNavbar,
|
||||
isHidden && styles.sidebarHidden,
|
||||
)}
|
||||
>
|
||||
{hideOnScroll && <Logo tabIndex={-1} className={styles.sidebarLogo} />}
|
||||
<Content path={path} sidebar={sidebar} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(DocSidebarDesktop)
|
||||
@@ -0,0 +1,37 @@
|
||||
@media (min-width: 997px) {
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding-top: var(--ifm-navbar-height);
|
||||
width: var(--doc-sidebar-width);
|
||||
}
|
||||
|
||||
.sidebarWithHideableNavbar {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.sidebarHidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.sidebarLogo {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
margin: 0 var(--ifm-navbar-padding-horizontal);
|
||||
min-height: var(--ifm-navbar-height);
|
||||
max-height: var(--ifm-navbar-height);
|
||||
color: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.sidebarLogo img {
|
||||
margin-right: 0.5rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebarLogo {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import { useWindowSize } from '@docusaurus/theme-common'
|
||||
import DocSidebarDesktop from '@theme/DocSidebar/Desktop'
|
||||
import DocSidebarMobile from '@theme/DocSidebar/Mobile'
|
||||
export default function DocSidebar(props) {
|
||||
const windowSize = useWindowSize()
|
||||
// Desktop sidebar visible on hydration: need SSR rendering
|
||||
const shouldRenderSidebarDesktop =
|
||||
windowSize === 'desktop' || windowSize === 'ssr'
|
||||
// Mobile sidebar not visible on hydration: can avoid SSR rendering
|
||||
const shouldRenderSidebarMobile = windowSize === 'mobile'
|
||||
return (
|
||||
<>
|
||||
{shouldRenderSidebarDesktop && <DocSidebarDesktop {...props} />}
|
||||
{shouldRenderSidebarMobile && <DocSidebarMobile {...props} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
ThemeClassNames,
|
||||
useThemeConfig,
|
||||
usePrevious,
|
||||
Collapsible,
|
||||
useCollapsible,
|
||||
} from '@docusaurus/theme-common'
|
||||
import {
|
||||
isActiveSidebarItem,
|
||||
findFirstCategoryLink,
|
||||
useDocSidebarItemsExpandedState,
|
||||
isSamePath,
|
||||
} from '@docusaurus/theme-common/internal'
|
||||
import Link from '@docusaurus/Link'
|
||||
import { translate } from '@docusaurus/Translate'
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser'
|
||||
import DocSidebarItems from '@theme/DocSidebarItems'
|
||||
import { ArrowDownIcon, ArrowUpIcon } from '@acid-info/lsd-react'
|
||||
|
||||
// If we navigate to a category and it becomes active, it should automatically
|
||||
// expand itself
|
||||
function useAutoExpandActiveCategory({ isActive, collapsed, updateCollapsed }) {
|
||||
const wasActive = usePrevious(isActive)
|
||||
useEffect(() => {
|
||||
const justBecameActive = isActive && !wasActive
|
||||
if (justBecameActive && collapsed) {
|
||||
updateCollapsed(false)
|
||||
}
|
||||
}, [isActive, wasActive, collapsed, updateCollapsed])
|
||||
}
|
||||
/**
|
||||
* When a collapsible category has no link, we still link it to its first child
|
||||
* during SSR as a temporary fallback. This allows to be able to navigate inside
|
||||
* the category even when JS fails to load, is delayed or simply disabled
|
||||
* React hydration becomes an optional progressive enhancement
|
||||
* see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188
|
||||
* see https://github.com/facebook/docusaurus/issues/3030
|
||||
*/
|
||||
|
||||
function useCategoryHrefWithSSRFallback(item) {
|
||||
const isBrowser = useIsBrowser()
|
||||
return useMemo(() => {
|
||||
if (item.href) {
|
||||
return item.href
|
||||
}
|
||||
// In these cases, it's not necessary to render a fallback
|
||||
// We skip the "findFirstCategoryLink" computation
|
||||
if (isBrowser || !item.collapsible) {
|
||||
return undefined
|
||||
}
|
||||
return findFirstCategoryLink(item)
|
||||
}, [item, isBrowser])
|
||||
}
|
||||
|
||||
function CollapseButton({ categoryLabel, onClick }) {
|
||||
return (
|
||||
<button
|
||||
aria-label={translate(
|
||||
{
|
||||
id: 'theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel',
|
||||
message: "Toggle the collapsible sidebar category '{label}'",
|
||||
description:
|
||||
'The ARIA label to toggle the collapsible sidebar category',
|
||||
},
|
||||
{ label: categoryLabel },
|
||||
)}
|
||||
type="button"
|
||||
className="clean-btn menu__caret"
|
||||
onClick={onClick}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DocSidebarItemCategory({
|
||||
item,
|
||||
onItemClick,
|
||||
activePath,
|
||||
level,
|
||||
index,
|
||||
...props
|
||||
}) {
|
||||
const { items, label, collapsible, className, href } = item
|
||||
const {
|
||||
docs: {
|
||||
sidebar: { autoCollapseCategories },
|
||||
},
|
||||
} = useThemeConfig()
|
||||
const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item)
|
||||
const isActive = isActiveSidebarItem(item, activePath)
|
||||
const isCurrentPage = isSamePath(href, activePath)
|
||||
|
||||
const { collapsed, setCollapsed } = useCollapsible({
|
||||
// Active categories are always initialized as expanded. The default
|
||||
// (`item.collapsed`) is only used for non-active categories.
|
||||
initialState: () => {
|
||||
if (!collapsible) {
|
||||
return false
|
||||
}
|
||||
return isActive ? false : item.collapsed
|
||||
},
|
||||
})
|
||||
|
||||
const { expandedItem, setExpandedItem } = useDocSidebarItemsExpandedState()
|
||||
|
||||
// Use this instead of `setCollapsed`, because it is also reactive
|
||||
const updateCollapsed = (toCollapsed = !collapsed) => {
|
||||
setExpandedItem(toCollapsed ? null : index)
|
||||
setCollapsed(toCollapsed)
|
||||
}
|
||||
|
||||
useAutoExpandActiveCategory({ isActive, collapsed, updateCollapsed })
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
collapsible &&
|
||||
expandedItem != null &&
|
||||
expandedItem !== index &&
|
||||
autoCollapseCategories
|
||||
) {
|
||||
setCollapsed(true)
|
||||
}
|
||||
}, [collapsible, expandedItem, index, setCollapsed, autoCollapseCategories])
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx(
|
||||
ThemeClassNames.docs.docSidebarItemCategory,
|
||||
ThemeClassNames.docs.docSidebarItemCategoryLevel(level),
|
||||
'menu__list-item',
|
||||
{
|
||||
'menu__list-item--collapsed': collapsed,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx('menu__list-item-collapsible', {
|
||||
'menu__list-item-collapsible--active': isCurrentPage,
|
||||
})}
|
||||
>
|
||||
<Link
|
||||
className={clsx('menu__link', {
|
||||
'menu__link--sublist': collapsible,
|
||||
'menu__link--active': isActive,
|
||||
})}
|
||||
onClick={
|
||||
collapsible
|
||||
? (e) => {
|
||||
onItemClick?.(item)
|
||||
if (href) {
|
||||
updateCollapsed(false)
|
||||
} else {
|
||||
e.preventDefault()
|
||||
updateCollapsed()
|
||||
}
|
||||
}
|
||||
: () => {
|
||||
onItemClick?.(item)
|
||||
}
|
||||
}
|
||||
aria-current={isCurrentPage ? 'page' : undefined}
|
||||
aria-expanded={collapsible ? !collapsed : undefined}
|
||||
href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
|
||||
{...props}
|
||||
>
|
||||
{label}
|
||||
{collapsed ? <ArrowUpIcon /> : <ArrowDownIcon />}
|
||||
</Link>
|
||||
{href && collapsible && (
|
||||
<CollapseButton
|
||||
categoryLabel={label}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
updateCollapsed()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
|
||||
<DocSidebarItems
|
||||
items={items}
|
||||
tabIndex={collapsed ? -1 : 0}
|
||||
onItemClick={onItemClick}
|
||||
activePath={activePath}
|
||||
level={level + 1}
|
||||
/>
|
||||
</Collapsible>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user