aboutsummaryrefslogtreecommitdiffstats
path: root/docs/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--docs/src/css/custom.css716
-rw-r--r--docs/src/theme/DocSidebarItem/Category/index.tsx270
2 files changed, 961 insertions, 25 deletions
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index 2bc6a4cf..573d7e39 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -1,30 +1,696 @@
/**
- * Any CSS included here will be global. The classic template
- * bundles Infima by default. Infima is a CSS framework designed to
- * work well for content-centric websites.
+ * Modern Docusaurus theme
+ * Clean, minimal aesthetics with excellent dark mode support
*/
-/* You can override the default Infima variables here. */
+/* Import Inter font for modern typography */
+@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap");
+
+/* ============================================
+ CSS Custom Properties (Light Mode)
+ ============================================ */
:root {
- --ifm-color-primary: #2e8555;
- --ifm-color-primary-dark: #29784c;
- --ifm-color-primary-darker: #277148;
- --ifm-color-primary-darkest: #205d3b;
- --ifm-color-primary-light: #33925d;
- --ifm-color-primary-lighter: #359962;
- --ifm-color-primary-lightest: #3cad6e;
- --ifm-code-font-size: 95%;
- --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
-}
-
-/* For readability concerns, you should choose a lighter palette in dark mode. */
-[data-theme='dark'] {
- --ifm-color-primary: #25c2a0;
- --ifm-color-primary-dark: #21af90;
- --ifm-color-primary-darker: #1fa588;
- --ifm-color-primary-darkest: #1a8870;
- --ifm-color-primary-light: #29d5b0;
- --ifm-color-primary-lighter: #32d8b4;
- --ifm-color-primary-lightest: #4fddbf;
- --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
+ /* Primary brand colors - modern blue/indigo */
+ --ifm-color-primary: #6366f1;
+ --ifm-color-primary-dark: #4f46e5;
+ --ifm-color-primary-darker: #4338ca;
+ --ifm-color-primary-darkest: #3730a3;
+ --ifm-color-primary-light: #818cf8;
+ --ifm-color-primary-lighter: #a5b4fc;
+ --ifm-color-primary-lightest: #c7d2fe;
+
+ /* Background colors */
+ --ifm-background-color: #ffffff;
+ --ifm-background-surface-color: #fafafa;
+ --docs-color-border: #e5e7eb;
+
+ /* Text colors */
+ --ifm-font-color-base: #111827;
+ --ifm-font-color-secondary: #6b7280;
+ --ifm-heading-color: #111827;
+
+ /* Typography */
+ --ifm-font-family-base: "Inter", -apple-system, BlinkMacSystemFont,
+ "Segoe UI", Roboto, Oxygen, Ubuntu, sans-serif;
+ --ifm-font-family-monospace: "JetBrains Mono", "Fira Code", "SF Mono", Monaco,
+ Consolas, monospace;
+ --ifm-font-size-base: 16px;
+ --ifm-line-height-base: 1.7;
+ --ifm-heading-font-weight: 600;
+
+ /* Spacing */
+ --ifm-spacing-horizontal: 1.5rem;
+ --ifm-global-radius: 0.5rem;
+
+ /* Code */
+ --ifm-code-font-size: 0.875rem;
+ --ifm-code-padding-vertical: 0.1rem;
+ --ifm-code-padding-horizontal: 0.375rem;
+ --ifm-code-background: #f3f4f6;
+ --docusaurus-highlighted-code-line-bg: rgba(99, 102, 241, 0.1);
+
+ /* Navbar */
+ --ifm-navbar-background-color: rgba(255, 255, 255, 1);
+ --ifm-navbar-shadow: none;
+ --ifm-navbar-height: 4rem;
+ --ifm-navbar-padding-horizontal: 1.5rem;
+ --ifm-navbar-padding-vertical: 0;
+
+ /* Sidebar */
+ --doc-sidebar-width: 280px;
+ --ifm-menu-color: #4b5563;
+ --ifm-menu-color-active: var(--ifm-color-primary);
+ --ifm-menu-color-background-active: rgba(99, 102, 241, 0.08);
+ --ifm-menu-color-background-hover: rgba(0, 0, 0, 0.03);
+
+ /* Table of contents */
+ --ifm-toc-border-color: var(--docs-color-border);
+
+ /* Cards and containers */
+ --ifm-card-background-color: #ffffff;
+ --ifm-alert-border-left-width: 0;
+ --ifm-alert-border-radius: 0.75rem;
+ --ifm-alert-padding-horizontal: 1.25rem;
+ --ifm-alert-padding-vertical: 1rem;
+
+ /* Transitions */
+ --transition-fast: 150ms ease;
+ --transition-normal: 200ms ease;
+}
+
+/* ============================================
+ Dark Mode
+ ============================================ */
+[data-theme="dark"] {
+ /* Primary brand colors - brighter for dark mode */
+ --ifm-color-primary: #818cf8;
+ --ifm-color-primary-dark: #6366f1;
+ --ifm-color-primary-darker: #4f46e5;
+ --ifm-color-primary-darkest: #4338ca;
+ --ifm-color-primary-light: #a5b4fc;
+ --ifm-color-primary-lighter: #c7d2fe;
+ --ifm-color-primary-lightest: #e0e7ff;
+
+ /* Background colors - true dark */
+ --ifm-background-color: #0a0a0a;
+ --ifm-background-surface-color: #111111;
+ --docs-color-border: #262626;
+
+ /* Text colors */
+ --ifm-font-color-base: #e5e7eb;
+ --ifm-font-color-secondary: #9ca3af;
+ --ifm-heading-color: #f9fafb;
+
+ /* Code */
+ --ifm-code-background: #1f1f1f;
+ --docusaurus-highlighted-code-line-bg: rgba(129, 140, 248, 0.15);
+
+ /* Navbar */
+ --ifm-navbar-background-color: rgba(10, 10, 10, 1);
+
+ /* Sidebar */
+ --ifm-menu-color: #a1a1aa;
+ --ifm-menu-color-background-active: rgba(129, 140, 248, 0.12);
+ --ifm-menu-color-background-hover: rgba(255, 255, 255, 0.05);
+
+ /* Cards */
+ --ifm-card-background-color: #141414;
+}
+
+/* ============================================
+ Global Styles
+ ============================================ */
+html {
+ font-feature-settings: "cv02", "cv03", "cv04", "cv11";
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ letter-spacing: -0.011em;
+}
+
+/* ============================================
+ Typography
+ ============================================ */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight: var(--ifm-heading-font-weight);
+ letter-spacing: -0.025em;
+}
+
+h1 {
+ font-size: 2.25rem;
+ line-height: 1.2;
+ margin-bottom: 1.5rem;
+}
+
+h2 {
+ font-size: 1.5rem;
+ line-height: 1.3;
+ margin-top: 2.5rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid var(--docs-color-border);
+}
+
+h3 {
+ font-size: 1.25rem;
+ line-height: 1.4;
+ margin-top: 2rem;
+}
+
+
+/* ============================================
+ Navbar
+ ============================================ */
+.navbar {
+ border-bottom: 1px solid var(--docs-color-border);
+ transition: background-color var(--transition-normal);
+}
+
+.navbar__brand {
+ margin-right: 1.5rem;
+}
+
+.navbar__title {
+ font-weight: 600;
+}
+
+.navbar__items {
+ gap: 0.25rem;
+}
+
+.navbar__item {
+ font-size: 0.875rem;
+ font-weight: 500;
+ padding: 0.5rem 0.875rem;
+ border-radius: 0.375rem;
+ transition:
+ background-color var(--transition-fast),
+ color var(--transition-fast);
+}
+
+.navbar__item:hover {
+ background-color: var(--ifm-menu-color-background-hover);
+ text-decoration: none;
+}
+
+.navbar__link--active {
+ color: var(--ifm-color-primary);
+}
+
+/* Search button styling */
+.navbar__search-input {
+ background-color: var(--ifm-background-surface-color);
+ border: 1px solid var(--docs-color-border);
+ border-radius: 0.5rem;
+ font-size: 0.875rem;
+ padding: 0.5rem 1rem 0.5rem 2.25rem;
+ transition:
+ border-color var(--transition-fast),
+ box-shadow var(--transition-fast);
+}
+
+.navbar__search-input:focus {
+ border-color: var(--ifm-color-primary);
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
+ outline: none;
+}
+
+[data-theme="dark"] .navbar__search-input:focus {
+ box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.15);
+}
+
+/* ============================================
+ Sidebar
+ ============================================ */
+.theme-doc-sidebar-container {
+ border-right: 1px solid var(--docs-color-border) !important;
+}
+
+.menu {
+ padding: 1rem 0.75rem;
+ font-size: 0.875rem;
+}
+
+.menu__list {
+ transition: height var(--transition-normal);
+}
+
+.menu__list-item {
+ margin-bottom: 0.125rem;
+}
+
+.menu__link {
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.375rem;
+ font-weight: 450;
+ transition: all var(--transition-fast);
+}
+
+.menu__link:hover:not(.menu__link--active) {
+ background-color: var(--ifm-menu-color-background-hover);
+}
+
+.menu__link--active {
+ background-color: var(--ifm-menu-color-background-active);
+ color: var(--ifm-color-primary);
+ font-weight: 500;
+}
+
+.menu__link--sublist-caret::after {
+ background-size: 1.25rem 1.25rem;
+ opacity: 0.5;
+}
+
+.theme-doc-sidebar-item-category-level-1 > .menu__list-item-collapsible {
+ font-weight: 600;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--ifm-font-color-secondary);
+}
+
+.theme-doc-sidebar-item-category-level-1
+ > .menu__list-item-collapsible
+ .menu__link {
+ font-weight: 600;
+}
+
+/* ============================================
+ Main Content Area
+ ============================================ */
+.main-wrapper {
+ background-color: var(--ifm-background-color);
+}
+
+[data-theme="dark"] .main-wrapper,
+[data-theme="dark"] .navbar,
+[data-theme="dark"] .theme-doc-sidebar-container {
+ background-color: #0a0a0a;
+}
+
+[data-theme="dark"] .navbar {
+ background-color: rgba(10, 10, 10, 0.8);
+}
+
+article {
+ max-width: 100%;
+ min-width: 0;
+}
+
+.theme-doc-markdown {
+ margin-top: 1rem;
+ max-width: 100%;
+}
+
+/* Breadcrumbs */
+.breadcrumbs {
+ margin-bottom: 1.5rem;
+}
+
+.breadcrumbs__item {
+ font-size: 0.875rem;
+}
+
+.breadcrumbs__link {
+ color: var(--ifm-font-color-secondary);
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.25rem;
+ transition: all var(--transition-fast);
+}
+
+.breadcrumbs__link:hover {
+ background-color: var(--ifm-menu-color-background-hover);
+ text-decoration: none;
+}
+
+/* ============================================
+ Code Blocks
+ ============================================ */
+code {
+ border: 1px solid var(--docs-color-border);
+ border-radius: 0.375rem;
+ font-size: var(--ifm-code-font-size);
+ padding: var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal);
+ background-color: var(--ifm-code-background);
+}
+
+pre {
+ border: 1px solid var(--docs-color-border);
+ border-radius: 0.75rem;
+ background-color: var(--ifm-code-background) !important;
+}
+
+pre code {
+ border: none;
+ padding: 0;
+ background: transparent;
+}
+
+.prism-code {
+ font-size: 0.875rem;
+ line-height: 1.6;
+}
+
+/* Code block title */
+.theme-code-block-title {
+ background-color: var(--ifm-code-background);
+ border-bottom: 1px solid var(--docs-color-border);
+ font-size: 0.8125rem;
+ font-weight: 500;
+ padding: 0.625rem 1rem;
+}
+
+/* Copy button */
+.theme-code-block button {
+ border-radius: 0.375rem;
+ transition: all var(--transition-fast);
+ opacity: 0;
+}
+
+.theme-code-block:hover button {
+ opacity: 1;
+}
+
+/* ============================================
+ Admonitions / Alerts
+ ============================================ */
+.alert {
+ border: 1px solid var(--docs-color-border);
+ box-shadow: none;
+}
+
+.alert--info {
+ --ifm-alert-background-color: rgba(99, 102, 241, 0.08);
+ --ifm-alert-border-color: rgba(99, 102, 241, 0.3);
+ border-color: var(--ifm-alert-border-color);
+}
+
+.alert--warning {
+ --ifm-alert-background-color: rgba(245, 158, 11, 0.08);
+ --ifm-alert-border-color: rgba(245, 158, 11, 0.3);
+ border-color: var(--ifm-alert-border-color);
+}
+
+.alert--danger {
+ --ifm-alert-background-color: rgba(239, 68, 68, 0.08);
+ --ifm-alert-border-color: rgba(239, 68, 68, 0.3);
+ border-color: var(--ifm-alert-border-color);
+}
+
+.alert--success {
+ --ifm-alert-background-color: rgba(34, 197, 94, 0.08);
+ --ifm-alert-border-color: rgba(34, 197, 94, 0.3);
+ border-color: var(--ifm-alert-border-color);
+}
+
+[data-theme="dark"] .alert--info {
+ --ifm-alert-background-color: rgba(129, 140, 248, 0.1);
+ --ifm-alert-border-color: rgba(129, 140, 248, 0.25);
+}
+
+[data-theme="dark"] .alert--warning {
+ --ifm-alert-background-color: rgba(251, 191, 36, 0.1);
+ --ifm-alert-border-color: rgba(251, 191, 36, 0.25);
+}
+
+[data-theme="dark"] .alert--danger {
+ --ifm-alert-background-color: rgba(248, 113, 113, 0.1);
+ --ifm-alert-border-color: rgba(248, 113, 113, 0.25);
+}
+
+[data-theme="dark"] .alert--success {
+ --ifm-alert-background-color: rgba(74, 222, 128, 0.1);
+ --ifm-alert-border-color: rgba(74, 222, 128, 0.25);
+}
+
+.admonition-heading h5 {
+ font-size: 0.875rem;
+ text-transform: uppercase;
+ letter-spacing: 0.025em;
+}
+
+/* ============================================
+ Tables
+ ============================================ */
+table {
+ display: block;
+ width: 100%;
+ border-collapse: collapse;
+ margin: 1.5rem 0;
+ font-size: 0.875rem;
+ overflow-x: auto;
+}
+
+th {
+ background-color: var(--ifm-background-surface-color);
+ font-weight: 600;
+ text-align: left;
+ padding: 0.75rem 1rem;
+ border-bottom: 2px solid var(--docs-color-border);
+}
+
+td {
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid var(--docs-color-border);
+}
+
+tr:last-child td {
+ border-bottom: none;
+}
+
+/* ============================================
+ Blockquotes
+ ============================================ */
+blockquote {
+ border-left: 3px solid var(--ifm-color-primary);
+ background-color: var(--ifm-background-surface-color);
+ margin: 1.5rem 0;
+ padding: 1rem 1.5rem;
+ border-radius: 0 0.5rem 0.5rem 0;
+ color: var(--ifm-font-color-secondary);
+}
+
+blockquote p:last-child {
+ margin-bottom: 0;
+}
+
+/* ============================================
+ Lists
+ ============================================ */
+ul,
+ol {
+ padding-left: 1.5rem;
+ margin-bottom: 1.25rem;
+}
+
+li {
+ margin-bottom: 0.5rem;
+}
+
+li::marker {
+ color: var(--ifm-font-color-secondary);
+}
+
+/* ============================================
+ Pagination
+ ============================================ */
+.pagination-nav {
+ margin-top: 3rem;
+ gap: 1rem;
+}
+
+.pagination-nav__link {
+ border: 1px solid var(--docs-color-border);
+ border-radius: 0.75rem;
+ padding: 1rem 1.25rem;
+ transition: all var(--transition-fast);
+}
+
+.pagination-nav__link:hover {
+ border-color: var(--ifm-color-primary);
+ background-color: transparent;
+ text-decoration: none;
+}
+
+.pagination-nav__sublabel {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--ifm-font-color-secondary);
+ margin-bottom: 0.25rem;
+}
+
+.pagination-nav__label {
+ font-weight: 500;
+ font-size: 0.9375rem;
+}
+
+/* ============================================
+ Scrollbar Styling
+ ============================================ */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--docs-color-border);
+ border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--ifm-font-color-secondary);
+}
+
+/* ============================================
+ Algolia Search
+ ============================================ */
+.DocSearch-Button {
+ border-radius: 0.5rem !important;
+ padding: 0 1rem !important;
+ height: 2.25rem !important;
+ background-color: var(--ifm-background-surface-color) !important;
+ border: 1px solid var(--docs-color-border) !important;
+ transition:
+ border-color var(--transition-fast),
+ box-shadow var(--transition-fast) !important;
+}
+
+.DocSearch-Button:hover {
+ border-color: var(--ifm-color-primary) !important;
+ box-shadow: none !important;
+}
+
+.DocSearch-Button-Placeholder {
+ font-size: 0.875rem !important;
+ font-weight: 400 !important;
+}
+
+.DocSearch-Button-Keys {
+ min-width: auto !important;
+ gap: 0.125rem !important;
+}
+
+.DocSearch-Button-Key {
+ background: var(--docs-color-border) !important;
+ border-radius: 0.25rem !important;
+ padding: 0.125rem 0.375rem !important;
+ font-size: 0.75rem !important;
+ box-shadow: none !important;
+ border: none !important;
+}
+
+/* ============================================
+ API Docs Specific Styles
+ ============================================ */
+
+.api-method > .menu__link {
+ align-items: center;
+ justify-content: start;
+}
+
+.openapi-tabs__code-container {
+ border-radius: 0.75rem;
+ border: 1px solid var(--docs-color-border);
+}
+
+.openapi-markdown__details {
+ border: 1px solid var(--docs-color-border);
+ border-radius: 0.5rem;
+ background-color: var(--ifm-background-surface-color);
+}
+
+/* HTTP Method badges */
+.api-method .menu__link::before,
+.openapi-method-badge {
+ border-radius: 0.25rem;
+ font-size: 0.625rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.025em;
+ padding: 0.125rem 0.375rem;
+}
+
+/* ============================================
+ Responsive Adjustments
+ ============================================ */
+@media (max-width: 996px) {
+ .navbar {
+ padding: 0 1rem;
+ }
+
+ h1 {
+ font-size: 1.875rem;
+ }
+
+ h2 {
+ font-size: 1.375rem;
+ }
+
+ .footer {
+ padding: 2rem 0;
+ }
+}
+
+@media (max-width: 576px) {
+ :root {
+ --ifm-spacing-horizontal: 1rem;
+ }
+
+ h1 {
+ font-size: 1.625rem;
+ }
+
+ .pagination-nav {
+ flex-direction: column;
+ }
+
+ .pagination-nav__link {
+ width: 100%;
+ }
+}
+
+/* ============================================
+ Smooth Animations
+ ============================================ */
+.menu__list-item-collapsible--active .menu__link--sublist-caret::after,
+.menu__list-item-collapsible .menu__link--sublist-caret::after {
+ transition: transform var(--transition-fast);
+}
+
+/* Fade in for content */
+.theme-doc-markdown > * {
+ animation: fadeIn 0.3s ease-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Reduce motion for users who prefer it */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
}
diff --git a/docs/src/theme/DocSidebarItem/Category/index.tsx b/docs/src/theme/DocSidebarItem/Category/index.tsx
new file mode 100644
index 00000000..9426a026
--- /dev/null
+++ b/docs/src/theme/DocSidebarItem/Category/index.tsx
@@ -0,0 +1,270 @@
+import React, {
+ type ComponentProps,
+ type ReactNode,
+ useEffect,
+ useMemo,
+} from 'react';
+import clsx from 'clsx';
+import {
+ ThemeClassNames,
+ useThemeConfig,
+ usePrevious,
+ Collapsible,
+ useCollapsible,
+} from '@docusaurus/theme-common';
+import {isSamePath} from '@docusaurus/theme-common/internal';
+import {
+ isActiveSidebarItem,
+ findFirstSidebarItemLink,
+ useDocSidebarItemsExpandedState,
+} from '@docusaurus/plugin-content-docs/client';
+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 {
+ Rocket,
+ Package,
+ Settings,
+ BookOpen,
+ Plug,
+ Wrench,
+ Users,
+ Code,
+ type LucideIcon,
+} from 'lucide-react';
+
+// Map category labels to Lucide icons
+const categoryIcons: Record<string, LucideIcon> = {
+ 'Getting Started': Rocket,
+ 'Installation': Package,
+ 'Configuration': Settings,
+ 'Using Karakeep': BookOpen,
+ 'Integrations': Plug,
+ 'Administration': Wrench,
+ 'Community': Users,
+ 'Development': Code,
+ 'API': Code,
+};
+
+// 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 && !item.linkUnlisted) {
+ 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 findFirstSidebarItemLink(item);
+ }, [item, isBrowser]);
+}
+
+function CollapseButton({
+ collapsed,
+ categoryLabel,
+ onClick,
+}: {
+ collapsed: boolean;
+ categoryLabel: string;
+ onClick: ComponentProps<'button'>['onClick'];
+}) {
+ return (
+ <button
+ aria-label={
+ collapsed
+ ? translate(
+ {
+ id: 'theme.DocSidebarItem.expandCategoryAriaLabel',
+ message: "Expand sidebar category '{label}'",
+ description: 'The ARIA label to expand the sidebar category',
+ },
+ {label: categoryLabel},
+ )
+ : translate(
+ {
+ id: 'theme.DocSidebarItem.collapseCategoryAriaLabel',
+ message: "Collapse sidebar category '{label}'",
+ description: 'The ARIA label to collapse the sidebar category',
+ },
+ {label: categoryLabel},
+ )
+ }
+ aria-expanded={!collapsed}
+ type="button"
+ className="clean-btn menu__caret"
+ onClick={onClick}
+ />
+ );
+}
+
+export default function DocSidebarItemCategory({
+ item,
+ onItemClick,
+ activePath,
+ level,
+ index,
+ ...props
+}: Props): ReactNode {
+ 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 (
+ <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--sublist-caret': !href && collapsible,
+ 'menu__link--active': isActive,
+ })}
+ onClick={
+ collapsible
+ ? (e) => {
+ onItemClick?.(item);
+ if (href) {
+ // When already on the category's page, we collapse it
+ // We don't use "isActive" because it would collapse the
+ // category even when we browse a children element
+ // See https://github.com/facebook/docusaurus/issues/11213
+ if (isCurrentPage) {
+ e.preventDefault();
+ updateCollapsed();
+ } else {
+ // When navigating to a new category, we always expand
+ // see https://github.com/facebook/docusaurus/issues/10854#issuecomment-2609616182
+ updateCollapsed(false);
+ }
+ } else {
+ e.preventDefault();
+ updateCollapsed();
+ }
+ }
+ : () => {
+ onItemClick?.(item);
+ }
+ }
+ aria-current={isCurrentPage ? 'page' : undefined}
+ role={collapsible && !href ? 'button' : undefined}
+ aria-expanded={collapsible && !href ? !collapsed : undefined}
+ href={collapsible ? hrefWithSSRFallback ?? '#' : hrefWithSSRFallback}
+ {...props}>
+ {(() => {
+ const Icon = categoryIcons[label];
+ if (Icon) {
+ return (
+ <span style={{display: 'flex', alignItems: 'center', gap: '0.625rem'}}>
+ <Icon size={16} strokeWidth={1.5} style={{opacity: 0.6}} />
+ {label}
+ </span>
+ );
+ }
+ return label;
+ })()}
+ </Link>
+ {href && collapsible && (
+ <CollapseButton
+ collapsed={collapsed}
+ 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>
+ );
+}