diff --git a/src/components/Icon/Icon.tsx b/src/components/Icon/Icon.tsx
index f81ea21..703bc84 100644
--- a/src/components/Icon/Icon.tsx
+++ b/src/components/Icon/Icon.tsx
@@ -16,6 +16,7 @@ import DiscourseSvg from '@site/static/icons/discourse.svg'
import TelegramSvg from '@site/static/icons/telegram.svg'
import StatusSvg from '@site/static/icons/status.svg'
import SearchSvg from '@site/static/icons/search.svg'
+import DropdownSvg from '@site/static/icons/dropdown.svg'
type TIconProps = {
size?: 's' | 'm' | 'l'
@@ -120,3 +121,9 @@ export const IconSearch = (props: TIconProps): JSX.Element => (
)
+
+export const IconDropdown = (props: TIconProps): JSX.Element => (
+
+
+
+)
diff --git a/src/theme/DocSidebarItem/Category/index.tsx b/src/theme/DocSidebarItem/Category/index.tsx
new file mode 100644
index 0000000..3486fae
--- /dev/null
+++ b/src/theme/DocSidebarItem/Category/index.tsx
@@ -0,0 +1,215 @@
+import React, { type ComponentProps, 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 type { Props } from '@theme/DocSidebarItem/Category'
+import {
+ IconArrowLeftCircle,
+ IconArrowRightCircle,
+ IconDropdown,
+} from '@site/src/components/Icon'
+
+// If we navigate to a category and it becomes active, it should automatically
+// expand itself
+function useAutoExpandActiveCategory({
+ isActive,
+ collapsed,
+ updateCollapsed,
+}: {
+ isActive: boolean
+ collapsed: boolean
+ updateCollapsed: (b: boolean) => void
+}) {
+ 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: Props['item'],
+): string | undefined {
+ 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,
+}: {
+ categoryLabel: string
+ onClick: ComponentProps<'button'>['onClick']
+}) {
+ return (
+
+ )
+}
+
+export default function DocSidebarItemCategory({
+ item,
+ onItemClick,
+ activePath,
+ level,
+ index,
+ ...props
+}: Props): JSX.Element {
+ 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: boolean = !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 (
+
+
+ {
+ 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}
+
+
+
+
+ {href && collapsible && (
+ {
+ e.preventDefault()
+ updateCollapsed()
+ }}
+ />
+ )}
+
+
+
+
+
+
+ )
+}
diff --git a/src/theme/Navbar/Content/styles.module.scss b/src/theme/Navbar/Content/styles.module.scss
index 6b1d0e4..50f18fc 100644
--- a/src/theme/Navbar/Content/styles.module.scss
+++ b/src/theme/Navbar/Content/styles.module.scss
@@ -179,7 +179,7 @@
border-radius: 12px;
}
- transition: 0.7s ease-in-out;
+ transition: all 0.8s, opacity 0.4s;
transform: translateX(0%);
&.shifted {
diff --git a/src/theme/Navbar/index.scss b/src/theme/Navbar/index.scss
index cc8ad5b..c03137b 100644
--- a/src/theme/Navbar/index.scss
+++ b/src/theme/Navbar/index.scss
@@ -22,3 +22,49 @@
.overflow-hidden {
overflow: hidden !important;
}
+
+.navbar-sidebar__item.menu,
+.theme-doc-sidebar-menu {
+ li a,
+ .theme-doc-sidebar-item-category a {
+ padding: 0.75rem 1.125rem;
+ border-radius: 0.75rem;
+
+ font-size: 0.875rem;
+ color: #373738;
+
+ .menu__link--active {
+ background-color: #eeeef0;
+ }
+ }
+
+ .menu__list-item-collapsible {
+ border-radius: 0.75rem;
+
+ a::after {
+ display: none;
+ }
+
+ .dropdown-icon {
+ position: absolute;
+ right: 1.125rem;
+ }
+ }
+
+ li.menu__list-item {
+ .dropdown-icon {
+ transform: rotate(0deg);
+ transition: transform var(--ifm-transition-fast);
+ }
+
+ &--collapsed {
+ .dropdown-icon {
+ transform: rotate(-90deg);
+ }
+ }
+ }
+
+ li:not(:first-child) {
+ margin-top: 0.125rem;
+ }
+}