Skip to content

Commit a931856

Browse files
committed
fix: improve docs navigation and search UX
1 parent 7fa3446 commit a931856

6 files changed

Lines changed: 349 additions & 65 deletions

File tree

content/navigation-bar/docs-navigation-bar.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"title": "Introduction",
1010
"items": [
1111
{
12+
"title": "Documentation",
1213
"slug": "content/docs/index.mdx",
1314
"_template": "item"
1415
}
@@ -18,18 +19,22 @@
1819
"title": "OACP Protocol",
1920
"items": [
2021
{
22+
"title": "What is OACP?",
2123
"slug": "content/docs/oacp/what-is-oacp.mdx",
2224
"_template": "item"
2325
},
2426
{
27+
"title": "How OACP Works",
2528
"slug": "content/docs/oacp/how-it-works.mdx",
2629
"_template": "item"
2730
},
2831
{
32+
"title": "Getting Started",
2933
"slug": "content/docs/oacp/getting-started.mdx",
3034
"_template": "item"
3135
},
3236
{
37+
"title": "OACP Integration",
3338
"slug": "content/docs/oacp/non-flutter-integration.mdx",
3439
"_template": "item"
3540
}
@@ -39,14 +44,17 @@
3944
"title": "Hark",
4045
"items": [
4146
{
47+
"title": "Hark Overview",
4248
"slug": "content/docs/hark/overview.mdx",
4349
"_template": "item"
4450
},
4551
{
52+
"title": "Architecture",
4653
"slug": "content/docs/hark/architecture.mdx",
4754
"_template": "item"
4855
},
4956
{
57+
"title": "OACP Test App Demo",
5058
"slug": "content/docs/hark/demo-walkthrough.mdx",
5159
"_template": "item"
5260
}
@@ -59,18 +67,22 @@
5967
"title": "Kotlin SDK",
6068
"items": [
6169
{
70+
"title": "Quick Start",
6271
"slug": "content/docs/sdks/kotlin/quick-start.mdx",
6372
"_template": "item"
6473
},
6574
{
75+
"title": "Core Components",
6676
"slug": "content/docs/sdks/kotlin/core-components.mdx",
6777
"_template": "item"
6878
},
6979
{
80+
"title": "Async Results",
7081
"slug": "content/docs/sdks/kotlin/async-results.mdx",
7182
"_template": "item"
7283
},
7384
{
85+
"title": "API Reference",
7486
"slug": "content/docs/sdks/kotlin/api-reference.mdx",
7587
"_template": "item"
7688
}
@@ -81,6 +93,7 @@
8193
"title": "Flutter SDK (under development)",
8294
"items": [
8395
{
96+
"title": "Under development",
8497
"slug": "content/docs/sdks/flutter/overview.mdx",
8598
"_template": "item"
8699
}
@@ -91,6 +104,7 @@
91104
"title": "React Native SDK (under development)",
92105
"items": [
93106
{
107+
"title": "Under development",
94108
"slug": "content/docs/sdks/react-native/overview.mdx",
95109
"_template": "item"
96110
}
@@ -103,6 +117,7 @@
103117
"title": "Roadmap",
104118
"items": [
105119
{
120+
"title": "Roadmap",
106121
"slug": "content/docs/roadmap.mdx",
107122
"_template": "item"
108123
}

src/app/docs/layout.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { TabsLayout } from "@/components/docs/layout/tab-layout";
22
import navigationBar from "@/content/navigation-bar/docs-navigation-bar.json";
3+
import { enrichNavigationBar } from "@/utils/docs/navigation/server-navigation";
34
import type React from "react";
45

5-
export default function DocsLayout({
6+
export default async function DocsLayout({
67
children,
78
}: {
89
children: React.ReactNode;
910
}) {
11+
const enrichedNavigationBar = await enrichNavigationBar(navigationBar);
12+
1013
return (
11-
<TabsLayout props={{ children }} tinaProps={{ data: { navigationBar } }} />
14+
<TabsLayout
15+
props={{ children }}
16+
tinaProps={{ data: { navigationBar: enrichedNavigationBar } }}
17+
/>
1218
);
1319
}

src/components/navigation/navigation-items/nav-level.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,36 @@ const getEndpointSlug = (endpoint_slug: string | string[] | undefined) => {
2424
if (typeof endpoint_slug === "string") {
2525
return titleCase(endpoint_slug?.replace(/-/g, " "));
2626
}
27-
return endpoint_slug.length === 1
28-
? titleCase(endpoint_slug[0]?.replace(/-/g, " "))
27+
const validSlugs = endpoint_slug.filter(
28+
(slug): slug is string => typeof slug === "string" && slug.length > 0
29+
);
30+
return validSlugs.length === 1
31+
? titleCase(validSlugs[0].replace(/-/g, " "))
2932
: "";
3033
};
3134

35+
const formatAcronyms = (value: string) =>
36+
value
37+
.replace(/\bOacp\b/g, "OACP")
38+
.replace(/\bSdk\b/g, "SDK")
39+
.replace(/\bApi\b/g, "API")
40+
.replace(/\bLlm\b/g, "LLM")
41+
.replace(/\bNlu\b/g, "NLU");
42+
43+
const getSlugTitle = (slug: unknown) => {
44+
if (typeof slug !== "string" || slug.length === 0) return "";
45+
46+
const normalizedSlug = getUrl(slug).replace(/\/$/, "");
47+
if (normalizedSlug === "/docs") {
48+
return "Documentation";
49+
}
50+
51+
const lastSegment = normalizedSlug.split("/").filter(Boolean).pop();
52+
if (!lastSegment) return "";
53+
54+
return formatAcronyms(titleCase(lastSegment.replace(/-/g, " ")));
55+
};
56+
3257
export const NavLevel: React.FC<NavLevelProps> = ({
3358
navListElem,
3459
categoryData,
@@ -43,6 +68,7 @@ export const NavLevel: React.FC<NavLevelProps> = ({
4368
// If there is only one endpoint slug, use it as the default title
4469
// This will be used only when endpoint title is not set
4570
const defaultTitle = getEndpointSlug(endpoint_slug);
71+
const fallbackSlugTitle = getSlugTitle(categoryData.slug);
4672
const slug = getUrl(categoryData.slug).replace(/\/$/, "");
4773
const [expanded, setExpanded] = React.useState(
4874
matchActualTarget(slug || getUrl(categoryData.href), path) ||
@@ -181,6 +207,7 @@ export const NavLevel: React.FC<NavLevelProps> = ({
181207
>
182208
{categoryData.slug.title ||
183209
categoryData.title ||
210+
fallbackSlugTitle ||
184211
defaultTitle}
185212
</span>
186213
<ChevronRightIcon className="ml-2 flex-shrink-0 opacity-0 w-5 h-auto" />

src/components/search-docs/search-results.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,34 @@ interface SearchResult {
44
url: string;
55
title: string;
66
excerpt: string;
7+
sectionPath: string[];
78
}
89

910
interface SearchResultsProps {
1011
results: SearchResult[];
1112
isLoading: boolean;
1213
searchTerm: string;
14+
onSelect?: () => void;
1315
}
1416

1517
const searchResultsContainer =
16-
"absolute mt-2 p-4 z-10 py-2 max-h-[45vh] md:w-11/12 w-full mx-auto rounded-lg shadow-lg md:ml-2 left translate-x-1 overflow-y-auto bg-neutral-background";
18+
"rounded-2xl bg-neutral-surface/35 p-1 dark:bg-neutral-surface/20";
1719

1820
export function SearchResults({
1921
results,
2022
isLoading,
2123
searchTerm,
24+
onSelect,
2225
}: SearchResultsProps) {
2326
if (isLoading) {
2427
return (
2528
<div
2629
className={searchResultsContainer}
2730
data-testid="search-results-container"
2831
>
29-
<h4 className="text-brand-primary font-bold my-2">Searching docs...</h4>
32+
<h4 className="px-3 py-4 text-sm font-semibold text-brand-primary">
33+
Searching docs...
34+
</h4>
3035
</div>
3136
);
3237
}
@@ -41,13 +46,19 @@ export function SearchResults({
4146
<Link
4247
key={index}
4348
href={result.url}
44-
className="block p-2 border-b-1 border-b-gray-200 last:border-b-0 group"
49+
className="group block rounded-xl px-3 py-3 transition hover:bg-brand-primary/8"
50+
onClick={onSelect}
4551
>
52+
{result.sectionPath.length > 0 && (
53+
<p className="mb-1 text-[11px] font-semibold uppercase tracking-[0.12em] text-neutral-text-secondary">
54+
{result.sectionPath.join(" / ")}
55+
</p>
56+
)}
4657
<h3 className="font-medium text-brand-primary group-hover:text-brand-primary-hover">
4758
{result.title}
4859
</h3>
4960
<p
50-
className="mt-1 text-sm text-neutral-text"
61+
className="mt-1 text-sm text-neutral-text-secondary"
5162
// biome-ignore lint/security/noDangerouslySetInnerHtml: For Highlighting the search term, it is important to use dangerouslySetInnerHTML
5263
dangerouslySetInnerHTML={{
5364
__html: result.excerpt || "",
@@ -66,7 +77,7 @@ export function SearchResults({
6677
data-testid="search-results-container"
6778
>
6879
<div
69-
className="py-2 px-4 text-md font-inter font-semibold text-neutral-text-secondary text-bold"
80+
className="px-3 py-4 text-sm font-semibold text-neutral-text-secondary"
7081
data-testid="no-results-message"
7182
>
7283
No matching docs found.

0 commit comments

Comments
 (0)