diff options
| author | Mohamed Bassem <me@mbassem.com> | 2026-02-01 10:44:36 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2026-02-01 10:44:54 +0000 |
| commit | 453331c629cfb22aed42876a88eb0d2ce16c8f44 (patch) | |
| tree | 9af83b4f036e9170f64b95a836d7420b5cc6b933 /docs/src | |
| parent | 4bc1b90f9f0de41851cb4288d4614edc35b63144 (diff) | |
| download | karakeep-453331c629cfb22aed42876a88eb0d2ce16c8f44.tar.zst | |
docs: replace emojis with icon in category names
Diffstat (limited to 'docs/src')
| -rw-r--r-- | docs/src/css/custom.css | 1 | ||||
| -rw-r--r-- | docs/src/theme/DocSidebarItem/Category/index.tsx | 270 |
2 files changed, 271 insertions, 0 deletions
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 9cfc2a44..573d7e39 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -593,6 +593,7 @@ li::marker { /* ============================================ API Docs Specific Styles ============================================ */ + .api-method > .menu__link { align-items: center; justify-content: start; 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> + ); +} |
