From 118c9b0bd22c89f94438c9272ed22bd316e93787 Mon Sep 17 00:00:00 2001 From: Kushal Rathod Date: Sun, 14 Jun 2026 23:59:31 +0530 Subject: [PATCH 1/3] feat(docs): add breadcrumb navigation to documentation pages --- components/Docs/DocsClient.tsx | 2 + components/Docs/docsMain/docsBreadcrumbs.tsx | 147 +++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 components/Docs/docsMain/docsBreadcrumbs.tsx diff --git a/components/Docs/DocsClient.tsx b/components/Docs/DocsClient.tsx index f94ec0ae02..4a00f5b4d3 100644 --- a/components/Docs/DocsClient.tsx +++ b/components/Docs/DocsClient.tsx @@ -1,6 +1,7 @@ 'use client'; import { useDocsNavigation } from 'components/Docs/DocsNavigationContext'; +import DocsBreadcrumbs from 'components/Docs/docsMain/docsBreadcrumbs'; import MainDocsBodyHeader from 'components/Docs/docsMain/docsMainBody'; import TocOverflowButton from 'components/Docs/docsMain/tocOverflowButton'; import ToC from 'components/Docs/toc'; @@ -87,6 +88,7 @@ export default function DocsClient({ !DocumentationData?.tocIsHidden ? 'xl:col-span-1' : '' }`} > + string | null, + currentPath: BreadcrumbItem[] = [] +): BreadcrumbItem[] | null => { + for (const item of items) { + const normalizedItemSlug = normalizeSlug(item.slug); + const newPath = [...currentPath, { title: item.title, slug: item.slug, originalSlug: normalizedItemSlug }]; + + if (normalizedItemSlug === targetContentId) { + return newPath; + } + + if (item.items && item.items.length > 0) { + const childPath = findNavigationPathWithSlugs(item.items, targetContentId, normalizeSlug, newPath); + if (childPath) { + return childPath; + } + } + } + + return null; +}; + +const DocsBreadcrumbs = ({ locale }: { locale: Locale }) => { + const { NavigationDocsData, NavigationLearnData } = useNavigationData(); + const { learnActive } = useDocsNavigation(); + const navigationItems = learnActive ? NavigationLearnData?.data : NavigationDocsData?.data; + + const currentPath = typeof window !== 'undefined' ? window.location.pathname : ''; + + // Convert pathname to content ID format + const convertPathToId = (pathname: string, locale: Locale): string | null => { + let path = pathname; + if (locale === 'zh' && pathname.startsWith('/zh/')) { + path = pathname.substring(3); + } else if (pathname.startsWith('/docs/')) { + path = pathname.substring(6); + } + + if (!path || path === '/') return null; + + path = path.replace(/\/$/, ''); + + return `content/docs/${path}.mdx`; + }; + + const contentId = convertPathToId(currentPath, locale); + + if (!contentId || !navigationItems) { + return null; + } + + // Helper to normalize slugs for comparison + const normalizeSlug = (slug: string | undefined): string | null => { + if (!slug) return null; + + // If it's already in content/docs/ format + if (slug.startsWith('content/docs/')) { + return slug; + } + + // If it's a URL slug format (starts with /) + if (slug.startsWith('/')) { + // Remove leading / and trailing / if present + let path = slug; + if (path.startsWith('/')) { + path = path.substring(1); + } + if (path.endsWith('/')) { + path = path.substring(0, path.length - 1); + } + // Slugs already have /docs/ prefix, so use content/ + path + return `content/${path}.mdx`; + } + + return slug; + }; + + const breadcrumbPath = findNavigationPathWithSlugs(navigationItems, contentId, normalizeSlug); + if (!breadcrumbPath || breadcrumbPath.length < 1) { + return null; + } + + return ( + + ); +}; + +export default DocsBreadcrumbs; From d1d70e04b7f45e9ef2752667d12892723fac3f76 Mon Sep 17 00:00:00 2001 From: Kushal Rathod Date: Thu, 2 Jul 2026 17:49:45 +0530 Subject: [PATCH 2/3] refactor(docs): simplify breadcrumbs matching and resolve parent category links --- components/Docs/docsMain/docsBreadcrumbs.tsx | 167 +++++++++---------- 1 file changed, 81 insertions(+), 86 deletions(-) diff --git a/components/Docs/docsMain/docsBreadcrumbs.tsx b/components/Docs/docsMain/docsBreadcrumbs.tsx index e197b06589..932dbe2294 100644 --- a/components/Docs/docsMain/docsBreadcrumbs.tsx +++ b/components/Docs/docsMain/docsBreadcrumbs.tsx @@ -1,139 +1,134 @@ 'use client'; import { useNavigationData } from 'components/Docs/DocsLayoutClient'; -import { buildDocLinkSlug } from 'utils/i18n/buildLinkSlug'; -import type { Locale } from 'utils/i18n/localeRouteConfig'; import { useDocsNavigation } from 'components/Docs/DocsNavigationContext'; import React from 'react'; interface BreadcrumbItem { title: string; slug?: string; - originalSlug?: string; } -// Recursively create path -const findNavigationPathWithSlugs = ( +const cleanPath = (path: string | undefined): string => { + return path ? path.replace(/\/+$/, '').replace(/^\/+/, '') : ''; +}; + +const matchSlug = ( + itemSlug: string | undefined, + currentPath: string, +): boolean => { + if (!itemSlug) return false; + return cleanPath(itemSlug) === cleanPath(currentPath); +}; + +const getFirstChildSlug = (item: any): string | undefined => { + if (!item) return undefined; + if (item.slug) return item.slug; + if (item.items && item.items.length > 0) { + for (const child of item.items) { + const slug = getFirstChildSlug(child); + if (slug) { + return slug; + } + } + } + return undefined; +}; + +const findNavigationPath = ( items: any[], - targetContentId: string, - normalizeSlug: (slug: string | undefined) => string | null, - currentPath: BreadcrumbItem[] = [] + currentPath: string, + currentBreadcrumbs: BreadcrumbItem[] = [], ): BreadcrumbItem[] | null => { + if (!items) { + return null; + } for (const item of items) { - const normalizedItemSlug = normalizeSlug(item.slug); - const newPath = [...currentPath, { title: item.title, slug: item.slug, originalSlug: normalizedItemSlug }]; - - if (normalizedItemSlug === targetContentId) { - return newPath; + if (!item) { + continue; } - + const isMatch = matchSlug(item.slug, currentPath); + const resolvedSlug = item.slug || getFirstChildSlug(item); + const newPath = [ + ...currentBreadcrumbs, + { title: item.title || '', slug: resolvedSlug }, + ]; + + if (isMatch) return newPath; + if (item.items && item.items.length > 0) { - const childPath = findNavigationPathWithSlugs(item.items, targetContentId, normalizeSlug, newPath); + const childPath = findNavigationPath(item.items, currentPath, newPath); if (childPath) { return childPath; } } } - + return null; }; -const DocsBreadcrumbs = ({ locale }: { locale: Locale }) => { +const DocsBreadcrumbs = () => { const { NavigationDocsData, NavigationLearnData } = useNavigationData(); const { learnActive } = useDocsNavigation(); - const navigationItems = learnActive ? NavigationLearnData?.data : NavigationDocsData?.data; - - const currentPath = typeof window !== 'undefined' ? window.location.pathname : ''; - - // Convert pathname to content ID format - const convertPathToId = (pathname: string, locale: Locale): string | null => { - let path = pathname; - if (locale === 'zh' && pathname.startsWith('/zh/')) { - path = pathname.substring(3); - } else if (pathname.startsWith('/docs/')) { - path = pathname.substring(6); - } - - if (!path || path === '/') return null; - - path = path.replace(/\/$/, ''); - - return `content/docs/${path}.mdx`; - }; - - const contentId = convertPathToId(currentPath, locale); - - if (!contentId || !navigationItems) { - return null; - } + const navigationItems = learnActive + ? NavigationLearnData?.data + : NavigationDocsData?.data; - // Helper to normalize slugs for comparison - const normalizeSlug = (slug: string | undefined): string | null => { - if (!slug) return null; - - // If it's already in content/docs/ format - if (slug.startsWith('content/docs/')) { - return slug; - } - - // If it's a URL slug format (starts with /) - if (slug.startsWith('/')) { - // Remove leading / and trailing / if present - let path = slug; - if (path.startsWith('/')) { - path = path.substring(1); - } - if (path.endsWith('/')) { - path = path.substring(0, path.length - 1); - } - // Slugs already have /docs/ prefix, so use content/ + path - return `content/${path}.mdx`; - } - - return slug; - }; + const currentPath = + typeof window !== 'undefined' ? window.location.pathname : ''; + if (!navigationItems) return null; - const breadcrumbPath = findNavigationPathWithSlugs(navigationItems, contentId, normalizeSlug); - if (!breadcrumbPath || breadcrumbPath.length < 1) { - return null; - } + const breadcrumbPath = findNavigationPath(navigationItems, currentPath); + if (!breadcrumbPath || breadcrumbPath.length < 1) return null; return ( -